The asynchttpserver module now uses a HttpHeaders object.
This commit is contained in:
parent
2b162c04b1
commit
3074973f54
2 changed files with 24 additions and 76 deletions
|
|
@ -25,12 +25,22 @@
|
||||||
##
|
##
|
||||||
## waitFor server.serve(Port(8080), cb)
|
## 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
|
type
|
||||||
Request* = object
|
Request* = object
|
||||||
client*: AsyncSocket # TODO: Separate this into a Response object?
|
client*: AsyncSocket # TODO: Separate this into a Response object?
|
||||||
reqMethod*: string
|
reqMethod*: string
|
||||||
headers*: StringTableRef
|
headers*: HttpHeaders
|
||||||
protocol*: tuple[orig: string, major, minor: int]
|
protocol*: tuple[orig: string, major, minor: int]
|
||||||
url*: Uri
|
url*: Uri
|
||||||
hostname*: string ## The hostname of the client that made the request.
|
hostname*: string ## The hostname of the client that made the request.
|
||||||
|
|
@ -41,83 +51,27 @@ type
|
||||||
reuseAddr: bool
|
reuseAddr: bool
|
||||||
reusePort: 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,
|
{.deprecated: [TRequest: Request, PAsyncHttpServer: AsyncHttpServer,
|
||||||
THttpCode: HttpCode, THttpVersion: HttpVersion].}
|
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 =
|
proc newAsyncHttpServer*(reuseAddr = true, reusePort = false): AsyncHttpServer =
|
||||||
## Creates a new ``AsyncHttpServer`` instance.
|
## Creates a new ``AsyncHttpServer`` instance.
|
||||||
new result
|
new result
|
||||||
result.reuseAddr = reuseAddr
|
result.reuseAddr = reuseAddr
|
||||||
result.reusePort = reusePort
|
result.reusePort = reusePort
|
||||||
|
|
||||||
proc addHeaders(msg: var string, headers: StringTableRef) =
|
proc addHeaders(msg: var string, headers: HttpHeaders) =
|
||||||
for k, v in headers:
|
for k, v in headers:
|
||||||
msg.add(k & ": " & v & "\c\L")
|
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.
|
## Sends the specified headers to the requesting client.
|
||||||
var msg = ""
|
var msg = ""
|
||||||
addHeaders(msg, headers)
|
addHeaders(msg, headers)
|
||||||
return req.client.send(msg)
|
return req.client.send(msg)
|
||||||
|
|
||||||
proc respond*(req: Request, code: HttpCode, content: string,
|
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
|
## Responds to the request with the specified ``HttpCode``, headers and
|
||||||
## content.
|
## content.
|
||||||
##
|
##
|
||||||
|
|
@ -130,16 +84,6 @@ proc respond*(req: Request, code: HttpCode, content: string,
|
||||||
msg.add(content)
|
msg.add(content)
|
||||||
result = req.client.send(msg)
|
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] =
|
proc parseProtocol(protocol: string): tuple[orig: string, major, minor: int] =
|
||||||
var i = protocol.skipIgnoreCase("HTTP/")
|
var i = protocol.skipIgnoreCase("HTTP/")
|
||||||
if i != 5:
|
if i != 5:
|
||||||
|
|
@ -158,7 +102,7 @@ proc processClient(client: AsyncSocket, address: string,
|
||||||
Future[void] {.closure, gcsafe.}) {.async.} =
|
Future[void] {.closure, gcsafe.}) {.async.} =
|
||||||
var request: Request
|
var request: Request
|
||||||
request.url = initUri()
|
request.url = initUri()
|
||||||
request.headers = newStringTable(modeCaseInsensitive)
|
request.headers = newHttpHeaders()
|
||||||
var lineFut = newFutureVar[string]("asynchttpserver.processClient")
|
var lineFut = newFutureVar[string]("asynchttpserver.processClient")
|
||||||
lineFut.mget() = newStringOfCap(80)
|
lineFut.mget() = newStringOfCap(80)
|
||||||
var key, value = ""
|
var key, value = ""
|
||||||
|
|
@ -167,7 +111,7 @@ proc processClient(client: AsyncSocket, address: string,
|
||||||
# GET /path HTTP/1.1
|
# GET /path HTTP/1.1
|
||||||
# Header: val
|
# Header: val
|
||||||
# \n
|
# \n
|
||||||
request.headers.clear(modeCaseInsensitive)
|
request.headers.clear()
|
||||||
request.body = ""
|
request.body = ""
|
||||||
request.hostname.shallowCopy(address)
|
request.hostname.shallowCopy(address)
|
||||||
assert client != nil
|
assert client != nil
|
||||||
|
|
@ -214,7 +158,7 @@ proc processClient(client: AsyncSocket, address: string,
|
||||||
if request.reqMethod == "post":
|
if request.reqMethod == "post":
|
||||||
# Check for Expect header
|
# Check for Expect header
|
||||||
if request.headers.hasKey("Expect"):
|
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")
|
await client.sendStatus("100 Continue")
|
||||||
else:
|
else:
|
||||||
await client.sendStatus("417 Expectation Failed")
|
await client.sendStatus("417 Expectation Failed")
|
||||||
|
|
@ -223,7 +167,7 @@ proc processClient(client: AsyncSocket, address: string,
|
||||||
# - Check for Content-length header
|
# - Check for Content-length header
|
||||||
if request.headers.hasKey("Content-Length"):
|
if request.headers.hasKey("Content-Length"):
|
||||||
var contentLength = 0
|
var contentLength = 0
|
||||||
if parseInt(request.headers.getOrDefault("Content-Length"),
|
if parseInt(request.headers["Content-Length"],
|
||||||
contentLength) == 0:
|
contentLength) == 0:
|
||||||
await request.respond(Http400, "Bad Request. Invalid Content-Length.")
|
await request.respond(Http400, "Bad Request. Invalid Content-Length.")
|
||||||
continue
|
continue
|
||||||
|
|
@ -291,7 +235,7 @@ when not defined(testing) and isMainModule:
|
||||||
#echo(req.headers)
|
#echo(req.headers)
|
||||||
let headers = {"Date": "Tue, 29 Apr 2014 23:40:08 GMT",
|
let headers = {"Date": "Tue, 29 Apr 2014 23:40:08 GMT",
|
||||||
"Content-type": "text/plain; charset=utf-8"}
|
"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)
|
asyncCheck server.serve(Port(5555), cb)
|
||||||
runForever()
|
runForever()
|
||||||
|
|
|
||||||
|
|
@ -59,6 +59,10 @@ Changes affecting backwards compatibility
|
||||||
for the C backend (using ``strcmp``), not reference comparisons anymore.
|
for the C backend (using ``strcmp``), not reference comparisons anymore.
|
||||||
Convert the cstrings to pointers if you really want reference equality
|
Convert the cstrings to pointers if you really want reference equality
|
||||||
for speed.
|
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
|
Library Additions
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue