diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpField.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpField.java index e04d7e2345e2..f0bf6f0c1c12 100644 --- a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpField.java +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpField.java @@ -292,6 +292,11 @@ public boolean isSameName(HttpField field) return _name.equalsIgnoreCase(field.getName()); } + public boolean is(String name) + { + return _name.equalsIgnoreCase(name); + } + private int nameHashCode() { int h = this.hash; diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpFields.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpFields.java index 019b8601fc89..e436ded5d348 100644 --- a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpFields.java +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpFields.java @@ -32,6 +32,7 @@ import java.util.Objects; import java.util.Set; import java.util.StringTokenizer; +import java.util.function.BiFunction; import java.util.function.ToIntFunction; import java.util.stream.Stream; @@ -88,6 +89,152 @@ public HttpFields(HttpFields fields) _size = fields._size; } + /** + *

Computes a single field for the given HttpHeader and for existing fields with the same header.

+ * + *

The compute function receives the field name and a list of fields with the same name + * so that their values can be used to compute the value of the field that is returned + * by the compute function. + * If the compute function returns {@code null}, the fields with the given name are removed.

+ *

This method comes handy when you want to add an HTTP header if it does not exist, + * or add a value if the HTTP header already exists, similarly to + * {@link Map#compute(Object, BiFunction)}.

+ * + *

This method can be used to {@link #put(HttpField) put} a new field (or blindly replace its value):

+ *
+     * httpFields.computeField("X-New-Header",
+     *     (name, fields) -> new HttpField(name, "NewValue"));
+     * 
+ * + *

This method can be used to coalesce many fields into one:

+ *
+     * // Input:
+     * GET / HTTP/1.1
+     * Host: localhost
+     * Cookie: foo=1
+     * Cookie: bar=2,baz=3
+     * User-Agent: Jetty
+     *
+     * // Computation:
+     * httpFields.computeField("Cookie", (name, fields) ->
+     * {
+     *     // No cookies, nothing to do.
+     *     if (fields == null)
+     *         return null;
+     *
+     *     // Coalesces all cookies.
+     *     String coalesced = fields.stream()
+     *         .flatMap(field -> Stream.of(field.getValues()))
+     *         .collect(Collectors.joining(", "));
+     *
+     *     // Returns a single Cookie header with all cookies.
+     *     return new HttpField(name, coalesced);
+     * }
+     *
+     * // Output:
+     * GET / HTTP/1.1
+     * Host: localhost
+     * Cookie: foo=1, bar=2, baz=3
+     * User-Agent: Jetty
+     * 
+ * + *

This method can be used to replace a field:

+ *
+     * httpFields.computeField("X-Length", (name, fields) ->
+     * {
+     *     if (fields == null)
+     *         return null;
+     *
+     *     // Get any value among the X-Length headers.
+     *     String length = fields.stream()
+     *         .map(HttpField::getValue)
+     *         .findAny()
+     *         .orElse("0");
+     *
+     *     // Replace X-Length headers with X-Capacity header.
+     *     return new HttpField("X-Capacity", length);
+     * });
+     * 
+ * + *

This method can be used to remove a field:

+ *
+     * httpFields.computeField("Connection", (name, fields) -> null);
+     * 
+ * + * @param header the HTTP header + * @param computeFn the compute function + */ + public void computeField(HttpHeader header, BiFunction, HttpField> computeFn) + { + computeField(header, computeFn, (f, h) -> f.getHeader() == h); + } + + /** + *

Computes a single field for the given HTTP header name and for existing fields with the same name.

+ * + * @param name the HTTP header name + * @param computeFn the compute function + * @see #computeField(HttpHeader, BiFunction) + */ + public void computeField(String name, BiFunction, HttpField> computeFn) + { + computeField(name, computeFn, HttpField::is); + } + + private void computeField(T header, BiFunction, HttpField> computeFn, BiFunction matcher) + { + // Look for first occurrence + int first = -1; + for (int i = 0; i < _size; i++) + { + HttpField f = _fields[i]; + if (matcher.apply(f, header)) + { + first = i; + break; + } + } + + // If the header is not found, add a new one; + if (first < 0) + { + HttpField newField = computeFn.apply(header, null); + if (newField != null) + add(newField); + return; + } + + // Are there any more occurrences? + List found = null; + for (int i = first + 1; i < _size; i++) + { + HttpField f = _fields[i]; + if (matcher.apply(f, header)) + { + if (found == null) + { + found = new ArrayList<>(); + found.add(_fields[first]); + } + // Remember and remove additional fields + found.add(f); + remove(i); + } + } + + // If no additional fields were found, handle singleton case + if (found == null) + found = Collections.singletonList(_fields[first]); + else + found = Collections.unmodifiableList(found); + + HttpField newField = computeFn.apply(header, found); + if (newField == null) + remove(first); + else + _fields[first] = newField; + } + public int size() { return _size; @@ -167,7 +314,7 @@ public HttpField getField(String name) for (int i = 0; i < _size; i++) { HttpField f = _fields[i]; - if (f.getName().equalsIgnoreCase(name)) + if (f.is(name)) return f; } return null; @@ -189,6 +336,22 @@ public List getFields(HttpHeader header) return fields == null ? Collections.emptyList() : fields; } + public List getFields(String name) + { + List fields = null; + for (int i = 0; i < _size; i++) + { + HttpField f = _fields[i]; + if (f.is(name)) + { + if (fields == null) + fields = new ArrayList<>(); + fields.add(f); + } + } + return fields == null ? Collections.emptyList() : fields; + } + public boolean contains(HttpField field) { for (int i = _size; i-- > 0; ) @@ -216,7 +379,7 @@ public boolean contains(String name, String value) for (int i = _size; i-- > 0; ) { HttpField f = _fields[i]; - if (f.getName().equalsIgnoreCase(name) && f.contains(value)) + if (f.is(name) && f.contains(value)) return true; } return false; @@ -238,7 +401,7 @@ public boolean containsKey(String name) for (int i = _size; i-- > 0; ) { HttpField f = _fields[i]; - if (f.getName().equalsIgnoreCase(name)) + if (f.is(name)) return true; } return false; @@ -272,7 +435,7 @@ public String get(String header) for (int i = 0; i < _size; i++) { HttpField f = _fields[i]; - if (f.getName().equalsIgnoreCase(header)) + if (f.is(header)) return f.getValue(); } return null; @@ -308,7 +471,7 @@ public List getValuesList(String name) for (int i = 0; i < _size; i++) { HttpField f = _fields[i]; - if (f.getName().equalsIgnoreCase(name)) + if (f.is(name)) { if (list == null) list = new ArrayList<>(size() - i); @@ -363,7 +526,7 @@ public boolean addCSV(String name, String... values) for (int i = 0; i < _size; i++) { HttpField f = _fields[i]; - if (f.getName().equalsIgnoreCase(name)) + if (f.is(name)) { if (existing == null) existing = new QuotedCSV(false); @@ -451,7 +614,7 @@ public List getCSV(String name, boolean keepQuotes) QuotedCSV values = null; for (HttpField f : this) { - if (f.getName().equalsIgnoreCase(name)) + if (f.is(name)) { if (values == null) values = new QuotedCSV(keepQuotes); @@ -509,7 +672,7 @@ public List getQualityCSV(String name) QuotedQualityCSV values = null; for (HttpField f : this) { - if (f.getName().equalsIgnoreCase(name)) + if (f.is(name)) { if (values == null) values = new QuotedQualityCSV(); @@ -531,7 +694,7 @@ public Enumeration getValues(final String name) { final HttpField f = _fields[i]; - if (f.getName().equalsIgnoreCase(name) && f.getValue() != null) + if (f.is(name) && f.getValue() != null) { final int first = i; return new Enumeration() @@ -547,7 +710,7 @@ public boolean hasMoreElements() while (i < _size) { field = _fields[i++]; - if (field.getName().equalsIgnoreCase(name) && field.getValue() != null) + if (field.is(name) && field.getValue() != null) return true; } field = null; @@ -761,8 +924,7 @@ public HttpField remove(HttpHeader name) if (f.getHeader() == name) { removed = f; - _size--; - System.arraycopy(_fields, i + 1, _fields, i, _size - i); + remove(i); } } return removed; @@ -780,16 +942,22 @@ public HttpField remove(String name) for (int i = _size; i-- > 0; ) { HttpField f = _fields[i]; - if (f.getName().equalsIgnoreCase(name)) + if (f.is(name)) { removed = f; - _size--; - System.arraycopy(_fields, i + 1, _fields, i, _size - i); + remove(i); } } return removed; } + private void remove(int i) + { + _size--; + System.arraycopy(_fields, i + 1, _fields, i, _size - i); + _fields[_size] = null; + } + /** * Get a header as an long value. Returns the value of an integer field or -1 if not found. The * case of the field name is ignored. @@ -1181,8 +1349,7 @@ public void remove() { if (_current < 0) throw new IllegalStateException(); - _size--; - System.arraycopy(_fields, _current + 1, _fields, _current, _size - _current); + HttpFields.this.remove(_current); _fields[_size] = null; _cursor = _current; _current = -1; diff --git a/jetty-http/src/test/java/org/eclipse/jetty/http/HttpFieldsTest.java b/jetty-http/src/test/java/org/eclipse/jetty/http/HttpFieldsTest.java index 8ad677a9f27f..2e1978d125e2 100644 --- a/jetty-http/src/test/java/org/eclipse/jetty/http/HttpFieldsTest.java +++ b/jetty-http/src/test/java/org/eclipse/jetty/http/HttpFieldsTest.java @@ -866,4 +866,32 @@ public void testStream() assertThat(header.stream().count(), is(3L)); assertThat(header.stream().map(HttpField::getName).filter("name2"::equalsIgnoreCase).count(), is(1L)); } + + @Test + public void testComputeField() + { + HttpFields header = new HttpFields(); + assertThat(header.size(), is(0)); + + header.computeField("Test", (n, f) -> null); + assertThat(header.size(), is(0)); + + header.add(new HttpField("Before", "value")); + assertThat(header.stream().map(HttpField::toString).collect(Collectors.toList()), contains("Before: value")); + + header.computeField("Test", (n, f) -> new HttpField(n, "one")); + assertThat(header.stream().map(HttpField::toString).collect(Collectors.toList()), contains("Before: value", "Test: one")); + + header.add(new HttpField("After", "value")); + assertThat(header.stream().map(HttpField::toString).collect(Collectors.toList()), contains("Before: value", "Test: one", "After: value")); + + header.add(new HttpField("Test", "extra")); + assertThat(header.stream().map(HttpField::toString).collect(Collectors.toList()), contains("Before: value", "Test: one", "After: value", "Test: extra")); + + header.computeField("Test", (n, f) -> new HttpField("TEST", "count=" + f.size())); + assertThat(header.stream().map(HttpField::toString).collect(Collectors.toList()), contains("Before: value", "TEST: count=2", "After: value")); + + header.computeField("TEST", (n, f) -> null); + assertThat(header.stream().map(HttpField::toString).collect(Collectors.toList()), contains("Before: value", "After: value")); + } } diff --git a/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/AbstractProxyServlet.java b/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/AbstractProxyServlet.java index 589c5bb0f58d..7ea2264b9909 100644 --- a/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/AbstractProxyServlet.java +++ b/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/AbstractProxyServlet.java @@ -29,6 +29,8 @@ import java.util.Set; import java.util.concurrent.Executor; import java.util.concurrent.TimeoutException; +import java.util.stream.Collectors; +import java.util.stream.Stream; import javax.servlet.AsyncContext; import javax.servlet.ServletConfig; import javax.servlet.ServletContext; @@ -48,6 +50,7 @@ import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpHeaderValue; import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.util.HttpCookieStore; import org.eclipse.jetty.util.ProcessorUtils; import org.eclipse.jetty.util.StringUtil; @@ -452,6 +455,14 @@ protected boolean expects100Continue(HttpServletRequest request) return HttpHeaderValue.CONTINUE.is(request.getHeader(HttpHeader.EXPECT.asString())); } + protected Request newProxyRequest(HttpServletRequest request, String rewrittenTarget) + { + return getHttpClient().newRequest(rewrittenTarget) + .method(request.getMethod()) + .version(HttpVersion.fromString(request.getProtocol())) + .attribute(CLIENT_REQUEST_ATTRIBUTE, request); + } + protected void copyRequestHeaders(HttpServletRequest clientRequest, Request proxyRequest) { // First clear possibly existing headers, as we are going to copy those from the client request. @@ -513,9 +524,50 @@ protected void addProxyHeaders(HttpServletRequest clientRequest, Request proxyRe addXForwardedHeaders(clientRequest, proxyRequest); } + /** + * Adds the HTTP {@code Via} header to the proxied request. + * + * @param proxyRequest the request being proxied + * @see #addViaHeader(HttpServletRequest, Request) + */ protected void addViaHeader(Request proxyRequest) { - proxyRequest.header(HttpHeader.VIA, "http/1.1 " + getViaHost()); + HttpServletRequest clientRequest = (HttpServletRequest)proxyRequest.getAttributes().get(CLIENT_REQUEST_ATTRIBUTE); + addViaHeader(clientRequest, proxyRequest); + } + + /** + *

Adds the HTTP {@code Via} header to the proxied request, taking into account data present in the client request.

+ *

This method considers the protocol of the client request when forming the proxied request. If it + * is HTTP, then the protocol name will not be included in the {@code Via} header that is sent by the proxy, and only + * the protocol version will be sent. If it is not, the entire protocol (name and version) will be included. + * If the client request includes a {@code Via} header, the result will be appended to that to form a chain.

+ * + * @param clientRequest the client request + * @param proxyRequest the request being proxied + * @see RFC 7230 section 5.7.1 + */ + protected void addViaHeader(HttpServletRequest clientRequest, Request proxyRequest) + { + String protocol = clientRequest.getProtocol(); + String[] parts = protocol.split("/", 2); + // Retain only the version if the protocol is HTTP. + String protocolPart = parts.length == 2 && "HTTP".equalsIgnoreCase(parts[0]) ? parts[1] : protocol; + String viaHeaderValue = protocolPart + " " + getViaHost(); + proxyRequest.getHeaders().computeField(HttpHeader.VIA, (header, viaFields) -> + { + if (viaFields == null || viaFields.isEmpty()) + return new HttpField(header, viaHeaderValue); + String separator = ", "; + String newValue = viaFields.stream() + .flatMap(field -> Stream.of(field.getValues())) + .filter(value -> !StringUtil.isBlank(value)) + .collect(Collectors.joining(separator)); + if (newValue.length() > 0) + newValue += separator; + newValue += viaHeaderValue; + return new HttpField(HttpHeader.VIA, newValue); + }); } protected void addXForwardedHeaders(HttpServletRequest clientRequest, Request proxyRequest) diff --git a/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/AsyncMiddleManServlet.java b/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/AsyncMiddleManServlet.java index baf1e5ec35e5..187414a1aa17 100644 --- a/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/AsyncMiddleManServlet.java +++ b/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/AsyncMiddleManServlet.java @@ -47,7 +47,6 @@ import org.eclipse.jetty.client.api.Result; import org.eclipse.jetty.client.util.DeferredContentProvider; import org.eclipse.jetty.http.HttpHeader; -import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.io.RuntimeIOException; import org.eclipse.jetty.util.BufferUtil; @@ -92,9 +91,7 @@ protected void service(HttpServletRequest clientRequest, HttpServletResponse pro return; } - final Request proxyRequest = getHttpClient().newRequest(rewrittenTarget) - .method(clientRequest.getMethod()) - .version(HttpVersion.fromString(clientRequest.getProtocol())); + Request proxyRequest = newProxyRequest(clientRequest, rewrittenTarget); copyRequestHeaders(clientRequest, proxyRequest); @@ -115,7 +112,6 @@ protected void service(HttpServletRequest clientRequest, HttpServletResponse pro if (expects100Continue(clientRequest)) { - proxyRequest.attribute(CLIENT_REQUEST_ATTRIBUTE, clientRequest); proxyRequest.attribute(CONTINUE_ACTION_ATTRIBUTE, (Runnable)() -> { try diff --git a/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/ProxyServlet.java b/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/ProxyServlet.java index fae0f8a4148c..9b510ee1eddb 100644 --- a/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/ProxyServlet.java +++ b/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/ProxyServlet.java @@ -38,7 +38,6 @@ import org.eclipse.jetty.client.api.Result; import org.eclipse.jetty.client.util.DeferredContentProvider; import org.eclipse.jetty.client.util.InputStreamContentProvider; -import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.IteratingCallback; @@ -76,9 +75,7 @@ protected void service(final HttpServletRequest request, final HttpServletRespon return; } - final Request proxyRequest = getHttpClient().newRequest(rewrittenTarget) - .method(request.getMethod()) - .version(HttpVersion.fromString(request.getProtocol())); + Request proxyRequest = newProxyRequest(request, rewrittenTarget); copyRequestHeaders(request, proxyRequest); @@ -95,7 +92,6 @@ protected void service(final HttpServletRequest request, final HttpServletRespon { DeferredContentProvider deferred = new DeferredContentProvider(); proxyRequest.content(deferred); - proxyRequest.attribute(CLIENT_REQUEST_ATTRIBUTE, request); proxyRequest.attribute(CONTINUE_ACTION_ATTRIBUTE, (Runnable)() -> { try diff --git a/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ProxyServletTest.java b/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ProxyServletTest.java index 71e68c619a55..eac3637571dc 100644 --- a/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ProxyServletTest.java +++ b/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ProxyServletTest.java @@ -58,6 +58,7 @@ import javax.servlet.http.Cookie; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletRequestWrapper; import javax.servlet.http.HttpServletResponse; import org.eclipse.jetty.client.DuplexConnectionPool; @@ -87,15 +88,18 @@ import org.eclipse.jetty.util.IO; import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.thread.QueuedThreadPool; -import org.hamcrest.Matchers; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.params.provider.ValueSource; import static org.eclipse.jetty.http.HttpFieldsMatchers.containsHeader; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.greaterThanOrEqualTo; import static org.hamcrest.Matchers.instanceOf; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -145,6 +149,11 @@ private void startProxy(Class proxyServletClass) throws } private void startProxy(Class proxyServletClass, Map initParams) throws Exception + { + startProxy(proxyServletClass.getConstructor().newInstance(), initParams); + } + + private void startProxy(AbstractProxyServlet proxyServlet, Map initParams) throws Exception { QueuedThreadPool proxyPool = new QueuedThreadPool(); proxyPool.setName("proxy"); @@ -159,9 +168,8 @@ private void startProxy(Class proxyServletClass, Map proxyServletClass) throws Exception + { + startServer(new EmptyHttpServlet() + { + @Override + protected void service(HttpServletRequest request, HttpServletResponse response) throws IOException + { + PrintWriter writer = response.getWriter(); + List viaValues = Collections.list(request.getHeaders("Via")); + writer.write(String.join(", ", viaValues)); + } + }); + String viaHost = "my-good-via-host.example.org"; + startProxy(proxyServletClass, Collections.singletonMap("viaHost", viaHost)); + startClient(); + + ContentResponse response = client.GET("http://localhost:" + serverConnector.getLocalPort()); + assertThat(response.getContentAsString(), equalTo("1.1 " + viaHost)); + } + + @ParameterizedTest + @MethodSource("impls") + public void testProxyViaHeaderValueIsAppended(Class proxyServletClass) throws Exception + { + startServer(new EmptyHttpServlet() + { + @Override + protected void service(HttpServletRequest request, HttpServletResponse response) throws IOException + { + // Make sure the proxy coalesced the Via headers into just one. + org.eclipse.jetty.server.Request jettyRequest = (org.eclipse.jetty.server.Request)request; + assertEquals(1, jettyRequest.getHttpFields().getFields(HttpHeader.VIA).size()); + PrintWriter writer = response.getWriter(); + List viaValues = Collections.list(request.getHeaders("Via")); + writer.write(String.join(", ", viaValues)); + } + }); + String viaHost = "beatrix"; + startProxy(proxyServletClass, Collections.singletonMap("viaHost", viaHost)); + startClient(); + + String existingViaHeader = "1.0 charon"; + ContentResponse response = client.newRequest("http://localhost:" + serverConnector.getLocalPort()) + .header(HttpHeader.VIA, existingViaHeader) + .send(); + String expected = String.join(", ", existingViaHeader, "1.1 " + viaHost); + assertThat(response.getContentAsString(), equalTo(expected)); + } + + @ParameterizedTest + @ValueSource(strings = {"HTTP/2.0", "FCGI/1.0"}) + public void testViaHeaderProtocols(String protocol) throws Exception + { + startServer(new EmptyHttpServlet() + { + @Override + protected void service(HttpServletRequest request, HttpServletResponse response) throws IOException + { + PrintWriter writer = response.getWriter(); + List viaValues = Collections.list(request.getHeaders("Via")); + writer.write(String.join(", ", viaValues)); + } + }); + String viaHost = "proxy"; + startProxy(new ProxyServlet() + { + @Override + protected void addViaHeader(HttpServletRequest clientRequest, Request proxyRequest) + { + HttpServletRequest wrapped = new HttpServletRequestWrapper(clientRequest) + { + @Override + public String getProtocol() + { + return protocol; + } + }; + super.addViaHeader(wrapped, proxyRequest); + } + }, Collections.singletonMap("viaHost", viaHost)); + startClient(); + + ContentResponse response = client.GET("http://localhost:" + serverConnector.getLocalPort()); + + String expectedProtocol = protocol.startsWith("HTTP/") ? protocol.substring("HTTP/".length()) : protocol; + String expected = expectedProtocol + " " + viaHost; + assertThat(response.getContentAsString(), equalTo(expected)); } @ParameterizedTest @@ -940,7 +1039,7 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws Se ContentResponse response = client.newRequest("localhost", serverConnector.getLocalPort()) .timeout(5, TimeUnit.SECONDS) .send(); - assertThat(response.getStatus(), Matchers.greaterThanOrEqualTo(500)); + assertThat(response.getStatus(), greaterThanOrEqualTo(500)); } catch (ExecutionException e) { @@ -1073,7 +1172,7 @@ private boolean await(CountDownLatch latch, long ms) throws IOException // Make sure there is error page content, as the proxy-to-client response has been reset. InputStream input = listener.getInputStream(); String body = IO.toString(input); - assertThat(body, Matchers.containsString("HTTP ERROR 504")); + assertThat(body, containsString("HTTP ERROR 504")); chunk1Latch.countDown(); // Result succeeds because a 504 is a valid HTTP response. diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/gzip/GzipHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/gzip/GzipHandler.java index 28a2a0db8748..72b987a7ed9f 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/gzip/GzipHandler.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/gzip/GzipHandler.java @@ -662,15 +662,13 @@ public void handle(String target, Request baseRequest, HttpServletRequest reques baseRequest.getHttpInput().addInterceptor(new GzipHttpInputInterceptor(baseRequest.getHttpChannel().getByteBufferPool(), _inflateBufferSize)); - for (ListIterator i = baseRequest.getHttpFields().listIterator(); i.hasNext(); ) + baseRequest.getHttpFields().computeField(HttpHeader.CONTENT_LENGTH, (header, fields) -> { - HttpField field = i.next(); - if (field.getHeader() == HttpHeader.CONTENT_LENGTH) - { - i.set(new HttpField("X-Content-Length", field.getValue())); - break; - } - } + if (fields == null) + return null; + String length = fields.stream().map(HttpField::getValue).findAny().orElse("0"); + return new HttpField("X-Content-Length", length); + }); } }