返回 DeepSeek-Reasonix
gzip.go
根目录 / internal / serve / gzip.go
1 package serve
2
3 import (
4 "bytes"
5 "compress/gzip"
6 "io"
7 "net/http"
8 "strconv"
9 "strings"
10 "sync"
11 )
12
13 const gzipThreshold = 1024
14
15 var gzipWriterPool = sync.Pool{New: func() any { return gzip.NewWriter(io.Discard) }}
16
17 // gzipMiddleware compresses sufficiently large responses for remote clients.
18 // SSE and HEAD bypass it; streaming handlers must retain their flush contract.
19 func gzipMiddleware(next http.Handler) http.Handler {
20 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
21 if r.Method == http.MethodHead || r.URL.Path == "/events" || !acceptsGzip(r.Header.Get("Accept-Encoding")) {
22 next.ServeHTTP(w, r)
23 return
24 }
25 gw := &gzipBufferedWriter{ResponseWriter: w}
26 defer gw.close()
27 next.ServeHTTP(gw, r)
28 })
29 }
30
31 func acceptsGzip(header string) bool {
32 gzipQ, wildcardQ := -1.0, -1.0
33 for part := range strings.SplitSeq(header, ",") {
34 codingPart, parameters, _ := strings.Cut(part, ";")
35 coding := strings.ToLower(strings.TrimSpace(codingPart))
36 q := 1.0
37 for parameter := range strings.SplitSeq(parameters, ";") {
38 name, value, ok := strings.Cut(parameter, "=")
39 if !ok || !strings.EqualFold(strings.TrimSpace(name), "q") {
40 continue
41 }
42 parsed, err := strconv.ParseFloat(strings.TrimSpace(value), 64)
43 if err != nil || parsed < 0 || parsed > 1 {
44 q = 0
45 } else {
46 q = parsed
47 }
48 }
49 switch coding {
50 case "gzip":
51 gzipQ = q
52 case "*":
53 wildcardQ = q
54 }
55 }
56 if gzipQ >= 0 {
57 return gzipQ > 0
58 }
59 return wildcardQ > 0
60 }
61
62 type gzipBufferedWriter struct {
63 http.ResponseWriter
64 buf bytes.Buffer
65 gz *gzip.Writer
66 started bool
67 plain bool
68 status int
69 }
70
71 func (g *gzipBufferedWriter) Unwrap() http.ResponseWriter { return g.ResponseWriter }
72
73 func (g *gzipBufferedWriter) WriteHeader(code int) {
74 if g.started || g.status != 0 {
75 return
76 }
77 g.status = code
78 }
79
80 func (g *gzipBufferedWriter) Write(p []byte) (int, error) {
81 // Establish the response type at the byte-write boundary, including writes
82 // after Flush. Explicit handler types (JSON, HTML, downloads) are preserved.
83 if g.ResponseWriter.Header().Get("Content-Type") == "" {
84 g.ResponseWriter.Header().Set("Content-Type", "text/plain; charset=utf-8")
85 }
86 g.ResponseWriter.Header().Set("X-Content-Type-Options", "nosniff")
87 if g.gz != nil {
88 return g.gz.Write(p)
89 }
90 if g.plain {
91 return g.ResponseWriter.Write(p)
92 }
93 if g.status == 0 {
94 g.status = http.StatusOK
95 }
96 if responseHasNoBody(g.status) {
97 g.startPlain()
98 return len(p), nil
99 }
100 _, _ = g.buf.Write(p)
101 if g.buf.Len() >= gzipThreshold {
102 g.start()
103 }
104 return len(p), nil
105 }
106
107 func (g *gzipBufferedWriter) start() {
108 if g.started {
109 return
110 }
111 if g.status == 0 {
112 g.status = http.StatusOK
113 }
114 if responseHasNoBody(g.status) || g.Header().Get("Content-Encoding") != "" {
115 g.startPlain()
116 return
117 }
118 h := g.Header()
119 setSafeDefaultContentType(h)
120 h.Set("Content-Encoding", "gzip")
121 h.Add("Vary", "Accept-Encoding")
122 h.Del("Content-Length")
123 g.ResponseWriter.WriteHeader(g.status)
124 g.started = true
125 g.gz = gzipWriterPool.Get().(*gzip.Writer)
126 g.gz.Reset(g.ResponseWriter)
127 _, _ = g.buf.WriteTo(g.gz)
128 }
129
130 func (g *gzipBufferedWriter) startPlain() {
131 if g.started {
132 return
133 }
134 if g.status == 0 {
135 g.status = http.StatusOK
136 }
137 if !responseHasNoBody(g.status) {
138 setSafeDefaultContentType(g.Header())
139 }
140 g.ResponseWriter.WriteHeader(g.status)
141 g.started = true
142 g.plain = true
143 if !responseHasNoBody(g.status) {
144 _, _ = g.buf.WriteTo(g.ResponseWriter)
145 } else {
146 g.buf.Reset()
147 }
148 }
149
150 func setSafeDefaultContentType(header http.Header) {
151 if header.Get("Content-Type") == "" {
152 header.Set("Content-Type", "text/plain; charset=utf-8")
153 }
154 if header.Get("X-Content-Type-Options") == "" {
155 header.Set("X-Content-Type-Options", "nosniff")
156 }
157 }
158
159 func (g *gzipBufferedWriter) Flush() {
160 if !g.started {
161 g.start()
162 }
163 if g.gz != nil {
164 _ = g.gz.Flush()
165 }
166 if flusher, ok := g.ResponseWriter.(http.Flusher); ok {
167 flusher.Flush()
168 }
169 }
170
171 func (g *gzipBufferedWriter) close() {
172 if g.gz != nil {
173 _ = g.gz.Close()
174 gzipWriterPool.Put(g.gz)
175 g.gz = nil
176 return
177 }
178 if !g.started {
179 g.startPlain()
180 }
181 }
182
183 func responseHasNoBody(status int) bool {
184 return status >= 100 && status < 200 || status == http.StatusNoContent || status == http.StatusNotModified
185 }
186
186 lines GO