Merge branch 'master' of github.com:Araq/Nimrod
This commit is contained in:
commit
1bb1db79b2
3 changed files with 184 additions and 30 deletions
|
|
@ -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,26 +225,30 @@ 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
|
||||||
sock.deleg.open = true
|
if sock.deleg != nil:
|
||||||
|
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
|
||||||
sock.deleg.open = false
|
if sock.deleg != nil:
|
||||||
|
sock.deleg.open = false
|
||||||
|
|
||||||
proc bindAddr*(sock: PAsyncSocket, port = TPort(0), address = "") =
|
proc bindAddr*(sock: PAsyncSocket, port = TPort(0), address = "") =
|
||||||
## Equivalent to ``sockets.bindAddr``.
|
## Equivalent to ``sockets.bindAddr``.
|
||||||
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
|
||||||
sock.deleg.open = true
|
if sock.deleg != nil:
|
||||||
|
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
|
||||||
sock.deleg.open = true
|
if sock.deleg != nil:
|
||||||
|
sock.deleg.open = true
|
||||||
|
|
||||||
proc acceptAddr*(server: PAsyncSocket, client: var PAsyncSocket,
|
proc acceptAddr*(server: PAsyncSocket, client: var PAsyncSocket,
|
||||||
address: var string) =
|
address: var string) =
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
@ -215,7 +215,11 @@ type
|
||||||
headers*: PStringTable ## headers with which the client made the request
|
headers*: PStringTable ## headers with which the client made the request
|
||||||
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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue