From c331a46be4064cf1a0226fe2fbc2fb07d41a520c Mon Sep 17 00:00:00 2001 From: Aofusa <5000enPCinAOFUSA@gmail.com> Date: Thu, 5 Nov 2026 00:41:42 +0000 Subject: [PATCH] Add veil-python (Python) Co-Authored-By: Claude Opus 5.5 --- frameworks/veil-python/Dockerfile | 8 ++ frameworks/veil-python/README.md | 30 ++++++ frameworks/veil-python/app.py | 108 +++++++++++++++++++ frameworks/veil-python/meta.json | 41 +++++++ frameworks/veil-python/proto/benchmark.proto | 37 +++++++ frameworks/veil-python/requirements.txt | 3 + frameworks/veil-python/veil.toml | 50 +++++++++ 7 files changed, 277 insertions(+) create mode 100644 frameworks/veil-python/Dockerfile create mode 100644 frameworks/veil-python/README.md create mode 100644 frameworks/veil-python/app.py create mode 100644 frameworks/veil-python/meta.json create mode 100644 frameworks/veil-python/proto/benchmark.proto create mode 100644 frameworks/veil-python/requirements.txt create mode 100644 frameworks/veil-python/veil.toml diff --git a/frameworks/veil-python/Dockerfile b/frameworks/veil-python/Dockerfile new file mode 100644 index 0000000000..96ae29e833 --- /dev/null +++ b/frameworks/veil-python/Dockerfile @@ -0,0 +1,8 @@ +FROM python:3.13-slim +WORKDIR /app +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt +COPY . . +RUN mkdir -p gen && python -m grpc_tools.protoc -I proto --python_out=gen --grpc_python_out=gen proto/benchmark.proto +EXPOSE 8080 8081 8082 8443/tcp 8443/udp +CMD ["python3", "app.py"] diff --git a/frameworks/veil-python/README.md b/frameworks/veil-python/README.md new file mode 100644 index 0000000000..bf26dc9927 --- /dev/null +++ b/frameworks/veil-python/README.md @@ -0,0 +1,30 @@ +# veil-python + +An application written with [veil-api](https://pypi.org/project/veil-api/), the Python bindings of the +[veil](https://github.com/aofusa/veil-proxy) server. The HTTP/1.1, HTTP/2, HTTP/3 and WebSocket server is veil's +(Rust); Python runs the handlers. The Linux wheels use veil's epoll readiness reactor. + +## Endpoints + +| Endpoint | Implementation | +|---|---| +| `GET`/`POST /baseline11`, `GET /baseline2`, `GET /pipeline` | `@app.get` / `@app.post` with `a: int, b: int` bound from the query; `offload=False` runs these small, non-blocking handlers on the network worker (documented in veil-api's production notes) | +| `GET /json/{count}?m=` | returns a `dict`, serialized to JSON by veil on every request | +| `GET /delay/{ms}` | `async def` with `await asyncio.sleep`, on veil's persistent asyncio event loop | +| `POST /echo` | returns the request body | +| `GET /ws` | `@app.websocket`, echoing text and binary messages | +| `benchmark.BenchmarkService` (gRPC) | the servicer generated by `grpcio-tools`, added to `veil.grpc.Server(app)` with the generated `add_BenchmarkServiceServicer_to_server` | +| `/static/*` | `app.serve_dir` (veil's File backend: br/gzip compressed on the fly on HTTP/1.1, HTTP/2 and HTTP/3, uncompressed responses with sendfile on HTTP/1.1, caches that follow the disk) | + +## Configuration + +`veil.toml` holds the settings of veil-api's production guide: JSON application log, no access log, OpenAPI off, +response compression on (`[api] compression`), static caches following the disk. The four HttpArena listeners are +`[[listener]]` entries in **one process**. + +## Completeness + +- routing, request, response: decorators with path parameters; query, header and body binding (buffered, or a + stream with `stream_body=True`); `dict` / `str` / `bytes` / `veil.Response` / streaming and file responses. +- middleware: declared `false`. veil-api has dependencies (`Depends`, run before the handler and able to answer + early) and native CORS, but no general before-and-after middleware around native routes. diff --git a/frameworks/veil-python/app.py b/frameworks/veil-python/app.py new file mode 100644 index 0000000000..1277e84d57 --- /dev/null +++ b/frameworks/veil-python/app.py @@ -0,0 +1,108 @@ +"""HttpArena entry for veil-api (Python). + +Handlers are ``@app.get`` / ``@app.post`` functions; ``async def`` handlers run on veil's persistent event loop, +``/ws`` is ``@app.websocket``, gRPC uses the grpcio-generated servicer on ``veil.grpc.Server``, and ``/static`` is +``app.serve_dir``. Listeners, TLS, compression and logging come from ``veil.toml`` (one process, four listeners). +""" + +import asyncio +import json +import os +import sys + +import veil +import veil.grpc + +HERE = os.path.dirname(os.path.abspath(__file__)) +sys.path.insert(0, os.path.join(HERE, "gen")) + +import benchmark_pb2 as pb # noqa: E402 +import benchmark_pb2_grpc # noqa: E402 + +try: + with open("/data/dataset.json") as f: + DATASET = json.load(f) +except OSError: + DATASET = [] + +app = veil.App(title="httparena") + + +@app.get("/baseline11", offload=False) +def baseline11_get(a: int, b: int): + return str(a + b) + + +@app.post("/baseline11", offload=False) +def baseline11_post(request: veil.Request, a: int, b: int): + try: + n = int(request.body.strip() or 0) + except ValueError: + n = 0 + return str(a + b + n) + + +@app.get("/baseline2", offload=False) +def baseline2(a: int, b: int): + return str(a + b) + + +@app.get("/pipeline", offload=False) +def pipeline(): + return "ok" + + +@app.get("/json/{count}") +def json_items(count: int, m: int = 1): + items = [dict(item, total=item["price"] * item["quantity"] * m) for item in DATASET[:count]] + return {"items": items, "count": len(items)} + + +@app.get("/delay/{ms}") +async def delay(ms: int): + await asyncio.sleep(min(ms, 60000) / 1000) + return str(ms) + + +@app.post("/echo") +def echo(request: veil.Request): + return veil.Response(request.body, media_type="application/octet-stream") + + +@app.websocket("/ws") +async def ws(websocket: veil.WebSocket): + await websocket.accept() + while True: + m = await websocket.receive() + if m["type"] == "websocket.disconnect": + return + if m.get("text") is not None: + await websocket.send_text(m["text"]) + else: + await websocket.send_bytes(m["bytes"]) + + +class Benchmark(benchmark_pb2_grpc.BenchmarkServiceServicer): + def GetSum(self, request, context): + return pb.SumReply(result=request.a + request.b) + + def StreamSum(self, request, context): + reply = pb.SumReply(result=request.a + request.b) + for _ in range(max(0, min(request.count, 1000000))): + yield reply + + def CollectSum(self, request_iterator, context): + return pb.SumReply(result=sum(r.a + r.b for r in request_iterator)) + + def EchoSum(self, request_iterator, context): + for r in request_iterator: + yield pb.SumReply(result=r.a + r.b) + + +app.serve_dir("/static", "/data/static", compression={"enabled": True}) + +server = veil.grpc.Server(app) +benchmark_pb2_grpc.add_BenchmarkServiceServicer_to_server(Benchmark(), server) + +if __name__ == "__main__": + app.run(config_path=os.environ.get("VEIL_CONFIG", os.path.join(HERE, "veil.toml"))) diff --git a/frameworks/veil-python/meta.json b/frameworks/veil-python/meta.json new file mode 100644 index 0000000000..5b32a30eb7 --- /dev/null +++ b/frameworks/veil-python/meta.json @@ -0,0 +1,41 @@ +{ + "display_name": "veil-python", + "language": "Python", + "type": "emerging", + "mode": "standard", + "completeness": { + "middleware": false + }, + "engine": "veil", + "description": "veil-api 0.10 from PyPI (Python bindings of the veil server; Linux wheels use the epoll readiness reactor). @app.get/@app.post handlers with path/query binding, dict responses serialized to JSON, async def handlers on veil's persistent asyncio event loop, @app.websocket for /ws, gRPC through the grpcio-generated servicer on veil.grpc.Server, /static/* through app.serve_dir, compression through [api] compression. One process with four listeners from the configuration file.", + "repo": "https://github.com/aofusa/veil-proxy", + "website": "https://github.com/aofusa/veil-proxy", + "enabled": true, + "tests": [ + "baseline", + "pipelined", + "limited-conn", + "async", + "latency-1m", + "latency-10k", + "latency-500k-8cpu", + "json-comp", + "json-tls", + "8gbit", + "static-tls", + "baseline-h2", + "static-h2", + "baseline-h2c", + "json-h2c", + "baseline-h3", + "static-h3", + "unary-grpc", + "unary-grpc-tls", + "echo-ws", + "echo-ws-pipeline", + "echo-ws-limited" + ], + "maintainers": [ + "aofusa" + ] +} diff --git a/frameworks/veil-python/proto/benchmark.proto b/frameworks/veil-python/proto/benchmark.proto new file mode 100644 index 0000000000..e7c9623705 --- /dev/null +++ b/frameworks/veil-python/proto/benchmark.proto @@ -0,0 +1,37 @@ +// Canonical benchmark.proto used by ghz for gRPC streaming tests. +// Each gRPC framework must implement the RPCs it subscribes to in its own +// service; the shapes must match this file exactly. + +syntax = "proto3"; + +package benchmark; + +service BenchmarkService { + // Unary — 1 request, 1 reply. Used by unary-grpc (h2load). + rpc GetSum (SumRequest) returns (SumReply); + + // Server streaming — 1 request, server emits `count` replies. Used by + // stream-grpc / stream-grpc-tls (ghz). + rpc StreamSum (StreamRequest) returns (stream SumReply); + + // Client streaming — N requests streamed in, 1 reply with the total. + rpc CollectSum (stream SumRequest) returns (SumReply); + + // Bidirectional streaming — echo: 1 reply per request over a persistent stream. + rpc EchoSum (stream SumRequest) returns (stream SumReply); +} + +message SumRequest { + int32 a = 1; + int32 b = 2; +} + +message StreamRequest { + int32 a = 1; + int32 b = 2; + int32 count = 3; +} + +message SumReply { + int32 result = 1; +} diff --git a/frameworks/veil-python/requirements.txt b/frameworks/veil-python/requirements.txt new file mode 100644 index 0000000000..108d581810 --- /dev/null +++ b/frameworks/veil-python/requirements.txt @@ -0,0 +1,3 @@ +veil-api==0.10.1 +grpcio==1.84.1 +grpcio-tools==1.84.1 diff --git a/frameworks/veil-python/veil.toml b/frameworks/veil-python/veil.toml new file mode 100644 index 0000000000..4dc0fbe234 --- /dev/null +++ b/frameworks/veil-python/veil.toml @@ -0,0 +1,50 @@ +# veil API library settings for HttpArena: the recommended production settings +# (docs/guide/library-production.md), with the four HttpArena listeners in one process. + +[[listener]] +name = "plain" # HTTP/1.1, h2c by prior knowledge (gRPC), WebSocket +listen = "0.0.0.0:8080" +tls = false + +[[listener]] +name = "h1tls" # TLS, HTTP/1.1 only +listen = "0.0.0.0:8081" +tls = true +http2 = false + +[[listener]] +name = "h2c" # cleartext HTTP/2 +listen = "0.0.0.0:8082" +tls = false + +[[listener]] +name = "https" # TLS h2 + HTTP/1.1, and HTTP/3 on UDP 8443 +listen = "0.0.0.0:8443" +tls = true +http2 = true +http3 = true + +[server] +graceful_shutdown_timeout_secs = 5 + +[tls] +cert_path = "/certs/server.crt" +key_path = "/certs/server.key" + +[logging] +level = "info" +format = "json" + +[access_log] +enabled = false + +[api] +openapi_enabled = false +compression = true +max_body_size = 1048576 + +[performance] +open_file_cache_watch = true # static files follow the disk + +[static_file_cache] +watch = true