Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
handle manual 1xx responses including make connection upgrade generic…
… rather than websocket specific
  • Loading branch information
robaho committed Oct 8, 2025
commit 2de1c20d9812b7c520b108115142086e99b7167e
3 changes: 3 additions & 0 deletions src/main/java/robaho/net/httpserver/Code.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
public class Code {

public static final int HTTP_CONTINUE = 100;
public static final int HTTP_SWITCHING_PROTOCOLS = 101;
public static final int HTTP_OK = 200;
public static final int HTTP_CREATED = 201;
public static final int HTTP_ACCEPTED = 202;
Expand Down Expand Up @@ -71,6 +72,8 @@ static String msg(int code) {
return " OK";
case HTTP_CONTINUE:
return " Continue";
case HTTP_SWITCHING_PROTOCOLS:
return " Switching Protocols";
case HTTP_CREATED:
return " Created";
case HTTP_ACCEPTED:
Expand Down
49 changes: 24 additions & 25 deletions src/main/java/robaho/net/httpserver/ExchangeImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,6 @@

import com.sun.net.httpserver.*;

import robaho.net.httpserver.websockets.WebSocketHandler;

class ExchangeImpl {

Headers reqHdrs, rspHdrs;
Expand Down Expand Up @@ -69,7 +67,8 @@ class ExchangeImpl {

private static final String HEAD = "HEAD";
private static final String CONNECT = "CONNECT";

private static final String HEADER_CONNECTION = "Connection";
private static final String HEADER_CONNECTION_UPGRADE = "Upgrade";
/*
* streams which take care of the HTTP protocol framing
* and are passed up to higher layers
Expand All @@ -85,7 +84,7 @@ class ExchangeImpl {
Map<String, Object> attributes;
int rcode = -1;
HttpPrincipal principal;
final boolean websocket;
boolean connectionUpgraded = false;

ExchangeImpl(
String m, URI u, Request req, long len, HttpConnection connection) throws IOException {
Expand All @@ -97,11 +96,6 @@ class ExchangeImpl {
this.method = m;
this.uri = u;
this.connection = connection;
this.websocket = WebSocketHandler.isWebsocketRequested(this.reqHdrs);
if (this.websocket) {
// length is indeterminate
len = -1;
}
this.reqContentLen = len;
/* ros only used for headers, body written directly to stream */
this.ros = req.outputStream();
Expand Down Expand Up @@ -135,6 +129,9 @@ private boolean isHeadRequest() {
private boolean isConnectRequest() {
return CONNECT.equals(getRequestMethod());
}
private boolean isUpgradeRequest() {
return HEADER_CONNECTION_UPGRADE.equalsIgnoreCase(reqHdrs.getFirst(HEADER_CONNECTION));
}

public void close() {
if (closed) {
Expand Down Expand Up @@ -170,7 +167,7 @@ public InputStream getRequestBody() {
if (uis != null) {
return uis;
}
if (websocket || isConnectRequest()) {
if (connectionUpgraded || isConnectRequest() || isUpgradeRequest()) {
// connection cannot be re-used
uis = ris;
} else if (reqContentLen == -1L) {
Expand Down Expand Up @@ -232,7 +229,6 @@ public void sendResponseHeaders(int rCode, long contentLen)
ros.write(statusLine.getBytes(ISO_CHARSET));
boolean noContentToSend = false; // assume there is content
boolean noContentLengthHeader = false; // must not send Content-length is set
rspHdrs.set("Date", ActivityTimer.dateAndTime());

Integer bufferSize = (Integer)this.getAttribute(Attributes.SOCKET_WRITE_BUFFER);
if(bufferSize!=null) {
Expand All @@ -245,18 +241,17 @@ public void sendResponseHeaders(int rCode, long contentLen)
var informational = rCode >= 100 && rCode < 200;

if (informational) {

if (rCode == 101) {
logger.log(Level.DEBUG, () -> "switching protocols");
if (contentLen != 0) {
String msg = "sendResponseHeaders: rCode = " + rCode
+ ": forcing contentLen = 0";
logger.log(Level.WARNING, msg);
contentLen = 0;
}
connectionUpgraded = true;
}
if (contentLen != 0) {
String msg = "sendResponseHeaders: rCode = " + rCode
+ ": forcing contentLen = 0";
logger.log(Level.WARNING, msg);
}
contentLen = 0;
flush = true;

noContentLengthHeader = true; // the Content-length header must not be set for interim responses as they cannot have a body
} else if ((rCode == 204) /* no content */
|| (rCode == 304)) /* not modified */
{
Expand All @@ -269,6 +264,10 @@ public void sendResponseHeaders(int rCode, long contentLen)
noContentLengthHeader = (rCode != 304);
}

if(!informational) {
rspHdrs.set("Date", ActivityTimer.dateAndTime());
}

if (isHeadRequest() || rCode == 304) {
/*
* HEAD requests or 304 responses should not set a content length by passing it
Expand All @@ -281,16 +280,16 @@ public void sendResponseHeaders(int rCode, long contentLen)
noContentToSend = true;
contentLen = 0;
o.setWrappedStream(new FixedLengthOutputStream(this, ros, contentLen));
} else if(informational && !connectionUpgraded) {
// don't want to set the stream for 1xx responses, except 101, the handler must call sendResponseHeaders again with the final code
flush = true;
} else { /* not a HEAD request or 304 response */
if (contentLen == 0) {
if (websocket || isConnectRequest()) {
if (connectionUpgraded || isConnectRequest()) {
o.setWrappedStream(ros);
close = true;
flush = true;
}
else if (informational) {
flush = true;
}
else if (http10) {
o.setWrappedStream(new UndefLengthOutputStream(this, ros));
close = true;
Expand Down Expand Up @@ -331,7 +330,7 @@ else if (http10) {
this.rspContentLen = contentLen;
sentHeaders = !informational;
if(logger.isLoggable(Level.TRACE)) {
logger.log(Level.TRACE, "Sent headers: noContentToSend=" + noContentToSend);
logger.log(Level.TRACE, "sendResponseHeaders(), code="+rCode+", noContentToSend=" + noContentToSend + ", contentLen=" + contentLen);
}
if(flush) {
ros.flush();
Expand Down
9 changes: 7 additions & 2 deletions src/main/java/robaho/net/httpserver/ServerImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -862,12 +862,17 @@ void sendReply(
builder.append("HTTP/1.1 ")
.append(code).append(Code.msg(code)).append("\r\n");

var informational = (code >= 100 && code < 200);

if (text != null && text.length() != 0) {
builder.append("Content-length: ")
.append(text.length()).append("\r\n")
.append("Content-type: text/html\r\n");
} else {
builder.append("Content-length: 0\r\n");
if (!informational) {
// no body for 1xx responses
builder.append("Content-length: 0\r\n");
}
text = "";
}
if (closeNow) {
Expand Down Expand Up @@ -898,7 +903,7 @@ void logReply(int code, String requestStr, String text) {
} else {
r = requestStr;
}
logger.log(Level.DEBUG, () -> "reply "+ r + " [" + code + " " + Code.msg(code) + "] (" + (text!=null ? text : "") + ")");
logger.log(Level.DEBUG, () -> "reply "+ r + " [" + code + Code.msg(code) + "] (" + (text!=null ? text : "") + ")");
}

void delay() {
Expand Down