diff options
-rw-r--r-- | lib/pure/asyncio.nim | 30 | ||||
-rwxr-xr-x | lib/pure/httpserver.nim | 147 | ||||
-rwxr-xr-x | lib/pure/scgi.nim | 37 |
3 files changed, 184 insertions, 30 deletions
diff --git a/lib/pure/asyncio.nim b/lib/pure/asyncio.nim index 20f92906f..6b384b1a7 100644 --- a/lib/pure/asyncio.nim +++ b/lib/pure/asyncio.nim @@ -55,7 +55,7 @@ import sockets, os ## ## var disp: PDispatcher = newDispatcher() ## ... -## proc handleAccept(s: PAsyncSocket, arg: Pobject) {.nimcall.} = +## proc handleAccept(s: PAsyncSocket) = ## echo("Accepted client.") ## var client: PAsyncSocket ## new(client) @@ -69,6 +69,20 @@ import sockets, os ## received messages and can be read from and the latter gets called whenever ## the socket has established a connection to a server socket; from that point ## it can be safely written to. +## +## Getting a blocking client from a PAsyncSocket +## ============================================= +## +## If you need a asynchronous server socket but you wish to process the clients +## synchronously then you can use the ``getSocket`` converter to get a TSocket +## object from the PAsyncSocket object, this can then be combined with ``accept`` +## like so: +## +## .. code-block:: nimrod +## +## proc handleAccept(s: PAsyncSocket) = +## var client: TSocket +## getSocket(s).accept(client) when defined(windows): from winlean import TTimeVal, TFdSet, FD_ZERO, FD_SET, FD_ISSET, select @@ -137,6 +151,8 @@ proc newAsyncSocket(): PAsyncSocket = proc AsyncSocket*(domain: TDomain = AF_INET, typ: TType = SOCK_STREAM, protocol: TProtocol = IPPROTO_TCP, buffered = true): PAsyncSocket = + ## Initialises an AsyncSocket object. If a socket cannot be initialised + ## EOS is raised. result = newAsyncSocket() result.socket = socket(domain, typ, protocol, buffered) result.proto = protocol @@ -209,26 +225,30 @@ proc connect*(sock: PAsyncSocket, name: string, port = TPort(0), ## Begins connecting ``sock`` to ``name``:``port``. sock.socket.connectAsync(name, port, af) sock.info = SockConnecting - sock.deleg.open = true + if sock.deleg != nil: + sock.deleg.open = true proc close*(sock: PAsyncSocket) = ## Closes ``sock``. Terminates any current connections. sock.socket.close() sock.info = SockClosed - sock.deleg.open = false + if sock.deleg != nil: + sock.deleg.open = false proc bindAddr*(sock: PAsyncSocket, port = TPort(0), address = "") = ## Equivalent to ``sockets.bindAddr``. sock.socket.bindAddr(port, address) if sock.proto == IPPROTO_UDP: sock.info = SockUDPBound - sock.deleg.open = true + if sock.deleg != nil: + sock.deleg.open = true proc listen*(sock: PAsyncSocket) = ## Equivalent to ``sockets.listen``. sock.socket.listen() sock.info = SockListening - sock.deleg.open = true + if sock.deleg != nil: + sock.deleg.open = true proc acceptAddr*(server: PAsyncSocket, client: var PAsyncSocket, address: var string) = diff --git a/lib/pure/httpserver.nim b/lib/pure/httpserver.nim index 7a314b9c6..b8ef6e113 100755 --- a/lib/pure/httpserver.nim +++ b/lib/pure/httpserver.nim @@ -1,7 +1,7 @@ # # # Nimrod's Runtime Library -# (c) Copyright 2012 Andreas Rumpf +# (c) Copyright 2012 Andreas Rumpf, Dominik Picheta # # See the file "copying.txt", included in this # distribution, for details about the copyright. @@ -23,7 +23,7 @@ ## run(handleRequest, TPort(80)) ## -import parseutils, strutils, os, osproc, strtabs, streams, sockets +import parseutils, strutils, os, osproc, strtabs, streams, sockets, asyncio const wwwNL* = "\r\L" @@ -206,7 +206,7 @@ proc acceptRequest(client: TSocket) = executeCgi(client, path, query, meth) type - TServer* = object ## contains the current server state + TServer* = object of TObject ## contains the current server state socket: TSocket port: TPort client*: TSocket ## the socket to write the file data to @@ -215,7 +215,11 @@ type headers*: PStringTable ## headers with which the client made the request body*: string ## only set with POST requests ip*: string ## ip address of the requesting client - + + PAsyncHTTPServer* = ref TAsyncHTTPServer + TAsyncHTTPServer = object of TServer + asyncSocket: PAsyncSocket + proc open*(s: var TServer, port = TPort(80)) = ## creates a new server at port `port`. If ``port == 0`` a free port is ## acquired that can be accessed later by the ``port`` proc. @@ -363,6 +367,141 @@ proc run*(handleRequest: proc (client: TSocket, close(s.client) close(s) +# -- AsyncIO begin + +proc nextAsync(s: PAsyncHTTPServer) = + ## proceed to the first/next request. + var client: TSocket + new(client) + var ip: string + acceptAddr(getSocket(s.asyncSocket), client, ip) + s.client = client + s.ip = ip + s.headers = newStringTable(modeCaseInsensitive) + #headers(s.client, "") + var data = "" + while not s.client.recvLine(data): nil + if data == "": + # Socket disconnected + s.client.close() + return + var header = "" + while true: + if s.client.recvLine(header): + if header == "\c\L": break + if header != "": + var i = 0 + var key = "" + var value = "" + i = header.parseUntil(key, ':') + inc(i) # skip : + i += header.skipWhiteSpace(i) + i += header.parseUntil(value, {'\c', '\L'}, i) + s.headers[key] = value + else: + s.client.close() + return + + var i = skipWhitespace(data) + if skipIgnoreCase(data, "GET") > 0: + s.reqMethod = "GET" + inc(i, 3) + elif skipIgnoreCase(data, "POST") > 0: + s.reqMethod = "POST" + inc(i, 4) + else: + unimplemented(s.client) + s.client.close() + return + + if s.reqMethod == "POST": + # Check for Expect header + if s.headers.hasKey("Expect"): + if s.headers["Expect"].toLower == "100-continue": + s.client.sendStatus("100 Continue") + else: + s.client.sendStatus("417 Expectation Failed") + + # Read the body + # - Check for Content-length header + if s.headers.hasKey("Content-Length"): + var contentLength = 0 + if parseInt(s.headers["Content-Length"], contentLength) == 0: + badRequest(s.client) + s.client.close() + return + else: + var totalRead = 0 + var totalBody = "" + while totalRead < contentLength: + var chunkSize = 8000 + if (contentLength - totalRead) < 8000: + chunkSize = (contentLength - totalRead) + var bodyData = newString(chunkSize) + var octetsRead = s.client.recv(cstring(bodyData), chunkSize) + if octetsRead <= 0: + s.client.close() + return + totalRead += octetsRead + totalBody.add(bodyData) + if totalBody.len != contentLength: + s.client.close() + return + + s.body = totalBody + else: + badRequest(s.client) + s.client.close() + return + + var L = skipWhitespace(data, i) + inc(i, L) + # XXX we ignore "HTTP/1.1" etc. for now here + var query = 0 + var last = i + while last < data.len and data[last] notin whitespace: + if data[last] == '?' and query == 0: query = last + inc(last) + if query > 0: + s.query = data.substr(query+1, last-1) + s.path = data.substr(i, query-1) + else: + s.query = "" + s.path = data.substr(i, last-1) + +proc asyncHTTPServer*(handleRequest: proc (server: PAsyncHTTPServer, client: TSocket, + path, query: string): bool {.closure.}, + port = TPort(80), address = ""): PAsyncHTTPServer = + ## Creates an Asynchronous HTTP server at ``port``. + var capturedRet: PAsyncHTTPServer + new(capturedRet) + capturedRet.asyncSocket = AsyncSocket() + capturedRet.asyncSocket.handleAccept = + proc (s: PAsyncSocket) = + nextAsync(capturedRet) + let quit = handleRequest(capturedRet, capturedRet.client, capturedRet.path, + capturedRet.query) + if quit: capturedRet.asyncSocket.close() + + capturedRet.asyncSocket.bindAddr(port, address) + capturedRet.asyncSocket.listen() + if port == TPort(0): + capturedRet.port = getSockName(capturedRet.asyncSocket) + else: + capturedRet.port = port + + capturedRet.client = InvalidSocket + capturedRet.reqMethod = "" + capturedRet.body = "" + capturedRet.path = "" + capturedRet.query = "" + capturedRet.headers = {:}.newStringTable() + result = capturedRet + +proc register*(d: PDispatcher, s: PAsyncHTTPServer) = + ## Registers a PAsyncHTTPServer with a PDispatcher. + d.register(s.asyncSocket) + when isMainModule: var counter = 0 diff --git a/lib/pure/scgi.nim b/lib/pure/scgi.nim index badc19096..04dc3b015 100755 --- a/lib/pure/scgi.nim +++ b/lib/pure/scgi.nim @@ -67,9 +67,8 @@ type TAsyncScgiState* = object of TScgiState handleRequest: proc (server: var TAsyncScgiState, client: TSocket, - input: string, headers: PStringTable, - userArg: PObject) {.nimcall.} - userArg: PObject + input: string, headers: PStringTable) {.closure.} + asyncServer: PAsyncSocket PAsyncScgiState* = ref TAsyncScgiState proc recvBuffer(s: var TScgiState, L: int) = @@ -142,25 +141,25 @@ proc run*(handleRequest: proc (client: TSocket, input: string, s.client.close() s.close() +# -- AsyncIO start + proc open*(handleRequest: proc (server: var TAsyncScgiState, client: TSocket, - input: string, headers: PStringTable, - userArg: PObject) {.nimcall.}, - port = TPort(4000), address = "127.0.0.1", - userArg: PObject = nil): PAsyncScgiState = + input: string, headers: PStringTable) {.closure.}, + port = TPort(4000), address = "127.0.0.1"): PAsyncScgiState = ## Alternative of ``open`` for asyncio compatible SCGI. new(result) - open(result[], port, address) - result.handleRequest = handleRequest - result.userArg = userArg + result.bufLen = 4000 + result.input = newString(result.buflen) # will be reused -proc getSocket(h: PObject): tuple[info: TInfo, sock: TSocket] = - var s = PAsyncScgiState(h) - return (SockListening, s.server) + result.asyncServer = AsyncSocket() + bindAddr(result.asyncServer, port, address) + listen(result.asyncServer) + result.handleRequest = handleRequest proc handleAccept(h: PObject) = var s = PAsyncScgiState(h) - accept(s.server, s.client) + accept(getSocket(s.asyncServer), s.client) var L = 0 while true: var d = s.client.recvChar() @@ -178,15 +177,11 @@ proc handleAccept(h: PObject) = L = parseInt(s.headers["CONTENT_LENGTH"]) recvBuffer(s[], L) - s.handleRequest(s[], s.client, s.input, s.headers, s.userArg) + s.handleRequest(s[], s.client, s.input, s.headers) -proc register*(d: PDispatcher, s: PAsyncScgiState) = +proc register*(d: PDispatcher, s: PAsyncScgiState): PDelegate {.discardable.} = ## Registers ``s`` with dispatcher ``d``. - var dele = newDelegate() - dele.deleVal = s - #dele.getSocket = getSocket - dele.handleAccept = handleAccept - d.register(dele) + result = d.register(s.asyncServer) when false: var counter = 0 |