Skip to content

Commit 7ed4d26

Browse files
SentryManrobaho
andauthored
Support 1xx codes (robaho#31)
* support 1xx codes * Create InputRead100Test.java * no contentlength header * update build for gradle 9.1.0 * test 100 Continue to Expect header * handle manual 1xx responses including make connection upgrade generic rather than websocket specific * update comments --------- Co-authored-by: robert engels <robaho@users.noreply.github.com>
1 parent 66dae75 commit 7ed4d26

File tree

5 files changed

+215
-61
lines changed

5 files changed

+215
-61
lines changed

build.gradle

Lines changed: 17 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -92,22 +92,15 @@ sourceSets {
9292
}
9393
}
9494

95-
def getGitVersion () {
96-
def output = new ByteArrayOutputStream()
97-
exec {
98-
commandLine 'git', 'rev-list', '--tags', '--max-count=1'
99-
standardOutput = output
100-
}
101-
def revision = output.toString().trim()
102-
output.reset()
103-
exec {
104-
commandLine 'git', 'describe', '--tags', revision
105-
standardOutput = output
106-
}
107-
return output.toString().trim()
108-
}
95+
// Use a lazy Provider to get the git version. This is the modern, configuration-cache-friendly approach.
96+
def gitVersionProvider = project.providers.exec {
97+
// 1. Describe the latest revision with a tag
98+
commandLine = ['git', 'describe', '--tags', '--always']
99+
ignoreExitValue = true // Don't fail the build if git fails (e.g., no tags exist)
100+
}.standardOutput.asText.map { it.trim() }
109101

110-
version = getGitVersion()
102+
// Apply the git version to your project
103+
version = gitVersionProvider.get()
111104

