├── _config.yml ├── docs ├── html │ ├── naboris │ │ ├── Naboris │ │ │ ├── .dune-keep │ │ │ ├── Query │ │ │ │ └── index.html │ │ │ ├── RequestHandler │ │ │ │ └── index.html │ │ │ ├── MimeTypes │ │ │ │ └── index.html │ │ │ ├── ErrorHandler │ │ │ │ └── index.html │ │ │ ├── Middleware │ │ │ │ └── index.html │ │ │ ├── Cookie │ │ │ │ └── index.html │ │ │ ├── Session │ │ │ │ └── index.html │ │ │ ├── Router │ │ │ │ └── index.html │ │ │ ├── SessionConfig │ │ │ │ └── index.html │ │ │ ├── Route │ │ │ │ └── index.html │ │ │ ├── SessionManager │ │ │ │ └── index.html │ │ │ ├── Method │ │ │ │ └── index.html │ │ │ └── Req │ │ │ │ └── index.html │ │ ├── Naboris__ │ │ │ ├── .dune-keep │ │ │ ├── Static │ │ │ │ └── index.html │ │ │ ├── Query │ │ │ │ └── index.html │ │ │ ├── MimeTypes │ │ │ │ └── index.html │ │ │ ├── RequestHandler │ │ │ │ └── index.html │ │ │ ├── ErrorHandler │ │ │ │ └── index.html │ │ │ ├── Cookie │ │ │ │ └── index.html │ │ │ ├── Middleware │ │ │ │ └── index.html │ │ │ ├── Session │ │ │ │ └── index.html │ │ │ ├── Router │ │ │ │ └── index.html │ │ │ ├── SessionConfig │ │ │ │ └── index.html │ │ │ ├── Server │ │ │ │ └── index.html │ │ │ ├── Route │ │ │ │ └── index.html │ │ │ ├── SessionManager │ │ │ │ └── index.html │ │ │ ├── Method │ │ │ │ └── index.html │ │ │ └── Req │ │ │ │ └── index.html │ │ ├── Naboris__Query │ │ │ ├── .dune-keep │ │ │ └── index.html │ │ ├── Naboris__Req │ │ │ ├── .dune-keep │ │ │ └── index.html │ │ ├── Naboris__Res │ │ │ └── .dune-keep │ │ ├── Naboris__Route │ │ │ ├── .dune-keep │ │ │ └── index.html │ │ ├── Naboris__Cookie │ │ │ ├── .dune-keep │ │ │ └── index.html │ │ ├── Naboris__Method │ │ │ ├── .dune-keep │ │ │ └── index.html │ │ ├── Naboris__Middleware │ │ │ ├── .dune-keep │ │ │ └── index.html │ │ ├── Naboris__MimeTypes │ │ │ ├── .dune-keep │ │ │ └── index.html │ │ ├── Naboris__Router │ │ │ ├── .dune-keep │ │ │ └── index.html │ │ ├── Naboris__Server │ │ │ ├── .dune-keep │ │ │ └── index.html │ │ ├── Naboris__Session │ │ │ ├── .dune-keep │ │ │ └── index.html │ │ ├── Naboris__Static │ │ │ ├── .dune-keep │ │ │ └── index.html │ │ ├── Naboris__ErrorHandler │ │ │ ├── .dune-keep │ │ │ └── index.html │ │ ├── Naboris__RequestHandler │ │ │ ├── .dune-keep │ │ │ └── index.html │ │ ├── Naboris__ServerConfig │ │ │ └── .dune-keep │ │ ├── Naboris__SessionConfig │ │ │ ├── .dune-keep │ │ │ └── index.html │ │ ├── Naboris__SessionManager │ │ │ ├── .dune-keep │ │ │ └── index.html │ │ └── index.html │ └── index.html └── index.html ├── .esyrc ├── dune-project ├── src ├── Query.ml ├── ErrorHandler.ml ├── RequestHandler.ml ├── Middleware.ml ├── Query.mli ├── utils │ ├── Static.ml │ ├── MimeTypes.mli │ ├── Cookie.mli │ ├── Etag.ml │ ├── Cookie.ml │ └── DateUtils.ml ├── dune ├── Session.ml ├── ErrorHandler.mli ├── Method.mli ├── Route.ml ├── Router.mli ├── Session.mli ├── SessionConfig.mli ├── SessionConfig.ml ├── Route.mli ├── Method.ml ├── Router.ml ├── SessionManager.mli ├── Naboris.ml ├── SessionManager.ml ├── Req.mli ├── Req.ml ├── Naboris.mli ├── Res.mli └── ServerConfig.ml ├── test ├── integration-test │ ├── TestSession.ml │ └── test_assets │ │ └── text │ │ ├── text_file.txt │ │ └── 1024.txt ├── dune ├── RouterTest.ml ├── utils │ ├── DateUtilsTest.ml │ ├── EtagTest.ml │ └── CookieTest.ml ├── test.ml ├── SessionManagerTest.ml └── MethodTest.ml ├── ci ├── test-standard.sh ├── test-libev.sh ├── deploy-docs.sh ├── build-docs.sh └── test-under-load.sh ├── deploy_key.enc ├── docs-src ├── static │ ├── favicon.ico │ ├── images │ │ └── hero-desert.jpg │ ├── logos │ │ ├── logo-w-text.png │ │ ├── logo-color-16x16.png │ │ ├── logo-color-32x32.png │ │ └── logo-color-96x96.png │ ├── fonts │ │ ├── fa │ │ │ ├── fa-brands-400.eot │ │ │ ├── fa-brands-400.ttf │ │ │ ├── fa-brands-400.woff │ │ │ ├── fa-regular-400.eot │ │ │ ├── fa-regular-400.ttf │ │ │ ├── fa-solid-900.eot │ │ │ ├── fa-solid-900.ttf │ │ │ ├── fa-solid-900.woff │ │ │ ├── fa-solid-900.woff2 │ │ │ ├── fa-brands-400.woff2 │ │ │ ├── fa-regular-400.woff │ │ │ └── fa-regular-400.woff2 │ │ └── google │ │ │ ├── noto-sans-greek.woff2 │ │ │ ├── noto-sans-latin.woff2 │ │ │ ├── noto-sans-cyrillic.woff2 │ │ │ ├── noto-sans-devanagari.woff2 │ │ │ ├── noto-sans-greek-ext.woff2 │ │ │ ├── noto-sans-latin-ext.woff2 │ │ │ ├── noto-sans-vietnamese.woff2 │ │ │ └── noto-sans-cyrillic-ext.woff2 │ ├── README.md │ └── icons │ │ ├── reason.svg │ │ └── ocaml.svg ├── assets │ ├── js │ │ ├── pages.js │ │ ├── main-actions.js │ │ └── shared.js │ ├── sass │ │ ├── _markdown.scss │ │ ├── _icons.scss │ │ ├── _bulma.scss │ │ ├── main.scss │ │ └── _fonts.scss │ └── README.md ├── content │ └── docs │ │ ├── scaffolding.md │ │ ├── guides │ │ ├── performance-best-practices.md │ │ ├── security-best-practices.md │ │ └── templating-engines.md │ │ └── quick-start │ │ ├── server-configuration.md │ │ ├── static-files.md │ │ └── installation.md ├── components │ ├── README.md │ ├── nav │ │ ├── Search.vue │ │ └── NavBar.vue │ └── Footer.vue ├── jsconfig.json ├── .editorconfig ├── .babelrc ├── layouts │ ├── README.md │ └── default.vue ├── test │ └── nav │ │ └── NavBar.spec.js ├── pages │ ├── README.md │ ├── thank-you │ │ └── index.vue │ ├── quick-start │ │ └── index.vue │ └── guides │ │ └── index.vue ├── plugins │ ├── README.md │ └── matomo.js ├── .eslintrc.js ├── middleware │ └── README.md ├── store │ ├── README.md │ └── index.js ├── README.md ├── jest.config.js ├── package.json ├── .gitignore └── nuxt.config.js ├── load-test ├── dune ├── scripts │ ├── build-and-start.sh │ └── load-test.sh ├── Load_test.ml └── public │ └── random.txt ├── Makefile ├── .gitignore ├── LICENSE ├── naboris.opam ├── .travis.yml ├── libev.json ├── .githooks └── pre-commit └── package.json /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-cayman -------------------------------------------------------------------------------- /docs/html/naboris/Naboris/.dune-keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/html/naboris/Naboris__/.dune-keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.esyrc: -------------------------------------------------------------------------------- 1 | { 2 | "prefixPath": ".esy" 3 | } -------------------------------------------------------------------------------- /docs/html/naboris/Naboris__Query/.dune-keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/html/naboris/Naboris__Req/.dune-keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/html/naboris/Naboris__Res/.dune-keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/html/naboris/Naboris__Route/.dune-keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/html/naboris/Naboris__Cookie/.dune-keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/html/naboris/Naboris__Method/.dune-keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/html/naboris/Naboris__Middleware/.dune-keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/html/naboris/Naboris__MimeTypes/.dune-keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/html/naboris/Naboris__Router/.dune-keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/html/naboris/Naboris__Server/.dune-keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/html/naboris/Naboris__Session/.dune-keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/html/naboris/Naboris__Static/.dune-keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dune-project: -------------------------------------------------------------------------------- 1 | (lang dune 1.6) 2 | (name naboris) -------------------------------------------------------------------------------- /src/Query.ml: -------------------------------------------------------------------------------- 1 | module QueryMap = Map.Make(String) -------------------------------------------------------------------------------- /docs/html/naboris/Naboris__ErrorHandler/.dune-keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/html/naboris/Naboris__RequestHandler/.dune-keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/html/naboris/Naboris__ServerConfig/.dune-keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/html/naboris/Naboris__SessionConfig/.dune-keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/html/naboris/Naboris__SessionManager/.dune-keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/integration-test/TestSession.ml: -------------------------------------------------------------------------------- 1 | type t = { username : string } -------------------------------------------------------------------------------- /test/integration-test/test_assets/text/text_file.txt: -------------------------------------------------------------------------------- 1 | Hello world! -------------------------------------------------------------------------------- /ci/test-standard.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | esy install 4 | esy b dune runtest -------------------------------------------------------------------------------- /deploy_key.enc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shawn-mcginty/naboris/HEAD/deploy_key.enc -------------------------------------------------------------------------------- /src/ErrorHandler.ml: -------------------------------------------------------------------------------- 1 | type t = exn -> Route.t -> ((string * string) list * string) Lwt.t -------------------------------------------------------------------------------- /ci/test-libev.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | esy @libev install 4 | esy @libev b dune runtest -------------------------------------------------------------------------------- /src/RequestHandler.ml: -------------------------------------------------------------------------------- 1 | type 'sessionData t = Route.t -> 'sessionData Req.t -> Res.t -> Res.t Lwt.t -------------------------------------------------------------------------------- /docs-src/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shawn-mcginty/naboris/HEAD/docs-src/static/favicon.ico -------------------------------------------------------------------------------- /docs-src/assets/js/pages.js: -------------------------------------------------------------------------------- 1 | export default { 2 | MAIN: 'MAIN', 3 | GETTING_STARTED: 'GETTING_STARTED' 4 | }; 5 | -------------------------------------------------------------------------------- /load-test/dune: -------------------------------------------------------------------------------- 1 | (executable 2 | (name load_test) 3 | (libraries lwt lwt.unix re naboris) 4 | ) 5 | (include_subdirs unqualified) -------------------------------------------------------------------------------- /src/Middleware.ml: -------------------------------------------------------------------------------- 1 | type 'sessionData t = 'sessionData RequestHandler.t -> Route.t -> 'sessionData Req.t -> Res.t -> Res.t Lwt.t -------------------------------------------------------------------------------- /docs-src/static/images/hero-desert.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shawn-mcginty/naboris/HEAD/docs-src/static/images/hero-desert.jpg -------------------------------------------------------------------------------- /docs-src/static/logos/logo-w-text.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shawn-mcginty/naboris/HEAD/docs-src/static/logos/logo-w-text.png -------------------------------------------------------------------------------- /docs-src/static/fonts/fa/fa-brands-400.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shawn-mcginty/naboris/HEAD/docs-src/static/fonts/fa/fa-brands-400.eot -------------------------------------------------------------------------------- /docs-src/static/fonts/fa/fa-brands-400.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shawn-mcginty/naboris/HEAD/docs-src/static/fonts/fa/fa-brands-400.ttf -------------------------------------------------------------------------------- /docs-src/static/fonts/fa/fa-brands-400.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shawn-mcginty/naboris/HEAD/docs-src/static/fonts/fa/fa-brands-400.woff -------------------------------------------------------------------------------- /docs-src/static/fonts/fa/fa-regular-400.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shawn-mcginty/naboris/HEAD/docs-src/static/fonts/fa/fa-regular-400.eot -------------------------------------------------------------------------------- /docs-src/static/fonts/fa/fa-regular-400.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shawn-mcginty/naboris/HEAD/docs-src/static/fonts/fa/fa-regular-400.ttf -------------------------------------------------------------------------------- /docs-src/static/fonts/fa/fa-solid-900.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shawn-mcginty/naboris/HEAD/docs-src/static/fonts/fa/fa-solid-900.eot -------------------------------------------------------------------------------- /docs-src/static/fonts/fa/fa-solid-900.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shawn-mcginty/naboris/HEAD/docs-src/static/fonts/fa/fa-solid-900.ttf -------------------------------------------------------------------------------- /docs-src/static/fonts/fa/fa-solid-900.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shawn-mcginty/naboris/HEAD/docs-src/static/fonts/fa/fa-solid-900.woff -------------------------------------------------------------------------------- /docs-src/static/fonts/fa/fa-solid-900.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shawn-mcginty/naboris/HEAD/docs-src/static/fonts/fa/fa-solid-900.woff2 -------------------------------------------------------------------------------- /docs-src/static/logos/logo-color-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shawn-mcginty/naboris/HEAD/docs-src/static/logos/logo-color-16x16.png -------------------------------------------------------------------------------- /docs-src/static/logos/logo-color-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shawn-mcginty/naboris/HEAD/docs-src/static/logos/logo-color-32x32.png -------------------------------------------------------------------------------- /docs-src/static/logos/logo-color-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shawn-mcginty/naboris/HEAD/docs-src/static/logos/logo-color-96x96.png -------------------------------------------------------------------------------- /docs-src/static/fonts/fa/fa-brands-400.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shawn-mcginty/naboris/HEAD/docs-src/static/fonts/fa/fa-brands-400.woff2 -------------------------------------------------------------------------------- /docs-src/static/fonts/fa/fa-regular-400.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shawn-mcginty/naboris/HEAD/docs-src/static/fonts/fa/fa-regular-400.woff -------------------------------------------------------------------------------- /docs-src/static/fonts/fa/fa-regular-400.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shawn-mcginty/naboris/HEAD/docs-src/static/fonts/fa/fa-regular-400.woff2 -------------------------------------------------------------------------------- /docs-src/static/fonts/google/noto-sans-greek.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shawn-mcginty/naboris/HEAD/docs-src/static/fonts/google/noto-sans-greek.woff2 -------------------------------------------------------------------------------- /docs-src/static/fonts/google/noto-sans-latin.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shawn-mcginty/naboris/HEAD/docs-src/static/fonts/google/noto-sans-latin.woff2 -------------------------------------------------------------------------------- /docs-src/static/fonts/google/noto-sans-cyrillic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shawn-mcginty/naboris/HEAD/docs-src/static/fonts/google/noto-sans-cyrillic.woff2 -------------------------------------------------------------------------------- /docs-src/static/fonts/google/noto-sans-devanagari.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shawn-mcginty/naboris/HEAD/docs-src/static/fonts/google/noto-sans-devanagari.woff2 -------------------------------------------------------------------------------- /docs-src/static/fonts/google/noto-sans-greek-ext.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shawn-mcginty/naboris/HEAD/docs-src/static/fonts/google/noto-sans-greek-ext.woff2 -------------------------------------------------------------------------------- /docs-src/static/fonts/google/noto-sans-latin-ext.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shawn-mcginty/naboris/HEAD/docs-src/static/fonts/google/noto-sans-latin-ext.woff2 -------------------------------------------------------------------------------- /docs-src/static/fonts/google/noto-sans-vietnamese.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shawn-mcginty/naboris/HEAD/docs-src/static/fonts/google/noto-sans-vietnamese.woff2 -------------------------------------------------------------------------------- /docs-src/static/fonts/google/noto-sans-cyrillic-ext.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shawn-mcginty/naboris/HEAD/docs-src/static/fonts/google/noto-sans-cyrillic-ext.woff2 -------------------------------------------------------------------------------- /docs-src/assets/sass/_markdown.scss: -------------------------------------------------------------------------------- 1 | .frontmatter-markdown { 2 | 3 | h4 { 4 | margin-top: 3em; 5 | a { 6 | padding-top: 3em; 7 | } 8 | } 9 | 10 | } -------------------------------------------------------------------------------- /docs-src/content/docs/scaffolding.md: -------------------------------------------------------------------------------- 1 | ```bash 2 | $ git clone git@github.com:shawn-mcginty/naboris-re-scaffold.git 3 | $ npm run install 4 | $ npm run build 5 | $ npm run start 6 | ``` -------------------------------------------------------------------------------- /src/Query.mli: -------------------------------------------------------------------------------- 1 | (** Implements {{: https://caml.inria.fr/pub/docs/manual-ocaml/libref/Map.Make.html} Map} 2 | with [key] of [string]. *) 3 | module QueryMap : Map.S with type key = String.t -------------------------------------------------------------------------------- /src/utils/Static.ml: -------------------------------------------------------------------------------- 1 | let getFilePath basePath pathParts = 2 | let slash = 3 | match Sys.os_type with 4 | | _ -> "/" 5 | in 6 | basePath ^ List.fold_left (fun a b -> a ^ slash ^ b) slash pathParts -------------------------------------------------------------------------------- /src/dune: -------------------------------------------------------------------------------- 1 | (library 2 | (name naboris) 3 | (public_name naboris) 4 | (libraries digestif httpaf httpaf-lwt-unix lwt lwt.unix re uri base64) 5 | (preprocess (pps lwt_ppx)) 6 | ) 7 | (include_subdirs unqualified) -------------------------------------------------------------------------------- /docs-src/assets/js/main-actions.js: -------------------------------------------------------------------------------- 1 | export default { 2 | SET_DARK_MODE: 'TOGGLE_DARK_MODE', 3 | SET_LANGUAGE: 'TOGGLE_LANGUAGE', 4 | DARK_MODE: 'NABORIS_DARK_MODE', 5 | LANG: 'NABORIS_LANG', 6 | SET_LOADING: 'SET_LOADING' 7 | }; 8 | -------------------------------------------------------------------------------- /src/Session.ml: -------------------------------------------------------------------------------- 1 | type 'sessionData t = { 2 | id : string; 3 | data : 'sessionData; 4 | } 5 | 6 | let create id data = { 7 | id; data; 8 | } 9 | 10 | let data sessionData = sessionData.data 11 | 12 | let id sessionData = sessionData.id -------------------------------------------------------------------------------- /src/utils/MimeTypes.mli: -------------------------------------------------------------------------------- 1 | (** Exposed only for unit testing *) 2 | val getExtension : string -> string 3 | 4 | (** Given a filename returns content type. 5 | Defaults to ["text/plain"] if type cannot be inferred. *) 6 | val getMimeType : string -> string -------------------------------------------------------------------------------- /docs-src/components/README.md: -------------------------------------------------------------------------------- 1 | # COMPONENTS 2 | 3 | **This directory is not required, you can delete it if you don't want to use it.** 4 | 5 | The components directory contains your Vue.js Components. 6 | 7 | _Nuxt.js doesn't supercharge these components._ 8 | -------------------------------------------------------------------------------- /ci/deploy-docs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | npm run build-docs 4 | cd docs-src 5 | npm install 6 | npm run load-docs 7 | npm run generate 8 | scp -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i ../deploy_key -r dist naboris-docs@shawnmcginty.com:~/www -------------------------------------------------------------------------------- /test/dune: -------------------------------------------------------------------------------- 1 | (executable 2 | (name test) 3 | (libraries alcotest alcotest-lwt httpaf httpaf-lwt-unix lwt lwt.unix cohttp cohttp-lwt-unix re naboris) 4 | ) 5 | (include_subdirs unqualified) 6 | (alias 7 | (name runtest) 8 | (deps test.exe) 9 | (action (run %{deps} --verbose))) -------------------------------------------------------------------------------- /load-test/scripts/build-and-start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # exit if any return non 0 3 | set -e 4 | npm run build-load-test-server 5 | echo old: $DUNE_BUILD_DIR 6 | export DUNE_BUILD_DIR="" 7 | source command-env 8 | echo new: $DUNE_BUILD_DIR 9 | $DUNE_BUILD_DIR/default/load-test/load_test.exe -------------------------------------------------------------------------------- /docs-src/jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "paths": { 5 | "~/*": ["./*"], 6 | "@/*": ["./*"], 7 | "~~/*": ["./*"], 8 | "@@/*": ["./*"] 9 | } 10 | }, 11 | "exclude": ["node_modules", ".nuxt", "dist"] 12 | } 13 | -------------------------------------------------------------------------------- /src/ErrorHandler.mli: -------------------------------------------------------------------------------- 1 | (** Called when [Res.reportError] is called. Expects return values 2 | of an [Lwt.t] promise containing a tuple of [headers] ([(string * string) list]) and 3 | [response_body] ([string]). *) 4 | type t = exn -> Route.t -> ((string * string) list * string) Lwt.t -------------------------------------------------------------------------------- /src/utils/Cookie.mli: -------------------------------------------------------------------------------- 1 | (** Given the session id key and cookie header string values extracts sessonId *) 2 | val getSessionId : string -> string -> string option 3 | 4 | (** Extract sessionId from http cookie headers in [Req.t] *) 5 | val sessionIdOfReq : 'sessionData Req.t -> string option -------------------------------------------------------------------------------- /docs-src/.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /test/RouterTest.ml: -------------------------------------------------------------------------------- 1 | let testSuite () = 2 | ("Router", 3 | [ 4 | Alcotest_lwt.test_case 5 | "processPath errors on empty string" 6 | `Quick 7 | (fun _lwtSwitch _ -> 8 | let _ = Naboris.Router.processPath "" in 9 | Lwt.return_unit); 10 | ]) -------------------------------------------------------------------------------- /docs-src/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "test": { 4 | "presets": [ 5 | [ 6 | "@babel/preset-env", 7 | { 8 | "targets": { 9 | "node": "current" 10 | } 11 | } 12 | ] 13 | ] 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /docs-src/layouts/README.md: -------------------------------------------------------------------------------- 1 | # LAYOUTS 2 | 3 | **This directory is not required, you can delete it if you don't want to use it.** 4 | 5 | This directory contains your Application Layouts. 6 | 7 | More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/views#layouts). 8 | -------------------------------------------------------------------------------- /docs-src/test/nav/NavBar.spec.js: -------------------------------------------------------------------------------- 1 | import { mount } from '@vue/test-utils'; 2 | import NavBar from '@/components/nav/NavBar.vue'; 3 | 4 | describe('NavBar', () => { 5 | test('is a Vue instance', () => { 6 | const wrapper = mount(NavBar); 7 | expect(wrapper.isVueInstance()).toBeTruthy(); 8 | }); 9 | }); 10 | -------------------------------------------------------------------------------- /docs-src/pages/README.md: -------------------------------------------------------------------------------- 1 | # PAGES 2 | 3 | This directory contains your Application Views and Routes. 4 | The framework reads all the `*.vue` files inside this directory and creates the router of your application. 5 | 6 | More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/routing). 7 | -------------------------------------------------------------------------------- /src/Method.mli: -------------------------------------------------------------------------------- 1 | (** Represents an HTTP Method *) 2 | type t = 3 | | GET 4 | | POST 5 | | PUT 6 | | PATCH 7 | | DELETE 8 | | CONNECT 9 | | OPTIONS 10 | | TRACE 11 | | Other of string 12 | 13 | val ofString : string -> t 14 | 15 | val toString : t -> string 16 | 17 | val ofHttpAfMethod : Httpaf.Method.t -> t -------------------------------------------------------------------------------- /src/Route.ml: -------------------------------------------------------------------------------- 1 | type t = { 2 | path : string list; 3 | meth : Method.t; 4 | rawQuery : string; 5 | query : string list Query.QueryMap.t; 6 | } 7 | 8 | let create path meth rawQuery query = {path; meth; rawQuery; query} 9 | let path r = r.path 10 | let meth r = r.meth 11 | let rawQuery r = r.rawQuery 12 | let query r = r.query -------------------------------------------------------------------------------- /docs-src/assets/README.md: -------------------------------------------------------------------------------- 1 | # ASSETS 2 | 3 | **This directory is not required, you can delete it if you don't want to use it.** 4 | 5 | This directory contains your un-compiled assets such as LESS, SASS, or JavaScript. 6 | 7 | More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/assets#webpacked). 8 | -------------------------------------------------------------------------------- /src/Router.mli: -------------------------------------------------------------------------------- 1 | exception InvalidUrl of string 2 | exception DuplicateRoute of string 3 | 4 | (** Generate a route record from a uri target and http method. *) 5 | val generateRoute : string -> Method.t -> Route.t 6 | 7 | (** Extracts useful parts from a uri string. *) 8 | val processPath : string -> (string list * string * string list Query.QueryMap.t) -------------------------------------------------------------------------------- /load-test/scripts/load-test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | echo "GET http://localhost:9997/static/random.txt" | vegeta attack -duration=10s -rate 1025/1s | vegeta report | tee veg-report.txt 3 | 4 | if cat veg-report.txt | grep -i -E '^success.*100\.00%$'; then 5 | veg_exit=0 6 | else 7 | veg_exit=1 8 | fi 9 | 10 | rm -f veg-report.txt 11 | exit $veg_exit -------------------------------------------------------------------------------- /docs-src/plugins/README.md: -------------------------------------------------------------------------------- 1 | # PLUGINS 2 | 3 | **This directory is not required, you can delete it if you don't want to use it.** 4 | 5 | This directory contains Javascript plugins that you want to run before mounting the root Vue.js application. 6 | 7 | More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/plugins). 8 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: install build test clean 2 | 3 | # Create a new opam switch 4 | switch: 5 | opam switch create . --no-install 6 | 7 | # Install project dependencies 8 | install: 9 | opam install . --deps-only --with-test --confirm-level unsafe-yes 10 | 11 | build: 12 | dune build 13 | 14 | test: 15 | dune runtest 16 | 17 | clean: 18 | dune clean 19 | -------------------------------------------------------------------------------- /src/Session.mli: -------------------------------------------------------------------------------- 1 | type 'sessionData t 2 | 3 | (** Creates new ['sessionData t] with id of [string]. *) 4 | val create : string -> 'sessionData -> 'sessionData t 5 | 6 | (** Return session data of given ['sessionData t]. *) 7 | val data : 'sessionData t -> 'sessionData 8 | 9 | (** Return session id of given ['sessionData t]. *) 10 | val id : 'sessionData t -> string -------------------------------------------------------------------------------- /docs-src/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | browser: true, 5 | node: true 6 | }, 7 | parserOptions: { 8 | parser: 'babel-eslint' 9 | }, 10 | extends: [ 11 | '@nuxtjs', 12 | 'plugin:nuxt/recommended' 13 | ], 14 | // add your custom rules here 15 | rules: { 16 | semi: [2, "always"] 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /ci/build-docs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | esy b dune build @doc 4 | rm -f tmp-docs-build-env 5 | esy build-env | grep DUNE_BUILD_DIR > tmp-docs-build-env 6 | echo old: $DUNE_BUILD_DIR 7 | export DUNE_BUILD_DIR="" 8 | source tmp-docs-build-env 9 | echo new: $DUNE_BUILD_DIR 10 | rm -rf docs/html 11 | rm -f tmp-docs-build-env 12 | cp -r $DUNE_BUILD_DIR/default/_doc/_html ./docs/html -------------------------------------------------------------------------------- /ci/test-under-load.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | npm run build-load-test-server 3 | echo old: $DUNE_BUILD_DIR 4 | export DUNE_BUILD_DIR="" 5 | source command-env 6 | echo new: $DUNE_BUILD_DIR 7 | $DUNE_BUILD_DIR/default/load-test/load_test.exe & 8 | # let the server fully start 9 | test_load_pid=$! 10 | sleep 10 11 | /bin/bash load-test/scripts/load-test.sh 12 | test_res=$? 13 | kill -9 $test_load_pid 14 | exit $test_res -------------------------------------------------------------------------------- /docs-src/middleware/README.md: -------------------------------------------------------------------------------- 1 | # MIDDLEWARE 2 | 3 | **This directory is not required, you can delete it if you don't want to use it.** 4 | 5 | This directory contains your application middleware. 6 | Middleware let you define custom functions that can be run before rendering either a page or a group of pages. 7 | 8 | More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/routing#middleware). 9 | -------------------------------------------------------------------------------- /docs-src/store/README.md: -------------------------------------------------------------------------------- 1 | # STORE 2 | 3 | **This directory is not required, you can delete it if you don't want to use it.** 4 | 5 | This directory contains your Vuex Store files. 6 | Vuex Store option is implemented in the Nuxt.js framework. 7 | 8 | Creating a file in this directory automatically activates the option in the framework. 9 | 10 | More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/vuex-store). 11 | -------------------------------------------------------------------------------- /docs-src/README.md: -------------------------------------------------------------------------------- 1 | # docs-src 2 | 3 | > Documentation for naboris 4 | 5 | ## Build Setup 6 | 7 | ``` bash 8 | # install dependencies 9 | $ npm install 10 | 11 | # serve with hot reload at localhost:3000 12 | $ npm run dev 13 | 14 | # build for production and launch server 15 | $ npm run build 16 | $ npm run start 17 | 18 | # generate static project 19 | $ npm run generate 20 | ``` 21 | 22 | For detailed explanation on how things work, check out [Nuxt.js docs](https://nuxtjs.org). 23 | -------------------------------------------------------------------------------- /docs-src/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | moduleNameMapper: { 3 | '^@/(.*)$': '/$1', 4 | '^~/(.*)$': '/$1', 5 | '^vue$': 'vue/dist/vue.common.js' 6 | }, 7 | moduleFileExtensions: ['js', 'vue', 'json'], 8 | transform: { 9 | '^.+\\.js$': 'babel-jest', 10 | '.*\\.(vue)$': 'vue-jest' 11 | }, 12 | collectCoverage: true, 13 | collectCoverageFrom: [ 14 | '/components/**/*.vue', 15 | '/pages/**/*.vue' 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /docs-src/static/README.md: -------------------------------------------------------------------------------- 1 | # STATIC 2 | 3 | **This directory is not required, you can delete it if you don't want to use it.** 4 | 5 | This directory contains your static files. 6 | Each file inside this directory is mapped to `/`. 7 | Thus you'd want to delete this README.md before deploying to production. 8 | 9 | Example: `/static/robots.txt` is mapped as `/robots.txt`. 10 | 11 | More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/assets#static). 12 | -------------------------------------------------------------------------------- /src/SessionConfig.mli: -------------------------------------------------------------------------------- 1 | type 'sessionData t = { 2 | getSession : string option -> 'sessionData Session.t option Lwt.t; 3 | sidKey : string; 4 | maxAge : int; 5 | secret : string; 6 | } 7 | 8 | (** Returns key to be used for session cookies. *) 9 | val sidKey : 'sessionData t option -> string 10 | 11 | (** Returns max age to be used for session cookies. *) 12 | val maxAge : 'sessionData t option -> int 13 | 14 | (** Returns secret used to sign session id cookies. *) 15 | val secret : 'sessionData t option -> string -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.annot 2 | *.cmo 3 | *.cma 4 | *.cmi 5 | *.a 6 | *.o 7 | *.cmx 8 | *.cmxs 9 | *.cmxa 10 | 11 | # ocamlbuild working directory 12 | _build/ 13 | 14 | # ocamlbuild targets 15 | *.byte 16 | *.native 17 | 18 | # oasis generated files 19 | setup.data 20 | setup.log 21 | 22 | # Merlin configuring file for Vim and Emacs 23 | .merlin 24 | 25 | _esy 26 | _coverage 27 | .esy 28 | node_modules 29 | naboris.install 30 | .vscode 31 | esy.lock 32 | command-env 33 | libev.esy.lock 34 | .DS_Store 35 | deploy_key 36 | _opam 37 | -------------------------------------------------------------------------------- /src/SessionConfig.ml: -------------------------------------------------------------------------------- 1 | type 'sessionData t = { 2 | getSession : string option -> 'sessionData Session.t option Lwt.t; 3 | sidKey : string; 4 | maxAge : int; 5 | secret : string; 6 | } 7 | 8 | let maxAge conf = match conf with 9 | | Some sessConf -> sessConf.maxAge 10 | | _ -> 2592000 11 | 12 | let sidKey conf = match conf with 13 | | Some sessConf -> sessConf.sidKey 14 | | _ -> "nab.sid" 15 | 16 | let secret conf = match conf with 17 | | Some sessConf -> sessConf.secret 18 | | _ -> "Keep it secret, keep it safe!" -------------------------------------------------------------------------------- /src/Route.mli: -------------------------------------------------------------------------------- 1 | type t 2 | 3 | (** Get path ([string list]) of [t]. *) 4 | val path : t -> string list 5 | 6 | (** Get http method ([Method.t]) of [t]. *) 7 | val meth : t -> Method.t 8 | 9 | (** Get query [string] of [t]. *) 10 | val rawQuery : t -> string 11 | 12 | (** Get query map [string list Query.QueryMap.t] of [t]. *) 13 | val query : t -> string list Query.QueryMap.t 14 | 15 | (** {b Intended for internal use.} 16 | 17 | Create route record [t]. *) 18 | val create : string list -> Method.t -> string -> string list Query.QueryMap.t -> t -------------------------------------------------------------------------------- /docs/html/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | index 5 | 6 | 7 | 8 | 9 | 10 |
11 |
12 |

OCaml package documentation

13 |
    14 |
  1. naboris 0.1.1
  2. 15 |
16 |
17 |
18 | 19 | -------------------------------------------------------------------------------- /load-test/Load_test.ml: -------------------------------------------------------------------------------- 1 | open Naboris 2 | 3 | let main ()= 4 | let nab_conf = ServerConfig.create () 5 | |> ServerConfig.setOnListen(fun () -> print_endline("Server started at 9997")) 6 | |> ServerConfig.setRequestHandler(fun route req res -> match (Route.path route) with 7 | | "static" :: path -> 8 | let public_dir = Sys.getcwd () ^ "/load-test/public/" in 9 | Naboris.Res.static public_dir path req res 10 | | _ -> res |> Res.status 404 |> Res.text req "Not Found") in 11 | listenAndWaitForever 9997 nab_conf 12 | 13 | let () = Lwt_engine.set (new Lwt_engine.libev ()) 14 | let _ = Lwt_main.run(main ()) -------------------------------------------------------------------------------- /test/utils/DateUtilsTest.ml: -------------------------------------------------------------------------------- 1 | let testSuite () = 2 | ("utils_DateUtils", 3 | [ 4 | Alcotest_lwt.test_case 5 | "formatForHeader taks a float and formats it properly" 6 | `Quick 7 | (fun _lwtSwitch _ -> 8 | (* 7/26/2020 7:36pm GMT *) 9 | let time = 1595792156.0 in 10 | let formattedTime = Naboris.DateUtils.formatForHeaders time in 11 | Alcotest.( 12 | check 13 | string 14 | "formatted time" 15 | formattedTime 16 | "Sun, 26 Jul 2020 19:35:56 GMT" 17 | ); 18 | Lwt.return_unit); 19 | ]) -------------------------------------------------------------------------------- /docs-src/static/icons/reason.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/test.ml: -------------------------------------------------------------------------------- 1 | let runTests () = 2 | if Sys.getenv_opt "NABORIS_LIBEV_TEST" = Some "true" then begin 3 | Lwt_engine.set (new Lwt_engine.libev ()); 4 | print_endline "Set lwt engine to libev explicitly."; 5 | end else begin 6 | print_endline "DIT NOT set lwt engine to libev explicitly."; 7 | end; 8 | 9 | print_endline (Sys.getcwd ()); 10 | 11 | Lwt_main.run @@ Alcotest_lwt.run 12 | "Naboris_Tests" 13 | [ 14 | CookieTest.testSuite (); 15 | DateUtilsTest.testSuite (); 16 | MimeTypesTest.testSuite (); 17 | EtagTest.testSuite (); 18 | MethodTest.testSuite (); 19 | RouterTest.testSuite (); 20 | SessionManagerTest.testSuite (); 21 | IntegrationTest.testSuite (); 22 | ] 23 | 24 | let () = runTests () -------------------------------------------------------------------------------- /docs-src/plugins/matomo.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | export default ({ app }) => { 3 | if (process.env.NODE_ENV !== 'production') { 4 | return; 5 | } 6 | // Matomo 7 | window._paq = window._paq || []; 8 | /* tracker methods like "setCustomDimension" should be called before "trackPageView" */ 9 | window._paq.push(['trackPageView']); 10 | window._paq.push(['enableLinkTracking']); 11 | (function() { 12 | var u="//matomo.shawnmcginty.com/"; 13 | window._paq.push(['setTrackerUrl', u+'matomo.php']); 14 | window._paq.push(['setSiteId', '2']); 15 | var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0]; 16 | g.type='text/javascript'; g.async=true; g.defer=true; g.src=u+'matomo.js'; s.parentNode.insertBefore(g,s); 17 | })(); 18 | // End Matomo Code 19 | }; 20 | -------------------------------------------------------------------------------- /src/Method.ml: -------------------------------------------------------------------------------- 1 | type t = 2 | | GET 3 | | POST 4 | | PUT 5 | | PATCH 6 | | DELETE 7 | | CONNECT 8 | | OPTIONS 9 | | TRACE 10 | | Other of string 11 | 12 | let ofString str = 13 | match str with 14 | | "GET" -> GET 15 | | "POST" -> POST 16 | | "PUT" -> PUT 17 | | "PATCH" -> PATCH 18 | | "DELETE" -> DELETE 19 | | "CONNECT" -> CONNECT 20 | | "OPTIONS" -> OPTIONS 21 | | "TRACE" -> TRACE 22 | | s -> Other s 23 | 24 | let toString meth = 25 | match meth with 26 | | GET -> "GET" 27 | | POST -> "POST" 28 | | PUT -> "PUT" 29 | | PATCH -> "PATCH" 30 | | DELETE -> "DELETE" 31 | | CONNECT -> "CONNECT" 32 | | OPTIONS -> "OPTIONS" 33 | | TRACE -> "TRACE" 34 | | Other s -> s 35 | 36 | let ofHttpAfMethod meth = 37 | let methString = Httpaf.Method.to_string meth in 38 | ofString methString -------------------------------------------------------------------------------- /docs/html/naboris/index.html: -------------------------------------------------------------------------------- 1 | 2 | index (naboris.index)

naboris index

Library naboris

The entry point of this library is the module: Naboris.

-------------------------------------------------------------------------------- /docs/html/naboris/Naboris__Static/index.html: -------------------------------------------------------------------------------- 1 | 2 | Naboris__Static (naboris.Naboris__Static)

Module Naboris__Static

val getFilePath : string -> string list -> string
-------------------------------------------------------------------------------- /src/utils/Etag.ml: -------------------------------------------------------------------------------- 1 | type strength = [`Weak | `Strong] 2 | 3 | let fromString entity = match String.length entity with 4 | | 0 -> "\"0-2jmj7l5rSw0yVb/vlWAYkK/YBwk\"" (* empty entity, use hardcoded empty etag *) 5 | | len -> 6 | let etag = Digestif.digest_string Digestif.sha1 entity 7 | |> Digestif.to_raw_string Digestif.sha1 8 | |> Base64.encode_exn in 9 | 10 | let maxLen = match String.length etag with 11 | | x when x < 27 -> x 12 | | _ -> 27 13 | in 14 | 15 | "\"" ^ string_of_int len ^ "-" ^ String.sub etag 0 maxLen ^ "\"" 16 | 17 | let weakFromString entity = "W/" ^ fromString entity 18 | 19 | let fromFilePath path : string Lwt.t = 20 | let%lwt stats = Lwt_unix.stat path in 21 | let size = string_of_int stats.st_size in 22 | fromString (size ^ path) |> Lwt.return 23 | 24 | let weakFromPath path = 25 | let%lwt etag = fromFilePath path in 26 | Lwt.return ("W/" ^ etag) -------------------------------------------------------------------------------- /docs/html/naboris/Naboris__/Static/index.html: -------------------------------------------------------------------------------- 1 | 2 | Static (naboris.Naboris__.Static)

Module Naboris__.Static

val getFilePath : string -> string list -> string
-------------------------------------------------------------------------------- /src/Router.ml: -------------------------------------------------------------------------------- 1 | exception InvalidUrl of string 2 | exception DuplicateRoute of string 3 | 4 | open Query 5 | 6 | let getRawQuery uri = 7 | match Uri.verbatim_query uri with 8 | | None -> "" 9 | | Some rawQuery -> rawQuery 10 | 11 | let addQuery queries (key, value) = QueryMap.add key value queries 12 | 13 | let getQuery uri = 14 | match Uri.verbatim_query uri with 15 | | Some encodedQueryStr -> 16 | List.fold_left 17 | (fun queries (key, value) -> addQuery queries (key, value)) 18 | QueryMap.empty 19 | (Uri.query_of_encoded encodedQueryStr) 20 | | None -> QueryMap.empty 21 | 22 | let processPath target = 23 | let uri = Uri.of_string target in 24 | let path = String.split_on_char '/' (Uri.path uri) |> List.tl in 25 | (List.map Uri.pct_decode path, getRawQuery uri, getQuery uri) 26 | 27 | let generateRoute target meth = 28 | let (path, rawQuery, query) = processPath target in 29 | Route.create path meth rawQuery query -------------------------------------------------------------------------------- /src/SessionManager.mli: -------------------------------------------------------------------------------- 1 | (** Create a new session with ['sessionData] and add cookie headers to [Res.t]. Returns newly created session id [string]. *) 2 | val startSession : 'sessionData Req.t -> Res.t -> 'sessionData -> ('sessionData Req.t * Res.t * string) 3 | 4 | (** Sets headers on `Res.t` to expire the session. *) 5 | val removeSession : 'sessionData Req.t -> Res.t -> Res.t 6 | 7 | (** {b Intended for internal use.} 8 | 9 | Applies [mapSession] from config to request which uses the session id from the request cookies. 10 | Returns promise of a new request with session data available if it was found. *) 11 | val resumeSession : 'sessionData ServerConfig.t -> 'sessionData Req.t -> 'sessionData Req.t Lwt.t 12 | 13 | (** {b Intended for internal use.} *) 14 | val generateSessionId : unit -> string 15 | 16 | (** {b Intended for internal use.} *) 17 | val sign : string -> string -> string 18 | 19 | (** {b Intended for internal use.} *) 20 | val unsign : string -> string -> string option -------------------------------------------------------------------------------- /test/integration-test/test_assets/text/1024.txt: -------------------------------------------------------------------------------- 1 | opt29TjjTGf2iZbBDaPsXpTxfvgwe7CYe48JQLSrkkLkE15zDusVrzn8XaHjGRDSsvQ9AWlFHZVzwyHfWrRs6wXOMoE1El4zzTJLgLqFHdPdGHsfqtH62FSlREK5sF1abUslIvXEQPjBj1Pf56SoywBSCAbnE5uSmudUbXkVWfagxnh8rTvZs1DNh0B7gGiOax8JzJjS1Ta5jLxD8UT2SrBHeNCucrsCF0uu899p8cO0QHSb4iSdrcl15jRmKEVbhtyVbNwTCH3MBzyZ9gcZRngA7vdLkC9gLlOsFbHmY2PAwp1idZl8H0miFHJ5qkq4Hp7sWc4PLig7ak3tLNbZFBs9NOrOL4wblCgzigIM7IFKhArC2M7DmqjytLzcQgOr4WCyZVrJSAq1HGgPXj947sn2ZbE7D3Y9idyontv2MfaX52OarBTRpfaxTcyGEvWeamnhJ2qfRS00wYwpnCdoVDcO7VWnm7MBYo5917X5561llc9LcG1eeaRkWc7yMMDqYmiiDbeJVYkOC7lYpG5MnegpmmDYmwEMvWjLUhxh3uowOZufTBPgo3m4YC5Jku7Pu3DqY35LrrX3YsTgb3dq2L3vVYIGVjZnxxXv359TAx050kPQNdBJUbkFz8Wt1cFDyiIjLsUmF3Ix4bkHXH8HzmQ68unRyMbZYbHDjqH7JNq6sV9AWpf2M4e7ngRvC7LcHDVhVcG562SsOCbbebLUkQxDRKtpPe50bkGPvU3s78kA68BZRwxTWSnjVlDmaBmQUdqbwgBL7VS6yEruXPmvc0xJraIG35jJXYIvfumGVJVp8d3wgE4SUZ5kCZnHeCdiN5OfUFtJZSUUaVeggKejORfuzDkd0epprCrKQNdZFc35uOdB2z0XYadrISlvMDV7cjj226RtTTbRzmo1KmMT5chZv9x35dxTpyrJAe5pNu0RbYyWs0mVpO3gNE6mUmGAkfAmkjsrU6HTeV6DcTvEMzyokYLDLuOA5TSup9fpxcsJYyIrabQNLWxDRWSZNMPx -------------------------------------------------------------------------------- /src/utils/Cookie.ml: -------------------------------------------------------------------------------- 1 | let rec getSessionId sidKey cookieStr = 2 | let sessionKey = sidKey ^ "=" in 3 | let cookieLength = String.length cookieStr in 4 | let keyLength = String.length sessionKey in 5 | let startOfString = 0 in 6 | 7 | match String.index_opt cookieStr sessionKey.[startOfString] with 8 | | None -> None 9 | | Some i -> 10 | let highestLen = keyLength + i in 11 | if cookieLength < highestLen then 12 | None 13 | else if String.sub cookieStr i keyLength = sessionKey then 14 | let partialCookie = 15 | String.sub cookieStr highestLen (cookieLength - highestLen) in 16 | 17 | (match String.index_opt partialCookie ';' with 18 | | None -> Some partialCookie 19 | | Some endOfCookie -> 20 | Some (String.sub partialCookie startOfString endOfCookie)) 21 | else 22 | getSessionId sidKey (String.sub cookieStr (i + 1) (cookieLength - (i + 1))) 23 | 24 | let sessionIdOfReq req = 25 | match Req.getHeader "Cookie" req with 26 | | None -> None 27 | | Some header -> getSessionId (Req.sidKey req) header -------------------------------------------------------------------------------- /docs-src/assets/sass/_icons.scss: -------------------------------------------------------------------------------- 1 | .button { 2 | svg { 3 | fill: $white-bis; 4 | height: 100%; 5 | width: 100%; 6 | margin-top: 5px; 7 | } 8 | 9 | svg.is-danger { 10 | fill: $danger; 11 | } 12 | 13 | svg.is-warning { 14 | fill: $warning; 15 | } 16 | } 17 | 18 | .navbar-item { 19 | svg.naboris-svg { 20 | height: 36px; 21 | width: 36px; 22 | margin-right: 5px; 23 | } 24 | } 25 | 26 | svg.naboris-svg.is-white { 27 | #white-group { 28 | path { 29 | fill: white !important; 30 | } 31 | } 32 | } 33 | 34 | svg.naboris-svg.is-white { 35 | #black-group { 36 | path { 37 | fill: white !important; 38 | fill-opacity: 0.5 !important; 39 | } 40 | } 41 | } 42 | 43 | svg.naboris-svg.is-white { 44 | #shadow-group { 45 | path { 46 | fill: white !important; 47 | fill-opacity: 0.7 !important; 48 | } 49 | } 50 | } 51 | 52 | svg.naboris-svg.is-white { 53 | #camel-group { 54 | path { 55 | fill: white !important; 56 | fill-opacity: 0.8 !important; 57 | } 58 | } 59 | } -------------------------------------------------------------------------------- /docs/html/naboris/Naboris__Query/index.html: -------------------------------------------------------------------------------- 1 | 2 | Naboris__Query (naboris.Naboris__Query)

Module Naboris__Query

module QueryMap : Stdlib.Map.S with type QueryMap.key = Stdlib.String.t

Implements Map with key of string.

-------------------------------------------------------------------------------- /docs/html/naboris/Naboris__/Query/index.html: -------------------------------------------------------------------------------- 1 | 2 | Query (naboris.Naboris__.Query)

Module Naboris__.Query

module QueryMap : Stdlib.Map.S with type QueryMap.key = Stdlib.String.t

Implements Map with key of string.

-------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Shawn McGinty 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /naboris.opam: -------------------------------------------------------------------------------- 1 | opam-version: "2.0" 2 | name: "naboris" 3 | version: "0.1.3" 4 | synopsis: "Simple http server" 5 | description: "Simple http server built on httpaf and lwt" 6 | maintainer: "Shawn McGinty " 7 | authors: [ "Shawn McGinty " ] 8 | license: "MIT" 9 | homepage: "https://github.com/shawn-mcginty/naboris" 10 | bug-reports: "https://github.com/shawn-mcginty/naboris/issues" 11 | dev-repo: "git+https://github.com/shawn-mcginty/naboris.git" 12 | build: [ 13 | ["dune" "subst"] {pinned} 14 | ["dune" "build" "-p" name "-j" jobs] 15 | ] 16 | depends: [ 17 | "ocaml" {>= "4.07"} 18 | "base64" {>= "3.4.0"} 19 | "dune" {>= "1.6"} 20 | "digestif" {>= "0.8.0"} 21 | "reason" {>= "3.4.0"} 22 | "httpaf" {>= "0.6.0"} 23 | "httpaf-lwt-unix" {>= "0.6.0"} 24 | "lwt" {>= "5.1.1"} 25 | "lwt_ppx" {>= "2.0.1"} 26 | "uri" {>= "2.2.0"} 27 | "alcotest" {with-test & >= "1.9.0"} 28 | "alcotest-lwt" {with-test & >= "1.9.0"} 29 | "cohttp" {with-test & >= "6.1.0"} 30 | "cohttp-lwt-unix" {with-test & >= "6.1.0"} 31 | "ocaml-lsp-server" {with-test & >= "1.23.0"} 32 | ] 33 | depopts: [ 34 | "conf-libev" 35 | ] 36 | -------------------------------------------------------------------------------- /docs-src/layouts/default.vue: -------------------------------------------------------------------------------- 1 | 13 | 42 | 47 | -------------------------------------------------------------------------------- /docs-src/assets/sass/_bulma.scss: -------------------------------------------------------------------------------- 1 | @charset "utf-8"; 2 | /*! bulma.io v0.8.0 | MIT License | github.com/jgthms/bulma */ 3 | @import "../../node_modules/bulma/sass/utilities/_all"; 4 | @import "../../node_modules/bulma/sass/base/_all"; 5 | @import "../../node_modules/bulma/sass/elements/box.sass"; 6 | @import "../../node_modules/bulma/sass/elements/button.sass"; 7 | @import "../../node_modules/bulma/sass/elements/container.sass"; 8 | @import "../../node_modules/bulma/sass/elements/content.sass"; 9 | @import "../../node_modules/bulma/sass/elements/icon.sass"; 10 | @import "../../node_modules/bulma/sass/elements/image.sass"; 11 | @import "../../node_modules/bulma/sass/elements/notification.sass"; 12 | @import "../../node_modules/bulma/sass/elements/progress.sass"; 13 | @import "../../node_modules/bulma/sass/elements/table.sass"; 14 | @import "../../node_modules/bulma/sass/elements/tag.sass"; 15 | @import "../../node_modules/bulma/sass/elements/title.sass"; 16 | @import "../../node_modules/bulma/sass/form/_all"; 17 | @import "../../node_modules/bulma/sass/components/_all"; 18 | @import "../../node_modules/bulma/sass/grid/_all"; 19 | @import "../../node_modules/bulma/sass/layout/_all"; -------------------------------------------------------------------------------- /src/Naboris.ml: -------------------------------------------------------------------------------- 1 | module Server = Server 2 | module Req = Req 3 | module Res = Res 4 | module Method = Method 5 | module Route = Route 6 | module Router = Router 7 | module Query = Query 8 | module MimeTypes = MimeTypes 9 | module Middleware = Middleware 10 | module RequestHandler = RequestHandler 11 | module Session = Session 12 | module SessionManager = SessionManager 13 | module Cookie = Cookie 14 | module ServerConfig = ServerConfig 15 | module SessionConfig = SessionConfig 16 | module ErrorHandler = ErrorHandler 17 | module DateUtils = DateUtils 18 | module Etag = Etag 19 | 20 | let listen ?(inetAddr = Unix.inet_addr_any) port serverConfig = 21 | let listenAddress = Unix.(ADDR_INET (inetAddr, port)) in 22 | let connectionHandler = Server.buildConnectionHandler serverConfig in 23 | 24 | Lwt.async (fun () -> 25 | let%lwt _server = Lwt_io.establish_server_with_client_socket 26 | listenAddress 27 | connectionHandler 28 | in 29 | ServerConfig.onListen serverConfig (); 30 | Lwt.return_unit 31 | ); 32 | 33 | Lwt.wait () 34 | 35 | let listenAndWaitForever ?(inetAddr = Unix.inet_addr_any) port serverConfig = 36 | let (forever, _) = listen ~inetAddr port serverConfig in 37 | forever -------------------------------------------------------------------------------- /docs/html/naboris/Naboris/Query/index.html: -------------------------------------------------------------------------------- 1 | 2 | Query (naboris.Naboris.Query)

Module Naboris.Query

Map type for working with queries from routed requests.

module QueryMap : Stdlib.Map.S with type QueryMap.key = Stdlib.String.t

Implements Map with key of string.

-------------------------------------------------------------------------------- /docs/html/naboris/Naboris__MimeTypes/index.html: -------------------------------------------------------------------------------- 1 | 2 | Naboris__MimeTypes (naboris.Naboris__MimeTypes)

Module Naboris__MimeTypes

val getExtension : string -> string

Exposed only for unit testing

val getMimeType : string -> string

Given a filename returns content type. Defaults to "text/plain" if type cannot be inferred.

-------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | os: 2 | - linux 3 | dist: 4 | - bionic 5 | language: node_js 6 | node_js: 7 | - '12' 8 | before_install: 9 | - openssl aes-256-cbc -K $encrypted_dfdcfd5172af_key -iv $encrypted_dfdcfd5172af_iv 10 | -in deploy_key.enc -out ./deploy_key -d 11 | - eval `ssh-agent` 12 | - chmod 400 ./deploy_key 13 | - ssh-add ./deploy_key 14 | - sudo add-apt-repository ppa:longsleep/golang-backports -y 15 | - sudo apt-get update 16 | - sudo apt-get install -y libc6 17 | - sudo apt-get install -y golang-go 18 | - sudo apt-get install -y libev-dev 19 | - go env | grep GOPATH > gopath.env 20 | - source gopath.env 21 | - export PATH=$PATH:$GOPATH/bin 22 | - go get -u github.com/tsenart/vegeta 23 | - npm install -g esy 24 | install: 25 | - esy install -q 26 | - esy @libev install -q 27 | before_script: 28 | - npm run build 29 | jobs: 30 | include: 31 | - stage: Test - Standard 32 | script: "/bin/bash ci/test-standard.sh" 33 | - stage: Test - Libev 34 | if: branch = master 35 | script: "/bin/bash ci/test-libev.sh" 36 | - stage: Load Test - Libev 37 | if: branch = master 38 | script: "/bin/bash ci/test-under-load.sh" 39 | - stage: Deploy Docs 40 | if: branch = master 41 | script: "/bin/bash ci/deploy-docs.sh" 42 | -------------------------------------------------------------------------------- /docs/html/naboris/Naboris__RequestHandler/index.html: -------------------------------------------------------------------------------- 1 | 2 | Naboris__RequestHandler (naboris.Naboris__RequestHandler)

Module Naboris__RequestHandler

type 'sessionData t = Naboris.Route.t -> 'sessionData Naboris.Req.t -> Naboris.Res.t -> Naboris.Res.t Lwt.t
-------------------------------------------------------------------------------- /docs-src/store/index.js: -------------------------------------------------------------------------------- 1 | import * as Cookie from 'js-cookie'; 2 | 3 | import actionLabels from '~/assets/js/main-actions'; 4 | 5 | const { 6 | DARK_MODE, 7 | LANG, 8 | SET_DARK_MODE, 9 | SET_LANGUAGE, 10 | SET_LOADING 11 | } = actionLabels; 12 | 13 | export const state = () => { 14 | return { 15 | darkMode: true, 16 | language: 'reason', 17 | loading: true 18 | }; 19 | }; 20 | 21 | export const mutations = { 22 | [SET_DARK_MODE]: (state, { darkMode }) => { 23 | state.darkMode = darkMode; 24 | return state; 25 | }, 26 | [SET_LANGUAGE]: (state, { language }) => { 27 | state.language = language; 28 | return state; 29 | }, 30 | [SET_LOADING]: (state, { loading }) => { 31 | state.loading = loading; 32 | return state; 33 | } 34 | }; 35 | 36 | export const actions = { 37 | [SET_DARK_MODE]: ({ commit }, { darkMode }) => { 38 | Cookie.set(DARK_MODE, JSON.stringify(darkMode), { expires: 365 }); 39 | 40 | commit({ 41 | type: SET_DARK_MODE, 42 | darkMode 43 | }); 44 | }, 45 | [SET_LANGUAGE]: ({ commit }, { language }) => { 46 | Cookie.set(LANG, language, { expires: 365 }); 47 | 48 | commit({ 49 | type: SET_LANGUAGE, 50 | language 51 | }); 52 | } 53 | }; 54 | -------------------------------------------------------------------------------- /docs/html/naboris/Naboris__/MimeTypes/index.html: -------------------------------------------------------------------------------- 1 | 2 | MimeTypes (naboris.Naboris__.MimeTypes)

Module Naboris__.MimeTypes

val getExtension : string -> string

Exposed only for unit testing

val getMimeType : string -> string

Given a filename returns content type. Defaults to "text/plain" if type cannot be inferred.

-------------------------------------------------------------------------------- /docs/html/naboris/Naboris/RequestHandler/index.html: -------------------------------------------------------------------------------- 1 | 2 | RequestHandler (naboris.Naboris.RequestHandler)

Module Naboris.RequestHandler

Module defining RequestHandler functions.

type 'sessionData t = Route.t -> 'sessionData Req.t -> Res.t -> Res.t Lwt.t
-------------------------------------------------------------------------------- /docs/html/naboris/Naboris__ErrorHandler/index.html: -------------------------------------------------------------------------------- 1 | 2 | Naboris__ErrorHandler (naboris.Naboris__ErrorHandler)

Module Naboris__ErrorHandler

type t = exn -> Naboris.Route.t -> ((string * string) list * string) Lwt.t

Called when Res.reportError is called. Expects return values of an Lwt.t promise containing a tuple of headers (list(string * string)) and response_body (string).

-------------------------------------------------------------------------------- /docs-src/content/docs/guides/performance-best-practices.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Performance Best Practices 3 | --- 4 | 5 | ## Performance Best Practices 6 | Best practices for enhanced performance. 7 | 8 | - [Libev](#libev) 9 | - [Static Files](#static-files) 10 | 11 | ### # Libev 12 | [Lwt](https://ocsigen.org/lwt) promises are used heavily by naboris. [Lwt](https://ocsigen.org/lwt) uses a scheduler underneath the hood to keep promises pausing and resuming as needed. If available it is highly recommended to use [libev](http://software.schmorp.de/). This can easily be done by installing [libev](http://software.schmorp.de/) on the host system and including [`conf-libev`](https://opam.ocaml.org/packages/conf-libev/) in the `opam` environment used to run naboris. 13 | 14 | The [installation page](http://localhost:3999/quick-start/installation#libev) has more info on installing libev. 15 | 16 | ### # Static Files 17 | It is highly recommended that static files be [served via a reverse proxy such as nginx](https://docs.nginx.com/nginx/admin-guide/web-server/serving-static-content/). These servers are highly optimized and configurable for serving static files in ways that naboris would take much more work to achieve. -------------------------------------------------------------------------------- /load-test/public/random.txt: -------------------------------------------------------------------------------- 1 | Just a text file with some stuff in it. 2 | 3 | To Dan 4 | 5 | By Paul Laurence Dunbar 6 | 7 | 8 | 9 | Step me now a bridal measure, 10 | Work give way to love and leisure, 11 | Hearts be free and hearts be gay-- 12 | Doctor Dan doth wed to-day. 13 | 14 | Diagnosis, cease your squalling-- 15 | Check that scalpel's senseless bawling, 16 | Put that ugly knife away-- 17 | Doctor Dan doth wed to-day. 18 | 19 | 'Tis no time for things unsightly, 20 | Life's the day and life goes lightly; 21 | Science lays aside her sway-- 22 | Love rules Dr. Dan to-day. 23 | 24 | Gather, gentlemen and ladies, 25 | For the nuptial feast now made is, 26 | Swing your garlands, chant your lay 27 | For the pair who wed to-day. 28 | 29 | Wish them happy days and many, 30 | Troubles few and griefs not any, 31 | Lift your brimming cups and say 32 | God bless them who wed to-day. 33 | 34 | Then a cup to Cupid daring, 35 | Who for conquest ever faring, 36 | With his arrows dares assail 37 | E'en a doctor's coat of mail. 38 | 39 | So with blithe and happy hymning 40 | And with harmless goblets brimming, 41 | Dance a step--musicians play-- 42 | Doctor Dan doth wed to-day. -------------------------------------------------------------------------------- /src/utils/DateUtils.ml: -------------------------------------------------------------------------------- 1 | let formatForHeaders time = 2 | let tm = Unix.gmtime time in 3 | let weekDay = match tm.tm_wday with 4 | | 0 -> "Sun" 5 | | 1 -> "Mon" 6 | | 2 -> "Tue" 7 | | 3 -> "Wed" 8 | | 4 -> "Thu" 9 | | 5 -> "Fri" 10 | | 6 -> "Sat" 11 | | _ -> "Sun" 12 | in 13 | let month = match tm.tm_mon with 14 | | 0 -> "Jan" 15 | | 1 -> "Feb" 16 | | 2 -> "Mar" 17 | | 3 -> "Apr" 18 | | 4 -> "May" 19 | | 5 -> "Jun" 20 | | 6 -> "Jul" 21 | | 7 -> "Aug" 22 | | 8 -> "Sep" 23 | | 9 -> "Oct" 24 | | 10 -> "Nov" 25 | | 11 -> "Dec" 26 | | _ -> "Jan" 27 | in 28 | 29 | let day = match tm.tm_mday with 30 | | mday when mday < 10 -> "0" ^ string_of_int mday 31 | | mday -> string_of_int mday 32 | in 33 | 34 | let hours = match tm.tm_hour with 35 | | hr when hr < 10 -> "0" ^ string_of_int hr 36 | | hr -> string_of_int hr 37 | in 38 | 39 | let minutes = match tm.tm_min with 40 | | min when min < 10 -> "0" ^ string_of_int min 41 | | min -> string_of_int min 42 | in 43 | 44 | let seconds = match tm.tm_sec with 45 | | sec when sec < 10 -> "0" ^ string_of_int sec 46 | | sec -> string_of_int sec 47 | in 48 | 49 | weekDay ^ ", " ^ day ^ " " ^ month ^ " " ^ string_of_int (tm.tm_year + 1900) ^ " " ^ hours ^ ":" ^ minutes ^ ":" ^ seconds ^ " GMT" -------------------------------------------------------------------------------- /docs/html/naboris/Naboris__/RequestHandler/index.html: -------------------------------------------------------------------------------- 1 | 2 | RequestHandler (naboris.Naboris__.RequestHandler)

Module Naboris__.RequestHandler

type 'sessionData t = Naboris.Route.t -> 'sessionData Naboris.Req.t -> Naboris.Res.t -> Naboris.Res.t Lwt.t
-------------------------------------------------------------------------------- /docs/html/naboris/Naboris/MimeTypes/index.html: -------------------------------------------------------------------------------- 1 | 2 | MimeTypes (naboris.Naboris.MimeTypes)

Module Naboris.MimeTypes

Less commonly used.

val getExtension : string -> string

Exposed only for unit testing

val getMimeType : string -> string

Given a filename returns content type. Defaults to "text/plain" if type cannot be inferred.

-------------------------------------------------------------------------------- /docs/html/naboris/Naboris__/ErrorHandler/index.html: -------------------------------------------------------------------------------- 1 | 2 | ErrorHandler (naboris.Naboris__.ErrorHandler)

Module Naboris__.ErrorHandler

type t = exn -> Naboris.Route.t -> ((string * string) list * string) Lwt.t

Called when Res.reportError is called. Expects return values of an Lwt.t promise containing a tuple of headers (list(string * string)) and response_body (string).

-------------------------------------------------------------------------------- /docs/html/naboris/Naboris/ErrorHandler/index.html: -------------------------------------------------------------------------------- 1 | 2 | ErrorHandler (naboris.Naboris.ErrorHandler)

Module Naboris.ErrorHandler

Module with error handler types.

type t = exn -> Route.t -> ((string * string) list * string) Lwt.t

Called when Res.reportError is called. Expects return values of an Lwt.t promise containing a tuple of headers (list(string * string)) and response_body (string).

-------------------------------------------------------------------------------- /docs/html/naboris/Naboris__Middleware/index.html: -------------------------------------------------------------------------------- 1 | 2 | Naboris__Middleware (naboris.Naboris__Middleware)

Module Naboris__Middleware

type 'sessionData t = 'sessionData Naboris.RequestHandler.t -> Naboris.Route.t -> 'sessionData Naboris.Req.t -> Naboris.Res.t -> Naboris.Res.t Lwt.t
-------------------------------------------------------------------------------- /docs-src/pages/thank-you/index.vue: -------------------------------------------------------------------------------- 1 | 36 | 50 | -------------------------------------------------------------------------------- /docs/html/naboris/Naboris/Middleware/index.html: -------------------------------------------------------------------------------- 1 | 2 | Middleware (naboris.Naboris.Middleware)

Module Naboris.Middleware

Module defining middleware functions.

type 'sessionData t = 'sessionData RequestHandler.t -> Route.t -> 'sessionData Req.t -> Res.t -> Res.t Lwt.t
-------------------------------------------------------------------------------- /docs/html/naboris/Naboris__Cookie/index.html: -------------------------------------------------------------------------------- 1 | 2 | Naboris__Cookie (naboris.Naboris__Cookie)

Module Naboris__Cookie

val getSessionId : string -> string -> string option

Given the session id key and cookie header string values extracts sessonId

val sessionIdOfReq : 'sessionData Naboris.Req.t -> string option

Extract sessionId from http cookie headers in Req.t

-------------------------------------------------------------------------------- /docs/html/naboris/Naboris__/Cookie/index.html: -------------------------------------------------------------------------------- 1 | 2 | Cookie (naboris.Naboris__.Cookie)

Module Naboris__.Cookie

val getSessionId : string -> string -> string option

Given the session id key and cookie header string values extracts sessonId

val sessionIdOfReq : 'sessionData Naboris.Req.t -> string option

Extract sessionId from http cookie headers in Req.t

-------------------------------------------------------------------------------- /docs/html/naboris/Naboris__/Middleware/index.html: -------------------------------------------------------------------------------- 1 | 2 | Middleware (naboris.Naboris__.Middleware)

Module Naboris__.Middleware

type 'sessionData t = 'sessionData Naboris.RequestHandler.t -> Naboris.Route.t -> 'sessionData Naboris.Req.t -> Naboris.Res.t -> Naboris.Res.t Lwt.t
-------------------------------------------------------------------------------- /docs/html/naboris/Naboris/Cookie/index.html: -------------------------------------------------------------------------------- 1 | 2 | Cookie (naboris.Naboris.Cookie)

Module Naboris.Cookie

Less commonly used.

val getSessionId : string -> string -> string option

Given the session id key and cookie header string values extracts sessonId

val sessionIdOfReq : 'sessionData Req.t -> string option

Extract sessionId from http cookie headers in Req.t

-------------------------------------------------------------------------------- /docs-src/assets/js/shared.js: -------------------------------------------------------------------------------- 1 | import * as Cookie from 'js-cookie'; 2 | 3 | import actions from '~/assets/js/main-actions'; 4 | 5 | const { 6 | SET_LANGUAGE, 7 | SET_DARK_MODE, 8 | DARK_MODE, 9 | LANG, 10 | SET_LOADING 11 | } = actions; 12 | 13 | export default { 14 | fetch ({ store }) { 15 | const { 16 | commit, 17 | state 18 | } = store; 19 | const { 20 | language, 21 | darkMode 22 | } = state; 23 | 24 | const storedLanguage = Cookie.get(LANG); 25 | const storedDarkModeStr = Cookie.get(DARK_MODE); 26 | 27 | // eslint-disable-next-line no-console 28 | console.log({ 29 | storedDarkModeStr, 30 | storedLanguage, 31 | state: JSON.stringify(state) 32 | }); 33 | if (storedLanguage !== undefined && storedLanguage !== language) { 34 | commit({ 35 | type: SET_LANGUAGE, 36 | language: storedLanguage 37 | }); 38 | } 39 | 40 | if (storedDarkModeStr) { 41 | try { 42 | const storedDarkMode = JSON.parse(storedDarkModeStr); 43 | if (storedDarkMode !== darkMode) { 44 | commit({ 45 | type: SET_DARK_MODE, 46 | darkMode: storedDarkMode 47 | }); 48 | } 49 | } catch (e) { 50 | // eslint-disable-next-line no-console 51 | console.error(e); 52 | } 53 | } 54 | 55 | commit({ 56 | type: SET_LOADING, 57 | loading: false 58 | }); 59 | } 60 | }; 61 | -------------------------------------------------------------------------------- /src/SessionManager.ml: -------------------------------------------------------------------------------- 1 | let () = Random.init (int_of_float (Unix.gettimeofday () *. 1000.0)) 2 | 3 | let generateSessionId () = 4 | Random.bits () |> string_of_int |> Digest.string |> Digest.to_hex 5 | 6 | let sign secret sid = 7 | let hmac = Digestif.hmaci_string 8 | Digestif.sha256 9 | ~key:secret 10 | (fun f -> f sid) 11 | in 12 | sid ^ "." ^ Digestif.to_hex Digestif.sha256 hmac 13 | 14 | let unsign secret signedSid = match String.split_on_char '.' signedSid with 15 | | [sid; _hash] -> 16 | if sign secret sid = signedSid then 17 | Some sid 18 | else 19 | None 20 | | _ -> None 21 | 22 | let startSession req res data = 23 | let newSessionId = generateSessionId () |> sign (Req.secret req) in 24 | 25 | let req2 = Req.setSessionData (Some (Session.create newSessionId data)) req in 26 | let res2 = Res.setSessionCookies newSessionId (Req.sidKey req2) (Req.maxAge req2) res in 27 | (req2, res2, newSessionId) 28 | 29 | let resumeSession (serverConfig : 'sessionData ServerConfig.t) req = 30 | match ServerConfig.sessionConfig serverConfig with 31 | | None -> Lwt.return req 32 | | Some sessionConfig -> 33 | let sid = match Cookie.sessionIdOfReq req with 34 | | Some signedSid -> unsign (Req.secret req) signedSid 35 | | None -> None 36 | in 37 | let%lwt maybeSessionData = sessionConfig.getSession sid in 38 | let req2 = Req.setSessionData maybeSessionData req in 39 | Lwt.return req2 40 | 41 | let removeSession req res = Res.setSessionCookies "" (Req.sidKey req) 0 res -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Naboris - Documentation - Simple http server for OCaml and ReasonML. 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 |
18 |
19 |

20 | Naboris 21 |

22 |

23 | Simple http server for OCaml and ReasonML. 24 |

25 |
26 |
27 |
28 |
29 |
30 |

More better documentation is in the works and coming soon. For now please review the ocaml typespec below.

31 | odocs 32 |
33 |
34 |
35 |
36 |

37 | Naboris by Shawn McGinty. The source code is licensed 38 | MIT. Feel free to use it and make improvements. 39 |

40 |
41 |
42 | 43 | -------------------------------------------------------------------------------- /docs-src/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "docs-src", 3 | "version": "1.0.0", 4 | "description": "Simple http server built for OCaml and ReasonML.", 5 | "author": "Shawn McGinty", 6 | "private": true, 7 | "scripts": { 8 | "pre-build": "rm -rf static/fonts/fa && cp -r node_modules/@fortawesome/fontawesome-free/webfonts static/fonts/fa", 9 | "dev": "nuxt -p 3999", 10 | "build": "npm run prebuild && nuxt build", 11 | "start": "nuxt start", 12 | "generate": "nuxt generate", 13 | "lint": "eslint --ext .js,.vue --ignore-path .gitignore .", 14 | "test": "jest", 15 | "load-docs": "rm -rf static/odocs && cp -r ../docs/html static/odocs" 16 | }, 17 | "lint-staged": { 18 | "*.{js,vue}": "npm run lint" 19 | }, 20 | "husky": { 21 | "hooks": { 22 | "pre-commit": "lint-staged" 23 | } 24 | }, 25 | "dependencies": { 26 | "@fortawesome/fontawesome-free": "^5.12.1", 27 | "@nuxtjs/bulma": "^1.2.1", 28 | "frontmatter-markdown-loader": "^3.1.0", 29 | "js-cookie": "^2.2.1", 30 | "markdown-it": "^10.0.0", 31 | "markdown-it-prism": "^2.0.4", 32 | "node-sass": "^4.13.1", 33 | "nuxt": "^2.0.0", 34 | "sass-loader": "^8.0.2", 35 | "vue-github-button": "^1.1.2" 36 | }, 37 | "devDependencies": { 38 | "@nuxtjs/eslint-config": "^2.0.0", 39 | "@nuxtjs/eslint-module": "^1.0.0", 40 | "@vue/test-utils": "^1.0.0-beta.27", 41 | "babel-eslint": "^10.0.1", 42 | "babel-jest": "^24.1.0", 43 | "eslint": "^6.1.0", 44 | "eslint-plugin-nuxt": ">=0.4.2", 45 | "husky": "^4.0.0", 46 | "jest": "^24.1.0", 47 | "lint-staged": "^10.0.0", 48 | "vue-jest": "^4.0.0-0" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Req.mli: -------------------------------------------------------------------------------- 1 | type 'sessionData t 2 | 3 | (** Get HttpAf request descriptor. *) 4 | val reqd : 'sessionData t -> Httpaf.Reqd.t 5 | 6 | (** Get header from request. 7 | [None] if no matching header is found. *) 8 | val getHeader : string -> 'sessionData t -> string option 9 | 10 | (** Get lwt promise of the body string from an http request. *) 11 | val getBody : 'sessionData t -> string Lwt.t 12 | 13 | (** Extracts ['sessionData] from request. 14 | 15 | Returns [None] if no session exists. *) 16 | val getSessionData : 'sessionData t -> 'sessionData option 17 | 18 | (** Sets ['sessionData] onto a request. *) 19 | val setSessionData : 'sessionData Session.t option -> 'sessionData t -> 'sessionData t 20 | 21 | (** {b Intended for internal use.} 22 | Creates default req record. *) 23 | val fromReqd : Httpaf.Reqd.t -> 'sessionData SessionConfig.t option -> string option -> bool -> Etag.strength option -> 'sessionData t 24 | 25 | (** Get key for session id cookie *) 26 | val sidKey : 'sessionData t -> string 27 | 28 | (** Get max age for session id cookies (in seconds) *) 29 | val maxAge : 'sessionData t -> int 30 | 31 | (** Get secret used to sign session id cookies. *) 32 | val secret : 'sessionData t -> string 33 | 34 | (** Get [Cache-control] header value for static requests based on [ServerConfig.t]. *) 35 | val staticCacheControl : 'sessionData t -> string option 36 | 37 | (** Get [bool] value where true signals the server to set [Last-modified] headers for static requests. *) 38 | val staticLastModified : 'sessionData t -> bool 39 | 40 | (** Get [Etag.strength option] which is set by [ServerConfig.t]. *) 41 | val responseEtag : 'sessionData t -> Etag.strength option -------------------------------------------------------------------------------- /docs-src/assets/sass/main.scss: -------------------------------------------------------------------------------- 1 | @charset "utf-8"; 2 | 3 | $camel: #c19a6b; 4 | $inverted_camel: #3e6594; 5 | 6 | // general bulma colors 7 | $cyan: #59a2d3; 8 | $red: #c16b9a; 9 | $green: #8cae7e; 10 | $yellow: #b0ae7c; 11 | $orange: #cb7c62; 12 | $purple: #9f74b8; 13 | $blue: #6579c7; 14 | $turquoise: #69c3c1; 15 | 16 | $grey-darker: #20344b; 17 | $grey-dark: #28415e; 18 | $grey: hsl(32, 21%, 48%); 19 | $grey-light: hsl(32, 44%, 71%); 20 | $grey-lighter: hsl(30, 46%, 86%); 21 | $grey-lightest: hsl(32, 49%, 93%); 22 | $white-ter: hsl(32, 49%, 96%); 23 | $white-bis: hsl(32, 49%, 98%); 24 | 25 | // bulma specifics 26 | $family-sans-serif: 'Noto Sans', BlinkMacSystemFont, -apple-system, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", "Helvetica", "Arial", sans-serif; 27 | $family-serif: 'Noto Serif', serif; 28 | 29 | // font awesome 30 | $fa-font-path: "~static/fonts/fa"; 31 | 32 | @import "./bulma.scss"; 33 | @import "../../node_modules/@fortawesome/fontawesome-free/scss/fontawesome.scss"; 34 | @import "../../node_modules/@fortawesome/fontawesome-free/scss/solid.scss"; 35 | @import "../../node_modules/@fortawesome/fontawesome-free/scss/regular.scss"; 36 | @import "../../node_modules/@fortawesome/fontawesome-free/scss/brands.scss"; 37 | 38 | // our imports 39 | @import "./syntax-highlight-theme.scss"; 40 | @import "./fonts.scss"; 41 | @import "./markdown.scss"; 42 | @import "./icons.scss"; 43 | 44 | .main-hero { 45 | background-image: url('~static/images/hero-desert.jpg'); 46 | background-size: cover; 47 | } 48 | 49 | .main-section { 50 | // compensate for the fixed navbar on top of the screen 51 | padding-top: 52px; 52 | } 53 | -------------------------------------------------------------------------------- /docs-src/.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### Node template 3 | # Logs 4 | /logs 5 | *.log 6 | npm-debug.log* 7 | yarn-debug.log* 8 | yarn-error.log* 9 | 10 | # Runtime data 11 | pids 12 | *.pid 13 | *.seed 14 | *.pid.lock 15 | 16 | # Directory for instrumented libs generated by jscoverage/JSCover 17 | lib-cov 18 | 19 | # Coverage directory used by tools like istanbul 20 | coverage 21 | 22 | # nyc test coverage 23 | .nyc_output 24 | 25 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 26 | .grunt 27 | 28 | # Bower dependency directory (https://bower.io/) 29 | bower_components 30 | 31 | # node-waf configuration 32 | .lock-wscript 33 | 34 | # Compiled binary addons (https://nodejs.org/api/addons.html) 35 | build/Release 36 | 37 | # Dependency directories 38 | node_modules/ 39 | jspm_packages/ 40 | 41 | # TypeScript v1 declaration files 42 | typings/ 43 | 44 | # Optional npm cache directory 45 | .npm 46 | 47 | # Optional eslint cache 48 | .eslintcache 49 | 50 | # Optional REPL history 51 | .node_repl_history 52 | 53 | # Output of 'npm pack' 54 | *.tgz 55 | 56 | # Yarn Integrity file 57 | .yarn-integrity 58 | 59 | # dotenv environment variables file 60 | .env 61 | 62 | # parcel-bundler cache (https://parceljs.org/) 63 | .cache 64 | 65 | # next.js build output 66 | .next 67 | 68 | # nuxt.js build output 69 | .nuxt 70 | 71 | # Nuxt generate 72 | dist 73 | 74 | # vuepress build output 75 | .vuepress/dist 76 | 77 | # Serverless directories 78 | .serverless 79 | 80 | # IDE / Editor 81 | .idea 82 | 83 | # Service worker 84 | sw.* 85 | 86 | # macOS 87 | .DS_Store 88 | 89 | # Vim swap files 90 | *.swp 91 | 92 | static/odocs 93 | -------------------------------------------------------------------------------- /libev.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "naboris", 3 | "version": "0.1.3", 4 | "description": "Simple http server built on httpaf and lwt.", 5 | "esy": { 6 | "build": [ 7 | "dune build -p #{self.name}" 8 | ], 9 | "buildInSource": "_build", 10 | "exportedEnv": { 11 | "NABORIS_LIBEV_TEST": { 12 | "val": "true", 13 | "scope": "global" 14 | } 15 | }, 16 | "buildEnv": { 17 | "NABORIS_LIBEV_TEST": "true", 18 | "NABORIS_ROOT_PWD": "#{self.root}" 19 | } 20 | }, 21 | "repository": { 22 | "type": "git", 23 | "url": "git+https://github.com/shawn-mcginty/naboris.git" 24 | }, 25 | "author": "", 26 | "license": "MIT", 27 | "bugs": { 28 | "url": "https://github.com/shawn-mcginty/naboris/issues" 29 | }, 30 | "homepage": "https://github.com/shawn-mcginty/naboris#readme", 31 | "dependencies": { 32 | "@opam/base64": ">=3.4.0", 33 | "@opam/dune": "*", 34 | "@opam/digestif": ">=0.8.0", 35 | "@opam/reason": ">=3.4.0", 36 | "@opam/httpaf": ">=0.6.0", 37 | "@opam/httpaf-lwt-unix": ">=0.6.0", 38 | "@opam/lwt": ">=5.1.1", 39 | "@opam/lwt_ppx": ">=2.0.1", 40 | "@opam/uri": ">=2.2.0" 41 | }, 42 | "devDependencies": { 43 | "@opam/alcotest": "~1.0.0", 44 | "@opam/alcotest-lwt": "~1.0.0", 45 | "@opam/cohttp": "*", 46 | "@opam/cohttp-lwt-unix": "*", 47 | "@opam/conf-libev": "*", 48 | "@opam/odoc": "*", 49 | "@opam/merlin": "*", 50 | "esy": "^0.5.6", 51 | "ocaml": "~4.8", 52 | "reason-cli": "*" 53 | }, 54 | "peerDependencies": { 55 | "ocaml": ">=4.8.0" 56 | }, 57 | "resolutions": { 58 | "@opam/conf-libev": "esy-packages/libev:package.json#0b5eb66" 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /docs-src/components/nav/Search.vue: -------------------------------------------------------------------------------- 1 | 11 | 52 | 63 | -------------------------------------------------------------------------------- /.githooks/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # An example hook script to verify what is about to be committed. 4 | # Called by "git commit" with no arguments. The hook should 5 | # exit with non-zero status after issuing an appropriate message if 6 | # it wants to stop the commit. 7 | # 8 | # To enable this hook, rename this file to "pre-commit". 9 | echo "Running pre commit script..." 10 | 11 | if git rev-parse --verify HEAD >/dev/null 2>&1 12 | then 13 | against=HEAD 14 | else 15 | # Initial commit: diff against an empty tree object 16 | against=$(git hash-object -t tree /dev/null) 17 | fi 18 | 19 | # If you want to allow non-ASCII filenames set this variable to true. 20 | allownonascii=$(git config --bool hooks.allownonascii) 21 | 22 | # Redirect output to stderr. 23 | exec 1>&2 24 | 25 | # Cross platform projects tend to avoid non-ASCII filenames; prevent 26 | # them from being added to the repository. We exploit the fact that the 27 | # printable range starts at the space character and ends with tilde. 28 | if [ "$allownonascii" != "true" ] && 29 | # Note that the use of brackets around a tr range is ok here, (it's 30 | # even required, for portability to Solaris 10's /usr/bin/tr), since 31 | # the square bracket bytes happen to fall in the designated range. 32 | test $(git diff --cached --name-only --diff-filter=A -z $against | 33 | LC_ALL=C tr -d '[ -~]\0'| wc -c) != 0 34 | then 35 | cat <<\EOF 36 | Error: Attempt to add a non-ASCII file name. 37 | 38 | This can cause problems if you want to work with people on other platforms. 39 | 40 | To be portable it is advisable to rename the file. 41 | 42 | If you know what you are doing you can disable this check using: 43 | 44 | git config hooks.allownonascii true 45 | EOF 46 | exit 1 47 | fi 48 | 49 | # If there are whitespace errors, print the offending file names and fail. 50 | exec git diff-index --check --cached $against -- 51 | -------------------------------------------------------------------------------- /docs/html/naboris/Naboris__Session/index.html: -------------------------------------------------------------------------------- 1 | 2 | Naboris__Session (naboris.Naboris__Session)

Module Naboris__Session

type 'sessionData t
val create : string -> 'sessionData -> 'sessionData t

Creates new t('sessionData) with id of string.

val data : 'sessionData t -> 'sessionData

Return session data of given t('sessionData).

val id : 'sessionData t -> string

Return session id of given t('sessionData).

-------------------------------------------------------------------------------- /docs/html/naboris/Naboris__/Session/index.html: -------------------------------------------------------------------------------- 1 | 2 | Session (naboris.Naboris__.Session)

Module Naboris__.Session

type 'sessionData t
val create : string -> 'sessionData -> 'sessionData t

Creates new t('sessionData) with id of string.

val data : 'sessionData t -> 'sessionData

Return session data of given t('sessionData).

val id : 'sessionData t -> string

Return session id of given t('sessionData).

-------------------------------------------------------------------------------- /docs/html/naboris/Naboris__Router/index.html: -------------------------------------------------------------------------------- 1 | 2 | Naboris__Router (naboris.Naboris__Router)

Module Naboris__Router

exception InvalidUrl of string
exception DuplicateRoute of string
val generateRoute : string -> Naboris.Method.t -> Naboris.Route.t

Generate a route record from a uri target and http method.

val processPath : string -> string list * string * string list Naboris__.Query.QueryMap.t

Extracts useful parts from a uri string.

-------------------------------------------------------------------------------- /src/Req.ml: -------------------------------------------------------------------------------- 1 | type 'sessionData t = { 2 | requestDescriptor : Httpaf.Reqd.t; 3 | session : 'sessionData Session.t option; 4 | sidKey : string; 5 | maxAge : int; 6 | secret : string; 7 | staticCacheControl : string option; 8 | staticLastModified : bool; 9 | responseEtag : Etag.strength option; 10 | } 11 | 12 | let reqd req = req.requestDescriptor 13 | 14 | let getHeader headerKey req = 15 | match Httpaf.Reqd.request req.requestDescriptor with 16 | | {headers; _} -> Httpaf.Headers.get headers headerKey 17 | 18 | let getBody {requestDescriptor; _} = 19 | let body = Httpaf.Reqd.request_body requestDescriptor in 20 | let (bodyStream, pushToBodyStream) = Lwt_stream.create () in 21 | 22 | let rec on_read bigstr ~off:_ ~len:_ = 23 | let str = Bigstringaf.to_string bigstr in 24 | pushToBodyStream (Some str); 25 | Httpaf.Body.schedule_read body ~on_read ~on_eof 26 | and on_eof () = pushToBodyStream None in 27 | 28 | Httpaf.Body.schedule_read body ~on_read ~on_eof; 29 | 30 | Lwt_stream.fold (fun a b -> a ^ b) bodyStream "" 31 | 32 | let fromReqd reqd sessionConfig staticCacheControl staticLastModified responseEtag = 33 | let sidKey = SessionConfig.sidKey sessionConfig in 34 | let maxAge =SessionConfig.maxAge sessionConfig in 35 | let secret = SessionConfig.secret sessionConfig in 36 | let defaultReq = {requestDescriptor = reqd; session = None; sidKey; maxAge; secret; staticCacheControl; staticLastModified; responseEtag} in 37 | defaultReq 38 | 39 | let getSessionData req = 40 | match req.session with 41 | | None -> None 42 | | Some session -> Some (Session.data session) 43 | 44 | let setSessionData maybeSession req = 45 | {req with session = maybeSession} 46 | 47 | let sidKey req = req.sidKey 48 | 49 | let maxAge req = req.maxAge 50 | 51 | let secret req = req.secret 52 | 53 | let staticCacheControl req = req.staticCacheControl 54 | 55 | let staticLastModified req = req.staticLastModified 56 | 57 | let responseEtag req = req.responseEtag -------------------------------------------------------------------------------- /docs/html/naboris/Naboris/Session/index.html: -------------------------------------------------------------------------------- 1 | 2 | Session (naboris.Naboris.Session)

Module Naboris.Session

Module for working with sessions and session data.

type 'sessionData t
val create : string -> 'sessionData -> 'sessionData t

Creates new t('sessionData) with id of string.

val data : 'sessionData t -> 'sessionData

Return session data of given t('sessionData).

val id : 'sessionData t -> string

Return session id of given t('sessionData).

-------------------------------------------------------------------------------- /docs/html/naboris/Naboris__/Router/index.html: -------------------------------------------------------------------------------- 1 | 2 | Router (naboris.Naboris__.Router)

Module Naboris__.Router

exception InvalidUrl of string
exception DuplicateRoute of string
val generateRoute : string -> Naboris.Method.t -> Naboris.Route.t

Generate a route record from a uri target and http method.

val processPath : string -> string list * string * string list Naboris__.Query.QueryMap.t

Extracts useful parts from a uri string.

-------------------------------------------------------------------------------- /docs/html/naboris/Naboris/Router/index.html: -------------------------------------------------------------------------------- 1 | 2 | Router (naboris.Naboris.Router)

Module Naboris.Router

Less commonly used.

exception InvalidUrl of string
exception DuplicateRoute of string
val generateRoute : string -> Method.t -> Route.t

Generate a route record from a uri target and http method.

val processPath : string -> string list * string * string list Naboris__.Query.QueryMap.t

Extracts useful parts from a uri string.

-------------------------------------------------------------------------------- /src/Naboris.mli: -------------------------------------------------------------------------------- 1 | (** Will start an http server listening at [inetAddr] on port [int] with [ServerConfig.t('sessionData)]. *) 2 | val listen : 3 | ?inetAddr:Unix.inet_addr -> int -> 'sessionData ServerConfig.t -> 'a Lwt.t * 'a Lwt.u 4 | 5 | (** Same as [listen] but will specifically throw away the [Lwt.u('a)] and never resolve the promise. 6 | Keeping the server alive until the process is killed. *) 7 | val listenAndWaitForever : 8 | ?inetAddr:Unix.inet_addr -> int -> 'sessionData ServerConfig.t -> 'a Lwt.t 9 | 10 | (** Module with error handler types. *) 11 | module ErrorHandler : module type of ErrorHandler 12 | 13 | (** Module for working with incoming requests. *) 14 | module Req : module type of Req 15 | 16 | (** Module for creating and sending responses. *) 17 | module Res : module type of Res 18 | 19 | (** Module to extract routing data. *) 20 | module Route : module type of Route 21 | 22 | (** Module for configuring the naboris http server. *) 23 | module ServerConfig : module type of ServerConfig 24 | 25 | (** Module for working with sessions and session data. *) 26 | module Session : module type of Session 27 | 28 | (** Module with types used for matching requests. *) 29 | module Method : module type of Method 30 | 31 | (** Module defining middleware functions. *) 32 | module Middleware : module type of Middleware 33 | 34 | (** Module defining RequestHandler functions. *) 35 | module RequestHandler : module type of RequestHandler 36 | 37 | (** Module with some utility functions for dates for HTTP headers. *) 38 | module DateUtils : module type of DateUtils 39 | 40 | (** [Map] type for working with queries from routed requests. *) 41 | module Query : module type of Query 42 | 43 | (** {b Less commonly used.} *) 44 | module Cookie : module type of Cookie 45 | 46 | (** {b Less commonly used.} *) 47 | module MimeTypes : module type of MimeTypes 48 | 49 | (** {b Less commonly used.} *) 50 | module SessionManager : module type of SessionManager 51 | 52 | (** {b Less commonly used.} *) 53 | module Router : module type of Router 54 | 55 | (** {b Less commonly used.} *) 56 | module SessionConfig : module type of SessionConfig 57 | 58 | (** {b Less commonly used.} *) 59 | module Etag : module type of Etag -------------------------------------------------------------------------------- /docs/html/naboris/Naboris__SessionConfig/index.html: -------------------------------------------------------------------------------- 1 | 2 | Naboris__SessionConfig (naboris.Naboris__SessionConfig)

Module Naboris__SessionConfig

type 'sessionData t = {
getSession : string option -> 'sessionData Naboris.Session.t option Lwt.t;
sidKey : string;
maxAge : int;
}
val sidKey : 'sessionData t option -> string

Returns key to be used for session cookies.

val maxAge : 'sessionData t option -> int

Returns max age to be used for session cookies.

-------------------------------------------------------------------------------- /test/SessionManagerTest.ml: -------------------------------------------------------------------------------- 1 | let testSuite () = 2 | ("SessionManager", 3 | [ 4 | Alcotest_lwt.test_case 5 | "generateSessionId() - returns unique 32 char long stringsz" 6 | `Quick 7 | (fun _lwtSwitch _ -> 8 | let id1 = Naboris.SessionManager.generateSessionId () in 9 | let id2 = Naboris.SessionManager.generateSessionId () in 10 | Alcotest.( 11 | check bool "ids should not match" false (id1 = id2) 12 | ); 13 | Alcotest.( 14 | check int "should be 32 chars" 32 (String.length id1) 15 | ); 16 | Alcotest.( 17 | check int "should be 32 chars" 32 (String.length id2) 18 | ); 19 | 20 | Lwt.return_unit); 21 | Alcotest_lwt.test_case 22 | "sign() - returns sid dot separated by a hash" 23 | `Quick 24 | (fun _lwtSwitch _ -> 25 | let secret = "keep it secret, keep it safe" in 26 | let sid = Naboris.SessionManager.generateSessionId () in 27 | let signedSid = Naboris.SessionManager.sign secret sid in 28 | let (moreSid, hash) = 29 | match String.split_on_char '.' signedSid with 30 | | [x; y] -> (x, y) 31 | | _ -> ("", "") 32 | in 33 | Alcotest.( 34 | check bool "sids should match" true (sid = moreSid) 35 | ); 36 | Alcotest.( 37 | check bool "ids should not match" false (sid = hash) 38 | ); 39 | Alcotest.( 40 | check bool "ids should not match" false (sid = signedSid) 41 | ); 42 | Lwt.return_unit); 43 | Alcotest_lwt.test_case 44 | "unsign() - takes sid.hash returns sid if hashed properly" 45 | `Quick 46 | (fun _lwtSwitch _ -> 47 | let secret = "keep it secret, keep it safe" in 48 | let sid = Naboris.SessionManager.generateSessionId () in 49 | let signedSid = Naboris.SessionManager.sign secret sid in 50 | let unsignedSid = Naboris.SessionManager.unsign secret signedSid in 51 | Alcotest.( 52 | check (option string) "sids should match" (Some sid) unsignedSid 53 | ); 54 | Lwt.return_unit); 55 | ]) -------------------------------------------------------------------------------- /docs/html/naboris/Naboris__/SessionConfig/index.html: -------------------------------------------------------------------------------- 1 | 2 | SessionConfig (naboris.Naboris__.SessionConfig)

Module Naboris__.SessionConfig

type 'sessionData t = {
getSession : string option -> 'sessionData Naboris.Session.t option Lwt.t;
sidKey : string;
maxAge : int;
}
val sidKey : 'sessionData t option -> string

Returns key to be used for session cookies.

val maxAge : 'sessionData t option -> int

Returns max age to be used for session cookies.

-------------------------------------------------------------------------------- /test/utils/EtagTest.ml: -------------------------------------------------------------------------------- 1 | let testSuite () = 2 | ("utils_Etag", 3 | [ 4 | Alcotest_lwt.test_case 5 | "fromString handles empty payload properly" 6 | `Quick 7 | (fun _lwtSwitch _ -> 8 | let payload = "" in (* empty string response *) 9 | let emptyEtag = "\"0-2jmj7l5rSw0yVb/vlWAYkK/YBwk\"" in 10 | Alcotest.( 11 | check 12 | string 13 | "empty payload etag" 14 | (Naboris.Etag.fromString payload) 15 | emptyEtag 16 | ); 17 | Lwt.return_unit); 18 | Alcotest_lwt.test_case 19 | "fromString returns the same etag for the same entity" 20 | `Quick 21 | (fun _lwtSwitch _ -> 22 | let payload = "Some payload here there and around. And let's make it even longer with another line." in 23 | let expectedEtag = "\"84-rp6iUbxN39hxY6WTUx4KmuI2TTo\"" in 24 | Alcotest.( 25 | check 26 | string 27 | "empty payload etag" 28 | (Naboris.Etag.fromString payload) 29 | expectedEtag 30 | ); 31 | Lwt.return_unit); 32 | Alcotest_lwt.test_case 33 | "weakFromString handles empty payload properly" 34 | `Quick 35 | (fun _lwtSwitch _ -> 36 | let payload = "" in (* empty string response *) 37 | let emptyEtag = "W/\"0-2jmj7l5rSw0yVb/vlWAYkK/YBwk\"" in 38 | Alcotest.( 39 | check 40 | string 41 | "empty payload etag" 42 | (Naboris.Etag.weakFromString payload) 43 | emptyEtag 44 | ); 45 | Lwt.return_unit); 46 | Alcotest_lwt.test_case 47 | "weakFromString returns the same etag for the same entity" 48 | `Quick 49 | (fun _lwtSwitch _ -> 50 | let payload = "Some payload here there and around. And let's make it even longer with another line." in 51 | let expectedEtag = "W/\"84-rp6iUbxN39hxY6WTUx4KmuI2TTo\"" in 52 | Alcotest.( 53 | check 54 | string 55 | "empty payload etag" 56 | (Naboris.Etag.weakFromString payload) 57 | expectedEtag 58 | ); 59 | Lwt.return_unit); 60 | ]) -------------------------------------------------------------------------------- /docs-src/content/docs/quick-start/server-configuration.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Server Configuration 3 | --- 4 | 5 | ## Server Configuration 6 | There are a number of helper functions for building server config records. 7 | 8 | - [Creating a Server Config](#creating-server-config) 9 | - [Listen Callback](#listen-callback) 10 | - [Request Handler](#request-handler) 11 | 12 | #### # Creating a Server Config 13 | [`ServerConfig.create`](/odocs/naboris/Naboris/ServerConfig/index.html#val-create) is used to generate a default server config object, this will be the starting point. 14 | ```reason 15 | // ReasonML 16 | let create: unit => ServerConfig.t('sessionData); 17 | ``` 18 | ```ocaml 19 | (* OCaml *) 20 | val create: unit -> 'sessionData ServerConfig.t 21 | ``` 22 | 23 | #### # Listen Callback 24 | [`ServerConfig.setOnListen`](/odocs/naboris/Naboris/ServerConfig/index.html#val-setOnListen) will set the function that will be called once the server has started and is listening for connections. The `onListen` function has the type signature `unit => unit`. 25 | ```reason 26 | // ReasonML 27 | let setOnListen: (unit => unit, ServerConfig.t('sessionData)) => ServerConfig.t('sessionData) 28 | ``` 29 | ```ocaml 30 | (* OCaml *) 31 | val setOnListen: (unit -> unit) -> 'sessionData ServerConfig.t -> 'sessionData ServerConfig.t 32 | ``` 33 | 34 | #### # Request Handler 35 | [`ServerConfig.setRequestHandler`](/odocs/naboris/Naboris/ServerConfig/index.html#val-setRequestHandler) will set the main request handler function on the config. This function is the main entry point for http requests and usually where routing the request happens. The `requestHandler` function has the type signature `(Route.t, Req.t('sessionData), Res.t) => Lwt.t(Res.t)`. 36 | ```reason 37 | // ReasonML 38 | let setRequestHandler: ( 39 | (Route.t, Req.t('sessionData), Res.t) => Lwt.t(Res.t), 40 | ServerConfig.t('sessionData) 41 | ) => ServerConfig.t('sessionData) 42 | ``` 43 | ```ocaml 44 | (* OCaml *) 45 | val setRequestHandler: (Route.t -> 'sessionData Req.t -> Res.t -> Res.t Lwt.t) 46 | -> 'sessionData ServerConfig.t -> 'sessionData ServerConfig.t 47 | ``` -------------------------------------------------------------------------------- /docs/html/naboris/Naboris/SessionConfig/index.html: -------------------------------------------------------------------------------- 1 | 2 | SessionConfig (naboris.Naboris.SessionConfig)

Module Naboris.SessionConfig

Less commonly used.

type 'sessionData t = {
getSession : string option -> 'sessionData Session.t option Lwt.t;
sidKey : string;
maxAge : int;
}
val sidKey : 'sessionData t option -> string

Returns key to be used for session cookies.

val maxAge : 'sessionData t option -> int

Returns max age to be used for session cookies.

-------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "naboris", 3 | "version": "0.1.3", 4 | "description": "Simple http server built on httpaf and lwt.", 5 | "esy": { 6 | "build": [ 7 | "dune build -p #{self.name}" 8 | ], 9 | "buildInSource": "_build" 10 | }, 11 | "scripts": { 12 | "set-git-config": "cp -f .githooks/* .git/hooks", 13 | "source": "rm -f tmp-build-env && esy build-env | grep DUNE_BUILD_DIR > tmp-build-env && source tmp-build-env && rm tmp-build-env", 14 | "test": "npm run unit-test", 15 | "unit-test": "esy b dune runtest", 16 | "install": "esy install", 17 | "install-globals": "npm install -g esy", 18 | "build-docs": "/bin/bash ci/build-docs.sh", 19 | "build": "esy b dune build @install", 20 | "clean": "esy b dune clean", 21 | "clean-install": "rm -rf node_modules && rm -rf esy.lock && rm -rf _esy", 22 | "clean-world": "npm run clean && npm run clean-install", 23 | "test-coco": "npm run clean && npm run gen-test-json && npm run test && npm run coco-report && npm run gen-prod-json", 24 | "build-load-test-server": "esy @libev b dune build load-test/load_test.exe && npm run output-env", 25 | "output-env": "rm -f command-env && esy @libev build-env | grep DUNE_BUILD_DIR > command-env" 26 | }, 27 | "repository": { 28 | "type": "git", 29 | "url": "git+https://github.com/shawn-mcginty/naboris.git" 30 | }, 31 | "author": "", 32 | "license": "MIT", 33 | "bugs": { 34 | "url": "https://github.com/shawn-mcginty/naboris/issues" 35 | }, 36 | "homepage": "https://github.com/shawn-mcginty/naboris#readme", 37 | "dependencies": { 38 | "@opam/base64": ">=3.4.0", 39 | "@opam/dune": "*", 40 | "@opam/digestif": ">=0.8.0", 41 | "@opam/reason": ">=3.4.0", 42 | "@opam/httpaf": ">=0.6.0", 43 | "@opam/httpaf-lwt-unix": ">=0.6.0", 44 | "@opam/lwt": ">=5.1.1", 45 | "@opam/lwt_ppx": ">=2.0.1", 46 | "@opam/uri": ">=2.2.0" 47 | }, 48 | "devDependencies": { 49 | "@opam/alcotest": "~1.0.0", 50 | "@opam/alcotest-lwt": "~1.0.0", 51 | "@opam/cohttp": "*", 52 | "@opam/cohttp-lwt-unix": "*", 53 | "@opam/odoc": "*", 54 | "@opam/merlin": "*", 55 | "esy": "^0.5.6", 56 | "ocaml": "~4.8", 57 | "reason-cli": "*" 58 | }, 59 | "peerDependencies": { 60 | "ocaml": ">=4.8.0" 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /docs-src/content/docs/guides/security-best-practices.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Security Best Practices 3 | --- 4 | 5 | ## Security Best Practices 6 | Best practices for enhanced performance. 7 | 8 | - [SSL/TLS](#ssl-tls) 9 | - [Session Configuration](#session-configuration) 10 | - [Follow HTTP Best Practices](#follow-http-best-practices) 11 | 12 | #### # SSL/TLS 13 | For any public traffic it's important to use SSL/TLS encryption. You may notice reading the docs that there is no SSL/TLS configuration options for naboris. That is because naboris communicates exclusively over unsecured http. Encryption is still possible, and highly recommended, by using a [reverse proxy](https://en.wikipedia.org/wiki/Reverse_proxy). 14 | 15 | Here is an example configuration for an [nginx server](https://www.nginx.com/) using SSL/TLS and forwarding traffic to a local naboris server listening on port 8000. You can [find more information here](https://docs.nginx.com/nginx/admin-guide/web-server/reverse-proxy/). 16 | 17 | ```nginx 18 | server { 19 | listen 443 ssl; 20 | listen [::]:443 ssl; 21 | 22 | server_name ssltest.shawnmcginty.com www.ssltest.shawnmcginty.com; 23 | 24 | location / { 25 | proxy_pass http://localhost:8000; 26 | } 27 | 28 | ssl_certificate /fake/path/to/fullchain.pem; 29 | ssl_certificate_key /fake/path/to/privkey.pem; 30 | } 31 | ``` 32 | 33 | #### # Session Configuration 34 | If your server makes use of sessions it is important to change the default session id key. This makes many automated attacks much more difficult. [`ServerConfig.setSessionConfig`](http://localhost:3999/odocs/naboris/Naboris/ServerConfig/index.html#val-setSessionConfig) takes optional parameter `~sidKey` making it very easy to change the default session id key. More importantly the optional parameter `~secret` sets the secret used when signing session id cookies. 35 | 36 | 37 | #### # Follow HTTP Best Practices 38 | There are many guidelines and best practices to follow when securing an HTTP server. 39 | 40 | * [Use Security Related HTTP Headers](https://owasp.org/www-project-secure-headers/) 41 | * Use a Reverse Proxy Server 42 | * Run naboris With Minimum Privileges -------------------------------------------------------------------------------- /docs/html/naboris/Naboris__Server/index.html: -------------------------------------------------------------------------------- 1 | 2 | Naboris__Server (naboris.Naboris__Server)

Module Naboris__Server

module Req = Naboris.Req
module Res = Naboris.Res
module Router = Naboris.Router
exception UnknownError of string
type unknownError =
| UnknownError
val buildConnectionHandler : 'a Naboris.ServerConfig.t -> Unix.sockaddr -> Lwt_unix.file_descr -> unit Lwt.t
-------------------------------------------------------------------------------- /docs-src/pages/quick-start/index.vue: -------------------------------------------------------------------------------- 1 | 42 | 83 | -------------------------------------------------------------------------------- /docs/html/naboris/Naboris__/Server/index.html: -------------------------------------------------------------------------------- 1 | 2 | Server (naboris.Naboris__.Server)

Module Naboris__.Server

module Req = Naboris.Req
module Res = Naboris.Res
module Router = Naboris.Router
exception UnknownError of string
type unknownError =
| UnknownError
val buildConnectionHandler : 'a Naboris.ServerConfig.t -> Unix.sockaddr -> Lwt_unix.file_descr -> unit Lwt.t
-------------------------------------------------------------------------------- /docs-src/components/Footer.vue: -------------------------------------------------------------------------------- 1 | 62 | 67 | -------------------------------------------------------------------------------- /test/utils/CookieTest.ml: -------------------------------------------------------------------------------- 1 | let testSuite () = 2 | ("utils_Cookie", 3 | [ 4 | Alcotest_lwt.test_case 5 | "getSessionId returns the id from the ugly string" 6 | `Quick 7 | (fun _lwtSwitch _ -> 8 | let expectedSid = "this-is-my-big-ass-session-id-woohoo" in 9 | let cookieStr = 10 | "this=ugly; super=ugly; nab.sid=" ^ expectedSid ^ "; also=here;" 11 | in 12 | Alcotest.( 13 | check 14 | (option string) 15 | "same sid" 16 | (Naboris.Cookie.getSessionId "nab.sid" cookieStr) 17 | (Some expectedSid) 18 | ); 19 | Lwt.return_unit); 20 | Alcotest_lwt.test_case 21 | "getSessionId fix recursion bug" 22 | `Quick 23 | (fun _lwtSwitch _ -> 24 | let cookieStr = "nnect.sid=s%3AadVKe5fVEcZVq4X5ZUrMen2U88jmjy4f.LOwere3akcgCno7WDqinHgL%2BXWXVp2SgbHZzv7%2Btbt4" in 25 | let maybeCookie = Naboris.Cookie.getSessionId "nab.sid" cookieStr in 26 | Alcotest.( 27 | check (option string) "no matching cookie" None maybeCookie 28 | ); 29 | Lwt.return_unit); 30 | Alcotest_lwt.test_case 31 | "getSessionId fix big cookie str" 32 | `Quick 33 | (fun _lwtSwitch _ -> 34 | let expectedSid = "67f67df4c5d9711ef89bbf8b509d49e2cc1ce51e3d95c90d45485a7b3cf40ca4ec9cbbceb0ca6ad844ec4a4779fd9981b130c40f81646f2ef286749c7184e66f" in 35 | let cookieStr = "_ga=GA1.1.1652070095.1563853850; express.sid=s%3AhSEgvCCmOADa-0Flv4ulT1FltA8TzHeq.G1UoU2xXC8X8wkEO5I0J%2BhE3NCjUoggAlGnz0jA1%2B2w; _gid=GA1.1.1409339010.1564626384; connect.sid=s%3AClROuVLX_Dalzkmf0D4d0Xath-HHG16M.8zaxTWykLFnypEw%2BCAIZRTPJR7IKBDUcAamWUch4Czk; nab.sid=67f67df4c5d9711ef89bbf8b509d49e2cc1ce51e3d95c90d45485a7b3cf40ca4ec9cbbceb0ca6ad844ec4a4779fd9981b130c40f81646f2ef286749c7184e66f" in 36 | Alcotest.( 37 | check 38 | (option string) 39 | "same sid" 40 | (Naboris.Cookie.getSessionId "nab.sid" cookieStr) 41 | (Some expectedSid) 42 | ); 43 | Lwt.return_unit); 44 | Alcotest_lwt.test_case 45 | "getSessionId returns none when cookie str is part of the key" 46 | `Quick 47 | (fun _lwtSwitch _ -> 48 | let cookieStr = "nib" in 49 | Alcotest.( 50 | check 51 | (option string) 52 | "empty" 53 | (Naboris.Cookie.getSessionId "nab.sid" cookieStr) 54 | None 55 | ); 56 | Lwt.return_unit); 57 | ]) -------------------------------------------------------------------------------- /docs/html/naboris/Naboris__Route/index.html: -------------------------------------------------------------------------------- 1 | 2 | Naboris__Route (naboris.Naboris__Route)

Module Naboris__Route

type t
val path : t -> string list

Get path (list(string)) of t.

val meth : t -> Naboris.Method.t

Get http method (Method.t) of t.

val rawQuery : t -> string

Get query sring of t.

val query : t -> string list Naboris__.Query.QueryMap.t

Get query map Query.QueryMap.t(list(string)) ot t.

val create : string list -> Naboris.Method.t -> string -> string list Naboris__.Query.QueryMap.t -> t

Intended for internal use.

Create route record t.

-------------------------------------------------------------------------------- /docs-src/pages/guides/index.vue: -------------------------------------------------------------------------------- 1 | 42 | 88 | -------------------------------------------------------------------------------- /docs/html/naboris/Naboris__/Route/index.html: -------------------------------------------------------------------------------- 1 | 2 | Route (naboris.Naboris__.Route)

Module Naboris__.Route

type t
val path : t -> string list

Get path (list(string)) of t.

val meth : t -> Naboris.Method.t

Get http method (Method.t) of t.

val rawQuery : t -> string

Get query sring of t.

val query : t -> string list Naboris__.Query.QueryMap.t

Get query map Query.QueryMap.t(list(string)) ot t.

val create : string list -> Naboris.Method.t -> string -> string list Naboris__.Query.QueryMap.t -> t

Intended for internal use.

Create route record t.

-------------------------------------------------------------------------------- /docs/html/naboris/Naboris/Route/index.html: -------------------------------------------------------------------------------- 1 | 2 | Route (naboris.Naboris.Route)

Module Naboris.Route

Module to extract routing data.

type t
val path : t -> string list

Get path (list(string)) of t.

val meth : t -> Method.t

Get http method (Method.t) of t.

val rawQuery : t -> string

Get query sring of t.

val query : t -> string list Naboris__.Query.QueryMap.t

Get query map Query.QueryMap.t(list(string)) ot t.

val create : string list -> Method.t -> string -> string list Naboris__.Query.QueryMap.t -> t

Intended for internal use.

Create route record t.

-------------------------------------------------------------------------------- /docs/html/naboris/Naboris/SessionManager/index.html: -------------------------------------------------------------------------------- 1 | 2 | SessionManager (naboris.Naboris.SessionManager)

Module Naboris.SessionManager

Less commonly used.

val startSession : 'sessionData Req.t -> Res.t -> 'sessionData -> 'sessionData Req.t * Res.t * string

Create a new session with 'sessionData and add cookie headers to Res.t. Returns newly created session id string.

val removeSession : 'sessionData Req.t -> Res.t -> Res.t

Sets headers on `Res.t` to expire the session.

val resumeSession : 'sessionData ServerConfig.t -> 'sessionData Req.t -> 'sessionData Req.t Lwt.t

Intended for internal use.

Applies mapSession from config to request which uses the session id from the request cookies. Returns promise of a new request with session data available if it was found.

-------------------------------------------------------------------------------- /docs/html/naboris/Naboris__SessionManager/index.html: -------------------------------------------------------------------------------- 1 | 2 | Naboris__SessionManager (naboris.Naboris__SessionManager)

Module Naboris__SessionManager

val startSession : 'sessionData Naboris.Req.t -> Naboris.Res.t -> 'sessionData -> 'sessionData Naboris.Req.t * Naboris.Res.t * string

Create a new session with 'sessionData and add cookie headers to Res.t. Returns newly created session id string.

val removeSession : 'sessionData Naboris.Req.t -> Naboris.Res.t -> Naboris.Res.t

Sets headers on `Res.t` to expire the session.

val resumeSession : 'sessionData Naboris.ServerConfig.t -> 'sessionData Naboris.Req.t -> 'sessionData Naboris.Req.t Lwt.t

Intended for internal use.

Applies mapSession from config to request which uses the session id from the request cookies. Returns promise of a new request with session data available if it was found.

-------------------------------------------------------------------------------- /docs-src/static/icons/ocaml.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/html/naboris/Naboris__/SessionManager/index.html: -------------------------------------------------------------------------------- 1 | 2 | SessionManager (naboris.Naboris__.SessionManager)

Module Naboris__.SessionManager

val startSession : 'sessionData Naboris.Req.t -> Naboris.Res.t -> 'sessionData -> 'sessionData Naboris.Req.t * Naboris.Res.t * string

Create a new session with 'sessionData and add cookie headers to Res.t. Returns newly created session id string.

val removeSession : 'sessionData Naboris.Req.t -> Naboris.Res.t -> Naboris.Res.t

Sets headers on `Res.t` to expire the session.

val resumeSession : 'sessionData Naboris.ServerConfig.t -> 'sessionData Naboris.Req.t -> 'sessionData Naboris.Req.t Lwt.t

Intended for internal use.

Applies mapSession from config to request which uses the session id from the request cookies. Returns promise of a new request with session data available if it was found.

-------------------------------------------------------------------------------- /docs-src/assets/sass/_fonts.scss: -------------------------------------------------------------------------------- 1 | $google-font-path: "~static/fonts/google"; 2 | 3 | 4 | /* cyrillic-ext */ 5 | @font-face { 6 | font-family: 'Noto Sans'; 7 | font-style: normal; 8 | font-weight: 400; 9 | font-display: block; 10 | src: local('Noto Sans'), local('NotoSans'), url("#{$google-font-path}/noto-sans-cyrillic-ext.woff2") format('woff2'); 11 | unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F; 12 | } 13 | /* cyrillic */ 14 | @font-face { 15 | font-family: 'Noto Sans'; 16 | font-style: normal; 17 | font-weight: 400; 18 | font-display: block; 19 | src: local('Noto Sans'), local('NotoSans'), url("#{$google-font-path}/noto-sans-cyrillic.woff2") format('woff2'); 20 | unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; 21 | } 22 | /* devanagari */ 23 | @font-face { 24 | font-family: 'Noto Sans'; 25 | font-style: normal; 26 | font-weight: 400; 27 | font-display: block; 28 | src: local('Noto Sans'), local('NotoSans'), url("#{$google-font-path}/noto-sans-devanagari.woff2") format('woff2'); 29 | unicode-range: U+0900-097F, U+1CD0-1CF6, U+1CF8-1CF9, U+200C-200D, U+20A8, U+20B9, U+25CC, U+A830-A839, U+A8E0-A8FB; 30 | } 31 | /* greek-ext */ 32 | @font-face { 33 | font-family: 'Noto Sans'; 34 | font-style: normal; 35 | font-weight: 400; 36 | font-display: block; 37 | src: local('Noto Sans'), local('NotoSans'), url("#{$google-font-path}/noto-sans-greek-ext.woff2") format('woff2'); 38 | unicode-range: U+1F00-1FFF; 39 | } 40 | /* greek */ 41 | @font-face { 42 | font-family: 'Noto Sans'; 43 | font-style: normal; 44 | font-weight: 400; 45 | font-display: block; 46 | src: local('Noto Sans'), local('NotoSans'), url("#{$google-font-path}/noto-sans-greek.woff2") format('woff2'); 47 | unicode-range: U+0370-03FF; 48 | } 49 | /* vietnamese */ 50 | @font-face { 51 | font-family: 'Noto Sans'; 52 | font-style: normal; 53 | font-weight: 400; 54 | font-display: block; 55 | src: local('Noto Sans'), local('NotoSans'), url("#{$google-font-path}/noto-sans-vietnamese.woff2") format('woff2'); 56 | unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB; 57 | } 58 | /* latin-ext */ 59 | @font-face { 60 | font-family: 'Noto Sans'; 61 | font-style: normal; 62 | font-weight: 400; 63 | font-display: block; 64 | src: local('Noto Sans'), local('NotoSans'), url("#{$google-font-path}/noto-sans-latin-ext.woff2") format('woff2'); 65 | unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF; 66 | } 67 | /* latin */ 68 | @font-face { 69 | font-family: 'Noto Sans'; 70 | font-style: normal; 71 | font-weight: 400; 72 | font-display: block; 73 | src: local('Noto Sans'), local('NotoSans'), url("#{$google-font-path}/noto-sans-latin.woff2") format('woff2'); 74 | unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; 75 | } -------------------------------------------------------------------------------- /docs-src/content/docs/quick-start/static-files.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Static Files 3 | --- 4 | 5 | ## Static Files 6 | **naboris** provides some helpers to make serving static files easy. 7 | 8 | - [Middleware](#middleware) 9 | - [Routing](#routing) 10 | 11 | #### # Middleware 12 | 13 | `Naboris.ServerConfig` has a sweet helper function to create a middleware 14 | which will map incoming requests to a local directory. 15 | 16 | ```reason 17 | /** 18 | Creates a virtual path prefix [list(string)] and maps it to a local directory [string]. 19 | 20 | Middlewares are executed in the order they are added. The final "middleware" is the [requestHandler]. 21 | */ 22 | let addStaticMiddleware: (list(string), string, t('sessionData)) => t('sessionData); 23 | ``` 24 | ```ocaml 25 | (** 26 | Creates a virtual path prefix [string list] and maps it to a local directory [string]. 27 | 28 | Middlewares are executed in the order they are added. The final "middleware" is the [requestHandler]. 29 | *) 30 | val addStaticMiddleware: string list -> string -> 'sessionData t -> 'sessionData t 31 | ``` 32 | 33 | * `list(string)` - Will match against the `Route.path` of each incoming request. 34 | * `string` - Path to a local directory which will have the rest of the path applied to. 35 | * `t` - Current `Naboris.ServerConfig`. 36 | 37 | The rest of the incoming request will be applied to the local path 38 | e.g. given inputs `["static"], "/path/to/public"` a request for `/static/images/logo.png` would map to `/path/to/public/images/logo.png`. 39 | 40 | ```reason 41 | let serverConfig = Naboris.ServerConfig.create() 42 | |> Naboris.ServerConfig.addStaticMiddleware(["static"], Sys.getcwd("cur__root") ++ "/public/"); 43 | ``` 44 | ```ocaml 45 | let server_config = Naboris.ServerConfig.create () 46 | |> Naboris.ServerConfig.addStaticMiddleware 47 | ["static"] 48 | (Sys.getcwd () ^ "/public/") in 49 | ``` 50 | 51 | #### # Routing 52 | 53 | For more fine grained control over serving static files there is also 54 | a helper function `Res.static`. The inputs are the same as the above 55 | middleware helper function. 56 | 57 | ```reason 58 | let publicDir = Sys.getcwd() ++ "/public/"; 59 | let serverConfig = Naboris.ServerConfig.create() 60 | |> Naboris.ServerConfig.setRequestHandler((route, req, res) => { 61 | switch (Naboris.Route.meth(route), Naboris.Route.path(route)) { 62 | | _ => 63 | Naboris.Res.status(404, res) 64 | |> Naboris.Res.static(publicDir, ["not_found.html"], req); 65 | } 66 | }); 67 | ``` 68 | ```ocaml 69 | let public_dir = Sys.getcwd () ^ "/public/" in 70 | let server_config = Naboris.ServerConfig.create () 71 | |> Naboris.ServerConfig.setRequestHandler(fun route req res -> 72 | match (Naboris.Route.meth route, Naboris.Route.path route) with 73 | | _ -> 74 | Naboris.Res.status 404 res 75 | |> Naboris.Res.static 76 | public_dir 77 | ["not_found.html"] 78 | req) in 79 | ``` 80 | -------------------------------------------------------------------------------- /src/Res.mli: -------------------------------------------------------------------------------- 1 | type t 2 | 3 | (** Creates a default response record with empty headers and a 200 status. *) 4 | val default : unit -> t 5 | 6 | (** Creates new response from input response with status of [int]. *) 7 | val status : int -> t -> t 8 | 9 | (** Sends response [t] with body [string]. 10 | Adding headers [Content-type: application/json] and [Content-length] 11 | 12 | {e This function will end the http request/response lifecycle.} *) 13 | val json : 'sessionData Req.t -> string -> t -> t Lwt.t 14 | 15 | (** Sends response [t] with body [string]. 16 | Adding headers [Content-type: text/html] and [Content-length] 17 | 18 | {e This function will end the http request/response lifecycle.} *) 19 | val html : 'sessionData Req.t -> string -> t -> t Lwt.t 20 | 21 | (** Sends response [t] with body [string]. 22 | Adding headers [Content-type: text/plain] and [Content-length] 23 | 24 | {e This function will end the http request/response lifecycle.} *) 25 | val text : 'sessionData Req.t -> string -> t -> t Lwt.t 26 | 27 | (** Sends response [t] with body [string]. 28 | 29 | {e This function will add [Content-length] header with the length of [string].} 30 | {e This function will add [Connection: keep-alive] header.} 31 | {e This function will end the http request/response lifecycle.} *) 32 | val raw : 'sessionData Req.t -> string -> t -> t Lwt.t 33 | 34 | (** Creates a [Lwt_io.channel(Output)] which can be written to to stream data to the client. 35 | And a [Lwt.t(t)] promise, which will resolve when the output channel is closed. 36 | This will set [Transfer-Encoding: chunked] header and follow the protocol for chunked responses. *) 37 | val writeChannel : 'a Req.t -> t -> (Lwt_io.output Lwt_io.channel * t Lwt.t) 38 | 39 | (** Creates new response from [t] with header [(string, string)] added. *) 40 | val addHeader : (string * string) -> t -> t 41 | 42 | (** Opens file starting at path [string] and following [string list]. 43 | Sets [Content-type] header based on file extension. If type cannot be inferred [text/plain] is used. 44 | Sets [Content-length] header with the size of the file in bytes. 45 | Responds with [404] if file does not exist. 46 | 47 | {e This function will end the http request/response lifecycle.} *) 48 | val static : string -> string list -> 'sessionData Req.t -> t -> t Lwt.t 49 | 50 | (** Sets [Location] header to [string] and responds with [302]. 51 | Redirecting client to [string]. *) 52 | val redirect : string -> 'sessionData Req.t -> t -> t Lwt.t 53 | 54 | (** Report an error [exn] by executing [error_handler] from your [Naboris.ServerConfig]. 55 | Final response code is always [500]. 56 | 57 | {e This function will create a new [Res] and any headers on the current [Res] will be lost.} *) 58 | val reportError : exn -> 'sessionData Req.t -> t -> t Lwt.t 59 | 60 | (** Adds [Set-Cookie] header to response [t] with 61 | [string] sessionId 62 | [string] cookie key 63 | [int] max age of cookie in seconds 64 | 65 | {e These will be configurable in future versions.} *) 66 | val setSessionCookies : string -> string -> int -> t -> t -------------------------------------------------------------------------------- /docs-src/content/docs/quick-start/installation.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Installation 3 | --- 4 | 5 | > During the time of this writing naboris has not been tested on **Windows**. 6 | 7 | ## Installation 8 | How to install naboris. 9 | 10 | - [Requirements](#requirements) 11 | - [Scaffolding](#scaffolding) 12 | - [Opam](#opam) 13 | - [Dune](#dune) 14 | - [Esy](#esy) 15 | - [Libev](#libev) 16 | 17 | #### # Requirements 18 | 19 | - **OCaml** - `>= 4.07.1` 20 | - **Lwt** - `>= 5.1.1` 21 | 22 | ###### Optional requirements 23 | Naboris makes heavy use of `Lwt` promises. For best performance 24 | it is recommended (per [the Lwt documentation](https://ocsigen.org/lwt/5.1.1/manual/manual)) 25 | to install `libev` and use the `conf-libev` opam package. Read more about it [below](#libev). 26 | 27 | #### # Scaffolding 28 | For an easy way to get a server up and running there are scaffolding projects available on 29 | GitHub. These projects use [esy](https://esy.sh) to sandbox opam and build. This means 30 | `node.js` is required to run these projects as is. 31 | 32 | OCaml: 33 | ```bash 34 | $ git clone git@github.com:shawn-mcginty/naboris-ml-scaffold.git 35 | $ npm run install 36 | $ npm run build 37 | $ npm run start 38 | ``` 39 | 40 | ReasonML: 41 | ```bash 42 | $ git clone git@github.com:shawn-mcginty/naboris-re-scaffold.git 43 | $ npm run install 44 | $ npm run build 45 | $ npm run start 46 | ``` 47 | 48 | #### # Opam 49 | naboris is available on `opam` 50 | 51 | ```bash 52 | $ opam install naboris 53 | ``` 54 | 55 | #### # Dune 56 | ```lisp 57 | (libraries naboris) 58 | ``` 59 | 60 | #### # Esy 61 | ```json 62 | "dependencies": { 63 | "@opam/naboris": "*", 64 | } 65 | ``` 66 | 67 | _If you're using esy please read the libev section._ 68 | 69 | #### # Libev 70 | It is highly recommended to install `libev` and use the `conf-libev` opam package 71 | which will configure `lwt` to run using the `libev` scheduler. 72 | 73 | `libev` can most likely be installed using your package manager. 74 | 75 | e.g. homebrew 76 | ```bash 77 | $ brew install libev 78 | ``` 79 | 80 | e.g. apt 81 | ```bash 82 | $ sudo apt-get update 83 | $ sudo apt-get install libev-dev 84 | ``` 85 | 86 | Check out the [libev homepage](http://software.schmorp.de/pkg/libev.html) for more info. 87 | 88 | `conf-libev` can be installed via opam 89 | 90 | Opam: 91 | ```bash 92 | $ opam install conf-libev 93 | ``` 94 | 95 | 96 | If you use **esy** for sandboxing you'll have to use a special resolution: 97 | ```json 98 | "resolutions": { 99 | "@opam/conf-libev": "esy-packages/libev:package.json#0b5eb66" 100 | } 101 | ``` 102 | 103 | _Notes about esy custom resolution: This is pegged to a specific commit. At the time of this writing the commit listed above worked great. You may need to check the [GitHub repo](https://github.com/esy-packages/libev) and switch to a fresher commit._ 104 | 105 | -------------------------------------------------------------------------------- /docs/html/naboris/Naboris__Method/index.html: -------------------------------------------------------------------------------- 1 | 2 | Naboris__Method (naboris.Naboris__Method)

Module Naboris__Method

type t =
| GET
| POST
| PUT
| PATCH
| DELETE
| CONNECT
| OPTIONS
| TRACE
| Other of string

Represents an HTTP Method

val ofString : string -> t
val toString : t -> string
val ofHttpAfMethod : Httpaf.Method.t -> t
-------------------------------------------------------------------------------- /docs-src/content/docs/guides/templating-engines.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Templating Engines 3 | --- 4 | 5 | 6 | ## Templating Engines 7 | Example usage of HTML templating engines with naboris. 8 | 9 | - [Basics](#basics) 10 | - [Full Example](#full-example) 11 | 12 | #### # Basics 13 | At the end of the day templating engines are just functions which return a `string`. This makes it much easier to create dynamic HTML documents generated by your server. There can be many implementations of this but usually you will define a **template** and use some data structure to augment that template. The result of all of this will be a partial or complete document which then you can server to the user. 14 | 15 | Some libraries for templating: 16 | * [Jingoo](https://github.com/tategakibunko/jingoo) 17 | * [Mustache](https://github.com/rgrinberg/ocaml-mustache) 18 | * [TyXML](https://github.com/ocsigen/tyxml) 19 | 20 | #### # Full Example 21 | Here is a very small example of a `requestHandler` using [Mustache](https://github.com/rgrinberg/ocaml-mustache) to create a dynamic HTML document 22 | 23 | ```reason 24 | let template = Mustache.of_string(" 25 | 26 | {{pageName}} 27 | 28 | 29 |

Welcome to {{pageName}} page

30 | 31 | "); 32 | 33 | let startServer = () => { 34 | let port = 9000; 35 | let serverConfig = Naboris.ServerConfig.create() 36 | |> Naboris.ServerConfig.setRequestHandler((route, req, res) => 37 | switch (Naboris.Route.meth(route), Naboris.Route.path(route)) { 38 | | (GET, [pageName]) => 39 | let json = `O([("pageName", `String(pageName))]); 40 | let html = Mustache.render(template, json); 41 | Naboris.Res.status(200, res) 42 | |> Naboris.Res.html(req, html); 43 | | _ => 44 | Naboris.Res.status(404, res) 45 | |> Naboris.Res.text(req, "Not Found."); 46 | }); 47 | 48 | Naboris.listenAndWaitForever(port, serverConfig); 49 | } 50 | 51 | Lwt_main.run(startServer()); 52 | ``` 53 | ```ocaml 54 | let template = Mustache.of_string "\ 55 | \ 56 | {{page_name}}\ 57 | \ 58 | \ 59 |

Welcome to {{page_name}} page

\ 60 | \ 61 | " 62 | 63 | let start_server ()= 64 | let port = 9000 in 65 | let server_config = Naboris.ServerConfig.create() 66 | |> Naboris.ServerConfig.setRequestHandler(fun route req res -> 67 | match (Naboris.Route.meth route, Naboris.Route.path route) with 68 | | (GET, [page_name]) -> 69 | let json = `O [ ("page_name", `String page_name) ] in 70 | let html = Mustache.render template json in 71 | Naboris.Res.status 200 res 72 | |> Naboris.Res.html req html 73 | | _ -> 74 | Naboris.Res.status 404 res 75 | |> Naboris.Res.text req "Not Found.") in 76 | 77 | Naboris.listenAndWaitForever port server_config 78 | 79 | let _ = Lwt_main.run(start_server ()) 80 | ``` 81 | 82 | Visiting `http://localhost:9000/:pageName` will render an html document with the parameter from the path. -------------------------------------------------------------------------------- /docs/html/naboris/Naboris__/Method/index.html: -------------------------------------------------------------------------------- 1 | 2 | Method (naboris.Naboris__.Method)

Module Naboris__.Method

type t =
| GET
| POST
| PUT
| PATCH
| DELETE
| CONNECT
| OPTIONS
| TRACE
| Other of string

Represents an HTTP Method

val ofString : string -> t
val toString : t -> string
val ofHttpAfMethod : Httpaf.Method.t -> t
-------------------------------------------------------------------------------- /docs/html/naboris/Naboris/Method/index.html: -------------------------------------------------------------------------------- 1 | 2 | Method (naboris.Naboris.Method)

Module Naboris.Method

Module with types used for matching requests.

type t =
| GET
| POST
| PUT
| PATCH
| DELETE
| CONNECT
| OPTIONS
| TRACE
| Other of string

Represents an HTTP Method

val ofString : string -> t
val toString : t -> string
val ofHttpAfMethod : Httpaf.Method.t -> t
-------------------------------------------------------------------------------- /src/ServerConfig.ml: -------------------------------------------------------------------------------- 1 | type httpAfConfig = { 2 | read_buffer_size : int; 3 | request_body_buffer_size : int; 4 | response_buffer_size : int; 5 | response_body_buffer_size : int; 6 | } 7 | 8 | type 'sessionData t = { 9 | onListen : unit -> unit; 10 | routeRequest : Route.t -> 'sessionData Req.t -> Res.t -> Res.t Lwt.t; 11 | sessionConfig : 'sessionData SessionConfig.t option; 12 | errorHandler : ErrorHandler.t option; 13 | httpAfConfig : httpAfConfig option; 14 | middlewares : 'sessionData Middleware.t list; 15 | staticCacheControl : string option; 16 | staticLastModified : bool; 17 | etag : Etag.strength option; 18 | } 19 | 20 | let default = { 21 | onListen = (fun () -> ()); 22 | errorHandler = None; 23 | routeRequest = (fun _route req res -> 24 | res |> Res.status 404 |> Res.raw req "Resource not found"); 25 | sessionConfig = None; 26 | httpAfConfig = None; 27 | middlewares = []; 28 | staticCacheControl = Some "public, max-age=0"; 29 | staticLastModified = true; 30 | etag = Some `Weak; 31 | } 32 | 33 | let sessionConfig conf = conf.sessionConfig 34 | 35 | let errorHandler conf = conf.errorHandler 36 | 37 | let routeRequest conf = conf.routeRequest 38 | 39 | let onListen conf = conf.onListen 40 | 41 | let create () = default 42 | 43 | let setOnListen onListenFn conf = { conf with onListen = onListenFn } 44 | 45 | let setRequestHandler reqHandlerFn conf = { 46 | conf with routeRequest = reqHandlerFn; 47 | } 48 | 49 | let setErrorHandler errHandlerFn conf = { 50 | conf with errorHandler = Some errHandlerFn; 51 | } 52 | 53 | let setHttpAfConfig httpAfConfig conf = { 54 | conf with httpAfConfig = Some httpAfConfig; 55 | } 56 | 57 | let addMiddleware middleware conf = { 58 | conf with middlewares = List.append conf.middlewares [middleware]; 59 | } 60 | 61 | let middlewares conf = conf.middlewares 62 | 63 | let rec matchPaths matcher path = 64 | match (matcher, path) with 65 | | ([x], y :: rest) when x = y -> Some rest 66 | | (x :: restMatcher, y :: restPath) when x = y -> 67 | matchPaths restMatcher restPath 68 | | _ -> None 69 | 70 | let addStaticMiddleware pathPrefix publicPath conf = 71 | conf 72 | |> addMiddleware (fun next route req res -> 73 | match (Route.meth route, Route.path route) with 74 | | (Method.GET, path) -> 75 | (match matchPaths pathPrefix path with 76 | | Some remainingPath -> 77 | Res.static publicPath remainingPath req res 78 | | _ -> next route req res) 79 | | _ -> next route req res) 80 | 81 | let setSessionConfig ?(maxAge=2592000) ?(sidKey="nab.sid") ?(secret="please set to a secure value") getSessionFn conf = 82 | let sessionConfig : 'sessionData SessionConfig.t = { 83 | SessionConfig.getSession = getSessionFn; 84 | maxAge; 85 | sidKey; 86 | secret; 87 | } in 88 | { conf with sessionConfig = Some sessionConfig } 89 | 90 | let staticCacheControl conf = conf.staticCacheControl 91 | 92 | let setStaticCacheControl cacheControl conf = { conf with staticCacheControl = cacheControl } 93 | 94 | let staticLastModified conf = conf.staticLastModified 95 | 96 | let setStaticLastModified staticLastModified conf = { conf with staticLastModified } 97 | 98 | let etag conf = conf.etag 99 | 100 | let setEtag etag (conf : 'sessionData t) = { conf with etag } 101 | 102 | let httpAfConfig (conf : 'sessionData t) : Httpaf.Config.t option = 103 | match conf.httpAfConfig with 104 | | None -> None 105 | | Some httpConf -> 106 | let { 107 | read_buffer_size; 108 | request_body_buffer_size; 109 | response_buffer_size; 110 | response_body_buffer_size; 111 | } = httpConf in 112 | Some { 113 | Httpaf.Config.read_buffer_size; 114 | request_body_buffer_size; 115 | response_buffer_size; 116 | response_body_buffer_size; 117 | } -------------------------------------------------------------------------------- /test/MethodTest.ml: -------------------------------------------------------------------------------- 1 | let testSuite () = 2 | ("Method", 3 | [ 4 | Alcotest_lwt.test_case 5 | "ofString returns good values for all good strings" 6 | `Quick 7 | (fun _lwtSwitch _ -> 8 | Alcotest.( 9 | check bool "GET" true (Naboris.Method.ofString "GET" = GET) 10 | ); 11 | Alcotest.( 12 | check bool "POST" true (Naboris.Method.ofString "POST" = POST) 13 | ); 14 | Alcotest.( 15 | check bool "PUT" true (Naboris.Method.ofString "PUT" = PUT) 16 | ); 17 | Alcotest.( 18 | check 19 | bool 20 | "PATCH" 21 | true 22 | (Naboris.Method.ofString "PATCH" = PATCH) 23 | ); 24 | Alcotest.( 25 | check 26 | bool 27 | "DELETE" 28 | true 29 | (Naboris.Method.ofString "DELETE" = DELETE) 30 | ); 31 | Alcotest.( 32 | check 33 | bool 34 | "CONNECT" 35 | true 36 | (Naboris.Method.ofString "CONNECT" = CONNECT) 37 | ); 38 | Alcotest.( 39 | check 40 | bool 41 | "OPTIONS" 42 | true 43 | (Naboris.Method.ofString "OPTIONS" = OPTIONS) 44 | ); 45 | Alcotest.( 46 | check 47 | bool 48 | "TRACE" 49 | true 50 | (Naboris.Method.ofString "TRACE" = TRACE) 51 | ); 52 | Lwt.return_unit); 53 | Alcotest_lwt.test_case 54 | "ofString returns Other(s) when given s non standard value" 55 | `Quick 56 | (fun _lwtSwitch _ -> 57 | Alcotest.( 58 | check 59 | bool 60 | "foo" 61 | true 62 | (Naboris.Method.ofString "foo" = Other "foo") 63 | ); 64 | Alcotest.( 65 | check 66 | bool 67 | "bar" 68 | true 69 | (Naboris.Method.ofString "bar" = Other "bar") 70 | ); 71 | Lwt.return_unit); 72 | Alcotest_lwt.test_case 73 | "toString converts all standard meths to string values" 74 | `Quick 75 | (fun _lwtSwitch _ -> 76 | Alcotest.(check string "GET" (Naboris.Method.toString GET) "GET"); 77 | Alcotest.( 78 | check string "POST" (Naboris.Method.toString POST) "POST" 79 | ); 80 | Alcotest.(check string "PUT" (Naboris.Method.toString PUT) "PUT"); 81 | Alcotest.( 82 | check string "PATCH" (Naboris.Method.toString PATCH) "PATCH" 83 | ); 84 | Alcotest.( 85 | check string "DELETE" (Naboris.Method.toString DELETE) "DELETE" 86 | ); 87 | Alcotest.( 88 | check 89 | string 90 | "CONNECT" 91 | (Naboris.Method.toString CONNECT) 92 | "CONNECT" 93 | ); 94 | Alcotest.( 95 | check 96 | string 97 | "OPTIONS" 98 | (Naboris.Method.toString OPTIONS) 99 | "OPTIONS" 100 | ); 101 | Alcotest.( 102 | check string "TRACE" (Naboris.Method.toString TRACE) "TRACE" 103 | ); 104 | Lwt.return_unit); 105 | Alcotest_lwt.test_case 106 | "toString converts non standard to string" 107 | `Quick 108 | (fun _lwtSwitch _ -> 109 | Alcotest.( 110 | check string "foo" (Naboris.Method.toString (Other "foo")) "foo" 111 | ); 112 | Alcotest.( 113 | check string "bar" (Naboris.Method.toString (Other "bar")) "bar" 114 | ); 115 | Lwt.return_unit); 116 | ]) -------------------------------------------------------------------------------- /docs/html/naboris/Naboris__Req/index.html: -------------------------------------------------------------------------------- 1 | 2 | Naboris__Req (naboris.Naboris__Req)

Module Naboris__Req

type 'sessionData t
val reqd : 'sessionData t -> Httpaf.Reqd.t

Get HttpAf request descriptor.

val getHeader : string -> 'sessionData t -> string option

Get header from request. None if no matching header is found.

val getBody : 'sessionData t -> string Lwt.t

Get lwt promise of the body string from an http request.

val getSessionData : 'sessionData t -> 'sessionData option

Extracts 'sessionData from request.

Returns None if no session exists.

val setSessionData : 'sessionData Naboris.Session.t option -> 'sessionData t -> 'sessionData t

Sets 'sessionData onto a request.

val fromReqd : Httpaf.Reqd.t -> 'sessionData Naboris.SessionConfig.t option -> 'sessionData t

Intended for internal use. Creates default req record.

val sidKey : 'sessionData t -> string

Get key for session id cookie

val maxAge : 'sessionData t -> int

Get max age for session id cookies (in seconds)

-------------------------------------------------------------------------------- /docs/html/naboris/Naboris__/Req/index.html: -------------------------------------------------------------------------------- 1 | 2 | Req (naboris.Naboris__.Req)

Module Naboris__.Req

type 'sessionData t
val reqd : 'sessionData t -> Httpaf.Reqd.t

Get HttpAf request descriptor.

val getHeader : string -> 'sessionData t -> string option

Get header from request. None if no matching header is found.

val getBody : 'sessionData t -> string Lwt.t

Get lwt promise of the body string from an http request.

val getSessionData : 'sessionData t -> 'sessionData option

Extracts 'sessionData from request.

Returns None if no session exists.

val setSessionData : 'sessionData Naboris.Session.t option -> 'sessionData t -> 'sessionData t

Sets 'sessionData onto a request.

val fromReqd : Httpaf.Reqd.t -> 'sessionData Naboris.SessionConfig.t option -> 'sessionData t

Intended for internal use. Creates default req record.

val sidKey : 'sessionData t -> string

Get key for session id cookie

val maxAge : 'sessionData t -> int

Get max age for session id cookies (in seconds)

-------------------------------------------------------------------------------- /docs/html/naboris/Naboris/Req/index.html: -------------------------------------------------------------------------------- 1 | 2 | Req (naboris.Naboris.Req)

Module Naboris.Req

Module for working with incoming requests.

type 'sessionData t
val reqd : 'sessionData t -> Httpaf.Reqd.t

Get HttpAf request descriptor.

val getHeader : string -> 'sessionData t -> string option

Get header from request. None if no matching header is found.

val getBody : 'sessionData t -> string Lwt.t

Get lwt promise of the body string from an http request.

val getSessionData : 'sessionData t -> 'sessionData option

Extracts 'sessionData from request.

Returns None if no session exists.

val setSessionData : 'sessionData Session.t option -> 'sessionData t -> 'sessionData t

Sets 'sessionData onto a request.

val fromReqd : Httpaf.Reqd.t -> 'sessionData SessionConfig.t option -> 'sessionData t

Intended for internal use. Creates default req record.

val sidKey : 'sessionData t -> string

Get key for session id cookie

val maxAge : 'sessionData t -> int

Get max age for session id cookies (in seconds)

-------------------------------------------------------------------------------- /docs-src/components/nav/NavBar.vue: -------------------------------------------------------------------------------- 1 | 90 | 126 | -------------------------------------------------------------------------------- /docs-src/nuxt.config.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import glob from 'glob'; 3 | 4 | import markdownIt from 'markdown-it'; 5 | import markdownItPrism from 'markdown-it-prism'; 6 | import Mode from 'frontmatter-markdown-loader/mode'; 7 | 8 | const plugin = (md, params) => { 9 | const defaultHighlight = md.options.highlight; 10 | 11 | md.options.highlight = (str, lang) => { 12 | if (!lang) { 13 | return str; 14 | } 15 | const langPre = `
`;
 16 |     let lineNum = 0;
 17 |     let newLine = '';
 18 |     const newStr = defaultHighlight(str, lang)
 19 |       .replace(langPre, `${langPre}\n`)
 20 |       .replace('\n
', '') 21 | .replace(/\n/g, () => { 22 | lineNum++; 23 | if (lineNum > 1) { 24 | newLine = '\n'; 25 | } 26 | return `${newLine}${lineNum}`; 27 | }); 28 | return newStr; 29 | }; 30 | }; 31 | 32 | function getDynamicPaths (urlFilepathTable) { 33 | return [].concat( 34 | ...Object.keys(urlFilepathTable).map((url) => { 35 | const filepathGlob = urlFilepathTable[url]; 36 | return glob 37 | .sync(filepathGlob, { cwd: 'content' }) 38 | .map(filepath => `${url}/${path.basename(filepath, '.md')}`); 39 | }) 40 | ); 41 | } 42 | 43 | export default { 44 | mode: 'universal', 45 | /* 46 | ** Headers of the page 47 | */ 48 | head: { 49 | htmlAttrs: { 50 | lang: 'en' 51 | }, 52 | title: process.env.npm_package_name || '', 53 | meta: [ 54 | { charset: 'utf-8' }, 55 | { name: 'viewport', content: 'width=device-width, initial-scale=1' }, 56 | { hid: 'description', name: 'description', content: process.env.npm_package_description || '' } 57 | ], 58 | link: [ 59 | { rel: 'icon', type: 'image/png', href: '/logos/logo-color-16x16.png', sizes: '16x16' }, 60 | { rel: 'icon', type: 'image/png', href: '/logos/logo-color-32x32.png', sizes: '32x32' }, 61 | { rel: 'icon', type: 'image/png', href: '/logos/logo-color-96x96.png', sizes: '96x96' }, 62 | { rel: 'stylesheet', href: 'https://cdn.jsdelivr.net/npm/docsearch.js@2/dist/cdn/docsearch.min.css' } 63 | ], 64 | script: [ 65 | { src: 'https://cdn.jsdelivr.net/npm/docsearch.js@2/dist/cdn/docsearch.min.js' }, 66 | { async: true, defer: true, src: 'https://buttons.github.io/buttons.js' }, 67 | { async: true, src: 'https://platform.twitter.com/widgets.js', charset: 'utf-8' } 68 | ] 69 | }, 70 | generate: { 71 | routes: getDynamicPaths({ 72 | '/quick-start': 'docs/quick-start/*.md', 73 | '/guides': 'docs/guides/*.md' 74 | }) 75 | }, 76 | /* 77 | ** Customize the progress-bar color 78 | */ 79 | loading: { color: '#fff' }, 80 | /* 81 | ** Global CSS 82 | */ 83 | css: [ 84 | '@/assets/sass/main.scss' 85 | ], 86 | /* 87 | ** Plugins to load before mounting the App 88 | */ 89 | plugins: [ 90 | { src: '~plugins/matomo.js', mode: 'client' } 91 | ], 92 | /* 93 | ** Nuxt.js dev-modules 94 | */ 95 | buildModules: [ 96 | // Doc: https://github.com/nuxt-community/eslint-module 97 | '@nuxtjs/eslint-module' 98 | ], 99 | /* 100 | ** Nuxt.js modules 101 | */ 102 | modules: [ 103 | ], 104 | /* 105 | ** Build configuration 106 | */ 107 | build: { 108 | postcss: { 109 | preset: { 110 | features: { 111 | customProperties: false 112 | } 113 | } 114 | }, 115 | /* 116 | ** You can extend webpack config here 117 | */ 118 | extend (config, ctx) { 119 | config.module.rules.push({ 120 | test: /\.md$/, 121 | include: path.resolve(__dirname, 'content'), 122 | loader: 'frontmatter-markdown-loader', 123 | options: { 124 | mode: [Mode.VUE_COMPONENT, Mode.META], 125 | markdownIt: markdownIt({ 126 | html: true 127 | }).use(markdownItPrism).use(plugin) 128 | } 129 | }); 130 | } 131 | } 132 | }; 133 | --------------------------------------------------------------------------------