Merge branch 'devel' of github.com:nim-lang/Nim into devel

This commit is contained in:
Andreas Rumpf 2017-05-04 10:58:21 +02:00
commit c1aa973758
22 changed files with 698 additions and 368 deletions

View file

@ -17,15 +17,17 @@ proc addPath*(path: string, info: TLineInfo) =
proc versionSplitPos(s: string): int = proc versionSplitPos(s: string): int =
result = s.len-2 result = s.len-2
while result > 1 and s[result] in {'0'..'9', '.'}: dec result #while result > 1 and s[result] in {'0'..'9', '.'}: dec result
while result > 1 and s[result] != '-': dec result
if s[result] != '-': result = s.len if s[result] != '-': result = s.len
const const
latest = "head" latest = ""
proc `<.`(a, b: string): bool = proc `<.`(a, b: string): bool =
# wether a has a smaller version than b: # wether a has a smaller version than b:
if a == latest: return false if a == latest: return true
elif b == latest: return false
var i = 0 var i = 0
var j = 0 var j = 0
var verA = 0 var verA = 0
@ -33,8 +35,13 @@ proc `<.`(a, b: string): bool =
while true: while true:
let ii = parseInt(a, verA, i) let ii = parseInt(a, verA, i)
let jj = parseInt(b, verB, j) let jj = parseInt(b, verB, j)
# if A has no number left, but B has, B is preferred: 0.8 vs 0.8.3 if ii <= 0 or jj <= 0:
if ii <= 0 or jj <= 0: return jj > 0 # if A has no number and B has but A has no number whatsoever ("#head"),
# A is preferred:
if ii > 0 and jj <= 0 and j == 0: return true
if ii <= 0 and jj > 0 and i == 0: return false
# if A has no number left, but B has, B is preferred: 0.8 vs 0.8.3
return jj > 0
if verA < verB: return true if verA < verB: return true
elif verA > verB: return false elif verA > verB: return false
# else: same version number; continue: # else: same version number; continue:
@ -46,12 +53,9 @@ proc `<.`(a, b: string): bool =
proc addPackage(packages: StringTableRef, p: string) = proc addPackage(packages: StringTableRef, p: string) =
let x = versionSplitPos(p) let x = versionSplitPos(p)
let name = p.substr(0, x-1) let name = p.substr(0, x-1)
if x < p.len: let version = if x < p.len: p.substr(x+1) else: ""
let version = p.substr(x+1) if packages.getOrDefault(name) <. version:
if packages.getOrDefault(name) <. version: packages[name] = version
packages[name] = version
else:
packages[name] = latest
iterator chosen(packages: StringTableRef): string = iterator chosen(packages: StringTableRef): string =
for key, val in pairs(packages): for key, val in pairs(packages):
@ -76,3 +80,18 @@ proc addPathRec(dir: string, info: TLineInfo) =
proc nimblePath*(path: string, info: TLineInfo) = proc nimblePath*(path: string, info: TLineInfo) =
addPathRec(path, info) addPathRec(path, info)
addNimblePath(path, info) addNimblePath(path, info)
when isMainModule:
var rr = newStringTable()
addPackage rr, "irc-#head"
addPackage rr, "irc-0.1.0"
addPackage rr, "irc"
addPackage rr, "another"
addPackage rr, "another-0.1"
addPackage rr, "ab-0.1.3"
addPackage rr, "ab-0.1"
addPackage rr, "justone"
for p in rr.chosen:
echo p

View file

@ -97,15 +97,6 @@ when not defined(macosx):
## Second-granularity time of last status change. ## Second-granularity time of last status change.
result = s.st_ctim.tv_sec result = s.st_ctim.tv_sec
proc WIFCONTINUED*(s:cint) : bool {.importc, header: "<sys/wait.h>".}
## True if child has been continued.
proc WIFEXITED*(s:cint) : bool {.importc, header: "<sys/wait.h>".}
## True if child exited normally.
proc WIFSIGNALED*(s:cint) : bool {.importc, header: "<sys/wait.h>".}
## True if child exited due to uncaught signal.
proc WIFSTOPPED*(s:cint) : bool {.importc, header: "<sys/wait.h>".}
## True if child is currently stopped.
when hasAioH: when hasAioH:
proc aio_cancel*(a1: cint, a2: ptr Taiocb): cint {.importc, header: "<aio.h>".} proc aio_cancel*(a1: cint, a2: ptr Taiocb): cint {.importc, header: "<aio.h>".}
proc aio_error*(a1: ptr Taiocb): cint {.importc, header: "<aio.h>".} proc aio_error*(a1: ptr Taiocb): cint {.importc, header: "<aio.h>".}

View file

@ -602,3 +602,12 @@ var
include posix_linux_amd64_consts include posix_linux_amd64_consts
const POSIX_SPAWN_USEVFORK* = cint(0x40) # needs _GNU_SOURCE! const POSIX_SPAWN_USEVFORK* = cint(0x40) # needs _GNU_SOURCE!
# <sys/wait.h>
proc WEXITSTATUS*(s: cint): cint = (s and 0xff00) shr 8
proc WTERMSIG*(s:cint): cint = s and 0x7f
proc WSTOPSIG*(s:cint): cint = WEXITSTATUS(s)
proc WIFEXITED*(s:cint) : bool = WTERMSIG(s) == 0
proc WIFSIGNALED*(s:cint) : bool = (cast[int8]((s and 0x7f) + 1) shr 1) > 0
proc WIFSTOPPED*(s:cint) : bool = (s and 0xff) == 0x7f
proc WIFCONTINUED*(s:cint) : bool = s == W_CONTINUED

View file

@ -611,3 +611,18 @@ when hasSpawnH:
# OR'ing of flags: # OR'ing of flags:
const POSIX_SPAWN_USEVFORK* = cint(0) const POSIX_SPAWN_USEVFORK* = cint(0)
# <sys/wait.h>
proc WEXITSTATUS*(s: cint): cint {.importc, header: "<sys/wait.h>".}
## Exit code, iff WIFEXITED(s)
proc WTERMSIG*(s: cint): cint {.importc, header: "<sys/wait.h>".}
## Termination signal, iff WIFSIGNALED(s)
proc WSTOPSIG*(s: cint): cint {.importc, header: "<sys/wait.h>".}
## Stop signal, iff WIFSTOPPED(s)
proc WIFEXITED*(s: cint): bool {.importc, header: "<sys/wait.h>".}
## True if child exited normally.
proc WIFSIGNALED*(s: cint): bool {.importc, header: "<sys/wait.h>".}
## True if child exited due to uncaught signal.
proc WIFSTOPPED*(s: cint): bool {.importc, header: "<sys/wait.h>".}
## True if child is currently stopped.
proc WIFCONTINUED*(s: cint): bool {.importc, header: "<sys/wait.h>".}
## True if child has been continued.

View file

