Removed dependency on classic (blocking) I/O APIs from RequestProtocolCompliance and ResponseProtocolCompliance; removed compliance checks unrelated to HTTP caching enforced by protocol handlers

This commit is contained in:
Oleg Kalnichevski 2017-10-02 11:49:04 +02:00
parent d75c5c2be2
commit 0226eaff6d
9 changed files with 70 additions and 426 deletions

View File

@ -168,4 +168,35 @@ class CachedHttpResponseGenerator {
return request.getMethod().equals(HeaderConstants.GET_METHOD) && cacheEntry.getResource() != null; return request.getMethod().equals(HeaderConstants.GET_METHOD) && cacheEntry.getResource() != null;
} }
/**
* Extract error information about the {@link HttpRequest} telling the 'caller'
* that a problem occured.
*
* @param errorCheck What type of error should I get
* @return The {@link ClassicHttpResponse} that is the error generated
*/
public ClassicHttpResponse getErrorForRequest(final RequestProtocolError errorCheck) {
switch (errorCheck) {
case BODY_BUT_NO_LENGTH_ERROR:
return new BasicClassicHttpResponse(HttpStatus.SC_LENGTH_REQUIRED, "");
case WEAK_ETAG_AND_RANGE_ERROR:
return new BasicClassicHttpResponse(HttpStatus.SC_BAD_REQUEST,
"Weak eTag not compatible with byte range");
case WEAK_ETAG_ON_PUTDELETE_METHOD_ERROR:
return new BasicClassicHttpResponse(HttpStatus.SC_BAD_REQUEST,
"Weak eTag not compatible with PUT or DELETE requests");
case NO_CACHE_DIRECTIVE_WITH_FIELD_NAME:
return new BasicClassicHttpResponse(HttpStatus.SC_BAD_REQUEST,
"No-Cache directive MUST NOT include a field name");
default:
throw new IllegalStateException(
"The request was compliant, therefore no error can be generated for it.");
}
}
} }

View File

@ -360,7 +360,7 @@ public class CachingExec implements ExecChainHandler {
for (final RequestProtocolError error : fatalError) { for (final RequestProtocolError error : fatalError) {
setResponseStatus(context, CacheResponseStatus.CACHE_MODULE_RESPONSE); setResponseStatus(context, CacheResponseStatus.CACHE_MODULE_RESPONSE);
fatalErrorResponse = requestCompliance.getErrorForRequest(error); fatalErrorResponse = responseGenerator.getErrorForRequest(error);
} }
return fatalErrorResponse; return fatalErrorResponse;
} }

View File

