mirror of
https://github.com/status-im/nim-chronos.git
synced 2026-08-27 13:01:12 +00:00
HTTP server middleware implementation. (#483)
* HTTP server middleware implementation and test. * Address review comments. * Address review comments.
This commit is contained in:
+296
-3
@@ -18,9 +18,16 @@ suite "HTTP server testing suite":
|
||||
TooBigTest = enum
|
||||
GetBodyTest, ConsumeBodyTest, PostUrlTest, PostMultipartTest
|
||||
TestHttpResponse = object
|
||||
status: int
|
||||
headers: HttpTable
|
||||
data: string
|
||||
|
||||
FirstMiddlewareRef = ref object of HttpServerMiddlewareRef
|
||||
someInteger: int
|
||||
|
||||
SecondMiddlewareRef = ref object of HttpServerMiddlewareRef
|
||||
someString: string
|
||||
|
||||
proc httpClient(address: TransportAddress,
|
||||
data: string): Future[string] {.async.} =
|
||||
var transp: StreamTransport
|
||||
@@ -50,7 +57,7 @@ suite "HTTP server testing suite":
|
||||
zeroMem(addr buffer[0], len(buffer))
|
||||
await transp.readExactly(addr buffer[0], length)
|
||||
let data = bytesToString(buffer.toOpenArray(0, length - 1))
|
||||
let headers =
|
||||
let (status, headers) =
|
||||
block:
|
||||
let resp = parseResponse(hdata, false)
|
||||
if resp.failed():
|
||||
@@ -58,8 +65,38 @@ suite "HTTP server testing suite":
|
||||
var res = HttpTable.init()
|
||||
for key, value in resp.headers(hdata):
|
||||
res.add(key, value)
|
||||
res
|
||||
return TestHttpResponse(headers: headers, data: data)
|
||||
(resp.code, res)
|
||||
TestHttpResponse(status: status, headers: headers, data: data)
|
||||
|
||||
proc httpClient3(address: TransportAddress,
|
||||
data: string): Future[TestHttpResponse] {.async.} =
|
||||
var
|
||||
transp: StreamTransport
|
||||
buffer = newSeq[byte](4096)
|
||||
sep = @[0x0D'u8, 0x0A'u8, 0x0D'u8, 0x0A'u8]
|
||||
try:
|
||||
transp = await connect(address)
|
||||
if len(data) > 0:
|
||||
let wres = await transp.write(data)
|
||||
if wres != len(data):
|
||||
raise newException(ValueError, "Unable to write full request")
|
||||
let hres = await transp.readUntil(addr buffer[0], len(buffer), sep)
|
||||
var hdata = @buffer
|
||||
hdata.setLen(hres)
|
||||
var rres = bytesToString(await transp.read())
|
||||
let (status, headers) =
|
||||
block:
|
||||
let resp = parseResponse(hdata, false)
|
||||
if resp.failed():
|
||||
raise newException(ValueError, "Unable to decode response headers")
|
||||
var res = HttpTable.init()
|
||||
for key, value in resp.headers(hdata):
|
||||
res.add(key, value)
|
||||
(resp.code, res)
|
||||
TestHttpResponse(status: status, headers: headers, data: rres)
|
||||
finally:
|
||||
if not(isNil(transp)):
|
||||
await closeWait(transp)
|
||||
|
||||
proc testTooBigBodyChunked(operation: TooBigTest): Future[bool] {.async.} =
|
||||
var serverRes = false
|
||||
@@ -1490,5 +1527,261 @@ suite "HTTP server testing suite":
|
||||
await server.stop()
|
||||
await server.closeWait()
|
||||
|
||||
asyncTest "HTTP middleware request filtering test":
|
||||
proc init(t: typedesc[FirstMiddlewareRef],
|
||||
data: int): HttpServerMiddlewareRef =
|
||||
proc shandler(
|
||||
middleware: HttpServerMiddlewareRef,
|
||||
reqfence: RequestFence,
|
||||
nextHandler: HttpProcessCallback2
|
||||
): Future[HttpResponseRef] {.async: (raises: [CancelledError]).} =
|
||||
let mw = FirstMiddlewareRef(middleware)
|
||||
if reqfence.isErr():
|
||||
# Our handler is not supposed to handle request errors, so we
|
||||
# call next handler in sequence which could process errors.
|
||||
return await nextHandler(reqfence)
|
||||
|
||||
let request = reqfence.get()
|
||||
if request.uri.path == "/first":
|
||||
# This is request we are waiting for, so we going to process it.
|
||||
try:
|
||||
await request.respond(Http200, $mw.someInteger)
|
||||
except HttpWriteError as exc:
|
||||
defaultResponse(exc)
|
||||
else:
|
||||
# We know nothing about request's URI, so we pass this request to the
|
||||
# next handler which could process such request.
|
||||
await nextHandler(reqfence)
|
||||
|
||||
HttpServerMiddlewareRef(
|
||||
FirstMiddlewareRef(someInteger: data, handler: shandler))
|
||||
|
||||
proc init(t: typedesc[SecondMiddlewareRef],
|
||||
data: string): HttpServerMiddlewareRef =
|
||||
proc shandler(
|
||||
middleware: HttpServerMiddlewareRef,
|
||||
reqfence: RequestFence,
|
||||
nextHandler: HttpProcessCallback2
|
||||
): Future[HttpResponseRef] {.async: (raises: [CancelledError]).} =
|
||||
let mw = SecondMiddlewareRef(middleware)
|
||||
if reqfence.isErr():
|
||||
# Our handler is not supposed to handle request errors, so we
|
||||
# call next handler in sequence which could process errors.
|
||||
return await nextHandler(reqfence)
|
||||
|
||||
let request = reqfence.get()
|
||||
|
||||
if request.uri.path == "/second":
|
||||
# This is request we are waiting for, so we going to process it.
|
||||
try:
|
||||
await request.respond(Http200, mw.someString)
|
||||
except HttpWriteError as exc:
|
||||
defaultResponse(exc)
|
||||
else:
|
||||
# We know nothing about request's URI, so we pass this request to the
|
||||
# next handler which could process such request.
|
||||
await nextHandler(reqfence)
|
||||
|
||||
HttpServerMiddlewareRef(
|
||||
SecondMiddlewareRef(someString: data, handler: shandler))
|
||||
|
||||
proc process(r: RequestFence): Future[HttpResponseRef] {.
|
||||
async: (raises: [CancelledError]).} =
|
||||
if r.isOk():
|
||||
let request = r.get()
|
||||
if request.uri.path == "/test":
|
||||
try:
|
||||
await request.respond(Http200, "ORIGIN")
|
||||
except HttpWriteError as exc:
|
||||
defaultResponse(exc)
|
||||
else:
|
||||
defaultResponse()
|
||||
else:
|
||||
defaultResponse()
|
||||
|
||||
let
|
||||
middlewares = [FirstMiddlewareRef.init(655370),
|
||||
SecondMiddlewareRef.init("SECOND")]
|
||||
socketFlags = {ServerFlags.TcpNoDelay, ServerFlags.ReuseAddr}
|
||||
res = HttpServerRef.new(initTAddress("127.0.0.1:0"), process,
|
||||
socketFlags = socketFlags,
|
||||
middlewares = middlewares)
|
||||
check res.isOk()
|
||||
|
||||
let server = res.get()
|
||||
server.start()
|
||||
let
|
||||
address = server.instance.localAddress()
|
||||
req1 = "GET /test HTTP/1.1\r\n\r\n"
|
||||
req2 = "GET /first HTTP/1.1\r\n\r\n"
|
||||
req3 = "GET /second HTTP/1.1\r\n\r\n"
|
||||
req4 = "GET /noway HTTP/1.1\r\n\r\n"
|
||||
resp1 = await httpClient3(address, req1)
|
||||
resp2 = await httpClient3(address, req2)
|
||||
resp3 = await httpClient3(address, req3)
|
||||
resp4 = await httpClient3(address, req4)
|
||||
|
||||
check:
|
||||
resp1.status == 200
|
||||
resp1.data == "ORIGIN"
|
||||
resp2.status == 200
|
||||
resp2.data == "655370"
|
||||
resp3.status == 200
|
||||
resp3.data == "SECOND"
|
||||
resp4.status == 404
|
||||
|
||||
await server.stop()
|
||||
await server.closeWait()
|
||||
|
||||
asyncTest "HTTP middleware request modification test":
|
||||
proc init(t: typedesc[FirstMiddlewareRef],
|
||||
data: int): HttpServerMiddlewareRef =
|
||||
proc shandler(
|
||||
middleware: HttpServerMiddlewareRef,
|
||||
reqfence: RequestFence,
|
||||
nextHandler: HttpProcessCallback2
|
||||
): Future[HttpResponseRef] {.async: (raises: [CancelledError]).} =
|
||||
let mw = FirstMiddlewareRef(middleware)
|
||||
if reqfence.isErr():
|
||||
# Our handler is not supposed to handle request errors, so we
|
||||
# call next handler in sequence which could process errors.
|
||||
return await nextHandler(reqfence)
|
||||
|
||||
let
|
||||
request = reqfence.get()
|
||||
modifiedUri = "/modified/" & $mw.someInteger & request.rawPath
|
||||
var modifiedHeaders = request.headers
|
||||
modifiedHeaders.add("X-Modified", "test-value")
|
||||
|
||||
let res = request.updateRequest(modifiedUri, modifiedHeaders)
|
||||
if res.isErr():
|
||||
return defaultResponse(res.error)
|
||||
|
||||
# We sending modified request to the next handler.
|
||||
await nextHandler(reqfence)
|
||||
|
||||
HttpServerMiddlewareRef(
|
||||
FirstMiddlewareRef(someInteger: data, handler: shandler))
|
||||
|
||||
proc process(r: RequestFence): Future[HttpResponseRef] {.
|
||||
async: (raises: [CancelledError]).} =
|
||||
if r.isOk():
|
||||
let request = r.get()
|
||||
try:
|
||||
await request.respond(Http200, request.rawPath & ":" &
|
||||
request.headers.getString("x-modified"))
|
||||
except HttpWriteError as exc:
|
||||
defaultResponse(exc)
|
||||
else:
|
||||
defaultResponse()
|
||||
|
||||
let
|
||||
middlewares = [FirstMiddlewareRef.init(655370)]
|
||||
socketFlags = {ServerFlags.TcpNoDelay, ServerFlags.ReuseAddr}
|
||||
res = HttpServerRef.new(initTAddress("127.0.0.1:0"), process,
|
||||
socketFlags = socketFlags,
|
||||
middlewares = middlewares)
|
||||
check res.isOk()
|
||||
|
||||
let server = res.get()
|
||||
server.start()
|
||||
let
|
||||
address = server.instance.localAddress()
|
||||
req1 = "GET /test HTTP/1.1\r\n\r\n"
|
||||
req2 = "GET /first HTTP/1.1\r\n\r\n"
|
||||
req3 = "GET /second HTTP/1.1\r\n\r\n"
|
||||
req4 = "GET /noway HTTP/1.1\r\n\r\n"
|
||||
resp1 = await httpClient3(address, req1)
|
||||
resp2 = await httpClient3(address, req2)
|
||||
resp3 = await httpClient3(address, req3)
|
||||
resp4 = await httpClient3(address, req4)
|
||||
|
||||
check:
|
||||
resp1.status == 200
|
||||
resp1.data == "/modified/655370/test:test-value"
|
||||
resp2.status == 200
|
||||
resp2.data == "/modified/655370/first:test-value"
|
||||
resp3.status == 200
|
||||
resp3.data == "/modified/655370/second:test-value"
|
||||
resp4.status == 200
|
||||
resp4.data == "/modified/655370/noway:test-value"
|
||||
|
||||
await server.stop()
|
||||
await server.closeWait()
|
||||
|
||||
asyncTest "HTTP middleware request blocking test":
|
||||
proc init(t: typedesc[FirstMiddlewareRef],
|
||||
data: int): HttpServerMiddlewareRef =
|
||||
proc shandler(
|
||||
middleware: HttpServerMiddlewareRef,
|
||||
reqfence: RequestFence,
|
||||
nextHandler: HttpProcessCallback2
|
||||
): Future[HttpResponseRef] {.async: (raises: [CancelledError]).} =
|
||||
if reqfence.isErr():
|
||||
# Our handler is not supposed to handle request errors, so we
|
||||
# call next handler in sequence which could process errors.
|
||||
return await nextHandler(reqfence)
|
||||
|
||||
let request = reqfence.get()
|
||||
if request.uri.path == "/first":
|
||||
# Blocking request by disconnecting remote peer.
|
||||
dropResponse()
|
||||
elif request.uri.path == "/second":
|
||||
# Blocking request by sending HTTP error message with 401 code.
|
||||
codeResponse(Http401)
|
||||
else:
|
||||
# Allow all other requests to be processed by next handler.
|
||||
await nextHandler(reqfence)
|
||||
|
||||
HttpServerMiddlewareRef(
|
||||
FirstMiddlewareRef(someInteger: data, handler: shandler))
|
||||
|
||||
proc process(r: RequestFence): Future[HttpResponseRef] {.
|
||||
async: (raises: [CancelledError]).} =
|
||||
if r.isOk():
|
||||
let request = r.get()
|
||||
try:
|
||||
await request.respond(Http200, "ORIGIN")
|
||||
except HttpWriteError as exc:
|
||||
defaultResponse(exc)
|
||||
else:
|
||||
defaultResponse()
|
||||
|
||||
let
|
||||
middlewares = [FirstMiddlewareRef.init(655370)]
|
||||
socketFlags = {ServerFlags.TcpNoDelay, ServerFlags.ReuseAddr}
|
||||
res = HttpServerRef.new(initTAddress("127.0.0.1:0"), process,
|
||||
socketFlags = socketFlags,
|
||||
middlewares = middlewares)
|
||||
check res.isOk()
|
||||
|
||||
let server = res.get()
|
||||
server.start()
|
||||
let
|
||||
address = server.instance.localAddress()
|
||||
req1 = "GET /test HTTP/1.1\r\n\r\n"
|
||||
req2 = "GET /first HTTP/1.1\r\n\r\n"
|
||||
req3 = "GET /second HTTP/1.1\r\n\r\n"
|
||||
resp1 = await httpClient3(address, req1)
|
||||
resp3 = await httpClient3(address, req3)
|
||||
|
||||
check:
|
||||
resp1.status == 200
|
||||
resp1.data == "ORIGIN"
|
||||
resp3.status == 401
|
||||
|
||||
let checked =
|
||||
try:
|
||||
let res {.used.} = await httpClient3(address, req2)
|
||||
false
|
||||
except TransportIncompleteError:
|
||||
true
|
||||
|
||||
check:
|
||||
checked == true
|
||||
|
||||
await server.stop()
|
||||
await server.closeWait()
|
||||
|
||||
test "Leaks test":
|
||||
checkLeaks()
|
||||
|
||||
Reference in New Issue
Block a user