diff options
-rw-r--r-- | lib/pure/asyncio.nim | 3 | ||||
-rw-r--r-- | lib/pure/scgi.nim | 124 | ||||
-rw-r--r-- | lib/pure/sockets.nim | 3 | ||||
-rw-r--r-- | web/news.txt | 31 |
4 files changed, 124 insertions, 37 deletions
diff --git a/lib/pure/asyncio.nim b/lib/pure/asyncio.nim index 88e3e848f..403401ff1 100644 --- a/lib/pure/asyncio.nim +++ b/lib/pure/asyncio.nim @@ -420,6 +420,9 @@ proc isListening*(s: PAsyncSocket): bool = proc isConnecting*(s: PAsyncSocket): bool = ## Determines whether ``s`` is connecting. return s.info == SockConnecting +proc isClosed*(s: PAsyncSocket): bool = + ## Determines whether ``s`` has been closed. + return s.info == SockClosed proc setHandleWrite*(s: PAsyncSocket, handleWrite: proc (s: PAsyncSocket) {.closure.}) = diff --git a/lib/pure/scgi.nim b/lib/pure/scgi.nim index 0f3b44e00..57fa0b144 100644 --- a/lib/pure/scgi.nim +++ b/lib/pure/scgi.nim @@ -1,7 +1,7 @@ # # # Nimrod's Runtime Library -# (c) Copyright 2012 Andreas Rumpf +# (c) Copyright 2013 Andreas Rumpf, Dominik Picheta # # See the file "copying.txt", included in this # distribution, for details about the copyright. @@ -67,10 +67,24 @@ type headers*: PStringTable ## the parsed headers input*: string ## the input buffer - TAsyncScgiState* = object of TScgiState - handleRequest: proc (server: var TAsyncScgiState, client: TSocket, + + # Async + + TClientMode = enum + ClientReadChar, ClientReadHeaders, ClientReadContent + + PAsyncClient = ref object + c: PAsyncSocket + mode: TClientMode + dataLen: int + headers: PStringTable ## the parsed headers + input: string ## the input buffer + + TAsyncScgiState = object + handleRequest: proc (client: PAsyncSocket, input: string, headers: PStringTable) {.closure.} asyncServer: PAsyncSocket + disp: PDispatcher PAsyncScgiState* = ref TAsyncScgiState proc recvBuffer(s: var TScgiState, L: int) = @@ -145,37 +159,88 @@ proc run*(handleRequest: proc (client: TSocket, input: string, s.close() # -- AsyncIO start + +proc recvBufferAsync(client: PAsyncClient, L: int): TReadLineResult = + result = ReadPartialLine + var data = "" + if L < 1: + scgiError("Cannot read negative or zero length: " & $L) + let ret = recvAsync(client.c, data, L) + if ret == 0 and data == "": + client.c.close() + return ReadDisconnected + if ret == -1: + return ReadNone # No more data available + client.input.add(data) + if ret == L: + return ReadFullLine + +proc handleClientRead(client: PAsyncClient, s: PAsyncScgiState) = + case client.mode + of ClientReadChar: + while true: + var d = "" + let ret = client.c.recvAsync(d, 1) + if d == "" and ret == 0: + # Disconnected + client.c.close() + return + if ret == -1: + return # No more data available + if d[0] notin strutils.digits: + if d[0] != ':': scgiError("':' after length expected") + break + client.dataLen = client.dataLen * 10 + ord(d[0]) - ord('0') + client.mode = ClientReadHeaders + handleClientRead(client, s) # Allow progression + of ClientReadHeaders: + let ret = recvBufferAsync(client, (client.dataLen+1)-client.input.len) + case ret + of ReadFullLine: + client.headers = parseHeaders(client.input, client.input.len-1) + if client.headers["SCGI"] != "1": scgiError("SCGI Version 1 expected") + client.input = "" # For next part + + let contentLen = parseInt(client.headers["CONTENT_LENGTH"]) + if contentLen > 0: + client.mode = ClientReadContent + else: + s.handleRequest(client.c, client.input, client.headers) + if not client.c.isClosed: client.c.close() + of ReadPartialLine, ReadDisconnected, ReadNone: return + of ClientReadContent: + let L = parseInt(client.headers["CONTENT_LENGTH"])-client.input.len + if L > 0: + let ret = recvBufferAsync(client, L) + case ret + of ReadFullLine: + s.handleRequest(client.c, client.input, client.headers) + if not client.c.isClosed: client.c.close() + of ReadPartialLine, ReadDisconnected, ReadNone: return + else: + s.handleRequest(client.c, client.input, client.headers) + if not client.c.isClosed: client.c.close() + proc handleAccept(sock: PAsyncSocket, s: PAsyncScgiState) = - new(s.client) - accept(getSocket(s.asyncServer), s.client) - var L = 0 - while true: - var d = s.client.recvChar() - if d == '\0': - # Disconnected - s.client.close() - return - if d notin strutils.digits: - if d != ':': scgiError("':' after length expected") - break - L = L * 10 + ord(d) - ord('0') - recvBuffer(s[], L+1) - s.headers = parseHeaders(s.input, L) - if s.headers["SCGI"] != "1": scgiError("SCGI Version 1 expected") - L = parseInt(s.headers["CONTENT_LENGTH"]) - recvBuffer(s[], L) - - s.handleRequest(s[], s.client, s.input, s.headers) - -proc open*(handleRequest: proc (server: var TAsyncScgiState, client: TSocket, + var client: PAsyncSocket + new(client) + accept(s.asyncServer, client) + var asyncClient = PAsyncClient(c: client, mode: ClientReadChar, dataLen: 0, + headers: newStringTable(), input: "") + client.handleRead = + proc (sock: PAsyncSocket) = + handleClientRead(asyncClient, s) + s.disp.register(client) + +proc open*(handleRequest: proc (client: PAsyncSocket, input: string, headers: PStringTable) {.closure.}, port = TPort(4000), address = "127.0.0.1"): PAsyncScgiState = - ## Alternative of ``open`` for asyncio compatible SCGI. + ## Creates an ``PAsyncScgiState`` object which serves as a SCGI server. + ## + ## After the execution of ``handleRequest`` the client socket will be closed + ## automatically unless it has already been closed. var cres: PAsyncScgiState new(cres) - cres.bufLen = 4000 - cres.input = newString(cres.buflen) # will be reused - cres.asyncServer = AsyncSocket() cres.asyncServer.handleAccept = proc (s: PAsyncSocket) = handleAccept(s, cres) bindAddr(cres.asyncServer, port, address) @@ -186,6 +251,7 @@ proc open*(handleRequest: proc (server: var TAsyncScgiState, client: TSocket, proc register*(d: PDispatcher, s: PAsyncScgiState): PDelegate {.discardable.} = ## Registers ``s`` with dispatcher ``d``. result = d.register(s.asyncServer) + s.disp = d proc close*(s: PAsyncScgiState) = ## Closes the ``PAsyncScgiState``. diff --git a/lib/pure/sockets.nim b/lib/pure/sockets.nim index 17e4d3118..cde2d4795 100644 --- a/lib/pure/sockets.nim +++ b/lib/pure/sockets.nim @@ -629,7 +629,8 @@ proc close*(socket: TSocket) = discard winlean.closeSocket(socket.fd) else: discard posix.close(socket.fd) - + # TODO: These values should not be discarded. An EOS should be raised. + # http://stackoverflow.com/questions/12463473/what-happens-if-you-call-close-on-a-bsd-socket-multiple-times when defined(ssl): if socket.isSSL: discard SSLShutdown(socket.sslHandle) diff --git a/web/news.txt b/web/news.txt index e948e762b..f66c06667 100644 --- a/web/news.txt +++ b/web/news.txt @@ -2,6 +2,15 @@ News ==== +New website design! +=================== + +A brand new website is now live. All thanks go to Philip Witte and +Dominik Picheta, Philip Witte for the design of the website (together with +the logo) as well as the HTML and CSS code for his template, and Dominik Picheta +for integrating Philip's design with the ``nimweb`` utility. We're sure you will +agree that Philip's design is beautiful. + 2013-XX-XX Version 0.9.2 released ================================= @@ -19,7 +28,10 @@ Bugfixes - ``cast`` for floating point types now does the bitcast as specified in the manual. This breaks code that erroneously uses ``cast`` to convert different floating point values. +- SCGI module's performance has been improved greatly, it will no longer block + on many concurrent requests. +- In total fixed over 70 github issues and merged over 60 pull requests. Library Additions ----------------- @@ -46,9 +58,7 @@ Changes affecting backwards compatibility this affects very little (if any) real world code. - The expression/statement unification has been implemented. Again this only affects edge cases and no known real world code. -- The scope rules of ``if`` statements will change in 0.9.4. This affects the - ``=~`` pegs/re templates. - +- Changed the async interface of the ``scgi`` module. Compiler Additions ------------------ @@ -59,9 +69,8 @@ Compiler Additions to be turned on explicitly via ``--warning[ShadowIdent]:on``. - The compiler now supports almost every pragma in a ``push`` pragma. - Generic converters have been implemented. -- Added a ``noforward`` pragma enabling a special compilation mode that largely - eliminates the need for forward declarations. - +- Added a **highly experimental** ``noforward`` pragma enabling a special + compilation mode that largely eliminates the need for forward declarations. Language Additions ------------------ @@ -75,7 +84,7 @@ Language Additions exceptions for you. - User defined effects ("tags") tracking has been added and the ``doc2`` command annotates possible tags for you. -- Types can be annotated with the new syntax ``not nil`` to explictly state +- Types can be annotated with the new syntax ``not nil`` to explicitly state that ``nil`` is not allowed. However currently the compiler performs no advanced static checking for this; for now it's merely for documentation purposes. @@ -88,6 +97,14 @@ Language Additions - There is a new syntactic construct ``(;)`` unifying expressions and statements. +Notes for the future +-------------------- + +- The scope rules of ``if`` statements will change in 0.9.4. This affects the + ``=~`` pegs/re templates. +- The ``sockets`` module will become a low-level wrapper of OS-specific socket + functions. All the high-level features of the current ``sockets`` module + will be moved to a ``network`` module. 2012-09-23 Version 0.9.0 released ================================= |