A minimal HTTP server written in Wren.
System packages:
sudo apt-get install netcat # for bin/wren-server (single-connection mode)
sudo apt-get install ucspi-tcp # for bin/wren-tcpserver (concurrent mode)Two server modes are provided in bin/:
Single-connection (one request at a time, useful for development):
bin/wren-serverUses a named pipe and netcat to loop indefinitely, handling one connection before accepting the next. All raw traffic is logged to log/inflow and log/outflow.
Concurrent (spawns a new process per connection):
bin/wren-tcpserverUses tcpserver from ucspi-tcp to accept multiple simultaneous connections.
Both modes listen on port 8080.
Each incoming HTTP request is piped to a fresh wren_cli server.wren process via stdin. The pipeline is:
Request → request.wren → router.wren → routes.wren → resource method → response.wren → Response
-
request.wrenreads the request line and headers from stdin (terminating at the blank line), parses headers into a map, then reads the body up toContent-Lengthbytes usingStdin.readByte(). It exposesmethod,uri,route,headers,queryString,body, andparams. -
parameters.wrenparses anapplication/x-www-form-urlencodedbody into key/value pairs, with full URL decoding (+→ space,%XX→ character). Access viaparams.get("key")orparams.all. -
routes.wrenmaps route strings (e.g."POST /test") to resource method expressions (e.g."TestResource.create()"). -
router.wrenlooks up the route, usesMeta.compileExpression()to evaluate the resource method string, runs it in aFiberto catch errors, and responds with 200, 404, or 500 accordingly. -
response.wrenbuilds and writes the HTTP/1.0 response with status, headers, and body.
| Method | Path | Handler |
|---|---|---|
| GET | / |
StatusResource.index() |
| GET | /test |
TestResource.index() |
| POST | /test |
TestResource.create() |
| GET | /test/new |
TestResource.newForm() |
| GET | /test/1/edit |
TestResource.edit() |
| PATCH | /test/1 |
TestResource.update() |
| DELETE | /test/1 |
TestResource.destroy() |
Route IDs are currently hardcoded. Dynamic segment matching (:id) is not yet implemented.
Add an entry to the map in routes.wren and a corresponding static method on a resource class in router.wren:
// routes.wren
"GET /widgets": "WidgetResource.index()",
// router.wren
class WidgetResource {
static index() {
return "list of widgets"
}
}
# Fire repeated POST requests
test/curler
# Load test with siege
test/bencher