summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--lib/pure/asyncio.nim30
-rwxr-xr-xlib/pure/httpserver.nim147
-rwxr-xr-xlib/pure/scgi.nim37
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