What happened:
The following two requests work without issue
wget -S --header="accept-encoding: gzip" http://localhost:8080/assets/logo.svg
wget -S --header="Range: bytes=0-2023" http://localhost:8080/assets/logo.svg
But this one fails and wget does retry to fetch the first chunk indefinitely
wget -S --header="accept-encoding: gzip" --header="Range: bytes=0-2023" http://localhost:8080/assets/logo.svg
The content range of the response is
content-range: bytes 0-2023/2249
but only 1038 bytes are actually sent.
I assume this is because Spring does fetch the first 2023 bytes of the file and then compresses them to 1038 bytes.
It fails to set the content range to 0-1038, like it should from what I understand from RFC7233.
What you expected to happen:
HTTP response 206 with a correct contnet-range header.
How to reproduce it:
wget -S --header="accept-encoding: gzip" --header="Range: bytes=0-2023" http://localhost:8080/assets/logo.svg
Anything else we need to know?: This issue is similar to 25976 except it happens for all resources and in a newer Spring version.
Environment: Server: Undertow Spring Boot 3.2.4 openjdk 21.0.2 2024-01-16 LTS OpenJDK Runtime Environment Temurin-21.0.2+13 (build 21.0.2+13-LTS) OpenJDK 64-Bit Server VM Temurin-21.0.2+13 (build 21.0.2+13-LTS, mixed mode, sharing)
Comment From: bclozel
Could you elaborate on how you are configuring the gzip compression of resources in your application?
Comment From: TheRealArlie
Hi @bclozel , thank you for your fast reply.
We just configured it in our application.yml:
...
server:
shutdown: graceful
compression:
enabled: true
mime-types: text/html,text/xml,text/plain,text/css,application/javascript,application/json,image/svg+xml
min-response-size: 1024
...
Comment From: bclozel
When configuring your application with server.compression.enabled=true
, Spring Boot configures the underlying web server to use its native gzip compression support. This means that Spring Framework writes a valid range response and it's up to the server to handle compression, or not.
I've created a sample application with the following properties
server.compression.enabled=true
server.compression.mime-types=image/jpeg
With Tomcat, we're getting the following behavior:
$ http http://localhost:8080/image.jpg Accept-encoding:gzip
HTTP/1.1 200
Accept-Ranges: bytes
Connection: keep-alive
Content-Encoding: gzip
Content-Type: image/jpeg
Date: Wed, 17 Apr 2024 16:10:06 GMT
Keep-Alive: timeout=60
Last-Modified: Wed, 17 Apr 2024 16:10:00 GMT
Transfer-Encoding: chunked
Vary: origin,access-control-request-method,access-control-request-headers,accept-encoding
+-----------------------------------------+
| NOTE: binary data not shown in terminal |
+-----------------------------------------+
$ http http://localhost:8080/image.jpg Accept-encoding:gzip Range:bytes=0-2023
HTTP/1.1 206
Accept-Ranges: bytes
Connection: keep-alive
Content-Length: 2024
Content-Range: bytes 0-2023/388000
Content-Type: image/jpeg
Date: Wed, 17 Apr 2024 17:20:06 GMT
Keep-Alive: timeout=60
Last-Modified: Wed, 17 Apr 2024 16:10:00 GMT
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
+-----------------------------------------+
| NOTE: binary data not shown in terminal |
+-----------------------------------------+
The compression does not happen for range requests. With Undertow, I'm getting the following:
http http://localhost:8080/image.jpg Accept-encoding:gzip
HTTP/1.1 200 OK
Accept-Ranges: bytes
Connection: keep-alive
Content-Encoding: gzip
Content-Type: image/jpeg
Date: Wed, 17 Apr 2024 17:24:36 GMT
Last-Modified: Wed, 17 Apr 2024 16:10:00 GMT
Transfer-Encoding: chunked
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
+-----------------------------------------+
| NOTE: binary data not shown in terminal |
+-----------------------------------------+
http http://localhost:8080/image.jpg Accept-encoding:gzip Range:bytes=0-2023
HTTP/1.1 206 Partial Content
Accept-Ranges: bytes
Connection: keep-alive
Content-Length: 2024
Content-Range: bytes 0-2023/388000
Content-Type: image/jpeg
Date: Wed, 17 Apr 2024 17:24:47 GMT
Last-Modified: Wed, 17 Apr 2024 16:10:00 GMT
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
+-----------------------------------------+
| NOTE: binary data not shown in terminal |
+-----------------------------------------+
With Undertow, the response is not compressed either for content-range requests. Please provide a sample application.
Comment From: emulatalk1
I think the problem is that the Content-Length shows the wrong value because it's written before the compression process, while the actual resource remains compressed.🤔
Comment From: weinzierl
I think the problem is that the Content-Length shows the wrong value because it's written before the compression process, while the actual resource remains compressed.🤔
According to RFC2616, ranges need to be to applied the content-encoded content. In other words the image must be compressed first and then the first 2024 bytes of it must be delivered. What I observe with Spring Boot 3.4.2 and undertow is that:
-
It first takes the leading 2024 bytes and then compresses them, which is against the spec
-
It blindly sets the length to 2024 and the content-range to 0-2023, even if the delivered payload is smaller, which again is different from what the spec says.
Comment From: bclozel
@emulatalk1 @weinzierl - before chiming in here, please notice that I could not reproduce the problem described by the OP. Can you provide a sample application that reproduces the problem?
Now the range request is processed by the web Framework, and the compression happens at a lower level in the web server. Here the web server should modify the content-length accordingly, because it did apply compression without the web framework's knowledge - or it should bail out and not apply compression (which is what I'm seeing right now). Let's wait for @TheRealArlie 's feedback first.
Comment From: weinzierl
Here the web server should modify the content-length accordingly, because it did apply compression without the web framework's knowledge
I thought this as well, but digging through the standards I believe now that this would not be conformant behaviour. Standard says, ranges have to be applied after content-encoding (gzip).
- or it should bail out and not apply compression (which is what I'm seeing right now).
I did a couple of quick experiments and this is what most servers seem to do in their default config. The only notable exception is nginx (with gzip_static on; gzip on;) which ignores the range instead. Not a single one tried to do both.
Let's wait for @TheRealArlie 's feedback first.
Yep.
Comment From: TheRealArlie
Hi @bclozel, @emulatalk1, @weinzierl, thank you for your technical feedback. Unfortunately even my best developer colleagues are not able to reproduce the described buggy behavior in a minimal example. I do not know what in our code is producing this but spring in the standard config seems not to be the root course here. I would close the ticket for now until we are able to provide a minimal working example that is able to reproduce the problem.
Thank you so much and sorry for the late reply.
Comment From: bclozel
Thanks for letting us know @TheRealArlie