Skip to content

Commit 4f0adc2

Browse files
committed
Working
1 parent 2ec79ed commit 4f0adc2

25 files changed

+168
-272
lines changed

README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ For more information about Project Loom and virtual threads, here is a good arti
1111
- Very fast
1212
- Easy to make a simple web server like you can in Node.js
1313
- No dependencies
14-
- To not boil the ocean. This is a purpose built HTTP server that probably won't do everything.
14+
- To not boil the ocean. This is a purpose built HTTP server that probably won't do everything.
1515

1616
## Installation
1717

@@ -187,7 +187,7 @@ Load test last performed May 30, 2025. Using the [fusionauth-load-test](https://
187187

188188
### Running load tests
189189

190-
Start the HTTP server to test.
190+
Start the HTTP server to test.
191191

192192
#### java-http
193193

@@ -201,6 +201,7 @@ sb clean start
201201
#### Apache Tomcat
202202

203203
Start the HTTP server. Run the following commands from the `java-http` repo.
204+
204205
```bash
205206
cd load-tests/tomcat
206207
sb clean start

load-tests/benchmarks/LinuxConfig - Ultimate Web Server Benchmark.md

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
# LinuxConfig - Ultimate Web Server Benchmark
2+
23
- https://linuxconfig.org/ultimate-web-server-benchmark-apache-nginx-litespeed-openlitespeed-caddy-lighttpd-compared
34

45
I asked about the `-k` parameter for the `ab` usages to ensure I understand how HTTP Keep Alive was being used in the RPS measurements.
@@ -10,10 +11,9 @@ I asked about the `-k` parameter for the `ab` usages to ensure I understand how
1011

1112
> Static file handling is a fundamental task for any web server. This test measures how efficiently each server serves a simple HTML page under concurrent requests. A web server optimized for static content should deliver high requests per second (RPS) with minimal latency and resource usage. This test is crucial for scenarios where websites serve mostly cached, pre-generated pages, such as blogs, documentation sites, and content delivery networks (CDNs).
1213
13-
1414
### Command
15-
The test was conducted using Apache Benchmark (`ab`) with the following command:
1615

16+
The test was conducted using Apache Benchmark (`ab`) with the following command:
1717

1818
```sh
1919
ab -n 100000 -c 100 http://localhost:8080/
@@ -23,10 +23,10 @@ ab -n 100000 -c 100 http://localhost:8080/
2323
- -c 50 → Number of concurrent users (50)
2424
- http://localhost:8080/ → URL of the static test page
2525

26-
2726
### Results
2827

2928
Test run on:
29+
3030
- MacBook Pro 16-inch 2021, Apple M1 Max, 64 GB
3131

3232
| Server | Requests per second (RPS) | Latency (ms) | Normalized Performance (%) |
@@ -53,6 +53,7 @@ For fun, here is the same test using the `-k` parameter. In practice this may be
5353
> Serving large files efficiently is critical for websites that deliver downloads, streaming media, or large assets such as high-resolution images or software packages. This test evaluates how well each web server handles the transfer of a 10MB file under concurrent requests. A well-optimized server should maintain high transfer rates while keeping CPU and memory usage minimal.
5454
5555
### Command
56+
5657
The test was conducted using Apache Benchmark (`ab`) with the following command:
5758

5859
```sh
@@ -66,6 +67,7 @@ ab -n 500 -c 10 http://localhost:8080/file?size=10485760
6667
### Results
6768

6869
Test run on:
70+
6971
- MacBook Pro 16-inch 2021, Apple M1 Max, 64 GB
7072

7173
| Server | Requests per second (RPS) | Latency (ms) | Transfer Rate (MB/sec) | Normalized Performance (%) |
@@ -77,7 +79,6 @@ Note to calculate the Transfer Rate in MB/sec, I am taking the `ab` result of `K
7779

7880
Same test with `-k` which assumes we are using an HTTP proxy with Keep Alive.
7981

80-
8182
| Server | Requests per second (RPS) | Latency (ms) | Transfer Rate (MB/sec) | Normalized Performance (%) |
8283
|---------------|---------------------------|--------------|------------------------|----------------------------|
8384
| java-http | 682 | 14.647 | 6,632 | |
@@ -92,6 +93,7 @@ It is unexpected that Apache Tomcat would perform worse with `-k` enabled. It is
9293
> Web servers must efficiently handle high traffic volumes, especially during peak loads. This test measures how well each server performs when faced with 1,000 simultaneous users making requests to a simple HTML page. A well-optimized server should maintain a high request rate with minimal latency and avoid excessive CPU and memory consumption. This test is crucial for sites experiencing traffic spikes, such as e-commerce platforms, news websites, and online services.
9394
9495
### Command
96+
9597
The test was conducted using Apache Benchmark (`ab`) with the following command:
9698

9799
```sh

load-tests/self/build.savant

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import java.nio.file.Paths
2-
31
/*
42
* Copyright (c) 2022-2025, FusionAuth, All Rights Reserved
53
*

load-tests/tomcat/src/main/tomcat/conf/server.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
<Host name="localhost" autoDeploy="false" unpackWARs="false">
3232
<Context path="" docBase="${catalina.base}/../web">
3333
<Manager pathname=""/>
34-
<Resources allowLinking="true" />
34+
<Resources allowLinking="true"/>
3535
</Context>
3636
</Host>
3737
</Engine>

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,4 +188,4 @@
188188
</build>
189189
</profile>
190190
</profiles>
191-
</project>
191+
</project>

src/main/java/io/fusionauth/http/Cookie.java

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2021-2022, FusionAuth, All Rights Reserved
2+
* Copyright (c) 2021-2025, FusionAuth, All Rights Reserved
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -281,13 +281,13 @@ public boolean equals(Object o) {
281281
return false;
282282
}
283283
return httpOnly == cookie.httpOnly &&
284-
secure == cookie.secure &&
285-
Objects.equals(domain, cookie.domain) &&
286-
Objects.equals(expires, cookie.expires) &&
287-
Objects.equals(maxAge, cookie.maxAge) &&
288-
Objects.equals(name, cookie.name) &&
289-
Objects.equals(path, cookie.path) &&
290-
Objects.equals(value, cookie.value);
284+
secure == cookie.secure &&
285+
Objects.equals(domain, cookie.domain) &&
286+
Objects.equals(expires, cookie.expires) &&
287+
Objects.equals(maxAge, cookie.maxAge) &&
288+
Objects.equals(name, cookie.name) &&
289+
Objects.equals(path, cookie.path) &&
290+
Objects.equals(value, cookie.value);
291291
}
292292

293293
public String getDomain() {

src/main/java/io/fusionauth/http/HTTPMethod.java

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2021-2022, FusionAuth, All Rights Reserved
2+
* Copyright (c) 2021-2025, FusionAuth, All Rights Reserved
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -48,6 +48,18 @@ public class HTTPMethod {
4848

4949
private final String name;
5050

51+
static {
52+
StandardMethods.put(CONNECT.name(), CONNECT);
53+
StandardMethods.put(DELETE.name(), DELETE);
54+
StandardMethods.put(GET.name(), GET);
55+
StandardMethods.put(HEAD.name(), HEAD);
56+
StandardMethods.put(OPTIONS.name(), OPTIONS);
57+
StandardMethods.put(PATCH.name(), PATCH);
58+
StandardMethods.put(POST.name(), POST);
59+
StandardMethods.put(PUT.name(), PUT);
60+
StandardMethods.put(TRACE.name(), TRACE);
61+
}
62+
5163
private HTTPMethod(String name) {
5264
Objects.requireNonNull(name);
5365
this.name = name.toUpperCase(Locale.ROOT);
@@ -95,16 +107,4 @@ public String name() {
95107
public String toString() {
96108
return name;
97109
}
98-
99-
static {
100-
StandardMethods.put(CONNECT.name(), CONNECT);
101-
StandardMethods.put(DELETE.name(), DELETE);
102-
StandardMethods.put(GET.name(), GET);
103-
StandardMethods.put(HEAD.name(), HEAD);
104-
StandardMethods.put(OPTIONS.name(), OPTIONS);
105-
StandardMethods.put(PATCH.name(), PATCH);
106-
StandardMethods.put(POST.name(), POST);
107-
StandardMethods.put(PUT.name(), PUT);
108-
StandardMethods.put(TRACE.name(), TRACE);
109-
}
110110
}

src/main/java/io/fusionauth/http/ChunkException.java renamed to src/main/java/io/fusionauth/http/io/ChunkException.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
* either express or implied. See the License for the specific
1414
* language governing permissions and limitations under the License.
1515
*/
16-
package io.fusionauth.http;
16+
package io.fusionauth.http.io;
1717

1818
/**
1919
* Exception that is thrown if a Chunked request or response is invalid.

src/main/java/io/fusionauth/http/io/ChunkedInputStream.java

Lines changed: 4 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
import java.io.IOException;
1919
import java.io.InputStream;
2020

21-
import io.fusionauth.http.ChunkException;
2221
import io.fusionauth.http.ParseException;
2322
import io.fusionauth.http.util.HTTPTools;
2423
import static io.fusionauth.http.util.HTTPTools.makeParseException;
@@ -33,7 +32,7 @@ public class ChunkedInputStream extends InputStream {
3332

3433
private final byte[] buffer;
3534

36-
private final InputStream delegate;
35+
private final PushbackInputStream delegate;
3736

3837
private final StringBuilder headerSizeHex = new StringBuilder();
3938

@@ -47,7 +46,7 @@ public class ChunkedInputStream extends InputStream {
4746

4847
private ChunkedBodyState state = ChunkedBodyState.ChunkSize;
4948

50-
public ChunkedInputStream(InputStream delegate, int bufferSize) {
49+
public ChunkedInputStream(PushbackInputStream delegate, int bufferSize) {
5150
this.delegate = delegate;
5251
this.buffer = new byte[bufferSize];
5352
}
@@ -75,17 +74,7 @@ private int processChunk(byte[] destination, int offset, int length) throws IOEx
7574
// We need to push back any remaining bytes to the InputStream since we may have read more bytes than we needed.
7675
int leftOver = bufferLength - bufferIndex;
7776
if (leftOver > 0) {
78-
// TODO : Daniel : Review : This doesn't seem like a good idea. It will fail silently, but this is required.
79-
// Discuss with Brian.
80-
// .
81-
// Options:
82-
// - Leave as is
83-
// - Throw a OverReadException that is caught by the HTTPInputStream which has a typed ref to PushbackInputStream and handled.
84-
// - Keep a typed ref for PushbackInputStream, but this messes up the 'commit' path in HTTPInputStream that swaps the pointer.
85-
// So we could re-work how we handle a chunked request body - instead of swapping out pointers we do something else?
86-
if (delegate instanceof PushbackInputStream pis) {
87-
pis.push(buffer, bufferIndex, leftOver);
88-
}
77+
delegate.push(buffer, bufferIndex, leftOver);
8978
}
9079

9180
return -1;
@@ -114,16 +103,7 @@ private int processChunk(byte[] destination, int offset, int length) throws IOEx
114103
if (nextState == ChunkedBodyState.Complete) {
115104
state = nextState;
116105
bufferIndex++;
117-
// We need to push back any remaining bytes to the InputStream since we may have read more bytes than we needed.
118-
int leftOver = bufferLength - bufferIndex;
119-
if (leftOver > 0) {
120-
// TODO : Daniel : Review : This doesn't seem like a good idea. It will fail silently, but this is required.
121-
// Discuss with Brian.
122-
if (delegate instanceof PushbackInputStream pis) {
123-
pis.push(buffer, bufferIndex, leftOver);
124-
}
125-
}
126-
return -1;
106+
continue;
127107
}
128108

129109
// Record the size hex digit

src/main/java/io/fusionauth/http/io/FastByteArrayOutputStream.java

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,13 @@ public FastByteArrayOutputStream(int size, int growthRate) {
4242
this.growthRate = growthRate;
4343
}
4444

45+
/**
46+
* @return The byte array (directly without copying).
47+
*/
48+
public byte[] bytes() {
49+
return buffer;
50+
}
51+
4552
@Override
4653
public void close() {
4754
}
@@ -60,13 +67,6 @@ public int size() {
6067
return count;
6168
}
6269

63-
/**
64-
* @return The byte array (directly without copying).
65-
*/
66-
public byte[] bytes() {
67-
return buffer;
68-
}
69-
7070
@Override
7171
public void write(int b) {
7272
if (count + 1 >= buffer.length) {

src/main/java/io/fusionauth/http/io/MultipartStream.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,9 @@
3535
import io.fusionauth.http.HTTPValues.DispositionParameters;
3636
import io.fusionauth.http.HTTPValues.Headers;
3737
import io.fusionauth.http.ParseException;
38-
import io.fusionauth.http.util.RequestPreambleState;
3938
import io.fusionauth.http.util.HTTPTools;
4039
import io.fusionauth.http.util.HTTPTools.HeaderValue;
40+
import io.fusionauth.http.util.RequestPreambleState;
4141

4242
/**
4343
* Handles the multipart body encoding and file uploads.

src/main/java/io/fusionauth/http/io/PushbackInputStream.java

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,17 @@
2626
public class PushbackInputStream extends InputStream {
2727
private final byte[] b1 = new byte[1];
2828

29+
private final InputStream delegate;
30+
2931
private byte[] buffer;
3032

3133
private int bufferEndPosition;
3234

3335
private int bufferPosition;
3436

35-
private InputStream delegate;
37+
public PushbackInputStream(InputStream delegate) {
38+
this.delegate = delegate;
39+
}
3640

3741
public int getAvailableBufferedBytesRemaining() {
3842
return buffer != null ? (bufferEndPosition - bufferPosition) : 0;
@@ -75,6 +79,8 @@ public int read(byte[] b, int off, int len) throws IOException {
7579
// - So I think we have to return, and allow the caller to decide if they want to read more bytes based upon
7680
// the contents of the bytes we return.
7781
// TODO : Daniel : Review the above statement.
82+
// ...
83+
// TODO : Put back the code... we want to continue reading past the buffer here.
7884
return read;
7985
}
8086

@@ -90,8 +96,4 @@ public int read() throws IOException {
9096

9197
return b1[0] & 0xFF;
9298
}
93-
94-
public void setDelegate(InputStream delegate) {
95-
this.delegate = delegate;
96-
}
9799
}

src/main/java/io/fusionauth/http/log/FileLoggerFactory.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,12 @@ public class FileLoggerFactory implements LoggerFactory {
2525

2626
private static FileLogger logger;
2727

28+
public static void setLogger(FileLogger logger) {
29+
FileLoggerFactory.logger = logger;
30+
}
31+
2832
@Override
2933
public Logger getLogger(Class<?> klass) {
3034
return logger;
3135
}
32-
33-
public static void setLogger(FileLogger logger) {
34-
FileLoggerFactory.logger = logger;
35-
}
3636
}

0 commit comments

Comments
 (0)