Merge branch 'devel' of https://github.com/Araq/Nimrod into devel

This commit is contained in:
Araq 2014-12-26 22:43:40 +01:00
commit 010b8f85c7
16 changed files with 265 additions and 118 deletions

View file

@ -436,12 +436,12 @@ when defined(windows) or defined(nimdoc):
if not initPointer(dummySock, getAcceptExSockAddrsPtr, WSAID_GETACCEPTEXSOCKADDRS):
raiseOSError(osLastError())
proc connectEx(s: SocketHandle, name: ptr TSockAddr, namelen: cint,
proc connectEx(s: SocketHandle, name: ptr SockAddr, namelen: cint,
lpSendBuffer: pointer, dwSendDataLength: Dword,
lpdwBytesSent: PDword, lpOverlapped: POVERLAPPED): bool =
if connectExPtr.isNil: raise newException(ValueError, "Need to initialise ConnectEx().")
let fun =
cast[proc (s: SocketHandle, name: ptr TSockAddr, namelen: cint,
cast[proc (s: SocketHandle, name: ptr SockAddr, namelen: cint,
lpSendBuffer: pointer, dwSendDataLength: Dword,
lpdwBytesSent: PDword, lpOverlapped: POVERLAPPED): bool {.stdcall,gcsafe.}](connectExPtr)
@ -464,16 +464,16 @@ when defined(windows) or defined(nimdoc):
proc getAcceptExSockaddrs(lpOutputBuffer: pointer,
dwReceiveDataLength, dwLocalAddressLength, dwRemoteAddressLength: Dword,
LocalSockaddr: ptr ptr TSockAddr, LocalSockaddrLength: LPInt,
RemoteSockaddr: ptr ptr TSockAddr, RemoteSockaddrLength: LPInt) =
LocalSockaddr: ptr ptr SockAddr, LocalSockaddrLength: LPInt,
RemoteSockaddr: ptr ptr SockAddr, RemoteSockaddrLength: LPInt) =
if getAcceptExSockAddrsPtr.isNil:
raise newException(ValueError, "Need to initialise getAcceptExSockAddrs().")
let fun =
cast[proc (lpOutputBuffer: pointer,
dwReceiveDataLength, dwLocalAddressLength,
dwRemoteAddressLength: Dword, LocalSockaddr: ptr ptr TSockAddr,
LocalSockaddrLength: LPInt, RemoteSockaddr: ptr ptr TSockAddr,
dwRemoteAddressLength: Dword, LocalSockaddr: ptr ptr SockAddr,
LocalSockaddrLength: LPInt, RemoteSockaddr: ptr ptr SockAddr,
RemoteSockaddrLength: LPInt) {.stdcall,gcsafe.}](getAcceptExSockAddrsPtr)
fun(lpOutputBuffer, dwReceiveDataLength, dwLocalAddressLength,
@ -489,12 +489,12 @@ when defined(windows) or defined(nimdoc):
verifyPresence(socket)
var retFuture = newFuture[void]("connect")
# Apparently ``ConnectEx`` expects the socket to be initially bound:
var saddr: Tsockaddr_in
var saddr: Sockaddr_in
saddr.sin_family = int16(toInt(af))
saddr.sin_port = 0
saddr.sin_addr.s_addr = INADDR_ANY
if bindAddr(socket.SocketHandle, cast[ptr TSockAddr](addr(saddr)),
sizeof(saddr).TSockLen) < 0'i32:
if bindAddr(socket.SocketHandle, cast[ptr SockAddr](addr(saddr)),
sizeof(saddr).SockLen) < 0'i32:
raiseOSError(osLastError())
var aiList = getAddrInfo(address, port, af)
@ -516,7 +516,7 @@ when defined(windows) or defined(nimdoc):
)
var ret = connectEx(socket.SocketHandle, it.ai_addr,
sizeof(Tsockaddr_in).cint, nil, 0, nil,
sizeof(Sockaddr_in).cint, nil, 0, nil,
cast[POVERLAPPED](ol))
if ret:
# Request to connect completed immediately.
@ -700,17 +700,17 @@ when defined(windows) or defined(nimdoc):
var lpOutputBuf = newString(lpOutputLen)
var dwBytesReceived: Dword
let dwReceiveDataLength = 0.Dword # We don't want any data to be read.
let dwLocalAddressLength = Dword(sizeof (Tsockaddr_in) + 16)
let dwRemoteAddressLength = Dword(sizeof(Tsockaddr_in) + 16)
let dwLocalAddressLength = Dword(sizeof (Sockaddr_in) + 16)
let dwRemoteAddressLength = Dword(sizeof(Sockaddr_in) + 16)
template completeAccept(): stmt {.immediate, dirty.} =
var listenSock = socket
let setoptRet = setsockopt(clientSock, SOL_SOCKET,
SO_UPDATE_ACCEPT_CONTEXT, addr listenSock,
sizeof(listenSock).TSockLen)
sizeof(listenSock).SockLen)
if setoptRet != 0: raiseOSError(osLastError())
var localSockaddr, remoteSockaddr: ptr TSockAddr
var localSockaddr, remoteSockaddr: ptr SockAddr
var localLen, remoteLen: int32
getAcceptExSockaddrs(addr lpOutputBuf[0], dwReceiveDataLength,
dwLocalAddressLength, dwRemoteAddressLength,
@ -719,7 +719,7 @@ when defined(windows) or defined(nimdoc):
register(clientSock.TAsyncFD)
# TODO: IPv6. Check ``sa_family``. http://stackoverflow.com/a/9212542/492186
retFuture.complete(
(address: $inet_ntoa(cast[ptr Tsockaddr_in](remoteSockAddr).sin_addr),
(address: $inet_ntoa(cast[ptr Sockaddr_in](remoteSockAddr).sin_addr),
client: clientSock.TAsyncFD)
)

View file

@ -18,7 +18,7 @@
##
## .. code-block::nim
## var server = newAsyncHttpServer()
## proc cb(req: TRequest) {.async.} =
## proc cb(req: Request) {.async.} =
## await req.respond(Http200, "Hello World")
##
## asyncCheck server.serve(Port(8080), cb)
@ -27,25 +27,52 @@
import strtabs, asyncnet, asyncdispatch, parseutils, uri, strutils
type
Request* = object
client*: PAsyncSocket # TODO: Separate this into a Response object?
client*: AsyncSocket # TODO: Separate this into a Response object?
reqMethod*: string
headers*: PStringTable
headers*: StringTableRef
protocol*: tuple[orig: string, major, minor: int]
url*: TUri
url*: Uri
hostname*: string ## The hostname of the client that made the request.
body*: string
AsyncHttpServer* = ref object
socket: PAsyncSocket
socket: AsyncSocket
reuseAddr: bool
HttpCode* = enum
Http100 = "100 Continue",
Http101 = "101 Switching Protocols",
Http200 = "200 OK",
Http303 = "303 Moved",
Http201 = "201 Created",
Http202 = "202 Accepted",
Http204 = "204 No Content",
Http205 = "205 Reset Content",
Http206 = "206 Partial Content",
Http300 = "300 Multiple Choices",
Http301 = "301 Moved Permanently",
Http302 = "302 Found",
Http303 = "303 See Other",
Http304 = "304 Not Modified",
Http305 = "305 Use Proxy",
Http307 = "307 Temporary Redirect",
Http400 = "400 Bad Request",
Http401 = "401 Unauthorized",
Http403 = "403 Forbidden",
Http404 = "404 Not Found",
Http405 = "405 Method Not Allowed",
Http406 = "406 Not Acceptable",
Http407 = "407 Proxy Authentication Required",
Http408 = "408 Request Timeout",
Http409 = "409 Conflict",
Http410 = "410 Gone",
Http411 = "411 Length Required",
Http418 = "418 I'm a teapot",
Http500 = "500 Internal Server Error",
Http502 = "502 Bad Gateway"
Http501 = "501 Not Implemented",
Http502 = "502 Bad Gateway",
Http503 = "503 Service Unavailable",
Http504 = "504 Gateway Timeout",
Http505 = "505 HTTP Version Not Supported"
HttpVersion* = enum
HttpVer11,
@ -55,7 +82,7 @@ type
THttpCode: HttpCode, THttpVersion: HttpVersion].}
proc `==`*(protocol: tuple[orig: string, major, minor: int],
ver: THttpVersion): bool =
ver: HttpVersion): bool =
let major =
case ver
of HttpVer11, HttpVer10: 1
@ -65,23 +92,23 @@ proc `==`*(protocol: tuple[orig: string, major, minor: int],
of HttpVer10: 0
result = protocol.major == major and protocol.minor == minor
proc newAsyncHttpServer*(reuseAddr = true): PAsyncHttpServer =
proc newAsyncHttpServer*(reuseAddr = true): AsyncHttpServer =
## Creates a new ``AsyncHttpServer`` instance.
new result
result.reuseAddr = reuseAddr
proc addHeaders(msg: var string, headers: PStringTable) =
proc addHeaders(msg: var string, headers: StringTableRef) =
for k, v in headers:
msg.add(k & ": " & v & "\c\L")
proc sendHeaders*(req: TRequest, headers: PStringTable): Future[void] =
proc sendHeaders*(req: Request, headers: StringTableRef): Future[void] =
## Sends the specified headers to the requesting client.
var msg = ""
addHeaders(msg, headers)
return req.client.send(msg)
proc respond*(req: TRequest, code: THttpCode,
content: string, headers: PStringTable = newStringTable()) {.async.} =
proc respond*(req: Request, code: HttpCode,
content: string, headers = newStringTable()) {.async.} =
## Responds to the request with the specified ``HttpCode``, headers and
## content.
##
@ -92,7 +119,7 @@ proc respond*(req: TRequest, code: THttpCode,
msg.addHeaders(customHeaders)
await req.client.send(msg & "\c\L" & content)
proc newRequest(): TRequest =
proc newRequest(): Request =
result.headers = newStringTable(modeCaseInsensitive)
result.hostname = ""
result.body = ""
@ -107,20 +134,20 @@ proc parseHeader(line: string): tuple[key, value: string] =
proc parseProtocol(protocol: string): tuple[orig: string, major, minor: int] =
var i = protocol.skipIgnoreCase("HTTP/")
if i != 5:
raise newException(EInvalidValue, "Invalid request protocol. Got: " &
raise newException(ValueError, "Invalid request protocol. Got: " &
protocol)
result.orig = protocol
i.inc protocol.parseInt(result.major, i)
i.inc # Skip .
i.inc protocol.parseInt(result.minor, i)
proc sendStatus(client: PAsyncSocket, status: string): Future[void] =
proc sendStatus(client: AsyncSocket, status: string): Future[void] =
client.send("HTTP/1.1 " & status & "\c\L")
proc processClient(client: PAsyncSocket, address: string,
callback: proc (request: TRequest):
proc processClient(client: AsyncSocket, address: string,
callback: proc (request: Request):
Future[void] {.closure, gcsafe.}) {.async.} =
while not client.closed:
while not client.isClosed:
# GET /path HTTP/1.1
# Header: val
# \n
@ -160,7 +187,7 @@ proc processClient(client: PAsyncSocket, address: string,
request.url = parseUri(path)
try:
request.protocol = protocol.parseProtocol()
except EInvalidValue:
except ValueError:
asyncCheck request.respond(Http400, "Invalid request protocol. Got: " &
protocol)
continue
@ -206,8 +233,8 @@ proc processClient(client: PAsyncSocket, address: string,
request.client.close()
break
proc serve*(server: PAsyncHttpServer, port: Port,
callback: proc (request: TRequest): Future[void] {.closure,gcsafe.},
proc serve*(server: AsyncHttpServer, port: Port,
callback: proc (request: Request): Future[void] {.closure,gcsafe.},
address = "") {.async.} =
## Starts the process of listening for incoming HTTP connections on the
## specified address and port.
@ -227,14 +254,14 @@ proc serve*(server: PAsyncHttpServer, port: Port,
#echo(f.isNil)
#echo(f.repr)
proc close*(server: PAsyncHttpServer) =
proc close*(server: AsyncHttpServer) =
## Terminates the async http server instance.
server.socket.close()
when isMainModule:
proc main =
var server = newAsyncHttpServer()
proc cb(req: TRequest) {.async.} =
proc cb(req: Request) {.async.} =
#echo(req.reqMethod, " ", req.url)
#echo(req.headers)
let headers = {"Date": "Tue, 29 Apr 2014 23:40:08 GMT",

View file

@ -10,6 +10,11 @@ include "system/inclrtl"
import sockets, os
##
## **Warning:** This module is deprecated since version 0.10.2.
## Use the brand new `asyncdispatch <asyncdispatch.html>`_ module together
## with the `asyncnet <asyncnet.html>`_ module.
## This module implements an asynchronous event loop together with asynchronous
## sockets which use this event loop.
## It is akin to Python's asyncore module. Many modules that use sockets
@ -90,6 +95,8 @@ import sockets, os
## var client: Socket
## getSocket(s).accept(client)
{.deprecated.}
when defined(windows):
from winlean import TimeVal, SocketHandle, FD_SET, FD_ZERO, TFdSet,
FD_ISSET, select

View file

@ -69,13 +69,13 @@ type
# TODO: I would prefer to just do:
# AsyncSocket* {.borrow: `.`.} = distinct Socket. But that doesn't work.
AsyncSocketDesc = object
fd*: SocketHandle
closed*: bool ## determines whether this socket has been closed
case isBuffered*: bool ## determines whether this socket is buffered.
fd: SocketHandle
closed: bool ## determines whether this socket has been closed
case isBuffered: bool ## determines whether this socket is buffered.
of true:
buffer*: array[0..BufferSize, char]
currPos*: int # current index in buffer
bufLen*: int # current length of buffer
buffer: array[0..BufferSize, char]
currPos: int # current index in buffer
bufLen: int # current length of buffer
of false: nil
case isSsl: bool
of true:
@ -91,7 +91,8 @@ type
# TODO: Save AF, domain etc info and reuse it in procs which need it like connect.
proc newSocket(fd: TAsyncFD, isBuff: bool): AsyncSocket =
proc newAsyncSocket*(fd: TAsyncFD, isBuff: bool): AsyncSocket =
## Creates a new ``AsyncSocket`` based on the supplied params.
assert fd != osInvalidSocket.TAsyncFD
new(result)
result.fd = fd.SocketHandle
@ -102,11 +103,17 @@ proc newSocket(fd: TAsyncFD, isBuff: bool): AsyncSocket =
proc newAsyncSocket*(domain: Domain = AF_INET, typ: SockType = SOCK_STREAM,
protocol: Protocol = IPPROTO_TCP, buffered = true): AsyncSocket =
## Creates a new asynchronous socket.
result = newSocket(newAsyncRawSocket(domain, typ, protocol), buffered)
##
## This procedure will also create a brand new file descriptor for
## this socket.
result = newAsyncSocket(newAsyncRawSocket(domain, typ, protocol), buffered)
proc newAsyncSocket*(domain, typ, protocol: cint, buffered = true): AsyncSocket =
## Creates a new asynchronous socket.
result = newSocket(newAsyncRawSocket(domain, typ, protocol), buffered)
##
## This procedure will also create a brand new file descriptor for
## this socket.
result = newAsyncSocket(newAsyncRawSocket(domain, typ, protocol), buffered)
when defined(ssl):
proc getSslError(handle: SslPtr, err: cint): cint =
@ -275,7 +282,7 @@ proc acceptAddr*(socket: AsyncSocket, flags = {SocketFlag.SafeDisconn}):
retFuture.fail(future.readError)
else:
let resultTup = (future.read.address,
newSocket(future.read.client, socket.isBuffered))
newAsyncSocket(future.read.client, socket.isBuffered))
retFuture.complete(resultTup)
return retFuture
@ -439,6 +446,18 @@ proc setSockOpt*(socket: AsyncSocket, opt: SOBool, value: bool,
var valuei = cint(if value: 1 else: 0)
setSockOptInt(socket.fd, cint(level), toCInt(opt), valuei)
proc isSsl*(socket: AsyncSocket): bool =
## Determines whether ``socket`` is a SSL socket.
socket.isSsl
proc getFd*(socket: AsyncSocket): SocketHandle =
## Returns the socket's file descriptor.
return socket.fd
proc isClosed*(socket: AsyncSocket): bool =
## Determines whether the socket has been closed.
return socket.closed
when isMainModule:
type
TestCases = enum

View file

@ -44,21 +44,21 @@ const
type
SocketImpl* = object ## socket type
fd*: SocketHandle
case isBuffered*: bool # determines whether this socket is buffered.
fd: SocketHandle
case isBuffered: bool # determines whether this socket is buffered.
of true:
buffer*: array[0..BufferSize, char]
currPos*: int # current index in buffer
bufLen*: int # current length of buffer
buffer: array[0..BufferSize, char]
currPos: int # current index in buffer
bufLen: int # current length of buffer
of false: nil
when defined(ssl):
case isSsl*: bool
case isSsl: bool
of true:
sslHandle*: SSLPtr
sslContext*: SSLContext
sslNoHandshake*: bool # True if needs handshake.
sslHasPeekChar*: bool
sslPeekChar*: char
sslHandle: SSLPtr
sslContext: SSLContext
sslNoHandshake: bool # True if needs handshake.
sslHasPeekChar: bool
sslPeekChar: char
of false: nil
Socket* = ref SocketImpl
@ -100,7 +100,8 @@ proc toOSFlags*(socketFlags: set[SocketFlag]): cint =
result = result or MSG_PEEK
of SocketFlag.SafeDisconn: continue
proc createSocket(fd: SocketHandle, isBuff: bool): Socket =
proc newSocket(fd: SocketHandle, isBuff: bool): Socket =
## Creates a new socket as specified by the params.
assert fd != osInvalidSocket
new(result)
result.fd = fd
@ -115,7 +116,7 @@ proc newSocket*(domain, typ, protocol: cint, buffered = true): Socket =
let fd = newRawSocket(domain, typ, protocol)
if fd == osInvalidSocket:
raiseOSError(osLastError())
result = createSocket(fd, buffered)
result = newSocket(fd, buffered)
proc newSocket*(domain: Domain = AF_INET, typ: SockType = SOCK_STREAM,
protocol: Protocol = IPPROTO_TCP, buffered = true): Socket =
@ -125,7 +126,7 @@ proc newSocket*(domain: Domain = AF_INET, typ: SockType = SOCK_STREAM,
let fd = newRawSocket(domain, typ, protocol)
if fd == osInvalidSocket:
raiseOSError(osLastError())
result = createSocket(fd, buffered)
result = newSocket(fd, buffered)
when defined(ssl):
CRYPTO_malloc_init()
@ -937,10 +938,10 @@ proc connect*(socket: Socket, address: string, port = Port(0), timeout: int,
doAssert socket.handshake()
socket.fd.setBlocking(true)
proc isSSL*(socket: Socket): bool = return socket.isSSL
proc isSsl*(socket: Socket): bool = return socket.isSSL
## Determines whether ``socket`` is a SSL socket.
proc getFD*(socket: Socket): SocketHandle = return socket.fd
proc getFd*(socket: Socket): SocketHandle = return socket.fd
## Returns the socket's file descriptor
type

View file

@ -7,10 +7,12 @@
# distribution, for details about the copyright.
#
## Parses & constructs URLs.
## **Warnings:** This module is deprecated since version 0.10.2.
## Use the `uri <uri.html>`_ module instead.
##
## **Note**: This module will be deprecated in the future and merged into a
## new ``url`` module.
## Parses & constructs URLs.
{.deprecated.}
import strutils

View file

@ -25,6 +25,10 @@
##
## **Warning:** The API of this module is unstable, and therefore is subject
## to change.
##
## **Warning:** This module only supports the old asynchronous interface.
## You may wish to use the `asynchttpserver <asynchttpserver.html>`_
## instead for web applications.
include "system/inclrtl"

View file

@ -7,6 +7,10 @@
# distribution, for details about the copyright.
#
## **Warning:** Since version 0.10.2 this module is deprecated.
## Use the `net <net.html>`_ or the
## `rawsockets <rawsockets.html>`_ module instead.
##
## This module implements portable sockets, it supports a mix of different types
## of sockets. Sockets are buffered by default meaning that data will be
## received in ``BufferSize`` (4000) sized chunks, buffering
@ -23,9 +27,6 @@
##
## Asynchronous sockets are supported, however a better alternative is to use
## the `asyncio <asyncio.html>`_ module.
##
## Since version 0.10.2 this module is deprecated. Use the `net <net.html>`_
## or the `rawsockets <rawsockets.html>`_ module instead.
{.deprecated.}

View file

@ -19,14 +19,14 @@ type
{.deprecated: [TUrl: Url, TUri: Uri].}
proc `$`*(url: TUrl): string {.deprecated.} =
## **Deprecated since 0.9.6**: Use ``TUri`` instead.
proc `$`*(url: Url): string {.deprecated.} =
## **Deprecated since 0.9.6**: Use ``Uri`` instead.
return string(url)
proc `/`*(a, b: TUrl): TUrl {.deprecated.} =
proc `/`*(a, b: Url): Url {.deprecated.} =
## Joins two URLs together, separating them with / if needed.
##
## **Deprecated since 0.9.6**: Use ``TUri`` instead.
## **Deprecated since 0.9.6**: Use ``Uri`` instead.
var urlS = $a
var bS = $b
if urlS == "": return b
@ -36,15 +36,15 @@ proc `/`*(a, b: TUrl): TUrl {.deprecated.} =
urlS.add(bS.substr(1))
else:
urlS.add(bs)
result = TUrl(urlS)
result = Url(urlS)
proc add*(url: var TUrl, a: TUrl) {.deprecated.} =
proc add*(url: var Url, a: Url) {.deprecated.} =
## Appends url to url.
##
## **Deprecated since 0.9.6**: Use ``TUri`` instead.
## **Deprecated since 0.9.6**: Use ``Uri`` instead.
url = url / a
proc parseAuthority(authority: string, result: var TUri) =
proc parseAuthority(authority: string, result: var Uri) =
var i = 0
var inPort = false
while true:
@ -65,7 +65,7 @@ proc parseAuthority(authority: string, result: var TUri) =
result.hostname.add(authority[i])
i.inc
proc parsePath(uri: string, i: var int, result: var TUri) =
proc parsePath(uri: string, i: var int, result: var Uri) =
i.inc parseUntil(uri, result.path, {'?', '#'}, i)
@ -82,11 +82,11 @@ proc parsePath(uri: string, i: var int, result: var TUri) =
i.inc # Skip '#'
i.inc parseUntil(uri, result.anchor, {}, i)
proc initUri(): TUri =
result = TUri(scheme: "", username: "", password: "", hostname: "", port: "",
proc initUri(): Uri =
result = Uri(scheme: "", username: "", password: "", hostname: "", port: "",
path: "", query: "", anchor: "")
proc parseUri*(uri: string): TUri =
proc parseUri*(uri: string): Uri =
## Parses a URI.
result = initUri()
@ -113,7 +113,7 @@ proc parseUri*(uri: string): TUri =
var authority = ""
i.inc parseUntil(uri, authority, {'/', '?', '#'}, i)
if authority == "":
raise newException(EInvalidValue, "Expected authority got nothing.")
raise newException(ValueError, "Expected authority got nothing.")
parseAuthority(authority, result)
# Path
@ -150,7 +150,7 @@ proc removeDotSegments(path: string): string =
result = collection.join("/")
if endsWithSlash: result.add '/'
proc merge(base, reference: TUri): string =
proc merge(base, reference: Uri): string =
# http://tools.ietf.org/html/rfc3986#section-5.2.3
if base.hostname != "" and base.path == "":
'/' & reference.path
@ -161,7 +161,7 @@ proc merge(base, reference: TUri): string =
else:
base.path[0 .. lastSegment] & reference.path
proc combine*(base: TUri, reference: TUri): TUri =
proc combine*(base: Uri, reference: Uri): Uri =
## Combines a base URI with a reference URI.
##
## This uses the algorithm specified in
@ -216,13 +216,13 @@ proc combine*(base: TUri, reference: TUri): TUri =
result.scheme = base.scheme
result.anchor = reference.anchor
proc combine*(uris: varargs[TUri]): TUri =
proc combine*(uris: varargs[Uri]): Uri =
## Combines multiple URIs together.
result = uris[0]
for i in 1 .. <uris.len:
result = combine(result, uris[i])
proc `/`*(x: TUri, path: string): TUri =
proc `/`*(x: Uri, path: string): Uri =
## Concatenates the path specified to the specified URI's path.
##
## Contrary to the ``combine`` procedure you do not have to worry about
@ -251,7 +251,7 @@ proc `/`*(x: TUri, path: string): TUri =
result.path.add '/'
result.path.add(path)
proc `$`*(u: TUri): string =
proc `$`*(u: Uri): string =
## Returns the string representation of the specified URI object.
result = ""
if u.scheme.len > 0: