90 lines
2.9 KiB
Nim
90 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[Ref]) 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)
|
|
|
|
var http = newAsyncHttpServer()
|
|
asyncCheck serve(http, Port 8888, handleRequest)
|
|
|
|
runForever()
|