Initial http_protocol utility
This commit is contained in:
commit
ff6f7553e6
|
@ -0,0 +1 @@
|
|||
http_translator
|
|
@ -0,0 +1,7 @@
|
|||
# Syndicate utils
|
||||
|
||||
## http_translator
|
||||
|
||||
Dispatches HTTP requests to registered handlers.
|
||||
|
||||
See [http_translator.config-example.pr](./http_translator.config-example.pr) for an example configuration.
|
|
@ -0,0 +1,20 @@
|
|||
version 1 .
|
||||
|
||||
Method = =GET / =HEAD / =POST / =PUT / =DELETE / =CONNECT / =OPTIONS / =TRACE / =PATCH .
|
||||
Methods = #{Method} .
|
||||
|
||||
; A URL path split into elements
|
||||
Path = [string ...] .
|
||||
|
||||
; Register an entity that will handle requests at path prefix.
|
||||
; TODO: assert the public base URL of the handler to the entity.
|
||||
Handler = <handler @methods Methods @path Path @entity #!any> .
|
||||
|
||||
Headers = {string: [string ...] ...:...} .
|
||||
|
||||
; A request awaiting a response at handle.
|
||||
; TODO: query parameters
|
||||
Request = <http @handle int @method Method @headers Headers @path Path @body string> .
|
||||
|
||||
; A response to handle.
|
||||
Response = <http @handle int @code int @headers Headers @body string> .
|
|
@ -0,0 +1,21 @@
|
|||
<require-service <daemon http_translator>>
|
||||
|
||||
<daemon http_translator {
|
||||
argv: "/home/repo/syndicate/syndicate_utils/src/http_translator"
|
||||
protocol: text/syndicate
|
||||
}>
|
||||
|
||||
let ?other = dataspace
|
||||
|
||||
$other [
|
||||
? <http ?handle GET ?headers ?path ?body> [
|
||||
<http $handle 200 {} "get handler invoked">
|
||||
]
|
||||
]
|
||||
|
||||
? <service-object <daemon http_translator> ?cap> [
|
||||
$cap [
|
||||
; publish GET requests with prefix "/foo/bar" to other dataspace
|
||||
<handler #{GET} ["foo" "bar" ] $other>
|
||||
]
|
||||
]
|
|
@ -0,0 +1,2 @@
|
|||
include_rules
|
||||
: foreach ../*.prs |> !preserves_schema_nim |> %B.nim
|
|
@ -0,0 +1,40 @@
|
|||
|
||||
import
|
||||
std/typetraits, preserves, std/tables, std/sets
|
||||
|
||||
type
|
||||
Path* = seq[string]
|
||||
Headers* = TableRef[string, seq[string]]
|
||||
Response* {.preservesRecord: "http".} = object
|
||||
`handle`*: int
|
||||
`code`*: int
|
||||
`headers`*: Headers
|
||||
`body`*: string
|
||||
|
||||
Handler*[E] {.preservesRecord: "handler".} = ref object
|
||||
`methods`*: Methods
|
||||
`path`*: Path
|
||||
`entity`*: Preserve[E]
|
||||
|
||||
`Method`* {.preservesOr, pure.} = enum
|
||||
`GET`, `HEAD`, `POST`, `PUT`, `DELETE`, `CONNECT`, `OPTIONS`, `TRACE`,
|
||||
`PATCH`
|
||||
Request* {.preservesRecord: "http".} = object
|
||||
`handle`*: int
|
||||
`method`*: Method
|
||||
`headers`*: Headers
|
||||
`path`*: Path
|
||||
`body`*: string
|
||||
|
||||
Methods* = HashSet[Method]
|
||||
proc `$`*[E](x: Handler[E]): string =
|
||||
`$`(toPreserve(x, E))
|
||||
|
||||
proc encode*[E](x: Handler[E]): seq[byte] =
|
||||
encode(toPreserve(x, E))
|
||||
|
||||
proc `$`*(x: Path | Headers | Response | Request | Methods): string =
|
||||
`$`(toPreserve(x))
|
||||
|
||||
proc encode*(x: Path | Headers | Response | Request | Methods): seq[byte] =
|
||||
encode(toPreserve(x))
|
|
@ -0,0 +1,89 @@
|
|||
# SPDX-FileCopyrightText: ☭ 2022 Emery Hemingway
|
||||
# SPDX-License-Identifier: Unlicense
|
||||
|
||||
import std/[asyncdispatch, asynchttpserver, sets, strutils, tables, uri]
|
||||
import preserves
|
||||
import syndicate, syndicate/actors
|
||||
|
||||
import ./http_protocol.nim
|
||||
|
||||
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()
|
|
@ -0,0 +1,13 @@
|
|||
# Package
|
||||
|
||||
version = "0.1.0"
|
||||
author = "Emery Hemingway"
|
||||
description = "Utilites for Syndicated Actors and Synit"
|
||||
license = "unlicense"
|
||||
srcDir = "src"
|
||||
bin = @["http_translator"]
|
||||
|
||||
|
||||
# Dependencies
|
||||
|
||||
requires "nim >= 1.6.6", "syndicate >= 0.3.1"
|
Loading…
Reference in New Issue