@ -9,7 +9,7 @@
include "system/inclrtl" include "system/inclrtl"
import os, oids, tables, strutils, times, heapqueue import os, oids, tables, strutils, times, heapqueue, options
import nativesockets, net, deques import nativesockets, net, deques
@ -385,68 +385,6 @@ when defined(windows) or defined(nimdoc):
dwRemoteAddressLength, LocalSockaddr, LocalSockaddrLength, dwRemoteAddressLength, LocalSockaddr, LocalSockaddrLength,
RemoteSockaddr, RemoteSockaddrLength) RemoteSockaddr, RemoteSockaddrLength)
proc connect*(socket: AsyncFD, address: string, port: Port,
domain = nativesockets.AF_INET): Future[void] =
## Connects ``socket`` to server at ``address:port``.
##
## Returns a ``Future`` which will complete when the connection succeeds
## or an error occurs.
verifyPresence(socket)
var retFuture = newFuture[void]("connect")
# Apparently ``ConnectEx`` expects the socket to be initially bound:
var saddr: Sockaddr_in
saddr.sin_family = int16(toInt(domain))
saddr.sin_port = 0
saddr.sin_addr.s_addr = INADDR_ANY
if bindAddr(socket.SocketHandle, cast[ptr SockAddr](addr(saddr)),
sizeof(saddr).SockLen) < 0'i32:
raiseOSError(osLastError())
var aiList = getAddrInfo(address, port, domain)
var success = false
var lastError: OSErrorCode
var it = aiList
while it != nil:
# "the OVERLAPPED structure must remain valid until the I/O completes"
# http://blogs.msdn.com/b/oldnewthing/archive/2011/02/02/10123392.aspx
var ol = PCustomOverlapped()
GC_ref(ol)
ol.data = CompletionData(fd: socket, cb:
proc (fd: AsyncFD, bytesCount: Dword, errcode: OSErrorCode) =
if not retFuture.finished:
if errcode == OSErrorCode(-1):
retFuture.complete()
else:
retFuture.fail(newException(OSError, osErrorMsg(errcode)))
)
var ret = connectEx(socket.SocketHandle, it.ai_addr,
sizeof(Sockaddr_in).cint, nil, 0, nil,
cast[POVERLAPPED](ol))
if ret:
# Request to connect completed immediately.
success = true
retFuture.complete()
# We don't deallocate ``ol`` here because even though this completed
# immediately poll will still be notified about its completion and it will
# free ``ol``.
break
else:
lastError = osLastError()
if lastError.int32 == ERROR_IO_PENDING:
# In this case ``ol`` will be deallocated in ``poll``.
success = true
break
else:
GC_unref(ol)
success = false
it = it.ai_next
freeAddrInfo(aiList)
if not success:
retFuture.fail(newException(OSError, osErrorMsg(lastError)))
return retFuture
proc recv*(socket: AsyncFD, size: int, proc recv*(socket: AsyncFD, size: int,
flags = {SocketFlag.SafeDisconn}): Future[string] = flags = {SocketFlag.SafeDisconn}): Future[string] =
## Reads **up to** ``size`` bytes from ``socket``. Returned future will ## Reads **up to** ``size`` bytes from ``socket``. Returned future will
@ -754,8 +692,8 @@ when defined(windows) or defined(nimdoc):
var lpOutputBuf = newString(lpOutputLen) var lpOutputBuf = newString(lpOutputLen)
var dwBytesReceived: Dword var dwBytesReceived: Dword
let dwReceiveDataLength = 0.Dword # We don't want any data to be read. let dwReceiveDataLength = 0.Dword # We don't want any data to be read.
let dwLocalAddressLength = Dword(sizeof(Sockaddr_in) + 16) let dwLocalAddressLength = Dword(sizeof(Sockaddr_in6) + 16)
let dwRemoteAddressLength = Dword(sizeof(Sockaddr_in) + 16) let dwRemoteAddressLength = Dword(sizeof(Sockaddr_in6) + 16)
template failAccept(errcode) = template failAccept(errcode) =
if flags.isDisconnectionError(errcode): if flags.isDisconnectionError(errcode):
@ -785,12 +723,14 @@ when defined(windows) or defined(nimdoc):
dwLocalAddressLength, dwRemoteAddressLength, dwLocalAddressLength, dwRemoteAddressLength,
addr localSockaddr, addr localLen, addr localSockaddr, addr localLen,
addr remoteSockaddr, addr remoteLen) addr remoteSockaddr, addr remoteLen)
register(clientSock.AsyncFD) try:
# TODO: IPv6. Check ``sa_family``. http://stackoverflow.com/a/9212542/492186 let address = getAddrString(remoteSockAddr)
retFuture.complete( register(clientSock.AsyncFD)
(address: $inet_ntoa(cast[ptr Sockaddr_in](remoteSockAddr).sin_addr), retFuture.complete((address: address, client: clientSock.AsyncFD))
client: clientSock.AsyncFD) except:
) # getAddrString may raise
clientSock.close()
retFuture.fail(getCurrentException())
var ol = PCustomOverlapped() var ol = PCustomOverlapped()
GC_ref(ol) GC_ref(ol)
@ -823,20 +763,6 @@ when defined(windows) or defined(nimdoc):
return retFuture return retFuture
proc newAsyncNativeSocket*(domain, sockType, protocol: cint): AsyncFD =
## Creates a new socket and registers it with the dispatcher implicitly.
result = newNativeSocket(domain, sockType, protocol).AsyncFD
result.SocketHandle.setBlocking(false)
register(result)
proc newAsyncNativeSocket*(domain: Domain = nativesockets.AF_INET,
sockType: SockType = SOCK_STREAM,
protocol: Protocol = IPPROTO_TCP): AsyncFD =
## Creates a new socket and registers it with the dispatcher implicitly.
result = newNativeSocket(domain, sockType, protocol).AsyncFD
result.SocketHandle.setBlocking(false)
register(result)
proc closeSocket*(socket: AsyncFD) = proc closeSocket*(socket: AsyncFD) =
## Closes a socket and ensures that it is unregistered. ## Closes a socket and ensures that it is unregistered.
socket.SocketHandle.close() socket.SocketHandle.close()
@ -1015,23 +941,6 @@ else:
var data = PData(fd: fd, readCBs: @[], writeCBs: @[]) var data = PData(fd: fd, readCBs: @[], writeCBs: @[])
p.selector.register(fd.SocketHandle, {}, data.RootRef) p.selector.register(fd.SocketHandle, {}, data.RootRef)
proc newAsyncNativeSocket*(domain: cint, sockType: cint,
protocol: cint): AsyncFD =
result = newNativeSocket(domain, sockType, protocol).AsyncFD
result.SocketHandle.setBlocking(false)
when defined(macosx):
result.SocketHandle.setSockOptInt(SOL_SOCKET, SO_NOSIGPIPE, 1)
register(result)
proc newAsyncNativeSocket*(domain: Domain = AF_INET,
sockType: SockType = SOCK_STREAM,
protocol: Protocol = IPPROTO_TCP): AsyncFD =
result = newNativeSocket(domain, sockType, protocol).AsyncFD
result.SocketHandle.setBlocking(false)
when defined(macosx):
result.SocketHandle.setSockOptInt(SOL_SOCKET, SO_NOSIGPIPE, 1)
register(result)
proc closeSocket*(sock: AsyncFD) = proc closeSocket*(sock: AsyncFD) =
let disp = getGlobalDispatcher() let disp = getGlobalDispatcher()
disp.selector.unregister(sock.SocketHandle) disp.selector.unregister(sock.SocketHandle)
@ -1115,50 +1024,6 @@ else:
# Callback queue processing # Callback queue processing
processPendingCallbacks(p) processPendingCallbacks(p)
proc connect*(socket: AsyncFD, address: string, port: Port,
domain = AF_INET): Future[void] =
var retFuture = newFuture[void]("connect")
proc cb(fd: AsyncFD): bool =
var ret = SocketHandle(fd).getSockOptInt(cint(SOL_SOCKET), cint(SO_ERROR))
if ret == 0:
# We have connected.
retFuture.complete()
return true
elif ret == EINTR:
# interrupted, keep waiting
return false
else:
retFuture.fail(newException(OSError, osErrorMsg(OSErrorCode(ret))))
return true
assert getSockDomain(socket.SocketHandle) == domain
var aiList = getAddrInfo(address, port, domain)
var success = false
var lastError: OSErrorCode
var it = aiList
while it != nil:
var ret = connect(socket.SocketHandle, it.ai_addr, it.ai_addrlen.Socklen)
if ret == 0:
# Request to connect completed immediately.
success = true
retFuture.complete()
break
else:
lastError = osLastError()
if lastError.int32 == EINTR or lastError.int32 == EINPROGRESS:
success = true
addWrite(socket, cb)
break
else:
success = false
it = it.ai_next
freeAddrInfo(aiList)
if not success:
retFuture.fail(newException(OSError, osErrorMsg(lastError)))
return retFuture
proc recv*(socket: AsyncFD, size: int, proc recv*(socket: AsyncFD, size: int,
flags = {SocketFlag.SafeDisconn}): Future[string] = flags = {SocketFlag.SafeDisconn}): Future[string] =
var retFuture = newFuture[string]("recv") var retFuture = newFuture[string]("recv")
@ -1320,11 +1185,20 @@ else:
else: else:
retFuture.fail(newException(OSError, osErrorMsg(lastError))) retFuture.fail(newException(OSError, osErrorMsg(lastError)))
else: else:
register(client.AsyncFD) try:
retFuture.complete((getAddrString(cast[ptr SockAddr](addr sockAddress)), client.AsyncFD)) let address = getAddrString(cast[ptr SockAddr](addr sockAddress))
register(client.AsyncFD)
retFuture.complete((address, client.AsyncFD))
except:
# getAddrString may raise
client.close()
retFuture.fail(getCurrentException())
addRead(socket, cb) addRead(socket, cb)
return retFuture return retFuture
# Common procedures between current and upcoming asyncdispatch
include includes.asynccommon
proc sleepAsync*(ms: int): Future[void] = proc sleepAsync*(ms: int): Future[void] =
## Suspends the execution of the current async procedure for the next ## Suspends the execution of the current async procedure for the next
## ``ms`` milliseconds. ## ``ms`` milliseconds.

View file