112105
task showGitVersion {
113106
doLast {
@@ -116,9 +109,6 @@ task showGitVersion {
116109
}
117110

118111
build {
119-
doFirst {
120-
getGitVersion
121-
}
122112
}
123113

124114
jar {
@@ -181,16 +171,17 @@ task runSimpleFileServer(type: JavaExec) {
181171
}
182172
dependsOn testClasses
183173
classpath sourceSets.test.runtimeClasspath
184-
main "SimpleFileServer"
174+
175+
// FIX 1: Use 'mainClass' instead of 'main'
176+
// FIX 2: Replace "SimpleFileServer" with the FULLY QUALIFIED class name
177+
// (e.g., if it's in a package named com.example)
178+
mainClass = "com.example.SimpleFileServer"
179+
185180
args = ['fileserver','443','fileserver/logfile.txt']
186-
// args = ['fileserver','8080','fileserver/logfile.txt']
187-
javaLauncher = javaToolchains.launcherFor {
181+
182+
javaLauncher = javaToolchains.launcherFor {
188183
languageVersion = JavaLanguageVersion.of(23)
189184
}
190-
// debugOptions {
191-
// enabled = true
192-
// suspend = true
193-
// }
194185
}
195186

196187
task testJar(type: Jar) {
@@ -205,7 +196,7 @@ task runAllTests(type: Test) {
205196
}
206197

207198
publish {
208-
// dependsOn runAllTests
199+
dependsOn runAllTests
209200
}
210201

211202
publishing {

src/main/java/robaho/net/httpserver/Code.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
public class Code {
2929

3030
public static final int HTTP_CONTINUE = 100;
31+
public static final int HTTP_SWITCHING_PROTOCOLS = 101;
3132
public static final int HTTP_OK = 200;
3233
public static final int HTTP_CREATED = 201;
3334
public static final int HTTP_ACCEPTED = 202;
@@ -71,6 +72,8 @@ static String msg(int code) {
7172
return " OK";
7273
case HTTP_CONTINUE:
7374
return " Continue";
75+
case HTTP_SWITCHING_PROTOCOLS:
76+
return " Switching Protocols";
7477
case HTTP_CREATED:
7578
return " Created";
7679
case HTTP_ACCEPTED:

src/main/java/robaho/net/httpserver/ExchangeImpl.java

Lines changed: 37 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,6 @@
4040

4141
import com.sun.net.httpserver.*;
4242

43-
import robaho.net.httpserver.websockets.WebSocketHandler;
44-
4543
class ExchangeImpl {
4644

4745
Headers reqHdrs, rspHdrs;
@@ -69,7 +67,8 @@ class ExchangeImpl {
6967

7068
private static final String HEAD = "HEAD";
7169
private static final String CONNECT = "CONNECT";
72-
70+
private static final String HEADER_CONNECTION = "Connection";
71+
private static final String HEADER_CONNECTION_UPGRADE = "Upgrade";
7372
/*
7473
* streams which take care of the HTTP protocol framing
7574
* and are passed up to higher layers
@@ -85,7 +84,7 @@ class ExchangeImpl {
8584
Map<String, Object> attributes;
8685
int rcode = -1;
8786
HttpPrincipal principal;
88-
final boolean websocket;
87+
boolean connectionUpgraded = false;
8988

9089
ExchangeImpl(
9190
String m, URI u, Request req, long len, HttpConnection connection) throws IOException {
@@ -97,11 +96,6 @@ class ExchangeImpl {
9796
this.method = m;
9897
this.uri = u;
9998
this.connection = connection;
100-
this.websocket = WebSocketHandler.isWebsocketRequested(this.reqHdrs);
101-
if (this.websocket) {
102-
// length is indeterminate
103-
len = -1;
104-
}
10599
this.reqContentLen = len;
106100
/* ros only used for headers, body written directly to stream */
107101
this.ros = req.outputStream();
@@ -135,6 +129,9 @@ private boolean isHeadRequest() {
135129
private boolean isConnectRequest() {
136130
return CONNECT.equals(getRequestMethod());
137131
}
132+
private boolean isUpgradeRequest() {
133+
return HEADER_CONNECTION_UPGRADE.equalsIgnoreCase(reqHdrs.getFirst(HEADER_CONNECTION));
134+
}
138135

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

237233
Integer bufferSize = (Integer)this.getAttribute(Attributes.SOCKET_WRITE_BUFFER);
238234
if(bufferSize!=null) {
@@ -242,19 +238,21 @@ public void sendResponseHeaders(int rCode, long contentLen)
242238
boolean flush = false;
243239

244240
/* check for response type that is not allowed to send a body */
245-
if (rCode == 101) {
246-
logger.log(Level.DEBUG, () -> "switching protocols");
247-
248-
if (contentLen != 0) {
249-
String msg = "sendResponseHeaders: rCode = " + rCode
250-
+ ": forcing contentLen = 0";
251-
logger.log(Level.WARNING, msg);
252-
}
253-
contentLen = 0;
254-
flush = true;
255-
256-
} else if ((rCode >= 100 && rCode < 200) /* informational */
257-
|| (rCode == 204) /* no content */
241+
var informational = rCode >= 100 && rCode < 200;
242+
243+
if (informational) {
244+
if (rCode == 101) {
245+
logger.log(Level.DEBUG, () -> "switching protocols");
246+
if (contentLen != 0) {
247+
String msg = "sendResponseHeaders: rCode = " + rCode
248+
+ ": forcing contentLen = 0";
249+
logger.log(Level.WARNING, msg);
250+
contentLen = 0;
251+
}
252+
connectionUpgraded = true;
253+
}
254+
noContentLengthHeader = true; // the Content-length header must not be set for interim responses as they cannot have a body
255+
} else if ((rCode == 204) /* no content */
258256
|| (rCode == 304)) /* not modified */
259257
{
260258
if (contentLen != -1) {
@@ -266,6 +264,10 @@ public void sendResponseHeaders(int rCode, long contentLen)
266264
noContentLengthHeader = (rCode != 304);
267265
}
268266

267+
if(!informational) {
268+
rspHdrs.set("Date", ActivityTimer.dateAndTime());
269+
}
270+
269271
if (isHeadRequest() || rCode == 304) {
270272
/*
271273
* HEAD requests or 304 responses should not set a content length by passing it
@@ -278,14 +280,16 @@ public void sendResponseHeaders(int rCode, long contentLen)
278280
noContentToSend = true;
279281
contentLen = 0;
280282
o.setWrappedStream(new FixedLengthOutputStream(this, ros, contentLen));
281-
} else { /* not a HEAD request or 304 response */
283+
} else if(informational && !connectionUpgraded) {
284+
// don't want to set the stream for 1xx responses, except 101, the handler must call sendResponseHeaders again with the final code
285+
flush = true;
286+
} else if(connectionUpgraded || isConnectRequest()) {
287+
o.setWrappedStream(ros);
288+
close = true;
289+
flush = true;
290+
} else { /* standard response with possible response data */
282291
if (contentLen == 0) {
283-
if (websocket || isConnectRequest()) {
284-
o.setWrappedStream(ros);
285-
close = true;
286-
flush = true;
287-
}
288-
else if (http10) {
292+
if (http10) {
289293
o.setWrappedStream(new UndefLengthOutputStream(this, ros));
290294
close = true;
291295
} else {
@@ -323,9 +327,9 @@ else if (http10) {
323327

324328
writeHeaders(rspHdrs, ros);
325329
this.rspContentLen = contentLen;
326-
sentHeaders = true;
330+
sentHeaders = !informational;
327331
if(logger.isLoggable(Level.TRACE)) {
328-
logger.log(Level.TRACE, "Sent headers: noContentToSend=" + noContentToSend);
332+
logger.log(Level.TRACE, "sendResponseHeaders(), code="+rCode+", noContentToSend=" + noContentToSend + ", contentLen=" + contentLen);
329333
}
330334
if(flush) {
331335
ros.flush();

src/main/java/robaho/net/httpserver/ServerImpl.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -862,12 +862,17 @@ void sendReply(
862862
builder.append("HTTP/1.1 ")
863863
.append(code).append(Code.msg(code)).append("\r\n");
864864

865+
var informational = (code >= 100 && code < 200);
866+
865867
if (text != null && text.length() != 0) {
866868
builder.append("Content-length: ")
867869
.append(text.length()).append("\r\n")
868870
.append("Content-type: text/html\r\n");
869871
} else {
870-
builder.append("Content-length: 0\r\n");
872+
if (!informational) {
873+
// no body for 1xx responses
874+
builder.append("Content-length: 0\r\n");
875+
}
871876
text = "";
872877
}
873878
if (closeNow) {
@@ -898,7 +903,7 @@ void logReply(int code, String requestStr, String text) {
898903
} else {
899904
r = requestStr;
900905
}
901-
logger.log(Level.DEBUG, () -> "reply "+ r + " [" + code + " " + Code.msg(code) + "] (" + (text!=null ? text : "") + ")");
906+
logger.log(Level.DEBUG, () -> "reply "+ r + " [" + code + Code.msg(code) + "] (" + (text!=null ? text : "") + ")");
902907
}
903908

904909
void delay() {

0 commit comments

Comments
 (0)