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
17 changes: 17 additions & 0 deletions frameworks/javalin/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
FROM maven:3.9-eclipse-temurin-21 AS build
WORKDIR /app
COPY pom.xml .
RUN mvn -B -q dependency:go-offline
COPY src ./src
RUN mvn -B -q clean package -DskipTests

FROM eclipse-temurin:21-jre
WORKDIR /app
COPY --from=build /app/target/javalin-httparena.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", \
"-server", \
"-XX:+UseG1GC", \
"-XX:+AlwaysPreTouch", \
"-Dorg.slf4j.simpleLogger.defaultLogLevel=warn", \
"-jar", "app.jar"]
28 changes: 28 additions & 0 deletions frameworks/javalin/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# javalin

Javalin on embedded Jetty, default configuration.

## Stack

- **Language:** Java 21
- **Framework:** Javalin 7.2 on Jetty 12
- **Build:** Maven shade jar, `eclipse-temurin:21-jre` runtime

## Endpoints

| Endpoint | Method | Description |
|----------|--------|-------------|
| `/pipeline` | GET | Returns `ok` (plain text) |
| `/baseline11` | GET | Sums query parameter values |
| `/baseline11` | POST | Sums query parameters + request body |
| `/json/{count}?m=N` | GET | First `count` dataset items with `total = price * quantity * m` |
| `/upload` | POST | Streams the body and returns the byte count |

## Notes

- Routing, path and query params through the Javalin router
- JSON written with `ctx.json`, serialized by the Jackson mapper Javalin ships
- Compression through Javalin's own `CompressionStrategy.GZIP`, defaults untouched
- One JVM with the default Jetty thread pool, which sizes itself from the cores the container gets
- `/upload` is counted off the request stream, so the 20 MB body is never buffered
- The dataset is read once at startup from `DATASET_PATH`, defaulting to `/data/dataset.json`
19 changes: 19 additions & 0 deletions frameworks/javalin/meta.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"display_name": "javalin",
"language": "Java",
"type": "flagship",
"mode": "standard",
"engine": "jetty",
"description": "Javalin 7 on embedded Jetty 12, default configuration. Routing and path params through the Javalin router, JSON through ctx.json with the bundled Jackson mapper, gzip through Javalin's own CompressionStrategy.",
"repo": "https://github.com/javalin/javalin",
"enabled": true,
"tests": [
"baseline",
"pipelined",
"limited-conn",
"json",
"json-comp",
"upload"
],
"maintainers": []
}
87 changes: 87 additions & 0 deletions frameworks/javalin/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
<?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/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>httparena</groupId>
<artifactId>javalin</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>

<properties>
<maven.compiler.release>21</maven.compiler.release>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<javalin.version>7.2.3</javalin.version>
<jackson.version>2.22.1</jackson.version>
<slf4j.version>2.0.18</slf4j.version>
</properties>

<dependencies>
<dependency>
<groupId>io.javalin</groupId>
<artifactId>javalin</artifactId>
<version>${javalin.version}</version>
</dependency>
<!-- Javalin keeps its JSON mapper optional, so the Jackson one it ships is pulled in here -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>${slf4j.version}</version>
</dependency>
</dependencies>

<build>
<finalName>javalin-httparena</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.13.0</version>
<configuration>
<parameters>true</parameters>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.6.0</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<manifestEntries>
<Main-Class>httparena.Main</Main-Class>
</manifestEntries>
</transformer>
<transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
</transformers>
<filters>
<filter>
<artifact>*:*</artifact>
<excludes>
<exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.DSA</exclude>
<exclude>META-INF/*.RSA</exclude>
<exclude>module-info.class</exclude>
<exclude>META-INF/versions/*/module-info.class</exclude>
</excludes>
</filter>
</filters>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
128 changes: 128 additions & 0 deletions frameworks/javalin/src/main/java/httparena/Main.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
package httparena;

import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;

import io.javalin.Javalin;
import io.javalin.compression.CompressionStrategy;
import io.javalin.http.ContentType;
import io.javalin.http.Context;
import io.javalin.json.JavalinJackson;

