Skip to content

Commit 8715655

Browse files
authored
Merge pull request #33 from FusionAuth/degroff/virtual_threads_update
degroff/virtual threads update
2 parents b3d7434 + 8519136 commit 8715655

File tree

74 files changed

+3668
-1669
lines changed

Some content is hidden

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

74 files changed

+3668
-1669
lines changed

README.md

Lines changed: 58 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,18 @@
11
## FusionAuth HTTP client and server ![semver 2.0.0 compliant](http://img.shields.io/badge/semver-2.0.0-brightgreen.svg?style=flat-square) [![test](https://github.com/FusionAuth/java-http/actions/workflows/test.yml/badge.svg)](https://github.com/FusionAuth/java-http/actions/workflows/test.yml)
22

3-
**NOTE:** This project is in progress.
3+
**NOTE:** This project is in progress. Version `0.3.0` is production ready, version `0.4.0` which will likely become `1.0.0` is still in development.
44

55
The goal of this project is to build a full-featured HTTP server and client in plain Java without the use of any libraries. The client and server will use Project Loom virtual threads and blocking I/O so that the Java VM will handle all the context switching between virtual threads as they block on I/O.
66

77
For more information about Project Loom and virtual threads, here is a good article to read: https://blogs.oracle.com/javamagazine/post/java-virtual-threads
88

9+
## Project Goals
10+
11+
- Very fast
12+
- Easy to make a simple web server like you can in Node.js
13+
- No dependencies
14+
- To not boil the ocean. This is a purpose built HTTP server that probably won't do everything.
15+
916
## Installation
1017

1118
To add this library to your project, you can include this dependency in your Maven POM:
@@ -14,20 +21,20 @@ To add this library to your project, you can include this dependency in your Mav
1421
<dependency>
1522
<groupId>io.fusionauth</groupId>
1623
<artifactId>java-http</artifactId>
17-
<version>0.4.0-RC.4</version>
24+
<version>0.4.0-RC.7</version>
1825
</dependency>
1926
```
2027

2128
If you are using Gradle, you can add this to your build file:
2229

2330
```groovy
24-
implementation 'io.fusionauth:java-http:0.4.0-RC.4'
31+
implementation 'io.fusionauth:java-http:0.4.0-RC.7'
2532
```
2633

2734
If you are using Savant, you can add this to your build file:
2835

2936
```groovy
30-
dependency(id: "io.fusionauth:java-http:0.4.0-RC.4")
37+
dependency(id: "io.fusionauth:java-http:0.4.0-RC.7")
3138
```
3239

3340
## Examples Usages:
@@ -158,17 +165,54 @@ Then you can open `https://example.org` in a browser or call it using an HTTP cl
158165

159166
## Performance
160167

