Add `server` and `host` headers handling. (#164)

Fix response headers generation to avoid unnecessary computations.
This commit is contained in:
Eugene Kabanov 2021-03-11 13:41:13 +02:00 committed by GitHub
parent f774644129
commit 0b78606e41
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 68 additions and 34 deletions

View File

@ -7,7 +7,7 @@
# Apache License, version 2.0, (LICENSE-APACHEv2)
# MIT license (LICENSE-MIT)
import std/[tables, options, uri, strutils]
import stew/results, httputils
import stew/[results, base10], httputils
import ../../asyncloop, ../../asyncsync
import ../../streams/[asyncstream, boundstream, chunkstream, tlsstream]
import httptable, httpcommon, multipart
@ -53,6 +53,7 @@ type
maxConnections*: int
backlogSize: int
baseUri*: Uri
serverIdent*: string
flags*: set[HttpServerFlags]
socketFlags*: set[ServerFlags]
secureFlags*: set[TLSFlags]
@ -124,6 +125,7 @@ proc new*(htype: typedesc[HttpServerRef],
serverFlags: set[HttpServerFlags] = {},
socketFlags: set[ServerFlags] = {ReuseAddr},
serverUri = Uri(),
serverIdent = "",
tlsPrivateKey: TLSPrivateKey = nil,
tlsCertificate: TLSCertificate = nil,
secureFlags: set[TLSFlags] = {},
@ -140,6 +142,7 @@ proc new*(htype: typedesc[HttpServerRef],
var res = HttpServerRef(
address: address,
serverIdent: serverIdent,
maxConnections: maxConnections,
headersTimeout: httpHeadersTimeout,
maxHeadersSize: maxHeadersSize,
@ -193,6 +196,12 @@ proc getResponse*(req: HttpRequestRef): HttpResponseRef {.raises: [Defect].} =
else:
req.response.get()
proc getHostname*(server: HttpServerRef): string =
if len(server.baseUri.port) > 0:
server.baseUri.hostname & ":" & server.baseUri.port
else:
server.baseUri.hostname
proc dumbResponse*(): HttpResponseRef {.raises: [Defect].} =
## Create an empty response to return when request processor got no request.
HttpResponseRef(state: HttpResponseState.Dumb, version: HttpVersion11)
@ -401,11 +410,13 @@ proc sendErrorResponse(conn: HttpConnectionRef, version: HttpVersion,
answer.add("Date: " & httpDate() & "\r\n")
if len(datatype) > 0:
answer.add("Content-Type: " & datatype & "\r\n")
answer.add("Content-Length: " & $len(databody) & "\r\n")
answer.add("Content-Length: " &
Base10.toString(uint64(len(databody))) & "\r\n")
if keepAlive:
answer.add("Connection: keep-alive\r\n")
else:
answer.add("Connection: close\r\n")
answer.add("Host: " & conn.server.getHostname() & "\r\n")
answer.add("\r\n")
if len(databody) > 0:
answer.add(databody)
@ -840,69 +851,87 @@ proc keepalive*(resp: HttpResponseRef): bool {.raises: [Defect].} =
proc setHeader*(resp: HttpResponseRef, key, value: string) {.
raises: [Defect].} =
## Sets value of header ``key`` to ``value``.
doAssert(resp.state == HttpResponseState.Empty)
resp.headersTable.set(key, value)
proc setHeaderDefault*(resp: HttpResponseRef, key, value: string) {.
raises: [Defect].} =
## Sets value of header ``key`` to ``value``, only if header ``key`` is not
## present in the headers table.
discard resp.headersTable.hasKeyOrPut(key, value)
proc addHeader*(resp: HttpResponseRef, key, value: string) {.
raises: [Defect].}=
raises: [Defect].} =
## Adds value ``value`` to header's ``key`` value.
doAssert(resp.state == HttpResponseState.Empty)
resp.headersTable.add(key, value)
proc getHeader*(resp: HttpResponseRef, key: string,
default: string = ""): string {.raises: [Defect].} =
## Returns value of header with name ``name`` or ``default``, if header is
## not present in the table.
resp.headersTable.getString(key, default)
proc hasHeader*(resp: HttpResponseRef, key: string): bool {.raises: [Defect].} =
## Returns ``true`` if header with name ``key`` present in the headers table.
key in resp.headersTable
template doHeaderDef(buf, resp, name, default) =
buf.add(name)
buf.add(": ")
buf.add(resp.getHeader(name, default))
buf.add("\r\n")
template doHeaderVal(buf, name, value) =
buf.add(name)
buf.add(": ")
buf.add(value)
buf.add("\r\n")
template checkPending(t: untyped) =
if t.state != HttpResponseState.Empty:
raiseHttpCriticalError("Response body was already sent")
proc prepareLengthHeaders(resp: HttpResponseRef, length: int): string {.
raises: [Defect].}=
var answer = $(resp.version) & " " & $(resp.status) & "\r\n"
answer.doHeaderDef(resp, "Date", httpDate())
if length > 0:
answer.doHeaderDef(resp, "Content-Type", "text/html; charset=utf-8")
answer.doHeaderVal("Content-Length", $(length))
if "Connection" notin resp.headersTable:
if not(resp.hasHeader("date")):
resp.setHeader("date", httpDate())
if not(resp.hasHeader("content-type")):
resp.setHeader("content-type", "text/html; charset=utf-8")
if not(resp.hasHeader("content-length")):
resp.setHeader("content-length", Base10.toString(uint64(length)))
if not(resp.hasHeader("server")):
resp.setHeader("server", resp.connection.server.serverIdent)
if not(resp.hasHeader("host")):
resp.setHeader("host", resp.connection.server.getHostname())
if not(resp.hasHeader("connection")):
if KeepAlive in resp.flags:
answer.doHeaderVal("Connection", "keep-alive")
resp.setHeader("connection", "keep-alive")
else:
answer.doHeaderVal("Connection", "close")
resp.setHeader("connection", "close")
var answer = $(resp.version) & " " & $(resp.status) & "\r\n"
for k, v in resp.headersTable.stringItems():
if k notin ["date", "content-type", "content-length"]:
answer.doHeaderVal(normalizeHeaderName(k), v)
if len(v) > 0:
answer.add(normalizeHeaderName(k))
answer.add(": ")
answer.add(v)
answer.add("\r\n")
answer.add("\r\n")
answer
proc prepareChunkedHeaders(resp: HttpResponseRef): string {.
raises: [Defect].} =
var answer = $(resp.version) & " " & $(resp.status) & "\r\n"
answer.doHeaderDef(resp, "Date", httpDate())
answer.doHeaderDef(resp, "Content-Type", "text/html; charset=utf-8")
answer.doHeaderDef(resp, "Transfer-Encoding", "chunked")
if "Connection" notin resp.headersTable:
if not(resp.hasHeader("date")):
resp.setHeader("date", httpDate())
if not(resp.hasHeader("content-type")):
resp.setHeader("content-type", "text/html; charset=utf-8")
if not(resp.hasHeader("transfer-encoding")):
resp.setHeader("transfer-encoding", "chunked")
if not(resp.hasHeader("server")):
resp.setHeader("server", resp.connection.server.serverIdent)
if not(resp.hasHeader("host")):
resp.setHeader("host", resp.connection.server.getHostname())
if not(resp.hasHeader("connection")):
if KeepAlive in resp.flags:
answer.doHeaderVal("Connection", "keep-alive")
resp.setHeader("connection", "keep-alive")
else:
answer.doHeaderVal("Connection", "close")
resp.setHeader("connection", "close")
var answer = $(resp.version) & " " & $(resp.status) & "\r\n"
for k, v in resp.headersTable.stringItems():
if k notin ["date", "content-type", "content-length", "transfer-encoding"]:
answer.doHeaderVal(normalizeHeaderName(k), v)
if len(v) > 0:
answer.add(normalizeHeaderName(k))
answer.add(": ")
answer.add(v)
answer.add("\r\n")
answer.add("\r\n")
answer

View File

@ -34,6 +34,11 @@ proc set*(ht: var HttpTables, key: string, value: string) =
let lowkey = key.toLowerAscii()
ht.table[lowkey] = @[value]
proc hasKeyOrPut*(ht: var HttpTables, key: string, value: string): bool =
## Returns true if ``key`` is in the table ``ht``,
## otherwise inserts ``value``.
ht.table.hasKeyOrPut(key, @[value])
proc contains*(ht: HttpTables, key: string): bool =
## Returns ``true`` if header with name ``key`` is present in HttpTable/Ref.
ht.table.contains(key.toLowerAscii())