public final class Main {

public static void main(String[] args) {
List<Row> dataset = loadDataset();

Javalin.create(config -> {
config.jetty.host = "0.0.0.0";
config.jetty.port = 8080;
config.jsonMapper(new JavalinJackson());
// standard mode: gzip is Javalin's own CompressionStrategy with its default level
// and default minimum size, nothing hand-rolled
config.http.compressionStrategy = CompressionStrategy.GZIP;

config.routes.get("/pipeline", ctx -> ctx.contentType(ContentType.PLAIN).result("ok"));
config.routes.get("/baseline11", ctx -> baseline(ctx, false));
config.routes.post("/baseline11", ctx -> baseline(ctx, true));
config.routes.get("/json/{count}", ctx -> json(ctx, dataset));
config.routes.post("/upload", Main::upload);
}).start();
}

// A missing or unreadable dataset serves an empty list instead of taking the server down
private static List<Row> loadDataset() {
String path = System.getenv().getOrDefault("DATASET_PATH", "/data/dataset.json");
try {
byte[] bytes = Files.readAllBytes(Path.of(path));
return new ObjectMapper().readValue(bytes, new TypeReference<List<Row>>() {});
} catch (Exception ignored) {
return List.of();
}
}

private static void baseline(Context ctx, boolean withBody) {
long sum = 0;
for (List<String> values : ctx.queryParamMap().values()) {
for (String value : values) {
sum += parseOrZero(value);
}
}
if (withBody) {
sum += parseOrZero(ctx.body());
}
ctx.contentType(ContentType.PLAIN).result(Long.toString(sum));
}

private static void json(Context ctx, List<Row> dataset) {
int count;
try {
count = Integer.parseInt(ctx.pathParam("count"));
} catch (NumberFormatException e) {
count = 0;
}
count = Math.max(0, Math.min(count, dataset.size()));

long m = 1;
String multiplier = ctx.queryParam("m");
if (multiplier != null) {
try {
m = Long.parseLong(multiplier);
} catch (NumberFormatException ignored) {
}
}

List<Item> items = new ArrayList<>(count);
for (int i = 0; i < count; i++) {
Row row = dataset.get(i);
items.add(new Item(row.id(), row.name(), row.category(), row.price(), row.quantity(),
row.active(), row.tags(), row.rating(), row.price() * row.quantity() * m));
}
// json-comp negotiation belongs to the CompressionStrategy configured above
ctx.json(new Items(items, count));
}

// The body is counted off the request stream, so the 20 MB upload is never buffered
private static void upload(Context ctx) throws Exception {
long size = 0;
byte[] buffer = new byte[65536];
try (InputStream in = ctx.bodyInputStream()) {
int read;
while ((read = in.read(buffer)) != -1) {
size += read;
}
}
ctx.contentType(ContentType.PLAIN).result(Long.toString(size));
}

private static long parseOrZero(String value) {
if (value == null) {
return 0;
}
try {
return Long.parseLong(value.trim());
} catch (NumberFormatException e) {
return 0;
}
}

public record Rating(long score, long count) {
}

public record Row(long id, String name, String category, long price, long quantity,
boolean active, List<String> tags, Rating rating) {
}

public record Item(long id, String name, String category, long price, long quantity,
boolean active, List<String> tags, Rating rating, long total) {
}

public record Items(List<Item> items, int count) {
}
}
8 changes: 8 additions & 0 deletions site/data/frameworks.json
Original file line number Diff line number Diff line change
Expand Up @@ -659,6 +659,14 @@
"type": "engine",
"engine": "epoll"
},
"javalin": {
"dir": "javalin",
"description": "Javalin 7 on embedded Jetty 12, default configuration. Routing and path params through the Javalin router, JSON through ctx.json with the bundled Jackson mapper, gzip through Javalin's own CompressionStrategy.",
"repo": "https://github.com/javalin/javalin",
"type": "flagship",
"engine": "jetty",
"mode": "standard"
},
"koa": {
"dir": "koa",
"description": "Koa 3 on the Node http server, default configuration, one cluster worker per core. Routing through @koa/router, JSON through the koa response body, gzip through koa-compress.",
Expand Down
Loading