mirror of
https://github.com/nim-lang/Nim.git
synced 2026-04-22 23:35:22 +00:00
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:
committed by
Andreas Rumpf
parent
6377b52d8e
commit
ecf278c467
@@ -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
60
tests/stdlib/tnetdial.nim
Normal 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()
|
||||
Reference in New Issue
Block a user