Skip to content

GzipHandler fails to set Vary header on 304 responses when client does not accept gzip encoding #9042

@markslater

Description

@markslater

Jetty version(s)
11.0.13

Java version/vendor (use: java -version)
openjdk version "11.0.17" 2022-10-18
OpenJDK Runtime Environment (build 11.0.17+8-post-Ubuntu-1ubuntu220.04)
OpenJDK 64-Bit Server VM (build 11.0.17+8-post-Ubuntu-1ubuntu220.04, mixed mode, sharing)

OS type/version
Linux 5.4.0-120-generic

Description
#8906 improves the handling of 304 responses by the GzipHandler by setting the vary header on 304 responses that would have had that same header set on a 200 response. It works by identifying that an ETag sent by the client has a signature implying it was generated by the GzipHandler in the first place. However (and I feel a little guilty here as the implementation was my suggestion in #8905), this doesn't work when GzipHandler returns an uncompressed response due to a client not accepting gzip encoding. In this case, GzipHandler correctly adds a vary header to a 200 response, but because the resultant ETag has no identifying marks added to it, a subsequent 304 response doesn't have the vary header set.

Unfortunately, I didn't realise this until I tried 11.0.13 today :(

How to reproduce?

Use the following class:

package com.example;

import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.gzip.GzipHandler;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;

import java.io.IOException;
import java.io.Writer;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.http.HttpResponse.BodyHandlers;
import java.util.UUID;

public class GzipETagServer {
    public static void main(String[] args) throws Exception {
        final Server server = new Server(8080);
        final ServletContextHandler servletContextHandler = new ServletContextHandler();
        servletContextHandler.addServlet(new ServletHolder(new FooServlet()), "/foo");
        final GzipHandler gzipHandler = new GzipHandler();
        gzipHandler.setHandler(servletContextHandler);
        server.setHandler(gzipHandler);
        server.start();
        try {
            final HttpResponse<Void> httpResponse = HttpClient.newHttpClient().send(
                    HttpRequest.newBuilder(new URI("http://localhost:8080/foo")).build(),
                    BodyHandlers.discarding()
            );
            final String etag = httpResponse.headers().firstValue("etag").get();
            System.out.println("httpResponse.headers().allValues(\"vary\") = " + httpResponse.headers().allValues("vary"));
            final HttpResponse<Void> notModifiedResponse = HttpClient.newHttpClient().send(
                    HttpRequest.newBuilder(new URI("http://localhost:8080/foo")).setHeader("if-none-match", etag).build(),
                    BodyHandlers.discarding()
            );
            System.out.println("notModifiedResponse.headers().allValues(\"vary\") = " + notModifiedResponse.headers().allValues("vary"));
        } finally {
            server.stop();
        }
    }

    private static final class FooServlet extends HttpServlet {

        private final String eTag = '"' + UUID.randomUUID().toString() + '"';

        @Override
        protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
            resp.setHeader("etag", eTag);
            if (eTag.equals(req.getHeader("if-none-match"))) {
                resp.setStatus(304);
            } else {
                resp.setStatus(200);
                try (Writer responseWriter = resp.getWriter()) {
                    responseWriter.write("Foo".repeat(100));
                }
            }
        }
    }
}

Outputs:

httpResponse.headers().allValues("vary") = [Accept-Encoding]
notModifiedResponse.headers().allValues("vary") = []

Metadata

Metadata

Assignees

No one assigned

    Labels

    BugFor general bugs on Jetty sideStaleFor auto-closed stale issues and pull requests

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions