guildenstern/httpserver

Search:
Group by:

HTTP server, with three operating modes

see examples/httptest.nim, examples/streamingposttest.nim, and examples/replychunkedtest.nim for examples.

Types

ContentType = enum
  NoBody,                   ## offers slightly faster handling for requests like GET that do not have a body
  Compact,                  ## the default mode. Whole request body must fit into the request string (size defined with [bufferlength] parameter), from where it can then be accessed with [getRequest], [isBody] and [getBody] procs
  Streaming                  ## read the body yourself with the [receiveStream] iterator 
mode of the server
HttpContext = ref object of SocketContext
  request*: string
  requestlen*: int
  uristart*: int
  urilen*: int
  methlen*: int
  bodystart*: int
  contentlength*: int64
  contentreceived*: int64
  contentdelivered*: int64
  headers*: StringTableRef
HttpServer = ref object of GuildenServer
  contenttype*: ContentType
  maxheaderlength* = 10000   ## Maximum allowed size for http header part.
  bufferlength* = 100000     ## Every thread will reserve this much memory, for buffering the incoming request. Must be larger than maxheaderlength.
  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
  headerfields*: seq[string] ## list of header fields to be parsed 
  
SocketState = enum
  Fail = -1, TryAgain = 0, Progress = 1, Complete = 2

Lets

longdivider = "\r\n\r\n"
shortdivider = "\r\n"

Consts

MSG_DONTWAIT = 64'i32
MSG_MORE = 32768'i32

Procs

proc checkSocketState(ret: int): SocketState {....raises: [], tags: [RootEffect],
    forbids: [].}
proc getBody(): string {....raises: [], tags: [RootEffect], 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 getContentLength(): bool {....raises: [], tags: [RootEffect], forbids: [].}
proc getMethod(): string {....raises: [], tags: [], forbids: [].}
Returns the method as a string copy
proc getRequest(): string {....raises: [], tags: [], forbids: [].}
proc getUri(): string {....raises: [], tags: [], forbids: [].}
Returns the uri as a string copy
proc handleHttpThreadInitialization(gserver: GuildenServer) {....raises: [],
    tags: [RootEffect], forbids: [].}
proc initHttpServer(s: HttpServer; loglevel: LogLevel; parserequestline: bool;
                    contenttype: ContentType; headerfields: openArray[string]) {.
    ...raises: [], tags: [], forbids: [].}
proc isBody(body: string): bool {....raises: [], tags: [RootEffect], forbids: [].}
Compares the body without making a string copy
proc isHeaderreceived(previouslen, currentlen: int): bool {....raises: [],
    tags: [], forbids: [].}
proc isHttpContext(): bool {....raises: [], tags: [], forbids: [].}
proc isMethod(amethod: string): bool {....raises: [], tags: [], forbids: [].}
Compares method 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;
                   contenttype = Compact; headerfields: openArray[string] = []): HttpServer {.
    ...raises: [], tags: [RootEffect], forbids: [].}

Constructs a new http server. The essential thing here is to set the onrequestcallback proc. When it is triggered, the http thread-local socket context is accessible.

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

proc parseMethod(): bool {....raises: [], tags: [RootEffect], forbids: [].}
proc parseRequestLine(): bool {....gcsafe, raises: [], tags: [RootEffect],
                                forbids: [].}
proc prepareHttpContext(socketdata: ptr SocketData) {.inline, ...raises: [],
    tags: [], forbids: [].}
proc readHeader(): bool {....gcsafe, raises: [], tags: [RootEffect], forbids: [].}
proc reply(code: HttpCode): SocketState {.discardable, inline, ...gcsafe,
    raises: [], tags: [RootEffect], forbids: [].}
proc reply(code: HttpCode; body: ptr string; headers: openArray[string]) {.
    inline, ...gcsafe, raises: [], tags: [RootEffect], forbids: [].}
proc reply(code: HttpCode; body: ptr string; headers: ptr string) {.inline,
    ...gcsafe, raises: [], tags: [RootEffect], forbids: [].}
proc reply(code: HttpCode; body: ptr string; lengthstring: string; length: int;
           headers: ptr string; moretocome: bool): SocketState {....gcsafe,
    raises: [], tags: [RootEffect], forbids: [].}
One-shot reply to a request
proc replyContinueChunked(chunk: string): bool {....raises: [], tags: [RootEffect],
    forbids: [].}
proc replyFinish(): SocketState {.discardable, inline, ...gcsafe, raises: [],
                                  tags: [RootEffect], forbids: [].}
proc replyFinishChunked(): bool {.discardable, ...raises: [], tags: [RootEffect],
                                  forbids: [].}
proc replyMore(bodypart: ptr string; start: int; partlength: int = -1): (
    SocketState, int) {.inline, ...gcsafe, raises: [], tags: [RootEffect],
                        forbids: [].}
Continuation to replyStart.
proc replyStart(code: HttpCode; contentlength: int; headers: openArray[string]): SocketState {.
    inline, ...gcsafe, raises: [], tags: [RootEffect], forbids: [].}
proc replyStart(code: HttpCode; contentlength: int; headers: ptr string = nil): SocketState {.
    inline, ...gcsafe, raises: [], tags: [RootEffect], forbids: [].}
Start replying to a request (continue with replyMore and replyFinish). If you do not know the content-length yet, use replyStartChunked instead.
proc replyStartChunked(code: HttpCode = Http200; headers: openArray[string] = []): bool {.
    ...raises: [], tags: [RootEffect], forbids: [].}

Starts replying http response as Transfer-encoding: chunked. Mainly for sending dynamic data, where Content-length header cannot be set.

Continue response with calls to replyContinueChunked.

End response with replyContinueChunked.

See examples/replychunkedtest.nim for a concrete example.

proc startsUri(uristart: string): bool {....raises: [], tags: [], forbids: [].}
Compares the beginning of the uri without making a string copy

Iterators

iterator receiveStream(): (SocketState, string) {....gcsafe, raises: [],
    tags: [RootEffect], forbids: [].}
Receives a http request in chunks, yielding the state of operation and a possibly received new chuck on every iteration. With this, you can receive data incrementally without worries about main memory usage. See examples/streamingposttest.nim for a concrete working example of how to use this iterator.

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