mirror of
https://github.com/status-im/nim-chronos.git
synced 2025-02-20 23:18:22 +00:00
Fix multipart end of message handling.
Add apps.nim. Change copyrights dates. Add httpserver tests to test suite.
This commit is contained in:
parent
789a5f8252
commit
1a3e9162a4
10
chronos/apps.nim
Normal file
10
chronos/apps.nim
Normal 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
|
@ -1,6 +1,6 @@
|
||||
#
|
||||
# Chronos HTTP/S common types
|
||||
# (c) Copyright 2019-Present
|
||||
# (c) Copyright 2021-Present
|
||||
# Status Research & Development GmbH
|
||||
#
|
||||
# Licensed under either of
|
||||
|
@ -1,6 +1,6 @@
|
||||
#
|
||||
# Chronos HTTP/S server implementation
|
||||
# (c) Copyright 2019-Present
|
||||
# (c) Copyright 2021-Present
|
||||
# Status Research & Development GmbH
|
||||
#
|
||||
# Licensed under either of
|
||||
@ -14,7 +14,6 @@ import httptable, httpcommon, multipart
|
||||
export httptable, httpcommon, multipart
|
||||
|
||||
when defined(useChroniclesLogging):
|
||||
echo "Importing chronicles"
|
||||
import chronicles
|
||||
|
||||
type
|
||||
@ -67,8 +66,8 @@ type
|
||||
HttpServerRef* = ref HttpServer
|
||||
|
||||
HttpRequest* = object of RootObj
|
||||
headersTable: HttpTable
|
||||
queryTable: HttpTable
|
||||
headers*: HttpTable
|
||||
query*: HttpTable
|
||||
postTable: Option[HttpTable]
|
||||
rawPath*: string
|
||||
rawQuery*: string
|
||||
@ -222,14 +221,14 @@ proc prepareRequest(conn: HttpConnectionRef,
|
||||
uri.path = "*"
|
||||
uri
|
||||
|
||||
request.queryTable =
|
||||
request.query =
|
||||
block:
|
||||
var table = HttpTable.init()
|
||||
for key, value in queryParams(request.uri.query):
|
||||
table.add(key, value)
|
||||
table
|
||||
|
||||
request.headersTable =
|
||||
request.headers =
|
||||
block:
|
||||
var table = HttpTable.init()
|
||||
# Retrieve headers and values
|
||||
@ -248,8 +247,7 @@ proc prepareRequest(conn: HttpConnectionRef,
|
||||
# Preprocessing "Content-Encoding" header.
|
||||
request.contentEncoding =
|
||||
block:
|
||||
let res = getContentEncoding(
|
||||
request.headersTable.getList("content-encoding"))
|
||||
let res = getContentEncoding(request.headers.getList("content-encoding"))
|
||||
if res.isErr():
|
||||
return err(Http400)
|
||||
else:
|
||||
@ -259,7 +257,7 @@ proc prepareRequest(conn: HttpConnectionRef,
|
||||
request.transferEncoding =
|
||||
block:
|
||||
let res = getTransferEncoding(
|
||||
request.headersTable.getList("transfer-encoding"))
|
||||
request.headers.getList("transfer-encoding"))
|
||||
if res.isErr():
|
||||
return err(Http400)
|
||||
else:
|
||||
@ -267,8 +265,8 @@ proc prepareRequest(conn: HttpConnectionRef,
|
||||
|
||||
# Almost all HTTP requests could have body (except TRACE), we perform some
|
||||
# steps to reveal information about body.
|
||||
if "content-length" in request.headersTable:
|
||||
let length = request.headersTable.getInt("content-length")
|
||||
if "content-length" in request.headers:
|
||||
let length = request.headers.getInt("content-length")
|
||||
if length > 0:
|
||||
if request.meth == MethodTrace:
|
||||
return err(Http400)
|
||||
@ -290,16 +288,16 @@ proc prepareRequest(conn: HttpConnectionRef,
|
||||
UrlEncodedType = "application/x-www-form-urlencoded"
|
||||
MultipartType = "multipart/form-data"
|
||||
|
||||
if "content-type" in request.headersTable:
|
||||
let contentType = request.headersTable.getString("content-type")
|
||||
if "content-type" in request.headers:
|
||||
let contentType = request.headers.getString("content-type")
|
||||
let tmp = strip(contentType).toLowerAscii()
|
||||
if tmp.startsWith(UrlEncodedType):
|
||||
request.requestFlags.incl(HttpRequestFlags.UrlencodedForm)
|
||||
elif tmp.startsWith(MultipartType):
|
||||
request.requestFlags.incl(HttpRequestFlags.MultipartForm)
|
||||
|
||||
if "expect" in request.headersTable:
|
||||
let expectHeader = request.headersTable.getString("expect")
|
||||
if "expect" in request.headers:
|
||||
let expectHeader = request.headers.getString("expect")
|
||||
if strip(expectHeader).toLowerAscii() == "100-continue":
|
||||
request.requestFlags.incl(HttpRequestFlags.ClientExpect)
|
||||
|
||||
@ -369,9 +367,10 @@ proc sendErrorResponse(conn: HttpConnectionRef, version: HttpVersion,
|
||||
databody = ""): Future[bool] {.async.} =
|
||||
var answer = $version & " " & $code & "\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-Length: " & $len(databody) & "\r\n")
|
||||
if len(databody) > 0:
|
||||
answer.add("Content-Length: " & $len(databody) & "\r\n")
|
||||
if keepAlive:
|
||||
answer.add("Connection: keep-alive\r\n")
|
||||
else:
|
||||
@ -494,18 +493,25 @@ proc processLoop(server: HttpServerRef, transp: StreamTransport) {.async.} =
|
||||
break
|
||||
else:
|
||||
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):
|
||||
case resp.state
|
||||
of HttpResponseState.Empty:
|
||||
# Response was ignored
|
||||
discard await conn.sendErrorResponse(HttpVersion11, Http404, keepConn)
|
||||
of HttpResponseState.Prepared:
|
||||
# Response was prepared but not sent.
|
||||
discard await conn.sendErrorResponse(HttpVersion11, Http409, keepConn)
|
||||
if isNil(resp):
|
||||
# Response was `nil`.
|
||||
discard await conn.sendErrorResponse(HttpVersion11, Http404,
|
||||
false)
|
||||
else:
|
||||
# some data was already sent to the client.
|
||||
discard
|
||||
case resp.state
|
||||
of HttpResponseState.Empty:
|
||||
# Response was ignored
|
||||
discard await conn.sendErrorResponse(HttpVersion11, Http404,
|
||||
keepConn)
|
||||
of HttpResponseState.Prepared:
|
||||
# Response was prepared but not sent.
|
||||
discard await conn.sendErrorResponse(HttpVersion11, Http409,
|
||||
keepConn)
|
||||
else:
|
||||
# some data was already sent to the client.
|
||||
discard
|
||||
else:
|
||||
discard await conn.sendErrorResponse(HttpVersion11, Http503, true)
|
||||
|
||||
@ -607,12 +613,12 @@ proc getMultipartReader*(req: HttpRequestRef): HttpResult[MultiPartReaderRef] =
|
||||
## Create new MultiPartReader interface for specific request.
|
||||
if req.meth in PostMethods:
|
||||
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":
|
||||
err("Content type is not supported")
|
||||
else:
|
||||
let boundary = ? getMultipartBoundary(
|
||||
req.headersTable.getList("content-type")
|
||||
req.headers.getList("content-type")
|
||||
)
|
||||
var stream = ? req.getBodyStream()
|
||||
ok(MultiPartReaderRef.new(stream, boundary))
|
||||
@ -899,7 +905,22 @@ proc finish*(resp: HttpResponseRef) {.async.} =
|
||||
resp.state = HttpResponseState.Failed
|
||||
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 =
|
||||
## 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 =
|
||||
case contentType
|
||||
of "text/text":
|
||||
@ -952,14 +973,14 @@ proc requestInfo*(req: HttpRequestRef, contentType = "text/text"): string =
|
||||
"not available"
|
||||
res.add(kv("request.body", body))
|
||||
|
||||
if not(req.queryTable.isEmpty()):
|
||||
if not(req.query.isEmpty()):
|
||||
res.add(h("Query arguments"))
|
||||
for k, v in req.queryTable.stringItems():
|
||||
for k, v in req.query.stringItems():
|
||||
res.add(kv(k, v))
|
||||
|
||||
if not(req.headersTable.isEmpty()):
|
||||
if not(req.headers.isEmpty()):
|
||||
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))
|
||||
|
||||
if req.meth in PostMethods:
|
||||
|
@ -1,7 +1,7 @@
|
||||
#
|
||||
# Chronos HTTP/S case-insensitive non-unique
|
||||
# key-value memory storage
|
||||
# (c) Copyright 2019-Present
|
||||
# (c) Copyright 2021-Present
|
||||
# Status Research & Development GmbH
|
||||
#
|
||||
# Licensed under either of
|
||||
@ -105,6 +105,20 @@ proc init*(htt: typedesc[HttpTable]): HttpTable =
|
||||
proc new*(htt: typedesc[HttpTableRef]): HttpTableRef =
|
||||
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 =
|
||||
## Returns ``true`` if HttpTable ``ht`` is empty (do not have any values).
|
||||
len(ht.table) == 0
|
||||
|
@ -1,7 +1,7 @@
|
||||
#
|
||||
# Chronos HTTP/S multipart/form
|
||||
# encoding and decoding helper procedures
|
||||
# (c) Copyright 2019-Present
|
||||
# (c) Copyright 2021-Present
|
||||
# Status Research & Development GmbH
|
||||
#
|
||||
# Licensed under either of
|
||||
@ -144,7 +144,7 @@ proc readPart*(mpr: MultiPartReaderRef): Future[MultiPart] {.async.} =
|
||||
doAssert(mpr.kind == MultiPartSource.Stream)
|
||||
if mpr.firstTime:
|
||||
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)
|
||||
mpr.firstTime = false
|
||||
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
|
||||
try:
|
||||
# Read 2 bytes more
|
||||
await mpr.stream.readExactly(addr mpr.buffer[0], 2)
|
||||
if mpr.buffer[0] == byte('-') and mpr.buffer[1] == byte('-'):
|
||||
raise newException(MultiPartEoM,
|
||||
"End of multipart message")
|
||||
# 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,
|
||||
"End of multipart message")
|
||||
else:
|
||||
raise newException(MultiPartIncorrectError,
|
||||
"Incorrect part headers found")
|
||||
if mpr.buffer[0] != 0x0D'u8 or mpr.buffer[1] != 0x0A'u8:
|
||||
raise newException(MultiPartIncorrectError,
|
||||
"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),
|
||||
HeadersMark)
|
||||
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
|
||||
# of multipart message.
|
||||
mpr.offset += 2
|
||||
return err("End of multipart form encountered")
|
||||
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")
|
||||
else:
|
||||
return err("Incorrect multipart last boundary")
|
||||
else:
|
||||
return err("Incomplete multipart form")
|
||||
|
||||
if mpr.buffer[mpr.offset] == 0x0D'u8 and
|
||||
mpr.buffer[mpr.offset + 1] == 0x0A'u8:
|
||||
|
@ -7,4 +7,4 @@
|
||||
# MIT license (LICENSE-MIT)
|
||||
import testmacro, testsync, testsoon, testtime, testfut, testsignal,
|
||||
testaddress, testdatagram, teststream, testserver, testbugs, testnet,
|
||||
testasyncstream, testutils
|
||||
testasyncstream, testutils, testhttpserver
|
||||
|
367
tests/testhttpserver.nim
Normal file
367
tests/testhttpserver.nim
Normal 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
|
Loading…
x
Reference in New Issue
Block a user