diff --git a/lib/pure/asynchttpserver.nim b/lib/pure/asynchttpserver.nim index 4500d994eff11..86f12fa24f27e 100644 --- a/lib/pure/asynchttpserver.nim +++ b/lib/pure/asynchttpserver.nim @@ -44,6 +44,7 @@ import asyncnet, asyncdispatch, parseutils, uri, strutils import httpcore +from times import DateTime, now, `-`, inSeconds export httpcore except parseHeader @@ -64,6 +65,7 @@ type url*: Uri hostname*: string ## The hostname of the client that made the request. body*: string + disableKeepalive: bool AsyncHttpServer* = ref object socket: AsyncSocket @@ -71,11 +73,14 @@ type reusePort: bool maxBody: int ## The maximum content-length that will be read for the body. maxFDs: int + keepaliveTimeout: int ## The value for keepalive timeout in seconds and \ + ## after which the connection will be closed. proc newAsyncHttpServer*(reuseAddr = true, reusePort = false, - maxBody = 8388608): AsyncHttpServer = + maxBody = 8388608, keepaliveTimeout = 3600): AsyncHttpServer = ## Creates a new ``AsyncHttpServer`` instance. - result = AsyncHttpServer(reuseAddr: reuseAddr, reusePort: reusePort, maxBody: maxBody) + result = AsyncHttpServer(reuseAddr: reuseAddr, reusePort: reusePort, + maxBody: maxBody, keepaliveTimeout: keepaliveTimeout) proc addHeaders(msg: var string, headers: HttpHeaders) = for k, v in headers: @@ -276,10 +281,11 @@ proc processRequest( # connection will not be closed and will be kept in the connection pool. # Persistent connections - if (request.protocol == HttpVer11 and + if not request.disableKeepalive and + (request.protocol == HttpVer11 and cmpIgnoreCase(request.headers.getOrDefault("connection"), "close") != 0) or (request.protocol == HttpVer10 and - cmpIgnoreCase(request.headers.getOrDefault("connection"), "keep-alive") == 0): + cmpIgnoreCase(request.headers.getOrDefault("connection"), "keep-alive") == 0)): # In HTTP 1.1 we assume that connection is persistent. Unless connection # header states otherwise. # In HTTP 1.0 we assume that the connection should not be persistent. @@ -295,15 +301,27 @@ proc processClient(server: AsyncHttpServer, client: AsyncSocket, address: string var request = newFutureVar[Request]("asynchttpserver.processClient") request.mget().url = initUri() request.mget().headers = newHttpHeaders() + request.mget().disableKeepalive = false var lineFut = newFutureVar[string]("asynchttpserver.processClient") lineFut.mget() = newStringOfCap(80) + let startTimeout = now() while not client.isClosed: + let fds = activeDescriptors() + # Disables the keepalive if the active file descriptors exceeds the maxFDs value + # or if the maximum keepalive timeout is exceeded. + # The maxFDs should be replaced by the keepaliveConn? + if (fds > server.maxFDs) or ((now() - startTimeout).inSeconds > server.keepaliveTimeout): + request.mget().disableKeepalive = true + let retry = await processRequest( server, request, client, address, lineFut, callback ) if not retry: break + client.close() # Close the connection to not increase the number of open files. + + const nimMaxDescriptorsFallback* {.intdefine.} = 16_000 ## fallback value for \ ## when `maxDescriptors` is not available. @@ -339,6 +357,7 @@ proc acceptRequest*(server: AsyncHttpServer, var (address, client) = await server.socket.acceptAddr() asyncCheck processClient(server, client, address, callback) + proc serve*(server: AsyncHttpServer, port: Port, callback: proc (request: Request): Future[void] {.closure, gcsafe.}, address = ""; @@ -365,6 +384,7 @@ proc serve*(server: AsyncHttpServer, port: Port, #echo(f.isNil) #echo(f.repr) + proc close*(server: AsyncHttpServer) = ## Terminates the async http server instance. server.socket.close()