# 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()