Merged branch 'jetty-9.4.x' into 'jetty-10.0.x'.
This commit is contained in:
commit
da651e995f
|
@ -109,11 +109,16 @@ public class MetaData implements Iterable<HttpField>
|
||||||
return _contentLength;
|
return _contentLength;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setContentLength(long contentLength)
|
||||||
|
{
|
||||||
|
_contentLength = contentLength;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Iterator<HttpField> iterator()
|
public Iterator<HttpField> iterator()
|
||||||
{
|
{
|
||||||
HttpFields fields = getFields();
|
HttpFields fields = getFields();
|
||||||
return fields == null ? Collections.<HttpField>emptyIterator() : fields.iterator();
|
return fields == null ? Collections.emptyIterator() : fields.iterator();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -111,7 +111,7 @@ public class InterleavingTest extends AbstractTest
|
||||||
|
|
||||||
Stream serverStream1 = serverStreams.get(0);
|
Stream serverStream1 = serverStreams.get(0);
|
||||||
Stream serverStream2 = serverStreams.get(1);
|
Stream serverStream2 = serverStreams.get(1);
|
||||||
MetaData.Response response1 = new MetaData.Response(HttpVersion.HTTP_2, HttpStatus.OK_200, new HttpFields(), 0);
|
MetaData.Response response1 = new MetaData.Response(HttpVersion.HTTP_2, HttpStatus.OK_200, new HttpFields());
|
||||||
serverStream1.headers(new HeadersFrame(serverStream1.getId(), response1, null, false), Callback.NOOP);
|
serverStream1.headers(new HeadersFrame(serverStream1.getId(), response1, null, false), Callback.NOOP);
|
||||||
|
|
||||||
Random random = new Random();
|
Random random = new Random();
|
||||||
|
@ -120,7 +120,7 @@ public class InterleavingTest extends AbstractTest
|
||||||
byte[] content2 = new byte[2 * ((ISession)serverStream2.getSession()).updateSendWindow(0)];
|
byte[] content2 = new byte[2 * ((ISession)serverStream2.getSession()).updateSendWindow(0)];
|
||||||
random.nextBytes(content2);
|
random.nextBytes(content2);
|
||||||
|
|
||||||
MetaData.Response response2 = new MetaData.Response(HttpVersion.HTTP_2, HttpStatus.OK_200, new HttpFields(), 0);
|
MetaData.Response response2 = new MetaData.Response(HttpVersion.HTTP_2, HttpStatus.OK_200, new HttpFields());
|
||||||
serverStream2.headers(new HeadersFrame(serverStream2.getId(), response2, null, false), new Callback()
|
serverStream2.headers(new HeadersFrame(serverStream2.getId(), response2, null, false), new Callback()
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -224,7 +224,8 @@ public class HpackEncoder
|
||||||
// Remove fields as specified in RFC 7540, 8.1.2.2.
|
// Remove fields as specified in RFC 7540, 8.1.2.2.
|
||||||
if (fields != null)
|
if (fields != null)
|
||||||
{
|
{
|
||||||
// For example: Connection: Close, TE, Upgrade, Custom.
|
// Remove the headers specified in the Connection header,
|
||||||
|
// for example: Connection: Close, TE, Upgrade, Custom.
|
||||||
Set<String> hopHeaders = null;
|
Set<String> hopHeaders = null;
|
||||||
for (String value : fields.getCSV(HttpHeader.CONNECTION, false))
|
for (String value : fields.getCSV(HttpHeader.CONNECTION, false))
|
||||||
{
|
{
|
||||||
|
@ -232,6 +233,8 @@ public class HpackEncoder
|
||||||
hopHeaders = new HashSet<>();
|
hopHeaders = new HashSet<>();
|
||||||
hopHeaders.add(StringUtil.asciiToLowerCase(value));
|
hopHeaders.add(StringUtil.asciiToLowerCase(value));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
boolean contentLengthEncoded = false;
|
||||||
for (HttpField field : fields)
|
for (HttpField field : fields)
|
||||||
{
|
{
|
||||||
HttpHeader header = field.getHeader();
|
HttpHeader header = field.getHeader();
|
||||||
|
@ -246,8 +249,17 @@ public class HpackEncoder
|
||||||
String name = field.getLowerCaseName();
|
String name = field.getLowerCaseName();
|
||||||
if (hopHeaders != null && hopHeaders.contains(name))
|
if (hopHeaders != null && hopHeaders.contains(name))
|
||||||
continue;
|
continue;
|
||||||
|
if (header == HttpHeader.CONTENT_LENGTH)
|
||||||
|
contentLengthEncoded = true;
|
||||||
encode(buffer, field);
|
encode(buffer, field);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!contentLengthEncoded)
|
||||||
|
{
|
||||||
|
long contentLength = metadata.getContentLength();
|
||||||
|
if (contentLength >= 0)
|
||||||
|
encode(buffer, new HttpField(HttpHeader.CONTENT_LENGTH, String.valueOf(contentLength)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check size
|
// Check size
|
||||||
|
|
|
@ -0,0 +1,105 @@
|
||||||
|
//
|
||||||
|
// ========================================================================
|
||||||
|
// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
|
||||||
|
//
|
||||||
|
// This program and the accompanying materials are made available under
|
||||||
|
// the terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
// https://www.eclipse.org/legal/epl-2.0
|
||||||
|
//
|
||||||
|
// This Source Code may also be made available under the following
|
||||||
|
// Secondary Licenses when the conditions for such availability set
|
||||||
|
// forth in the Eclipse Public License, v. 2.0 are satisfied:
|
||||||
|
// the Apache License v2.0 which is available at
|
||||||
|
// https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
|
||||||
|
// ========================================================================
|
||||||
|
//
|
||||||
|
|
||||||
|
package org.eclipse.jetty.http2.client.http;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.client.api.ContentResponse;
|
||||||
|
import org.eclipse.jetty.http.HttpFields;
|
||||||
|
import org.eclipse.jetty.http.HttpHeader;
|
||||||
|
import org.eclipse.jetty.server.Request;
|
||||||
|
import org.eclipse.jetty.server.handler.gzip.GzipHandler;
|
||||||
|
import org.junit.jupiter.params.ParameterizedTest;
|
||||||
|
import org.junit.jupiter.params.provider.ValueSource;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
public class ContentLengthTest extends AbstractTest
|
||||||
|
{
|
||||||
|
@ParameterizedTest
|
||||||
|
@ValueSource(strings = {"GET", "HEAD", "POST", "PUT"})
|
||||||
|
public void testZeroContentLengthAddedByServer(String method) throws Exception
|
||||||
|
{
|
||||||
|
start(new EmptyServerHandler());
|
||||||
|
|
||||||
|
ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
|
||||||
|
.method(method)
|
||||||
|
.send();
|
||||||
|
|
||||||
|
HttpFields responseHeaders = response.getHeaders();
|
||||||
|
long contentLength = responseHeaders.getLongField(HttpHeader.CONTENT_LENGTH.asString());
|
||||||
|
assertEquals(0, contentLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@ValueSource(strings = {"GET", "HEAD", "POST", "PUT"})
|
||||||
|
public void testContentLengthAddedByServer(String method) throws Exception
|
||||||
|
{
|
||||||
|
byte[] data = new byte[512];
|
||||||
|
start(new EmptyServerHandler()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
protected void service(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException
|
||||||
|
{
|
||||||
|
response.getOutputStream().write(data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
|
||||||
|
.method(method)
|
||||||
|
.send();
|
||||||
|
|
||||||
|
HttpFields responseHeaders = response.getHeaders();
|
||||||
|
long contentLength = responseHeaders.getLongField(HttpHeader.CONTENT_LENGTH.asString());
|
||||||
|
assertEquals(data.length, contentLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@ValueSource(strings = {"GET", "HEAD", "POST", "PUT"})
|
||||||
|
public void testGzippedContentLengthAddedByServer(String method) throws Exception
|
||||||
|
{
|
||||||
|
byte[] data = new byte[4096];
|
||||||
|
|
||||||
|
GzipHandler gzipHandler = new GzipHandler();
|
||||||
|
gzipHandler.addIncludedMethods(method);
|
||||||
|
gzipHandler.setMinGzipSize(data.length / 2);
|
||||||
|
gzipHandler.setHandler(new EmptyServerHandler()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
protected void service(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException
|
||||||
|
{
|
||||||
|
response.setContentLength(data.length);
|
||||||
|
response.getOutputStream().write(data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
start(gzipHandler);
|
||||||
|
|
||||||
|
ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
|
||||||
|
.method(method)
|
||||||
|
.send();
|
||||||
|
|
||||||
|
HttpFields responseHeaders = response.getHeaders();
|
||||||
|
long contentLength = responseHeaders.getLongField(HttpHeader.CONTENT_LENGTH.asString());
|
||||||
|
assertTrue(0 < contentLength && contentLength < data.length);
|
||||||
|
}
|
||||||
|
}
|
|
@ -22,6 +22,7 @@ import java.nio.ByteBuffer;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.http.BadMessageException;
|
||||||
import org.eclipse.jetty.http.HttpFields;
|
import org.eclipse.jetty.http.HttpFields;
|
||||||
import org.eclipse.jetty.http.HttpMethod;
|
import org.eclipse.jetty.http.HttpMethod;
|
||||||
import org.eclipse.jetty.http.HttpStatus;
|
import org.eclipse.jetty.http.HttpStatus;
|
||||||
|
@ -107,6 +108,21 @@ public class HttpTransportOverHTTP2 implements HttpTransport
|
||||||
{
|
{
|
||||||
if (commit.compareAndSet(false, true))
|
if (commit.compareAndSet(false, true))
|
||||||
{
|
{
|
||||||
|
if (lastContent)
|
||||||
|
{
|
||||||
|
long realContentLength = BufferUtil.length(content);
|
||||||
|
long contentLength = response.getContentLength();
|
||||||
|
if (contentLength < 0)
|
||||||
|
{
|
||||||
|
response.setContentLength(realContentLength);
|
||||||
|
}
|
||||||
|
else if (hasContent && contentLength != realContentLength)
|
||||||
|
{
|
||||||
|
callback.failed(new BadMessageException(HttpStatus.INTERNAL_SERVER_ERROR_500, String.format("Incorrect Content-Length %d!=%d", contentLength, realContentLength)));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (hasContent)
|
if (hasContent)
|
||||||
{
|
{
|
||||||
Callback commitCallback = new Callback.Nested(callback)
|
Callback commitCallback = new Callback.Nested(callback)
|
||||||
|
|
Loading…
Reference in New Issue