diff options
author | def <dennis@felsin9.de> | 2015-03-02 03:08:17 +0100 |
---|---|---|
committer | def <dennis@felsin9.de> | 2015-03-17 19:39:02 +0100 |
commit | 477b3594ebdeec0d8ecdf1f050a61af7e0f96cbf (patch) | |
tree | 0c61ec0f372382d99ec042487007ee8656d8829e | |
parent | 07a50caf64d1ed2891349cff9a22b53c4ef61c2d (diff) | |
download | Nim-477b3594ebdeec0d8ecdf1f050a61af7e0f96cbf.tar.gz |
Speed up asynchttpserver significantly using all the previous changes
- Export socket field of AsyncHttpServer and addHeaders proc for templates - Make respond a template instead of proc because of how often it's called. This means no more "await" when invoking it. - Optimize respond template with special case for empty headers and Content-Length entry - newRequest doesn't allocate a hostname and body anymore because they're copied in later - Major changes to processClient to prevent allocations and copies
-rw-r--r-- | lib/pure/asynchttpserver.nim | 98 | ||||
-rw-r--r-- | lib/pure/asyncnet.nim | 2 |
2 files changed, 53 insertions, 47 deletions
diff --git a/lib/pure/asynchttpserver.nim b/lib/pure/asynchttpserver.nim index 64242234c..c288f6089 100644 --- a/lib/pure/asynchttpserver.nim +++ b/lib/pure/asynchttpserver.nim @@ -21,7 +21,7 @@ ## ## var server = newAsyncHttpServer() ## proc cb(req: Request) {.async.} = -## await req.respond(Http200, "Hello World") +## req.respond(Http200, "Hello World") ## ## asyncCheck server.serve(Port(8080), cb) ## runForever() @@ -38,7 +38,7 @@ type body*: string AsyncHttpServer* = ref object - socket: AsyncSocket + socket*: AsyncSocket reuseAddr: bool HttpCode* = enum @@ -99,7 +99,7 @@ proc newAsyncHttpServer*(reuseAddr = true): AsyncHttpServer = new result result.reuseAddr = reuseAddr -proc addHeaders(msg: var string, headers: StringTableRef) = +proc addHeaders*(msg: var string, headers: StringTableRef) = for k, v in headers: msg.add(k & ": " & v & "\c\L") @@ -109,22 +109,22 @@ proc sendHeaders*(req: Request, headers: StringTableRef): Future[void] = addHeaders(msg, headers) return req.client.send(msg) -proc respond*(req: Request, code: HttpCode, - content: string, headers = newStringTable()) {.async.} = +template respond*(req: Request, code: HttpCode, + content: string, headers: StringTableRef = nil) = ## Responds to the request with the specified ``HttpCode``, headers and ## content. ## - ## This procedure will **not** close the client socket. - var customHeaders = headers - customHeaders["Content-Length"] = $content.len + ## This template will **not** close the client socket. var msg = "HTTP/1.1 " & $code & "\c\L" - msg.addHeaders(customHeaders) - await req.client.send(msg & "\c\L" & content) + + if headers != nil: + msg.addHeaders(headers) + msg.add("Content-Length: " & $content.len & "\c\L\c\L") + msg.add(content) + result = req.client.send(msg) proc newRequest(): Request = result.headers = newStringTable(modeCaseInsensitive) - result.hostname = "" - result.body = "" proc parseHeader(line: string): tuple[key, value: string] = var i = 0 @@ -149,77 +149,83 @@ proc sendStatus(client: AsyncSocket, status: string): Future[void] = proc processClient(client: AsyncSocket, address: string, callback: proc (request: Request): Future[void] {.closure, gcsafe.}) {.async.} = + var request: Request + request.url = initUri() + request.headers = newStringTable(modeCaseInsensitive) + var line = newStringOfCap(80) + var key, value = "" + while not client.isClosed: # GET /path HTTP/1.1 # Header: val # \n - var request = newRequest() - request.hostname = address + request.headers.resetStringTable(modeCaseInsensitive) + request.hostname.shallowCopy(address) assert client != nil request.client = client # First line - GET /path HTTP/1.1 - let line = await client.recvLine() # TODO: Timeouts. + line.setLen(0) + client.recvLineInto(line) # TODO: Timeouts. if line == "": client.close() return - let lineParts = line.split(' ') - if lineParts.len != 3: - await request.respond(Http400, "Invalid request. Got: " & line) - continue - let reqMethod = lineParts[0] - let path = lineParts[1] - let protocol = lineParts[2] + var i = 0 + for linePart in line.split(' '): + case i + of 0: request.reqMethod.shallowCopy(linePart.normalize) + of 1: parseUri(linePart, request.url) + of 2: + try: + request.protocol = parseProtocol(linePart) + except ValueError: + request.respond(Http400, "Invalid request protocol. Got: " & + linePart) + continue + else: + request.respond(Http400, "Invalid request. Got: " & line) + continue + inc i # Headers - var i = 0 while true: i = 0 - let headerLine = await client.recvLine() - if headerLine == "": - client.close(); return - if headerLine == "\c\L": break - # TODO: Compiler crash - #let (key, value) = parseHeader(headerLine) - let kv = parseHeader(headerLine) - request.headers[kv.key] = kv.value + line.setLen(0) + client.recvLineInto(line) - request.reqMethod = reqMethod - request.url = parseUri(path) - try: - request.protocol = protocol.parseProtocol() - except ValueError: - asyncCheck request.respond(Http400, "Invalid request protocol. Got: " & - protocol) - continue + if line == "": + client.close(); return + if line == "\c\L": break + let (key, value) = parseHeader(line) + request.headers[key] = value - if reqMethod.normalize == "post": + if request.reqMethod == "post": # Check for Expect header if request.headers.hasKey("Expect"): if request.headers["Expect"].toLower == "100-continue": await client.sendStatus("100 Continue") else: await client.sendStatus("417 Expectation Failed") - + # Read the body # - Check for Content-length header if request.headers.hasKey("Content-Length"): var contentLength = 0 if parseInt(request.headers["Content-Length"], contentLength) == 0: - await request.respond(Http400, "Bad Request. Invalid Content-Length.") + request.respond(Http400, "Bad Request. Invalid Content-Length.") else: request.body = await client.recv(contentLength) assert request.body.len == contentLength else: - await request.respond(Http400, "Bad Request. No Content-Length.") + request.respond(Http400, "Bad Request. No Content-Length.") continue - case reqMethod.normalize + case request.reqMethod of "get", "post", "head", "put", "delete", "trace", "options", "connect", "patch": await callback(request) else: - await request.respond(Http400, "Invalid request method. Got: " & reqMethod) + request.respond(Http400, "Invalid request method. Got: " & request.reqMethod) # Persistent connections if (request.protocol == HttpVer11 and @@ -268,7 +274,7 @@ when 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()) + req.respond(Http200, "Hello World", headers.newStringTable()) asyncCheck server.serve(Port(5555), cb) runForever() diff --git a/lib/pure/asyncnet.nim b/lib/pure/asyncnet.nim index 7e1ff5db4..cb137cfe5 100644 --- a/lib/pure/asyncnet.nim +++ b/lib/pure/asyncnet.nim @@ -200,7 +200,7 @@ template readInto*(buf: cstring, size: int, socket: AsyncSocket, res = recvIntoFut.read() res -template readIntoBuf(socket: AsyncSocket, +template readIntoBuf*(socket: AsyncSocket, flags: set[SocketFlag]): int = var size = readInto(addr socket.buffer[0], BufferSize, socket, flags) socket.currPos = 0 |