I've encountered a couple of odd issues regarding HTTP in Nim 1.4.8. One issue manifests with HttpClient and maybe AsyncHttpClient, the other with just AsyncHttpClient.
There are two target systems that have triggered the issues (one each) so far, both using TLS:
System A, some proprietary Java application
System B, BMC/Remedy-based Java application
The first issue occurred in application X, which queries system A periodically: HttpClient would intermittently hang indefinitely despite timeout being set. Switching over to AsyncHttpClient and using withTimeout on request and body has prevented further hanging. body.withTimeout will now occasionally return false: if this is related to why it would previously hang is unknown, but it seems unlikely that it would take more than 60 seconds to read the response body.
The second issue occurred in application Y. Y does HTTP requests to several different systems, as the hang issue cannot be allowed to occur in any call I switched over all HTTP communication to async as well. It works as expected in requests to various services except for system B. For B, using body.withTimeout.waitFor or body.waitFor will nearly always result in an exception:
Async traceback:
/home/nxnl/y/src/ypkg/workers/no.nim(185) noProc
/usr/local/lib/nim/pure/asyncmacro.nim(262) execute
/usr/local/lib/nim/pure/asyncmacro.nim(29) executeNimAsyncContinue
/home/nxnl/y/src/ypkg/workers/no.nim(147) executeIter
/home/nxnl/y/src/ypkg/b.nim(53) appendTimeline
/usr/local/lib/nim/pure/asyncdispatch.nim(1935) waitFor
/usr/local/lib/nim/pure/asyncdispatch.nim(1627) poll
/usr/local/lib/nim/pure/asyncdispatch.nim(1368) runOnce
/usr/local/lib/nim/pure/asyncdispatch.nim(208) processPendingCallbacks
/usr/local/lib/nim/pure/asyncmacro.nim(29) bodyNimAsyncContinue
/usr/local/lib/nim/pure/asyncmacro.nim(145) bodyIter
/usr/local/lib/nim/pure/asyncfutures.nim(390) read
Exception message: Bad file descriptor
This behavior has changed from Nim 1.4.4 where body.withTimeout.waitFor would time out while body.waitFor would raise the exception: equally bad for this application but perhaps noteworthy. Using the HttpClient does not present this problem with these requests. For the time being I've opted to ignore the response body of system B as it's only used for logging error details.
Has anyone encountered something similar? Any ideas on troubleshooting these issues?
The development environment running application X and Y is WSL2 Debian on Windows connected to a company network through IPsec ESP VPN.
Like this:
let bodyFuture = r.body()
if not waitFor(bodyFuture.withTimeout(o.timeout)):
error(&"Reading system B request {r.status} body timed out")
return false
let err = &"System B error {r.status} {& bodyFuture.read()}"
Here's some code to better illustrate the "Bad file descriptor" situation:
proc request*(b: SysB, endpoint, mthd: string, body = ""): AsyncResponse =
## Send a request to system B.
let client = newAsyncHttpClient(sslContext = newContext(certFile = b.cert,
keyFile = b.key))
defer: client.close()
client.headers = newHttpHeaders()
client.headers["Content-Type"] = "application/json"
client.headers["Accept"] = "application/json"
# Login
var respFuture = client.post(url = &"{b.hostUri}/{login}",
body = $ %*{"password": b.pwd})
if not respFuture.withTimeout(b.timeout).waitFor():
raise newException(TemporaryError, "Login timed out")
var resp = respFuture.read()
if not resp.code.is2xx():
return resp
# Actual request
client.headers["Authorization"] = resp.headers["Authorization"]
respFuture = client.request(url = &"{b.hostUri}/{endpoint}",
httpMethod = mthd, body = body)
if not waitFor(respFuture.withTimeout(b.timeout)):
raise newException(TemporaryError, "Request timed out")
respFuture.read()
proc postThing*(b: SysB, item, msg: string): bool =
## Post thing to system B.
let r = b.request(&"path/{item}", "POST", $ %*[{"something": msg}])
if r.code.is2xx():
return true
let bodyFuture = r.body()
# This causes "Bad file descriptor"
if not waitFor(bodyFuture.withTimeout(b.timeout)):
error(&"Reading {r.status} body timed out")
return false
let err = &"{r.status} - {bodyFuture.read()}"
error(err)
Put the client on global, and call client.close() explicitly when done.
Oh, I didn't even think of the client being closed. That solves one mystery, thank you! I merged the two procs into one as there'll likely only be one request type against the system.
The hanging issue with HttpClient remains. I'll try to investigate that best I can.