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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,6 @@ node_modules/
# Python bytecode; a .pyc was tracked and churned on every build
__pycache__/
*.pyc

# clojure tools-deps classpath cache
.cpcache/
2 changes: 2 additions & 0 deletions frameworks/axum/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ edition = "2021"

[dependencies]
axum = "0.8"
axum-server = { version = "0.7", features = ["tls-rustls-no-provider"] }
rustls = { version = "0.23", default-features = false, features = ["ring", "logging", "std", "tls12"] }
tokio = { version = "1", features = ["full"] }
tower-http = { version = "0.6", features = ["compression-gzip"] }
serde = { version = "1", features = ["derive"] }
Expand Down
1 change: 1 addition & 0 deletions frameworks/axum/meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"pipelined",
"limited-conn",
"json",
"json-tls",
"json-comp",
"upload"
],
Expand Down
20 changes: 20 additions & 0 deletions frameworks/axum/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use axum::body::Bytes;
use axum::extract::{DefaultBodyLimit, Path, Query, State};
use axum::routing::{get, post};
use axum::{Json, Router};
use axum_server::tls_rustls::RustlsConfig;
use serde::{Deserialize, Serialize};
use tower_http::compression::CompressionLayer;

Expand Down Expand Up @@ -128,6 +129,25 @@ async fn main() {
.layer(DefaultBodyLimit::max(MAX_BODY))
.with_state(dataset);

// json-tls on 8081, served by the same Router. axum-server is axum's own
// TLS companion (it is what the axum tls-rustls example uses), so the
// accept loop is the framework's rather than hand-rolled here. The harness
// only mounts /certs for the TLS profiles, hence the guard.
let cert = std::path::Path::new("/certs/server.crt");
let key = std::path::Path::new("/certs/server.key");
if cert.exists() && key.exists() {
// ring rather than aws-lc-rs: same TLS, no C toolchain in the build image
rustls::crypto::ring::default_provider().install_default().ok();
let tls_config = RustlsConfig::from_pem_file(cert, key).await.unwrap();
let tls_app = app.clone();
tokio::spawn(async move {
axum_server::bind_rustls("0.0.0.0:8081".parse().unwrap(), tls_config)
.serve(tls_app.into_make_service())
.await
.unwrap();
});
}

let listener = tokio::net::TcpListener::bind("0.0.0.0:8080").await.unwrap();
axum::serve(listener, app).await.unwrap();
}
11 changes: 11 additions & 0 deletions frameworks/drogon/main.cc
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#include <filesystem>
#include <drogon/drogon.h>

#include <cstdlib>
Expand Down Expand Up @@ -109,6 +110,16 @@ int main()
},
{Post});

// json-tls on 8081, served by the same handlers as 8080 through Drogon's
// own TLS listener. The harness mounts /certs for the TLS profiles only,
// so without them the listener is not added.
const std::string certFile = "/certs/server.crt";
const std::string keyFile = "/certs/server.key";
if (std::filesystem::exists(certFile) && std::filesystem::exists(keyFile))
{
app().addListener("0.0.0.0", 8081, true, certFile, keyFile);
}

app().setLogLevel(trantor::Logger::kError)
.addListener("0.0.0.0", 8080)
.setThreadNum(0)
Expand Down
1 change: 1 addition & 0 deletions frameworks/drogon/meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"pipelined",
"limited-conn",
"json",
"json-tls",
"json-comp",
"upload"
],
Expand Down
22 changes: 19 additions & 3 deletions frameworks/fastify/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,11 @@ if (cluster.isPrimary) {
start();
}

async function start() {
const fastify = require('fastify')({ logger: false });
// Fastify binds one instance to one server, so the TLS listener needs its own
// instance. build() is the single definition of the app — both ports register
// the identical plugins and routes from it, rather than a hand-copied subset.
async function build(serverOpts) {
const fastify = require('fastify')({ logger: false, ...serverOpts });
const fs = require('fs');
const Database = require('better-sqlite3');

Expand Down Expand Up @@ -178,5 +181,18 @@ async function start() {
}
});

await fastify.listen({ port: 8080, host: '0.0.0.0' });
return fastify;
}

