Skip to content
Merged
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
2 changes: 1 addition & 1 deletion frameworks/swerver/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ RUN set -eux; \

# Clone swerver library
WORKDIR /src
RUN git clone --depth 1 --branch v0.1.0-alpha.24 \
RUN git clone --depth 1 --branch v0.1.0-alpha.30 \
https://github.com/justinGrosvenor/swerver.git .

# Symlink OpenSSL for Zig's linker
Expand Down
87 changes: 87 additions & 0 deletions frameworks/swerver/main.zig
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,92 @@ fn handleUpload(ctx: *router.HandlerContext) response_mod.Response {
};
}

// ── Unary gRPC ───────────────────────────────────────────────────

fn grpcError(status: []const u8) response_mod.Response {
return .{
.status = 200,
.headers = &[_]response_mod.Header{
.{ .name = "Content-Type", .value = "application/grpc" },
.{ .name = "grpc-status", .value = status },
},
.body = .none,
};
}

fn readProtoVarint(data: []const u8, offset: *usize) ?u64 {
var value: u64 = 0;
var shift: u6 = 0;
var count: usize = 0;
while (offset.* < data.len and count < 10) : (count += 1) {
const byte = data[offset.*];
offset.* += 1;
value |= @as(u64, byte & 0x7f) << shift;
if (byte & 0x80 == 0) return value;
if (shift == 63) return null;
shift += 7;
}
return null;
}

fn writeProtoVarint(dst: []u8, value_arg: u64) ?usize {
var value = value_arg;
var written: usize = 0;
while (true) {
if (written == dst.len) return null;
var byte: u8 = @intCast(value & 0x7f);
value >>= 7;
if (value != 0) byte |= 0x80;
dst[written] = byte;
written += 1;
if (value == 0) return written;
}
}

/// POST /benchmark.BenchmarkService/GetSum — unary gRPC SumRequest → SumReply.
/// The benchmark schema is two int32 varints (`a` field 1, `b` field 2) and a
/// single int32 varint result. The gRPC envelope is the standard compression
/// flag plus a four-byte big-endian protobuf length.
fn handleGrpcSum(ctx: *router.HandlerContext) response_mod.Response {
const request_body = ctx.request.body.sliceOrNull() orelse
ctx.request.body.copyTo(ctx.response_buf) orelse return grpcError("8");
if (request_body.len < 5 or request_body[0] != 0) return grpcError("3");

const message_len = std.mem.readInt(u32, request_body[1..5], .big);
if (message_len != request_body.len - 5) return grpcError("3");
const message = request_body[5..];

var a: u64 = 0;
var b: u64 = 0;
var offset: usize = 0;
while (offset < message.len) {
const tag = readProtoVarint(message, &offset) orelse return grpcError("3");
if (tag & 0x7 != 0) return grpcError("3");
const value = readProtoVarint(message, &offset) orelse return grpcError("3");
switch (tag >> 3) {
1 => a = value,
2 => b = value,
else => {},
}
}

const out = ctx.response_buf;
out[0] = 0;
out[5] = 0x08; // SumReply.result, protobuf field 1 / wire type 0
const value_len = writeProtoVarint(out[6..], a +% b) orelse return grpcError("13");
const protobuf_len: u32 = @intCast(1 + value_len);
std.mem.writeInt(u32, out[1..5], protobuf_len, .big);

return .{
.status = 200,
.headers = &[_]response_mod.Header{
.{ .name = "Content-Type", .value = "application/grpc" },
.{ .name = "grpc-status", .value = "0" },
},
.body = .{ .bytes = out[0 .. 5 + protobuf_len] },
};
}

