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

@@ -33,10 +33,10 @@ import org.springframework.http.MediaType;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockMultipartHttpServletRequest;
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.Parameters;
import org.springframework.restdocs.operation.StandardOperationRequest;
import org.springframework.restdocs.operation.StandardOperationRequestPart;
import org.springframework.util.FileCopyUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;
@@ -67,7 +67,7 @@ class MockMvcOperationRequestFactory {
* @return the {@code OperationRequest}
* @throws Exception if the request could not be created
*/
public OperationRequest createOperationRequest(MockHttpServletRequest mockRequest)
OperationRequest createOperationRequest(MockHttpServletRequest mockRequest)
throws Exception {
HttpHeaders headers = extractHeaders(mockRequest);
Parameters parameters = extractParameters(mockRequest);
@@ -76,8 +76,9 @@ class MockMvcOperationRequestFactory {
if (!StringUtils.hasText(queryString) && "GET".equals(mockRequest.getMethod())) {
queryString = parameters.toQueryString();
}
return new StandardOperationRequest(URI.create(getRequestUri(mockRequest)
+ (StringUtils.hasText(queryString) ? "?" + queryString : "")),
return new OperationRequestFactory().create(
URI.create(getRequestUri(mockRequest)
+ (StringUtils.hasText(queryString) ? "?" + queryString : "")),
HttpMethod.valueOf(mockRequest.getMethod()),
FileCopyUtils.copyToByteArray(mockRequest.getInputStream()), headers,
parameters, parts);
@@ -102,16 +103,17 @@ class MockMvcOperationRequestFactory {
return parts;
}
private StandardOperationRequestPart createOperationRequestPart(Part part)
throws IOException {
private OperationRequestPart createOperationRequestPart(Part part) throws IOException {
HttpHeaders partHeaders = extractHeaders(part);
List<String> contentTypeHeader = partHeaders.get(HttpHeaders.CONTENT_TYPE);
if (part.getContentType() != null && contentTypeHeader == null) {
partHeaders.setContentType(MediaType.parseMediaType(part.getContentType()));
}
return new StandardOperationRequestPart(part.getName(), StringUtils.hasText(part
.getSubmittedFileName()) ? part.getSubmittedFileName() : null,
FileCopyUtils.copyToByteArray(part.getInputStream()), partHeaders);
return new OperationRequestPartFactory()
.create(part.getName(),
StringUtils.hasText(part.getSubmittedFileName()) ? part
.getSubmittedFileName() : null, FileCopyUtils
.copyToByteArray(part.getInputStream()), partHeaders);
}
private List<OperationRequestPart> extractMultipartRequestParts(
@@ -126,14 +128,14 @@ class MockMvcOperationRequestFactory {
return parts;
}
private StandardOperationRequestPart createOperationRequestPart(MultipartFile file)
private OperationRequestPart createOperationRequestPart(MultipartFile file)
throws IOException {
HttpHeaders partHeaders = new HttpHeaders();
if (StringUtils.hasText(file.getContentType())) {
partHeaders.setContentType(MediaType.parseMediaType(file.getContentType()));
}
return new StandardOperationRequestPart(file.getName(), StringUtils.hasText(file
.getOriginalFilename()) ? file.getOriginalFilename() : null,
return new OperationRequestPartFactory().create(file.getName(), StringUtils
.hasText(file.getOriginalFilename()) ? file.getOriginalFilename() : null,
file.getBytes(), partHeaders);
}

View File

@@ -20,7 +20,7 @@ import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.restdocs.operation.OperationResponse;
import org.springframework.restdocs.operation.StandardOperationResponse;
import org.springframework.restdocs.operation.OperationResponseFactory;
/**
* A factory for creating an {@link OperationResponse} derived from a
@@ -36,8 +36,8 @@ class MockMvcOperationResponseFactory {
* @param mockResponse the response
* @return the {@code OperationResponse}
*/
public OperationResponse createOperationResponse(MockHttpServletResponse mockResponse) {
return new StandardOperationResponse(
OperationResponse createOperationResponse(MockHttpServletResponse mockResponse) {
return new OperationResponseFactory().create(
HttpStatus.valueOf(mockResponse.getStatus()),
extractHeaders(mockResponse), mockResponse.getContentAsByteArray());
}

View File

@@ -269,33 +269,31 @@ public class MockMvcRestDocumentationIntegrationTests {
.andDo(document("original-request"))
.andDo(document(
"preprocessed-request",
preprocessRequest(prettyPrint(), removeHeaders("a"),
preprocessRequest(
prettyPrint(),
removeHeaders("a", HttpHeaders.HOST,
HttpHeaders.CONTENT_LENGTH),
replacePattern(pattern, "\"<<beta>>\""))));
assertThat(
new File("build/generated-snippets/original-request/http-request.adoc"),
is(snippet().withContents(
httpRequest(RequestMethod.GET, "/").header("Host", "localhost")
.header("a", "alpha").header("b", "bravo")
httpRequest(RequestMethod.GET, "/").header("a", "alpha")
.header("b", "bravo")
.header("Content-Type", "application/json")
.header("Accept", MediaType.APPLICATION_JSON_VALUE)
.header("Host", "localhost")
.header("Content-Length", "13")
.content("{\"a\":\"alpha\"}"))));
String prettyPrinted = String.format("{%n \"a\" : \"<<beta>>\"%n}");
assertThat(
new File(
"build/generated-snippets/preprocessed-request/http-request.adoc"),
is(snippet()
.withContents(
httpRequest(RequestMethod.GET, "/")
.header("Host", "localhost")
.header("b", "bravo")
.header("Content-Type", "application/json")
.header("Accept",
MediaType.APPLICATION_JSON_VALUE)
.header("Content-Length",
Integer.toString(prettyPrinted.getBytes().length))
.content(prettyPrinted))));
is(snippet().withContents(
httpRequest(RequestMethod.GET, "/").header("b", "bravo")
.header("Content-Type", "application/json")
.header("Accept", MediaType.APPLICATION_JSON_VALUE)
.content(prettyPrinted))));
}
@Test