mirror of
https://github.com/nim-lang/Nim.git
synced 2026-07-22 08:51:21 +00:00
The asynchttpserver module now uses a HttpHeaders object.
This commit is contained in:
@@ -25,12 +25,22 @@
|
||||
##
|
||||
## waitFor server.serve(Port(8080), cb)
|
||||
|
||||
import strtabs, asyncnet, asyncdispatch, parseutils, uri, strutils
|
||||
import tables, asyncnet, asyncdispatch, parseutils, uri, strutils
|
||||
import httpcore
|
||||
|
||||
export HttpHeaders, httpcore.`[]`, httpcore.`[]=`, httpcore.add, httpcore.`==`,
|
||||
httpcore.HttpCode, httpcore.HttpVersion, httpcore.toString
|
||||
|
||||
# TODO: If it turns out that the decisions that asynchttpserver makes
|
||||
# explicitly, about whether to close the client sockets or upgrade them are
|
||||
# wrong, then add a return value which determines what to do for the callback.
|
||||
# Also, maybe move `client` out of `Request` object and into the args for
|
||||
# the proc.
|
||||
type
|
||||
Request* = object
|
||||
client*: AsyncSocket # TODO: Separate this into a Response object?
|
||||
reqMethod*: string
|
||||
headers*: StringTableRef
|
||||
headers*: HttpHeaders
|
||||
protocol*: tuple[orig: string, major, minor: int]
|
||||
url*: Uri
|
||||
hostname*: string ## The hostname of the client that made the request.
|
||||
@@ -41,83 +51,27 @@ type
|
||||
reuseAddr: bool
|
||||
reusePort: bool
|
||||
|
||||
HttpCode* = enum
|
||||
Http100 = "100 Continue",
|
||||
Http101 = "101 Switching Protocols",
|
||||
Http200 = "200 OK",
|
||||
Http201 = "201 Created",
|
||||
Http202 = "202 Accepted",
|
||||
Http204 = "204 No Content",
|
||||
Http205 = "205 Reset Content",
|
||||
Http206 = "206 Partial Content",
|
||||
Http300 = "300 Multiple Choices",
|
||||
Http301 = "301 Moved Permanently",
|
||||
Http302 = "302 Found",
|
||||
Http303 = "303 See Other",
|
||||
Http304 = "304 Not Modified",
|
||||
Http305 = "305 Use Proxy",
|
||||
Http307 = "307 Temporary Redirect",
|
||||
Http400 = "400 Bad Request",
|
||||
Http401 = "401 Unauthorized",
|
||||
Http403 = "403 Forbidden",
|
||||
Http404 = "404 Not Found",
|
||||
Http405 = "405 Method Not Allowed",
|
||||
Http406 = "406 Not Acceptable",
|
||||
Http407 = "407 Proxy Authentication Required",
|
||||
Http408 = "408 Request Timeout",
|
||||
Http409 = "409 Conflict",
|
||||
Http410 = "410 Gone",
|
||||
Http411 = "411 Length Required",
|
||||
Http412 = "412 Precondition Failed",
|
||||
Http413 = "413 Request Entity Too Large",
|
||||
Http414 = "414 Request-URI Too Long",
|
||||
Http415 = "415 Unsupported Media Type",
|
||||
Http416 = "416 Requested Range Not Satisfiable",
|
||||
Http417 = "417 Expectation Failed",
|
||||
Http418 = "418 I'm a teapot",
|
||||
Http500 = "500 Internal Server Error",
|
||||
Http501 = "501 Not Implemented",
|
||||
Http502 = "502 Bad Gateway",
|
||||
Http503 = "503 Service Unavailable",
|
||||
Http504 = "504 Gateway Timeout",
|
||||
Http505 = "505 HTTP Version Not Supported"
|
||||
|
||||
HttpVersion* = enum
|
||||
HttpVer11,
|
||||
HttpVer10
|
||||
|
||||
{.deprecated: [TRequest: Request, PAsyncHttpServer: AsyncHttpServer,
|
||||
THttpCode: HttpCode, THttpVersion: HttpVersion].}
|
||||
|
||||
proc `==`*(protocol: tuple[orig: string, major, minor: int],
|
||||
ver: HttpVersion): bool =
|
||||
let major =
|
||||
case ver
|
||||
of HttpVer11, HttpVer10: 1
|
||||
let minor =
|
||||
case ver
|
||||
of HttpVer11: 1
|
||||
of HttpVer10: 0
|
||||
result = protocol.major == major and protocol.minor == minor
|
||||
|
||||
proc newAsyncHttpServer*(reuseAddr = true, reusePort = false): AsyncHttpServer =
|
||||
## Creates a new ``AsyncHttpServer`` instance.
|
||||
new result
|
||||
result.reuseAddr = reuseAddr
|
||||
result.reusePort = reusePort
|
||||
|
||||
proc addHeaders(msg: var string, headers: StringTableRef) =
|
||||
proc addHeaders(msg: var string, headers: HttpHeaders) =
|
||||
for k, v in headers:
|
||||
msg.add(k & ": " & v & "\c\L")
|
||||
|
||||
proc sendHeaders*(req: Request, headers: StringTableRef): Future[void] =
|
||||
proc sendHeaders*(req: Request, headers: HttpHeaders): Future[void] =
|
||||
## Sends the specified headers to the requesting client.
|
||||
var msg = ""
|
||||
addHeaders(msg, headers)
|
||||
return req.client.send(msg)
|
||||
|
||||
proc respond*(req: Request, code: HttpCode, content: string,
|
||||
headers: StringTableRef = nil): Future[void] =
|
||||
headers: HttpHeaders = nil): Future[void] =
|
||||
## Responds to the request with the specified ``HttpCode``, headers and
|
||||
## content.
|
||||
##
|
||||
@@ -130,16 +84,6 @@ proc respond*(req: Request, code: HttpCode, content: string,
|
||||
msg.add(content)
|
||||
result = req.client.send(msg)
|
||||
|
||||
proc parseHeader(line: string): tuple[key, value: string] =
|
||||
var i = 0
|
||||
i = line.parseUntil(result.key, ':')
|
||||
inc(i) # skip :
|
||||
if i < len(line):
|
||||
i += line.skipWhiteSpace(i)
|
||||
i += line.parseUntil(result.value, {'\c', '\L'}, i)
|
||||
else:
|
||||
result.value = ""
|
||||
|
||||
proc parseProtocol(protocol: string): tuple[orig: string, major, minor: int] =
|
||||
var i = protocol.skipIgnoreCase("HTTP/")
|
||||
if i != 5:
|
||||
@@ -158,7 +102,7 @@ proc processClient(client: AsyncSocket, address: string,
|
||||
Future[void] {.closure, gcsafe.}) {.async.} =
|
||||
var request: Request
|
||||
request.url = initUri()
|
||||
request.headers = newStringTable(modeCaseInsensitive)
|
||||
request.headers = newHttpHeaders()
|
||||
var lineFut = newFutureVar[string]("asynchttpserver.processClient")
|
||||
lineFut.mget() = newStringOfCap(80)
|
||||
var key, value = ""
|
||||
@@ -167,7 +111,7 @@ proc processClient(client: AsyncSocket, address: string,
|
||||
# GET /path HTTP/1.1
|
||||
# Header: val
|
||||
# \n
|
||||
request.headers.clear(modeCaseInsensitive)
|
||||
request.headers.clear()
|
||||
request.body = ""
|
||||
request.hostname.shallowCopy(address)
|
||||
assert client != nil
|
||||
@@ -214,7 +158,7 @@ proc processClient(client: AsyncSocket, address: string,
|
||||
if request.reqMethod == "post":
|
||||
# Check for Expect header
|
||||
if request.headers.hasKey("Expect"):
|
||||
if request.headers.getOrDefault("Expect").toLower == "100-continue":
|
||||
if "100-continue" in request.headers["Expect"]:
|
||||
await client.sendStatus("100 Continue")
|
||||
else:
|
||||
await client.sendStatus("417 Expectation Failed")
|
||||
@@ -223,7 +167,7 @@ proc processClient(client: AsyncSocket, address: string,
|
||||
# - Check for Content-length header
|
||||
if request.headers.hasKey("Content-Length"):
|
||||
var contentLength = 0
|
||||
if parseInt(request.headers.getOrDefault("Content-Length"),
|
||||
if parseInt(request.headers["Content-Length"],
|
||||
contentLength) == 0:
|
||||
await request.respond(Http400, "Bad Request. Invalid Content-Length.")
|
||||
continue
|
||||
@@ -291,7 +235,7 @@ when not defined(testing) and isMainModule:
|
||||
#echo(req.headers)
|
||||
let headers = {"Date": "Tue, 29 Apr 2014 23:40:08 GMT",
|
||||
"Content-type": "text/plain; charset=utf-8"}
|
||||
await req.respond(Http200, "Hello World", headers.newStringTable())
|
||||
await req.respond(Http200, "Hello World", headers.newHttpHeaders())
|
||||
|
||||
asyncCheck server.serve(Port(5555), cb)
|
||||
runForever()
|
||||
|
||||
@@ -59,6 +59,10 @@ Changes affecting backwards compatibility
|
||||
for the C backend (using ``strcmp``), not reference comparisons anymore.
|
||||
Convert the cstrings to pointers if you really want reference equality
|
||||
for speed.
|
||||
- HTTP headers are now stored in a ``HttpHeaders`` object instead of a
|
||||
``StringTableRef``. This object allows multiple values to be associated with
|
||||
a single key. A new ``httpcore`` module implements it and it is used by
|
||||
both ``asynchttpserver`` and ``httpclient``.
|
||||
|
||||
|
||||
Library Additions
|
||||
|
||||
Reference in New Issue
Block a user