Files
Constantine MolchanovandJacek Sieka d70a2e30e4 Docs: HTTP Server tutorial (#622)
* Docs: HTTP Server tutorial.

* Add docstrings for Fence types.

* Docs: Examples: Reorganize HTTP server examples to Nimble projects instead of standalone files.

* Docs: Add shiftinclude an admonish preprocessors.

* Docs: Add admonish css.

* Add API docs building for apps inside chronos.

* Docs: HTTP Server: Finalize chapter 1.

* Docs: HTTP Server: Finalize intro.

* Docs: summary: Fix path to async_procs.md.

* Docs: HTTP Server: Finalize chapter 2.

* Docs: HTTP Server: Finalize chapter 3.

* CI: doc: Install mdbook-shiftinclude.

* Docs: HTTP Server: Finalize chapter 4.

* Docs: HTTP Server: Finalize chapter 5.

* CI: Update required mdbook-open-on-gh version.

* Docs: HTTP Server: Fix incorrect info about HttpProcessCallback2 in Chapter 4.

* Docs: http server: chapter 3,4: Replace mutating var with in-place try-except.

* Docs: http server: chapter 3,4: Remove explicit returns.

* Docs: http server: Use valueOr instead of explicit isErr checks.

* Docs: http server: Chapter 4: Add more info on middlewares.

* Docs: http server: Add info about HTTP protocol.

* Update docs/examples/http_server/chapter2/src/dashboard.nim

Co-authored-by: Jacek Sieka <jacek@status.im>

* Docs: HTTP Server: Remove redundant `return`s.

* Add examples tech files to ignore.

* Docs: Remove redundant css ref.

* Docs: HTTP Server: Replace global threadvar with a TableRef passed to a closure.

* Move examples out of docs.

Fix #662.

* Add example deps files to ignore.

---------

Co-authored-by: Jacek Sieka <jacek@status.im>
2026-06-23 15:15:56 +04:00

127 lines
3.9 KiB
Nim

import chronos/apps/http/httpserver
{.push raises: [].}
proc firstMiddlewareHandler(
middleware: HttpServerMiddlewareRef,
reqfence: RequestFence,
nextHandler: HttpProcessCallback2,
): Future[HttpResponseRef] {.async: (raises: [CancelledError]).} =
if reqfence.isErr():
# Ignore request errors
return await nextHandler(reqfence)
let request = reqfence.get()
var headers = request.headers
if request.uri.path.startsWith("/path/to/hidden/resources"):
headers.add("X-Filter", "drop")
elif request.uri.path.startsWith("/path/to/blocked/resources"):
headers.add("X-Filter", "block")
else:
headers.add("X-Filter", "pass")
# Updating request by adding new HTTP header `X-Filter`.
let res = request.updateRequest(headers)
if res.isErr():
# We use default error handler in case of error which will respond with
# proper HTTP status code error.
return defaultResponse(res.error)
# Calling next handler.
await nextHandler(reqfence)
proc secondMiddlewareHandler(
middleware: HttpServerMiddlewareRef,
reqfence: RequestFence,
nextHandler: HttpProcessCallback2,
): Future[HttpResponseRef] {.async: (raises: [CancelledError]).} =
if reqfence.isErr():
# Ignore request errors
return await nextHandler(reqfence)
let
request = reqfence.get()
filtered = request.headers.getString("X-Filter", "pass")
if filtered == "drop":
# Force HTTP server to drop connection with remote peer.
dropResponse()
elif filtered == "block":
# Force HTTP server to respond with HTTP `404 Not Found` error code.
codeResponse(Http404)
else:
# Calling next handler.
await nextHandler(reqfence)
proc thirdMiddlewareHandler(
middleware: HttpServerMiddlewareRef,
reqfence: RequestFence,
nextHandler: HttpProcessCallback2,
): Future[HttpResponseRef] {.async: (raises: [CancelledError]).} =
if reqfence.isErr():
# Ignore request errors
return await nextHandler(reqfence)
let request = reqfence.get()
echo "QUERY = [", request.rawPath, "]"
echo request.headers
try:
if request.uri.path == "/path/to/plugin/resources/page1":
await request.respond(Http200, "PLUGIN PAGE1")
elif request.uri.path == "/path/to/plugin/resources/page2":
await request.respond(Http200, "PLUGIN PAGE2")
else:
# Calling next handler.
await nextHandler(reqfence)
except HttpWriteError as exc:
# We use default error handler if we unable to send response.
defaultResponse(exc)
proc handler(
reqfence: RequestFence
): Future[HttpResponseRef] {.async: (raises: [CancelledError]).} =
if reqfence.isErr():
return defaultResponse()
let request = reqfence.get()
try:
if request.uri.path == "/path/to/original/page1":
await request.respond(Http200, "ORIGINAL PAGE1")
elif request.uri.path == "/path/to/original/page2":
await request.respond(Http200, "ORIGINAL PAGE2")
else:
# Force HTTP server to respond with `404 Not Found` status code.
codeResponse(Http404)
except HttpWriteError as exc:
defaultResponse(exc)
proc middlewareExample() {.async: (raises: []).} =
let
middlewares = [
HttpServerMiddlewareRef(handler: firstMiddlewareHandler),
HttpServerMiddlewareRef(handler: secondMiddlewareHandler),
HttpServerMiddlewareRef(handler: thirdMiddlewareHandler),
]
socketFlags = {ServerFlags.TcpNoDelay, ServerFlags.ReuseAddr}
boundAddress = if isAvailable(AddressFamily.IPv6): AnyAddress6 else: AnyAddress
res = HttpServerRef.new(
boundAddress, handler, socketFlags = socketFlags, middlewares = middlewares
)
doAssert(res.isOk(), "Unable to start HTTP server")
let server = res.get()
server.start()
let address = server.instance.localAddress()
echo "HTTP server running on ", address
try:
await server.join()
except CancelledError:
discard
finally:
await server.stop()
await server.closeWait()
when isMainModule:
waitFor middlewareExample()