@ -31,25 +31,14 @@ import java.util.Arrays;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import org.apache.hc.client5.http.ClientProtocolException;
import org.apache.hc.client5.http.cache.HeaderConstants; import org.apache.hc.client5.http.cache.HeaderConstants;
import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.Contract;
import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.annotation.ThreadingBehavior;
import org.apache.hc.core5.http.ClassicHttpRequest;
import org.apache.hc.core5.http.ClassicHttpResponse;
import org.apache.hc.core5.http.ContentType;
import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.Header;
import org.apache.hc.core5.http.HeaderElement; import org.apache.hc.core5.http.HeaderElement;
import org.apache.hc.core5.http.HeaderElements;
import org.apache.hc.core5.http.HttpEntity;
import org.apache.hc.core5.http.HttpHeaders;
import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.HttpRequest;
import org.apache.hc.core5.http.HttpStatus;
import org.apache.hc.core5.http.HttpVersion; import org.apache.hc.core5.http.HttpVersion;
import org.apache.hc.core5.http.ProtocolVersion; import org.apache.hc.core5.http.ProtocolVersion;
import org.apache.hc.core5.http.io.entity.HttpEntityWrapper;
import org.apache.hc.core5.http.message.BasicClassicHttpResponse;
import org.apache.hc.core5.http.message.BasicHeader;
import org.apache.hc.core5.http.message.MessageSupport; import org.apache.hc.core5.http.message.MessageSupport;
/** /**
@ -107,17 +96,8 @@ class RequestProtocolCompliance {
* fix the request here. * fix the request here.
* *
* @param request the request to check for compliance * @param request the request to check for compliance
* @throws ClientProtocolException when we have trouble making the request compliant
*/ */
public void makeRequestCompliant(final ClassicHttpRequest request) public void makeRequestCompliant(final HttpRequest request) {
throws ClientProtocolException {
if (requestMustNotHaveEntity(request)) {
request.setEntity(null);
}
verifyRequestWithExpectContinueFlagHas100continueHeader(request);
verifyOPTIONSRequestWithBodyHasContentType(request);
decrementOPTIONSMaxForwardsIfGreaterThen0(request); decrementOPTIONSMaxForwardsIfGreaterThen0(request);
stripOtherFreshnessDirectivesWithNoCache(request); stripOtherFreshnessDirectivesWithNoCache(request);
@ -160,10 +140,6 @@ class RequestProtocolCompliance {
return newHdr.toString(); return newHdr.toString();
} }
private boolean requestMustNotHaveEntity(final HttpRequest request) {
return HeaderConstants.TRACE_METHOD.equals(request.getMethod());
}
private void decrementOPTIONSMaxForwardsIfGreaterThen0(final HttpRequest request) { private void decrementOPTIONSMaxForwardsIfGreaterThen0(final HttpRequest request) {
if (!HeaderConstants.OPTIONS_METHOD.equals(request.getMethod())) { if (!HeaderConstants.OPTIONS_METHOD.equals(request.getMethod())) {
return; return;
@ -180,81 +156,6 @@ class RequestProtocolCompliance {
request.setHeader(HeaderConstants.MAX_FORWARDS, Integer.toString(currentMaxForwards - 1)); request.setHeader(HeaderConstants.MAX_FORWARDS, Integer.toString(currentMaxForwards - 1));
} }
private void verifyOPTIONSRequestWithBodyHasContentType(final ClassicHttpRequest request) {
if (!HeaderConstants.OPTIONS_METHOD.equals(request.getMethod())) {
return;
}
addContentTypeHeaderIfMissing(request);
}
private void addContentTypeHeaderIfMissing(final ClassicHttpRequest request) {
final HttpEntity entity = request.getEntity();
if (entity != null && entity.getContentType() == null) {
final HttpEntityWrapper entityWrapper = new HttpEntityWrapper(entity) {
@Override
public String getContentType() {
return ContentType.APPLICATION_OCTET_STREAM.getMimeType();
}
};
request.setEntity(entityWrapper);
}
}
private void verifyRequestWithExpectContinueFlagHas100continueHeader(final ClassicHttpRequest request) {
if (request.containsHeader(HttpHeaders.EXPECT) && request.getEntity() != null) {
add100ContinueHeaderIfMissing(request);
} else {
remove100ContinueHeaderIfExists(request);
}
}
private void remove100ContinueHeaderIfExists(final HttpRequest request) {
boolean hasHeader = false;
final Header[] expectHeaders = request.getHeaders(HttpHeaders.EXPECT);
List<HeaderElement> expectElementsThatAreNot100Continue = new ArrayList<>();
for (final Header h : expectHeaders) {
for (final HeaderElement elt : MessageSupport.parse(h)) {
if (!(HeaderElements.CONTINUE.equalsIgnoreCase(elt.getName()))) {
expectElementsThatAreNot100Continue.add(elt);
} else {
hasHeader = true;
}
}
if (hasHeader) {
request.removeHeader(h);
for (final HeaderElement elt : expectElementsThatAreNot100Continue) {
final BasicHeader newHeader = new BasicHeader(HeaderElements.CONTINUE, elt.getName());
request.addHeader(newHeader);
}
return;
} else {
expectElementsThatAreNot100Continue = new ArrayList<>();
}
}
}
private void add100ContinueHeaderIfMissing(final HttpRequest request) {
boolean hasHeader = false;
final Iterator<HeaderElement> it = MessageSupport.iterate(request, HttpHeaders.EXPECT);
while (it.hasNext()) {
final HeaderElement elt = it.next();
if (HeaderElements.CONTINUE.equalsIgnoreCase(elt.getName())) {
hasHeader = true;
}
}
if (!hasHeader) {
request.addHeader(HttpHeaders.EXPECT, HeaderElements.CONTINUE);
}
}
protected boolean requestMinorVersionIsTooHighMajorVersionsMatch(final HttpRequest request) { protected boolean requestMinorVersionIsTooHighMajorVersionsMatch(final HttpRequest request) {
final ProtocolVersion requestProtocol = request.getVersion(); final ProtocolVersion requestProtocol = request.getVersion();
if (requestProtocol == null) { if (requestProtocol == null) {
@ -276,37 +177,6 @@ class RequestProtocolCompliance {
return requestProtocol != null && requestProtocol.compareToVersion(HttpVersion.HTTP_1_1) < 0; return requestProtocol != null && requestProtocol.compareToVersion(HttpVersion.HTTP_1_1) < 0;
} }
/**
* Extract error information about the {@link HttpRequest} telling the 'caller'
* that a problem occured.
*
* @param errorCheck What type of error should I get
* @return The {@link ClassicHttpResponse} that is the error generated
*/
public ClassicHttpResponse getErrorForRequest(final RequestProtocolError errorCheck) {
switch (errorCheck) {
case BODY_BUT_NO_LENGTH_ERROR:
return new BasicClassicHttpResponse(HttpStatus.SC_LENGTH_REQUIRED, "");
case WEAK_ETAG_AND_RANGE_ERROR:
return new BasicClassicHttpResponse(HttpStatus.SC_BAD_REQUEST,
"Weak eTag not compatible with byte range");
case WEAK_ETAG_ON_PUTDELETE_METHOD_ERROR:
return new BasicClassicHttpResponse(HttpStatus.SC_BAD_REQUEST,
"Weak eTag not compatible with PUT or DELETE requests");
case NO_CACHE_DIRECTIVE_WITH_FIELD_NAME:
return new BasicClassicHttpResponse(HttpStatus.SC_BAD_REQUEST,
"No-Cache directive MUST NOT include a field name");
default:
throw new IllegalStateException(
"The request was compliant, therefore no error can be generated for it.");
}
}
private RequestProtocolError requestHasWeakETagAndRange(final HttpRequest request) { private RequestProtocolError requestHasWeakETagAndRange(final HttpRequest request) {
// TODO: Should these be looking at all the headers marked as Range? // TODO: Should these be looking at all the headers marked as Range?
final String method = request.getMethod(); final String method = request.getMethod();

View File

@ -36,12 +36,9 @@ import org.apache.hc.client5.http.cache.HeaderConstants;
import org.apache.hc.client5.http.utils.DateUtils; import org.apache.hc.client5.http.utils.DateUtils;
import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.Contract;
import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.annotation.ThreadingBehavior;
import org.apache.hc.core5.http.ClassicHttpRequest;
import org.apache.hc.core5.http.ClassicHttpResponse;
import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.Header;
import org.apache.hc.core5.http.HeaderElement; import org.apache.hc.core5.http.HeaderElement;
import org.apache.hc.core5.http.HeaderElements; import org.apache.hc.core5.http.HeaderElements;
import org.apache.hc.core5.http.HttpEntity;
import org.apache.hc.core5.http.HttpHeaders; import org.apache.hc.core5.http.HttpHeaders;
import org.apache.hc.core5.http.HttpRequest; import org.apache.hc.core5.http.HttpRequest;
import org.apache.hc.core5.http.HttpResponse; import org.apache.hc.core5.http.HttpResponse;
@ -72,14 +69,9 @@ class ResponseProtocolCompliance {
* @throws IOException Bad things happened * @throws IOException Bad things happened
*/ */
public void ensureProtocolCompliance( public void ensureProtocolCompliance(
final ClassicHttpRequest originalRequest, final HttpRequest originalRequest,
final ClassicHttpRequest request, final HttpRequest request,
final ClassicHttpResponse response) throws IOException { final HttpResponse response) throws IOException {
if (backendResponseMustNotHaveBody(request, response)) {
consumeBody(response);
response.setEntity(null);
}
requestDidNotExpect100ContinueButResponseIsOne(originalRequest, response); requestDidNotExpect100ContinueButResponseIsOne(originalRequest, response);
transferEncodingIsNotReturnedTo1_0Client(originalRequest, response); transferEncodingIsNotReturnedTo1_0Client(originalRequest, response);
@ -97,13 +89,6 @@ class ResponseProtocolCompliance {
warningsWithNonMatchingWarnDatesAreRemoved(response); warningsWithNonMatchingWarnDatesAreRemoved(response);
} }
private void consumeBody(final ClassicHttpResponse response) throws IOException {
final HttpEntity body = response.getEntity();
if (body != null) {
IOUtils.consume(body);
}
}
private void warningsWithNonMatchingWarnDatesAreRemoved( private void warningsWithNonMatchingWarnDatesAreRemoved(
final HttpResponse response) { final HttpResponse response) {
final Date responseDate = DateUtils.parseDate(response.getFirstHeader(HttpHeaders.DATE).getValue()); final Date responseDate = DateUtils.parseDate(response.getFirstHeader(HttpHeaders.DATE).getValue());
@ -180,13 +165,11 @@ class ResponseProtocolCompliance {
} }
private void ensurePartialContentIsNotSentToAClientThatDidNotRequestIt(final HttpRequest request, private void ensurePartialContentIsNotSentToAClientThatDidNotRequestIt(final HttpRequest request,
final ClassicHttpResponse response) throws IOException { final HttpResponse response) throws IOException {
if (request.getFirstHeader(HeaderConstants.RANGE) != null if (request.getFirstHeader(HeaderConstants.RANGE) != null
|| response.getCode() != HttpStatus.SC_PARTIAL_CONTENT) { || response.getCode() != HttpStatus.SC_PARTIAL_CONTENT) {
return; return;
} }
consumeBody(response);
throw new ClientProtocolException(UNEXPECTED_PARTIAL_CONTENT); throw new ClientProtocolException(UNEXPECTED_PARTIAL_CONTENT);
} }
@ -217,15 +200,8 @@ class ResponseProtocolCompliance {
} }
} }
private boolean backendResponseMustNotHaveBody(final HttpRequest request, final HttpResponse backendResponse) {
return HeaderConstants.HEAD_METHOD.equals(request.getMethod())
|| backendResponse.getCode() == HttpStatus.SC_NO_CONTENT
|| backendResponse.getCode() == HttpStatus.SC_RESET_CONTENT
|| backendResponse.getCode() == HttpStatus.SC_NOT_MODIFIED;
}
private void requestDidNotExpect100ContinueButResponseIsOne( private void requestDidNotExpect100ContinueButResponseIsOne(
final ClassicHttpRequest originalRequest, final ClassicHttpResponse response) throws IOException { final HttpRequest originalRequest, final HttpResponse response) throws IOException {
if (response.getCode() != HttpStatus.SC_CONTINUE) { if (response.getCode() != HttpStatus.SC_CONTINUE) {
return; return;
} }
@ -234,12 +210,11 @@ class ResponseProtocolCompliance {
if (header != null && header.getValue().equalsIgnoreCase(HeaderElements.CONTINUE)) { if (header != null && header.getValue().equalsIgnoreCase(HeaderElements.CONTINUE)) {
return; return;
} }
consumeBody(response);
throw new ClientProtocolException(UNEXPECTED_100_CONTINUE); throw new ClientProtocolException(UNEXPECTED_100_CONTINUE);
} }
private void transferEncodingIsNotReturnedTo1_0Client( private void transferEncodingIsNotReturnedTo1_0Client(
final ClassicHttpRequest originalRequest, final HttpResponse response) { final HttpRequest originalRequest, final HttpResponse response) {
final ProtocolVersion version = originalRequest.getVersion() != null ? originalRequest.getVersion() : HttpVersion.DEFAULT; final ProtocolVersion version = originalRequest.getVersion() != null ? originalRequest.getVersion() : HttpVersion.DEFAULT;
if (version.compareToVersion(HttpVersion.HTTP_1_1) >= 0) { if (version.compareToVersion(HttpVersion.HTTP_1_1) >= 0) {
return; return;

View File

@ -49,7 +49,6 @@ import java.util.ArrayList;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import org.apache.hc.client5.http.ClientProtocolException;
import org.apache.hc.client5.http.HttpRoute; import org.apache.hc.client5.http.HttpRoute;
import org.apache.hc.client5.http.cache.CacheResponseStatus; import org.apache.hc.client5.http.cache.CacheResponseStatus;
import org.apache.hc.client5.http.cache.HttpCacheContext; import org.apache.hc.client5.http.cache.HttpCacheContext;
@ -359,27 +358,6 @@ public abstract class TestCachingExecChain {
verifyMocks(); verifyMocks();
} }
@Test
public void testNonCompliantRequestWrapsAndReThrowsProtocolException() throws Exception {
final ClientProtocolException expected = new ClientProtocolException("ouch");
requestIsFatallyNonCompliant(null);
mockRequestProtocolCompliance.makeRequestCompliant((ClassicHttpRequest) anyObject());
expectLastCall().andThrow(expected);
boolean gotException = false;
replayMocks();
try {
execute(request);
} catch (final ClientProtocolException ex) {
Assert.assertSame(expected, ex);
gotException = true;
}
verifyMocks();
Assert.assertTrue(gotException);
}
@Test @Test
public void testSetsModuleGeneratedResponseContextForCacheOptionsResponse() throws Exception { public void testSetsModuleGeneratedResponseContextForCacheOptionsResponse() throws Exception {
impl = createCachingExecChain(new BasicHttpCache(), CacheConfig.DEFAULT); impl = createCachingExecChain(new BasicHttpCache(), CacheConfig.DEFAULT);

View File

@ -254,36 +254,6 @@ public class TestProtocolDeviations {
} }
} }
/*
* "If the OPTIONS request includes an entity-body (as indicated by the
* presence of Content-Length or Transfer-Encoding), then the media type
* MUST be indicated by a Content-Type field."
*
* http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.2
*/
@Test
public void testOPTIONSRequestsWithBodiesAndNoContentTypeHaveOneSupplied() throws Exception {
final ClassicHttpRequest options = new BasicClassicHttpRequest("OPTIONS", "/");
options.setEntity(body);
options.setHeader("Content-Length", "1");
final Capture<ClassicHttpRequest> reqCap = new Capture<>();
EasyMock.expect(
mockExecChain.proceed(
EasyMock.capture(reqCap),
EasyMock.isA(ExecChain.Scope.class))).andReturn(originResponse);
replayMocks();
execute(options);
verifyMocks();
final ClassicHttpRequest reqWithBody = reqCap.getValue();
final HttpEntity reqBody = reqWithBody.getEntity();
Assert.assertNotNull(reqBody);
Assert.assertNotNull(reqBody.getContentType());
}
/* /*
* "10.2.7 206 Partial Content ... The request MUST have included a Range * "10.2.7 206 Partial Content ... The request MUST have included a Range
* header field (section 14.35) indicating the desired range, and MAY have * header field (section 14.35) indicating the desired range, and MAY have

View File

@ -641,39 +641,6 @@ public class TestProtocolRequirements extends AbstractProtocolTest {
Assert.assertFalse(foundExpect); Assert.assertFalse(foundExpect);
} }
/*
* "A client MUST NOT send an Expect request-header field (section 14.20)
* with the '100-continue' expectation if it does not intend to send a
* request body."
*
* http://www.w3.org/Protocols/rfc2616/rfc2616-sec8.html#sec8.2.3
*/
@Test
public void testExpect100ContinueIsNotSentIfThereIsNoRequestBody() throws Exception {
request.addHeader("Expect", "100-continue");
final Capture<ClassicHttpRequest> reqCap = new Capture<>();
EasyMock.expect(
mockExecChain.proceed(
EasyMock.capture(reqCap),
EasyMock.isA(ExecChain.Scope.class))).andReturn(originResponse);
replayMocks();
execute(request);
verifyMocks();
final ClassicHttpRequest forwarded = reqCap.getValue();
boolean foundExpectContinue = false;
final Iterator<HeaderElement> it = MessageSupport.iterate(forwarded, HttpHeaders.EXPECT);
while (it.hasNext()) {
final HeaderElement elt = it.next();
if ("100-continue".equalsIgnoreCase(elt.getName())) {
foundExpectContinue = true;
break;
}
}
Assert.assertFalse(foundExpectContinue);
}
/* /*
* "If a proxy receives a request that includes an Expect request- header * "If a proxy receives a request that includes an Expect request- header
* field with the '100-continue' expectation, and the proxy either knows * field with the '100-continue' expectation, and the proxy either knows
@ -1030,32 +997,6 @@ public class TestProtocolRequirements extends AbstractProtocolTest {
verifyMocks(); verifyMocks();
} }
/*
* "A TRACE request MUST NOT include an entity."
*
* http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.8
*/
@Test
public void testForwardedTRACERequestsDoNotIncludeAnEntity() throws Exception {
final BasicClassicHttpRequest trace = new BasicClassicHttpRequest("TRACE", "/");
trace.setEntity(HttpTestUtils.makeBody(entityLength));
trace.setHeader("Content-Length", Integer.toString(entityLength));
final Capture<ClassicHttpRequest> reqCap = new Capture<>();
EasyMock.expect(
mockExecChain.proceed(
EasyMock.capture(reqCap),
EasyMock.isA(ExecChain.Scope.class))).andReturn(originResponse);
replayMocks();
execute(trace);
verifyMocks();
final ClassicHttpRequest bodyReq = reqCap.getValue();
Assert.assertTrue(bodyReq.getEntity() == null || bodyReq.getEntity().getContentLength() == 0);
}
/* /*
* "9.8 TRACE ... Responses to this method MUST NOT be cached." * "9.8 TRACE ... Responses to this method MUST NOT be cached."
* *
@ -1101,32 +1042,6 @@ public class TestProtocolRequirements extends AbstractProtocolTest {
final ClassicHttpResponse result = execute(request); final ClassicHttpResponse result = execute(request);
verifyMocks(); verifyMocks();
Assert.assertTrue(result.getEntity() == null || result.getEntity().getContentLength() == 0);
}
/*
* "10.2.6 205 Reset Content ... The response MUST NOT include an entity."
*
* http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.2.6
*/
@Test
public void test205ResponsesDoNotContainMessageBodies() throws Exception {
originResponse = new BasicClassicHttpResponse(HttpStatus.SC_RESET_CONTENT, "Reset Content");
originResponse.setEntity(HttpTestUtils.makeBody(entityLength));
EasyMock.expect(
mockExecChain.proceed(
EasyMock.isA(ClassicHttpRequest.class),
EasyMock.isA(ExecChain.Scope.class))).andReturn(originResponse);
replayMocks();
final ClassicHttpResponse result = execute(request);
verifyMocks();
Assert.assertTrue(result.getEntity() == null || result.getEntity().getContentLength() == 0);
} }
/* /*
@ -1704,8 +1619,6 @@ public class TestProtocolRequirements extends AbstractProtocolTest {
final ClassicHttpResponse result = execute(request); final ClassicHttpResponse result = execute(request);
verifyMocks(); verifyMocks();
Assert.assertTrue(result.getEntity() == null || result.getEntity().getContentLength() == 0);
} }
/* /*

View File

@ -27,33 +27,30 @@
package org.apache.hc.client5.http.impl.cache; package org.apache.hc.client5.http.impl.cache;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import java.util.Arrays; import java.util.Arrays;
import org.apache.hc.client5.http.classic.methods.HttpPut; import org.apache.hc.client5.http.impl.RequestCopier;
import org.apache.hc.client5.http.impl.classic.ClassicRequestCopier; import org.apache.hc.core5.http.HttpRequest;
import org.apache.hc.core5.http.ClassicHttpRequest;
import org.apache.hc.core5.http.HttpVersion; import org.apache.hc.core5.http.HttpVersion;
import org.apache.hc.core5.http.ProtocolVersion; import org.apache.hc.core5.http.ProtocolVersion;
import org.apache.hc.core5.http.message.BasicClassicHttpRequest; import org.apache.hc.core5.http.message.BasicHttpRequest;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
public class TestRequestProtocolCompliance { public class TestRequestProtocolCompliance {
private RequestProtocolCompliance impl; private RequestProtocolCompliance impl;
private ClassicHttpRequest req;
@Before @Before
public void setUp() { public void setUp() {
req = HttpTestUtils.makeDefaultRequest();
impl = new RequestProtocolCompliance(false); impl = new RequestProtocolCompliance(false);
} }
@Test @Test
public void testRequestWithWeakETagAndRange() throws Exception { public void testRequestWithWeakETagAndRange() throws Exception {
final HttpRequest req = new BasicHttpRequest("GET", "/");
req.setHeader("Range", "bytes=0-499"); req.setHeader("Range", "bytes=0-499");
req.setHeader("If-Range", "W/\"weak\""); req.setHeader("If-Range", "W/\"weak\"");
assertEquals(1, impl.requestIsFatallyNonCompliant(req).size()); assertEquals(1, impl.requestIsFatallyNonCompliant(req).size());
@ -61,14 +58,14 @@ public class TestRequestProtocolCompliance {
@Test @Test
public void testRequestWithWeekETagForPUTOrDELETEIfMatch() throws Exception { public void testRequestWithWeekETagForPUTOrDELETEIfMatch() throws Exception {
req = new HttpPut("http://example.com/"); final HttpRequest req = new BasicHttpRequest("PUT", "http://example.com/");
req.setHeader("If-Match", "W/\"weak\""); req.setHeader("If-Match", "W/\"weak\"");
assertEquals(1, impl.requestIsFatallyNonCompliant(req).size()); assertEquals(1, impl.requestIsFatallyNonCompliant(req).size());
} }
@Test @Test
public void testRequestWithWeekETagForPUTOrDELETEIfMatchAllowed() throws Exception { public void testRequestWithWeekETagForPUTOrDELETEIfMatchAllowed() throws Exception {
req = new HttpPut("http://example.com/"); final HttpRequest req = new BasicHttpRequest("PUT", "http://example.com/");
req.setHeader("If-Match", "W/\"weak\""); req.setHeader("If-Match", "W/\"weak\"");
impl = new RequestProtocolCompliance(true); impl = new RequestProtocolCompliance(true);
assertEquals(Arrays.asList(), impl.requestIsFatallyNonCompliant(req)); assertEquals(Arrays.asList(), impl.requestIsFatallyNonCompliant(req));
@ -76,41 +73,33 @@ public class TestRequestProtocolCompliance {
@Test @Test
public void testRequestContainsNoCacheDirectiveWithFieldName() throws Exception { public void testRequestContainsNoCacheDirectiveWithFieldName() throws Exception {
final HttpRequest req = new BasicHttpRequest("GET", "/");
req.setHeader("Cache-Control", "no-cache=false"); req.setHeader("Cache-Control", "no-cache=false");
assertEquals(1, impl.requestIsFatallyNonCompliant(req).size()); assertEquals(1, impl.requestIsFatallyNonCompliant(req).size());
} }
@Test @Test
public void doesNotModifyACompliantRequest() throws Exception { public void doesNotModifyACompliantRequest() throws Exception {
final ClassicHttpRequest wrapper = ClassicRequestCopier.INSTANCE.copy(req); final HttpRequest req = new BasicHttpRequest("GET", "/");
final HttpRequest wrapper = RequestCopier.INSTANCE.copy(req);
impl.makeRequestCompliant(wrapper); impl.makeRequestCompliant(wrapper);
assertTrue(HttpTestUtils.equivalent(req, wrapper)); assertTrue(HttpTestUtils.equivalent(req, wrapper));
} }
@Test
public void removesEntityFromTRACERequest() throws Exception {
final ClassicHttpRequest request = new BasicClassicHttpRequest("TRACE", "/");
request.setVersion(HttpVersion.HTTP_1_1);
request.setEntity(HttpTestUtils.makeBody(50));
final ClassicHttpRequest wrapper = ClassicRequestCopier.INSTANCE.copy(request);
impl.makeRequestCompliant(wrapper);
assertNull(wrapper.getEntity());
}
@Test @Test
public void upgrades1_0RequestTo1_1() throws Exception { public void upgrades1_0RequestTo1_1() throws Exception {
req = new BasicClassicHttpRequest("GET", "/"); final HttpRequest req = new BasicHttpRequest("GET", "/");
req.setVersion(HttpVersion.HTTP_1_0); req.setVersion(HttpVersion.HTTP_1_0);
final ClassicHttpRequest wrapper = ClassicRequestCopier.INSTANCE.copy(req); final HttpRequest wrapper = RequestCopier.INSTANCE.copy(req);
impl.makeRequestCompliant(wrapper); impl.makeRequestCompliant(wrapper);
assertEquals(HttpVersion.HTTP_1_1, wrapper.getVersion()); assertEquals(HttpVersion.HTTP_1_1, wrapper.getVersion());
} }
@Test @Test
public void downgrades1_2RequestTo1_1() throws Exception { public void downgrades1_2RequestTo1_1() throws Exception {
req = new BasicClassicHttpRequest("GET", "/"); final HttpRequest req = new BasicHttpRequest("GET", "/");
req.setVersion(new ProtocolVersion("HTTP", 1, 2)); req.setVersion(new ProtocolVersion("HTTP", 1, 2));
final ClassicHttpRequest wrapper = ClassicRequestCopier.INSTANCE.copy(req); final HttpRequest wrapper = RequestCopier.INSTANCE.copy(req);
impl.makeRequestCompliant(wrapper); impl.makeRequestCompliant(wrapper);
assertEquals(HttpVersion.HTTP_1_1, wrapper.getVersion()); assertEquals(HttpVersion.HTTP_1_1, wrapper.getVersion());
} }
@ -118,8 +107,9 @@ public class TestRequestProtocolCompliance {
@Test @Test
public void stripsMinFreshFromRequestIfNoCachePresent() public void stripsMinFreshFromRequestIfNoCachePresent()
throws Exception { throws Exception {
final HttpRequest req = new BasicHttpRequest("GET", "/");
req.setHeader("Cache-Control", "no-cache, min-fresh=10"); req.setHeader("Cache-Control", "no-cache, min-fresh=10");
final ClassicHttpRequest wrapper = ClassicRequestCopier.INSTANCE.copy(req); final HttpRequest wrapper = RequestCopier.INSTANCE.copy(req);
impl.makeRequestCompliant(wrapper); impl.makeRequestCompliant(wrapper);
assertEquals("no-cache", assertEquals("no-cache",
wrapper.getFirstHeader("Cache-Control").getValue()); wrapper.getFirstHeader("Cache-Control").getValue());
@ -128,8 +118,9 @@ public class TestRequestProtocolCompliance {
@Test @Test
public void stripsMaxFreshFromRequestIfNoCachePresent() public void stripsMaxFreshFromRequestIfNoCachePresent()
throws Exception { throws Exception {
final HttpRequest req = new BasicHttpRequest("GET", "/");
req.setHeader("Cache-Control", "no-cache, max-stale=10"); req.setHeader("Cache-Control", "no-cache, max-stale=10");
final ClassicHttpRequest wrapper = ClassicRequestCopier.INSTANCE.copy(req); final HttpRequest wrapper = RequestCopier.INSTANCE.copy(req);
impl.makeRequestCompliant(wrapper); impl.makeRequestCompliant(wrapper);
assertEquals("no-cache", assertEquals("no-cache",
wrapper.getFirstHeader("Cache-Control").getValue()); wrapper.getFirstHeader("Cache-Control").getValue());
@ -138,8 +129,9 @@ public class TestRequestProtocolCompliance {
@Test @Test
public void stripsMaxAgeFromRequestIfNoCachePresent() public void stripsMaxAgeFromRequestIfNoCachePresent()
throws Exception { throws Exception {
final HttpRequest req = new BasicHttpRequest("GET", "/");
req.setHeader("Cache-Control", "no-cache, max-age=10"); req.setHeader("Cache-Control", "no-cache, max-age=10");
final ClassicHttpRequest wrapper = ClassicRequestCopier.INSTANCE.copy(req); final HttpRequest wrapper = RequestCopier.INSTANCE.copy(req);
impl.makeRequestCompliant(wrapper); impl.makeRequestCompliant(wrapper);
assertEquals("no-cache", assertEquals("no-cache",
wrapper.getFirstHeader("Cache-Control").getValue()); wrapper.getFirstHeader("Cache-Control").getValue());
@ -148,8 +140,9 @@ public class TestRequestProtocolCompliance {
@Test @Test
public void doesNotStripMinFreshFromRequestWithoutNoCache() public void doesNotStripMinFreshFromRequestWithoutNoCache()
throws Exception { throws Exception {
final HttpRequest req = new BasicHttpRequest("GET", "/");
req.setHeader("Cache-Control", "min-fresh=10"); req.setHeader("Cache-Control", "min-fresh=10");
final ClassicHttpRequest wrapper = ClassicRequestCopier.INSTANCE.copy(req); final HttpRequest wrapper = RequestCopier.INSTANCE.copy(req);
impl.makeRequestCompliant(wrapper); impl.makeRequestCompliant(wrapper);
assertEquals("min-fresh=10", assertEquals("min-fresh=10",
wrapper.getFirstHeader("Cache-Control").getValue()); wrapper.getFirstHeader("Cache-Control").getValue());
@ -158,8 +151,9 @@ public class TestRequestProtocolCompliance {
@Test @Test
public void correctlyStripsMinFreshFromMiddleIfNoCache() public void correctlyStripsMinFreshFromMiddleIfNoCache()
throws Exception { throws Exception {
final HttpRequest req = new BasicHttpRequest("GET", "/");
req.setHeader("Cache-Control", "no-cache,min-fresh=10,no-store"); req.setHeader("Cache-Control", "no-cache,min-fresh=10,no-store");
final ClassicHttpRequest wrapper = ClassicRequestCopier.INSTANCE.copy(req); final HttpRequest wrapper = RequestCopier.INSTANCE.copy(req);
impl.makeRequestCompliant(wrapper); impl.makeRequestCompliant(wrapper);
assertEquals("no-cache,no-store", assertEquals("no-cache,no-store",
wrapper.getFirstHeader("Cache-Control").getValue()); wrapper.getFirstHeader("Cache-Control").getValue());

View File

@ -26,136 +26,49 @@
*/ */
package org.apache.hc.client5.http.impl.cache; package org.apache.hc.client5.http.impl.cache;
import static junit.framework.TestCase.assertNull;
import static junit.framework.TestCase.assertTrue;
import java.io.ByteArrayInputStream;
import java.util.Date; import java.util.Date;
import org.apache.hc.client5.http.ClientProtocolException; import org.apache.hc.client5.http.ClientProtocolException;
import org.apache.hc.client5.http.HttpRoute;
import org.apache.hc.client5.http.classic.methods.HttpGet; import org.apache.hc.client5.http.classic.methods.HttpGet;
import org.apache.hc.client5.http.classic.methods.HttpHead;
import org.apache.hc.client5.http.impl.classic.ClassicRequestCopier; import org.apache.hc.client5.http.impl.classic.ClassicRequestCopier;
import org.apache.hc.client5.http.utils.DateUtils; import org.apache.hc.client5.http.utils.DateUtils;
import org.apache.hc.core5.http.ClassicHttpRequest; import org.apache.hc.core5.http.HttpRequest;
import org.apache.hc.core5.http.ClassicHttpResponse; import org.apache.hc.core5.http.HttpResponse;
import org.apache.hc.core5.http.HttpEntity;
import org.apache.hc.core5.http.HttpHost;
import org.apache.hc.core5.http.HttpStatus; import org.apache.hc.core5.http.HttpStatus;
import org.apache.hc.core5.http.io.entity.ByteArrayEntity; import org.apache.hc.core5.http.message.BasicHttpResponse;
import org.apache.hc.core5.http.io.entity.InputStreamEntity;
import org.apache.hc.core5.http.message.BasicClassicHttpRequest;
import org.apache.hc.core5.http.message.BasicClassicHttpResponse;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
public class TestResponseProtocolCompliance { public class TestResponseProtocolCompliance {
private HttpRoute route;
private ResponseProtocolCompliance impl; private ResponseProtocolCompliance impl;
@Before @Before
public void setUp() { public void setUp() {
route = new HttpRoute(new HttpHost("foo.example.com", 80));
impl = new ResponseProtocolCompliance(); impl = new ResponseProtocolCompliance();
} }
private static class Flag { private void setMinimalResponseHeaders(final HttpResponse resp) {
public boolean set;
}
private void setMinimalResponseHeaders(final ClassicHttpResponse resp) {
resp.setHeader("Date", DateUtils.formatDate(new Date())); resp.setHeader("Date", DateUtils.formatDate(new Date()));
resp.setHeader("Server", "MyServer/1.0"); resp.setHeader("Server", "MyServer/1.0");
} }
private ByteArrayInputStream makeTrackableBody(final int nbytes, final Flag closed) { private HttpResponse makePartialResponse(final int nbytes) {
final byte[] buf = HttpTestUtils.getRandomBytes(nbytes); final HttpResponse resp = new BasicHttpResponse(HttpStatus.SC_PARTIAL_CONTENT, "Partial Content");
final ByteArrayInputStream bais = new ByteArrayInputStream(buf) {
@Override
public void close() {
closed.set = true;
}
};
return bais;
}
private ClassicHttpResponse makePartialResponse(final int nbytes) {
final ClassicHttpResponse resp = new BasicClassicHttpResponse(HttpStatus.SC_PARTIAL_CONTENT, "Partial Content");
setMinimalResponseHeaders(resp); setMinimalResponseHeaders(resp);
resp.setHeader("Content-Length","" + nbytes); resp.setHeader("Content-Length","" + nbytes);
resp.setHeader("Content-Range","0-127/256"); resp.setHeader("Content-Range","0-127/256");
return resp; return resp;
} }
@Test
public void consumesBodyIfOriginSendsOneInResponseToHEAD() throws Exception {
final HttpHead req = new HttpHead("http://foo.example.com/");
final ClassicHttpRequest wrapper = ClassicRequestCopier.INSTANCE.copy(req);
final int nbytes = 128;
final ClassicHttpResponse resp = new BasicClassicHttpResponse(HttpStatus.SC_OK, "OK");
setMinimalResponseHeaders(resp);
resp.setHeader("Content-Length","" + nbytes);
final Flag closed = new Flag();
final ByteArrayInputStream bais = makeTrackableBody(nbytes, closed);
resp.setEntity(new InputStreamEntity(bais, -1));
impl.ensureProtocolCompliance(wrapper, req, resp);
assertNull(resp.getEntity());
assertTrue(closed.set || bais.read() == -1);
}
@Test(expected=ClientProtocolException.class) @Test(expected=ClientProtocolException.class)
public void throwsExceptionIfOriginReturnsPartialResponseWhenNotRequested() throws Exception { public void throwsExceptionIfOriginReturnsPartialResponseWhenNotRequested() throws Exception {
final HttpGet req = new HttpGet("http://foo.example.com/"); final HttpGet req = new HttpGet("http://foo.example.com/");
final ClassicHttpRequest wrapper = ClassicRequestCopier.INSTANCE.copy(req); final HttpRequest wrapper = ClassicRequestCopier.INSTANCE.copy(req);
final int nbytes = 128; final int nbytes = 128;
final ClassicHttpResponse resp = makePartialResponse(nbytes); final HttpResponse resp = makePartialResponse(nbytes);
resp.setEntity(HttpTestUtils.makeBody(nbytes));
impl.ensureProtocolCompliance(wrapper, req, resp); impl.ensureProtocolCompliance(wrapper, req, resp);
} }
@Test
public void consumesPartialContentFromOriginEvenIfNotRequested() throws Exception {
final HttpGet req = new HttpGet("http://foo.example.com/");
final ClassicHttpRequest wrapper = ClassicRequestCopier.INSTANCE.copy(req);
final int nbytes = 128;
final ClassicHttpResponse resp = makePartialResponse(nbytes);
final Flag closed = new Flag();
final ByteArrayInputStream bais = makeTrackableBody(nbytes, closed);
resp.setEntity(new InputStreamEntity(bais, -1));
try {
impl.ensureProtocolCompliance(wrapper, req, resp);
} catch (final ClientProtocolException expected) {
}
assertTrue(closed.set || bais.read() == -1);
}
@Test
public void consumesBodyOf100ContinueResponseIfItArrives() throws Exception {
final ClassicHttpRequest req = new BasicClassicHttpRequest("POST", "/");
final int nbytes = 128;
req.setHeader("Content-Length","" + nbytes);
req.setHeader("Content-Type", "application/octet-stream");
final HttpEntity postBody = new ByteArrayEntity(HttpTestUtils.getRandomBytes(nbytes));
req.setEntity(postBody);
final ClassicHttpRequest wrapper = ClassicRequestCopier.INSTANCE.copy(req);
final ClassicHttpResponse resp = new BasicClassicHttpResponse(HttpStatus.SC_CONTINUE, "Continue");
final Flag closed = new Flag();
final ByteArrayInputStream bais = makeTrackableBody(nbytes, closed);
resp.setEntity(new InputStreamEntity(bais, -1));
try {
impl.ensureProtocolCompliance(wrapper, req, resp);
} catch (final ClientProtocolException expected) {
}
assertTrue(closed.set || bais.read() == -1);
}
} }