nwaku/vendor/nim-presto/presto/serverprivate.nim

198 lines
9.0 KiB
Nim

#
# REST API framework implementation
# (c) Copyright 2021-Present
# Status Research & Development GmbH
#
# Licensed under either of
# Apache License, version 2.0, (LICENSE-APACHEv2)
# MIT license (LICENSE-MIT)
import std/[options, strutils]
import chronos
import chronicles
import stew/results
import route, common, segpath, servercommon
proc getContentBody*(r: HttpRequestRef): Future[Option[ContentBody]] {.async.} =
if r.meth notin PostMethods:
return none[ContentBody]()
else:
var default: seq[byte]
let cres = getContentType(r.headers.getList("content-type"))
if not(cres.isOk()):
raise newException(RestBadRequestError, "Incorrect Content-Type header")
let data =
if r.hasBody():
await r.getBody()
else:
default
let cbody = ContentBody(contentType: cres.get(), data: data)
return some[ContentBody](cbody)
proc originsMatch(requestOrigin, allowedOrigin: string): bool =
if allowedOrigin.startsWith("http://") or allowedOrigin.startsWith("https://"):
requestOrigin == allowedOrigin
elif requestOrigin.startsWith("http://"):
requestOrigin.toOpenArray(7, requestOrigin.len - 1) == allowedOrigin
elif requestOrigin.startsWith("https://"):
requestOrigin.toOpenArray(8, requestOrigin.len - 1) == allowedOrigin
else:
false
proc processRestRequest*[T](server: T,
rf: RequestFence): Future[HttpResponseRef] {.
gcsafe, async.} =
if rf.isOk():
let request = rf.get()
let sres = SegmentedPath.init(request.meth, request.uri.path)
if sres.isOk():
debug "Received request", peer = $request.remoteAddress(),
meth = $request.meth, uri = $request.uri
let rres = server.router.getRoute(sres.get())
if rres.isSome():
let route = rres.get()
let pathParams = route.getParamsTable()
let queryParams = request.query
let optBody =
if RestRouterFlag.Raw notin route.flags:
try:
await request.getContentBody()
except HttpCriticalError as exc:
debug "Unable to obtain request body", uri = $request.uri,
peer = $request.remoteAddress(), meth = $request.meth,
error_msg = $exc.msg
return await request.respond(Http400)
except RestBadRequestError as exc:
debug "Request has incorrect content type", uri = $request.uri,
peer = $request.remoteAddress(), meth = $request.meth,
error_msg = $exc.msg
return await request.respond(Http400)
except CatchableError as exc:
warn "Unexpected exception while getting request body",
uri = $request.uri, peer = $request.remoteAddress(),
meth = $request.meth, error_name = $exc.name,
error_msg = $exc.msg
return await request.respond(Http400)
else:
none[ContentBody]()
debug "Serving API request", peer = $request.remoteAddress(),
meth = $request.meth, uri = $request.uri,
path_params = pathParams, query_params = queryParams,
content_body = optBody
let restRes =
try:
await route.callback(request, pathParams, queryParams, optBody)
except HttpCriticalError as exc:
debug "Critical error occurred while processing a request",
meth = $request.meth, peer = $request.remoteAddress(),
uri = $request.uri, code = exc.code,
path_params = pathParams, query_params = queryParams,
content_body = optBody, error_msg = $exc.msg
return await request.respond(exc.code)
except CatchableError as exc:
warn "Unexpected error occured while processing a request",
meth = $request.meth, peer = $request.remoteAddress(),
uri = $request.uri, path_params = pathParams,
query_params = queryParams, content_body = optBody,
error_msg = $exc.msg, error_name = $exc.name
return await request.respond(Http503)
try:
if not(request.responded()):
case restRes.kind
of RestApiResponseKind.Empty:
debug "Received empty response from handler",
meth = $request.meth, peer = $request.remoteAddress(),
uri = $request.uri
return await request.respond(Http410)
of RestApiResponseKind.Content:
var headers = HttpTable.init([("Content-Type",
restRes.content.contentType)])
if server.router.allowedOrigin.isSome:
let origin = request.headers.getList("Origin")
let everyOriginAllowed = server.router.allowedOrigin.get == "*"
if origin.len == 1:
if everyOriginAllowed:
headers.add("Access-Control-Allow-Origin", "*")
elif originsMatch(origin[0], server.router.allowedOrigin.get):
# The Vary: Origin header to must be set to prevent
# potential cache poisoning attacks:
# https://textslashplain.com/2018/08/02/cors-and-vary/
headers.add("Vary", "Origin")
headers.add("Access-Control-Allow-Origin", origin[0])
elif origin.len > 1:
return await request.respond(Http400,
"Only a single Origin header must be specified")
debug "Received response from handler",
status = restRes.status.toInt(),
meth = $request.meth, peer = $request.remoteAddress(),
uri = $request.uri,
content_type = restRes.content.contentType,
content_size = len(restRes.content.data)
return await request.respond(restRes.status,
restRes.content.data, headers)
of RestApiResponseKind.Error:
let error = restRes.errobj
debug "Received error response from handler",
status = restRes.status.toInt(),
meth = $request.meth, peer = $request.remoteAddress(),
uri = $request.uri, error
let headers = HttpTable.init([("Content-Type",
error.contentType)])
return await request.respond(error.status, error.message,
headers)
of RestApiResponseKind.Redirect:
debug "Received redirection from handler",
status = restRes.status.toInt(),
meth = $request.meth, peer = $request.remoteAddress(),
uri = $request.uri, location = restRes.location
let location =
block:
var uri = parseUri(restRes.location)
if restRes.preserveQuery:
if len(uri.query) == 0:
uri.query = request.uri.query
else:
uri.query = uri.query & "&" & request.uri.query
$uri
return await request.redirect(restRes.status, location)
else:
debug "Response was sent in request handler", meth = $request.meth,
peer = $request.remoteAddress(), uri = $request.uri,
path_params = pathParams, query_params = queryParams,
content_body = optBody
return request.getResponse()
except HttpCriticalError as exc:
debug "Critical error occured while sending response",
meth = $request.meth, peer = $request.remoteAddress(),
uri = $request.uri, code = exc.code, error_msg = $exc.msg
return dumbResponse()
except CatchableError as exc:
warn "Unexpected error occured while sending response",
meth = $request.meth, peer = $request.remoteAddress(),
uri = $request.uri, error_msg = $exc.msg,
error_name = $exc.name
return dumbResponse()
else:
debug "Request is not part of API", peer = $request.remoteAddress(),
meth = $request.meth, uri = $request.uri
return await request.respond(Http404, "", HttpTable.init())
else:
debug "Received invalid request", peer = $request.remoteAddress(),
meth = $request.meth, uri = $request.uri
return await request.respond(Http400, "", HttpTable.init())
else:
let httpErr = rf.error()
if httpErr.error == HttpServerError.DisconnectError:
debug "Remote peer disconnected", peer = $httpErr.remote,
reason = $httpErr.error
else:
debug "Remote peer dropped connection", peer = $httpErr.remote,
reason = $httpErr.error, code = $httpErr.code
return dumbResponse()