add support for DNS resolution (#81)

* add support for DNS resolution

- reworked API to be more consistent
  - string addresses and Uri types will be now resolved
- made the API more consistent

* log failed connection attempt

* agent string can't contain spaces

* add websock topic (#83)

* style

Co-authored-by: Tanguy Cizain <tanguycizain@gmail.com>
This commit is contained in:
Dmitriy Ryajov 2021-07-14 18:51:39 -06:00 committed by GitHub
parent fec0f2bac1
commit 06ae75cf7f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 64 additions and 100 deletions

View File

@ -19,19 +19,18 @@ const
# so we are using different port # so we are using different port
when defined tls: when defined tls:
const const
agent = "websock secure client" agent = "websock-secure-client"
secure = true secure = true
serverPort = 9002 serverPort = 9002
else: else:
const const
agent = "websock client" agent = "websock-client"
secure = false secure = false
serverPort = 9001 serverPort = 9001
proc connectServer(path: string, factories: seq[ExtFactory] = @[]): Future[WSSession] {.async.} = proc connectServer(path: string, factories: seq[ExtFactory] = @[]): Future[WSSession] {.async.} =
let ws = await WebSocket.connect( let ws = await WebSocket.connect(
host = "127.0.0.1", host = "127.0.0.1:$1" % [$serverPort],
port = Port(serverPort),
path = path, path = path,
secure=secure, secure=secure,
flags=clientFlags, flags=clientFlags,

View File

@ -17,14 +17,12 @@ import ../websock/websock
proc main() {.async.} = proc main() {.async.} =
let ws = when defined tls: let ws = when defined tls:
await WebSocket.connect( await WebSocket.connect(
"127.0.0.1", "127.0.0.1:8888",
Port(8888),
path = "/wss", path = "/wss",
flags = {TLSFlags.NoVerifyHost, TLSFlags.NoVerifyServerName}) flags = {TLSFlags.NoVerifyHost, TLSFlags.NoVerifyServerName})
else: else:
await WebSocket.connect( await WebSocket.connect(
"127.0.0.1", "127.0.0.1:8888",
Port(8888),
path = "/ws") path = "/ws")
trace "Websocket client: ", State = ws.readyState trace "Websocket client: ", State = ws.readyState

View File

@ -48,8 +48,7 @@ suite "permessage deflate compression":
server.start() server.start()
let client = await WebSocket.connect( let client = await WebSocket.connect(
host = "127.0.0.1", host = "127.0.0.1:8888",
port = Port(8888),
path = "/ws", path = "/ws",
protocols = @["proto"], protocols = @["proto"],
factories = @[deflateFactory] factories = @[deflateFactory]
@ -89,8 +88,7 @@ suite "permessage deflate compression":
server.start() server.start()
let client = await WebSocket.connect( let client = await WebSocket.connect(
host = "127.0.0.1", host = "127.0.0.1:8888",
port = Port(8888),
path = "/ws", path = "/ws",
protocols = @["proto"], protocols = @["proto"],
factories = @[deflateFactory] factories = @[deflateFactory]

View File

@ -43,8 +43,7 @@ suite "multiple extensions flow":
server.start() server.start()
let client = await WebSocket.connect( let client = await WebSocket.connect(
host = "127.0.0.1", host = "127.0.0.1:8888",
port = Port(8888),
path = "/ws", path = "/ws",
protocols = @["proto"], protocols = @["proto"],
factories = @[hexFactory, base64Factory] factories = @[hexFactory, base64Factory]
@ -76,8 +75,7 @@ suite "multiple extensions flow":
server.start() server.start()
let client = await WebSocket.connect( let client = await WebSocket.connect(
host = "127.0.0.1", host = "127.0.0.1:8888",
port = Port(8888),
path = "/ws", path = "/ws",
protocols = @["proto"], protocols = @["proto"],
factories = @[base64Factory, hexFactory] factories = @[base64Factory, hexFactory]

View File

@ -96,7 +96,7 @@ proc connectClient*(
rng: Rng = nil): Future[WSSession] {.async.} = rng: Rng = nil): Future[WSSession] {.async.} =
let secure = when defined secure: true else: false let secure = when defined secure: true else: false
return await WebSocket.connect( return await WebSocket.connect(
address = address, host = address,
flags = flags, flags = flags,
path = path, path = path,
secure = secure, secure = secure,

View File

@ -110,8 +110,7 @@ suite "UTF-8 validator in action":
server.start() server.start()
let session = await WebSocket.connect( let session = await WebSocket.connect(
"127.0.0.1", "127.0.0.1:8888",
Port(8888),
path = "/ws", path = "/ws",
protocols = @["proto"], protocols = @["proto"],
) )
@ -152,8 +151,7 @@ suite "UTF-8 validator in action":
server.start() server.start()
let session = await WebSocket.connect( let session = await WebSocket.connect(
"127.0.0.1", "127.0.0.1:8888",
Port(8888),
path = "/ws", path = "/ws",
protocols = @["proto"], protocols = @["proto"],
) )
@ -178,8 +176,7 @@ suite "UTF-8 validator in action":
server.start() server.start()
let session = await WebSocket.connect( let session = await WebSocket.connect(
"127.0.0.1", "127.0.0.1:8888",
Port(8888),
path = "/ws", path = "/ws",
protocols = @["proto"] protocols = @["proto"]
) )
@ -204,8 +201,7 @@ suite "UTF-8 validator in action":
server.start() server.start()
let session = await WebSocket.connect( let session = await WebSocket.connect(
"127.0.0.1", "127.0.0.1:8888",
Port(8888),
path = "/ws", path = "/ws",
protocols = @["proto"] protocols = @["proto"]
) )

View File

@ -33,7 +33,6 @@ suite "Test handshake":
test "Should not select incorrect protocol": test "Should not select incorrect protocol":
proc handle(request: HttpRequest) {.async.} = proc handle(request: HttpRequest) {.async.} =
check request.uri.path == WSPath check request.uri.path == WSPath
let let
server = WSServer.new(protos = ["proto"]) server = WSServer.new(protos = ["proto"])
ws = await server.handleRequest(request) ws = await server.handleRequest(request)

View File

@ -103,9 +103,6 @@ proc request*(
else: else:
url url
if requestUrl.scheme == "":
raise newException(HttpError, "No uri scheme supplied.")
let headerString = generateHeaders(requestUrl, httpMethod, client.version, headers) let headerString = generateHeaders(requestUrl, httpMethod, client.version, headers)
await client.stream.writer.write(headerString) await client.stream.writer.write(headerString)
@ -129,7 +126,8 @@ proc connect*(
version = HttpVersion11, version = HttpVersion11,
tlsFlags: set[TLSFlags] = {}, tlsFlags: set[TLSFlags] = {},
tlsMinVersion = TLSVersion.TLS11, tlsMinVersion = TLSVersion.TLS11,
tlsMaxVersion = TLSVersion.TLS12): Future[T] {.async.} = tlsMaxVersion = TLSVersion.TLS12,
hostName = ""): Future[T] {.async.} =
let transp = await connect(address) let transp = await connect(address)
let client = T( let client = T(
@ -150,7 +148,7 @@ proc connect*(
let tlsStream = newTLSClientAsyncStream( let tlsStream = newTLSClientAsyncStream(
stream.reader, stream.reader,
stream.writer, stream.writer,
address.host, serverName = hostName,
minVersion = tlsMinVersion, minVersion = tlsMinVersion,
maxVersion = tlsMaxVersion, maxVersion = tlsMaxVersion,
flags = tlsFlags) flags = tlsFlags)
@ -167,16 +165,27 @@ proc connect*(
proc connect*( proc connect*(
T: typedesc[HttpClient | TlsHttpClient], T: typedesc[HttpClient | TlsHttpClient],
host: string, host: string,
port: int = 80,
version = HttpVersion11, version = HttpVersion11,
tlsFlags: set[TLSFlags] = {}, tlsFlags: set[TLSFlags] = {},
tlsMinVersion = TLSVersion.TLS11, tlsMinVersion = TLSVersion.TLS11,
tlsMaxVersion = TLSVersion.TLS12): Future[T] tlsMaxVersion = TLSVersion.TLS12): Future[T]
{.raises: [Defect, HttpError].} = {.async, raises: [Defect, HttpError].} =
let address = try: let hostPort = host.split(":")
initTAddress(host, port) let addrs = resolveTAddress(host)
except TransportAddressError as exc: for a in addrs:
raise newException(HttpError, exc.msg) try:
let conn = await T.connect(
a,
version,
tlsFlags,
tlsMinVersion,
tlsMaxVersion,
hostName = hostPort[0])
return T.connect(address, version, tlsFlags, tlsMinVersion, tlsMaxVersion) return conn
except TransportError as exc:
trace "Error connecting to address", address = $a, exc = exc.msg
raise newException(HttpError,
"Unable to connect to host on any address!")

View File

@ -103,8 +103,8 @@ type
ExtFactoryProc* = proc( ExtFactoryProc* = proc(
isServer: bool, isServer: bool,
args: seq[ExtParam]): Result[Ext, string] {. args: seq[ExtParam]): Result[Ext, string]
gcsafe, raises: [Defect].} {.gcsafe, raises: [Defect].}
ExtFactory* = object ExtFactory* = object
name*: string name*: string

View File

@ -105,9 +105,11 @@ proc selectExt(isServer: bool,
proc connect*( proc connect*(
_: type WebSocket, _: type WebSocket,
uri: Uri, host: string | TransportAddress,
path: string,
protocols: seq[string] = @[], protocols: seq[string] = @[],
factories: seq[ExtFactory] = @[], factories: seq[ExtFactory] = @[],
secure = false,
flags: set[TLSFlags] = {}, flags: set[TLSFlags] = {},
version = WSDefaultVersion, version = WSDefaultVersion,
frameSize = WSDefaultFrameSize, frameSize = WSDefaultFrameSize,
@ -115,22 +117,15 @@ proc connect*(
onPong: ControlCb = nil, onPong: ControlCb = nil,
onClose: CloseCb = nil, onClose: CloseCb = nil,
rng: Rng = nil): Future[WSSession] {.async.} = rng: Rng = nil): Future[WSSession] {.async.} =
## create a new websockets client
##
var rng = if isNil(rng): newRng() else: rng let
var key = Base64Pad.encode(genWebSecKey(rng)) rng = if isNil(rng): newRng() else: rng
var uri = uri key = Base64Pad.encode(genWebSecKey(rng))
let client = case uri.scheme:
of "wss": let client = if secure:
uri.scheme = "https" await TlsHttpClient.connect(host, tlsFlags = flags)
await TlsHttpClient.connect(uri.hostname, uri.port.parseInt(), tlsFlags = flags)
of "ws":
uri.scheme = "http"
await HttpClient.connect(uri.hostname, uri.port.parseInt())
else: else:
raise newException(WSWrongUriSchemeError, await HttpClient.connect(host)
"uri scheme has to be 'ws' or 'wss'")
let headerData = [ let headerData = [
("Connection", "Upgrade"), ("Connection", "Upgrade"),
@ -138,7 +133,7 @@ proc connect*(
("Cache-Control", "no-cache"), ("Cache-Control", "no-cache"),
("Sec-WebSocket-Version", $version), ("Sec-WebSocket-Version", $version),
("Sec-WebSocket-Key", key), ("Sec-WebSocket-Key", key),
("Host", uri.hostname & ":" & uri.port)] ("Host", $host)]
var headers = HttpTable.init(headerData) var headers = HttpTable.init(headerData)
if protocols.len > 0: if protocols.len > 0:
@ -154,7 +149,7 @@ proc connect*(
headers.add("Sec-WebSocket-Extensions", extOffer) headers.add("Sec-WebSocket-Extensions", extOffer)
let response = try: let response = try:
await client.request(uri, headers = headers) await client.request(path, headers = headers)
except CatchableError as exc: except CatchableError as exc:
trace "Websocket failed during handshake", exc = exc.msg trace "Websocket failed during handshake", exc = exc.msg
await client.close() await client.close()
@ -195,63 +190,35 @@ proc connect*(
proc connect*( proc connect*(
_: type WebSocket, _: type WebSocket,
address: TransportAddress, uri: Uri,
path: string,
protocols: seq[string] = @[], protocols: seq[string] = @[],
factories: seq[ExtFactory] = @[], factories: seq[ExtFactory] = @[],
secure = false,
flags: set[TLSFlags] = {}, flags: set[TLSFlags] = {},
version = WSDefaultVersion, version = WSDefaultVersion,
frameSize = WSDefaultFrameSize, frameSize = WSDefaultFrameSize,
onPing: ControlCb = nil, onPing: ControlCb = nil,
onPong: ControlCb = nil, onPong: ControlCb = nil,
onClose: CloseCb = nil, onClose: CloseCb = nil,
rng: Rng = nil): Future[WSSession] {.async.} = rng: Rng = nil): Future[WSSession]
{.raises: [Defect, WSWrongUriSchemeError].} =
## Create a new websockets client ## Create a new websockets client
## using a string path ## using a Uri
## ##
var uri = if secure: let secure = case uri.scheme:
&"wss://" of "wss": true
of "ws": false
else: else:
&"ws://" raise newException(WSWrongUriSchemeError,
"uri scheme has to be 'ws' or 'wss'")
uri &= address.host & ":" & $address.port var uri = uri
if path.startsWith("/"): if uri.port.len <= 0:
uri.add path uri.port = if secure: "443" else: "80"
else:
uri.add &"/{path}"
return await WebSocket.connect( return WebSocket.connect(
uri = parseUri(uri), host = uri.hostname & ":" & uri.port,
protocols = protocols, path = uri.path,
factories = factories,
flags = flags,
version = version,
frameSize = frameSize,
onPing = onPing,
onPong = onPong,
onClose = onClose)
proc connect*(
_: type WebSocket,
host: string,
port: Port,
path: string,
protocols: seq[string] = @[],
factories: seq[ExtFactory] = @[],
secure = false,
flags: set[TLSFlags] = {},
version = WSDefaultVersion,
frameSize = WSDefaultFrameSize,
onPing: ControlCb = nil,
onPong: ControlCb = nil,
onClose: CloseCb = nil,
rng: Rng = nil): Future[WSSession] {.async.} =
return await WebSocket.connect(
address = initTAddress(host, port),
path = path,
protocols = protocols, protocols = protocols,
factories = factories, factories = factories,
secure = secure, secure = secure,