syndicate_utils/src/http_translator.nim

93 lines
2.9 KiB
Nim

# SPDX-FileCopyrightText: ☭ 2022 Emery Hemingway
# SPDX-License-Identifier: Unlicense
import std/[asyncdispatch, asynchttpserver, sets, strutils, tables, uri]
import preserves
import syndicate, syndicate/actors
import ./schema/http_protocol
func toHttpCore(methods: Methods): set[HttpMethod] =
# Convert the schema type to the type in the httpcore module.
for m in methods:
result.incl(
case m
of Method.GET: HttpGET
of Method.HEAD: HttpHEAD
of Method.POST: HttpPOST
of Method.PUT: HttpPUT
of Method.DELETE: HttpDELETE
of Method.CONNECT: HttpCONNECT
of Method.OPTIONS: HttpOPTIONS
of Method.TRACE: HttpTRACE
of Method.PATCH: HttpPATCH)
proc splitPath(u: Uri): Path =
u.path.strip(chars = {'/'}).split("/")
proc hitch(a, b: Future[void]) =
a.addCallback do (f: Future[void]):
if f.failed: fail(b, f.error)
else: complete(b)
bootDataspace("main") do (ds: Ref; turn: var Turn):
connectStdio(ds, turn)
var
handlers: Table[seq[string], (Ref, set[HttpMethod])]
pathPrefixMaxLen = 0
requestIdSource = 0
during(turn, ds, ?Handler) do (methods: Methods; path: seq[string]; entity: Ref):
handlers[path] = (entity, methods.toHttpCore)
pathPrefixMaxLen = max(pathPrefixMaxLen, path.len)
do:
handlers.del(path)
pathPrefixMaxLen = 0
for path in handlers.keys:
pathPrefixMaxLen = max(pathPrefixMaxLen, path.len)
var parentFacet = turn.facet
proc handleRequest(req: asynchttpserver.Request): Future[void] =
# TODO: use pattern matching
var
entity: Ref
methods: set[HttpMethod]
path = req.url.splitPath()
block:
var prefix = path[0..min(pathPrefixMaxLen.succ, path.high)]
while entity.isNil:
(entity, methods) = handlers.getOrDefault(prefix)
if prefix.len == 0: break
else: discard prefix.pop()
if entity.isNil:
result = req.respond(Http503, "no handler registered for this path")
else:
var parentFut = newFuture[void]("handleRequest")
result = parentFut
if req.reqMethod notin methods:
result = req.respond(Http405, "method not valid for this handler")
else:
run(parentFacet) do (turn: var Turn):
inc requestIdSource
let
rId = requestIdSource
let rHandle = publish(turn, entity, http_protocol.Request(
handle: rId,
`method`: Method.GET,
headers: req.headers.table,
path: path,
body: req.body))
onPublish(turn, entity, Response ? { 0: ?rId, 1: ?int, 3: ?string }) do (code: HttpCode, body: string):
req.respond(code, body).addCallback do (fut: Future[void]):
run(parentFacet) do (turn: var Turn): retract(turn, rHandle)
hitch(fut, parentFut)
during(turn, ds, ?Listener) do (port: Port):
var http = newAsyncHttpServer()
asyncCheck serve(http, port, handleRequest)
do:
close(http)
runForever()