Implements timeouts for synchronous HttpClient.
This commit is contained in:
parent
3c47e70d53
commit
0c99523ad3
2 changed files with 35 additions and 9 deletions
|
|
@ -639,6 +639,7 @@ type
|
||||||
headers*: HttpHeaders
|
headers*: HttpHeaders
|
||||||
maxRedirects: int
|
maxRedirects: int
|
||||||
userAgent: string
|
userAgent: string
|
||||||
|
timeout: int ## Only used for blocking HttpClient for now.
|
||||||
when defined(ssl):
|
when defined(ssl):
|
||||||
sslContext: net.SslContext
|
sslContext: net.SslContext
|
||||||
|
|
||||||
|
|
@ -646,7 +647,8 @@ type
|
||||||
HttpClient* = HttpClientBase[Socket]
|
HttpClient* = HttpClientBase[Socket]
|
||||||
|
|
||||||
proc newHttpClient*(userAgent = defUserAgent,
|
proc newHttpClient*(userAgent = defUserAgent,
|
||||||
maxRedirects = 5, sslContext = defaultSslContext): HttpClient =
|
maxRedirects = 5, sslContext = defaultSslContext,
|
||||||
|
timeout = -1): HttpClient =
|
||||||
## Creates a new HttpClient instance.
|
## Creates a new HttpClient instance.
|
||||||
##
|
##
|
||||||
## ``userAgent`` specifies the user agent that will be used when making
|
## ``userAgent`` specifies the user agent that will be used when making
|
||||||
|
|
@ -656,10 +658,14 @@ proc newHttpClient*(userAgent = defUserAgent,
|
||||||
## default is 5.
|
## default is 5.
|
||||||
##
|
##
|
||||||
## ``sslContext`` specifies the SSL context to use for HTTPS requests.
|
## ``sslContext`` specifies the SSL context to use for HTTPS requests.
|
||||||
|
##
|
||||||
|
## ``timeout`` specifies the number of miliseconds to allow before a
|
||||||
|
## ``TimeoutError`` is raised.
|
||||||
new result
|
new result
|
||||||
result.headers = newHttpHeaders()
|
result.headers = newHttpHeaders()
|
||||||
result.userAgent = userAgent
|
result.userAgent = userAgent
|
||||||
result.maxRedirects = maxRedirects
|
result.maxRedirects = maxRedirects
|
||||||
|
result.timeout = timeout
|
||||||
when defined(ssl):
|
when defined(ssl):
|
||||||
result.sslContext = sslContext
|
result.sslContext = sslContext
|
||||||
|
|
||||||
|
|
@ -683,6 +689,7 @@ proc newAsyncHttpClient*(userAgent = defUserAgent,
|
||||||
result.headers = newHttpHeaders()
|
result.headers = newHttpHeaders()
|
||||||
result.userAgent = userAgent
|
result.userAgent = userAgent
|
||||||
result.maxRedirects = maxRedirects
|
result.maxRedirects = maxRedirects
|
||||||
|
result.timeout = -1 # TODO
|
||||||
when defined(ssl):
|
when defined(ssl):
|
||||||
result.sslContext = sslContext
|
result.sslContext = sslContext
|
||||||
|
|
||||||
|
|
@ -693,11 +700,14 @@ proc close*(client: HttpClient | AsyncHttpClient) =
|
||||||
client.connected = false
|
client.connected = false
|
||||||
|
|
||||||
proc recvFull(socket: Socket | AsyncSocket,
|
proc recvFull(socket: Socket | AsyncSocket,
|
||||||
size: int): Future[string] {.multisync.} =
|
size: int, timeout: int): Future[string] {.multisync.} =
|
||||||
## Ensures that all the data requested is read and returned.
|
## Ensures that all the data requested is read and returned.
|
||||||
result = ""
|
result = ""
|
||||||
while true:
|
while true:
|
||||||
if size == result.len: break
|
if size == result.len: break
|
||||||
|
when socket is Socket:
|
||||||
|
let data = socket.recv(size - result.len, timeout)
|
||||||
|
else:
|
||||||
let data = await socket.recv(size - result.len)
|
let data = await socket.recv(size - result.len)
|
||||||
if data == "": break # We've been disconnected.
|
if data == "": break # We've been disconnected.
|
||||||
result.add data
|
result.add data
|
||||||
|
|
@ -729,10 +739,10 @@ proc parseChunks(client: HttpClient | AsyncHttpClient): Future[string]
|
||||||
httpError("Invalid chunk size: " & chunkSizeStr)
|
httpError("Invalid chunk size: " & chunkSizeStr)
|
||||||
inc(i)
|
inc(i)
|
||||||
if chunkSize <= 0:
|
if chunkSize <= 0:
|
||||||
discard await recvFull(client.socket, 2) # Skip \c\L
|
discard await recvFull(client.socket, 2, client.timeout) # Skip \c\L
|
||||||
break
|
break
|
||||||
result.add await recvFull(client.socket, chunkSize)
|
result.add await recvFull(client.socket, chunkSize, client.timeout)
|
||||||
discard await recvFull(client.socket, 2) # Skip \c\L
|
discard await recvFull(client.socket, 2, client.timeout) # Skip \c\L
|
||||||
# Trailer headers will only be sent if the request specifies that we want
|
# Trailer headers will only be sent if the request specifies that we want
|
||||||
# them: http://tools.ietf.org/html/rfc2616#section-3.6.1
|
# them: http://tools.ietf.org/html/rfc2616#section-3.6.1
|
||||||
|
|
||||||
|
|
@ -749,7 +759,7 @@ proc parseBody(client: HttpClient | AsyncHttpClient,
|
||||||
if contentLengthHeader != "":
|
if contentLengthHeader != "":
|
||||||
var length = contentLengthHeader.parseint()
|
var length = contentLengthHeader.parseint()
|
||||||
if length > 0:
|
if length > 0:
|
||||||
result = await client.socket.recvFull(length)
|
result = await client.socket.recvFull(length, client.timeout)
|
||||||
if result == "":
|
if result == "":
|
||||||
httpError("Got disconnected while trying to read body.")
|
httpError("Got disconnected while trying to read body.")
|
||||||
if result.len != length:
|
if result.len != length:
|
||||||
|
|
@ -763,7 +773,7 @@ proc parseBody(client: HttpClient | AsyncHttpClient,
|
||||||
if headers.getOrDefault"Connection" == "close" or httpVersion == "1.0":
|
if headers.getOrDefault"Connection" == "close" or httpVersion == "1.0":
|
||||||
var buf = ""
|
var buf = ""
|
||||||
while true:
|
while true:
|
||||||
buf = await client.socket.recvFull(4000)
|
buf = await client.socket.recvFull(4000, client.timeout)
|
||||||
if buf == "": break
|
if buf == "": break
|
||||||
result.add(buf)
|
result.add(buf)
|
||||||
|
|
||||||
|
|
@ -776,6 +786,9 @@ proc parseResponse(client: HttpClient | AsyncHttpClient,
|
||||||
result.headers = newHttpHeaders()
|
result.headers = newHttpHeaders()
|
||||||
while true:
|
while true:
|
||||||
linei = 0
|
linei = 0
|
||||||
|
when client is HttpClient:
|
||||||
|
line = await client.socket.recvLine(client.timeout)
|
||||||
|
else:
|
||||||
line = await client.socket.recvLine()
|
line = await client.socket.recvLine()
|
||||||
if line == "": break # We've been disconnected.
|
if line == "": break # We've been disconnected.
|
||||||
if line == "\c\L":
|
if line == "\c\L":
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import strutils
|
import strutils
|
||||||
|
from net import TimeoutError
|
||||||
|
|
||||||
import httpclient, asyncdispatch
|
import httpclient, asyncdispatch
|
||||||
|
|
||||||
|
|
@ -30,6 +31,18 @@ proc syncTest() =
|
||||||
resp = client.request("https://google.com/")
|
resp = client.request("https://google.com/")
|
||||||
doAssert(resp.code.is2xx or resp.code.is3xx)
|
doAssert(resp.code.is2xx or resp.code.is3xx)
|
||||||
|
|
||||||
|
client.close()
|
||||||
|
|
||||||
|
# Timeout test.
|
||||||
|
client = newHttpClient(timeout = 1)
|
||||||
|
try:
|
||||||
|
resp = client.request("http://example.com/")
|
||||||
|
doAssert false, "TimeoutError should have been raised."
|
||||||
|
except TimeoutError:
|
||||||
|
discard
|
||||||
|
except:
|
||||||
|
doAssert false, "TimeoutError should have been raised."
|
||||||
|
|
||||||
syncTest()
|
syncTest()
|
||||||
|
|
||||||
waitFor(asyncTest())
|
waitFor(asyncTest())
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue