Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
12 changes: 10 additions & 2 deletions frameworks/helidon-production/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
FROM maven:3.9-eclipse-temurin-25 AS build
FROM container-registry.oracle.com/java/jdk-no-fee-term:27 AS build
ARG MAVEN_VERSION=3.9.16
ARG MAVEN_SHA512=831a8591fe20c8243b1dbe7d71e3244f31d1665b0804b2e825e38cbbe5ce0cafb8338851f90780735568773e0a6cd07bbec107cda0b896b008b861075358b6f6
RUN curl -fsSL "https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/${MAVEN_VERSION}/apache-maven-${MAVEN_VERSION}-bin.tar.gz" -o /tmp/maven.tar.gz \
&& echo "${MAVEN_SHA512} /tmp/maven.tar.gz" | sha512sum -c - \
&& mkdir -p /usr/share/maven \
&& tar -xzf /tmp/maven.tar.gz -C /usr/share/maven --strip-components=1 \
&& rm /tmp/maven.tar.gz
ENV PATH="/usr/share/maven/bin:${PATH}"
WORKDIR /app
COPY pom.xml .
RUN mvn dependency:go-offline -q
COPY src ./src
RUN mvn package -DskipTests -q

FROM eclipse-temurin:25-jre
FROM container-registry.oracle.com/java/jdk-no-fee-term:27
WORKDIR /app
COPY --from=build /app/target/helidon-httparena-production.jar app.jar
COPY --from=build /app/target/libs ./libs
Expand Down
7 changes: 4 additions & 3 deletions frameworks/helidon-production/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ Helidon Production

# Project

This framework runs Helidon SE 4.5.4 on Níma WebServer as a `production`
This framework runs Helidon SE 27.0.0 on Níma WebServer as a `production`
benchmark entry.

The current subscribed benchmark profiles are:
Expand Down Expand Up @@ -44,8 +44,9 @@ The benchmark wiring is split by listener:
- `8082` (`h2c`): cleartext prior-knowledge HTTP/2 for `baseline-h2c` and `json-h2c`
- `8443` (`h2-tls`): HTTP/2 + TLS for `baseline-h2`, `static-h2`, and `unary-grpc-tls`

Static content and TLS are configured from `application.yaml`, not
programmatically.
Static content and TLS are configured from `application.yaml`. Helidon's
static-content feature serves `/data/static` with precompressed `.br` and `.gz`
sidecars, negotiates `Accept-Encoding`, and sets `Vary: Accept-Encoding`.

# Divergence from benchmark guidance

