multipart parsing, request body access, and chunked input stream support

This commit is contained in:
Relism
2026-03-19 12:45:34 +01:00
parent 96afbf665d
commit 16b5f8ac15
17 changed files with 1951 additions and 0 deletions
+58
View File
@@ -0,0 +1,58 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<parent>
<artifactId>flash-parent</artifactId>
<groupId>dev.relism</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>flash-bench</artifactId>
<build>
<finalName>flash-bench</finalName>
<plugins>
<plugin>
<artifactId>maven-shade-plugin</artifactId>
<version>3.5.3</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer>
<mainClass>dev.relism.bench.Main</mainClass>
</transformer>
<transformer />
</transformers>
<filters>
<filter>
<artifact>*:*</artifact>
<excludes>
<exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.DSA</exclude>
<exclude>META-INF/*.RSA</exclude>
</excludes>
</filter>
</filters>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.44</version>
<scope>provided</scope>
</dependency>
</dependencies>
<properties>
<maven.compiler.release>21</maven.compiler.release>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<ahc.version>2.12.3</ahc.version>
</properties>
</project>
@@ -0,0 +1,148 @@
package dev.relism.bench;
import dev.relism.HttpServer;
import dev.relism.HttpServerConfiguration;
import dev.relism.http.ContentType;
import org.asynchttpclient.AsyncHttpClient;
import org.asynchttpclient.DefaultAsyncHttpClientConfig;
import org.asynchttpclient.Dsl;
import org.asynchttpclient.Request;
import org.asynchttpclient.RequestBuilder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.util.Arrays;
public class Main {
// Lazy holder — Netty starts only on the first AHC request, not at class load.
// Routes like /plaintext that never call AHC are unaffected.
private static final class AhcHolder {
static final AsyncHttpClient AHC = Dsl.asyncHttpClient(
new DefaultAsyncHttpClientConfig.Builder()
.setMaxConnections(256)
.setMaxConnectionsPerHost(256)
.setKeepAlive(true)
.setPooledConnectionIdleTimeout(60_000)
.setConnectTimeout(5_000)
.setRequestTimeout(10_000)
.setIoThreadsCount(1)
.build()
);
}
// Pre-built request objects — URI parsing done once at startup.
private static final Request ELEMENT_REQ = new RequestBuilder("GET")
.setUrl("http://web-data-source/element.json").build();
private static final Request SHELLS_REQ = new RequestBuilder("GET")
.setUrl("http://web-data-source/shells.json").build();
private static final byte[] PREFIX_SHELLS = "{\"shells\":".getBytes(StandardCharsets.UTF_8);
private static final byte[] SUFFIX = "}".getBytes(StandardCharsets.UTF_8);
public static void main(String[] args) throws Exception {
int port = args.length > 0 ? Integer.parseInt(args[0]) : 3000;
HttpServer server = new HttpServer(
HttpServerConfiguration.builder()
.port(port).host("0.0.0.0").build()
);
server.get("/api/v1/periodic-table/element", (req, res) ->
fetchAndFilter(req.query("symbol"), false));
server.get("/api/v1/periodic-table/shells", (req, res) ->
fetchAndFilter(req.query("symbol"), true));
server.get("/plaintext", (req, res) -> "Hello, World!");
server.get("/json", (req, res) -> {
res.setContentType(ContentType.JSON);
return "{\"message\":\"Hello, World!\"}";
});
Path frontendDist = Path.of("C:\\Users\\elorc\\Documents\\Coding\\web\\PixelDocs\\.vitepress\\dist");
StaticResourceManager cdn = new StaticResourceManager(frontendDist, "/");
cdn.register(server);
server.start().thenRun(() -> System.out.println("Flash Sharkbench online on port " + port));
}
private static dev.relism.models.Response fetchAndFilter(String symbol, boolean wrapShells) {
if (symbol == null) return new dev.relism.models.Response(400, "Missing symbol", ContentType.TEXT_PLAIN);
try {
// executeRequest() returns a ListenableFuture; .get() parks the calling
// virtual thread (cheap) while Netty's I/O thread handles the socket.
byte[] body = AhcHolder.AHC.executeRequest(wrapShells ? SHELLS_REQ : ELEMENT_REQ)
.get()
.getResponseBodyAsBytes();
byte[] value = extractJsonValue(body, symbol);
if (value == null) return new dev.relism.models.Response(404, "Not Found", ContentType.TEXT_PLAIN);
if (wrapShells) {
byte[] wrapped = new byte[PREFIX_SHELLS.length + value.length + SUFFIX.length];
System.arraycopy(PREFIX_SHELLS, 0, wrapped, 0, PREFIX_SHELLS.length);
System.arraycopy(value, 0, wrapped, PREFIX_SHELLS.length, value.length);
System.arraycopy(SUFFIX, 0, wrapped, PREFIX_SHELLS.length + value.length, SUFFIX.length);
return new dev.relism.models.Response(200, wrapped, ContentType.JSON);
}
return new dev.relism.models.Response(200, value, ContentType.JSON);
} catch (Exception e) {
return new dev.relism.models.Response(500, "Internal Error", ContentType.TEXT_PLAIN);
}
}
/**
* Extracts the JSON value for a given key from a flat JSON object.
* Zero Jackson allocations — pure byte scan.
*/
private static byte[] extractJsonValue(byte[] json, String key) {
byte[] keyBytes = ("\"" + key + "\":").getBytes(StandardCharsets.UTF_8);
int pos = indexOf(json, keyBytes);
if (pos == -1) return null;
pos += keyBytes.length;
while (pos < json.length && json[pos] == ' ') pos++;
if (pos >= json.length) return null;
byte opener = json[pos];
byte closer;
if (opener == '{') closer = '}';
else if (opener == '[') closer = ']';
else return null;
int depth = 0;
boolean inString = false;
int start = pos;
while (pos < json.length) {
byte b = json[pos];
if (b == '"' && !isEscaped(json, pos)) inString = !inString;
if (!inString) {
if (b == opener) depth++;
else if (b == closer) { if (--depth == 0) { pos++; break; } }
}
pos++;
}
return Arrays.copyOfRange(json, start, pos);
}
private static int indexOf(byte[] haystack, byte[] needle) {
outer:
for (int i = 0; i <= haystack.length - needle.length; i++) {
for (int j = 0; j < needle.length; j++) {
if (haystack[i + j] != needle[j]) continue outer;
}
return i;
}
return -1;
}
private static boolean isEscaped(byte[] data, int pos) {
int backslashes = 0;
while (--pos >= 0 && data[pos] == '\\') backslashes++;
return (backslashes & 1) == 1;
}
}
@@ -0,0 +1,12 @@
{
"H": {"name": "Hydrogen", "number": 1, "group": 1},
"He": {"name": "Helium", "number": 2, "group": 18},
"Li": {"name": "Lithium", "number": 3, "group": 1},
"Be": {"name": "Beryllium", "number": 4, "group": 2},
"B": {"name": "Boron", "number": 5, "group": 13},
"C": {"name": "Carbon", "number": 6, "group": 14},
"N": {"name": "Nitrogen", "number": 7, "group": 15},
"O": {"name": "Oxygen", "number": 8, "group": 16},
"F": {"name": "Fluorine", "number": 9, "group": 17},
"Ne": {"name": "Neon", "number": 10, "group": 18}
}
@@ -0,0 +1,12 @@
{
"H": {"shells": [1]},
"He": {"shells": [2]},
"Li": {"shells": [2, 1]},
"Be": {"shells": [2, 2]},
"B": {"shells": [2, 3]},
"C": {"shells": [2, 4]},
"N": {"shells": [2, 5]},
"O": {"shells": [2, 6]},
"F": {"shells": [2, 7]},
"Ne": {"shells": [2, 8]}
}