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:

  1. It first takes the leading 2024 bytes and then compresses them, which is against the spec

  2. 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