Implement dial, support IPv6 in httpclient (#5763)

* Implement dial, support IPv6 in httpclient

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.

Fixed addrInfo loop in connect to behave properly.
Previously it would stop on first non-immediate failure, instead of
continuing and trying the remaining addresses.

Fixed newAsyncNativeSocket to raise proper error if socket creation
fails.

Fixes: #3811

* Check domain during connect() only on non-Windows

This is how it was in the previous implementation of connect().

* Call 'osLastError' before 'close' in net.dial

* Record osLastError before freeAddrInfo in net.dial

* Add missing docs for 'dial' proc

* Optimize dial to create one FD per domain, add tests

And make async IPv6 servers work on Windows.

* Add IPv6 test to uri module

* Fix getAddrString error handling
This commit is contained in:
Ruslan Mustakov
2017-05-02 14:25:50 +07:00
committed by Andreas Rumpf
parent 6377b52d8e
commit ecf278c467
13 changed files with 559 additions and 334 deletions

View File

@@ -1,11 +1,13 @@
discard """
cmd: "nim c --threads:on -d:ssl $file"
exitcode: 0
output: "OK"
"""
import strutils
from net import TimeoutError
import httpclient, asyncdispatch
import nativesockets, os, httpclient, asyncdispatch
const manualTests = false
@@ -112,6 +114,40 @@ proc syncTest() =
except:
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())
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()