async function start() {
const fs = require('fs');
await (await build({})).listen({ port: 8080, host: '0.0.0.0' });

// json-tls on 8081. The harness only mounts /certs for the TLS profiles.
const cert = '/certs/server.crt';
const key = '/certs/server.key';
if (fs.existsSync(cert) && fs.existsSync(key)) {
const tls = await build({ https: { key: fs.readFileSync(key), cert: fs.readFileSync(cert) } });
await tls.listen({ port: 8081, host: '0.0.0.0' });
}
}
1 change: 1 addition & 0 deletions frameworks/fastify/meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"pipelined",
"limited-conn",
"json",
"json-tls",
"json-comp",
"upload",
"api-4",
Expand Down
22 changes: 22 additions & 0 deletions frameworks/frankenphp-trueasync/Caddyfile
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,25 @@
}
}
}

# json-tls: the same worker as :8080, over TLS. /certs is mounted for every
# profile (scripts/lib/framework.sh), which is why :8443 above is unconditional
# too.
:8081 {
tls /certs/server.crt /certs/server.key {
# json-tls is HTTP/1.1 over TLS; 8443 keeps the h2 ALPN for its own profiles
alpn http/1.1
}
root * /app
php_server {
index off
file_server off
worker {
file /app/worker.php
num {$WORKERS:0}
async
buffer_size 1
match /*
}
}
}
8 changes: 8 additions & 0 deletions frameworks/frankenphp-trueasync/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
FROM trueasync/php-true-async:latest-frankenphp

# The frankenphp binary in this image links libwebp, but the image does not
# carry it -- it exits at startup with "error while loading shared libraries:
# libwebpdemux.so.2". The tag is :latest, so this arrives with whatever
# upstream last pushed rather than with anything in this entry.
RUN apt-get update && apt-get install -y --no-install-recommends \
libwebpdemux2 libwebpmux3 libwebp7 && \
rm -rf /var/lib/apt/lists/*

WORKDIR /app

COPY worker.php /app/worker.php
Expand Down
3 changes: 2 additions & 1 deletion frameworks/frankenphp-trueasync/meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"pipelined",
"limited-conn",
"json",
"json-tls",
"json-comp",
"upload",
"static",
Expand All @@ -30,4 +31,4 @@
"static-h3"
],
"maintainers": []
}
}
5 changes: 4 additions & 1 deletion frameworks/h2o-mruby/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,11 @@ RUN git clone --recurse-submodules --depth 1 https://github.com/h2o/h2o.git . &&
cmake --install build

FROM debian:bookworm-slim
# libbrotli1 carries libbrotlidec/libbrotlienc: the build stage installs
# libbrotli-dev, so h2o links against brotli and will not start without it
# ("error while loading shared libraries: libbrotlidec.so.1").
RUN apt-get update && apt-get install -y --no-install-recommends \
libssl3 jq && \
libssl3 libbrotli1 jq && \
rm -rf /var/lib/apt/lists/*
COPY --from=build /usr/local/bin/h2o /usr/local/bin/h2o
COPY --from=build /usr/local/share/h2o /usr/local/share/h2o
Expand Down
8 changes: 8 additions & 0 deletions frameworks/h2o-mruby/entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,14 @@ listen: &ssl_listen
listen:
<<: *ssl_listen
type: quic

# json-tls: HTTP/1.1 over TLS on its own port. 8443 advertises h2 through
# ALPN, so it cannot double as the HTTP/1.1 listener.
listen:
port: 8081
ssl:
certificate-file: ${CERT_FILE}
key-file: ${KEY_FILE}
EOF
fi

Expand Down
3 changes: 2 additions & 1 deletion frameworks/h2o-mruby/meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"pipelined",
"limited-conn",
"json",
"json-tls",
"json-comp",
"upload",
"static",
Expand All @@ -27,4 +28,4 @@
"static-h3"
],
"maintainers": []
}
}
1 change: 1 addition & 0 deletions frameworks/hono-node/meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"pipelined",
"limited-conn",
"json",
"json-tls",
"json-comp",
"upload"
],
Expand Down
18 changes: 17 additions & 1 deletion frameworks/hono-node/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import { Hono } from "hono";
import { compress } from "hono/compress";
import cluster from "node:cluster";
import os from "node:os";
import { readFileSync } from "node:fs";
import { existsSync, readFileSync } from "node:fs";
import { createServer as createHttpsServer } from "node:https";

const SERVER_NAME = "hono-node";

Expand Down Expand Up @@ -110,4 +111,19 @@ if (cluster.isPrimary) {

// Start — node:http through the Hono adapter, one worker per core
serve({ fetch: app.fetch, port: 8080 });

// json-tls on 8081: the same app.fetch behind node:https. The adapter takes the
// server factory, so this is the identical Hono instance and middleware chain,
// not a second copy of the routes. Certs are only mounted for the TLS profiles.
if (existsSync("/certs/server.crt") && existsSync("/certs/server.key")) {
serve({
fetch: app.fetch,
port: 8081,
createServer: createHttpsServer,
serverOptions: {
key: readFileSync("/certs/server.key"),
cert: readFileSync("/certs/server.crt"),
},
});
}
}
1 change: 1 addition & 0 deletions frameworks/http4k/meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"pipelined",
"limited-conn",
"json",
"json-tls",
"json-comp",
"upload"
],
Expand Down
47 changes: 46 additions & 1 deletion frameworks/http4k/src/main/kotlin/Main.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
import io.undertow.UndertowOptions
import java.io.File
import java.io.FileInputStream
import java.security.KeyFactory
import java.security.KeyStore
import java.security.cert.Certificate
import java.security.cert.CertificateFactory
import java.security.spec.PKCS8EncodedKeySpec
import java.util.Base64
import javax.net.ssl.KeyManagerFactory
import javax.net.ssl.SSLContext
import org.http4k.core.ContentType
import org.http4k.core.Method
import org.http4k.core.Request
Expand Down Expand Up @@ -106,18 +115,54 @@ val app = routes(
"/upload" bind Method.POST to ::upload
)

// json-tls needs HTTP/1.1 over TLS on 8081. Undertow takes an SSLContext, and
// the harness mounts PEMs, so the pair is converted in-process. Plain JDK
// crypto APIs rather than java.security.PEMDecoder, which is still a preview
// API on this JDK. Null when the certs are absent -- the harness only mounts
// them for the TLS profiles.
fun tlsContext(): SSLContext? {
val cert = File("/certs/server.crt")
val key = File("/certs/server.key")
if (!cert.exists() || !key.exists()) return null

val chain: Array<Certificate> = FileInputStream(cert).use {
CertificateFactory.getInstance("X.509").generateCertificates(it).toTypedArray()
}
val der = Base64.getDecoder().decode(
key.readText()
.replace(Regex("-----(BEGIN|END) PRIVATE KEY-----"), "")
.replace(Regex("\\s"), "")
)
val privateKey = KeyFactory.getInstance("RSA").generatePrivate(PKCS8EncodedKeySpec(der))
val password = CharArray(0)
val store = KeyStore.getInstance("PKCS12").apply {
load(null, password)
setKeyEntry("server", privateKey, password, chain)
}
val keyManagers = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm())
.apply { init(store, password) }
.keyManagers

return SSLContext.getInstance("TLS").apply { init(keyManagers, null, null) }
}

fun main() {
// The stock http4k Undertow config caps requests at 10 MB and the upload
// profile sends 20 MB, so the same server is built with a larger limit, the
// way the http4k Undertow source suggests.
val handler = ServerFilters.GZip().then(app)
val (httpHandler, rootHandler) = buildUndertowHandlers(handler, null, null, Immediate)

io.undertow.Undertow.builder()
val builder = io.undertow.Undertow.builder()
.addHttpListener(8080, "0.0.0.0")
.setServerOption(UndertowOptions.MAX_ENTITY_SIZE, 30L * 1024 * 1024)
.setWorkerThreads(32 * Runtime.getRuntime().availableProcessors())
.setHandler(rootHandler)

// Same builder, so 8081 runs the identical handler chain as 8080.
tlsContext()?.let { builder.addHttpsListener(8081, "0.0.0.0", it) }

builder
.buildHttp4kUndertowServer(httpHandler, Immediate, 8080)
.start()
}
1 change: 1 addition & 0 deletions frameworks/http4s/meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"pipelined",
"limited-conn",
"json",
"json-tls",
"json-comp",
"upload"
],
Expand Down
Loading
Loading