Add async post() procedure.

This commit is contained in:
cheatfate 2021-01-30 20:18:06 +02:00 committed by zah
parent 53aec163eb
commit 74b0f85fc7
2 changed files with 73 additions and 22 deletions

View File

@ -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]))

View File

@ -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,