Skip to content

Commit 6a77093

Browse files
c-clearydfuch
authored andcommitted
8283544: HttpClient GET method adds Content-Length: 0 header
Reviewed-by: dfuchs, jpai
1 parent ac41b78 commit 6a77093

File tree

2 files changed

+242
-14
lines changed

2 files changed

+242
-14
lines changed

src/java.net.http/share/classes/jdk/internal/net/http/Http1Request.java

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -291,21 +291,19 @@ List<ByteBuffer> headers() {
291291
if (uri != null) {
292292
systemHeadersBuilder.setHeader("Host", hostString());
293293
}
294-
if (requestPublisher == null) {
295-
// Not a user request, or maybe a method, e.g. GET, with no body.
296-
contentLength = 0;
297-
} else {
298-
contentLength = requestPublisher.contentLength();
299-
}
300294

301-
if (contentLength == 0) {
302-
systemHeadersBuilder.setHeader("Content-Length", "0");
303-
} else if (contentLength > 0) {
304-
systemHeadersBuilder.setHeader("Content-Length", Long.toString(contentLength));
305-
streaming = false;
306-
} else {
307-
streaming = true;
308-
systemHeadersBuilder.setHeader("Transfer-encoding", "chunked");
295+
// GET, HEAD and DELETE with no request body should not set the Content-Length header
296+
if (requestPublisher != null) {
297+
contentLength = requestPublisher.contentLength();
298+
if (contentLength == 0) {
299+
systemHeadersBuilder.setHeader("Content-Length", "0");
300+
} else if (contentLength > 0) {
301+
systemHeadersBuilder.setHeader("Content-Length", Long.toString(contentLength));
302+
streaming = false;
303+
} else {
304+
streaming = true;
305+
systemHeadersBuilder.setHeader("Transfer-encoding", "chunked");
306+
}
309307
}
310308
collectHeaders0(sb);
311309
String hs = sb.toString();
Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
/*
2+
* Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation.
8+
*
9+
* This code is distributed in the hope that it will be useful, but WITHOUT
10+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12+
* version 2 for more details (a copy is included in the LICENSE file that
13+
* accompanied this code).
14+
*
15+
* You should have received a copy of the GNU General Public License version
16+
* 2 along with this work; if not, write to the Free Software Foundation,
17+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18+
*
19+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20+
* or visit www.oracle.com if you need additional information or have any
21+
* questions.
22+
*/
23+
24+
/*
25+
* @test
26+
* @summary Tests that a Content-length header is not sent for GET requests
27+
* that do not have a body. Also checks that the header is sent when
28+
* a body is present on the GET request.
29+
* @library /test/lib
30+
* @bug 8283544
31+
* @run testng/othervm ContentLengthHeaderTest
32+
*/
33+
34+
35+
import com.sun.net.httpserver.HttpExchange;
36+
import com.sun.net.httpserver.HttpHandler;
37+
import com.sun.net.httpserver.HttpServer;
38+
import org.testng.annotations.AfterTest;
39+
import org.testng.annotations.BeforeTest;
40+
import org.testng.annotations.Test;
41+
42+
import java.io.IOException;
43+
import java.io.InputStream;
44+
import java.io.OutputStream;
45+
import java.io.PrintStream;
46+
import java.net.InetAddress;
47+
import java.net.InetSocketAddress;
48+
import java.net.ProxySelector;
49+
import java.net.URI;
50+
import java.net.URISyntaxException;
51+
import java.net.http.HttpClient;
52+
import java.net.http.HttpRequest;
53+
import java.net.http.HttpResponse;
54+
import jdk.test.lib.net.URIBuilder;
55+
56+
57+
import static java.net.http.HttpClient.Version.HTTP_1_1;
58+
import static java.nio.charset.StandardCharsets.UTF_8;
59+
import static org.testng.Assert.assertEquals;
60+
61+
62+
public class ContentLengthHeaderTest {
63+
64+
final String NO_BODY_PATH = "/no_body";
65+
final String BODY_PATH = "/body";
66+
67+
static HttpServer testContentLengthServer;
68+
static PrintStream testLog = System.err;
69+
70+
HttpClient hc;
71+
URI testContentLengthURI;
72+
73+
@BeforeTest
74+
public void setup() throws IOException, URISyntaxException {
75+
InetSocketAddress sa = new InetSocketAddress(InetAddress.getLoopbackAddress(), 0);
76+
testContentLengthServer = HttpServer.create(sa, 0);
77+
78+
// Create handlers for tests that check for the presence of a Content-length header
79+
testContentLengthServer.createContext(NO_BODY_PATH, new NoContentLengthHandler());
80+
testContentLengthServer.createContext(BODY_PATH, new ContentLengthHandler());
81+
testContentLengthURI = URIBuilder.newBuilder()
82+
.scheme("http")
83+
.loopback()
84+
.port(testContentLengthServer.getAddress().getPort())
85+
.build();
86+
87+
testContentLengthServer.start();
88+
testLog.println("Server up at address: " + testContentLengthServer.getAddress());
89+
testLog.println("Request URI for Client: " + testContentLengthURI);
90+
91+
hc = HttpClient.newBuilder()
92+
.proxy(HttpClient.Builder.NO_PROXY)
93+
.version(HTTP_1_1)
94+
.build();
95+
}
96+
97+
@AfterTest
98+
public void teardown() {
99+
if (testContentLengthServer != null)
100+
testContentLengthServer.stop(0);
101+
}
102+
103+
@Test
104+
// A GET request with no request body should have no Content-length header
105+
public void getWithNoBody() throws IOException, InterruptedException {
106+
testLog.println("Checking GET with no request body");
107+
HttpRequest req = HttpRequest.newBuilder()
108+
.version(HTTP_1_1)
109+
.GET()
110+
.uri(URI.create(testContentLengthURI + NO_BODY_PATH))
111+
.build();
112+
HttpResponse<String> resp = hc.send(req, HttpResponse.BodyHandlers.ofString(UTF_8));
113+
assertEquals(resp.statusCode(), 200, resp.body());
114+
}
115+
116+
@Test
117+
// A GET request with a request body should have a Content-length header
118+
public void getWithBody() throws IOException, InterruptedException {
119+
testLog.println("Checking GET with request body");
120+
HttpRequest req = HttpRequest.newBuilder()
121+
.version(HTTP_1_1)
122+
.method("GET", HttpRequest.BodyPublishers.ofString("GET Body"))
123+
.uri(URI.create(testContentLengthURI + BODY_PATH))
124+
.build();
125+
HttpResponse<String> resp = hc.send(req, HttpResponse.BodyHandlers.ofString(UTF_8));
126+
assertEquals(resp.statusCode(), 200, resp.body());
127+
}
128+
129+
@Test
130+
// A DELETE request with no request body should have no Content-length header
131+
public void deleteWithNoBody() throws IOException, InterruptedException {
132+
testLog.println("Checking DELETE with no request body");
133+
HttpRequest req = HttpRequest.newBuilder()
134+
.version(HTTP_1_1)
135+
.DELETE()
136+
.uri(URI.create(testContentLengthURI + NO_BODY_PATH))
137+
.build();
138+
HttpResponse<String> resp = hc.send(req, HttpResponse.BodyHandlers.ofString(UTF_8));
139+
assertEquals(resp.statusCode(), 200, resp.body());
140+
}
141+
142+
@Test
143+
// A DELETE request with a request body should have a Content-length header
144+
public void deleteWithBody() throws IOException, InterruptedException {
145+
testLog.println("Checking DELETE with request body");
146+
HttpRequest req = HttpRequest.newBuilder()
147+
.version(HTTP_1_1)
148+
.method("DELETE", HttpRequest.BodyPublishers.ofString("DELETE Body"))
149+
.uri(URI.create(testContentLengthURI + BODY_PATH))
150+
.build();
151+
HttpResponse<String> resp = hc.send(req, HttpResponse.BodyHandlers.ofString(UTF_8));
152+
assertEquals(resp.statusCode(), 200, resp.body());
153+
}
154+
155+
@Test
156+
// A HEAD request with no request body should have no Content-length header
157+
public void headWithNoBody() throws IOException, InterruptedException {
158+
testLog.println("Checking HEAD with no request body");
159+
HttpRequest req = HttpRequest.newBuilder()
160+
.version(HTTP_1_1)
161+
.HEAD()
162+
.uri(URI.create(testContentLengthURI + NO_BODY_PATH))
163+
.build();
164+
HttpResponse<String> resp = hc.send(req, HttpResponse.BodyHandlers.ofString(UTF_8));
165+
assertEquals(resp.statusCode(), 200, resp.body());
166+
}
167+
168+
@Test
169+
// A HEAD request with a request body should have a Content-length header
170+
public void headWithBody() throws IOException, InterruptedException {
171+
testLog.println("Checking HEAD with request body");
172+
HttpRequest req = HttpRequest.newBuilder()
173+
.version(HTTP_1_1)
174+
.method("HEAD", HttpRequest.BodyPublishers.ofString("HEAD Body"))
175+
.uri(URI.create(testContentLengthURI + BODY_PATH))
176+
.build();
177+
// Sending this request invokes sendResponseHeaders which emits a warning about including
178+
// a Content-length header with a HEAD request
179+
HttpResponse<String> resp = hc.send(req, HttpResponse.BodyHandlers.ofString(UTF_8));
180+
assertEquals(resp.statusCode(), 200, resp.body());
181+
}
182+
183+
public static void handleResponse(HttpExchange ex, String body, int rCode) throws IOException {
184+
try (InputStream is = ex.getRequestBody();
185+
OutputStream os = ex.getResponseBody()) {
186+
is.readAllBytes();
187+
byte[] bytes = body.getBytes(UTF_8);
188+
ex.sendResponseHeaders(rCode, bytes.length);
189+
os.write(bytes);
190+
}
191+
}
192+
193+
static class NoContentLengthHandler implements HttpHandler {
194+
195+
@Override
196+
public void handle(HttpExchange exchange) throws IOException {
197+
testLog.println("NoContentLengthHandler: Received Headers " + exchange.getRequestHeaders().entrySet() +
198+
" from " + exchange.getRequestMethod() + " request.");
199+
String contentLength = exchange.getRequestHeaders().getFirst("Content-length");
200+
201+
// Check Content-length header was not set
202+
if (contentLength == null) {
203+
handleResponse(exchange, "Request completed",200);
204+
} else {
205+
String responseBody = exchange.getRequestMethod() + " request contained an unexpected " +
206+
"Content-length header of value: " + exchange.getRequestHeaders().getFirst("Content-length");
207+
handleResponse(exchange, responseBody, 400);
208+
}
209+
}
210+
}
211+
212+
static class ContentLengthHandler implements HttpHandler {
213+
214+
@Override
215+
public void handle(HttpExchange exchange) throws IOException {
216+
testLog.println("ContentLengthHandler: Received Headers " + exchange.getRequestHeaders().entrySet() +
217+
" from " + exchange.getRequestMethod() + " request.");
218+
String contentLength = exchange.getRequestHeaders().getFirst("Content-length");
219+
220+
// Check Content-length header was set
221+
if (contentLength != null) {
222+
handleResponse(exchange, "Request completed",200);
223+
} else {
224+
String responseBody = "Expected a Content-length header in " +
225+
exchange.getRequestMethod() + " request but was not present.";
226+
handleResponse(exchange, responseBody, 400);
227+
}
228+
}
229+
}
230+
}

0 commit comments

Comments
 (0)