summary refs log tree commit diff stats
path: root/lib/pure
diff options
context:
space:
mode:
authorAraq <rumpf_a@web.de>2013-05-19 23:17:28 +0200
committerAraq <rumpf_a@web.de>2013-05-19 23:17:28 +0200
commit5edf02a7ab7f46c5841104538a313fad5dbba5f9 (patch)
tree27fe24fcba30d4edb0e4ce5d0b70682c27a2def1 /lib/pure
parent1c9b4e5d33ff6bca8f345951b72018171d47e251 (diff)
parent1180040871a682657e0595c625e8330b90390283 (diff)
downloadNim-5edf02a7ab7f46c5841104538a313fad5dbba5f9.tar.gz
Merge branch 'master' of github.com:Araq/Nimrod
Diffstat (limited to 'lib/pure')
-rw-r--r--lib/pure/asyncio.nim3
-rw-r--r--lib/pure/scgi.nim124
-rw-r--r--lib/pure/sockets.nim3
3 files changed, 100 insertions, 30 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)