@ -244,6 +244,17 @@ when defineSsl:
else: else:
raiseSSLError("Socket has been disconnected") raiseSSLError("Socket has been disconnected")
proc dial*(address: string, port: Port, protocol = IPPROTO_TCP,
buffered = true): Future[AsyncSocket] {.async.} =
## Establishes connection to the specified ``address``:``port`` pair via the
## specified protocol. The procedure iterates through possible
## resolutions of the ``address`` until it succeeds, meaning that it
## seamlessly works with both IPv4 and IPv6.
## Returns AsyncSocket ready to send or receive data.
let asyncFd = await asyncdispatch.dial(address, port, protocol)
let sockType = protocol.toSockType()
let domain = getSockDomain(asyncFd.SocketHandle)
result = newAsyncSocket(asyncFd, domain, sockType, protocol, buffered)
proc connect*(socket: AsyncSocket, address: string, port: Port) {.async.} = proc connect*(socket: AsyncSocket, address: string, port: Port) {.async.} =
## Connects ``socket`` to server at ``address:port``. ## Connects ``socket`` to server at ``address:port``.

View file

@ -1033,32 +1033,38 @@ proc newConnection(client: HttpClient | AsyncHttpClient,
if client.currentURL.hostname != url.hostname or if client.currentURL.hostname != url.hostname or
client.currentURL.scheme != url.scheme or client.currentURL.scheme != url.scheme or
client.currentURL.port != url.port: client.currentURL.port != url.port:
let isSsl = url.scheme.toLowerAscii() == "https"
if isSsl and not defined(ssl):
raise newException(HttpRequestError,
"SSL support is not available. Cannot connect over SSL.")
if client.connected: if client.connected:
client.close() client.close()
when client is HttpClient:
client.socket = newSocket()
elif client is AsyncHttpClient:
client.socket = newAsyncSocket()
else: {.fatal: "Unsupported client type".}
# TODO: I should be able to write 'net.Port' here... # TODO: I should be able to write 'net.Port' here...
let port = let port =
if url.port == "": if url.port == "":
if url.scheme.toLower() == "https": if isSsl:
nativesockets.Port(443) nativesockets.Port(443)
else: else:
nativesockets.Port(80) nativesockets.Port(80)
else: nativesockets.Port(url.port.parseInt) else: nativesockets.Port(url.port.parseInt)
if url.scheme.toLower() == "https": when client is HttpClient:
when defined(ssl): client.socket = await net.dial(url.hostname, port)
client.sslContext.wrapSocket(client.socket) elif client is AsyncHttpClient:
else: client.socket = await asyncnet.dial(url.hostname, port)
raise newException(HttpRequestError, else: {.fatal: "Unsupported client type".}
"SSL support is not available. Cannot connect over SSL.")
when defined(ssl):
if isSsl:
try:
client.sslContext.wrapConnectedSocket(client.socket, handshakeAsClient)
except:
client.socket.close()
raise getCurrentException()
await client.socket.connect(url.hostname, port)
client.currentURL = url client.currentURL = url
client.connected = true client.connected = true

View file

@ -0,0 +1,201 @@
template newAsyncNativeSocketImpl(domain, sockType, protocol) =
let handle = newNativeSocket(domain, sockType, protocol)
if handle == osInvalidSocket:
raiseOSError(osLastError())
handle.setBlocking(false)
when defined(macosx):
handle.setSockOptInt(SOL_SOCKET, SO_NOSIGPIPE, 1)
result = handle.AsyncFD
register(result)
proc newAsyncNativeSocket*(domain: cint, sockType: cint,
protocol: cint): AsyncFD =
newAsyncNativeSocketImpl(domain, sockType, protocol)
proc newAsyncNativeSocket*(domain: Domain = Domain.AF_INET,
sockType: SockType = SOCK_STREAM,
protocol: Protocol = IPPROTO_TCP): AsyncFD =
newAsyncNativeSocketImpl(domain, sockType, protocol)
when defined(windows) or defined(nimdoc):
proc bindToDomain(handle: SocketHandle, domain: Domain) =
# Extracted into a separate proc, because connect() on Windows requires
# the socket to be initially bound.
template doBind(saddr) =
if bindAddr(handle, cast[ptr SockAddr](addr(saddr)),
sizeof(saddr).SockLen) < 0'i32:
raiseOSError(osLastError())
if domain == Domain.AF_INET6:
var saddr: Sockaddr_in6
saddr.sin6_family = int16(toInt(domain))
doBind(saddr)
else:
var saddr: Sockaddr_in
saddr.sin_family = int16(toInt(domain))
doBind(saddr)
proc doConnect(socket: AsyncFD, addrInfo: ptr AddrInfo): Future[void] =
let retFuture = newFuture[void]("doConnect")
result = retFuture
var ol = PCustomOverlapped()
GC_ref(ol)
ol.data = CompletionData(fd: socket, cb:
proc (fd: AsyncFD, bytesCount: Dword, errcode: OSErrorCode) =
if not retFuture.finished:
if errcode == OSErrorCode(-1):
retFuture.complete()
else:
retFuture.fail(newException(OSError, osErrorMsg(errcode)))
)
let ret = connectEx(socket.SocketHandle, addrInfo.ai_addr,
cint(addrInfo.ai_addrlen), nil, 0, nil,
cast[POVERLAPPED](ol))
if ret:
# Request to connect completed immediately.
retFuture.complete()
# We don't deallocate ``ol`` here because even though this completed
# immediately poll will still be notified about its completion and it
# will free ``ol``.
else:
let lastError = osLastError()
if lastError.int32 != ERROR_IO_PENDING:
# With ERROR_IO_PENDING ``ol`` will be deallocated in ``poll``,
# and the future will be completed/failed there, too.
GC_unref(ol)
retFuture.fail(newException(OSError, osErrorMsg(lastError)))
else:
proc doConnect(socket: AsyncFD, addrInfo: ptr AddrInfo): Future[void] =
let retFuture = newFuture[void]("doConnect")
result = retFuture
proc cb(fd: AsyncFD): bool =
let ret = SocketHandle(fd).getSockOptInt(
cint(SOL_SOCKET), cint(SO_ERROR))
if ret == 0:
# We have connected.
retFuture.complete()
return true
elif ret == EINTR:
# interrupted, keep waiting
return false
else:
retFuture.fail(newException(OSError, osErrorMsg(OSErrorCode(ret))))
return true
let ret = connect(socket.SocketHandle,
addrInfo.ai_addr,
addrInfo.ai_addrlen.Socklen)
if ret == 0:
# Request to connect completed immediately.
retFuture.complete()
else:
let lastError = osLastError()
if lastError.int32 == EINTR or lastError.int32 == EINPROGRESS:
addWrite(socket, cb)
else:
retFuture.fail(newException(OSError, osErrorMsg(lastError)))
template asyncAddrInfoLoop(addrInfo: ptr AddrInfo, fd: untyped,
protocol: Protocol = IPPROTO_RAW) =
## Iterates through the AddrInfo linked list asynchronously
## until the connection can be established.
const shouldCreateFd = not declared(fd)
when shouldCreateFd:
let sockType = protocol.toSockType()
var fdPerDomain: array[low(Domain).ord..high(Domain).ord, AsyncFD]
for i in low(fdPerDomain)..high(fdPerDomain):
fdPerDomain[i] = osInvalidSocket.AsyncFD
template closeUnusedFds(domainToKeep = -1) {.dirty.} =
for i, fd in fdPerDomain:
if fd != osInvalidSocket.AsyncFD and i != domainToKeep:
fd.closeSocket()
var lastException: ref Exception
var curAddrInfo = addrInfo
var domain: Domain
when shouldCreateFd:
var curFd: AsyncFD
else:
var curFd = fd
proc tryNextAddrInfo(fut: Future[void]) {.gcsafe.} =
if fut == nil or fut.failed:
if fut != nil:
lastException = fut.readError()
while curAddrInfo != nil:
let domainOpt = curAddrInfo.ai_family.toKnownDomain()
if domainOpt.isSome:
domain = domainOpt.unsafeGet()
break
curAddrInfo = curAddrInfo.ai_next
if curAddrInfo == nil:
freeAddrInfo(addrInfo)
when shouldCreateFd:
closeUnusedFds()
if lastException != nil:
retFuture.fail(lastException)
else:
retFuture.fail(newException(
IOError, "Couldn't resolve address: " & address))
return
when shouldCreateFd:
curFd = fdPerDomain[ord(domain)]
if curFd == osInvalidSocket.AsyncFD:
try:
curFd = newAsyncNativeSocket(domain, sockType, protocol)
except:
freeAddrInfo(addrInfo)
closeUnusedFds()
raise getCurrentException()
when defined(windows):
curFd.SocketHandle.bindToDomain(domain)
fdPerDomain[ord(domain)] = curFd
doConnect(curFd, curAddrInfo).callback = tryNextAddrInfo
curAddrInfo = curAddrInfo.ai_next
else:
freeAddrInfo(addrInfo)
when shouldCreateFd:
closeUnusedFds(ord(domain))
retFuture.complete(curFd)
else:
retFuture.complete()
tryNextAddrInfo(nil)
proc dial*(address: string, port: Port,
protocol: Protocol = IPPROTO_TCP): Future[AsyncFD] =
## Establishes connection to the specified ``address``:``port`` pair via the
## specified protocol. The procedure iterates through possible
## resolutions of the ``address`` until it succeeds, meaning that it
## seamlessly works with both IPv4 and IPv6.
## Returns the async file descriptor, registered in the dispatcher of
## the current thread, ready to send or receive data.
let retFuture = newFuture[AsyncFD]("dial")
result = retFuture
let sockType = protocol.toSockType()
let aiList = getAddrInfo(address, port, Domain.AF_UNSPEC, sockType, protocol)
asyncAddrInfoLoop(aiList, noFD, protocol)
proc connect*(socket: AsyncFD, address: string, port: Port,
domain = Domain.AF_INET): Future[void] =
let retFuture = newFuture[void]("connect")
result = retFuture
when defined(windows):
verifyPresence(socket)
else:
assert getSockDomain(socket.SocketHandle) == domain
let aiList = getAddrInfo(address, port, domain)
when defined(windows):
socket.SocketHandle.bindToDomain(domain)
asyncAddrInfoLoop(aiList, socket)

View file

@ -1213,22 +1213,22 @@ when not defined(js):
proc parseJson*(s: Stream, filename: string): JsonNode = proc parseJson*(s: Stream, filename: string): JsonNode =
## Parses from a stream `s` into a `JsonNode`. `filename` is only needed ## Parses from a stream `s` into a `JsonNode`. `filename` is only needed
## for nice error messages. ## for nice error messages.
## If `s` contains extra data, it will raising `JsonParsingError`. ## If `s` contains extra data, it will raise `JsonParsingError`.
var p: JsonParser var p: JsonParser
p.open(s, filename) p.open(s, filename)
defer: p.close() defer: p.close()
discard getTok(p) # read first token discard getTok(p) # read first token
result = p.parseJson() result = p.parseJson()
eat(p, tkEof) # check there are no exstra data eat(p, tkEof) # check if there is no extra data
proc parseJson*(buffer: string): JsonNode = proc parseJson*(buffer: string): JsonNode =
## Parses JSON from `buffer`. ## Parses JSON from `buffer`.
## If `buffer` contains extra data, it will raising `JsonParsingError`. ## If `buffer` contains extra data, it will raise `JsonParsingError`.
result = parseJson(newStringStream(buffer), "input") result = parseJson(newStringStream(buffer), "input")
proc parseFile*(filename: string): JsonNode = proc parseFile*(filename: string): JsonNode =
## Parses `file` into a `JsonNode`. ## Parses `file` into a `JsonNode`.
## If `file` contains extra data, it will raising `JsonParsingError`. ## If `file` contains extra data, it will raise `JsonParsingError`.
var stream = newFileStream(filename, fmRead) var stream = newFileStream(filename, fmRead)
if stream == nil: if stream == nil:
raise newException(IOError, "cannot read from file: " & filename) raise newException(IOError, "cannot read from file: " & filename)

View file

@ -12,7 +12,7 @@
# TODO: Clean up the exports a bit and everything else in general. # TODO: Clean up the exports a bit and everything else in general.
import os import os, options
when hostOS == "solaris": when hostOS == "solaris":
{.passl: "-lsocket -lnsl".} {.passl: "-lsocket -lnsl".}
@ -52,9 +52,11 @@ type
Domain* = enum ## domain, which specifies the protocol family of the Domain* = enum ## domain, which specifies the protocol family of the
## created socket. Other domains than those that are listed ## created socket. Other domains than those that are listed
## here are unsupported. ## here are unsupported.
AF_UNIX, ## for local socket (using a file). Unsupported on Windows. AF_UNSPEC = 0, ## unspecified domain (can be detected automatically by
## some procedures, such as getaddrinfo)
AF_UNIX = 1, ## for local socket (using a file). Unsupported on Windows.
AF_INET = 2, ## for network protocol IPv4 or AF_INET = 2, ## for network protocol IPv4 or
AF_INET6 = 23 ## for network protocol IPv6. AF_INET6 = when defined(macosx): 30 else: 23 ## for network protocol IPv6.
SockType* = enum ## second argument to `socket` proc SockType* = enum ## second argument to `socket` proc
SOCK_STREAM = 1, ## reliable stream-oriented service or Stream Sockets SOCK_STREAM = 1, ## reliable stream-oriented service or Stream Sockets
@ -125,10 +127,19 @@ proc toInt*(p: Protocol): cint
when not useWinVersion: when not useWinVersion:
proc toInt(domain: Domain): cshort = proc toInt(domain: Domain): cshort =
case domain case domain
of AF_UNSPEC: result = posix.AF_UNSPEC.cshort
of AF_UNIX: result = posix.AF_UNIX.cshort of AF_UNIX: result = posix.AF_UNIX.cshort
of AF_INET: result = posix.AF_INET.cshort of AF_INET: result = posix.AF_INET.cshort
of AF_INET6: result = posix.AF_INET6.cshort of AF_INET6: result = posix.AF_INET6.cshort
else: discard
proc toKnownDomain*(family: cint): Option[Domain] =
## Converts the platform-dependent ``cint`` to the Domain or none(),
## if the ``cint`` is not known.
result = if family == posix.AF_UNSPEC: some(Domain.AF_UNSPEC)
elif family == posix.AF_UNIX: some(Domain.AF_UNIX)
elif family == posix.AF_INET: some(Domain.AF_INET)
elif family == posix.AF_INET6: some(Domain.AF_INET6)
else: none(Domain)
proc toInt(typ: SockType): cint = proc toInt(typ: SockType): cint =
case typ case typ
@ -136,7 +147,6 @@ when not useWinVersion:
of SOCK_DGRAM: result = posix.SOCK_DGRAM of SOCK_DGRAM: result = posix.SOCK_DGRAM
of SOCK_SEQPACKET: result = posix.SOCK_SEQPACKET of SOCK_SEQPACKET: result = posix.SOCK_SEQPACKET
of SOCK_RAW: result = posix.SOCK_RAW of SOCK_RAW: result = posix.SOCK_RAW
else: discard
proc toInt(p: Protocol): cint = proc toInt(p: Protocol): cint =
case p case p
@ -146,18 +156,33 @@ when not useWinVersion:
of IPPROTO_IPV6: result = posix.IPPROTO_IPV6 of IPPROTO_IPV6: result = posix.IPPROTO_IPV6
of IPPROTO_RAW: result = posix.IPPROTO_RAW of IPPROTO_RAW: result = posix.IPPROTO_RAW
of IPPROTO_ICMP: result = posix.IPPROTO_ICMP of IPPROTO_ICMP: result = posix.IPPROTO_ICMP
else: discard
else: else:
proc toInt(domain: Domain): cshort = proc toInt(domain: Domain): cshort =
result = toU16(ord(domain)) result = toU16(ord(domain))
proc toKnownDomain*(family: cint): Option[Domain] =
## Converts the platform-dependent ``cint`` to the Domain or none(),
## if the ``cint`` is not known.
result = if family == winlean.AF_UNSPEC: some(Domain.AF_UNSPEC)
elif family == winlean.AF_INET: some(Domain.AF_INET)
elif family == winlean.AF_INET6: some(Domain.AF_INET6)
else: none(Domain)
proc toInt(typ: SockType): cint = proc toInt(typ: SockType): cint =
result = cint(ord(typ)) result = cint(ord(typ))
proc toInt(p: Protocol): cint = proc toInt(p: Protocol): cint =
result = cint(ord(p)) result = cint(ord(p))
proc toSockType*(protocol: Protocol): SockType =
result = case protocol
of IPPROTO_TCP:
SOCK_STREAM
of IPPROTO_UDP:
SOCK_DGRAM
of IPPROTO_IP, IPPROTO_IPV6, IPPROTO_RAW, IPPROTO_ICMP:
SOCK_RAW
proc newNativeSocket*(domain: Domain = AF_INET, proc newNativeSocket*(domain: Domain = AF_INET,
sockType: SockType = SOCK_STREAM, sockType: SockType = SOCK_STREAM,
@ -392,14 +417,14 @@ proc getHostname*(): string {.tags: [ReadIOEffect].} =
proc getSockDomain*(socket: SocketHandle): Domain = proc getSockDomain*(socket: SocketHandle): Domain =
## returns the socket's domain (AF_INET or AF_INET6). ## returns the socket's domain (AF_INET or AF_INET6).
var name: SockAddr var name: Sockaddr_in6
var namelen = sizeof(name).SockLen var namelen = sizeof(name).SockLen
if getsockname(socket, cast[ptr SockAddr](addr(name)), if getsockname(socket, cast[ptr SockAddr](addr(name)),
addr(namelen)) == -1'i32: addr(namelen)) == -1'i32:
raiseOSError(osLastError()) raiseOSError(osLastError())
if name.sa_family == nativeAfInet: if name.sin6_family == nativeAfInet:
result = AF_INET result = AF_INET
elif name.sa_family == nativeAfInet6: elif name.sin6_family == nativeAfInet6:
result = AF_INET6 result = AF_INET6
else: else:
raiseOSError(osLastError(), "unknown socket family in getSockFamily") raiseOSError(osLastError(), "unknown socket family in getSockFamily")
@ -410,17 +435,23 @@ proc getAddrString*(sockAddr: ptr SockAddr): string =
if sockAddr.sa_family == nativeAfInet: if sockAddr.sa_family == nativeAfInet:
result = $inet_ntoa(cast[ptr Sockaddr_in](sockAddr).sin_addr) result = $inet_ntoa(cast[ptr Sockaddr_in](sockAddr).sin_addr)
elif sockAddr.sa_family == nativeAfInet6: elif sockAddr.sa_family == nativeAfInet6:
let addrLen = when not useWinVersion: posix.INET6_ADDRSTRLEN
else: 46 # it's actually 46 in both cases
result = newString(addrLen)
let addr6 = addr cast[ptr Sockaddr_in6](sockAddr).sin6_addr
when not useWinVersion: when not useWinVersion:
# TODO: Windows if posix.inet_ntop(posix.AF_INET6, addr6, addr result[0],
result = newString(posix.INET6_ADDRSTRLEN) result.len.int32) == nil:
let addr6 = addr cast[ptr Sockaddr_in6](sockAddr).sin6_addr raiseOSError(osLastError())
discard posix.inet_ntop(posix.AF_INET6, addr6, result.cstring,
result.len.int32)
if posix.IN6_IS_ADDR_V4MAPPED(addr6) != 0: if posix.IN6_IS_ADDR_V4MAPPED(addr6) != 0:
result = result.substr("::ffff:".len) result = result.substr("::ffff:".len)
else:
if winlean.inet_ntop(winlean.AF_INET6, addr6, addr result[0],
result.len.int32) == nil:
raiseOSError(osLastError())
setLen(result, len(cstring(result)))
else: else:
raiseOSError(osLastError(), "unknown socket family in getAddrString") raise newException(IOError, "Unknown socket family in getAddrString")
proc getSockName*(socket: SocketHandle): Port = proc getSockName*(socket: SocketHandle): Port =
## returns the socket's associated port number. ## returns the socket's associated port number.

View file

@ -66,7 +66,7 @@
## ##
{.deadCodeElim: on.} {.deadCodeElim: on.}
import nativesockets, os, strutils, parseutils, times, sets import nativesockets, os, strutils, parseutils, times, sets, options
export Port, `$`, `==` export Port, `$`, `==`
export Domain, SockType, Protocol export Domain, SockType, Protocol
@ -669,7 +669,7 @@ proc close*(socket: Socket) =
## Closes a socket. ## Closes a socket.
try: try:
when defineSsl: when defineSsl:
if socket.isSSL: if socket.isSSL and socket.sslHandle != nil:
ErrClearError() ErrClearError()
# As we are closing the underlying socket immediately afterwards, # As we are closing the underlying socket immediately afterwards,
# it is valid, under the TLS standard, to perform a unidirectional # it is valid, under the TLS standard, to perform a unidirectional
@ -1477,6 +1477,63 @@ proc isIpAddress*(address_str: string): bool {.tags: [].} =
return false return false
return true return true
proc dial*(address: string, port: Port,
protocol = IPPROTO_TCP, buffered = true): Socket
{.tags: [ReadIOEffect, WriteIOEffect].} =
## Establishes connection to the specified ``address``:``port`` pair via the
## specified protocol. The procedure iterates through possible
## resolutions of the ``address`` until it succeeds, meaning that it
## seamlessly works with both IPv4 and IPv6.
## Returns Socket ready to send or receive data.
let sockType = protocol.toSockType()
let aiList = getAddrInfo(address, port, AF_UNSPEC, sockType, protocol)
var fdPerDomain: array[low(Domain).ord..high(Domain).ord, SocketHandle]
for i in low(fdPerDomain)..high(fdPerDomain):
fdPerDomain[i] = osInvalidSocket
template closeUnusedFds(domainToKeep = -1) {.dirty.} =
for i, fd in fdPerDomain:
if fd != osInvalidSocket and i != domainToKeep:
fd.close()
var success = false
var lastError: OSErrorCode
var it = aiList
var domain: Domain
var lastFd: SocketHandle
while it != nil:
let domainOpt = it.ai_family.toKnownDomain()
if domainOpt.isNone:
it = it.ai_next
continue
domain = domainOpt.unsafeGet()
lastFd = fdPerDomain[ord(domain)]
if lastFd == osInvalidSocket:
lastFd = newNativeSocket(domain, sockType, protocol)
if lastFd == osInvalidSocket:
# we always raise if socket creation failed, because it means a
# network system problem (e.g. not enough FDs), and not an unreachable
# address.
let err = osLastError()
freeAddrInfo(aiList)
closeUnusedFds()
raiseOSError(err)
fdPerDomain[ord(domain)] = lastFd
if connect(lastFd, it.ai_addr, it.ai_addrlen.SockLen) == 0'i32:
success = true
break
lastError = osLastError()
it = it.ai_next
freeAddrInfo(aiList)
closeUnusedFds(ord(domain))
if success:
result = newSocket(lastFd, domain, sockType, protocol)
elif lastError != 0.OSErrorCode:
raiseOSError(lastError)
else:
raise newException(IOError, "Couldn't resolve address: " & address)
proc connect*(socket: Socket, address: string, proc connect*(socket: Socket, address: string,
port = Port(0)) {.tags: [ReadIOEffect].} = port = Port(0)) {.tags: [ReadIOEffect].} =

View file

@ -569,7 +569,7 @@ when declared(getEnv) or defined(nimscript):
## ``["exe", "cmd", "bat"]``, on Posix ``[""]``. ## ``["exe", "cmd", "bat"]``, on Posix ``[""]``.
proc findExe*(exe: string, followSymlinks: bool = true; proc findExe*(exe: string, followSymlinks: bool = true;
extensions=ExeExts): string {. extensions: openarray[string]=ExeExts): string {.
tags: [ReadDirEffect, ReadEnvEffect, ReadIOEffect].} = tags: [ReadDirEffect, ReadEnvEffect, ReadIOEffect].} =
## Searches for `exe` in the current working directory and then ## Searches for `exe` in the current working directory and then
## in directories listed in the ``PATH`` environment variable. ## in directories listed in the ``PATH`` environment variable.

View file

@ -209,9 +209,16 @@ proc waitForExit*(p: Process, timeout: int = -1): int {.rtl,
## ##
## **Warning**: Be careful when using waitForExit for processes created without ## **Warning**: Be careful when using waitForExit for processes created without
## poParentStreams because they may fill output buffers, causing deadlock. ## poParentStreams because they may fill output buffers, causing deadlock.
##
## On posix, if the process has exited because of a signal, 128 + signal
## number will be returned.
proc peekExitCode*(p: Process): int {.tags: [].} proc peekExitCode*(p: Process): int {.tags: [].}
## return -1 if the process is still running. Otherwise the process' exit code ## return -1 if the process is still running. Otherwise the process' exit code
##
## On posix, if the process has exited because of a signal, 128 + signal
## number will be returned.
proc inputStream*(p: Process): Stream {.rtl, extern: "nosp$1", tags: [].} proc inputStream*(p: Process): Stream {.rtl, extern: "nosp$1", tags: [].}
## returns ``p``'s input stream for writing to. ## returns ``p``'s input stream for writing to.
@ -679,6 +686,16 @@ elif not defined(useNimRtl):
readIdx = 0 readIdx = 0
writeIdx = 1 writeIdx = 1
proc isExitStatus(status: cint): bool =
WIFEXITED(status) or WIFSIGNALED(status)
proc exitStatus(status: cint): cint =
if WIFSIGNALED(status):
# like the shell!
128 + WTERMSIG(status)
else:
WEXITSTATUS(status)
proc envToCStringArray(t: StringTableRef): cstringArray = proc envToCStringArray(t: StringTableRef): cstringArray =
result = cast[cstringArray](alloc0((t.len + 1) * sizeof(cstring))) result = cast[cstringArray](alloc0((t.len + 1) * sizeof(cstring)))
var i = 0 var i = 0
@ -967,7 +984,7 @@ elif not defined(useNimRtl):
var status : cint = 1 var status : cint = 1
ret = waitpid(p.id, status, WNOHANG) ret = waitpid(p.id, status, WNOHANG)
if ret == int(p.id): if ret == int(p.id):
if WIFEXITED(status): if isExitStatus(status):
p.exitStatus = status p.exitStatus = status
return false return false
else: else:
@ -990,7 +1007,9 @@ elif not defined(useNimRtl):
import kqueue, times import kqueue, times
proc waitForExit(p: Process, timeout: int = -1): int = proc waitForExit(p: Process, timeout: int = -1): int =
if p.exitStatus != -3: return((p.exitStatus and 0xFF00) shr 8) if p.exitStatus != -3:
return exitStatus(p.exitStatus)
if timeout == -1: if timeout == -1:
var status : cint = 1 var status : cint = 1
if waitpid(p.id, status, 0) < 0: if waitpid(p.id, status, 0) < 0:
@ -1041,7 +1060,7 @@ elif not defined(useNimRtl):
finally: finally:
discard posix.close(kqFD) discard posix.close(kqFD)
result = ((p.exitStatus and 0xFF00) shr 8) result = exitStatus(p.exitStatus)
else: else:
import times import times
@ -1077,7 +1096,9 @@ elif not defined(useNimRtl):
# ``waitPid`` fails if the process is not running anymore. But then # ``waitPid`` fails if the process is not running anymore. But then
# ``running`` probably set ``p.exitStatus`` for us. Since ``p.exitStatus`` is # ``running`` probably set ``p.exitStatus`` for us. Since ``p.exitStatus`` is
# initialized with -3, wrong success exit codes are prevented. # initialized with -3, wrong success exit codes are prevented.
if p.exitStatus != -3: return((p.exitStatus and 0xFF00) shr 8) if p.exitStatus != -3:
return exitStatus(p.exitStatus)
if timeout == -1: if timeout == -1:
var status : cint = 1 var status : cint = 1
if waitpid(p.id, status, 0) < 0: if waitpid(p.id, status, 0) < 0:
@ -1151,17 +1172,19 @@ elif not defined(useNimRtl):
if sigprocmask(SIG_UNBLOCK, nmask, omask) == -1: if sigprocmask(SIG_UNBLOCK, nmask, omask) == -1:
raiseOSError(osLastError()) raiseOSError(osLastError())
result = ((p.exitStatus and 0xFF00) shr 8) result = exitStatus(p.exitStatus)
proc peekExitCode(p: Process): int = proc peekExitCode(p: Process): int =
var status = cint(0) var status = cint(0)
result = -1 result = -1
if p.exitStatus != -3: return((p.exitStatus and 0xFF00) shr 8) if p.exitStatus != -3:
return exitStatus(p.exitStatus)
var ret = waitpid(p.id, status, WNOHANG) var ret = waitpid(p.id, status, WNOHANG)
if ret > 0: if ret > 0:
if WIFEXITED(status): if isExitStatus(status):
p.exitStatus = status p.exitStatus = status
result = (status and 0xFF00) shr 8 result = exitStatus(status)
proc createStream(stream: var Stream, handle: var FileHandle, proc createStream(stream: var Stream, handle: var FileHandle,
fileMode: FileMode) = fileMode: FileMode) =
@ -1189,7 +1212,8 @@ elif not defined(useNimRtl):
proc execCmd(command: string): int = proc execCmd(command: string): int =
when defined(linux): when defined(linux):
result = csystem(command) shr 8 let tmp = csystem(command)
result = if tmp == -1: tmp else: exitStatus(tmp)
else: else:
result = csystem(command) result = csystem(command)

View file

@ -50,6 +50,7 @@ proc add*(url: var Url, a: Url) {.deprecated.} =
proc parseAuthority(authority: string, result: var Uri) = proc parseAuthority(authority: string, result: var Uri) =
var i = 0 var i = 0
var inPort = false var inPort = false
var inIPv6 = false
while true: while true:
case authority[i] case authority[i]
of '@': of '@':
@ -59,7 +60,14 @@ proc parseAuthority(authority: string, result: var Uri) =
result.hostname.setLen(0) result.hostname.setLen(0)
inPort = false inPort = false
of ':': of ':':
inPort = true if inIPv6:
result.hostname.add(authority[i])
else:
inPort = true
of '[':
inIPv6 = true
of ']':
inIPv6 = false
of '\0': break of '\0': break
else: else:
if inPort: if inPort:
@ -345,6 +353,17 @@ when isMainModule:
doAssert test.anchor == "nose" doAssert test.anchor == "nose"
doAssert($test == str) doAssert($test == str)
block:
# IPv6 address
let str = "foo://[::1]:1234/bar?baz=true&qux#quux"
let uri = parseUri(str)
doAssert uri.scheme == "foo"
doAssert uri.hostname == "::1"
doAssert uri.port == "1234"
doAssert uri.path == "/bar"
doAssert uri.query == "baz=true&qux"
doAssert uri.anchor == "quux"
block: block:
let str = "urn:example:animal:ferret:nose" let str = "urn:example:animal:ferret:nose"
let test = parseUri(str) let test = parseUri(str)

View file

@ -2016,6 +2016,12 @@ proc min*(x, y: float): float {.magic: "MinF64", noSideEffect.} =
if x <= y: x else: y if x <= y: x else: y
proc max*(x, y: float): float {.magic: "MaxF64", noSideEffect.} = proc max*(x, y: float): float {.magic: "MaxF64", noSideEffect.} =
if y <= x: x else: y if y <= x: x else: y
proc min*[T](x, y: T): T =
if x <= y: x else: y
proc max*[T](x, y: T): T =
if y <= x: x else: y
{.pop.} {.pop.}
proc clamp*[T](x, a, b: T): T = proc clamp*[T](x, a, b: T): T =

View file

@ -9,7 +9,7 @@
include "system/inclrtl" include "system/inclrtl"
import os, oids, tables, strutils, times, heapqueue, lists import os, oids, tables, strutils, times, heapqueue, lists, options
import nativesockets, net, deques import nativesockets, net, deques
@ -325,68 +325,6 @@ when defined(windows) or defined(nimdoc):
getAcceptExSockAddrs = cast[WSAPROC_GETACCEPTEXSOCKADDRS](fun) getAcceptExSockAddrs = cast[WSAPROC_GETACCEPTEXSOCKADDRS](fun)
close(dummySock) close(dummySock)
proc connect*(socket: AsyncFD, address: string, port: Port,
domain = nativesockets.AF_INET): Future[void] =
## Connects ``socket`` to server at ``address:port``.
##
## Returns a ``Future`` which will complete when the connection succeeds
## or an error occurs.
verifyPresence(socket)
var retFuture = newFuture[void]("connect")
# Apparently ``ConnectEx`` expects the socket to be initially bound:
var saddr: Sockaddr_in
saddr.sin_family = int16(toInt(domain))
saddr.sin_port = 0
saddr.sin_addr.s_addr = INADDR_ANY
if bindAddr(socket.SocketHandle, cast[ptr SockAddr](addr(saddr)),
sizeof(saddr).SockLen) < 0'i32:
raiseOSError(osLastError())
var aiList = getAddrInfo(address, port, domain)
var success = false
var lastError: OSErrorCode
var it = aiList
while it != nil:
# "the OVERLAPPED structure must remain valid until the I/O completes"
# http://blogs.msdn.com/b/oldnewthing/archive/2011/02/02/10123392.aspx
var ol = PCustomOverlapped()
GC_ref(ol)
ol.data = CompletionData(fd: socket, cb:
proc (fd: AsyncFD, bytesCount: Dword, errcode: OSErrorCode) =
if not retFuture.finished:
if errcode == OSErrorCode(-1):
retFuture.complete()
else:
retFuture.fail(newException(OSError, osErrorMsg(errcode)))
)
var ret = connectEx(socket.SocketHandle, it.ai_addr,
sizeof(Sockaddr_in).cint, nil, 0, nil,
cast[POVERLAPPED](ol))
if ret:
# Request to connect completed immediately.
success = true
retFuture.complete()
# We don't deallocate ``ol`` here because even though this completed
# immediately poll will still be notified about its completion and it will
# free ``ol``.
break
else:
lastError = osLastError()
if lastError.int32 == ERROR_IO_PENDING:
# In this case ``ol`` will be deallocated in ``poll``.
success = true
break
else:
GC_unref(ol)
success = false
it = it.ai_next
freeAddrInfo(aiList)
if not success:
retFuture.fail(newException(OSError, osErrorMsg(lastError)))
return retFuture
proc recv*(socket: AsyncFD, size: int, proc recv*(socket: AsyncFD, size: int,
flags = {SocketFlag.SafeDisconn}): Future[string] = flags = {SocketFlag.SafeDisconn}): Future[string] =
## Reads **up to** ``size`` bytes from ``socket``. Returned future will ## Reads **up to** ``size`` bytes from ``socket``. Returned future will
@ -739,8 +677,8 @@ when defined(windows) or defined(nimdoc):
var lpOutputBuf = newString(lpOutputLen) var lpOutputBuf = newString(lpOutputLen)
var dwBytesReceived: Dword var dwBytesReceived: Dword
let dwReceiveDataLength = 0.Dword # We don't want any data to be read. let dwReceiveDataLength = 0.Dword # We don't want any data to be read.
let dwLocalAddressLength = Dword(sizeof(Sockaddr_in) + 16) let dwLocalAddressLength = Dword(sizeof(Sockaddr_in6) + 16)
let dwRemoteAddressLength = Dword(sizeof(Sockaddr_in) + 16) let dwRemoteAddressLength = Dword(sizeof(Sockaddr_in6) + 16)
template failAccept(errcode) = template failAccept(errcode) =
if flags.isDisconnectionError(errcode): if flags.isDisconnectionError(errcode):
@ -770,12 +708,14 @@ when defined(windows) or defined(nimdoc):
dwLocalAddressLength, dwRemoteAddressLength, dwLocalAddressLength, dwRemoteAddressLength,
addr localSockaddr, addr localLen, addr localSockaddr, addr localLen,
addr remoteSockaddr, addr remoteLen) addr remoteSockaddr, addr remoteLen)
register(clientSock.AsyncFD) try:
# TODO: IPv6. Check ``sa_family``. http://stackoverflow.com/a/9212542/492186 let address = getAddrString(remoteSockAddr)
retFuture.complete( register(clientSock.AsyncFD)
(address: $inet_ntoa(cast[ptr Sockaddr_in](remoteSockAddr).sin_addr), retFuture.complete((address: address, client: clientSock.AsyncFD))
client: clientSock.AsyncFD) except:
) # getAddrString may raise
clientSock.close()
retFuture.fail(getCurrentException())
var ol = PCustomOverlapped() var ol = PCustomOverlapped()
GC_ref(ol) GC_ref(ol)
@ -808,20 +748,6 @@ when defined(windows) or defined(nimdoc):
return retFuture return retFuture
proc newAsyncNativeSocket*(domain, sockType, protocol: cint): AsyncFD =
## Creates a new socket and registers it with the dispatcher implicitly.
result = newNativeSocket(domain, sockType, protocol).AsyncFD
result.SocketHandle.setBlocking(false)
register(result)
proc newAsyncNativeSocket*(domain: Domain = nativesockets.AF_INET,
sockType: SockType = SOCK_STREAM,
protocol: Protocol = IPPROTO_TCP): AsyncFD =
## Creates a new socket and registers it with the dispatcher implicitly.
result = newNativeSocket(domain, sockType, protocol).AsyncFD
result.SocketHandle.setBlocking(false)
register(result)
proc closeSocket*(socket: AsyncFD) = proc closeSocket*(socket: AsyncFD) =
## Closes a socket and ensures that it is unregistered. ## Closes a socket and ensures that it is unregistered.
socket.SocketHandle.close() socket.SocketHandle.close()
@ -1159,23 +1085,6 @@ else:
var data = newAsyncData() var data = newAsyncData()
p.selector.registerHandle(fd.SocketHandle, {}, data) p.selector.registerHandle(fd.SocketHandle, {}, data)
proc newAsyncNativeSocket*(domain: cint, sockType: cint,
protocol: cint): AsyncFD =
result = newNativeSocket(domain, sockType, protocol).AsyncFD
result.SocketHandle.setBlocking(false)
when defined(macosx):
result.SocketHandle.setSockOptInt(SOL_SOCKET, SO_NOSIGPIPE, 1)
register(result)
proc newAsyncNativeSocket*(domain: Domain = AF_INET,
sockType: SockType = SOCK_STREAM,
protocol: Protocol = IPPROTO_TCP): AsyncFD =
result = newNativeSocket(domain, sockType, protocol).AsyncFD
result.SocketHandle.setBlocking(false)
when defined(macosx):
result.SocketHandle.setSockOptInt(SOL_SOCKET, SO_NOSIGPIPE, 1)
register(result)
proc closeSocket*(sock: AsyncFD) = proc closeSocket*(sock: AsyncFD) =
let disp = getGlobalDispatcher() let disp = getGlobalDispatcher()
disp.selector.unregister(sock.SocketHandle) disp.selector.unregister(sock.SocketHandle)
@ -1331,50 +1240,6 @@ else:
# Callback queue processing # Callback queue processing
processPendingCallbacks(p) processPendingCallbacks(p)
proc connect*(socket: AsyncFD, address: string, port: Port,
domain = AF_INET): Future[void] =
var retFuture = newFuture[void]("connect")
proc cb(fd: AsyncFD): bool =
var ret = SocketHandle(fd).getSockOptInt(cint(SOL_SOCKET), cint(SO_ERROR))
if ret == 0:
# We have connected.
retFuture.complete()
return true
elif ret == EINTR:
# interrupted, keep waiting
return false
else:
retFuture.fail(newException(OSError, osErrorMsg(OSErrorCode(ret))))
return true
assert getSockDomain(socket.SocketHandle) == domain
var aiList = getAddrInfo(address, port, domain)
var success = false
var lastError: OSErrorCode
var it = aiList
while it != nil:
var ret = connect(socket.SocketHandle, it.ai_addr, it.ai_addrlen.Socklen)
if ret == 0:
# Request to connect completed immediately.
success = true
retFuture.complete()
break
else:
lastError = osLastError()
if lastError.int32 == EINTR or lastError.int32 == EINPROGRESS:
success = true
addWrite(socket, cb)
break
else:
success = false
it = it.ai_next
freeAddrInfo(aiList)
if not success:
retFuture.fail(newException(OSError, osErrorMsg(lastError)))
return retFuture
proc recv*(socket: AsyncFD, size: int, proc recv*(socket: AsyncFD, size: int,
flags = {SocketFlag.SafeDisconn}): Future[string] = flags = {SocketFlag.SafeDisconn}): Future[string] =
var retFuture = newFuture[string]("recv") var retFuture = newFuture[string]("recv")
@ -1568,9 +1433,14 @@ else:
else: else:
retFuture.fail(newException(OSError, osErrorMsg(lastError))) retFuture.fail(newException(OSError, osErrorMsg(lastError)))
else: else:
register(client.AsyncFD) try:
retFuture.complete((getAddrString(cast[ptr SockAddr](addr sockAddress)), let address = getAddrString(cast[ptr SockAddr](addr sockAddress))
client.AsyncFD)) register(client.AsyncFD)
retFuture.complete((address, client.AsyncFD))
except:
# getAddrString may raise
client.close()
retFuture.fail(getCurrentException())
addRead(socket, cb) addRead(socket, cb)
return retFuture return retFuture
@ -1623,6 +1493,9 @@ else:
data.readList.add(cb) data.readList.add(cb)
p.selector.registerEvent(SelectEvent(ev), data) p.selector.registerEvent(SelectEvent(ev), data)
# Common procedures between current and upcoming asyncdispatch
include includes.asynccommon
proc sleepAsync*(ms: int): Future[void] = proc sleepAsync*(ms: int): Future[void] =
## Suspends the execution of the current async procedure for the next ## Suspends the execution of the current async procedure for the next
## ``ms`` milliseconds. ## ``ms`` milliseconds.

View file

@ -495,7 +495,7 @@ type
ai_family*: cint ## Address family of socket. ai_family*: cint ## Address family of socket.
ai_socktype*: cint ## Socket type. ai_socktype*: cint ## Socket type.
ai_protocol*: cint ## Protocol of socket. ai_protocol*: cint ## Protocol of socket.
ai_addrlen*: int ## Length of socket address. ai_addrlen*: csize ## Length of socket address.
ai_canonname*: cstring ## Canonical name of service location. ai_canonname*: cstring ## Canonical name of service location.
ai_addr*: ptr SockAddr ## Socket address of socket. ai_addr*: ptr SockAddr ## Socket address of socket.
ai_next*: ptr AddrInfo ## Pointer to next in list. ai_next*: ptr AddrInfo ## Pointer to next in list.
@ -803,6 +803,7 @@ const
SIO_GET_EXTENSION_FUNCTION_POINTER* = WSAIORW(IOC_WS2,6).DWORD SIO_GET_EXTENSION_FUNCTION_POINTER* = WSAIORW(IOC_WS2,6).DWORD
SO_UPDATE_ACCEPT_CONTEXT* = 0x700B SO_UPDATE_ACCEPT_CONTEXT* = 0x700B
AI_V4MAPPED* = 0x0008 AI_V4MAPPED* = 0x0008
AF_UNSPEC* = 0
AF_INET* = 2 AF_INET* = 2
AF_INET6* = 23 AF_INET6* = 23

View file

@ -0,0 +1,53 @@
discard """
file: "tasyncdial.nim"
output: '''
OK AF_INET
OK AF_INET6
'''
"""
import
nativesockets, os, asyncdispatch
proc setupServerSocket(hostname: string, port: Port, domain: Domain): AsyncFD =
## Creates a socket, binds it to the specified address, and starts listening for connecitons.
## Registers the descriptor with the dispatcher of the current thread
## Raises OSError in case of an error.
let fd = newNativeSocket(domain)
setSockOptInt(fd, SOL_SOCKET, SO_REUSEADDR, 1)
var aiList = getAddrInfo(hostname, port, domain)
if bindAddr(fd, aiList.ai_addr, aiList.ai_addrlen.Socklen) < 0'i32:
freeAddrInfo(aiList)
raiseOSError(osLastError())
freeAddrInfo(aiList)
if listen(fd) != 0:
raiseOSError(osLastError())
setBlocking(fd, false)
result = fd.AsyncFD
register(result)
proc doTest(domain: static[Domain]) {.async.} =
const
testHost = when domain == Domain.AF_INET6: "::1" else: "127.0.0.1"
testPort = Port(17384)
let serverFd = setupServerSocket(testHost, testPort, domain)
let acceptFut = serverFd.accept()
let clientFdFut = dial(testHost, testPort)
let serverClientFd = await acceptFut
serverFd.closeSocket()
let clientFd = await clientFdFut
let recvFut = serverClientFd.recv(2)
await clientFd.send("Hi")
let msg = await recvFut
serverClientFd.closeSocket()
clientFd.closeSocket()
if msg == "Hi":
echo "OK ", domain
waitFor(doTest(Domain.AF_INET))
waitFor(doTest(Domain.AF_INET6))

View file

@ -0,0 +1,36 @@
discard """
output: '''true
true'''
targets: "c"
"""
import os, osproc
when not defined(windows):
import posix
# Checks that the environment is passed correctly in startProcess
# To do that launches a copy of itself with a new environment.
if paramCount() == 0:
# Parent process
let p = startProcess(
getAppFilename(),
args = @["child"],
options = {poStdErrToStdOut, poUsePath, poParentStreams}
)
echo p.running()
p.kill()
when defined(windows):
# windows kill happens using TerminateProcess(h, 0), so we should get a
# 0 here
echo p.waitForExit() == 0
else:
# on posix (non-windows), kill sends SIGKILL
echo p.waitForExit() == 128 + SIGKILL
else:
sleep(5000) # should get killed before this

View file

@ -1,11 +1,13 @@
discard """ discard """
cmd: "nim c --threads:on -d:ssl $file" cmd: "nim c --threads:on -d:ssl $file"
exitcode: 0
output: "OK"
""" """
import strutils import strutils
from net import TimeoutError from net import TimeoutError
import httpclient, asyncdispatch import nativesockets, os, httpclient, asyncdispatch
const manualTests = false const manualTests = false
@ -112,6 +114,40 @@ proc syncTest() =
except: except:
doAssert false, "TimeoutError should have been raised." doAssert false, "TimeoutError should have been raised."
syncTest() proc makeIPv6HttpServer(hostname: string, port: Port): AsyncFD =
let fd = newNativeSocket(AF_INET6)
setSockOptInt(fd, SOL_SOCKET, SO_REUSEADDR, 1)
var aiList = getAddrInfo(hostname, port, AF_INET6)
if bindAddr(fd, aiList.ai_addr, aiList.ai_addrlen.Socklen) < 0'i32:
freeAddrInfo(aiList)
raiseOSError(osLastError())
freeAddrInfo(aiList)
if listen(fd) != 0:
raiseOSError(osLastError())
setBlocking(fd, false)
var serverFd = fd.AsyncFD
register(serverFd)
result = serverFd
proc onAccept(fut: Future[AsyncFD]) {.gcsafe.} =
if not fut.failed:
let clientFd = fut.read()
clientFd.send("HTTP/1.1 200 OK\r\LContent-Length: 0\r\LConnection: Closed\r\L\r\L").callback = proc() =
clientFd.closeSocket()
serverFd.accept().callback = onAccept
serverFd.accept().callback = onAccept
proc ipv6Test() =
var client = newAsyncHttpClient()
let serverFd = makeIPv6HttpServer("::1", Port(18473))
var resp = waitFor client.request("http://[::1]:18473/")
doAssert(resp.status == "200 OK")
serverFd.closeSocket()
client.close()
syncTest()
waitFor(asyncTest()) waitFor(asyncTest())
ipv6Test()
echo "OK"

60
tests/stdlib/tnetdial.nim Normal file
View file

@ -0,0 +1,60 @@
discard """
cmd: "nim c --threads:on $file"
exitcode: 0
output: "OK"
"""
import os, net, nativesockets, asyncdispatch
## Test for net.dial
const port = Port(28431)
proc initIPv6Server(hostname: string, port: Port): AsyncFD =
let fd = newNativeSocket(AF_INET6)
setSockOptInt(fd, SOL_SOCKET, SO_REUSEADDR, 1)
var aiList = getAddrInfo(hostname, port, AF_INET6)
if bindAddr(fd, aiList.ai_addr, aiList.ai_addrlen.Socklen) < 0'i32:
freeAddrInfo(aiList)
raiseOSError(osLastError())
freeAddrInfo(aiList)
if listen(fd) != 0:
raiseOSError(osLastError())
setBlocking(fd, false)
var serverFd = fd.AsyncFD
register(serverFd)
result = serverFd
# Since net.dial is synchronous, we use main thread to setup server,
# and dial to it from another thread.
proc testThread() {.thread.} =
let fd = net.dial("::1", port)
var s = newString(5)
doAssert fd.recv(addr s[0], 5) == 5
if s == "Hello":
echo "OK"
fd.close()
proc test() =
var t: Thread[void]
createThread(t, testThread)
let serverFd = initIPv6Server("::1", port)
var done = false
serverFd.accept().callback = proc(fut: Future[AsyncFD]) =
serverFd.closeSocket()
if not fut.failed:
let fd = fut.read()
fd.send("Hello").callback = proc() =
fd.closeSocket()
done = true
while not done:
poll()
joinThread(t)
test()

View file

@ -13,6 +13,9 @@ Changelog
Changes affecting backwards compatibility Changes affecting backwards compatibility
----------------------------------------- -----------------------------------------
- There are now two different HTTP response types, ``Response`` and
``AsyncResponse``. ``AsyncResponse``'s ``body`` accessor returns a
``Future[string]``!
- ``httpclient.request`` now respects ``maxRedirects`` option. Previously - ``httpclient.request`` now respects ``maxRedirects`` option. Previously
redirects were handled only by ``get`` and ``post`` procs. redirects were handled only by ``get`` and ``post`` procs.
- The IO routines now raise ``EOFError`` for the "end of file" condition. - The IO routines now raise ``EOFError`` for the "end of file" condition.
@ -56,12 +59,17 @@ Changes affecting backwards compatibility
checks. When fields within case objects are initialiazed, the compiler will checks. When fields within case objects are initialiazed, the compiler will
now demand that the respective discriminator field has a matching known now demand that the respective discriminator field has a matching known
compile-time value. compile-time value.
- On posix, the results of `waitForExit`, `peekExitCode`, `execCmd` will return
128 + signal number if the application terminates via signal.
Library Additions Library Additions
----------------- -----------------
- Added ``system.onThreadDestruction``. - Added ``system.onThreadDestruction``.
- Added ``dial`` procedure to networking modules: ``net``, ``asyncdispatch``,
``asyncnet``. It merges socket creation, address resolution, and connection
into single step. When using ``dial``, you don't have to worry about
IPv4 vs IPv6 problem. ``httpclient`` now supports IPv6.
Tool Additions Tool Additions
-------------- --------------