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 extends ProxyServlet> proxyServletClass) throws
}
private void startProxy(Class extends ProxyServlet> 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 extends ProxyServlet> 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 extends ProxyServlet> 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);
+ });
}
}