Initial commit of http driver
This commit is contained in:
parent
0dc419ebab
commit
dbe363052d
|
@ -0,0 +1,4 @@
|
|||
include_rules
|
||||
NIM_FLAGS += --path:$(TUP_CWD)/../../../taps/pkg
|
||||
: foreach *.nim | $(SYNDICATE_PROTOCOL) ../<schema> |> !nim_bin |> | {bin}
|
||||
: foreach {bin} |> !assert_built |>
|
|
@ -0,0 +1,142 @@
|
|||
# SPDX-FileCopyrightText: ☭ Emery Hemingway
|
||||
# SPDX-License-Identifier: Unlicense
|
||||
|
||||
import std/[httpcore, options, parseutils, streams, strutils, tables, times, uri]
|
||||
import pkg/sys/ioqueue
|
||||
import pkg/preserves
|
||||
import pkg/syndicate/protocols/http
|
||||
import taps
|
||||
|
||||
proc echo(args: varargs[string, `$`]) =
|
||||
stderr.writeLine(args)
|
||||
|
||||
proc `$`(b: seq[byte]): string = cast[string](b)
|
||||
|
||||
# a Date header on responses must be present if a clock is available
|
||||
|
||||
# An upgrade header can be used to switch over to native syndicate protocol.
|
||||
|
||||
# Check the response encoding matches or otherwise return 415
|
||||
|
||||
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)
|
||||
|
||||
proc extractQuery(s: var string): Table[Symbol, seq[QueryValue]] =
|
||||
let start = succ skipUntil(s, '?')
|
||||
if start < s.len:
|
||||
var query = s[start..s.high]
|
||||
s.setLen(start)
|
||||
for key, val in uri.decodeQuery(query):
|
||||
var list = result.getOrDefault(Symbol key)
|
||||
list.add QueryValue(orKind: QueryValueKind.string, string: val)
|
||||
result[Symbol key] = list
|
||||
|
||||
proc parseRequest(conn: Connection; text: string): (int, HttpRequest) =
|
||||
var
|
||||
token: string
|
||||
off: int
|
||||
|
||||
template advanceSp =
|
||||
let n = skipWhile(text, SP, off)
|
||||
if n < 1:
|
||||
badRequest(conn, "invalid request")
|
||||
return
|
||||
inc(off, n)
|
||||
|
||||
# method
|
||||
off.inc parseUntil(text, token, SP, off)
|
||||
result[1].method = Symbol(move token)
|
||||
advanceSp()
|
||||
|
||||
# target
|
||||
off.inc parseUntil(text, token, SP, off)
|
||||
advanceSp()
|
||||
|
||||
block:
|
||||
var version: string
|
||||
off.inc parseUntil(text, version, SP, off)
|
||||
advanceSp()
|
||||
if version != SupportedVersion:
|
||||
badRequest(conn, "version not supported")
|
||||
return
|
||||
|
||||
result[1].query = extractQuery(token)
|
||||
|
||||
result[1].path = split(token, '/')
|
||||
for p in result[1].path.mitems:
|
||||
# normalize the path
|
||||
for i, c in p:
|
||||
if c in {'A'..'Z'}:
|
||||
p[i] = char c.ord + 0x20
|
||||
|
||||
template advanceLine =
|
||||
inc off, skipWhile(text, {'\x0d'}, off)
|
||||
if text.high < off or text[off] != '\x0a':
|
||||
badRequest(conn, "invalid request")
|
||||
return
|
||||
inc off, 1
|
||||
|
||||
advanceLine()
|
||||
while off < text.len:
|
||||
off.inc parseUntil(text, token, {'\x0d', '\x0a'}, off)
|
||||
if token == "": break
|
||||
advanceLine()
|
||||
var
|
||||
(key, val) = httpcore.parseHeader(token)
|
||||
k = key.toLowerAscii.Symbol
|
||||
list = result[1].headers.getOrDefault(k)
|
||||
for v in val.mitems:
|
||||
list.add v.move.toLowerAscii
|
||||
result[1].headers[k] = list
|
||||
|
||||
result[0] = off
|
||||
|
||||
proc connectionHandler(conn: Connection) =
|
||||
# linkActor("http-conn") do (turn: var Turn):
|
||||
block:
|
||||
# let facet = turn.facet
|
||||
var seqNum: BiggestInt
|
||||
conn.onClosed do ():
|
||||
# stopActor(facet)
|
||||
echo "connection closing"
|
||||
close(conn)
|
||||
conn.onReceived do (data: seq[byte]; ctx: MessageContext):
|
||||
echo "connection received ", data.len, " bytes"
|
||||
var (n, req) = parseRequest(conn, cast[string](data))
|
||||
if n < 1:
|
||||
close(conn)
|
||||
else:
|
||||
inc seqNum
|
||||
req.sequenceNumber = seqNum
|
||||
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)
|
||||
# TODO: deal with transfer-encoding
|
||||
|
||||
conn.receive()
|
||||
|
||||
|
||||
proc listenHttp: Listener =
|
||||
var lp = newLocalEndpoint()
|
||||
lp.with Port 80
|
||||
result = listen newPreconnection(local=[lp])
|
||||
|
||||
proc main =
|
||||
var listener = listenHttp()
|
||||
listener.onConnectionReceived(connectionHandler)
|
||||
|
||||
ioqueue.run()
|
||||
|
||||
main()
|
Loading…
Reference in New Issue