/// GET /json/:count?m=X — return `count` items with total = price * quantity * m
fn handleJson(ctx: *router.HandlerContext) response_mod.Response {
const count_str = ctx.getParam("count") orelse "50";
Expand Down Expand Up @@ -338,6 +424,7 @@ pub fn main(init: std.process.Init) !void {
try app_router.post("/baseline2", handleBaseline);
try app_router.get("/json/:count", handleJson);
try app_router.postDiscard("/upload", handleUpload);
try app_router.post("/benchmark.BenchmarkService/GetSum", handleGrpcSum);
try db_routes.register(&app_router);

if (cfg.workers != 1) {
Expand Down
18 changes: 15 additions & 3 deletions frameworks/swerver/meta.json
Original file line number Diff line number Diff line change
@@ -1,26 +1,38 @@
{
"display_name": "swerver",
"language": "Zig",
"type": "engine",
"type": "experimental",
"mode": "tuned",
"engine": "swerver",
"description": "swerver as an engine: no pre-computed response bodies (pre-encoded registry off), JSON serialized per request through the framework's std.json-based ctx.jsonValue, static files served from an in-memory cache with precompressed .br/.gz negotiation, and the native async PostgreSQL client (pipelined, prepared-statement cache, park/resume) for async-db and fortunes. Single-threaded event loop per worker, SO_REUSEPORT fork fan-out, one multi-listener process for the four protocol ports, native HTTP/1.1 + HTTP/2 + HTTP/3 (QUIC), kqueue/epoll/io_uring backends.",
"completeness": {
"routing": true,
"middleware": true,
"request": false,
"response": true
},
"description": "Experimental Zig web framework built directly on swerver's Router, HandlerContext, middleware, response, and static-file APIs. No pre-computed response bodies (pre-encoded registry off); JSON is serialized per request through ctx.jsonValue; unary gRPC is decoded and encoded per request over h2c and TLS h2; async-db and fortunes use the native async PostgreSQL client (pipelining, prepared-statement cache, park/resume). Single-threaded event loop per worker, SO_REUSEPORT fork fan-out, one multi-listener process for HTTP/1.1, HTTP/2, HTTP/3 (QUIC), kqueue, epoll, and io_uring backends.",
"repo": "https://github.com/justinGrosvenor/swerver",
"enabled": false,
"enabled": true,
"tests": [
"baseline",
"pipelined",
"limited-conn",
"json",
"json-tls",
"upload",
"api-4",
"api-16",
"static",
"static-tls",
"baseline-h2",
"static-h2",
"baseline-h2c",
"json-h2c",
"json-comp",
"baseline-h3",
"static-h3",
"unary-grpc",
"unary-grpc-tls",
"async-db",
"fortunes"
],
Expand Down
13 changes: 10 additions & 3 deletions site/data/frameworks.json
Original file line number Diff line number Diff line change
Expand Up @@ -1667,10 +1667,17 @@
},
"swerver": {
"dir": "swerver",
"description": "swerver as an engine: no pre-computed response bodies (pre-encoded registry off), JSON serialized per request through the framework's std.json-based ctx.jsonValue, static files served from an in-memory cache with precompressed .br/.gz negotiation, and the native async PostgreSQL client (pipelined, prepared-statement cache, park/resume) for async-db and fortunes. Single-threaded event loop per worker, SO_REUSEPORT fork fan-out, one multi-listener process for the four protocol ports, native HTTP/1.1 + HTTP/2 + HTTP/3 (QUIC), kqueue/epoll/io_uring backends.",
"description": "Experimental Zig web framework built directly on swerver's Router, HandlerContext, middleware, response, and static-file APIs. No pre-computed response bodies (pre-encoded registry off); JSON is serialized per request through ctx.jsonValue; unary gRPC is decoded and encoded per request over h2c and TLS h2; async-db and fortunes use the native async PostgreSQL client (pipelining, prepared-statement cache, park/resume). Single-threaded event loop per worker, SO_REUSEPORT fork fan-out, one multi-listener process for HTTP/1.1, HTTP/2, HTTP/3 (QUIC), kqueue, epoll, and io_uring backends.",
"repo": "https://github.com/justinGrosvenor/swerver",
"type": "engine",
"engine": "swerver"
"type": "experimental",
"engine": "swerver",
"mode": "tuned",
"completeness": {
"routing": true,
"middleware": true,
"request": false,
"response": true
}
},
"swoole": {
"dir": "swoole",
Expand Down
Loading