Improve handling of the Content-Length header

Previously, some MockMvc-specific logic would add a Content-Length
header to every request that had content. This led to the curl request
snippet containing a -H option for the Content-Length header. This is
unnecessary as curl will automatically generate a Content-Length
header based on the data that's being sent to the server. A secondary
problem was the inconsistent automatic addition of a Content-Length
header; the header was not automatically added to responses.

This commit remove the MockMvc-specific logic in favour of some new
logic in the core project to automatically add a Content-Length header
to both requests and responses. The curl request snippet has been
updated to supress the header in favour of curl's automatic
generation.

Closes gh-111
This commit is contained in:
Andy Wilkinson
2015-09-28 11:14:58 +01:00
parent cc0edcb901
commit 5da4bee3c6
11 changed files with 81 additions and 67 deletions

View File

@@ -115,8 +115,10 @@ public class CurlRequestSnippet extends TemplatedSnippet {
private void writeHeaders(HttpHeaders headers, PrintWriter writer) {
for (Entry<String, List<String>> entry : headers.entrySet()) {
for (String header : entry.getValue()) {
writer.print(String.format(" -H '%s: %s'", entry.getKey(), header));
if (!HttpHeaders.CONTENT_LENGTH.equalsIgnoreCase(entry.getKey())) {
for (String header : entry.getValue()) {
writer.print(String.format(" -H '%s: %s'", entry.getKey(), header));
}
}
}
}

View File

@@ -35,7 +35,18 @@ abstract class AbstractOperationMessage {
AbstractOperationMessage(byte[] content, HttpHeaders headers) {
this.content = content == null ? new byte[0] : content;
this.headers = headers;
this.headers = createHeaders(content, headers);
}
private static HttpHeaders createHeaders(byte[] content, HttpHeaders input) {
HttpHeaders headers = new HttpHeaders();
if (input != null) {
headers.putAll(input);
}
if (content != null && content.length > 0 && headers.getContentLength() == -1) {
headers.setContentLength(content.length);
}
return headers;
}
public byte[] getContent() {

View File

@@ -63,9 +63,12 @@ public class ContentModifyingOperationPreprocessor implements OperationPreproces
private HttpHeaders getUpdatedHeaders(HttpHeaders headers, byte[] updatedContent) {
HttpHeaders updatedHeaders = new HttpHeaders();
updatedHeaders.putAll(headers);
if (updatedHeaders.getContentLength() > -1) {
if (updatedContent.length > 0) {
updatedHeaders.setContentLength(updatedContent.length);
}
else {
updatedHeaders.remove(HttpHeaders.CONTENT_LENGTH);
}
return updatedHeaders;
}

View File

@@ -75,30 +75,33 @@ public class HttpRequestSnippetTests {
@Test
public void postRequestWithContent() throws IOException {
String content = "Hello, world";
this.snippet.expectHttpRequest("post-request-with-content").withContents(
httpRequest(RequestMethod.POST, "/foo").header(HttpHeaders.HOST,
"localhost").content("Hello, world"));
httpRequest(RequestMethod.POST, "/foo")
.header(HttpHeaders.HOST, "localhost").content(content)
.header(HttpHeaders.CONTENT_LENGTH, content.getBytes().length));
new HttpRequestSnippet().document(new OperationBuilder(
"post-request-with-content", this.snippet.getOutputDirectory())
.request("http://localhost/foo").method("POST").content("Hello, world")
.build());
.request("http://localhost/foo").method("POST").content(content).build());
}
@Test
public void postRequestWithCharset() throws IOException {
String japaneseContent = "\u30b3\u30f3\u30c6\u30f3\u30c4";
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.CONTENT_LENGTH, contentBytes.length)
.content(japaneseContent));
new HttpRequestSnippet().document(new OperationBuilder(
"post-request-with-charset", this.snippet.getOutputDirectory())
.request("http://localhost/foo").method("POST")
.header("Content-Type", "text/plain;charset=UTF-8")
.content(japaneseContent.getBytes("UTF-8")).build());
.header("Content-Type", "text/plain;charset=UTF-8").content(contentBytes)
.build());
}
@Test
@@ -117,14 +120,15 @@ public class HttpRequestSnippetTests {
@Test
public void putRequestWithContent() throws IOException {
String content = "Hello, world";
this.snippet.expectHttpRequest("put-request-with-content").withContents(
httpRequest(RequestMethod.PUT, "/foo").header(HttpHeaders.HOST,
"localhost").content("Hello, world"));
httpRequest(RequestMethod.PUT, "/foo")
.header(HttpHeaders.HOST, "localhost").content(content)
.header(HttpHeaders.CONTENT_LENGTH, content.getBytes().length));
new HttpRequestSnippet().document(new OperationBuilder(
"put-request-with-content", this.snippet.getOutputDirectory())
.request("http://localhost/foo").method("PUT").content("Hello, world")
.build());
.request("http://localhost/foo").method("PUT").content(content).build());
}
@Test

View File

@@ -78,22 +78,27 @@ public class HttpResponseSnippetTests {
@Test
public void responseWithContent() throws IOException {
String content = "content";
this.snippet.expectHttpResponse("response-with-content").withContents(
httpResponse(HttpStatus.OK).content("content"));
httpResponse(HttpStatus.OK).content(content).header(
HttpHeaders.CONTENT_LENGTH, content.getBytes().length));
new HttpResponseSnippet().document(new OperationBuilder("response-with-content",
this.snippet.getOutputDirectory()).response().content("content").build());
this.snippet.getOutputDirectory()).response().content(content).build());
}
@Test
public void responseWithCharset() throws IOException {
String japaneseContent = "\u30b3\u30f3\u30c6\u30f3\u30c4";
byte[] contentBytes = japaneseContent.getBytes("UTF-8");
this.snippet.expectHttpResponse("response-with-charset").withContents(
httpResponse(HttpStatus.OK).header("Content-Type",
"text/plain;charset=UTF-8").content(japaneseContent));
httpResponse(HttpStatus.OK)
.header("Content-Type", "text/plain;charset=UTF-8")
.content(japaneseContent)
.header(HttpHeaders.CONTENT_LENGTH, contentBytes.length));
new HttpResponseSnippet().document(new OperationBuilder("response-with-charset",
this.snippet.getOutputDirectory()).response()
.header("Content-Type", "text/plain;charset=UTF-8")
.content(japaneseContent.getBytes("UTF-8")).build());
.header("Content-Type", "text/plain;charset=UTF-8").content(contentBytes)
.build());
}
@Test

View File

@@ -71,16 +71,6 @@ public class ContentModifyingOperationPreprocessorTests {
assertThat(preprocessed.getContent(), is(equalTo("modified".getBytes())));
}
@Test
public void unknownContentLengthIsUnchanged() {
StandardOperationRequest request = new StandardOperationRequest(
URI.create("http://localhost"), HttpMethod.GET, "content".getBytes(),
new HttpHeaders(), new Parameters(),
Collections.<OperationRequestPart>emptyList());
OperationRequest preprocessed = this.preprocessor.preprocess(request);
assertThat(preprocessed.getHeaders().getContentLength(), is(equalTo(-1L)));
}
@Test
public void contentLengthIsUpdated() {
HttpHeaders httpHeaders = new HttpHeaders();

View File

@@ -164,6 +164,12 @@ public final class SnippetMatchers {
return (T) this;
}
@SuppressWarnings("unchecked")
public T header(String name, long value) {
this.addLine(this.headerOffset++, name + ": " + value);
return (T) this;
}
}
/**