2021-06-27 06:35:36 +00:00
|
|
|
## nim-websock
|
2021-06-20 04:27:50 +00:00
|
|
|
## Copyright (c) 2021 Status Research & Development GmbH
|
|
|
|
## Licensed under either of
|
|
|
|
## * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
|
|
|
## * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
|
|
|
## at your option.
|
|
|
|
## This file may not be copied, modified, or distributed except according to
|
|
|
|
## those terms.
|
2021-06-01 02:39:14 +00:00
|
|
|
|
|
|
|
{.push raises: [Defect].}
|
|
|
|
|
|
|
|
import std/uri
|
|
|
|
import pkg/[
|
|
|
|
chronos,
|
|
|
|
chronicles,
|
|
|
|
httputils]
|
|
|
|
|
2021-06-28 13:05:28 +00:00
|
|
|
when isLogFormatUsed(json):
|
2021-06-25 16:03:37 +00:00
|
|
|
import json_serialization/std/net
|
2021-06-25 09:22:10 +00:00
|
|
|
|
2021-06-01 02:39:14 +00:00
|
|
|
import ./common
|
|
|
|
|
2021-07-14 17:26:46 +00:00
|
|
|
logScope:
|
|
|
|
topics = "websock http-server"
|
|
|
|
|
2021-06-01 02:39:14 +00:00
|
|
|
type
|
|
|
|
HttpAsyncCallback* = proc (request: HttpRequest):
|
|
|
|
Future[void] {.closure, gcsafe, raises: [Defect].}
|
|
|
|
|
|
|
|
HttpServer* = ref object of StreamServer
|
|
|
|
handler*: HttpAsyncCallback
|
2021-07-15 20:17:55 +00:00
|
|
|
case secure*: bool:
|
|
|
|
of true:
|
|
|
|
tlsFlags*: set[TLSFlags]
|
|
|
|
tlsPrivateKey*: TLSPrivateKey
|
|
|
|
tlsCertificate*: TLSCertificate
|
|
|
|
minVersion*: TLSVersion
|
|
|
|
maxVersion*: TLSVersion
|
|
|
|
else:
|
|
|
|
discard
|
|
|
|
|
|
|
|
TlsHttpServer* = HttpServer
|
2021-06-01 02:39:14 +00:00
|
|
|
|
|
|
|
proc validateRequest(
|
|
|
|
stream: AsyncStreamWriter,
|
|
|
|
header: HttpRequestHeader): Future[ReqStatus] {.async.} =
|
|
|
|
## Validate Request
|
|
|
|
##
|
|
|
|
|
|
|
|
if header.meth notin {MethodGet}:
|
2021-06-25 09:22:10 +00:00
|
|
|
trace "GET method is only allowed", address = stream.tsource.remoteAddress()
|
2021-06-01 02:39:14 +00:00
|
|
|
await stream.sendError(Http405, version = header.version)
|
|
|
|
return ReqStatus.Error
|
|
|
|
|
|
|
|
var hlen = header.contentLength()
|
|
|
|
if hlen < 0 or hlen > MaxHttpRequestSize:
|
2021-06-25 09:22:10 +00:00
|
|
|
trace "Invalid header length", address = stream.tsource.remoteAddress()
|
2021-06-01 02:39:14 +00:00
|
|
|
await stream.sendError(Http413, version = header.version)
|
|
|
|
return ReqStatus.Error
|
|
|
|
|
|
|
|
return ReqStatus.Success
|
|
|
|
|
2021-06-14 23:20:28 +00:00
|
|
|
proc parseRequest(
|
2021-06-01 02:39:14 +00:00
|
|
|
server: HttpServer,
|
2021-06-14 23:20:28 +00:00
|
|
|
stream: AsyncStream): Future[HttpRequest] {.async.} =
|
2021-06-01 02:39:14 +00:00
|
|
|
## Process transport data to the HTTP server
|
|
|
|
##
|
|
|
|
|
|
|
|
var buffer = newSeq[byte](MaxHttpHeadersSize)
|
|
|
|
let remoteAddr = stream.reader.tsource.remoteAddress()
|
2021-06-11 20:04:09 +00:00
|
|
|
trace "Received connection", address = $remoteAddr
|
2021-06-01 02:39:14 +00:00
|
|
|
try:
|
|
|
|
let hlenfut = stream.reader.readUntil(
|
|
|
|
addr buffer[0], MaxHttpHeadersSize, sep = HeaderSep)
|
|
|
|
let ores = await withTimeout(hlenfut, HttpHeadersTimeout)
|
|
|
|
if not ores:
|
|
|
|
# Timeout
|
2021-06-11 20:04:09 +00:00
|
|
|
trace "Timeout expired while receiving headers", address = $remoteAddr
|
2021-06-01 02:39:14 +00:00
|
|
|
await stream.writer.sendError(Http408, version = HttpVersion11)
|
2021-07-15 20:17:55 +00:00
|
|
|
raise newException(HttpError, "Didn't read headers in time!")
|
2021-06-01 02:39:14 +00:00
|
|
|
|
|
|
|
let hlen = hlenfut.read()
|
|
|
|
buffer.setLen(hlen)
|
|
|
|
let requestData = buffer.parseRequest()
|
|
|
|
if requestData.failed():
|
|
|
|
# Header could not be parsed
|
2021-06-11 20:04:09 +00:00
|
|
|
trace "Malformed header received", address = $remoteAddr
|
2021-06-01 02:39:14 +00:00
|
|
|
await stream.writer.sendError(Http400, version = HttpVersion11)
|
2021-07-15 20:17:55 +00:00
|
|
|
raise newException(HttpError, "Malformed header received")
|
2021-06-01 02:39:14 +00:00
|
|
|
|
|
|
|
var vres = await stream.writer.validateRequest(requestData)
|
|
|
|
let hdrs =
|
|
|
|
block:
|
|
|
|
var res = HttpTable.init()
|
|
|
|
for key, value in requestData.headers():
|
|
|
|
res.add(key, value)
|
|
|
|
res
|
|
|
|
|
|
|
|
if vres == ReqStatus.ErrorFailure:
|
2021-06-11 20:04:09 +00:00
|
|
|
trace "Remote peer disconnected", address = $remoteAddr
|
2021-07-15 20:17:55 +00:00
|
|
|
raise newException(HttpError, "Remote peer disconnected")
|
2021-06-01 02:39:14 +00:00
|
|
|
|
2021-07-15 20:17:55 +00:00
|
|
|
trace "Received valid HTTP request", address = $remoteAddr
|
2021-06-14 23:20:28 +00:00
|
|
|
return HttpRequest(
|
|
|
|
headers: hdrs,
|
|
|
|
stream: stream,
|
|
|
|
uri: requestData.uri().parseUri())
|
2021-06-01 02:39:14 +00:00
|
|
|
except TransportLimitError:
|
|
|
|
# size of headers exceeds `MaxHttpHeadersSize`
|
2021-06-11 20:04:09 +00:00
|
|
|
trace "maximum size of headers limit reached", address = $remoteAddr
|
2021-06-01 02:39:14 +00:00
|
|
|
await stream.writer.sendError(Http413, version = HttpVersion11)
|
|
|
|
except TransportIncompleteError:
|
|
|
|
# remote peer disconnected
|
2021-06-11 20:04:09 +00:00
|
|
|
trace "Remote peer disconnected", address = $remoteAddr
|
2021-06-01 02:39:14 +00:00
|
|
|
except TransportOsError as exc:
|
2021-06-11 20:04:09 +00:00
|
|
|
trace "Problems with networking", address = $remoteAddr, error = exc.msg
|
2021-06-01 02:39:14 +00:00
|
|
|
|
|
|
|
proc handleConnCb(
|
|
|
|
server: StreamServer,
|
|
|
|
transp: StreamTransport) {.async.} =
|
2021-06-14 23:20:28 +00:00
|
|
|
var stream: AsyncStream
|
|
|
|
try:
|
|
|
|
stream = AsyncStream(
|
|
|
|
reader: newAsyncStreamReader(transp),
|
|
|
|
writer: newAsyncStreamWriter(transp))
|
2021-06-01 02:39:14 +00:00
|
|
|
|
2021-06-14 23:20:28 +00:00
|
|
|
let httpServer = HttpServer(server)
|
|
|
|
let request = await httpServer.parseRequest(stream)
|
2021-06-01 02:39:14 +00:00
|
|
|
|
2021-06-14 23:20:28 +00:00
|
|
|
await httpServer.handler(request)
|
|
|
|
except CatchableError as exc:
|
|
|
|
debug "Exception in HttpHandler", exc = exc.msg
|
|
|
|
finally:
|
|
|
|
await stream.closeWait()
|
2021-06-01 02:39:14 +00:00
|
|
|
|
|
|
|
proc handleTlsConnCb(
|
|
|
|
server: StreamServer,
|
|
|
|
transp: StreamTransport) {.async.} =
|
|
|
|
|
|
|
|
let tlsHttpServer = TlsHttpServer(server)
|
2021-06-14 23:20:28 +00:00
|
|
|
let tlsStream = newTLSServerAsyncStream(
|
2021-06-01 02:39:14 +00:00
|
|
|
newAsyncStreamReader(transp),
|
|
|
|
newAsyncStreamWriter(transp),
|
|
|
|
tlsHttpServer.tlsPrivateKey,
|
|
|
|
tlsHttpServer.tlsCertificate,
|
|
|
|
minVersion = tlsHttpServer.minVersion,
|
|
|
|
maxVersion = tlsHttpServer.maxVersion,
|
|
|
|
flags = tlsHttpServer.tlsFlags)
|
|
|
|
|
2021-06-14 23:20:28 +00:00
|
|
|
var stream: ASyncStream
|
|
|
|
try:
|
|
|
|
stream = AsyncStream(
|
|
|
|
reader: tlsStream.reader,
|
|
|
|
writer: tlsStream.writer)
|
|
|
|
|
|
|
|
let httpServer = HttpServer(server)
|
|
|
|
let request = await httpServer.parseRequest(stream)
|
|
|
|
|
|
|
|
await httpServer.handler(request)
|
|
|
|
except CatchableError as exc:
|
|
|
|
debug "Exception in HttpHandler", exc = exc.msg
|
|
|
|
finally:
|
|
|
|
await stream.closeWait()
|
|
|
|
|
2021-07-15 20:17:55 +00:00
|
|
|
proc accept*(server: HttpServer): Future[HttpRequest]
|
2021-06-14 23:20:28 +00:00
|
|
|
{.async, raises: [Defect, HttpError].} =
|
|
|
|
|
|
|
|
if not isNil(server.handler):
|
|
|
|
raise newException(HttpError,
|
2021-07-15 20:17:55 +00:00
|
|
|
"Callback already registered - cannot mix callback and accepts styles!")
|
2021-06-14 23:20:28 +00:00
|
|
|
|
2021-07-15 20:17:55 +00:00
|
|
|
trace "Awaiting new request"
|
2021-06-14 23:20:28 +00:00
|
|
|
let transp = await StreamServer(server).accept()
|
2021-07-15 20:17:55 +00:00
|
|
|
let stream = if server.secure:
|
2021-06-14 23:20:28 +00:00
|
|
|
let tlsStream = newTLSServerAsyncStream(
|
|
|
|
newAsyncStreamReader(transp),
|
|
|
|
newAsyncStreamWriter(transp),
|
|
|
|
server.tlsPrivateKey,
|
|
|
|
server.tlsCertificate,
|
|
|
|
minVersion = server.minVersion,
|
|
|
|
maxVersion = server.maxVersion,
|
|
|
|
flags = server.tlsFlags)
|
|
|
|
|
2021-07-15 20:17:55 +00:00
|
|
|
AsyncStream(
|
2021-06-14 23:20:28 +00:00
|
|
|
reader: tlsStream.reader,
|
|
|
|
writer: tlsStream.writer)
|
|
|
|
else:
|
2021-07-15 20:17:55 +00:00
|
|
|
AsyncStream(
|
2021-06-14 23:20:28 +00:00
|
|
|
reader: newAsyncStreamReader(transp),
|
|
|
|
writer: newAsyncStreamWriter(transp))
|
|
|
|
|
2021-07-15 20:17:55 +00:00
|
|
|
trace "Got new request", isTls = server.secure
|
2021-06-14 23:20:28 +00:00
|
|
|
return await server.parseRequest(stream)
|
2021-06-01 02:39:14 +00:00
|
|
|
|
|
|
|
proc create*(
|
|
|
|
_: typedesc[HttpServer],
|
|
|
|
address: TransportAddress,
|
|
|
|
handler: HttpAsyncCallback = nil,
|
|
|
|
flags: set[ServerFlags] = {}): HttpServer
|
|
|
|
{.raises: [Defect, CatchableError].} = # TODO: remove CatchableError
|
|
|
|
## Make a new HTTP Server
|
|
|
|
##
|
|
|
|
|
|
|
|
var server = HttpServer(handler: handler)
|
|
|
|
server = HttpServer(
|
|
|
|
createStreamServer(
|
|
|
|
address,
|
|
|
|
handleConnCb,
|
|
|
|
flags,
|
|
|
|
child = StreamServer(server)))
|
|
|
|
|
2021-07-15 20:17:55 +00:00
|
|
|
trace "Created HTTP Server", host = $server.localAddress()
|
2021-06-11 20:04:09 +00:00
|
|
|
|
2021-06-01 02:39:14 +00:00
|
|
|
return server
|
|
|
|
|
|
|
|
proc create*(
|
|
|
|
_: typedesc[HttpServer],
|
|
|
|
host: string,
|
|
|
|
handler: HttpAsyncCallback = nil,
|
|
|
|
flags: set[ServerFlags] = {}): HttpServer
|
|
|
|
{.raises: [Defect, CatchableError].} = # TODO: remove CatchableError
|
|
|
|
## Make a new HTTP Server
|
|
|
|
##
|
|
|
|
|
2021-07-15 20:17:55 +00:00
|
|
|
return HttpServer.create(initTAddress(host), handler, flags)
|
2021-06-01 02:39:14 +00:00
|
|
|
|
|
|
|
proc create*(
|
|
|
|
_: typedesc[TlsHttpServer],
|
|
|
|
address: TransportAddress,
|
|
|
|
tlsPrivateKey: TLSPrivateKey,
|
|
|
|
tlsCertificate: TLSCertificate,
|
|
|
|
handler: HttpAsyncCallback = nil,
|
|
|
|
flags: set[ServerFlags] = {},
|
|
|
|
tlsFlags: set[TLSFlags] = {},
|
|
|
|
tlsMinVersion = TLSVersion.TLS12,
|
|
|
|
tlsMaxVersion = TLSVersion.TLS12): TlsHttpServer
|
|
|
|
{.raises: [Defect, CatchableError].} = # TODO: remove CatchableError
|
|
|
|
|
|
|
|
var server = TlsHttpServer(
|
2021-07-15 20:17:55 +00:00
|
|
|
secure: true,
|
2021-06-01 02:39:14 +00:00
|
|
|
handler: handler,
|
|
|
|
tlsPrivateKey: tlsPrivateKey,
|
|
|
|
tlsCertificate: tlsCertificate,
|
|
|
|
minVersion: tlsMinVersion,
|
|
|
|
maxVersion: tlsMaxVersion)
|
|
|
|
|
|
|
|
server = TlsHttpServer(
|
|
|
|
createStreamServer(
|
|
|
|
address,
|
|
|
|
handleTlsConnCb,
|
|
|
|
flags,
|
|
|
|
child = StreamServer(server)))
|
|
|
|
|
2021-07-15 20:17:55 +00:00
|
|
|
trace "Created TLS HTTP Server", host = $server.localAddress()
|
2021-06-11 20:04:09 +00:00
|
|
|
|
2021-06-01 02:39:14 +00:00
|
|
|
return server
|
|
|
|
|
|
|
|
proc create*(
|
|
|
|
_: typedesc[TlsHttpServer],
|
|
|
|
host: string,
|
|
|
|
tlsPrivateKey: TLSPrivateKey,
|
|
|
|
tlsCertificate: TLSCertificate,
|
|
|
|
handler: HttpAsyncCallback = nil,
|
|
|
|
flags: set[ServerFlags] = {},
|
|
|
|
tlsFlags: set[TLSFlags] = {},
|
|
|
|
tlsMinVersion = TLSVersion.TLS12,
|
|
|
|
tlsMaxVersion = TLSVersion.TLS12): TlsHttpServer
|
|
|
|
{.raises: [Defect, CatchableError].} = # TODO: remove CatchableError
|
|
|
|
TlsHttpServer.create(
|
2021-07-15 20:17:55 +00:00
|
|
|
address = initTAddress(host),
|
2021-06-01 02:39:14 +00:00
|
|
|
handler = handler,
|
|
|
|
tlsPrivateKey = tlsPrivateKey,
|
|
|
|
tlsCertificate = tlsCertificate,
|
|
|
|
flags = flags,
|
|
|
|
tlsFlags = tlsFlags)
|