mirror of
https://github.com/tailscale/tailscale.git
synced 2026-09-21 02:57:35 -04:00
Adds tsweb/compserve: content negotiation for precompressed static variants, with a transcode-to-identity fallback for clients that do not accept an encoding (including when the raw file is absent), and CompressWriter, which live-compresses dynamic responses with zstd in its fastest mode, streamed incrementally with no buffering. Negotiation is q-value and wildcard aware (gzip;q=0 previously matched gzip). client/web serves its prebuilt embedded assets through compserve, replacing brotli with zstd; the embedded FS is wrapped in tsweb/vcstime for conditional-request mod times. tsweb/compserve/gzip.go keeps transitional serving of gzip variants from pre-zstd file systems (such as the currently published web-client-prebuilt module): passthrough to gzip-accepting clients, transcoded to identity otherwise; it becomes inert once a zstd-only module is published. util/zstdframe gains pooled GetDecoder and GetStreamingEncoder (concurrency=1). util/precompress is now a build-time tool, generating zstd variants only. cmd/tsconnect and cmd/build-webclient consume the new precompress/compserve split. tsweb.AcceptsEncoding and tsweb/tswebutil are removed; negotiation lives in compserve and the deprecated shim had no callers. go.mod bumps web-client-prebuilt. Also fixes a transcoding bug where http.ServeContent's size probe via the promoted zstd.Decoder.WriteTo could report a zero length, serving empty bodies. Updates tailscale/corp#20099 Signed-off-by: James Tucker <james@tailscale.com>
93 lines
2.2 KiB
Go
93 lines
2.2 KiB
Go
// Copyright (c) Tailscale Inc & contributors
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
|
|
|
// The build-webclient tool generates the static resources needed for the
|
|
// web client (code at client/web).
|
|
//
|
|
// # Running
|
|
//
|
|
// Meant to be invoked from the tailscale/web-client-prebuilt repo when
|
|
// updating the production built web client assets. To run it manually,
|
|
// you can use `./tool/go run ./misc/build-webclient`
|
|
package main
|
|
|
|
import (
|
|
"flag"
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
|
|
"tailscale.com/util/precompress"
|
|
)
|
|
|
|
var (
|
|
outDir = flag.String("outDir", "build/", "path to output directory")
|
|
)
|
|
|
|
func main() {
|
|
flag.Parse()
|
|
|
|
// The toolDir flag is relative to the current working directory,
|
|
// so we need to resolve it to an absolute path.
|
|
toolDir, err := filepath.Abs("./tool")
|
|
if err != nil {
|
|
log.Fatalf("Cannot resolve tool-dir: %v", err)
|
|
}
|
|
|
|
if err := build(toolDir, "client/web"); err != nil {
|
|
log.Fatalf("%v", err)
|
|
}
|
|
}
|
|
|
|
func build(toolDir, appDir string) error {
|
|
if err := os.Chdir(appDir); err != nil {
|
|
return fmt.Errorf("Cannot change cwd: %w", err)
|
|
}
|
|
|
|
if err := yarn(toolDir); err != nil {
|
|
return fmt.Errorf("install failed: %w", err)
|
|
}
|
|
|
|
if err := yarn(toolDir, "lint"); err != nil {
|
|
return fmt.Errorf("lint failed: %w", err)
|
|
}
|
|
|
|
if err := yarn(toolDir, "build", "--outDir="+*outDir, "--emptyOutDir"); err != nil {
|
|
return fmt.Errorf("build failed: %w", err)
|
|
}
|
|
|
|
var compressedFiles []string
|
|
if err := precompress.PrecompressDir(*outDir, precompress.Options{
|
|
ProgressFn: func(path string) {
|
|
log.Printf("Pre-compressing %v\n", path)
|
|
compressedFiles = append(compressedFiles, path)
|
|
},
|
|
}); err != nil {
|
|
return fmt.Errorf("Cannot precompress: %w", err)
|
|
}
|
|
|
|
// Cleanup: the original (uncompressed) files are not embedded; only the
|
|
// ".zst" variants are served.
|
|
for _, f := range compressedFiles {
|
|
if err := os.Remove(f); err != nil {
|
|
log.Printf("Failed to cleanup %q: %v", f, err)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func yarn(toolDir string, args ...string) error {
|
|
args = append([]string{"--silent", "--non-interactive"}, args...)
|
|
return run(filepath.Join(toolDir, "yarn"), args...)
|
|
}
|
|
|
|
func run(name string, args ...string) error {
|
|
cmd := exec.Command(name, args...)
|
|
cmd.Stdout = os.Stdout
|
|
cmd.Stderr = os.Stderr
|
|
return cmd.Run()
|
|
}
|