-
Notifications
You must be signed in to change notification settings - Fork 2k
Description
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") = []