Files
tailscale/tsweb/compserve/gzip.go
James Tucker 6608b9a387 tsweb/compserve, client/web: add zstd, remove brotli for precompressed assets
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>
2026-09-17 15:23:49 -07:00

44 lines
1.0 KiB
Go

// Copyright (c) Tailscale Inc & contributors
// SPDX-License-Identifier: BSD-3-Clause
package compserve
import (
"compress/gzip"
"io"
"io/fs"
)
// Gzip serves gzip-precompressed variants ("*.gz"). It exists for
// transitional compatibility with file systems built before zstd-only
// asset generation, such as web-client-prebuilt modules published before
// cmd/build-webclient stopped writing gzip; file systems built since
// contain no .gz variants and never select it.
var Gzip = Encoding{
Token: "gzip",
Ext: ".gz",
Decompress: decompressGzip,
}
// decompressGzip wraps f in a streaming gzip decompressor, taking
// ownership of f.
func decompressGzip(f fs.File) (io.ReadCloser, error) {
zr, err := gzip.NewReader(f)
if err != nil {
f.Close()
return nil, err
}
return &gzipFile{f: f, Reader: zr}, nil
}
// gzipFile is a gzip.Reader reading from f; Close closes both.
type gzipFile struct {
f fs.File
*gzip.Reader
}
func (g *gzipFile) Close() error {
g.Reader.Close()
return g.f.Close()
}