@@ -97,6 +97,41 @@ pub fn accepts_gzip_encoding(headers: &HeaderMap<HeaderValue>) -> bool {
9797 false
9898}
9999
100+ /// Checks if a content type is compressible.
101+ /// This is used to determine if the Vary: Accept-Encoding header should be added,
102+ /// even if compression doesn't occur for this particular request.
103+ pub fn is_compressible_content_type (
104+ response_headers : & HeaderMap < HeaderValue > ,
105+ ) -> bool {
106+ // Only compress when we know the content type
107+ let Some ( content_type) = response_headers. get ( http:: header:: CONTENT_TYPE )
108+ else {
109+ return false ;
110+ } ;
111+ let Ok ( ct_str) = content_type. to_str ( ) else {
112+ return false ;
113+ } ;
114+
115+ let ct_lower = ct_str. to_ascii_lowercase ( ) ;
116+
117+ // SSE streams prioritize latency over compression
118+ if ct_lower. starts_with ( "text/event-stream" ) {
119+ return false ;
120+ }
121+
122+ let is_compressible = ct_lower. starts_with ( "application/json" )
123+ || ct_lower. starts_with ( "text/" )
124+ || ct_lower. starts_with ( "application/xml" )
125+ || ct_lower. starts_with ( "application/javascript" )
126+ || ct_lower. starts_with ( "application/x-javascript" ) ;
127+
128+ // RFC 6839 structured syntax suffixes (+json, +xml)
129+ let has_compressible_suffix =
130+ ct_lower. contains ( "+json" ) || ct_lower. contains ( "+xml" ) ;
131+
132+ is_compressible || has_compressible_suffix
133+ }
134+
100135/// Determines if a response should be compressed with gzip.
101136pub fn should_compress_response (
102137 request_method : & http:: Method ,
@@ -151,33 +186,9 @@ pub fn should_compress_response(
151186 }
152187 }
153188
154- // Only compress when we know the content type
155- let Some ( content_type) = response_headers. get ( http:: header:: CONTENT_TYPE )
156- else {
157- return false ;
158- } ;
159- let Ok ( ct_str) = content_type. to_str ( ) else {
160- return false ;
161- } ;
162-
163- let ct_lower = ct_str. to_ascii_lowercase ( ) ;
164-
165- // SSE streams prioritize latency over compression
166- if ct_lower. starts_with ( "text/event-stream" ) {
167- return false ;
168- }
169-
170- let is_compressible = ct_lower. starts_with ( "application/json" )
171- || ct_lower. starts_with ( "text/" )
172- || ct_lower. starts_with ( "application/xml" )
173- || ct_lower. starts_with ( "application/javascript" )
174- || ct_lower. starts_with ( "application/x-javascript" ) ;
175-
176- // RFC 6839 structured syntax suffixes (+json, +xml)
177- let has_compressible_suffix =
178- ct_lower. contains ( "+json" ) || ct_lower. contains ( "+xml" ) ;
179-
180- is_compressible || has_compressible_suffix
189+ // technically redundant with check outside of the call, but kept here
190+ // because it's logically part of "should compress?" question
191+ is_compressible_content_type ( response_headers)
181192}
182193
183194/// Minimum size in bytes for a response to be compressed.
@@ -222,18 +233,7 @@ pub fn apply_gzip_compression(response: Response<Body>) -> Response<Body> {
222233
223234 // Vary header is critical for caching - prevents serving compressed
224235 // responses to clients that don't accept gzip
225- let vary_has_accept_encoding = parts
226- . headers
227- . get_all ( http:: header:: VARY )
228- . iter ( )
229- . any ( header_value_contains_accept_encoding) ;
230-
231- if !vary_has_accept_encoding {
232- parts. headers . append (
233- http:: header:: VARY ,
234- HeaderValue :: from_static ( "Accept-Encoding" ) ,
235- ) ;
236- }
236+ add_vary_header ( & mut parts. headers ) ;
237237
238238 // because we're streaming, we can't handle ranges and we don't know the
239239 // length of the response ahead of time
@@ -250,6 +250,25 @@ fn header_value_contains_accept_encoding(value: &HeaderValue) -> bool {
250250 } )
251251}
252252
253+ /// Adds the Vary: Accept-Encoding header to a response if not already present.
254+ /// This is critical for correct caching behavior with intermediate caches.
255+ pub fn add_vary_header ( headers : & mut HeaderMap < HeaderValue > ) {
256+ let vary_values = headers. get_all ( http:: header:: VARY ) ;
257+
258+ // can't and shouldn't add anything if we already have "*"
259+ // https://datatracker.ietf.org/doc/html/rfc7231#section-7.1.4
260+ if vary_values. iter ( ) . any ( |v| v == "*" ) {
261+ return ;
262+ }
263+
264+ if !vary_values. iter ( ) . any ( header_value_contains_accept_encoding) {
265+ headers. append (
266+ http:: header:: VARY ,
267+ HeaderValue :: from_static ( "Accept-Encoding" ) ,
268+ ) ;
269+ }
270+ }
271+
253272#[ cfg( test) ]
254273mod tests {
255274 use super :: * ;
0 commit comments