server.bend fails
raw source on the hub · import 0x5e4e2a9db839a0214ace6923b04b685b/server.bend as Server
ezhttp/server: listen, accept, parse one request, write one response. Uses Base TCP (no TLS in v0). One exchange per connection with Connection: close. Handler is a pure Request -> Reply function.
3 imports
import Base import ./http.bend as Http import ./cors.bend as Cors
Definitions
def server.max source · line 9 · raw
U32
default recv budget for one request message
def server.listen source · line 13 · raw
@port:U32 -> IO(Listener)
listen on a port (RFC 9112 connection establishment stays at the socket)
def server.accept.of source · line 17 · raw
@lst:Listener -> @res:Result<&1, &1, Pair(U32, String), Socket> -> IO(Pair(Listener, Socket))
unpack accept: listener stays, socket from the Result
def server.accept.un source · line 25 · raw
@pair:Pair(Listener, Result<&1, &1, Pair(U32, String), Socket>) -> IO(Pair(Listener, Socket))
def server.accept source · line 30 · raw
@listener:Listener -> IO(Pair(Listener, Socket))
def server.read.why source · line 38 · raw
String
the short-body refusal, so a partial recv can continue
def server.read.short source · line 42 · raw
@why:String -> Bool
whether this refusal is a body that has not arrived yet
def server.read.need source · line 46 · raw
@req:0x5e4e2a9db839a0214ace6923b04b685b/http.Request -> Bool
a parsed request still wants more bytes
def server.read.fuel source · line 54 · raw
Nat
how many extra recvs to chase a Content-Length
def server.read.done source · line 58 · raw
@sock:Socket -> @got:String -> IO(Pair(Socket, 0x5e4e2a9db839a0214ace6923b04b685b/http.Request))
parse what has arrived
def server.read.next source · line 62 · raw
@sock:Socket -> @got:String -> @req:0x5e4e2a9db839a0214ace6923b04b685b/http.Request -> @rest:(@_:Socket -> @_:String -> IO(Pair(Socket, 0x5e4e2a9db839a0214ace6923b04b685b/http.Request))) -> @more:Bool -> IO(Pair(Socket, 0x5e4e2a9db839a0214ace6923b04b685b/http.Request))
stop on a finished message, or ask for another recv
def server.read.grew source · line 76 · raw
@sock:Socket -> @+got:String -> @rest:(@_:Socket -> @_:String -> IO(Pair(Socket, 0x5e4e2a9db839a0214ace6923b04b685b/http.Request))) -> IO(Pair(Socket, 0x5e4e2a9db839a0214ace6923b04b685b/http.Request))
append a chunk and decide whether it completes the request
def server.read.empty source · line 85 · raw
@sock:Socket -> @got:String -> @chunk:String -> @rest:(@_:Socket -> @_:String -> IO(Pair(Socket, 0x5e4e2a9db839a0214ace6923b04b685b/http.Request))) -> @closed:Bool -> IO(Pair(Socket, 0x5e4e2a9db839a0214ace6923b04b685b/http.Request))
peer closed, or the chunk extends the buffer
def server.read.use source · line 99 · raw
@sock:Socket -> @got:String -> @+chunk:String -> @rest:(@_:Socket -> @_:String -> IO(Pair(Socket, 0x5e4e2a9db839a0214ace6923b04b685b/http.Request))) -> IO(Pair(Socket, 0x5e4e2a9db839a0214ace6923b04b685b/http.Request))
one chunk of the request message
def server.read.pass source · line 108 · raw
@sock:Socket -> @got:String -> @rest:(@_:Socket -> @_:String -> IO(Pair(Socket, 0x5e4e2a9db839a0214ace6923b04b685b/http.Request))) -> @r:Result<&1, &1, Pair(U32, String), String> -> IO(Pair(Socket, 0x5e4e2a9db839a0214ace6923b04b685b/http.Request))
the bytes from one recv
def server.read.un source · line 118 · raw
@got:String -> @rest:(@_:Socket -> @_:String -> IO(Pair(Socket, 0x5e4e2a9db839a0214ace6923b04b685b/http.Request))) -> @pair:Pair(Socket, Result<&1, &1, Pair(U32, String), String>) -> IO(Pair(Socket, 0x5e4e2a9db839a0214ace6923b04b685b/http.Request))
unpack recv's socket and result
def server.read.go source · line 128 · raw
@fuel:Nat -> @sock:Socket -> @got:String -> IO(Pair(Socket, 0x5e4e2a9db839a0214ace6923b04b685b/http.Request))
recv until Content-Length is met, the peer closes, or fuel runs out. fuel is first so the recursive call is a decreasing self-call.
def server.read source · line 140 · raw
@sock:Socket -> IO(Pair(Socket, 0x5e4e2a9db839a0214ace6923b04b685b/http.Request))
recv then parse an HTTP request (RFC 9112 §3)
def server.write.res source · line 144 · raw
@sock:Socket -> @r:Result<&1, &1, Pair(U32, String), Unit> -> IO(Socket)
write a structured response and keep the socket
def server.write.un source · line 148 · raw
@pair:Pair(Socket, Result<&1, &1, Pair(U32, String), Unit>) -> IO(Socket)
def server.write source · line 153 · raw
@sock:Socket -> @reply:0x5e4e2a9db839a0214ace6923b04b685b/http.Reply -> IO(Socket)
Templates
template server.dispatch source · line 161 · raw
@-handle:(@_:0x5e4e2a9db839a0214ace6923b04b685b/http.Request -> 0x5e4e2a9db839a0214ace6923b04b685b/http.Reply) -> @req:0x5e4e2a9db839a0214ace6923b04b685b/http.Request -> 0x5e4e2a9db839a0214ace6923b04b685b/http.Reply
turn a bad request into 400; otherwise call the handler
template server.reply.built source · line 170 · raw
@-handle:(@_:0x5e4e2a9db839a0214ace6923b04b685b/http.Request -> 0x5e4e2a9db839a0214ace6923b04b685b/http.Reply) -> @method:String -> @req:0x5e4e2a9db839a0214ace6923b04b685b/http.Request -> 0x5e4e2a9db839a0214ace6923b04b685b/http.Reply
apply HEAD body stripping once the method token is known
template server.reply source · line 175 · raw
@-handle:(@_:0x5e4e2a9db839a0214ace6923b04b685b/http.Request -> 0x5e4e2a9db839a0214ace6923b04b685b/http.Reply) -> @req:0x5e4e2a9db839a0214ace6923b04b685b/http.Request -> 0x5e4e2a9db839a0214ace6923b04b685b/http.Reply
HEAD responses are written with an empty body (RFC 9110 §9.3.2)
template server.cors_reply source · line 185 · raw
@-handle:(@_:0x5e4e2a9db839a0214ace6923b04b685b/http.Request -> 0x5e4e2a9db839a0214ace6923b04b685b/http.Reply) -> @cfg:0x5e4e2a9db839a0214ace6923b04b685b/cors.Cfg -> @+req:0x5e4e2a9db839a0214ace6923b04b685b/http.Request -> 0x5e4e2a9db839a0214ace6923b04b685b/http.Reply
CORS preflight or simple headers, after HEAD bodies are dropped
template server.exchange.go source · line 193 · raw
@-handle:(@_:0x5e4e2a9db839a0214ace6923b04b685b/http.Request -> 0x5e4e2a9db839a0214ace6923b04b685b/http.Reply) -> @sock:Socket -> @req:0x5e4e2a9db839a0214ace6923b04b685b/http.Request -> IO(Unit)
after read: write the handler's reply and close
template server.exchange.un source · line 201 · raw
@-handle:(@_:0x5e4e2a9db839a0214ace6923b04b685b/http.Request -> 0x5e4e2a9db839a0214ace6923b04b685b/http.Reply) -> @pair:Pair(Socket, 0x5e4e2a9db839a0214ace6923b04b685b/http.Request) -> IO(Unit)
template server.exchange source · line 209 · raw
@-handle:(@_:0x5e4e2a9db839a0214ace6923b04b685b/http.Request -> 0x5e4e2a9db839a0214ace6923b04b685b/http.Reply) -> @sock:Socket -> IO(Unit)
one connection: read, handle, write, close
template server.loop.cont source · line 216 · raw
@-handle:(@_:0x5e4e2a9db839a0214ace6923b04b685b/http.Request -> 0x5e4e2a9db839a0214ace6923b04b685b/http.Reply) -> @again:(@_:Listener -> IO(Unit)) -> @pair:Pair(Listener, Socket) -> IO(Unit)
continue after accept: exchange, then call again with the listener.
again is a runtime closure so it can carry the fuel still left.
template server.loop.go source · line 226 · raw
@-handle:(@_:0x5e4e2a9db839a0214ace6923b04b685b/http.Request -> 0x5e4e2a9db839a0214ace6923b04b685b/http.Reply) -> @fuel:Nat -> @listener:Listener -> IO(Unit)
fuel-bounded accept loop (self-recursive)
template server.loop source · line 240 · raw
@-handle:(@_:0x5e4e2a9db839a0214ace6923b04b685b/http.Request -> 0x5e4e2a9db839a0214ace6923b04b685b/http.Reply) -> @listener:Listener -> @limit:U32 -> IO(Unit)
listen and serve up to limit connections (fuel from the limit)
template server.serve source · line 248 · raw
@-handle:(@_:0x5e4e2a9db839a0214ace6923b04b685b/http.Request -> 0x5e4e2a9db839a0214ace6923b04b685b/http.Reply) -> @port:U32 -> @limit:U32 -> IO(Unit)
listen and serve up to limit connections
template server.once.cont source · line 257 · raw
@-handle:(@_:0x5e4e2a9db839a0214ace6923b04b685b/http.Request -> 0x5e4e2a9db839a0214ace6923b04b685b/http.Reply) -> @pair:Pair(Listener, Socket) -> IO(Unit)
serve exactly one connection then stop
template server.once source · line 265 · raw
@-handle:(@_:0x5e4e2a9db839a0214ace6923b04b685b/http.Request -> 0x5e4e2a9db839a0214ace6923b04b685b/http.Reply) -> @port:U32 -> IO(Unit)