Fix multipart end of message handling.

Add apps.nim.
Change copyrights dates.
Add httpserver tests to test suite.
This commit is contained in:
cheatfate 2021-02-03 00:33:14 +02:00 committed by zah
parent 789a5f8252
commit 1a3e9162a4
7 changed files with 471 additions and 41 deletions

10
chronos/apps.nim Normal file
View File

@ -0,0 +1,10 @@
#
# Chronos HTTP/S common types
# (c) Copyright 2021-Present
# Status Research & Development GmbH
#
# Licensed under either of
# Apache License, version 2.0, (LICENSE-APACHEv2)
# MIT license (LICENSE-MIT)
import apps/http/httpserver
export httpserver

View File

@ -1,6 +1,6 @@
# #
# Chronos HTTP/S common types # Chronos HTTP/S common types
# (c) Copyright 2019-Present # (c) Copyright 2021-Present
# Status Research & Development GmbH # Status Research & Development GmbH
# #
# Licensed under either of # Licensed under either of

View File

@ -1,6 +1,6 @@
# #
# Chronos HTTP/S server implementation # Chronos HTTP/S server implementation
# (c) Copyright 2019-Present # (c) Copyright 2021-Present
# Status Research & Development GmbH # Status Research & Development GmbH
# #
# Licensed under either of # Licensed under either of
@ -14,7 +14,6 @@ import httptable, httpcommon, multipart
export httptable, httpcommon, multipart export httptable, httpcommon, multipart
when defined(useChroniclesLogging): when defined(useChroniclesLogging):
echo "Importing chronicles"
import chronicles import chronicles
type type
@ -67,8 +66,8 @@ type
HttpServerRef* = ref HttpServer HttpServerRef* = ref HttpServer
HttpRequest* = object of RootObj HttpRequest* = object of RootObj
headersTable: HttpTable headers*: HttpTable
queryTable: HttpTable query*: HttpTable
postTable: Option[HttpTable] postTable: Option[HttpTable]
rawPath*: string rawPath*: string
rawQuery*: string rawQuery*: string
@ -222,14 +221,14 @@ proc prepareRequest(conn: HttpConnectionRef,
uri.path = "*" uri.path = "*"
uri uri
request.queryTable = request.query =
block: block:
var table = HttpTable.init() var table = HttpTable.init()
for key, value in queryParams(request.uri.query): for key, value in queryParams(request.uri.query):
table.add(key, value) table.add(key, value)
table table
request.headersTable = request.headers =
block: block:
var table = HttpTable.init() var table = HttpTable.init()
# Retrieve headers and values # Retrieve headers and values
@ -248,8 +247,7 @@ proc prepareRequest(conn: HttpConnectionRef,
# Preprocessing "Content-Encoding" header. # Preprocessing "Content-Encoding" header.
request.contentEncoding = request.contentEncoding =
block: block:
let res = getContentEncoding( let res = getContentEncoding(request.headers.getList("content-encoding"))
request.headersTable.getList("content-encoding"))
if res.isErr(): if res.isErr():
return err(Http400) return err(Http400)
else: else:
@ -259,7 +257,7 @@ proc prepareRequest(conn: HttpConnectionRef,
request.transferEncoding = request.transferEncoding =
block: block:
let res = getTransferEncoding( let res = getTransferEncoding(
request.headersTable.getList("transfer-encoding")) request.headers.getList("transfer-encoding"))
if res.isErr(): if res.isErr():
return err(Http400) return err(Http400)
else: else:
@ -267,8 +265,8 @@ proc prepareRequest(conn: HttpConnectionRef,
# Almost all HTTP requests could have body (except TRACE), we perform some # Almost all HTTP requests could have body (except TRACE), we perform some
# steps to reveal information about body. # steps to reveal information about body.
if "content-length" in request.headersTable: if "content-length" in request.headers:
let length = request.headersTable.getInt("content-length") let length = request.headers.getInt("content-length")
if length > 0: if length > 0:
if request.meth == MethodTrace: if request.meth == MethodTrace:
return err(Http400) return err(Http400)
@ -290,16 +288,16 @@ proc prepareRequest(conn: HttpConnectionRef,
UrlEncodedType = "application/x-www-form-urlencoded" UrlEncodedType = "application/x-www-form-urlencoded"
MultipartType = "multipart/form-data" MultipartType = "multipart/form-data"
if "content-type" in request.headersTable: if "content-type" in request.headers:
let contentType = request.headersTable.getString("content-type") let contentType = request.headers.getString("content-type")
let tmp = strip(contentType).toLowerAscii() let tmp = strip(contentType).toLowerAscii()
if tmp.startsWith(UrlEncodedType): if tmp.startsWith(UrlEncodedType):
request.requestFlags.incl(HttpRequestFlags.UrlencodedForm) request.requestFlags.incl(HttpRequestFlags.UrlencodedForm)
elif tmp.startsWith(MultipartType): elif tmp.startsWith(MultipartType):
request.requestFlags.incl(HttpRequestFlags.MultipartForm) request.requestFlags.incl(HttpRequestFlags.MultipartForm)
if "expect" in request.headersTable: if "expect" in request.headers:
let expectHeader = request.headersTable.getString("expect") let expectHeader = request.headers.getString("expect")
if strip(expectHeader).toLowerAscii() == "100-continue": if strip(expectHeader).toLowerAscii() == "100-continue":
request.requestFlags.incl(HttpRequestFlags.ClientExpect) request.requestFlags.incl(HttpRequestFlags.ClientExpect)
@ -369,8 +367,9 @@ proc sendErrorResponse(conn: HttpConnectionRef, version: HttpVersion,
databody = ""): Future[bool] {.async.} = databody = ""): Future[bool] {.async.} =
var answer = $version & " " & $code & "\r\n" var answer = $version & " " & $code & "\r\n"
answer.add("Date: " & httpDate() & "\r\n") answer.add("Date: " & httpDate() & "\r\n")
if len(databody) > 0: if len(datatype) > 0:
answer.add("Content-Type: " & datatype & "\r\n") answer.add("Content-Type: " & datatype & "\r\n")
if len(databody) > 0:
answer.add("Content-Length: " & $len(databody) & "\r\n") answer.add("Content-Length: " & $len(databody) & "\r\n")
if keepAlive: if keepAlive:
answer.add("Connection: keep-alive\r\n") answer.add("Connection: keep-alive\r\n")
@ -494,15 +493,22 @@ proc processLoop(server: HttpServerRef, transp: StreamTransport) {.async.} =
break break
else: else:
let request = arg.get() let request = arg.get()
let keepConn = if request.version == HttpVersion11: true else: false var keepConn = if request.version == HttpVersion11: true else: false
if isNil(lastError): if isNil(lastError):
if isNil(resp):
# Response was `nil`.
discard await conn.sendErrorResponse(HttpVersion11, Http404,
false)
else:
case resp.state case resp.state
of HttpResponseState.Empty: of HttpResponseState.Empty:
# Response was ignored # Response was ignored
discard await conn.sendErrorResponse(HttpVersion11, Http404, keepConn) discard await conn.sendErrorResponse(HttpVersion11, Http404,
keepConn)
of HttpResponseState.Prepared: of HttpResponseState.Prepared:
# Response was prepared but not sent. # Response was prepared but not sent.
discard await conn.sendErrorResponse(HttpVersion11, Http409, keepConn) discard await conn.sendErrorResponse(HttpVersion11, Http409,
keepConn)
else: else:
# some data was already sent to the client. # some data was already sent to the client.
discard discard
@ -607,12 +613,12 @@ proc getMultipartReader*(req: HttpRequestRef): HttpResult[MultiPartReaderRef] =
## Create new MultiPartReader interface for specific request. ## Create new MultiPartReader interface for specific request.
if req.meth in PostMethods: if req.meth in PostMethods:
if MultipartForm in req.requestFlags: if MultipartForm in req.requestFlags:
let ctype = ? getContentType(req.headersTable.getList("content-type")) let ctype = ? getContentType(req.headers.getList("content-type"))
if ctype != "multipart/form-data": if ctype != "multipart/form-data":
err("Content type is not supported") err("Content type is not supported")
else: else:
let boundary = ? getMultipartBoundary( let boundary = ? getMultipartBoundary(
req.headersTable.getList("content-type") req.headers.getList("content-type")
) )
var stream = ? req.getBodyStream() var stream = ? req.getBodyStream()
ok(MultiPartReaderRef.new(stream, boundary)) ok(MultiPartReaderRef.new(stream, boundary))
@ -899,7 +905,22 @@ proc finish*(resp: HttpResponseRef) {.async.} =
resp.state = HttpResponseState.Failed resp.state = HttpResponseState.Failed
raise newHttpCriticalError("Unable to send response") raise newHttpCriticalError("Unable to send response")
proc respond*(req: HttpRequestRef, code: HttpCode, content: string,
headers: HttpTable): Future[HttpResponseRef] {.async.} =
## Responds to the request with the specified ``HttpCode``, headers and
## content.
let response = req.getResponse()
response.status = code
for k, v in headers.stringItems():
response.addHeader(k, v)
await response.sendBody(content)
return response
proc requestInfo*(req: HttpRequestRef, contentType = "text/text"): string = proc requestInfo*(req: HttpRequestRef, contentType = "text/text"): string =
## Returns comprehensive information about request for specific content
## type.
##
## Only two content-types are supported: "text/text" and "text/html".
proc h(t: string): string = proc h(t: string): string =
case contentType case contentType
of "text/text": of "text/text":
@ -952,14 +973,14 @@ proc requestInfo*(req: HttpRequestRef, contentType = "text/text"): string =
"not available" "not available"
res.add(kv("request.body", body)) res.add(kv("request.body", body))
if not(req.queryTable.isEmpty()): if not(req.query.isEmpty()):
res.add(h("Query arguments")) res.add(h("Query arguments"))
for k, v in req.queryTable.stringItems(): for k, v in req.query.stringItems():
res.add(kv(k, v)) res.add(kv(k, v))
if not(req.headersTable.isEmpty()): if not(req.headers.isEmpty()):
res.add(h("HTTP headers")) res.add(h("HTTP headers"))
for k, v in req.headersTable.stringItems(true): for k, v in req.headers.stringItems(true):
res.add(kv(k, v)) res.add(kv(k, v))
if req.meth in PostMethods: if req.meth in PostMethods:

View File

@ -1,7 +1,7 @@
# #
# Chronos HTTP/S case-insensitive non-unique # Chronos HTTP/S case-insensitive non-unique
# key-value memory storage # key-value memory storage
# (c) Copyright 2019-Present # (c) Copyright 2021-Present
# Status Research & Development GmbH # Status Research & Development GmbH
# #
# Licensed under either of # Licensed under either of
@ -105,6 +105,20 @@ proc init*(htt: typedesc[HttpTable]): HttpTable =
proc new*(htt: typedesc[HttpTableRef]): HttpTableRef = proc new*(htt: typedesc[HttpTableRef]): HttpTableRef =
HttpTableRef(table: initTable[string, seq[string]]()) HttpTableRef(table: initTable[string, seq[string]]())
proc init*(htt: typedesc[HttpTable],
data: openArray[tuple[key: string, value: string]]): HttpTable =
var res = HttpTable.init()
for item in data:
res.add(item.key, item.value)
res
proc new*(htt: typedesc[HttpTableRef],
data: openArray[tuple[key: string, value: string]]): HttpTableRef =
var res = HttpTableRef.new()
for item in data:
res.add(item.key, item.value)
res
proc isEmpty*(ht: HttpTables): bool = proc isEmpty*(ht: HttpTables): bool =
## Returns ``true`` if HttpTable ``ht`` is empty (do not have any values). ## Returns ``true`` if HttpTable ``ht`` is empty (do not have any values).
len(ht.table) == 0 len(ht.table) == 0

View File

@ -1,7 +1,7 @@
# #
# Chronos HTTP/S multipart/form # Chronos HTTP/S multipart/form
# encoding and decoding helper procedures # encoding and decoding helper procedures
# (c) Copyright 2019-Present # (c) Copyright 2021-Present
# Status Research & Development GmbH # Status Research & Development GmbH
# #
# Licensed under either of # Licensed under either of
@ -144,7 +144,7 @@ proc readPart*(mpr: MultiPartReaderRef): Future[MultiPart] {.async.} =
doAssert(mpr.kind == MultiPartSource.Stream) doAssert(mpr.kind == MultiPartSource.Stream)
if mpr.firstTime: if mpr.firstTime:
try: try:
# Read and verify initial <-><-><boundary><CR><LF> # Read and verify initial <-><-><boundary>
await mpr.stream.readExactly(addr mpr.buffer[0], len(mpr.boundary) - 2) await mpr.stream.readExactly(addr mpr.buffer[0], len(mpr.boundary) - 2)
mpr.firstTime = false mpr.firstTime = false
if not(startsWith(mpr.buffer.toOpenArray(0, len(mpr.boundary) - 3), if not(startsWith(mpr.buffer.toOpenArray(0, len(mpr.boundary) - 3),
@ -160,13 +160,23 @@ proc readPart*(mpr: MultiPartReaderRef): Future[MultiPart] {.async.} =
# Reading part's headers # Reading part's headers
try: try:
# Read 2 bytes more
await mpr.stream.readExactly(addr mpr.buffer[0], 2) await mpr.stream.readExactly(addr mpr.buffer[0], 2)
if mpr.buffer[0] == byte('-') and mpr.buffer[1] == byte('-'): if mpr.buffer[0] == byte('-') and mpr.buffer[1] == byte('-'):
# If two bytes are "--" we are at the end
await mpr.stream.readExactly(addr mpr.buffer[0], 2)
if mpr.buffer[0] == 0x0D'u8 and mpr.buffer[1] == 0x0A'u8:
# If 3rd and 4th bytes are CRLF we are exactly at the end of message.
raise newException(MultiPartEoM, raise newException(MultiPartEoM,
"End of multipart message") "End of multipart message")
else:
raise newException(MultiPartIncorrectError,
"Incorrect part headers found")
if mpr.buffer[0] != 0x0D'u8 or mpr.buffer[1] != 0x0A'u8: if mpr.buffer[0] != 0x0D'u8 or mpr.buffer[1] != 0x0A'u8:
raise newException(MultiPartIncorrectError, raise newException(MultiPartIncorrectError,
"Unexpected boundary suffix") "Unexpected boundary suffix")
# If two bytes are CRLF we are at the part beginning.
# Reading part's headers
let res = await mpr.stream.readUntil(addr mpr.buffer[0], len(mpr.buffer), let res = await mpr.stream.readUntil(addr mpr.buffer[0], len(mpr.buffer),
HeadersMark) HeadersMark)
var headersList = parseHeaders(mpr.buffer.toOpenArray(0, res - 1), false) var headersList = parseHeaders(mpr.buffer.toOpenArray(0, res - 1), false)
@ -314,7 +324,15 @@ proc getPart*(mpr: var MultiPartReader): Result[MultiPart, string] =
# If we have <-><-><boundary><-><-> it means we have found last boundary # If we have <-><-><boundary><-><-> it means we have found last boundary
# of multipart message. # of multipart message.
mpr.offset += 2 mpr.offset += 2
if len(mpr.buffer) <= mpr.offset + 1:
if mpr.buffer[mpr.offset] == 0x0D'u8 and
mpr.buffer[mpr.offset + 1] == 0x0A'u8:
mpr.offset += 2
return err("End of multipart form encountered") return err("End of multipart form encountered")
else:
return err("Incorrect multipart last boundary")
else:
return err("Incomplete multipart form")
if mpr.buffer[mpr.offset] == 0x0D'u8 and if mpr.buffer[mpr.offset] == 0x0D'u8 and
mpr.buffer[mpr.offset + 1] == 0x0A'u8: mpr.buffer[mpr.offset + 1] == 0x0A'u8:

View File

@ -7,4 +7,4 @@
# MIT license (LICENSE-MIT) # MIT license (LICENSE-MIT)
import testmacro, testsync, testsoon, testtime, testfut, testsignal, import testmacro, testsync, testsoon, testtime, testfut, testsignal,
testaddress, testdatagram, teststream, testserver, testbugs, testnet, testaddress, testdatagram, teststream, testserver, testbugs, testnet,
testasyncstream, testutils testasyncstream, testutils, testhttpserver

367
tests/testhttpserver.nim Normal file
View File

@ -0,0 +1,367 @@
# Chronos Test Suite
# (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/[strutils, unittest, algorithm, strutils]
import ../chronos, ../chronos/apps
suite "HTTP server testing suite":
proc httpClient(address: TransportAddress,
data: string): Future[string] {.async.} =
var transp: StreamTransport
try:
transp = await connect(address)
if len(data) > 0:
let wres {.used.} = await transp.write(data)
var rres = await transp.read()
var sres = newString(len(rres))
if len(rres) > 0:
copyMem(addr sres[0], addr rres[0], len(rres))
return sres
except CatchableError:
return "EXCEPTION"
finally:
if not(isNil(transp)):
await closeWait(transp)
test "Request headers timeout test":
proc testTimeout(address: TransportAddress): Future[bool] {.async.} =
var serverRes = false
proc process(r: RequestFence[HttpRequestRef]): Future[HttpResponseRef] {.
async.} =
if r.isOk():
let request = r.get()
return await request.respond(Http200, "TEST_OK", HttpTable.init())
else:
if r.error().error == HTTPServerError.TimeoutError:
serverRes = true
return dumbResponse()
let socketFlags = {ServerFlags.TcpNoDelay, ServerFlags.ReuseAddr}
let res = HttpServerRef.new(address, process, socketFlags = socketFlags,
httpHeadersTimeout = 100.milliseconds)
if res.isErr():
return false
let server = res.get()
server.start()
let data = await httpClient(address, "")
await server.stop()
await server.close()
return serverRes and (data.startsWith("HTTP/1.1 408"))
check waitFor(testTimeout(initTAddress("127.0.0.1:30080"))) == true
test "Empty headers test":
proc testEmpty(address: TransportAddress): Future[bool] {.async.} =
var serverRes = false
proc process(r: RequestFence[HttpRequestRef]): Future[HttpResponseRef] {.
async.} =
if r.isOk():
let request = r.get()
return await request.respond(Http200, "TEST_OK", HttpTable.init())
else:
if r.error().error == HTTPServerError.CriticalError:
serverRes = true
return dumbResponse()
let socketFlags = {ServerFlags.TcpNoDelay, ServerFlags.ReuseAddr}
let res = HttpServerRef.new(address, process, socketFlags = socketFlags)
if res.isErr():
return false
let server = res.get()
server.start()
let data = await httpClient(address, "\r\n\r\n")
await server.stop()
await server.close()
return serverRes and (data.startsWith("HTTP/1.1 400"))
check waitFor(testEmpty(initTAddress("127.0.0.1:30080"))) == true
test "Too big headers test":
proc testTooBig(address: TransportAddress): Future[bool] {.async.} =
var serverRes = false
proc process(r: RequestFence[HttpRequestRef]): Future[HttpResponseRef] {.
async.} =
if r.isOk():
let request = r.get()
return await request.respond(Http200, "TEST_OK", HttpTable.init())
else:
if r.error().error == HTTPServerError.CriticalError:
serverRes = true
return dumbResponse()
let socketFlags = {ServerFlags.TcpNoDelay, ServerFlags.ReuseAddr}
let res = HttpServerRef.new(address, process,
maxHeadersSize = 10,
socketFlags = socketFlags)
if res.isErr():
return false
let server = res.get()
server.start()
let data = await httpClient(address, "GET / HTTP/1.1\r\n\r\n")
await server.stop()
await server.close()
return serverRes and (data.startsWith("HTTP/1.1 413"))
check waitFor(testTooBig(initTAddress("127.0.0.1:30080"))) == true
test "Query arguments test":
proc testQuery(address: TransportAddress): Future[bool] {.async.} =
var serverRes = false
proc process(r: RequestFence[HttpRequestRef]): Future[HttpResponseRef] {.
async.} =
if r.isOk():
let request = r.get()
var kres = newSeq[string]()
for k, v in request.query.stringItems():
kres.add(k & ":" & v)
sort(kres)
serverRes = true
return await request.respond(Http200, "TEST_OK:" & kres.join(":"),
HttpTable.init())
else:
serverRes = false
return dumbResponse()
let socketFlags = {ServerFlags.TcpNoDelay, ServerFlags.ReuseAddr}
let res = HttpServerRef.new(address, process,
socketFlags = socketFlags)
if res.isErr():
return false
let server = res.get()
server.start()
let data1 = await httpClient(address,
"GET /?a=1&a=2&b=3&c=4 HTTP/1.0\r\n\r\n")
let data2 = await httpClient(address,
"GET /?a=%D0%9F&%D0%A4=%D0%91&b=%D0%A6&c=%D0%AE HTTP/1.0\r\n\r\n")
await server.stop()
await server.close()
let r = serverRes and
(data1.find("TEST_OK:a:1:a:2:b:3:c:4") >= 0) and
(data2.find("TEST_OK:a:П:b:Ц:c:Ю:Ф:Б") >= 0)
return r
check waitFor(testQuery(initTAddress("127.0.0.1:30080"))) == true
test "Headers test":
proc testHeaders(address: TransportAddress): Future[bool] {.async.} =
var serverRes = false
proc process(r: RequestFence[HttpRequestRef]): Future[HttpResponseRef] {.
async.} =
if r.isOk():
let request = r.get()
var kres = newSeq[string]()
for k, v in request.headers.stringItems():
kres.add(k & ":" & v)
sort(kres)
serverRes = true
return await request.respond(Http200, "TEST_OK:" & kres.join(":"),
HttpTable.init())
else:
serverRes = false
return dumbResponse()
let socketFlags = {ServerFlags.TcpNoDelay, ServerFlags.ReuseAddr}
let res = HttpServerRef.new(address, process,
socketFlags = socketFlags)
if res.isErr():
return false
let server = res.get()
server.start()
let message =
"GET / HTTP/1.0\r\n" &
"Host: www.google.com\r\n" &
"Content-Type: text/html\r\n" &
"Expect: 100-continue\r\n" &
"Cookie: 1\r\n" &
"Cookie: 2\r\n\r\n"
let expect = "TEST_OK:content-type:text/html:cookie:1:cookie:2" &
":expect:100-continue:host:www.google.com"
let data = await httpClient(address, message)
await server.stop()
await server.close()
return serverRes and (data.find(expect) >= 0)
check waitFor(testHeaders(initTAddress("127.0.0.1:30080"))) == true
test "POST arguments (application/x-www-form-urlencoded) test":
proc testPostUrl(address: TransportAddress): Future[bool] {.async.} =
var serverRes = false
proc process(r: RequestFence[HttpRequestRef]): Future[HttpResponseRef] {.
async.} =
if r.isOk():
var kres = newSeq[string]()
let request = r.get()
if request.meth in PostMethods:
let post = await request.post()
for k, v in post.stringItems():
kres.add(k & ":" & v)
sort(kres)
serverRes = true
return await request.respond(Http200, "TEST_OK:" & kres.join(":"),
HttpTable.init())
else:
serverRes = false
return dumbResponse()
let socketFlags = {ServerFlags.TcpNoDelay, ServerFlags.ReuseAddr}
let res = HttpServerRef.new(address, process,
socketFlags = socketFlags)
if res.isErr():
return false
let server = res.get()
server.start()
let message =
"POST / HTTP/1.0\r\n" &
"Content-Type: application/x-www-form-urlencoded\r\n" &
"Content-Length: 20" &
"Cookie: 2\r\n\r\n" &
"a=a&b=b&c=c&d=%D0%9F"
let data = await httpClient(address, message)
let expect = "TEST_OK:a:a:b:b:c:c:d:П"
await server.stop()
await server.close()
return serverRes and (data.find(expect) >= 0)
check waitFor(testPostUrl(initTAddress("127.0.0.1:30080"))) == true
test "POST arguments (multipart/form-data) test":
proc testPostMultipart(address: TransportAddress): Future[bool] {.async.} =
var serverRes = false
proc process(r: RequestFence[HttpRequestRef]): Future[HttpResponseRef] {.
async.} =
if r.isOk():
var kres = newSeq[string]()
let request = r.get()
if request.meth in PostMethods:
let post = await request.post()
for k, v in post.stringItems():
kres.add(k & ":" & v)
sort(kres)
serverRes = true
return await request.respond(Http200, "TEST_OK:" & kres.join(":"),
HttpTable.init())
else:
serverRes = false
return dumbResponse()
let socketFlags = {ServerFlags.TcpNoDelay, ServerFlags.ReuseAddr}
let res = HttpServerRef.new(address, process,
socketFlags = socketFlags)
if res.isErr():
return false
let server = res.get()
server.start()
let message =
"POST / HTTP/1.0\r\n" &
"Host: 127.0.0.1:30080\r\n" &
"User-Agent: curl/7.55.1\r\n" &
"Accept: */*\r\n" &
"Content-Length: 343\r\n" &
"Content-Type: multipart/form-data; " &
"boundary=------------------------ab5706ba6f80b795\r\n\r\n" &
"--------------------------ab5706ba6f80b795\r\n" &
"Content-Disposition: form-data; name=\"key1\"\r\n\r\n" &
"value1\r\n" &
"--------------------------ab5706ba6f80b795\r\n" &
"Content-Disposition: form-data; name=\"key2\"\r\n\r\n" &
"value2\r\n" &
"--------------------------ab5706ba6f80b795\r\n" &
"Content-Disposition: form-data; name=\"key2\"\r\n\r\n" &
"value4\r\n" &
"--------------------------ab5706ba6f80b795--\r\n"
let data = await httpClient(address, message)
let expect = "TEST_OK:key1:value1:key2:value2:key2:value4"
await server.stop()
await server.close()
return serverRes and (data.find(expect) >= 0)
check waitFor(testPostMultipart(initTAddress("127.0.0.1:30080"))) == true
test "POST arguments (multipart/form-data + chunked encoding) test":
proc testPostMultipart2(address: TransportAddress): Future[bool] {.async.} =
var serverRes = false
proc process(r: RequestFence[HttpRequestRef]): Future[HttpResponseRef] {.
async.} =
if r.isOk():
var kres = newSeq[string]()
let request = r.get()
if request.meth in PostMethods:
let post = await request.post()
for k, v in post.stringItems():
kres.add(k & ":" & v)
sort(kres)
serverRes = true
return await request.respond(Http200, "TEST_OK:" & kres.join(":"),
HttpTable.init())
else:
serverRes = false
return dumbResponse()
let socketFlags = {ServerFlags.TcpNoDelay, ServerFlags.ReuseAddr}
let res = HttpServerRef.new(address, process,
socketFlags = socketFlags)
if res.isErr():
return false
let server = res.get()
server.start()
let message =
"POST / HTTP/1.0\r\n" &
"Host: 127.0.0.1:30080\r\n" &
"Transfer-Encoding: chunked\r\n" &
"Content-Type: multipart/form-data; boundary=---" &
"---------------------f98f0e32c55fa2ae\r\n\r\n" &
"271\r\n" &
"--------------------------f98f0e32c55fa2ae\r\n" &
"Content-Disposition: form-data; name=\"key1\"\r\n\r\n" &
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" &
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\r\n" &
"--------------------------f98f0e32c55fa2ae\r\n" &
"Content-Disposition: form-data; name=\"key2\"\r\n\r\n" &
"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB" &
"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\r\n" &
"--------------------------f98f0e32c55fa2ae\r\n" &
"Content-Disposition: form-data; name=\"key2\"\r\n\r\n" &
"CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC" &
"CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\r\n" &
"--------------------------f98f0e32c55fa2ae--\r\n" &
"\r\n0\r\n\r\n"
let data = await httpClient(address, message)
let expect = "TEST_OK:key1:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" &
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" &
"AAAAA:key2:BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB" &
"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB" &
"BBB:key2:CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC" &
"CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC"
await server.stop()
await server.close()
return serverRes and (data.find(expect) >= 0)
check waitFor(testPostMultipart2(initTAddress("127.0.0.1:30080"))) == true
test "Leaks test":
check:
getTracker("async.stream.reader").isLeaked() == false
getTracker("async.stream.writer").isLeaked() == false
getTracker("stream.server").isLeaked() == false
getTracker("stream.transport").isLeaked() == false