Skip to content

Commit c8a49e7

Browse files
rurorujj
authored andcommitted
add robaho httpserver
1 parent 386f918 commit c8a49e7

5 files changed

Lines changed: 381 additions & 0 deletions

File tree

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
FROM maven:3.9-eclipse-temurin-21 AS build
2+
WORKDIR /app
3+
COPY pom.xml .
4+
RUN mvn dependency:go-offline -q
5+
COPY src ./src
6+
RUN mvn package -DskipTests -q
7+
8+
FROM eclipse-temurin:21-jre
9+
WORKDIR /app
10+
COPY --from=build /app/target/robaho-httpserver-1.0.0.jar app.jar
11+
COPY --from=build /app/target/libs ./libs
12+
EXPOSE 8080
13+
ENTRYPOINT ["java", "-jar", "app.jar"]
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{
2+
"display_name": "robaho-httpserver",
3+
"language": "Java",
4+
"type": "production",
5+
"engine": "robaho-httpserver",
6+
"description": "High-performance Java HTTP server using robaho/httpserver, a drop-in replacement for com.sun.net.httpserver with improved throughput.",
7+
"repo": "https://github.com/robaho/httpserver",
8+
"enabled": true,
9+
"tests": [
10+
"baseline",
11+
"pipelined",
12+
"limited-conn",
13+
"json",
14+
"json-comp",
15+
"upload",
16+
"static",
17+
"async-db",
18+
"fortunes",
19+
"api-4",
20+
"api-16"
21+
],
22+
"maintainers": []
23+
}
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
5+
<modelVersion>4.0.0</modelVersion>
6+
7+
<groupId>com.httparena</groupId>
8+
<artifactId>robaho-httpserver</artifactId>
9+
<version>1.0.0</version>
10+
<packaging>jar</packaging>
11+
12+
<properties>
13+
<java.version>21</java.version>
14+
<maven.compiler.source>${java.version}</maven.compiler.source>
15+
<maven.compiler.target>${java.version}</maven.compiler.target>
16+
</properties>
17+
18+
<dependencies>
19+
<dependency>
20+
<groupId>io.github.robaho</groupId>
21+
<artifactId>httpserver</artifactId>
22+
<version>1.0.25</version>
23+
</dependency>
24+
<dependency>
25+
<groupId>com.fasterxml.jackson.core</groupId>
26+
<artifactId>jackson-databind</artifactId>
27+
<version>2.19.0</version>
28+
</dependency>
29+
<dependency>
30+
<groupId>io.pebbletemplates</groupId>
31+
<artifactId>pebble</artifactId>
32+
<version>3.2.4</version>
33+
</dependency>
34+
<dependency>
35+
<groupId>io.vertx</groupId>
36+
<artifactId>vertx-pg-client</artifactId>
37+
<version>5.0.0.CR7</version>
38+
</dependency>
39+
<dependency>
40+
<groupId>com.ongres.scram</groupId>
41+
<artifactId>scram-client</artifactId>
42+
<version>3.1</version>
43+
</dependency>
44+
</dependencies>
45+
46+
<build>
47+
<plugins>
48+
<plugin>
49+
<groupId>org.apache.maven.plugins</groupId>
50+
<artifactId>maven-jar-plugin</artifactId>
51+
<version>3.4.2</version>
52+
<configuration>
53+
<archive>
54+
<manifest>
55+
<mainClass>com.httparena.Main</mainClass>
56+
<addClasspath>true</addClasspath>
57+
<classpathPrefix>libs/</classpathPrefix>
58+
</manifest>
59+
</archive>
60+
</configuration>
61+
</plugin>
62+
<plugin>
63+
<groupId>org.apache.maven.plugins</groupId>
64+
<artifactId>maven-dependency-plugin</artifactId>
65+
<version>3.7.1</version>
66+
<executions>
67+
<execution>
68+
<id>copy-dependencies</id>
69+
<phase>package</phase>
70+
<goals><goal>copy-dependencies</goal></goals>
71+
<configuration>
72+
<outputDirectory>${project.build.directory}/libs</outputDirectory>
73+
</configuration>
74+
</execution>
75+
</executions>
76+
</plugin>
77+
</plugins>
78+
</build>
79+
</project>
Lines changed: 254 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,254 @@
1+
package com.httparena;
2+
3+
import com.fasterxml.jackson.core.type.TypeReference;
4+
import com.fasterxml.jackson.databind.ObjectMapper;
5+
import io.pebbletemplates.pebble.PebbleEngine;
6+
import io.pebbletemplates.pebble.template.PebbleTemplate;
7+
import io.vertx.core.Vertx;
8+
import io.vertx.pgclient.PgBuilder;
9+
import io.vertx.pgclient.PgConnectOptions;
10+
import io.vertx.sqlclient.*;
11+
import robaho.net.httpserver.HttpServerImpl;
12+
import com.sun.net.httpserver.HttpExchange;
13+
import com.sun.net.httpserver.HttpHandler;
14+
import com.sun.net.httpserver.HttpServer;
15+
16+
import java.io.*;
17+
import java.net.InetSocketAddress;
18+
import java.net.URI;
19+
import java.nio.file.Files;
20+
import java.nio.file.Path;
21+
import java.util.*;
22+
import java.util.concurrent.Executors;
23+
import java.util.zip.GZIPOutputStream;
24+
25+
public class Main {
26+
27+
static final ObjectMapper mapper = new ObjectMapper();
28+
static final PebbleEngine pebble = new PebbleEngine.Builder().autoEscaping(true).build();
29+
static final PebbleTemplate fortunesTemplate = pebble.getTemplate("fortunes.html");
30+
static List<Item> dataset = List.of();
31+
static Pool pgPool;
32+
33+
public static void main(String[] args) throws Exception {
34+
loadDataset();
35+
initPostgres();
36+
37+
HttpServer server = HttpServerImpl.create(new InetSocketAddress(8080), 0);
38+
server.setExecutor(Executors.newVirtualThreadPerTaskExecutor());
39+
40+
server.createContext("/baseline11", new BaselineHandler());
41+
server.createContext("/baseline2", new BaselineHandler());
42+
server.createContext("/pipeline", new PipelineHandler());
43+
server.createContext("/json/", new JsonHandler());
44+
server.createContext("/upload", new UploadHandler());
45+
server.createContext("/static/", new StaticHandler());
46+
if (pgPool != null) {
47+
server.createContext("/async-db", new AsyncDbHandler());
48+
server.createContext("/fortunes", new FortunesHandler());
49+
}
50+
51+
server.start();
52+
}
53+
54+
static void loadDataset() {
55+
String path = System.getenv().getOrDefault("DATASET_PATH", "/data/dataset.json");
56+
try {
57+
dataset = mapper.readValue(new File(path), new TypeReference<>() {});
58+
} catch (Exception ignored) {}
59+
}
60+
61+
static void initPostgres() {
62+
String url = System.getenv("DATABASE_URL");
63+
if (url == null || url.isEmpty()) return;
64+
try {
65+
URI uri = new URI(url);
66+
String[] userInfo = uri.getUserInfo().split(":");
67+
PgConnectOptions connectOptions = new PgConnectOptions()
68+
.setHost(uri.getHost())
69+
.setPort(uri.getPort())
70+
.setDatabase(uri.getPath().substring(1))
71+
.setUser(userInfo[0])
72+
.setPassword(userInfo[1]);
73+
int maxConn = Integer.parseInt(System.getenv().getOrDefault("DATABASE_MAX_CONN", "256"));
74+
PoolOptions poolOptions = new PoolOptions().setMaxSize(maxConn);
75+
Vertx vertx = Vertx.vertx();
76+
pgPool = PgBuilder.pool()
77+
.with(poolOptions)
78+
.connectingTo(connectOptions)
79+
.using(vertx)
80+
.build();
81+
} catch (Exception ignored) {}
82+
}
83+
84+
static Map<String, String> parseQuery(String query) {
85+
Map<String, String> params = new HashMap<>();
86+
if (query == null) return params;
87+
for (String pair : query.split("&")) {
88+
int eq = pair.indexOf('=');
89+
if (eq > 0) params.put(pair.substring(0, eq), pair.substring(eq + 1));
90+
}
91+
return params;
92+
}
93+
94+
static void sendText(HttpExchange ex, String text) throws IOException {
95+
byte[] bytes = text.getBytes();
96+
ex.getResponseHeaders().set("Content-Type", "text/plain");
97+
ex.sendResponseHeaders(200, bytes.length);
98+
ex.getResponseBody().write(bytes);
99+
ex.close();
100+
}
101+
102+
static void sendJson(HttpExchange ex, Object obj) throws IOException {
103+
byte[] bytes = mapper.writeValueAsBytes(obj);
104+
String accept = ex.getRequestHeaders().getFirst("Accept-Encoding");
105+
if (accept != null && accept.contains("gzip")) {
106+
ByteArrayOutputStream baos = new ByteArrayOutputStream();
107+
try (GZIPOutputStream gz = new GZIPOutputStream(baos)) { gz.write(bytes); }
108+
bytes = baos.toByteArray();
109+
ex.getResponseHeaders().set("Content-Encoding", "gzip");
110+
}
111+
ex.getResponseHeaders().set("Content-Type", "application/json");
112+
ex.sendResponseHeaders(200, bytes.length);
113+
ex.getResponseBody().write(bytes);
114+
ex.close();
115+
}
116+
117+
// --- Handlers ---
118+
119+
static class BaselineHandler implements HttpHandler {
120+
public void handle(HttpExchange ex) throws IOException {
121+
Map<String, String> params = parseQuery(ex.getRequestURI().getQuery());
122+
int a = Integer.parseInt(params.getOrDefault("a", "0"));
123+
int b = Integer.parseInt(params.getOrDefault("b", "0"));
124+
int sum = a + b;
125+
if ("POST".equals(ex.getRequestMethod())) {
126+
String body = new String(ex.getRequestBody().readAllBytes());
127+
sum += Integer.parseInt(body.trim());
128+
}
129+
sendText(ex, String.valueOf(sum));
130+
}
131+
}
132+
133+
static class PipelineHandler implements HttpHandler {
134+
public void handle(HttpExchange ex) throws IOException {
135+
sendText(ex, "ok");
136+
}
137+
}
138+
139+
static class JsonHandler implements HttpHandler {
140+
public void handle(HttpExchange ex) throws IOException {
141+
String path = ex.getRequestURI().getPath();
142+
int count = Integer.parseInt(path.substring(path.lastIndexOf('/') + 1));
143+
Map<String, String> params = parseQuery(ex.getRequestURI().getQuery());
144+
int m = Integer.parseInt(params.getOrDefault("m", "1"));
145+
int n = Math.min(Math.max(count, 0), dataset.size());
146+
List<Map<String, Object>> items = new ArrayList<>(n);
147+
for (int i = 0; i < n; i++) {
148+
Item item = dataset.get(i);
149+
Map<String, Object> map = new LinkedHashMap<>();
150+
map.put("id", item.id());
151+
map.put("name", item.name());
152+
map.put("category", item.category());
153+
map.put("price", item.price());
154+
map.put("quantity", item.quantity());
155+
map.put("active", item.active());
156+
map.put("tags", item.tags());
157+
map.put("rating", item.rating());
158+
map.put("total", (long) item.price() * item.quantity() * m);
159+
items.add(map);
160+
}
161+
sendJson(ex, Map.of("items", items, "count", items.size()));
162+
}
163+
}
164+
165+
static class UploadHandler implements HttpHandler {
166+
public void handle(HttpExchange ex) throws IOException {
167+
long size = ex.getRequestBody().transferTo(OutputStream.nullOutputStream());
168+
sendText(ex, String.valueOf(size));
169+
}
170+
}
171+
172+
static class StaticHandler implements HttpHandler {
173+
public void handle(HttpExchange ex) throws IOException {
174+
String path = ex.getRequestURI().getPath();
175+
String file = path.substring("/static/".length());
176+
Path filePath = Path.of("/data/static", file);
177+
if (!Files.exists(filePath)) {
178+
ex.sendResponseHeaders(404, -1);
179+
ex.close();
180+
return;
181+
}
182+
byte[] data = Files.readAllBytes(filePath);
183+
String contentType = Files.probeContentType(filePath);
184+
if (contentType == null) contentType = "application/octet-stream";
185+
ex.getResponseHeaders().set("Content-Type", contentType);
186+
ex.sendResponseHeaders(200, data.length);
187+
ex.getResponseBody().write(data);
188+
ex.close();
189+
}
190+
}
191+
192+
static class AsyncDbHandler implements HttpHandler {
193+
public void handle(HttpExchange ex) throws IOException {
194+
Map<String, String> params = parseQuery(ex.getRequestURI().getQuery());
195+
int min = Integer.parseInt(params.getOrDefault("min", "10"));
196+
int max = Integer.parseInt(params.getOrDefault("max", "50"));
197+
int limit = Math.min(Math.max(Integer.parseInt(params.getOrDefault("limit", "50")), 1), 50);
198+
199+
RowSet<Row> rows;
200+
try {
201+
rows = pgPool.preparedQuery("SELECT id, name, category, price, quantity, active, tags, rating_score, rating_count FROM items WHERE price BETWEEN $1 AND $2 LIMIT $3")
202+
.execute(Tuple.of(min, max, limit))
203+
.toCompletionStage().toCompletableFuture().join();
204+
} catch (Exception e) {
205+
ex.sendResponseHeaders(500, -1);
206+
ex.close();
207+
return;
208+
}
209+
210+
List<Item> items = new ArrayList<>();
211+
for (Row row : rows) {
212+
List<String> tags = row.getJsonArray("tags").stream().map(Object::toString).collect(java.util.stream.Collectors.toList());
213+
items.add(new Item(row.getInteger("id"), row.getString("name"), row.getString("category"),
214+
row.getInteger("price"), row.getInteger("quantity"), row.getBoolean("active"),
215+
tags, new Rating(row.getInteger("rating_score"), row.getInteger("rating_count"))));
216+
}
217+
sendJson(ex, Map.of("items", items, "count", items.size()));
218+
}
219+
}
220+
221+
static class FortunesHandler implements HttpHandler {
222+
public void handle(HttpExchange ex) throws IOException {
223+
RowSet<Row> rows;
224+
try {
225+
rows = pgPool.preparedQuery("SELECT id, message FROM fortune")
226+
.execute()
227+
.toCompletionStage().toCompletableFuture().join();
228+
} catch (Exception e) {
229+
ex.sendResponseHeaders(500, -1);
230+
ex.close();
231+
return;
232+
}
233+
234+
List<Fortune> fortunes = new ArrayList<>();
235+
for (Row row : rows) {
236+
fortunes.add(new Fortune(row.getInteger("id"), row.getString("message")));
237+
}
238+
fortunes.add(new Fortune(0, "Additional fortune added at request time."));
239+
fortunes.sort((a, b) -> a.message().compareTo(b.message()));
240+
241+
StringWriter writer = new StringWriter();
242+
fortunesTemplate.evaluate(writer, Map.of("fortunes", fortunes));
243+
byte[] bytes = writer.toString().getBytes();
244+
ex.getResponseHeaders().set("Content-Type", "text/html; charset=utf-8");
245+
ex.sendResponseHeaders(200, bytes.length);
246+
ex.getResponseBody().write(bytes);
247+
ex.close();
248+
}
249+
}
250+
251+
record Fortune(int id, String message) {}
252+
record Item(int id, String name, String category, int price, int quantity, boolean active, List<String> tags, Rating rating) {}
253+
record Rating(int score, int count) {}
254+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head><title>Fortunes</title></head>
4+
<body>
5+
<table>
6+
<tr><th>id</th><th>message</th></tr>
7+
{% for f in fortunes %}
8+
<tr><td>{{ f.id }}</td><td>{{ f.message }}</td></tr>
9+
{% endfor %}
10+
</table>
11+
</body>
12+
</html>

0 commit comments

Comments
 (0)