Buildable Syndicate http_driver

This commit is contained in:
Emery Hemingway 2024-03-19 15:38:09 +00:00
parent 3996729824
commit 128df6dc03
2 changed files with 79 additions and 54 deletions

View File

@ -7,6 +7,12 @@ import pkg/preserves
import pkg/syndicate
import pkg/syndicate/protocols/http
import taps
import ../schema/config
const
SP = { ' ', '\x09', '\x0b', '\x0c', '\x0d' }
SupportedVersion = "HTTP/1.1"
IMF = initTimeFormat"ddd, dd MMM yyyy HH:mm:ss"
proc echo(args: varargs[string, `$`]) =
stderr.writeLine(args)
@ -22,22 +28,22 @@ proc `$`(b: seq[byte]): string = cast[string](b)
type HandlerEntity = ref object of Entity
handler: proc (turn: var Turn; req: HttpRequest; cap: Cap)
proc dateResponse(): HttpResponse
proc dateResponse(): HttpResponse =
result = HttpResponse(orKind: HttpResponseKind.header)
result.header.name = "date"
result.header.name = Symbol"date"
result.header.value = now().format(IMF)
method publish(e: Handler; turn: var Turn; a: AssertionRef; h: Handle) =
method publish(e: HandlerEntity; turn: var Turn; a: AssertionRef; h: Handle) =
var ctx = a.value.preservesTo HttpContext
if ctx.isSome:
var res = ctx.get.res.unembed Cap
if res.isSome:
e.handler(turn, ctx.get.req, cap)
e.handler(turn, ctx.get.req, res.get)
proc respond404(turn: var Turn; req: HttpRequest; cap: Cap) =
message(turn, cap, HttpResponse(
orKind: HttpResponseKind.status,
status: HttpResponseStatus(code: "404"),
status: HttpResponseStatus(code: 404),
))
message(turn, cap, dateResponse())
message(turn, cap, HttpResponse(orKind: HttpResponseKind.done))
@ -48,14 +54,9 @@ proc bind404Handler(turn: var Turn; ds: Cap; port: Port) =
b.port = BiggestInt port
b.method = MethodPattern(orKind: MethodPatternKind.any)
b.path = @[PathPatternElement(orKind: PathPatternElementKind.rest)]
p.handler = newCap(turn, HandlerEntity(handler: respond404))
b.handler = newCap(turn, HandlerEntity(handler: respond404)).toPreserves
discard publish(turn, ds, b)
const
SP = { ' ', '\x09', '\x0b', '\x0c', '\x0d' }
SupportedVersion = "HTTP/1.1"
IMF = initTimeFormat"ddd, dd MMM yyyy HH:mm:ss"
proc badRequest(conn: Connection; msg: string) =
conn.send(SupportedVersion & " 400 " & msg)
close(conn)
@ -71,6 +72,7 @@ proc extractQuery(s: var string): Table[Symbol, seq[QueryValue]] =
result[Symbol key] = list
proc parseRequest(conn: Connection; text: string): (int, HttpRequest) =
## Parse an `HttpRequest` request out of a `text` from a `Connection`.
var
token: string
off: int
@ -130,20 +132,16 @@ proc parseRequest(conn: Connection; text: string): (int, HttpRequest) =
result[0] = off
#[
var body = $req
var stream = newStringStream()
stream.writeLine(SupportedVersion, " 200 OK")
stream.writeLine("date: ", now().format(IMF))
stream.writeLine("content-length: ", body.len)
stream.writeLine()
stream.write(body)
echo "send ", stream.data.len, " bytes"
conn.send(stream.data, endOfMessage = true)
]#
proc send(conn: Connection; chunk: Chunk) =
case chunk.orKind
of ChunkKind.string:
conn.send chunk.string
of ChunkKind.bytes:
conn.send chunk.bytes
proc runSubfacet(facet: Facet, act: TurnAction) =
run(facet) do (t: var Turn): inFacet(t, act)
run(facet) do (t: var Turn):
discard inFacet(t, act)
type
Driver = ref object
@ -154,22 +152,46 @@ type
driver: Driver
conn: Connection
port: Port
Exchange = ref object
facet: Facet
Exchange = ref object of Entity
ses: Session
req: HttpRequest
handlers: seq[Cap]
stream: StringStream
proc match(driver: Driver; req: HttpRequest): Option[HttpBinding] =
discard
# TODO
method message(e: Exchange; turn: var Turn; a: AssertionRef) =
# Send responses back into a connection.
var res: HttpResponse
if res.fromPreserves a.value:
case res.orKind
of HttpResponseKind.status:
e.stream.writeLine(SupportedVersion, " ", res.status.code, " ", res.status.message)
e.stream.writeLine("date: ", now().format(IMF))
# add Date header automatically - RFC 9110 Section 6.6.1.
of HttpResponseKind.header:
e.stream.writeLine(res.header.name, ": ", res.header.value)
of HttpResponseKind.chunk:
if e.stream.data.len > 0:
e.stream.writeLine()
e.ses.conn.send(move e.stream.data)
e.ses.conn.send(res.chunk.chunk)
of HttpResponseKind.done:
e.ses.conn.send(res.done.chunk)
close e.ses.conn
# TODO: leave connection open.
proc service(turn: var Turn; exch: Exchange) =
## Service an HTTP message exchange.
let pat = HttpService ?:{
0: drop(),
1: ?ses.port,
2: ?req.method,
3: grab()
4: grab()
}
onPublish(turn, ses.driver.ds, pat) do (
var binding = exch.ses.driver.match exch.req
if binding.isSome:
var handler = binding.get.handler.unembed Cap
if handler.isSome:
publish(turn, handler.get, HttpContext(
req: exch.req,
res: embed newCap(turn, exch),
))
proc service(ses: Session) =
## Service a connection to an HTTP client.
@ -179,19 +201,21 @@ proc service(ses: Session) =
stop ses.facet
ses.conn.onReceived do (data: seq[byte]; ctx: MessageContext):
echo "connection received ", data.len, " bytes"
var (n, req) = parseRequest(conn, cast[string](data))
echo "parseRequest parsed ", n, " bytes"
if n < 1:
stop(ses.facet)
else:
runSubfacet(facet) do (turn: var Turn):
service Exchange(
facet: turn.facet,
ses: session,
req: req,
)
conn.receive()
conn.receive()
ses.facet.run do (turn: var Turn):
var (n, req) = parseRequest(ses.conn, cast[string](data))
echo "parseRequest parsed ", n, " bytes"
if n < 1:
stop(turn, ses.facet)
else:
inFacet(turn) do (turn: var Turn):
# start a facet
turn.service Exchange(
facet: turn.facet,
ses: ses,
req: req,
)
ses.conn.receive()
ses.conn.receive()
proc newListener(port: Port): Listener =
var lp = newLocalEndpoint()
@ -199,13 +223,14 @@ proc newListener(port: Port): Listener =
listen newPreconnection(local=[lp])
proc httpListen(turn: var Turn; driver: Driver; port: Port) =
let listener = newListener(port)
var listener = newListener(port)
# TODO: let listener
turn.facet.onStop do (turn: var Turn):
stop listener
listener.onConnectionReceived do (conn: Connection):
run(facet) do (turn: var Turn):
driver.facet.run do (turn: var Turn):
# start a new turn
linkActor("http-conn") do (turn: var Turn):
linkActor(turn, "http-conn") do (turn: var Turn):
# start a new actor
service Session(
facet: turn.facet,
@ -230,8 +255,8 @@ proc httpDriver(turn: var Turn; ds: Cap) =
# TODO: only here for testing
proc spawnHttpDriver*(turn: var Turn; root: Cap) =
during(turn, root, ?HttpDriverArguments) do (ds: Cap):
spawnActor("http-driver") do (turn: var Turn):
during(turn, root, ?:HttpDriverArguments) do (ds: Cap):
spawnActor("http-driver", turn) do (turn: var Turn):
httpDriver(turn, ds)
when isMainModule:

View File

@ -1,6 +1,6 @@
# Package
version = "20240209"
version = "20240319"
author = "Emery Hemingway"
description = "Utilites for Syndicated Actors and Synit"
license = "unlicense"
@ -10,4 +10,4 @@ bin = @["mintsturdyref", "mount_actor", "msg", "net_mapper", "preserve
# Dependencies
requires "nim >= 2.0.0", "illwill", "syndicate >= 20240208", "ws", "https://github.com/ehmry/nim-sys.git#b974e1a4ca6ae7d89fc9e7b3714b1e7daf6f33e5", "https://github.com/nim-works/cps"
requires "syndicate#b209548f5d15f7391c08fcaec3615ed843f8a410", "https://git.sr.ht/~ehmry/nim_taps#6f1252d0d17cd56fd707b831c893758ddca08755"