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 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