{"id":13721807,"url":"https://andreaferretti.github.io/rosencrantz/","last_synced_at":"2025-05-07T14:30:46.429Z","repository":{"id":66316532,"uuid":"54332271","full_name":"andreaferretti/rosencrantz","owner":"andreaferretti","description":"A web DSL for Nim","archived":false,"fork":false,"pushed_at":"2022-11-28T10:38:46.000Z","size":225,"stargazers_count":197,"open_issues_count":9,"forks_count":9,"subscribers_count":7,"default_branch":"master","last_synced_at":"2025-04-10T20:29:32.865Z","etag":null,"topics":["dsl","nim","web-framework"],"latest_commit_sha":null,"homepage":"http://andreaferretti.github.io/rosencrantz/","language":"Nim","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/andreaferretti.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2016-03-20T18:01:09.000Z","updated_at":"2025-02-25T22:50:41.000Z","dependencies_parsed_at":"2023-02-25T05:00:12.281Z","dependency_job_id":null,"html_url":"https://github.com/andreaferretti/rosencrantz","commit_stats":null,"previous_names":[],"tags_count":36,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/andreaferretti%2Frosencrantz","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/andreaferretti%2Frosencrantz/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/andreaferretti%2Frosencrantz/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/andreaferretti%2Frosencrantz/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/andreaferretti","download_url":"https://codeload.github.com/andreaferretti/rosencrantz/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252895488,"owners_count":21821169,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["dsl","nim","web-framework"],"created_at":"2024-08-03T01:01:21.676Z","updated_at":"2025-05-07T14:30:45.947Z","avatar_url":"https://github.com/andreaferretti.png","language":"Nim","funding_links":[],"categories":["Web"],"sub_categories":["Web Frameworks"],"readme":"# 1. Rosencrantz\n\n![shakespeare](https://raw.githubusercontent.com/andreaferretti/rosencrantz/master/shakespeare.jpg)\n\nRosencrantz is a DSL to write web servers, inspired by [Spray](http://spray.io/)\nand its successor [Akka HTTP](http://doc.akka.io/docs/akka/2.4.2/scala/http/introduction.html).\n\nIt sits on top of [asynchttpserver](http://nim-lang.org/docs/asynchttpserver.html)\nand provides a composable way to write HTTP handlers.\n\nVersion 0.4 of Rosencrantz is tested with Nim 1.0.0, but is compatible with\nversions of Nim from 0.19.0 on.\n\nTable of contents\n-----------------\n\n\u003c!-- TOC depthfrom:1 depthto:6 withlinks:false updateonsave:false orderedlist:false --\u003e\n\n- Rosencrantz\n  - Introduction\n    - Composing handlers\n    - Starting a server\n  - Structure of the package\n  - An example\n  - Basic handlers\n    - Path handling\n    - HTTP methods\n    - Failure containment\n    - Logging\n  - Working with headers\n  - Writing custom handlers\n  - JSON support\n  - Form and querystring support\n  - Static file support\n  - CORS support\n  - API stability\n\n\u003c!-- /TOC --\u003e\n\n## 1.1. Introduction\n\nThe core abstraction in Rosencrantz is the `Handler`, which is just an alias\nfor a `proc(req: ref Request, ctx: Context): Future[Context]`. Here `Request`\nis the HTTP request from `asynchttpserver`, while `Context` is a place where\nwe accumulate information such as:\n\n* what part of the path has been matched so far;\n* what headers to emit with the response;\n* whether the request has matched a route so far.\n\nA handler usually does one or more of the following:\n\n* filter the request, by returning `ctx.reject()` if some condition is not\n  satisfied;\n* accumulate some headers;\n* actually respond to the request, by calling the `complete` function or one\n  derived from it.\n\nRosencrantz provides many of those handlers, which are described below. For the\ncomplete API, check [here](http://andreaferretti.github.io/rosencrantz/rosencrantz.html).\n\n### 1.1.1. Composing handlers\n\nThe nice thing about handlers is that they are composable. There are two ways\nto compose two headers `h1` and `h2`:\n\n* `h1 -\u003e h2` (read `h1` **and** `h2`) returns a handler that passes the request\n  through `h1` to update the context; then, if `h1` does not reject the request,\n  it passes it, together with the new context, to `h2`. Think filtering first\n  by HTTP method, then by path.\n* `h1 ~ h2` (read `h1` **or** `h2`) returns a handler that passes the request\n  through `h1`; if it rejects the request, it tries again with `h2`. Think\n  matching on two alternative paths.\n\nThe combination `h1 -\u003e h2` can also be written `h1[h2]`, which makes it nicer\nwhen composing many handlers one inside each other. Also remember that,\naccording to Nim rules, `~` has higher precedence than `-\u003e` - use parentheses\nif necessary to compose your handlers.\n\n### 1.1.2. Starting a server\n\nOnce you have a handler, you can serve it using a server from `asynchttpserver`,\nlike this:\n\n```nim\nlet server = newAsyncHttpServer()\n\nwaitFor server.serve(Port(8080), handler)\n```\n\n## 1.2. Structure of the package\n\nRosencrantz can be fully imported with just\n\n```nim\nimport rosencrantz\n```\n\nThe `rosencrantz` module just re-exports functionality from the submodules\n`rosencrantz/core`, `rosencrantz/handlers`, `rosencrantz/jsonsupport` and so\non. These modules can be imported separately. The API is available\n[here](http://andreaferretti.github.io/rosencrantz/rosencrantz.html).\n\n## 1.3. An example\n\nThe following uses some of the predefined handlers and composes them together.\nWe write a small piece of a fictionary API to save and retrieve messages, and\nwe assume we have functions such as `getMessageById` that perform the actual\nbusiness logic. This should give a feel of how the DSL looks like:\n\n```nim\nlet handler = get[\n  path(\"/api/status\")[\n    ok(getStatus())\n  ] ~\n  pathChunk(\"/api/message\")[\n    accept(\"application/json\")[\n      intSegment(proc(id: int): auto =\n        let message = getMessageById(id)\n        ok(message)\n      )\n    ]\n  ]\n] ~ post[\n  path(\"/api/new-message\")[\n    jsonBody(proc(msg: Message): auto =\n      let\n        id = generateId()\n        saved = saveMessage(id, msg)\n      if saved: ok(id)\n      else: complete(Http500, \"save failed\")\n    )\n  ]\n]\n```\n\nFor more (actually working) examples, check the `tests` directory. In particular,\n[the server example](https://github.com/andreaferretti/rosencrantz/blob/master/tests/server.nim)\ntests every handler defined in Rosencrantz, while\n[the todo example](https://github.com/andreaferretti/rosencrantz/blob/master/tests/todo.nim)\nimplements a server compliant with the [TODO backend project](http://www.todobackend.com/)\nspecs.\n\n## 1.4. Basic handlers\n\nIn order to work with Rosencrantz, you can `import rosencrantz`. If you prefer\na more fine-grained control, there are packages `rosencrantz/core` (which\ncontains the definitions common to all handlers), `rosencrantz/handlers` (for\nthe handlers we are about to show), and then more specialized handlers under\n`rosencrantz/jsonsupport`, `rosencrantz/formsupport` and so on.\n\nThe simplest handlers are:\n\n* `complete(code, body, headers)` that actually responds to the request. Here\n  `code` is an instance of `HttpCode` from `asynchttpserver`, `body` is a\n  `string` and `headers` are an instance of `StringTableRef`.\n* `ok(body)`, which is a specialization of `complete` for a response of `200 Ok`\n  with a content type of `text/plain`.\n* `notFound(body)`, which is a specialization of `complete` for a response of\n  `404 Not Found` with a content type of `text/plain`.\n* `body(p)` extracts the body of the request. Here `p` is a\n  `proc(s: string): Handler` which takes the extracted body as input and\n  returns a handler.\n\nFor instance, a simple handler that echoes back the body of the request would\nlook like\n\n```nim\nbody(proc(s: string): auto =\n  ok(s)\n)\n```\n\n### 1.4.1. Path handling\n\nThere are a few handlers to filter by path and extract path parameters:\n\n* `path(s)` filters the requests where the path is equal to `s`.\n* `pathChunk(s)` does the same but only for a prefix of the path. This means\n  that one can nest more path handlers after it, unlike `path`, that matches\n  and consumes the whole path.\n* `pathEnd(p)` extracts whatever is not matched yet of the path and passes it\n  to `p`. Here `p` is a `proc(s: string): Handler` that takes the final part of\n  the path and returns a handler.\n* `pathEnd(s)` filters the requests where the remaining path is equal\n   to `s`. Defaults to case sensitive matching, but you can use\n   `pathEnd(s, caseSensitive=false)` to do a case insensitive match.\n* `segment(p)`, that extracts a segment of path among two `/` signs. Here `p`\n  is a `proc(s: string): Handler` that takes the matched segment and return a\n  handler. This fails if the position is not just before a `/` sign.\n* `segment(s)` filters the requests where the current path segment is equal\n   to `s`. Defaults to case sensitive matching, but you can use\n   `segment(s, caseSensitive=false)` to do a case insensitive match.\n   This fails if the position is not just before a `/` sign.\n* `intSegment(p)`, works the same as `segment`, but extracts and parses an\n  integer number. It fails if the segment does not represent an integer. Here\n  `p` is a `proc(s: int): Handler`.\n\nFor instance, to match and extract parameters out of a route like\n`repeat/$msg/$n`, one would nest the above to get\n\n```nim\npathChunk(\"/repeat\")[\n  segment(proc(msg: string): auto =\n    intSegment(proc(n: int): auto =\n      someHandler\n    )\n  )\n]\n```\n\n### 1.4.2. HTTP methods\n\nTo filter by HTTP method, one can use\n\n* `verb(m)`, where `m` is a member of the `HttpMethod` enum defined in\n  the standard library `httpcore`. There are corresponding specializations\n* `get`, `post`, `put`, `delete`, `head`, `patch`, `options`, `trace` and\n  `connect`\n\n### 1.4.3. Failure containment\n\nWhen a requests falls through all routes without matching, Rosencrantz will\nreturn a standard response of `404 Not Found`. Similarly, whenever an\nexception arises, Rosencrantz will respond with `500 Server Error`.\n\nSometimes, it can be useful to have more control over failure cases. For\ninstance, you are able only to generate responses with type `application/json`:\nif the `Accept` header does not match it, you may want to return a status code\nof `406 Not Accepted`.\n\nOne way to do this is to put the 406 response as an alternative, like this:\n\n```nim\naccept(\"application/json\")[\n  someResponse\n] ~ complete(Http406, \"JSON endpoint\")\n```\n\nHowever, it can be more clear to use an equivalent combinators that wraps\nan existing handler and it returns a given failure message in case the inner\nhandler fails to match. For this, there is\n\n* `failWith(code, s)`, to be used like this:\n\n```nim\nfailWith(Http406, \"JSON endpoint\")(\n  accept(\"application/json\")[\n    someResponse\n  ]\n)\n```\n\nSimilarly, you may want to customize the behaviour of Rosencrantz when the\napplication crashes.\n\n* `crashWith(code, s, logError)` can be used to wrap your handler:\n\n```nim\ncrashWith(Http500, \"Sorry :-(\")(\n  accept(\"application/json\")[\n    someResponse\n  ]\n)\n```\n\n### 1.4.4. Logging\n\nRosencrantz supports logging in two different moments: when a request arrives,\nor when a response is produced (of course you can also manually log at any other\nmoment). In the first case, you will only have available the information about\nthe current request, while in the latter both the request and the response\nwill be available.\n\nThe two basic handlers for logging are:\n\n* `logRequest(s)`, where `s` is a format string. The string is used inside\n  the system `format` function, and it is passed the following arguments in\n  order:\n  - the HTTP method of the request\n  - the path of the resource\n  - the headers, as a table\n  - the body of the request, if any.\n* `logResponse(s)`, where `s` is a format string. The first four arguments\n  are the same as in `logRequest`; then there are\n  - the HTTP code of the response\n  - the headers of the response, as a table\n  - the body of the response, if any.\n\nSo for instance, in order to log the incoming method and path, as well as the\nHTTP code of the response, you can use the following handler:\n\n```nim\nlogResponse(\"$1 $2 - $5\")\n```\n\nwhich will produce log strings such as\n\n```\nGET /api/users/181 - 200 OK\n```\n\n## 1.5. Working with headers\n\nUnder `rosencrantz/headersupport`, there are various handlers to read HTTP\nheaders, filter requests by their values, or accumulate HTTP headers for the\nresponse.\n\n* `headers(h1, h2, ...)` adds headers for the response. Here each argument is\n  a tuple of two strings, which are a key/value pair.\n* `contentType(s)` is a specialization to emit the `Content-Type` header, so\n  is is equivalent to `headers((\"Content-Type\", s))`.\n* `readAllHeaders(p)` extract the headers as a string table. Here `p` is a\n  `proc(hs: HttpHeaders): Handler`.\n* `readHeaders(s1, p)` extracts the value of the header with key `s1` and\n  passes it to `p`, which is of type `proc(h1: string): Handler`. It rejects\n  the request if the header `s1` is not defined. There are overloads\n  `readHeaders(s1, s2, p)` and `readHeaders(s1, s2, s3, p)`, where `p` is a\n  function of two arguments (resp. three arguments). To extract more than\n  three headers, one can use `readAllHeaders` or nest `readHeaders` calls.\n* `tryReadHeaders(s1, p)` works the same as `readHeaders`, but it does not\n  reject the request if header `s` is missing; instead, `p` receives an empty\n  string as default. Again, there are overloads for two and three arguments.\n* `checkHeaders(h1, h2, ...)` filters the request for the header value. Here\n  `h1` and the other are pairs of strings, representing a key and a value. If\n  the request does not have the corresponding headers with these values, it\n  will be rejected.\n* `accept(mimetype)` is equivalent to `checkHeaders((\"Accept\", mimetype))`.\n* `addDate()` returns a handler that adds the `Date` header, formatted as\n  a GMT date in the [HTTP date format](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html).\n\nFor example, if you can return a result both as JSON or XML, according to the\nrequest, you can do\n\n```nim\naccept(\"application/json\")[\n  contentType(\"application/json\")[\n    ok(someJsonValue)\n  ]\n] ~ accept(\"text/xml\")[\n  contentType(\"text/xml\")[\n    ok(someXmlValue)\n  ]\n]\n```\n\n## 1.6. Writing custom handlers\n\nSometimes, the need arises to write handlers that perform a little more custom\nlogic than those shown above. For those cases, Rosencrantz provides a few\nprocedures and templates (under `rosencrantz/custom`) that help creating\nyour handlers.\n\n* `getRequest(p)`, where `p` is a `proc(req: ref Request): Handler`. This\n  allows you to access the whole `Request` object, and as such allows more\n  flexibility.\n* `scope` is a template that creates a local scope. It us useful when one needs\n  to define a few variables to write a little logic inline before returning an\n  actual handler.\n* `scopeAsync` is like scope, but allows asyncronous logic (for instance waiting\n  on futures) in it.\n* `makeHandler` is a macro that removes some boilerplate in writing a custom\n  handler. It accepts the body of a handler, and surrounds it with the proper\n  function declaration, etc.\n\nAn example of usage of `scope` is the following:\n\n```nim\npath(\"/using-scope\")[\n  scope do:\n    let x = \"Hello, World!\"\n    echo \"We are returning: \", x\n    return ok(x)\n]\n```\n\nAn example of usage of `scopeAsync` is the following:\n\n```nim\npath(\"/using-scope\")[\n  scopeAsync do:\n    let x = \"Hello, World!\"\n    echo \"We are returning: \", x\n    await sleepAsync(100)\n    return ok(x)\n]\n```\n\nAn example of usage of `makeHandler` is the following:\n\n```nim\npath(\"/custom-handler\")[\n  makeHandler do:\n    let x = \"Hello, World!\"\n    await req[].respond(Http200, x, {\"Content-Type\": \"text/plain;charset=utf-8\"}.newStringTable)\n    return ctx\n]\n```\n\nThat is expanded into something like:\n\n```nim\npath(\"/custom-handler\")[\n  proc innerProc() =\n    proc h(req: ref Request, ctx: Context): Future[Context] {.async.} =\n      let x = \"Hello, World!\"\n      await req[].respond(Http200, x, {\"Content-Type\": \"text/plain;charset=utf-8\"}.newStringTable)\n      return ctx\n\n    return h\n\n  innerProc()\n]\n```\n\nNotice that `makeHandler` is a little lower-level than other parts of\nRosencrantz, and requires you to know how to write a custom handler.\n\n## 1.7. JSON support\n\nRosencrantz has support to parse and respond with JSON, under the\n`rosencrantz/jsonsupport` module. It defines two typeclasses:\n\n* a type `T` is `JsonReadable` if there is function `readFromJson(json, T): T`\n  where `json` is of type `JsonNode`;\n* a type `T` is `JsonWritable` if there is a function\n  `renderToJson(t: T): JsonNode`.\n\nThe module `rosencrantz/core` contains the following handlers:\n\n* `ok(j)`, where `j` is of type `JsonNode`, that will respond with a content\n  type of `application/json`.\n* `ok(t)`, where `t` has a type `T` that is `JsonWritable`, that will respond\n  with the JSON representation of `t` and a content type of `application/json`.\n* `jsonBody(p)`, where `p` is a `proc(j: JsonNode): Handler`, that extracts the\n  body as a `JsonNode` and passes it to `p`, failing if the body is not valid\n  JSON.\n* `jsonBody(p)`, where `p` is a `proc(t: T): Handler`, where `T` is a type that\n  is `JsonReadable`; it extracts the body as a `T` and passes it to `p`, failing\n  if the body is not valid JSON or cannot be converted to `T`.\n\n## 1.8. Form and querystring support\n\nRosencrantz has support to read the body of a form, either of type\n`application/x-www-form-urlencoded` or multipart. It also supports\nparsing the querystring as `application/x-www-form-urlencoded`.\n\nThe `rosencrantz/formsupport` module defines two typeclasses:\n\n* a type `T` is `UrlDecodable` if there is function `parseFromUrl(s, T): T`\n  where `s` is of type `StringTableRef`;\n* a type `T` is `UrlMultiDecodable` if there is a function\n  `parseFromUrl(s, T): T` where `s` is of type `TableRef[string, seq[string]]`.\n\nThe module `rosencrantz/formsupport` defines the following handlers:\n\n* `formBody(p)` where `p` is a `proc(s: StringTableRef): Handler`. It will\n  parse the body as an URL-encoded form and pass the corresponding string\n  table to `p`, rejecting the request if the body is not parseable.\n* `formBody(t)` where `t` has a type `T` that is `UrlDecodable`. It will\n  parse the body as an URL-encoded form, convert it to `T`, and pass the\n  resulting object to `p`. It will reject a request if the body is not parseable\n  or if the conversion to `T` fails.\n* `formBody(p)` where `p` is a\n  `proc(s: TableRef[string, seq[string]]): Handler`. It will parse the body as\n  an URL-encoded form, accumulating repeated parameters into sequences, and pass\n  table to `p`, rejecting the request if the body is not parseable.\n* `formBody(t)` where `t` has a type `T` that is `UrlMultiDecodable`. It will\n  parse the body as an URL-encoded with repeated parameters form, convert it\n  to `T`, and pass the resulting object to `p`. It will reject a request if the\n  body is not parseable or if the conversion to `T` fails.\n\nThere are similar handlers to extract the querystring from a request:\n\n* `queryString(p)`, where `p` is a `proc(s: string): Handler` allows to generate\n  a handler from the raw querystring (not parsed into parameters yet)\n* `queryString(p)`, where `p` is a `proc(s: StringTableRef): Handler` allows to\n  generate a handler from the querystring parameters, parsed as a string table.\n* `queryString(t)` where `t` has a type `T` that is `UrlDecodable`; works the\n  same as `formBody`.\n* `queryString(p)`, where `p` is a\n  `proc(s: TableRef[string, seq[string]]): Handler` allows to generate a handler\n  from the querystring with repeated parameters, parsed as a table.\n* `queryString(t)` where `t` has a type `T` that is `UrlMultiDecodable`; works\n  the same as `formBody`.\n\nFinally, there is a handler to parse multipart forms. The results are\naccumulated inside a `MultiPart` object, which is defined by\n\n```nim\ntype\n  MultiPartFile* = object\n    filename*, contentType*, content*: string\n  MultiPart* = object\n    fields*: StringTableRef\n    files*: TableRef[string, MultiPartFile]\n```\n\nThe handler for multipart forms is:\n\n* `multipart(p)`, where `p` is a `proc(m: MultiPart): Handler` is handed\n  the result of parsing the form as multipart. In case of parsing error, an\n  exception is raised - you can choose whether to let it propagate it and\n  return a 500 error, or contain it using `failWith`.\n\n## 1.9. Static file support\n\nRosencrantz has support to serve static files or directories. For now, it is\nlimited to small files, because it does not support streaming yet.\n\nThe module `rosencrantz/staticsupport` defines the following handlers:\n\n* `file(path)`, where `path` is either absolute or relative to the current\n  working directory. It will respond by serving the content of the file, if\n  it exists and is a simple file, or reject the request if it does not exist\n  or is a directory.\n* `dir(path)`, where `path` is either absolute or relative to the current\n  working directory. It will respond by taking the part of the URL\n  requested that is not matched yet, concatenate it to `path`, and serve the\n  corresponding file. Again, if the file does not exist or is a directory, the\n  handler will reject the request.\n\nTo make things concrete, consider the following handler:\n\n```nim\npath(\"/main\")[\n  file(\"index.html\")\n] ~\npathChunk(\"/static\")[\n  dir(\"public\")\n]\n```\n\nThis will server the file `index.html` when the request is for the path `/main`,\nand it will serve the contents of the directory `public` under the URL `static`.\nSo, for instance, a request for `/static/css/boostrap.css` will return the\ncontents of the file `./public/css/boostrap.css`.\n\nAll static handlers use the [mimetypes module](http://nim-lang.org/docs/mimetypes.html)\nto try to guess the correct content type depending on the file extension. This\nshould be usually enough; if you need more control, you can wrap a `file`\nhandler inside a `contentType` handler to override the content type.\n\n**Note** Due to a bug in Nim 0.14.2, the static handlers will not work on this\nversion. They work just fine on Nim 0.14.0 or on devel.\n\n\n## 1.10. CORS support\n\nRosencrantz has support for [Cross-Origin requests](https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS)\nunder the module `rosencrantz/corssupport`.\n\nThe following are essentially helper functions to produce headers related to\nhandling cross-origin HTTP requests, as well as reading common headers in\npreflight requests. These handlers are available:\n\n* `accessControlAllowOrigin(origin)` produces the header `Access-Control-Allow-Origin`\n  with the provided `origin` value.\n* `accessControlAllowAllOrigins` produces the header `Access-Control-Allow-Origin`\n  with the value `*`, which amounts to accepting all origins.\n* `accessControlExposeHeaders(headers)` produces the header `Access-Control-Expose-Headers`,\n  which is used to control which headers are exposed to the client.\n* `accessControlMaxAge(seconds)` produces the header `Access-Control-Max-Age`,\n  which controls the time validity for the preflight request.\n* `accessControlAllowCredentials(b)`, where `b` is a boolean value, produces\n  the header `Access-Control-Allow-Credentials`, which is used to allow the\n  client to pass cookies and headers related to HTTP authentication.\n* `accessControlAllowMethods(methods)`, where `methods` is an openarray of\n  `HttpMethod`, produces the header `Access-Control-Allow-Methods`, which is\n  used in preflight requests to communicate which methods are allowed on the\n  resource.\n* `accessControlAllowHeaders(headers)` produces the header `Access-Control-Allow-Headers`,\n  which is used in the preflight request to control which headers can be added\n  by the client.\n* `accessControlAllow(origin, methods, headers)` is used in preflight requests\n  for the common combination of specifying the origin as well as methods and\n  headers accepted.\n* `readAccessControl(p)` is used to extract information in the preflight request\n  from the CORS related headers at once.\n  Here `p` is a `proc(origin: string, m: HttpMethod, headers: seq[string]`\n  that will receive the origin of the request, the desired method and the\n  additional headers to be provided, and will return a suitable response.\n\n## 1.11. API stability\n\nWhile the basic design is not going to change, the API is not completely\nstable yet. It is possible that the `Context` will change to accomodate some\nmore information, or that it will be passed as a `ref` to handlers.\n\nAs long as you compose the handlers defined above, everything will continue to\nwork, but if you write your own handlers by hand, this is something to be\naware of.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/andreaferretti.github.io%2Frosencrantz%2F","html_url":"https://awesome.ecosyste.ms/projects/andreaferretti.github.io%2Frosencrantz%2F","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/andreaferretti.github.io%2Frosencrantz%2F/lists"}