Improve handling of Host header so a preprocessor can remove it

Previously, the Host header was treated specially in the HTTP request
snippet. If no Host header was specified, one would always be added
prior to producing the snippet. This ensured that the snippet was
valid (an HTTP 1.1 request must include a Host header), but came at
the cost of some confusion about why a preprocessor could not remove
it.

This commit updates the special treatment of the Host header so that
it's now performed in a central location so that all of the snippets
can benefit from a Host header being added if one isn't provided.
The handling of the Content-Length header has also been reworked so
that it's performed in the same location. The curl request snippet
has been updated so that it doesn't include setting the Host header
on the command line; it's unnecessary as curl will automatically
include a Host header in the request. The documentation of the
Host header's special treatment (made in 2fc0420) that noted that it
was unaffected by preprocessing has been reverted.

Closes gh-134
This commit is contained in:
Andy Wilkinson
2015-09-28 15:16:20 +01:00
parent 5da4bee3c6
commit 535bea24f9
23 changed files with 466 additions and 139 deletions

View File

@@ -54,8 +54,8 @@ public class HttpRequestSnippetTests {
@Test
public void getRequest() throws IOException {
this.snippet.expectHttpRequest("get-request").withContents(
httpRequest(RequestMethod.GET, "/foo").header(HttpHeaders.HOST,
"localhost").header("Alpha", "a"));
httpRequest(RequestMethod.GET, "/foo").header("Alpha", "a").header(
HttpHeaders.HOST, "localhost"));
new HttpRequestSnippet().document(new OperationBuilder("get-request",
this.snippet.getOutputDirectory()).request("http://localhost/foo")
@@ -92,8 +92,8 @@ public class HttpRequestSnippetTests {
byte[] contentBytes = japaneseContent.getBytes("UTF-8");
this.snippet.expectHttpRequest("post-request-with-charset").withContents(
httpRequest(RequestMethod.POST, "/foo")
.header(HttpHeaders.HOST, "localhost")
.header("Content-Type", "text/plain;charset=UTF-8")
.header(HttpHeaders.HOST, "localhost")
.header(HttpHeaders.CONTENT_LENGTH, contentBytes.length)
.content(japaneseContent));
@@ -151,10 +151,9 @@ public class HttpRequestSnippetTests {
+ "form-data; " + "name=image%n%n<< data >>"));
this.snippet.expectHttpRequest("multipart-post").withContents(
httpRequest(RequestMethod.POST, "/upload")
.header(HttpHeaders.HOST, "localhost")
.header("Content-Type",
"multipart/form-data; boundary=" + BOUNDARY)
.content(expectedContent));
.header(HttpHeaders.HOST, "localhost").content(expectedContent));
new HttpRequestSnippet().document(new OperationBuilder("multipart-post",
this.snippet.getOutputDirectory()).request("http://localhost/upload")
.method("POST")
@@ -175,10 +174,9 @@ public class HttpRequestSnippetTests {
String expectedContent = param1Part + param2Part + param3Part + filePart;
this.snippet.expectHttpRequest("multipart-post-with-parameters").withContents(
httpRequest(RequestMethod.POST, "/upload")
.header(HttpHeaders.HOST, "localhost")
.header("Content-Type",
"multipart/form-data; boundary=" + BOUNDARY)
.content(expectedContent));
.header(HttpHeaders.HOST, "localhost").content(expectedContent));
new HttpRequestSnippet().document(new OperationBuilder(
"multipart-post-with-parameters", this.snippet.getOutputDirectory())
.request("http://localhost/upload").method("POST")
@@ -194,10 +192,9 @@ public class HttpRequestSnippetTests {
+ "image/png%n%n<< data >>"));
this.snippet.expectHttpRequest("multipart-post-with-content-type").withContents(
httpRequest(RequestMethod.POST, "/upload")
.header(HttpHeaders.HOST, "localhost")
.header("Content-Type",
"multipart/form-data; boundary=" + BOUNDARY)
.content(expectedContent));
.header(HttpHeaders.HOST, "localhost").content(expectedContent));
new HttpRequestSnippet().document(new OperationBuilder(
"multipart-post-with-content-type", this.snippet.getOutputDirectory())
.request("http://localhost/upload").method("POST")

View File

@@ -26,7 +26,8 @@ import org.junit.rules.ExpectedException;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.restdocs.operation.StandardOperationResponse;
import org.springframework.restdocs.operation.OperationResponse;
import org.springframework.restdocs.operation.OperationResponseFactory;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
@@ -38,13 +39,15 @@ import static org.mockito.Mockito.verify;
*/
public class ContentTypeLinkExtractorTests {
private final OperationResponseFactory responseFactory = new OperationResponseFactory();
@Rule
public ExpectedException thrown = ExpectedException.none();
@Test
public void extractionFailsWithNullContentType() throws IOException {
this.thrown.expect(IllegalStateException.class);
new ContentTypeLinkExtractor().extractLinks(new StandardOperationResponse(
new ContentTypeLinkExtractor().extractLinks(this.responseFactory.create(
HttpStatus.OK, new HttpHeaders(), null));
}
@@ -55,7 +58,7 @@ public class ContentTypeLinkExtractorTests {
extractors.put(MediaType.APPLICATION_JSON, extractor);
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(MediaType.APPLICATION_JSON);
StandardOperationResponse response = new StandardOperationResponse(HttpStatus.OK,
OperationResponse response = this.responseFactory.create(HttpStatus.OK,
httpHeaders, null);
new ContentTypeLinkExtractor(extractors).extractLinks(response);
verify(extractor).extractLinks(response);
@@ -68,7 +71,7 @@ public class ContentTypeLinkExtractorTests {
extractors.put(MediaType.APPLICATION_JSON, extractor);
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(MediaType.parseMediaType("application/json;foo=bar"));
StandardOperationResponse response = new StandardOperationResponse(HttpStatus.OK,
OperationResponse response = this.responseFactory.create(HttpStatus.OK,
httpHeaders, null);
new ContentTypeLinkExtractor(extractors).extractLinks(response);
verify(extractor).extractLinks(response);

View File

@@ -30,7 +30,7 @@ import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
import org.springframework.http.HttpStatus;
import org.springframework.restdocs.operation.OperationResponse;
import org.springframework.restdocs.operation.StandardOperationResponse;
import org.springframework.restdocs.operation.OperationResponseFactory;
import org.springframework.util.FileCopyUtils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
@@ -46,6 +46,8 @@ import static org.junit.Assert.assertEquals;
@RunWith(Parameterized.class)
public class LinkExtractorsPayloadTests {
private final OperationResponseFactory responseFactory = new OperationResponseFactory();
private final LinkExtractor linkExtractor;
private final String linkType;
@@ -107,7 +109,7 @@ public class LinkExtractorsPayloadTests {
}
private OperationResponse createResponse(String contentName) throws IOException {
return new StandardOperationResponse(HttpStatus.OK, null,
return this.responseFactory.create(HttpStatus.OK, null,
FileCopyUtils.copyToByteArray(getPayloadFile(contentName)));
}

View File

@@ -25,11 +25,11 @@ import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.restdocs.operation.OperationRequest;
import org.springframework.restdocs.operation.OperationRequestFactory;
import org.springframework.restdocs.operation.OperationRequestPart;
import org.springframework.restdocs.operation.OperationResponse;
import org.springframework.restdocs.operation.OperationResponseFactory;
import org.springframework.restdocs.operation.Parameters;
import org.springframework.restdocs.operation.StandardOperationRequest;
import org.springframework.restdocs.operation.StandardOperationResponse;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.is;
@@ -43,6 +43,10 @@ import static org.junit.Assert.assertThat;
*/
public class ContentModifyingOperationPreprocessorTests {
private final OperationRequestFactory requestFactory = new OperationRequestFactory();
private final OperationResponseFactory responseFactory = new OperationResponseFactory();
private final ContentModifyingOperationPreprocessor preprocessor = new ContentModifyingOperationPreprocessor(
new ContentModifier() {
@@ -55,7 +59,7 @@ public class ContentModifyingOperationPreprocessorTests {
@Test
public void modifyRequestContent() {
StandardOperationRequest request = new StandardOperationRequest(
OperationRequest request = this.requestFactory.create(
URI.create("http://localhost"), HttpMethod.GET, "content".getBytes(),
new HttpHeaders(), new Parameters(),
Collections.<OperationRequestPart>emptyList());
@@ -65,7 +69,7 @@ public class ContentModifyingOperationPreprocessorTests {
@Test
public void modifyResponseContent() {
StandardOperationResponse response = new StandardOperationResponse(HttpStatus.OK,
OperationResponse response = this.responseFactory.create(HttpStatus.OK,
new HttpHeaders(), "content".getBytes());
OperationResponse preprocessed = this.preprocessor.preprocess(response);
assertThat(preprocessed.getContent(), is(equalTo("modified".getBytes())));
@@ -75,7 +79,7 @@ public class ContentModifyingOperationPreprocessorTests {
public void contentLengthIsUpdated() {
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentLength(7);
StandardOperationRequest request = new StandardOperationRequest(
OperationRequest request = this.requestFactory.create(
URI.create("http://localhost"), HttpMethod.GET, "content".getBytes(),
httpHeaders, new Parameters(),
Collections.<OperationRequestPart>emptyList());

View File

@@ -25,11 +25,11 @@ import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.restdocs.operation.OperationRequest;
import org.springframework.restdocs.operation.OperationRequestFactory;
import org.springframework.restdocs.operation.OperationRequestPart;
import org.springframework.restdocs.operation.OperationResponse;
import org.springframework.restdocs.operation.OperationResponseFactory;
import org.springframework.restdocs.operation.Parameters;
import org.springframework.restdocs.operation.StandardOperationRequest;
import org.springframework.restdocs.operation.StandardOperationResponse;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.is;
@@ -44,23 +44,29 @@ import static org.junit.Assert.assertThat;
*/
public class HeaderRemovingOperationPreprocessorTests {
private final OperationRequestFactory requestFactory = new OperationRequestFactory();
private final OperationResponseFactory responseFactory = new OperationResponseFactory();
private final HeaderRemovingOperationPreprocessor preprocessor = new HeaderRemovingOperationPreprocessor(
"b");
@Test
public void modifyRequestHeaders() {
StandardOperationRequest request = new StandardOperationRequest(
OperationRequest request = this.requestFactory.create(
URI.create("http://localhost"), HttpMethod.GET, new byte[0],
getHttpHeaders(), new Parameters(),
Collections.<OperationRequestPart>emptyList());
OperationRequest preprocessed = this.preprocessor.preprocess(request);
assertThat(preprocessed.getHeaders().size(), is(equalTo(1)));
assertThat(preprocessed.getHeaders().size(), is(equalTo(2)));
assertThat(preprocessed.getHeaders(), hasEntry("a", Arrays.asList("alpha")));
assertThat(preprocessed.getHeaders(),
hasEntry("Host", Arrays.asList("localhost")));
}
@Test
public void modifyResponseHeaders() {
StandardOperationResponse response = new StandardOperationResponse(HttpStatus.OK,
OperationResponse response = this.responseFactory.create(HttpStatus.OK,
getHttpHeaders(), new byte[0]);
OperationResponse preprocessed = this.preprocessor.preprocess(response);
assertThat(preprocessed.getHeaders().size(), is(equalTo(1)));

View File

@@ -29,13 +29,13 @@ import org.springframework.http.HttpStatus;
import org.springframework.restdocs.RestDocumentationContext;
import org.springframework.restdocs.operation.Operation;
import org.springframework.restdocs.operation.OperationRequest;
import org.springframework.restdocs.operation.OperationRequestFactory;
import org.springframework.restdocs.operation.OperationRequestPart;
import org.springframework.restdocs.operation.OperationRequestPartFactory;
import org.springframework.restdocs.operation.OperationResponse;
import org.springframework.restdocs.operation.OperationResponseFactory;
import org.springframework.restdocs.operation.Parameters;
import org.springframework.restdocs.operation.StandardOperation;
import org.springframework.restdocs.operation.StandardOperationRequest;
import org.springframework.restdocs.operation.StandardOperationRequestPart;
import org.springframework.restdocs.operation.StandardOperationResponse;
import org.springframework.restdocs.snippet.RestDocumentationContextPlaceholderResolver;
import org.springframework.restdocs.snippet.StandardWriterResolver;
import org.springframework.restdocs.snippet.WriterResolver;
@@ -122,7 +122,7 @@ public class OperationBuilder {
for (OperationRequestPartBuilder builder : this.partBuilders) {
parts.add(builder.buildPart());
}
return new StandardOperationRequest(this.requestUri, this.method,
return new OperationRequestFactory().create(this.requestUri, this.method,
this.content, this.headers, this.parameters, parts);
}
@@ -196,7 +196,7 @@ public class OperationBuilder {
}
private OperationRequestPart buildPart() {
return new StandardOperationRequestPart(this.name,
return new OperationRequestPartFactory().create(this.name,
this.submittedFileName, this.content, this.headers);
}
@@ -219,7 +219,8 @@ public class OperationBuilder {
private byte[] content = new byte[0];
private OperationResponse buildResponse() {
return new StandardOperationResponse(this.status, this.headers, this.content);
return new OperationResponseFactory().create(this.status, this.headers,
this.content);
}
public OperationResponseBuilder status(int status) {