guildenstern/httpserver

Search:
Group by:

A server for handling HTTP/1.1 traffic.

The various reply procs and templates should be self-explanatory. Exception is the replyStart - replyMore - replyFinish combo. They should be used when the response is not available as one chunk (for example, due to its size.) In this case, one replyStart starts the response, followed by one ore more replyMores, and finally a replyFinish finishes the response.

Example

import guildenstern/[dispatcher, httpserver]
import httpclient

const headerfields = ["afield", "anotherfield"]
var headers {.threadvar.}: array[2, string]

proc onRequest() =
  parseHeaders(headerfields, headers)
  echo headers
  reply(Http204)

let server = newHttpServer(onRequest)
server.start(5050)
var client = newHttpClient()
for i in 1 .. 10:
  client.headers = newHttpHeaders({"afield": "value" & $i, "bfield": "bfieldvalue"})
  discard client.request("http://localhost:5050")

Types

HttpContext = ref object of SocketContext
  request*: string           ## Contains the request itself in [0 ..< requestlen]
  requestlen*: int
  uristart*: int
  urilen*: int
  methlen*: int
  bodystart*: int
HttpServer = ref object of GuildenServer
  maxheaderlength* = 10000   ## Maximum allowed size for http header part.
  maxrequestlength* = 100000 ## Maximum allowed size for http requests. Every thread will reserve this much memory.
  sockettimeoutms* = 5000    ## If socket is unresponsive for longer, it will be closed.
  requestCallback*: proc () {....gcsafe, nimcall, ...raises: [].}
  parserequestline*: bool    ## If you don't need uri or method, but need max perf, set this to false
  hascontent*: bool          ## When serving GET or other method without body, set this to false
  
SocketState = enum
  Fail = -1, TryAgain = 0, Progress = 1, Complete = 2

Procs

proc getBody(): string {....raises: [], tags: [], forbids: [].}
Returns the body as a string copy. When --experimental:views compiler switch is used, there is also getBodyview proc that does not take a copy.
proc getBodylen(): int {....raises: [], tags: [], forbids: [].}
proc getMethod(): string {....raises: [], tags: [], forbids: [].}
When parserequestline == true, returns the method as a string copy
proc getRequest(): string {....raises: [], tags: [], forbids: [].}
proc getUri(): string {....raises: [], tags: [], forbids: [].}
When parserequestline == true, returns the uri as a string copy
proc isBody(body: string): bool {....raises: [], tags: [], forbids: [].}
Compares the body without making a string copy
proc isHttpContext(): bool {....raises: [], tags: [], forbids: [].}
proc isMethod(amethod: string): bool {....raises: [], tags: [], forbids: [].}
Compares method uri without making a string copy
proc isUri(uri: string): bool {....raises: [], tags: [], forbids: [].}
Compares the uri without making a string copy
proc newHttpServer(onrequestcallback: proc () {....gcsafe, nimcall, ...raises: [].};
                   loglevel = LogLevel.WARN; parserequestline = true;
                   hascontent = true): HttpServer {....raises: [], tags: [],
    forbids: [].}

Constructs a new http server. The essential thing here is to set the onrequestcallback proc. When it is triggered in some thread, that thread offers access to the http socket context.

If you want to tinker with maxheaderlength, maxrequestlength and sockettimeoutms, that is best done after the server is constructed but before it is started.

proc parseAllHeaders(headers: StringTableRef) {....raises: [], tags: [],
    forbids: [].}
Parses all headers into given strtabs.StringTable.
proc parseHeaders(fields: openArray[string]; toarray: var openArray[string]) {.
    ...raises: [], tags: [], forbids: [].}
Parses header fields values into toarray. See example above.
proc reply(code: HttpCode): SocketState {.discardable, inline, ...gcsafe,
    raises: [], tags: [], forbids: [].}
proc reply(code: HttpCode; body: ptr string; headers: openArray[string]) {.
    inline, ...gcsafe, raises: [], tags: [], forbids: [].}
proc reply(code: HttpCode; body: ptr string; headers: ptr string) {.inline,
    ...gcsafe, raises: [], tags: [], forbids: [].}
proc replyFinish(): SocketState {.discardable, inline, ...gcsafe, raises: [],
                                  tags: [], forbids: [].}
proc replyMore(bodypart: ptr string; start: int; partlength: int = -1): (
    SocketState, int) {.inline, ...gcsafe, raises: [], tags: [], forbids: [].}
proc replyStart(code: HttpCode; contentlength: int; headers: openArray[string]): SocketState {.
    inline, ...gcsafe, raises: [], tags: [], forbids: [].}
proc replyStart(code: HttpCode; contentlength: int; headers: ptr string = nil): SocketState {.
    inline, ...gcsafe, raises: [], tags: [], forbids: [].}
proc startsUri(uristart: string): bool {....raises: [], tags: [], forbids: [].}
Compares the beginning of the uri without making a string copy

Templates

template http(): untyped
Casts the socketcontext thread local variable into a HttpContext
template reply(body: string)
template reply(body: string; headers: openArray[string])
template reply(code: HttpCode; body: string)
template reply(code: HttpCode; body: string; headers: openArray[string])
template reply(code: HttpCode; headers: openArray[string])
template replyMore(bodypart: string): bool
template server(): untyped
Casts the socketcontext.socketdata.server into a HttpServer