Expand Down
2 changes: 1 addition & 1 deletion frameworks/helidon-production/meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"response": true
},
"engine": "níma",
"description": "Helidon SE 4.5.4 on Níma WebServer with Java 25, config-based static content, HTTP/2, gRPC, and WebSocket support.",
"description": "Helidon SE 27.0.0 on Níma WebServer with Java 27, config-based static content with precompressed sidecars, HTTP/2, gRPC, and WebSocket support.",
"repo": "https://github.com/helidon-io/helidon",
"enabled": true,
"tests": [
Expand Down
2 changes: 1 addition & 1 deletion frameworks/helidon-production/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<parent>
<groupId>io.helidon.applications</groupId>
<artifactId>helidon-se</artifactId>
<version>4.5.4</version>
<version>27.0.0</version>
<relativePath/>
</parent>
<groupId>com.httparena</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,6 @@ public void handle(ServerRequest req, ServerResponse res) {
int first = Integer.parseInt(query.getRaw("a"));
int second = Integer.parseInt(query.getRaw("b"));

res.send(String.valueOf(first + second).getBytes(StandardCharsets.US_ASCII));
res.send(String.valueOf(first + second).getBytes(StandardCharsets.ISO_8859_1));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,6 @@ public void handle(ServerRequest req, ServerResponse res) {
int second = Integer.parseInt(query.getRaw("b"));
int third = Integer.parseInt(req.content().as(GenericType.STRING));

res.send(String.valueOf(first + second + third).getBytes(StandardCharsets.US_ASCII));
res.send(String.valueOf(first + second + third).getBytes(StandardCharsets.ISO_8859_1));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,6 @@ public void handle(ServerRequest req, ServerResponse res) throws InterruptedExce
Thread.sleep(delay);

res.header(CONTENT_TYPE_TEXT_PLAIN);
res.send(Integer.toString(delay).getBytes(StandardCharsets.US_ASCII));
res.send(Integer.toString(delay).getBytes(StandardCharsets.ISO_8859_1));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
import static io.helidon.http.HeaderValues.CONTENT_TYPE_TEXT_PLAIN;

class PipelineHandler implements Handler {
private static final byte[] RESPONSE = "ok".getBytes(StandardCharsets.US_ASCII);
private static final byte[] RESPONSE = "ok".getBytes(StandardCharsets.ISO_8859_1);
private static final Header CONTENT_LENGTH = HeaderValues.createCached(HeaderNames.CONTENT_LENGTH, "2");

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ server:
features:
static-content:
sockets: ["@default", "h1-tls", "h2-tls"]
pre-compressed-enabled: true
path:
- context: "/static"
location: "${data.dir}/static"
12 changes: 10 additions & 2 deletions frameworks/helidon-tuned/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
FROM maven:3.9-eclipse-temurin-25 AS build
FROM container-registry.oracle.com/java/jdk-no-fee-term:27 AS build
ARG MAVEN_VERSION=3.9.16
ARG MAVEN_SHA512=831a8591fe20c8243b1dbe7d71e3244f31d1665b0804b2e825e38cbbe5ce0cafb8338851f90780735568773e0a6cd07bbec107cda0b896b008b861075358b6f6
RUN curl -fsSL "https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/${MAVEN_VERSION}/apache-maven-${MAVEN_VERSION}-bin.tar.gz" -o /tmp/maven.tar.gz \
&& echo "${MAVEN_SHA512} /tmp/maven.tar.gz" | sha512sum -c - \
&& mkdir -p /usr/share/maven \
&& tar -xzf /tmp/maven.tar.gz -C /usr/share/maven --strip-components=1 \
&& rm /tmp/maven.tar.gz
ENV PATH="/usr/share/maven/bin:${PATH}"
WORKDIR /app
COPY pom.xml .
RUN mvn dependency:go-offline -q
COPY src ./src
RUN mvn package -DskipTests -q

FROM eclipse-temurin:25-jre
FROM container-registry.oracle.com/java/jdk-no-fee-term:27
WORKDIR /app
COPY --from=build /app/target/helidon-httparena-tuned.jar app.jar
COPY --from=build /app/target/libs ./libs
Expand Down
2 changes: 1 addition & 1 deletion frameworks/helidon-tuned/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ Helidon Tuned

# Project

This framework runs Helidon SE 4.5.4 on Níma WebServer as a `tuned`
This framework runs Helidon SE 27.0.0 on Níma WebServer as a `tuned`
benchmark entry.

The current subscribed benchmark profiles are:
Expand Down
2 changes: 1 addition & 1 deletion frameworks/helidon-tuned/meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"response": true
},
"engine": "níma",
"description": "Helidon SE 4.5.4 on Níma WebServer with Java 25, tuned socket and protocol-validation settings, precompressed static content, HTTP/2, gRPC, and WebSocket support.",
"description": "Helidon SE 27.0.0 on Níma WebServer with Java 27, tuned socket and protocol-validation settings, custom precompressed static content, HTTP/2, gRPC, and WebSocket support.",
"repo": "https://github.com/helidon-io/helidon",
"enabled": true,
"tests": [
Expand Down
2 changes: 1 addition & 1 deletion frameworks/helidon-tuned/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<parent>
<groupId>io.helidon.applications</groupId>
<artifactId>helidon-se</artifactId>
<version>4.5.4</version>
<version>27.0.0</version>
<relativePath/>
</parent>
<groupId>com.httparena</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,6 @@ public void handle(ServerRequest req, ServerResponse res) {
int first = Integer.parseInt(query.getRaw("a"));
int second = Integer.parseInt(query.getRaw("b"));

res.send(String.valueOf(first + second).getBytes(StandardCharsets.US_ASCII));
res.send(String.valueOf(first + second).getBytes(StandardCharsets.ISO_8859_1));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,6 @@ public void handle(ServerRequest req, ServerResponse res) {
int second = Integer.parseInt(query.getRaw("b"));
int third = Integer.parseInt(req.content().as(GenericType.STRING));

res.send(String.valueOf(first + second + third).getBytes(StandardCharsets.US_ASCII));
res.send(String.valueOf(first + second + third).getBytes(StandardCharsets.ISO_8859_1));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,6 @@ public void handle(ServerRequest req, ServerResponse res) throws InterruptedExce
Thread.sleep(delay);

res.header(CONTENT_TYPE_TEXT_PLAIN);
res.send(Integer.toString(delay).getBytes(StandardCharsets.US_ASCII));
res.send(Integer.toString(delay).getBytes(StandardCharsets.ISO_8859_1));
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package com.httparena;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.io.ByteArrayOutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
Expand Down Expand Up @@ -49,7 +49,7 @@ public void handle(ServerRequest req, ServerResponse res) {
List<TotalItem> totalItems = jsonDataset.subList(0, count).stream()
.map(item -> TotalItem.create(item, multiplier))
.toList();
byte[] responseBody = JSON_BINDING.serializeToBytes(new TotalItems(totalItems, totalItems.size()));
TotalItems responseBody = new TotalItems(totalItems, totalItems.size());
String acceptEncoding = req.headers()
.first(HeaderNames.ACCEPT_ENCODING)
.orElse("")
Expand All @@ -60,7 +60,7 @@ public void handle(ServerRequest req, ServerResponse res) {
res.send(gzip(responseBody));
return;
}
res.send(responseBody);
res.send(JSON_BINDING.serializeToBytes(responseBody));
}

private static List<Item> loadJsonDataset(String dataLocation) throws IOException {
Expand All @@ -77,11 +77,11 @@ private static List<Item> loadJsonDataset(String dataLocation) throws IOExceptio
return JSON_BINDING.deserialize(Files.readAllBytes(datasetPath), new GenericType<List<Item>>() { });
}

private static byte[] gzip(byte[] bytes) {
private static byte[] gzip(TotalItems responseBody) {
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream(bytes.length);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try (GZIPOutputStream gzip = new GZIPOutputStream(baos)) {
gzip.write(bytes);
JSON_BINDING.serialize(gzip, responseBody);
}
return baos.toByteArray();
} catch (IOException e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
import static io.helidon.http.HeaderValues.CONTENT_TYPE_TEXT_PLAIN;

class PipelineHandler implements Handler {
private static final byte[] RESPONSE = "ok".getBytes(StandardCharsets.US_ASCII);
private static final byte[] RESPONSE = "ok".getBytes(StandardCharsets.ISO_8859_1);
private static final Header CONTENT_LENGTH = HeaderValues.createCached(HeaderNames.CONTENT_LENGTH, "2");

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
package com.httparena;

import java.io.ByteArrayInputStream;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.Duration;
import java.util.List;
import java.util.zip.GZIPInputStream;

import io.helidon.json.binding.JsonBinding;
import io.helidon.webserver.WebServer;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;

class JsonHandlerTest {
private static final JsonBinding JSON_BINDING = JsonBinding.create();
private static final String LARGE_NAME = "雪".repeat(6000);

@TempDir
Path dataDirectory;

private WebServer server;
private HttpClient client;

@BeforeEach
void startServer() throws Exception {
Files.writeString(dataDirectory.resolve("dataset.json"), """
[
{"id":1,"name":"Café \\"alpha\\"\\\\line\\nnext","category":"first",
"price":7,"quantity":3,"active":true,"tags":["日本語","a\\tb"],
"rating":{"score":4,"count":11}},
{"id":2,"name":"second","category":"other",
"price":2000000000,"quantity":4,"active":false,"tags":[],
"rating":{"score":2,"count":3000000000}},
{"id":3,"name":"%s","category":"large",
"price":0,"quantity":5,"active":true,"tags":["large"],
"rating":{"score":5,"count":1}}
]
""".formatted(LARGE_NAME));
JsonHandler handler = new JsonHandler(dataDirectory.toString());
server = WebServer.builder()
.host("127.0.0.1")
.port(0)
.routing(routing -> routing.get("/json/{count}", handler))
.build()
.start();
client = HttpClient.newBuilder()
.version(HttpClient.Version.HTTP_1_1)
.connectTimeout(Duration.ofSeconds(10))
.build();
}

@AfterEach
void stopServer() {
if (client != null) {
client.close();
}
if (server != null) {
server.stop();
}
}

@ParameterizedTest(name = "gzip count={0}, multiplier={1}, returned count={2}")
@CsvSource({"0,3,0", "-1,3,0", "1,3,1", "5,-2,3", "3,0,3"})
void gzipResponseHasCompleteJsonAndTrailer(int count, int multiplier, int expectedCount) throws Exception {
HttpResponse<byte[]> response = request("/json/" + count + "?m=" + multiplier, true);

assertHeaders(response);
assertEquals("gzip", response.headers().firstValue("Content-Encoding").orElseThrow());
byte[] decoded;
try (GZIPInputStream gzip = new GZIPInputStream(new ByteArrayInputStream(response.body()))) {
// Reading through EOF verifies the gzip checksum and uncompressed-size trailer.
decoded = gzip.readAllBytes();
assertEquals(-1, gzip.read(), "gzip stream must end after the complete JSON response");
}
assertEquals(expectedItems(expectedCount, multiplier), JSON_BINDING.deserialize(decoded, TotalItems.class));
}

@Test
void responseWithoutAcceptEncodingRemainsUncompressed() throws Exception {
HttpResponse<byte[]> response = request("/json/3?m=2", false);

assertHeaders(response);
assertFalse(response.headers().firstValue("Content-Encoding").isPresent(), "unexpected content encoding");
assertEquals(expectedItems(3, 2), JSON_BINDING.deserialize(response.body(), TotalItems.class));
}

@Test
void responseDefaultsToMultiplierOne() throws Exception {
HttpResponse<byte[]> response = request("/json/1?unused=1", false);

assertHeaders(response);
assertEquals(expectedItems(1, 1), JSON_BINDING.deserialize(response.body(), TotalItems.class));
}

private HttpResponse<byte[]> request(String path, boolean gzip) throws Exception {
HttpRequest.Builder request = HttpRequest.newBuilder(URI.create("http://127.0.0.1:" + server.port() + path))
.timeout(Duration.ofSeconds(10));
if (gzip) {
request.header("Accept-Encoding", "gzip");
}
return client.send(request.build(), HttpResponse.BodyHandlers.ofByteArray());
}

private static void assertHeaders(HttpResponse<byte[]> response) {
assertEquals(200, response.statusCode(), "HTTP status");
assertEquals("application/json", response.headers().firstValue("Content-Type").orElseThrow());
assertEquals("Accept-Encoding", response.headers().firstValue("Vary").orElseThrow());
assertEquals(response.body().length, response.headers().firstValueAsLong("Content-Length").orElseThrow(),
"Content-Length must describe the transmitted bytes");
}

private static TotalItems expectedItems(int count, int multiplier) {
List<TotalItem> items = List.of(
new TotalItem(1, "Café \"alpha\"\\line\nnext", "first", 7, 3, true,
List.of("日本語", "a\tb"), new Rating(4, 11), 21L * multiplier),
new TotalItem(2, "second", "other", 2000000000, 4, false,
List.of(), new Rating(2, 3000000000L), 8000000000L * multiplier),
new TotalItem(3, LARGE_NAME, "large", 0, 5, true,
List.of("large"), new Rating(5, 1), 0));
return new TotalItems(items.subList(0, count), count);
}
}
4 changes: 2 additions & 2 deletions site/data/frameworks.json
Original file line number Diff line number Diff line change
Expand Up @@ -755,7 +755,7 @@
},
"helidon-production": {
"dir": "helidon-production",
"description": "Helidon SE 4.5.4 on N\u00edma WebServer with Java 25, config-based static content, HTTP/2, gRPC, and WebSocket support.",
"description": "Helidon SE 27.0.0 on N\u00edma WebServer with Java 27, config-based static content with precompressed sidecars, HTTP/2, gRPC, and WebSocket support.",
"repo": "https://github.com/helidon-io/helidon",
"type": "flagship",
"engine": "n\u00edma",
Expand All @@ -769,7 +769,7 @@
},
"helidon-tuned": {
"dir": "helidon-tuned",
"description": "Helidon SE 4.5.4 on N\u00edma WebServer with Java 25, tuned socket and protocol-validation settings, precompressed static content, HTTP/2, gRPC, and WebSocket support.",
"description": "Helidon SE 27.0.0 on N\u00edma WebServer with Java 27, tuned socket and protocol-validation settings, config-based static content with precompressed sidecars, HTTP/2, gRPC, and WebSocket support.",
"repo": "https://github.com/helidon-io/helidon",
"type": "flagship",
"engine": "n\u00edma",
Expand Down
Loading
Loading