Skip to content

Commit 1877ff9

Browse files
8331195: Improve com.sun.net.httpserver.HttpExchange usability
Reviewed-by: jpai, dfuchs
1 parent 9d2fa8f commit 1877ff9

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+210
-129
lines changed

src/jdk.httpserver/share/classes/com/sun/net/httpserver/HttpExchange.java

Lines changed: 41 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2005, 2021, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -71,6 +71,27 @@
7171

7272
public abstract class HttpExchange implements AutoCloseable, Request {
7373

74+
/*
75+
* Symbolic values for the responseLength parameter of
76+
* sendResponseHeaders(int,long)
77+
*/
78+
79+
/**
80+
* No response body is being sent with this response
81+
*
82+
* @see #sendResponseHeaders(int, long)
83+
* @since 26
84+
*/
85+
public static final long RSPBODY_EMPTY = -1l;
86+
87+
/**
88+
* The response body length is unspecified and will be chunk encoded
89+
*
90+
* @see #sendResponseHeaders(int, long)
91+
* @since 26
92+
*/
93+
public static final long RSPBODY_CHUNKED = 0;
94+
7495
/**
7596
* Constructor for subclasses to call.
7697
*/
@@ -163,26 +184,36 @@ protected HttpExchange() {
163184
*/
164185
public abstract OutputStream getResponseBody();
165186

166-
167187
/**
168188
* Starts sending the response back to the client using the current set of
169189
* response headers and the numeric response code as specified in this
170190
* method. The response body length is also specified as follows. If the
171191
* response length parameter is greater than {@code zero}, this specifies an
172192
* exact number of bytes to send and the application must send that exact
173-
* amount of data. If the response length parameter is {@code zero}, then
174-
* chunked transfer encoding is used and an arbitrary amount of data may be
193+
* amount of data. If the response length parameter has the value
194+
* {@link #RSPBODY_CHUNKED} (zero) then the response body uses
195+
* chunked transfer encoding and an arbitrary amount of data may be
175196
* sent. The application terminates the response body by closing the
176197
* {@link OutputStream}.
177-
* If response length has the value {@code -1} then no response body is
178-
* being sent.
198+
* If response length has the value {@link #RSPBODY_EMPTY} then no
199+
* response body is being sent.
179200
*
180201
* <p> If the content-length response header has not already been set then
181202
* this is set to the appropriate value depending on the response length
182203
* parameter.
183204
*
184205
* <p> This method must be called prior to calling {@link #getResponseBody()}.
185206
*
207+
* @apiNote If a response body is to be sent from a byte array and the
208+
* length of the array is used as the responseLength parameter, then note
209+
* the behavior in the case when the array is empty. In that case, the
210+
* responseLength will be zero which is the value of {@link #RSPBODY_CHUNKED}
211+
* resulting in a zero length, but chunked encoded response body. While this
212+
* is not incorrect, it may be preferable to check for an empty array and set
213+
* responseLength to {@link #RSPBODY_EMPTY} instead. Also, when sending a
214+
* chunked encoded response body, whatever length, the output stream must
215+
* be explicitly closed by the handler.
216+
*
186217
* @implNote This implementation allows the caller to instruct the
187218
* server to force a connection close after the exchange terminates, by
188219
* supplying a {@code Connection: close} header to the {@linkplain
@@ -193,10 +224,11 @@ protected HttpExchange() {
193224
* @param responseLength if {@literal > 0}, specifies a fixed response body
194225
* length and that exact number of bytes must be written
195226
* to the stream acquired from {@link #getResponseCode()}
196-
* If {@literal == 0}, then chunked encoding is used,
227+
* If equal to {@link #RSPBODY_CHUNKED}, then chunked encoding is used,
197228
* and an arbitrary number of bytes may be written.
198-
* If {@literal <= -1}, then no response body length is
199-
* specified and no response body may be written.
229+
* If equal to {@link #RSPBODY_EMPTY}, then no response body length is
230+
* specified and no response body may be written. Any value {@literal <= -1}
231+
* is treated the same as {@link #RSPBODY_EMPTY}.
200232
* @throws IOException if the response headers have already been sent or an I/O error occurs
201233
* @see HttpExchange#getResponseBody()
202234
*/

src/jdk.httpserver/share/classes/com/sun/net/httpserver/HttpHandlers.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2021, 2025, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -28,6 +28,7 @@
2828
import java.nio.charset.StandardCharsets;
2929
import java.util.Objects;
3030
import java.util.function.Predicate;
31+
import static com.sun.net.httpserver.HttpExchange.RSPBODY_EMPTY;
3132

3233
/**
3334
* Implementations of {@link com.sun.net.httpserver.HttpHandler HttpHandler}
@@ -155,10 +156,10 @@ public static HttpHandler of(int statusCode, Headers headers, String body) {
155156
exchange.getResponseHeaders().putAll(headersCopy);
156157
if (exchange.getRequestMethod().equals("HEAD")) {
157158
exchange.getResponseHeaders().set("Content-Length", Integer.toString(bytes.length));
158-
exchange.sendResponseHeaders(statusCode, -1);
159+
exchange.sendResponseHeaders(statusCode, RSPBODY_EMPTY);
159160
}
160161
else if (bytes.length == 0) {
161-
exchange.sendResponseHeaders(statusCode, -1);
162+
exchange.sendResponseHeaders(statusCode, RSPBODY_EMPTY);
162163
} else {
163164
exchange.sendResponseHeaders(statusCode, bytes.length);
164165
exchange.getResponseBody().write(bytes);

src/jdk.httpserver/share/classes/sun/net/httpserver/AuthFilter.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2006, 2024, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2006, 2025, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -27,6 +27,7 @@
2727

2828
import com.sun.net.httpserver.*;
2929
import java.io.*;
30+
import static com.sun.net.httpserver.HttpExchange.RSPBODY_EMPTY;
3031

3132
public class AuthFilter extends Filter {
3233

@@ -66,11 +67,11 @@ public void doFilter (HttpExchange t, Filter.Chain chain) throws IOException
6667
} else if (r instanceof Authenticator.Retry) {
6768
Authenticator.Retry ry = (Authenticator.Retry)r;
6869
consumeInput (t);
69-
t.sendResponseHeaders (ry.getResponseCode(), -1);
70+
t.sendResponseHeaders (ry.getResponseCode(), RSPBODY_EMPTY);
7071
} else if (r instanceof Authenticator.Failure) {
7172
Authenticator.Failure f = (Authenticator.Failure)r;
7273
consumeInput (t);
73-
t.sendResponseHeaders (f.getResponseCode(), -1);
74+
t.sendResponseHeaders (f.getResponseCode(), RSPBODY_EMPTY);
7475
}
7576
} else {
7677
chain.doFilter (t);

src/jdk.httpserver/share/classes/sun/net/httpserver/ExchangeImpl.java

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@
3838
import java.time.format.DateTimeFormatter;
3939
import java.util.stream.Stream;
4040
import com.sun.net.httpserver.*;
41+
import static com.sun.net.httpserver.HttpExchange.RSPBODY_EMPTY;
42+
import static com.sun.net.httpserver.HttpExchange.RSPBODY_CHUNKED;
4143

4244
class ExchangeImpl {
4345

@@ -227,12 +229,12 @@ public void sendResponseHeaders (int rCode, long contentLen)
227229
||(rCode == 204) /* no content */
228230
||(rCode == 304)) /* not modified */
229231
{
230-
if (contentLen != -1) {
232+
if (contentLen != RSPBODY_EMPTY) {
231233
String msg = "sendResponseHeaders: rCode = "+ rCode
232-
+ ": forcing contentLen = -1";
234+
+ ": forcing contentLen = RSPBODY_EMPTY";
233235
logger.log (Level.WARNING, msg);
234236
}
235-
contentLen = -1;
237+
contentLen = RSPBODY_EMPTY;
236238
noContentLengthHeader = (rCode != 304);
237239
}
238240

@@ -249,7 +251,7 @@ public void sendResponseHeaders (int rCode, long contentLen)
249251
contentLen = 0;
250252
o.setWrappedStream (new FixedLengthOutputStream (this, ros, contentLen));
251253
} else { /* not a HEAD request or 304 response */
252-
if (contentLen == 0) {
254+
if (contentLen == RSPBODY_CHUNKED) {
253255
if (http10) {
254256
o.setWrappedStream (new UndefLengthOutputStream (this, ros));
255257
close = true;
@@ -258,7 +260,7 @@ public void sendResponseHeaders (int rCode, long contentLen)
258260
o.setWrappedStream (new ChunkedOutputStream (this, ros));
259261
}
260262
} else {
261-
if (contentLen == -1) {
263+
if (contentLen == RSPBODY_EMPTY) {
262264
noContentToSend = true;
263265
contentLen = 0;
264266
}

src/jdk.httpserver/share/classes/sun/net/httpserver/simpleserver/FileServerHandler.java

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2005, 2024, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -43,6 +43,7 @@
4343
import com.sun.net.httpserver.HttpHandler;
4444
import com.sun.net.httpserver.HttpHandlers;
4545
import static java.nio.charset.StandardCharsets.UTF_8;
46+
import static com.sun.net.httpserver.HttpExchange.RSPBODY_EMPTY;
4647

4748
/**
4849
* A basic HTTP file server handler for static content.
@@ -122,11 +123,11 @@ private void handleSupportedMethod(HttpExchange exchange, Path path, boolean wri
122123

123124
private void handleMovedPermanently(HttpExchange exchange) throws IOException {
124125
exchange.getResponseHeaders().set("Location", getRedirectURI(exchange.getRequestURI()));
125-
exchange.sendResponseHeaders(301, -1);
126+
exchange.sendResponseHeaders(301, RSPBODY_EMPTY);
126127
}
127128

128129
private void handleForbidden(HttpExchange exchange) throws IOException {
129-
exchange.sendResponseHeaders(403, -1);
130+
exchange.sendResponseHeaders(403, RSPBODY_EMPTY);
130131
}
131132

132133
private void handleNotFound(HttpExchange exchange) throws IOException {
@@ -139,7 +140,7 @@ private void handleNotFound(HttpExchange exchange) throws IOException {
139140

140141
if (exchange.getRequestMethod().equals("HEAD")) {
141142
exchange.getResponseHeaders().set("Content-Length", Integer.toString(bytes.length));
142-
exchange.sendResponseHeaders(404, -1);
143+
exchange.sendResponseHeaders(404, RSPBODY_EMPTY);
143144
} else {
144145
exchange.sendResponseHeaders(404, bytes.length);
145146
try (OutputStream os = exchange.getResponseBody()) {
@@ -260,7 +261,7 @@ private void serveDefaultFavIcon(HttpExchange exchange, boolean writeBody)
260261
}
261262
} else {
262263
respHdrs.set("Content-Length", Integer.toString(bytes.length));
263-
exchange.sendResponseHeaders(200, -1);
264+
exchange.sendResponseHeaders(200, RSPBODY_EMPTY);
264265
}
265266
}
266267
}
@@ -280,7 +281,7 @@ private void serveFile(HttpExchange exchange, Path path, boolean writeBody)
280281
}
281282
} else {
282283
respHdrs.set("Content-Length", Long.toString(Files.size(path)));
283-
exchange.sendResponseHeaders(200, -1);
284+
exchange.sendResponseHeaders(200, RSPBODY_EMPTY);
284285
}
285286
}
286287

@@ -298,7 +299,7 @@ private void listFiles(HttpExchange exchange, Path path, boolean writeBody)
298299
}
299300
} else {
300301
respHdrs.set("Content-Length", Integer.toString(bodyBytes.length));
301-
exchange.sendResponseHeaders(200, -1);
302+
exchange.sendResponseHeaders(200, RSPBODY_EMPTY);
302303
}
303304
}
304305

test/jdk/com/sun/net/httpserver/BasicAuthToken.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -39,6 +39,7 @@
3939
import com.sun.net.httpserver.Authenticator;
4040
import com.sun.net.httpserver.BasicAuthenticator;
4141
import com.sun.net.httpserver.HttpServer;
42+
import static com.sun.net.httpserver.HttpExchange.RSPBODY_EMPTY;
4243

4344
public class BasicAuthToken {
4445
private static final String CRLF = "\r\n";
@@ -59,11 +60,11 @@ static HttpServer server() throws Exception {
5960
HttpServer server = HttpServer.create(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0), 0);
6061
server.createContext(someContext, exchange -> {
6162
if (authenticator.authenticate(exchange) instanceof Authenticator.Failure) {
62-
exchange.sendResponseHeaders(401, -1);
63+
exchange.sendResponseHeaders(401, RSPBODY_EMPTY);
6364
exchange.close();
6465
return;
6566
}
66-
exchange.sendResponseHeaders(200, -1);
67+
exchange.sendResponseHeaders(200, RSPBODY_EMPTY);
6768
exchange.close();
6869
}).setAuthenticator(authenticator);
6970
server.start();

test/jdk/com/sun/net/httpserver/EchoHandler.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2005, 2019, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -29,6 +29,8 @@
2929
import com.sun.net.httpserver.Headers;
3030
import com.sun.net.httpserver.HttpExchange;
3131
import com.sun.net.httpserver.HttpHandler;
32+
import static com.sun.net.httpserver.HttpExchange.RSPBODY_EMPTY;
33+
import static com.sun.net.httpserver.HttpExchange.RSPBODY_CHUNKED;
3234

3335
/**
3436
* Implements a basic static EchoHandler for an HTTP server
@@ -50,9 +52,9 @@ public void handle (HttpExchange t)
5052
in = Integer.toString(in.length).getBytes(StandardCharsets.UTF_8);
5153
}
5254
if (fixedrequest != null) {
53-
t.sendResponseHeaders(200, in.length == 0 ? -1 : in.length);
55+
t.sendResponseHeaders(200, in.length == 0 ? RSPBODY_EMPTY : in.length);
5456
} else {
55-
t.sendResponseHeaders(200, 0);
57+
t.sendResponseHeaders(200, RSPBODY_CHUNKED);
5658
}
5759
os.write(in);
5860
close(t, os);

test/jdk/com/sun/net/httpserver/ExchangeAttributeTest.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
import java.util.logging.Level;
5252
import java.util.logging.Logger;
5353

54+
import static com.sun.net.httpserver.HttpExchange.RSPBODY_EMPTY;
5455
import static java.net.http.HttpClient.Builder.NO_PROXY;
5556
import static org.junit.jupiter.api.Assertions.*;
5657

@@ -117,10 +118,10 @@ public void handle(HttpExchange exchange) throws IOException {
117118
}
118119
exchange.setAttribute("attr", null);
119120
assertNull(exchange.getAttribute("attr"));
120-
exchange.sendResponseHeaders(200, -1);
121+
exchange.sendResponseHeaders(200, RSPBODY_EMPTY);
121122
} catch (Throwable t) {
122123
t.printStackTrace();
123-
exchange.sendResponseHeaders(500, -1);
124+
exchange.sendResponseHeaders(500, RSPBODY_EMPTY);
124125
}
125126
}
126127
}

test/jdk/com/sun/net/httpserver/FileServerHandler.java

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525
import java.net.*;
2626

2727
import com.sun.net.httpserver.*;
28+
import static com.sun.net.httpserver.HttpExchange.RSPBODY_EMPTY;
29+
import static com.sun.net.httpserver.HttpExchange.RSPBODY_CHUNKED;
2830

2931
/**
3032
* Implements a basic static content HTTP file server handler
@@ -65,10 +67,10 @@ public void handle (HttpExchange t)
6567
String method = t.getRequestMethod();
6668
if (method.equals ("HEAD")) {
6769
rmap.set ("Content-Length", Long.toString (f.length()));
68-
t.sendResponseHeaders (200, -1);
70+
t.sendResponseHeaders (200, RSPBODY_EMPTY);
6971
t.close();
7072
} else if (!method.equals("GET")) {
71-
t.sendResponseHeaders (405, -1);
73+
t.sendResponseHeaders (405, RSPBODY_EMPTY);
7274
t.close();
7375
return;
7476
}
@@ -84,7 +86,7 @@ public void handle (HttpExchange t)
8486
return;
8587
}
8688
rmap.set ("Content-Type", "text/html");
87-
t.sendResponseHeaders (200, 0);
89+
t.sendResponseHeaders (200, RSPBODY_CHUNKED);
8890
String[] list = f.list();
8991
try (final OutputStream os = t.getResponseBody();
9092
final PrintStream p = new PrintStream (os)) {
@@ -127,13 +129,13 @@ void moved (HttpExchange t) throws IOException {
127129
String location = "http://"+host+uri.getPath() + "/";
128130
map.set ("Content-Type", "text/html");
129131
map.set ("Location", location);
130-
t.sendResponseHeaders (301, -1);
132+
t.sendResponseHeaders (301, RSPBODY_EMPTY);
131133
t.close();
132134
}
133135

134136
void notfound (HttpExchange t, String p) throws IOException {
135137
t.getResponseHeaders().set ("Content-Type", "text/html");
136-
t.sendResponseHeaders (404, 0);
138+
t.sendResponseHeaders (404, RSPBODY_CHUNKED);
137139
OutputStream os = t.getResponseBody();
138140
String s = "<h2>File not found</h2>";
139141
s = s + p + "<p>";

0 commit comments

Comments
 (0)