From 74b0f85fc7f0e99a9fff44e503e9c2023911ae7a Mon Sep 17 00:00:00 2001 From: cheatfate Date: Sat, 30 Jan 2021 20:18:06 +0200 Subject: [PATCH] Add async post() procedure. --- chronos/apps/http/httpcommon.nim | 18 +------- chronos/apps/http/httpserver.nim | 77 +++++++++++++++++++++++++++++--- 2 files changed, 73 insertions(+), 22 deletions(-) diff --git a/chronos/apps/http/httpcommon.nim b/chronos/apps/http/httpcommon.nim index e123cce..f97a850 100644 --- a/chronos/apps/http/httpcommon.nim +++ b/chronos/apps/http/httpcommon.nim @@ -13,6 +13,7 @@ const useChroniclesLogging* {.booldefine.} = false HeadersMark* = @[byte(0x0D), byte(0x0A), byte(0x0D), byte(0x0A)] + PostMethods* = {MethodPost, MethodPatch, MethodPut, MethodDelete} type HttpResult*[T] = Result[T, string] @@ -109,20 +110,3 @@ func getContentType*(ch: openarray[string]): HttpResult[string] = else: let mparts = ch[0].split(";") ok(strip(mparts[0]).toLowerAscii()) - -func getMultipartBoundary*(contentType: string): HttpResult[string] = - ## Process ``multipart/form-data`` ``Content-Type`` header and return - ## multipart boundary. - let mparts = contentType.split(";") - if strip(mparts[0]).toLowerAscii() != "multipart/form-data": - return err("Content-Type is not multipart") - if len(mparts) < 2: - return err("Content-Type missing boundary value") - let stripped = strip(mparts[1]) - if not(stripped.toLowerAscii().startsWith("boundary")): - return err("Incorrect Content-Type boundary format") - let bparts = stripped.split("=") - if len(bparts) < 2: - err("Missing Content-Type boundary") - else: - ok(strip(bparts[1])) diff --git a/chronos/apps/http/httpserver.nim b/chronos/apps/http/httpserver.nim index 395950a..eebef9c 100644 --- a/chronos/apps/http/httpserver.nim +++ b/chronos/apps/http/httpserver.nim @@ -10,8 +10,8 @@ import std/[tables, options, uri, strutils] import stew/results, httputils import ../../asyncloop, ../../asyncsync import ../../streams/[asyncstream, boundstream, chunkstream] -import httptable, httpcommon -export httpcommon +import httptable, httpcommon, multipart +export httpcommon, multipart when defined(useChroniclesLogging): echo "Importing chronicles" @@ -58,10 +58,10 @@ type HttpServerState* = enum ServerRunning, ServerStopped, ServerClosed - HttpRequest* = object + HttpRequest* = ref object of RootRef headersTable: HttpTable queryTable: HttpTable - postTable: HttpTable + postTable: Option[HttpTable] rawPath*: string rawQuery*: string uri*: Uri @@ -398,6 +398,8 @@ proc processLoop(server: HttpServer, transp: StreamTransport) {.async.} = except CancelledError: breakLoop = true except CatchableError as exc: + resp = DropConnection + echo "Exception received from processor callback ", exc.name lastError = exc if breakLoop: @@ -424,15 +426,20 @@ proc processLoop(server: HttpServer, transp: StreamTransport) {.async.} = break else: if isNil(lastError): + echo "lastError = nil" + echo "mainWriter.bytesCount = ", arg.get().mainWriter.bytesCount + if arg.get().mainWriter.bytesCount == 0'u64: + echo "Sending 404 keepConn = ", keepConn # Processor callback finished without an error, but response was not # sent to client, so we going to send HTTP404 error. discard await conn.sendErrorResponse(HttpVersion11, Http404, keepConn) + echo "bytesCount = ", arg.get().mainWriter.bytesCount else: if arg.get().mainWriter.bytesCount == 0'u64: # Processor callback finished with an error, but response was not # sent to client, so we going to send HTTP503 error. - discard await conn.sendErrorResponse(HttpVersion11, Http503, keepConn) + discard await conn.sendErrorResponse(HttpVersion11, Http503, true) if not(keepConn): break @@ -525,9 +532,69 @@ proc join*(server: HttpServer): Future[void] = retFuture +proc post*(req: HttpRequest): Future[HttpTable] {.async.} = + ## Return POST parameters + if req.postTable.isSome(): + return req.postTable.get() + else: + if req.meth notin PostMethods: + return HttpTable.init() + + if UrlencodedForm in req.requestFlags: + var table = HttpTable.init() + var body = await req.getBody() + ## TODO (cheatfate) double copy here. + var strbody = newString(len(body)) + if len(body) > 0: + copyMem(addr strbody[0], addr body[0], len(body)) + for key, value in queryParams(strbody): + table.add(key, value) + req.postTable = some(table) + return table + elif MultipartForm in req.requestFlags: + let cres = getContentType(req.headersTable.getList("content-type")) + if cres.isErr(): + raise newHttpCriticalError(cres.error) + let bres = getMultipartBoundary(req.headersTable.getList("content-type")) + if bres.isErr(): + raise newHttpCriticalError(bres.error) + var reader = req.getBodyStream() + if reader.isErr(): + raise newHttpCriticalError(reader.error) + var mpreader = MultiPartReaderRef.new(reader.get(), bres.get()) + var table = HttpTable.init() + var runLoop = true + while runLoop: + try: + let res = await mpreader.readPart() + var value = await res.getBody() + ## TODO (cheatfate) double copy here. + var strvalue = newString(len(value)) + if len(value) > 0: + copyMem(addr strvalue[0], addr value[0], len(value)) + table.add(res.name, strvalue) + except MultiPartEoM: + runLoop = false + req.postTable = some(table) + return table + else: + if BoundBody in req.requestFlags: + if req.contentLength != 0: + raise newHttpCriticalError("Unsupported request body") + return HttpTable.init() + elif UnboundBody in req.requestFlags: + raise newHttpCriticalError("Unsupported request body") + when isMainModule: proc processCallback(req: RequestFence[HttpRequest]): Future[HttpStatus] {. async.} = + if req.isOk(): + let request = req.get() + echo "Got ", request.meth, " request" + let post = await request.post() + echo "post = ", post + else: + echo "Got FAILURE", req.error() echo "process callback" let res = HttpServer.new(initTAddress("127.0.0.1:30080"), processCallback,