Merge branch 'master' of github.com:Araq/Nimrod

This commit is contained in:
Araq 2012-09-10 19:34:35 +02:00
commit 1bb1db79b2
3 changed files with 184 additions and 30 deletions

View file

@ -55,7 +55,7 @@ import sockets, os
## ##
## var disp: PDispatcher = newDispatcher() ## var disp: PDispatcher = newDispatcher()
## ... ## ...
## proc handleAccept(s: PAsyncSocket, arg: Pobject) {.nimcall.} = ## proc handleAccept(s: PAsyncSocket) =
## echo("Accepted client.") ## echo("Accepted client.")
## var client: PAsyncSocket ## var client: PAsyncSocket
## new(client) ## new(client)
@ -69,6 +69,20 @@ import sockets, os
## received messages and can be read from and the latter gets called whenever ## 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 ## the socket has established a connection to a server socket; from that point
## it can be safely written to. ## 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): when defined(windows):
from winlean import TTimeVal, TFdSet, FD_ZERO, FD_SET, FD_ISSET, select 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, proc AsyncSocket*(domain: TDomain = AF_INET, typ: TType = SOCK_STREAM,
protocol: TProtocol = IPPROTO_TCP, protocol: TProtocol = IPPROTO_TCP,
buffered = true): PAsyncSocket = buffered = true): PAsyncSocket =
## Initialises an AsyncSocket object. If a socket cannot be initialised
## EOS is raised.
result = newAsyncSocket() result = newAsyncSocket()
result.socket = socket(domain, typ, protocol, buffered) result.socket = socket(domain, typ, protocol, buffered)
result.proto = protocol result.proto = protocol
@ -209,12 +225,14 @@ proc connect*(sock: PAsyncSocket, name: string, port = TPort(0),
## Begins connecting ``sock`` to ``name``:``port``. ## Begins connecting ``sock`` to ``name``:``port``.
sock.socket.connectAsync(name, port, af) sock.socket.connectAsync(name, port, af)
sock.info = SockConnecting sock.info = SockConnecting
if sock.deleg != nil:
sock.deleg.open = true sock.deleg.open = true
proc close*(sock: PAsyncSocket) = proc close*(sock: PAsyncSocket) =
## Closes ``sock``. Terminates any current connections. ## Closes ``sock``. Terminates any current connections.
sock.socket.close() sock.socket.close()
sock.info = SockClosed sock.info = SockClosed
if sock.deleg != nil:
sock.deleg.open = false sock.deleg.open = false
proc bindAddr*(sock: PAsyncSocket, port = TPort(0), address = "") = proc bindAddr*(sock: PAsyncSocket, port = TPort(0), address = "") =
@ -222,12 +240,14 @@ proc bindAddr*(sock: PAsyncSocket, port = TPort(0), address = "") =
sock.socket.bindAddr(port, address) sock.socket.bindAddr(port, address)
if sock.proto == IPPROTO_UDP: if sock.proto == IPPROTO_UDP:
sock.info = SockUDPBound sock.info = SockUDPBound
if sock.deleg != nil:
sock.deleg.open = true sock.deleg.open = true
proc listen*(sock: PAsyncSocket) = proc listen*(sock: PAsyncSocket) =
## Equivalent to ``sockets.listen``. ## Equivalent to ``sockets.listen``.
sock.socket.listen() sock.socket.listen()
sock.info = SockListening sock.info = SockListening
if sock.deleg != nil:
sock.deleg.open = true sock.deleg.open = true
proc acceptAddr*(server: PAsyncSocket, client: var PAsyncSocket, proc acceptAddr*(server: PAsyncSocket, client: var PAsyncSocket,

View file

@ -1,7 +1,7 @@
# #
# #
# Nimrod's Runtime Library # Nimrod's Runtime Library
# (c) Copyright 2012 Andreas Rumpf # (c) Copyright 2012 Andreas Rumpf, Dominik Picheta
# #
# See the file "copying.txt", included in this # See the file "copying.txt", included in this
# distribution, for details about the copyright. # distribution, for details about the copyright.
@ -23,7 +23,7 @@
## run(handleRequest, TPort(80)) ## run(handleRequest, TPort(80))
## ##
import parseutils, strutils, os, osproc, strtabs, streams, sockets import parseutils, strutils, os, osproc, strtabs, streams, sockets, asyncio
const const
wwwNL* = "\r\L" wwwNL* = "\r\L"
@ -206,7 +206,7 @@ proc acceptRequest(client: TSocket) =
executeCgi(client, path, query, meth) executeCgi(client, path, query, meth)
type type
TServer* = object ## contains the current server state TServer* = object of TObject ## contains the current server state
socket: TSocket socket: TSocket
port: TPort port: TPort
client*: TSocket ## the socket to write the file data to client*: TSocket ## the socket to write the file data to
@ -216,6 +216,10 @@ type
body*: string ## only set with POST requests body*: string ## only set with POST requests
ip*: string ## ip address of the requesting client 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)) = proc open*(s: var TServer, port = TPort(80)) =
## creates a new server at port `port`. If ``port == 0`` a free port is ## creates a new server at port `port`. If ``port == 0`` a free port is
## acquired that can be accessed later by the ``port`` proc. ## 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.client)
close(s) 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: when isMainModule:
var counter = 0 var counter = 0

View file

@ -67,9 +67,8 @@ type
TAsyncScgiState* = object of TScgiState TAsyncScgiState* = object of TScgiState
handleRequest: proc (server: var TAsyncScgiState, client: TSocket, handleRequest: proc (server: var TAsyncScgiState, client: TSocket,
input: string, headers: PStringTable, input: string, headers: PStringTable) {.closure.}
userArg: PObject) {.nimcall.} asyncServer: PAsyncSocket
userArg: PObject
PAsyncScgiState* = ref TAsyncScgiState PAsyncScgiState* = ref TAsyncScgiState
proc recvBuffer(s: var TScgiState, L: int) = proc recvBuffer(s: var TScgiState, L: int) =
@ -142,25 +141,25 @@ proc run*(handleRequest: proc (client: TSocket, input: string,
s.client.close() s.client.close()
s.close() s.close()
# -- AsyncIO start
proc open*(handleRequest: proc (server: var TAsyncScgiState, client: TSocket, proc open*(handleRequest: proc (server: var TAsyncScgiState, client: TSocket,
input: string, headers: PStringTable, input: string, headers: PStringTable) {.closure.},
userArg: PObject) {.nimcall.}, port = TPort(4000), address = "127.0.0.1"): PAsyncScgiState =
port = TPort(4000), address = "127.0.0.1",
userArg: PObject = nil): PAsyncScgiState =
## Alternative of ``open`` for asyncio compatible SCGI. ## Alternative of ``open`` for asyncio compatible SCGI.
new(result) new(result)
open(result[], port, address) result.bufLen = 4000
result.handleRequest = handleRequest result.input = newString(result.buflen) # will be reused
result.userArg = userArg
proc getSocket(h: PObject): tuple[info: TInfo, sock: TSocket] = result.asyncServer = AsyncSocket()
var s = PAsyncScgiState(h) bindAddr(result.asyncServer, port, address)
return (SockListening, s.server) listen(result.asyncServer)
result.handleRequest = handleRequest
proc handleAccept(h: PObject) = proc handleAccept(h: PObject) =
var s = PAsyncScgiState(h) var s = PAsyncScgiState(h)
accept(s.server, s.client) accept(getSocket(s.asyncServer), s.client)
var L = 0 var L = 0
while true: while true:
var d = s.client.recvChar() var d = s.client.recvChar()
@ -178,15 +177,11 @@ proc handleAccept(h: PObject) =
L = parseInt(s.headers["CONTENT_LENGTH"]) L = parseInt(s.headers["CONTENT_LENGTH"])
recvBuffer(s[], L) 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``. ## Registers ``s`` with dispatcher ``d``.
var dele = newDelegate() result = d.register(s.asyncServer)
dele.deleVal = s
#dele.getSocket = getSocket
dele.handleAccept = handleAccept
d.register(dele)
when false: when false:
var counter = 0 var counter = 0