161-
A key component for this project is to have awesome performance. Here are some basic metrics using the FusionAuth load test suite against a simple application using the Prime Framework MVC. The controller does nothing except return a simple 200. Here are some simple comparisons between `Tomcat`, `Netty`, and `java-http`.
168+
A key purpose for this project is obtain screaming performance. Here are some basic metrics using the FusionAuth load test suite against a boilerplate request handler. The request handler simply returns a `200`. Here are some simple comparisons between `apache-tomcat`, `Netty`, and `java-http`.
169+
170+
The load test configuration is set to `100` clients with `100,000` requests each per worker. This means the entire test will execute `10,000,000` requests. The HTTP client is [Restify](https://github.com/inversoft/restify) which is a FusionAuth library that uses `HttpURLConnection` under the hoods. This REST client is used because it is considerably faster than the native Java REST client. In a real life example, depending upon your application, this performance may not matter. For the purposes of a load test, we have attempted to remove as many limitations to pushing the server as hard as we can.
162171

163-
The load test configuration is set to 10 clients with 500,000 requests each. The client is Restify which is a FusionAuth library that uses `HttpURLConnection` under the hoods. All the servers were HTTP so that TLS would not introduce any additional latency.
172+
All the servers were HTTP so that TLS would not introduce any additional latency.
164173

165174
Here are the current test results:
166175

167-
| Server | RPS | Failures per second |
168-
|-------------|--------|---------------------|
169-
| `java-http` | 63,216 | 0 |
170-
| `Tomcat` | 51,351 | 0.103 |
171-
| `Netty` | 540 | 1.818 |
176+
| Server | Avg requests per second | Failures per second | Avg latency in ms | Normalized Performance (%) |
177+
|----------------|---------------------------|-----------------------|-------------------------|----------------------------|
178+
| java-http | 101,317 | 0 | 0.350 | 100% |
179+
| Apache Tomcat | 83,463 | 0 | 0.702 | 82.3% |
180+
| Netty | ? | ? | ? | |
181+
| OkHttp | ? | ? | ? | |
182+
| JDK HttpServer | ? | ? | ? | |
183+
184+
Note the JDK HTTP Server is `com.sun.net.httpserver.HttpServer`. I don't know that anyone would use this in production, the JDK has not yet made a version of this using a public API. It is included here for reference only.
185+
186+
Load test last performed May 30, 2025. Using the [fusionauth-load-test](https://github.com/fusionauth/fusionauth-load-tests) library.
187+
188+
### Running load tests
189+
190+
Start the HTTP server to test.
191+
192+
#### java-http
193+
194+
Start the HTTP server. Run the following commands from the `java-http` repo.
195+
196+
```bash
197+
cd load-tests/self
198+
sb clean start
199+
```
200+
201+
#### Apache Tomcat
202+
203+
Start the HTTP server. Run the following commands from the `java-http` repo.
204+
205+
```bash
206+
cd load-tests/tomcat
207+
sb clean start
208+
```
209+
210+
Once you have the server started you wish to test, start the load test. Run the following commands from the `fusionauth-load-tests` repo.
211+
212+
```bash
213+
sb clean int
214+
./load-test.sh HTTP.json
215+
```
172216

173217
Netty and Tomcat both seem to suffer from buffering and connection issues at very high scale. Regardless of the configuration, both servers always begins to fail with connection timeout problems at scale. `java-http` does not have these issues because of the way it handles connections via the selector. Connections don't back up and client connection pools can always be re-used with Keep-Alive.
174218

@@ -222,8 +266,8 @@ We are looking for Java developers that are interested in helping us build the c
222266
```bash
223267
$ mkdir ~/savant
224268
$ cd ~/savant
225-
$ wget http://savant.inversoft.org/org/savantbuild/savant-core/2.0.0-RC.6/savant-2.0.0-RC.7.tar.gz
226-
$ tar xvfz savant-2.0.0-RC.7.tar.gz
227-
$ ln -s ./savant-2.0.0-RC.7 current
269+
$ wget http://savant.inversoft.org/org/savantbuild/savant-core/2.0.0/savant-2.0.0.tar.gz
270+
$ tar xvfz savant-2.0.0.tar.gz
271+
$ ln -s ./savant-2.0.0 current
228272
$ export PATH=$PATH:~/savant/current/bin/
229273
```

build.savant

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,10 @@
1515
*/
1616
jackson5Version = "3.0.1"
1717
restifyVersion = "4.2.1"
18-
testngVersion = "7.10.2"
18+
slf4jVersion = "2.0.17"
19+
testngVersion = "7.11.0"
1920

20-
project(group: "io.fusionauth", name: "java-http", version: "0.4.0-RC.4", licenses: ["ApacheV2_0"]) {
21+
project(group: "io.fusionauth", name: "java-http", version: "0.4.0-RC.7", licenses: ["ApacheV2_0"]) {
2122
workflow {
2223
fetch {
2324
// Dependency resolution order:
@@ -41,10 +42,18 @@ project(group: "io.fusionauth", name: "java-http", version: "0.4.0-RC.4", licens
4142
}
4243

4344
dependencies {
45+
group(name: "compile") {
46+
// Ha! Just kidding. This is pure Java - no deps!
47+
}
4448
group(name: "test-compile", export: false) {
4549
dependency(id: "com.inversoft:jackson5:${jackson5Version}")
4650
dependency(id: "com.inversoft:restify:${restifyVersion}")
4751
dependency(id: "org.testng:testng:${testngVersion}")
52+
// Gets rid of SLF warnings on test run
53+
// SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
54+
// SLF4J: Defaulting to no-operation (NOP) logger implementation
55+
// SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
56+
dependency(id: "org.slf4j:slf4j-nop:${slf4jVersion}")
4857
}
4958
}
5059

@@ -54,25 +63,28 @@ project(group: "io.fusionauth", name: "java-http", version: "0.4.0-RC.4", licens
5463
}
5564

5665
// Plugins
57-
dependency = loadPlugin(id: "org.savantbuild.plugin:dependency:2.0.0-RC.7")
58-
java = loadPlugin(id: "org.savantbuild.plugin:java:2.0.0-RC.6")
59-
javaTestNG = loadPlugin(id: "org.savantbuild.plugin:java-testng:2.0.0-RC.6")
60-
idea = loadPlugin(id: "org.savantbuild.plugin:idea:2.0.0-RC.7")
61-
release = loadPlugin(id: "org.savantbuild.plugin:release-git:2.0.0-RC.6")
62-
pom = loadPlugin(id: "org.savantbuild.plugin:pom:2.0.0-RC.6")
66+
dependency = loadPlugin(id: "org.savantbuild.plugin:dependency:2.0.0")
67+
java = loadPlugin(id: "org.savantbuild.plugin:java:2.0.0")
68+
javaTestNG = loadPlugin(id: "org.savantbuild.plugin:java-testng:2.1.0")
69+
idea = loadPlugin(id: "org.savantbuild.plugin:idea:2.0.0")
70+
release = loadPlugin(id: "org.savantbuild.plugin:release-git:2.0.0")
71+
pom = loadPlugin(id: "org.savantbuild.plugin:pom:2.0.0")
6372

6473
java.settings.javaVersion = "21"
65-
java.settings.compilerArguments = "--add-exports java.base/sun.security.x509=ALL-UNNAMED --add-exports java.base/sun.security.util=ALL-UNNAMED -XDignore.symbol.file"
6674
javaTestNG.settings.javaVersion = "21"
67-
javaTestNG.settings.jvmArguments = "--add-exports java.base/sun.security.x509=ALL-UNNAMED --add-exports java.base/sun.security.util=ALL-UNNAMED"
75+
javaTestNG.settings.jvmArguments = "--add-exports java.base/sun.security.x509=ALL-UNNAMED --add-exports java.base/sun.security.util=ALL-UNNAMED "
6876
javaTestNG.settings.testngArguments = "-listener io.fusionauth.http.BaseTest\$TestListener"
6977

7078
target(name: "clean", description: "Cleans the build directory") {
7179
java.clean()
7280
}
7381

7482
target(name: "compile", description: "Compiles the Java source files") {
75-
java.compile()
83+
// We want to file compile on prod code if we need any -add-exports, so separate the prod and test compiles
84+
java.settings.compilerArguments = ""
85+
java.compileMain()
86+
java.settings.compilerArguments = "--add-exports java.base/sun.security.x509=ALL-UNNAMED --add-exports java.base/sun.security.util=ALL-UNNAMED -XDignore.symbol.file"
87+
java.compileTest()
7688
}
7789

7890
target(name: "jar", description: "Builds the project JARs", dependsOn: ["compile"]) {

java-http.iml

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -72,44 +72,55 @@
7272
<orderEntry type="module-library" scope="TEST">
7373
<library>
7474
<CLASSES>
75-
<root url="jar://$MODULE_DIR$/.savant/cache/org/testng/testng/7.10.2/testng-7.10.2.jar!/" />
75+
<root url="jar://$MODULE_DIR$/.savant/cache/org/testng/testng/7.11.0/testng-7.11.0.jar!/" />
7676
</CLASSES>
7777
<JAVADOC />
7878
<SOURCES>
79-
<root url="jar://$MODULE_DIR$/.savant/cache/org/testng/testng/7.10.2/testng-7.10.2-src.jar!/" />
79+
<root url="jar://$MODULE_DIR$/.savant/cache/org/testng/testng/7.11.0/testng-7.11.0-src.jar!/" />
8080
</SOURCES>
8181
</library>
8282
</orderEntry>
8383
<orderEntry type="module-library" scope="TEST">
8484
<library>
8585
<CLASSES>
86-
<root url="jar://$MODULE_DIR$/.savant/cache/org/slf4j/slf4j-api/1.7.36/slf4j-api-1.7.36.jar!/" />
86+
<root url="jar://$MODULE_DIR$/.savant/cache/org/jcommander/jcommander/1.83/jcommander-1.83.jar!/" />
8787
</CLASSES>
8888
<JAVADOC />
8989
<SOURCES>
90-
<root url="jar://$MODULE_DIR$/.savant/cache/org/slf4j/slf4j-api/1.7.36/slf4j-api-1.7.36-src.jar!/" />
90+
<root url="jar://$MODULE_DIR$/.savant/cache/org/jcommander/jcommander/1.83.0/jcommander-1.83.0-src.jar!/" />
9191
</SOURCES>
9292
</library>
9393
</orderEntry>
9494
<orderEntry type="module-library" scope="TEST">
9595
<library>
9696
<CLASSES>
97-
<root url="jar://$MODULE_DIR$/.savant/cache/com/beust/jcommander/1.82.0/jcommander-1.82.0.jar!/" />
97+
<root url="jar://$MODULE_DIR$/.savant/cache/org/webjars/jquery/3.7.1/jquery-3.7.1.jar!/" />
9898
</CLASSES>
9999
<JAVADOC />
100100
<SOURCES>
101-
<root url="jar://$MODULE_DIR$/.savant/cache/com/beust/jcommander/1.82.0/jcommander-1.82.0-src.jar!/" />
101+
<root url="jar://$MODULE_DIR$/.savant/cache/org/webjars/jquery/3.7.1/jquery-3.7.1-src.jar!/" />
102102
</SOURCES>
103103
</library>
104104
</orderEntry>
105105
<orderEntry type="module-library" scope="TEST">
106106
<library>
107107
<CLASSES>
108-
<root url="jar://$MODULE_DIR$/.savant/cache/org/webjars/jquery/3.7.1/jquery-3.7.1.jar!/" />
108+
<root url="jar://$MODULE_DIR$/.savant/cache/org/slf4j/slf4j-api/2.0.17/slf4j-api-2.0.17.jar!/" />
109109
</CLASSES>
110110
<JAVADOC />
111111
<SOURCES>
112-
<root url="jar://$MODULE_DIR$/.savant/cache/org/webjars/jquery/3.7.1/jquery-3.7.1-src.jar!/" />
112+
<root url="jar://$MODULE_DIR$/.savant/cache/org/slf4j/slf4j-api/2.0.17/slf4j-api-2.0.17-src.jar!/" />
113+
</SOURCES>
114+
</library>
115+
</orderEntry>
116+
<orderEntry type="module-library" scope="TEST">
117+
<library>
118+
<CLASSES>
119+
<root url="jar://$MODULE_DIR$/.savant/cache/org/slf4j/slf4j-nop/2.0.17/slf4j-nop-2.0.17.jar!/" />
120+
</CLASSES>
121+
<JAVADOC />
122+
<SOURCES>
123+
<root url="jar://$MODULE_DIR$/.savant/cache/org/slf4j/slf4j-nop/2.0.17/slf4j-nop-2.0.17-src.jar!/" />
113124
</SOURCES>
114125
</library>
115126
</orderEntry>

java-http.ipr

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -358,12 +358,14 @@
358358
</HTMLCodeStyleSettings>
359359
<JavaCodeStyleSettings>
360360
<option name="DO_NOT_WRAP_AFTER_SINGLE_ANNOTATION" value="true" />
361+
<option name="BLANK_LINES_AROUND_FIELD_WITH_ANNOTATIONS" value="1" />
361362
<option name="CLASS_NAMES_IN_JAVADOC" value="3" />
362363
<option name="INSERT_INNER_CLASS_IMPORTS" value="true" />
363364
<option name="CLASS_COUNT_TO_USE_IMPORT_ON_DEMAND" value="9999" />
364365
<option name="NAMES_COUNT_TO_USE_IMPORT_ON_DEMAND" value="9999" />
365366
<option name="IMPORT_LAYOUT_TABLE">
366367
<value>
368+
<package name="" withSubpackages="true" static="false" module="true" />
367369
<package name="javax" withSubpackages="true" static="false" />
368370
<package name="java" withSubpackages="true" static="false" />
369371
<emptyLine />
@@ -687,6 +689,7 @@
687689
<codeStyleSettings language="JAVA">
688690
<option name="BLANK_LINES_AROUND_FIELD" value="1" />
689691
<option name="ALIGN_MULTILINE_CHAINED_METHODS" value="true" />
692+
<option name="ALIGN_MULTILINE_BINARY_OPERATION" value="true" />
690693
<option name="METHOD_PARAMETERS_WRAP" value="1" />
691694
<option name="THROWS_KEYWORD_WRAP" value="1" />
692695
<option name="WRAP_COMMENTS" value="true" />
@@ -1434,4 +1437,17 @@
14341437
<component name="VcsDirectoryMappings">
14351438
<mapping directory="$PROJECT_DIR$" vcs="Git" />
14361439
</component>
1440+
<component name="libraryTable">
1441+
<library name=".kts definition dependencies">
1442+
<CLASSES>
1443+
<root url="jar://$KOTLIN_BUNDLED$/lib/kotlin-reflect.jar!/" />
1444+
<root url="jar://$KOTLIN_BUNDLED$/lib/kotlin-script-runtime.jar!/" />
1445+
<root url="jar://$KOTLIN_BUNDLED$/lib/kotlin-stdlib.jar!/" />
1446+
</CLASSES>
1447+
<JAVADOC />
1448+
<SOURCES>
1449+
<root url="jar://$KOTLIN_BUNDLED$/lib/kotlin-stdlib-sources.jar!/" />
1450+
</SOURCES>
1451+
</library>
1452+
</component>
14371453
</project>

0 commit comments

Comments
 (0)