diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 836c9e6..dd03bc5 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -70,3 +70,6 @@ jobs: with: files: _build/test/covertool/elli.covertool.xml env_vars: OTP_VERSION + + - name: Generate doc + run: rebar3 ex_doc diff --git a/.gitignore b/.gitignore index af9d429..b360cc7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,6 @@ .rebar3 -doc/*.html -!doc/tpl.html _build .dialyzer.plt .rebar rebar3.crashdump +doc diff --git a/CHANGELOG.md b/CHANGELOG.md index 63f004f..bdef897 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,189 +2,187 @@ ## pre-v4.0.0 - * Headers are now properly treated as case-insensitive - * Original headers that have not had `string:casefold/1` run on each header - name are still available in the request through `elli_request:original_headers` +* Headers are now properly treated as case-insensitive +* Original headers that have not had `string:casefold/1` run on each header +name are still available in the request through `elli_request:original_headers` ## v3.3.0 - * Do not use x-forwarded-for for peer #75 - * Handle arguments with no value in (post|get)_arg_decoded #82 - * Fix compile-time warnings on missing record info. from aleppo #81 +* Do not use x-forwarded-for for peer #75 +* Handle arguments with no value in (post|get)_arg_decoded #82 +* Fix compile-time warnings on missing record info. from aleppo #81 ## v3.2.0 - * Quell warnings on OTP-21: https://github.com/elli-lib/elli/pull/61 +* Quell warnings on OTP-21: - * Generate HTML docs: https://github.com/elli-lib/elli/pull/58 +* Generate HTML docs: - * Add OTP-21 to Travis build matrix: https://github.com/elli-lib/elli/pull/62 +* Add OTP-21 to Travis build matrix: - * Remove unnecessary `stacktrace_compat` dependency: https://github.com/elli-lib/elli/pull/63 +* Remove unnecessary `stacktrace_compat` dependency: - * Export `elli_request/uri_decode/1` and improve performance: https://github.com/elli-lib/elli/pull/67 +* Export `elli_request:uri_decode/1` and improve performance: - * Update Travis config: https://github.com/elli-lib/elli/pull/69 +* Update Travis config: - * Drop support for OTP-16: https://github.com/elli-lib/elli/pull/71 +* Drop support for OTP-16: - * Prefer `OTP_RELEASE` over `rebar_erl_vsn` plugin: https://github.com/elli-lib/elli/pull/73 +* Prefer `OTP_RELEASE` over `rebar_erl_vsn` plugin: - * Remove old `maintainers` metadata: https://github.com/elli-lib/elli/pull/74 +* Remove old `maintainers` metadata: ## v3.1.0 - * Update docs: https://github.com/elli-lib/elli/pull/57 +* Update docs: - * Logging and stacktrace OTP-21 support: https://github.com/elli-lib/elli/pull/55 +* Logging and stacktrace OTP-21 support: - * Include req_body size in sizes list: https://github.com/elli-lib/elli/pull/52 +* Include req_body size in sizes list: - * Update CHANGELOG.md for 3.0.0: https://github.com/elli-lib/elli/pull/49 +* Update CHANGELOG.md for 3.0.0: ## v3.0.0 - * `scheme`, `host`, and `port` added to the `#req{}` record. Corresponding - helper functions added to the `elli_request` module. +* `scheme`, `host`, and `port` added to the `#req{}` record. Corresponding + helper functions added to the `elli_request` module. ## v2.1.2 - * Update dependencies and re-enable linting +* Update dependencies and re-enable linting - * Increase test coverage +* Increase test coverage - * Declare optional callbacks to elli_handler +* Declare optional callbacks to elli_handler - * Add TLS sendfile implementation by James Fish (from Andrew Thompson) +* Add TLS sendfile implementation by James Fish (from Andrew Thompson) - * Use hackney instead of httpc in tests, due to httpc bug +* Use hackney instead of httpc in tests, due to httpc bug ## v2.0.2 - * Adapt [knutin/elli#108](https://github.com/knutin/elli/pull/108) by Michael Zazaian +* Adapt [knutin/elli#108](https://github.com/knutin/elli/pull/108) by Michael Zazaian - * Incomplete request regression fix by Evan Vigil-McClanahan +* Incomplete request regression fix by Evan Vigil-McClanahan - * Handle binary URIs on OTP >=20 +* Handle binary URIs on OTP >=20 - * Bespoke uri_decode/1 to obviate inets dependency by Christoffer Vikström +* Bespoke uri_decode/1 to obviate inets dependency by Christoffer Vikström ## v2.0.1 - * Miscellaneous tooling, test, and type spec tweaks +* Miscellaneous tooling, test, and type spec tweaks - * Helper functions to reduce redundancy +* Helper functions to reduce redundancy - * Request start timing fix +* Request start timing fix ## v2.0.0 - * Code and documentation cleanup +* Code and documentation cleanup - * Instrumentation facilities +* Instrumentation facilities - * RFC 2616 section 8.2.3 implementation by Martin Karlsson +* RFC 2616 section 8.2.3 implementation by Martin Karlsson - * Send 500 and close connection if file operations fail +* Send 500 and close connection if file operations fail ## v1.0.5 - * Optimization of SSL accept by Tristan Sloughter +* Optimization of SSL accept by Tristan Sloughter - * Dependency cleanup by Adam Lindberg +* Dependency cleanup by Adam Lindberg ## v1.0.4 - * OTP 18.0 compatibility, contributed by Florian Odronitz. +* OTP 18.0 compatibility, contributed by Florian Odronitz. ## v1.0.3 - * Various internal cleanup fixes from Andreas Stenius (github.com/kaos) +* Various internal cleanup fixes from Andreas Stenius (github.com/kaos) ## v1.0.2 - * Added `elli_request:get_args_decoded/1` which returns the list of - query args decoded each time it's called. - +* Added `elli_request:get_args_decoded/1` which returns the list of + query args decoded each time it's called. ## v1.0.1 - * Fix bug in SSL acceptor pool where due to failed handshakes, Elli - runs out of acceptors. Thanks to Stefan Grundmann. +* Fix bug in SSL acceptor pool where due to failed handshakes, Elli + runs out of acceptors. Thanks to Stefan Grundmann. - * In case a handler (or middleware) returns a response Elli does not - understand, Elli will now respond with a 500 error. Thanks to - Johannes Huning. +* In case a handler (or middleware) returns a response Elli does not + understand, Elli will now respond with a 500 error. Thanks to + Johannes Huning. - * Added `elli_request:get_arg_decoded/2,3` which HTTP URI decodes the - value passed in the request. Thanks to Mariano Valles. +* Added `elli_request:get_arg_decoded/2,3` which HTTP URI decodes the + value passed in the request. Thanks to Mariano Valles. ## v1.0 - * SSL using built-in ssl from Erlang/OTP. Thanks to Maas-Maarten Zeeman. +* SSL using built-in ssl from Erlang/OTP. Thanks to Maas-Maarten Zeeman. - * "Handover" a socket to user code, making it possible to implement - WebSockets(https://github.com/mmzeeman/elli_websocket +* "Handover" a socket to user code, making it possible to implement + WebSockets(). - * Type fixes from Ingo Struck and Andreas Hasselberg. +* Type fixes from Ingo Struck and Andreas Hasselberg. ## v0.4.1 - * Fix from Christian Lundgren for browsers that include spaces in the - value of the Content-Length header. +* Fix from Christian Lundgren for browsers that include spaces in the + value of the Content-Length header. ## v0.4 - * Added support for sending ranges of a file with sendfile by - returning `{Code, Headers, {file, Filename, {Offset, Length}}}`. If - no offset and length is specified, the entire file is sent. The - user must provide an appropriate "Content-Length" and - "Content-Range" header, see the example in - `elli_example_callback.erl` - (https://github.com/knutin/elli/blob/master/src/elli_example_callback.erl#L99). Thanks - Vincent Siliakus (zambal). - +* Added support for sending ranges of a file with sendfile by + returning `{Code, Headers, {file, Filename, {Offset, Length}}}`. If + no offset and length is specified, the entire file is sent. The + user must provide an appropriate "Content-Length" and + "Content-Range" header, see the example in + `elli_example_callback.erl` + (). + Thanks Vincent Siliakus (zambal). ## v0.3 - * Breaking change: Timeouts used in the HTTP protocol are now - configurable. To implement this, changing a record and some - callbacks was necessary. To upgrade, a restart of Elli is needed. +* Breaking change: Timeouts used in the HTTP protocol are now + configurable. To implement this, changing a record and some + callbacks was necessary. To upgrade, a restart of Elli is needed. - * Elli now supports pipelining of any type of request. Some proxies - or special clients (like ibrowse) will pipeline requests to reduce - latency. +* Elli now supports pipelining of any type of request. Some proxies + or special clients (like ibrowse) will pipeline requests to reduce + latency. - * If there are no more file descriptors, Elli will shut down. This - mimics the behaviour found in Yaws. +* If there are no more file descriptors, Elli will shut down. This + mimics the behaviour found in Yaws. - * Chunked transfer responses will now exit the Elli process when the - client closes the connection. Sending a synchronous chunk will - return `{error, closed}` if client has closed the connection and - the `chunk_complete` event is sent to your callback including which - end closed the connection. +* Chunked transfer responses will now exit the Elli process when the + client closes the connection. Sending a synchronous chunk will + return `{error, closed}` if client has closed the connection and + the `chunk_complete` event is sent to your callback including which + end closed the connection. ## v0.2.0 - * Breaking change: moved elli_access_log into a separate repository - at github.com/wooga/elli_access_log. Thanks martinrehfeld. +* Breaking change: moved elli_access_log into a separate repository + at github.com/wooga/elli_access_log. Thanks martinrehfeld. ## v0.1.3 - * Added elli_test which makes it easy to write unit tests for your - callbacks. Thanks anha0825. +* Added elli_test which makes it easy to write unit tests for your + callbacks. Thanks anha0825. - * Added sendfile support. Thanks chrisavl. +* Added sendfile support. Thanks chrisavl. ## v0.1.2 - * Added option to specify listen IP address. Thanks hukl. +* Added option to specify listen IP address. Thanks hukl. ## v0.1.1 - * Don't look up the peer ip address on every request anymore, do it - on demand using elli_request:peer/1. +* Don't look up the peer ip address on every request anymore, do it + on demand using elli_request:peer/1. ## v0.1 - * Initial release. +* Initial release. diff --git a/doc/README.md b/OVERVIEW.md similarity index 82% rename from doc/README.md rename to OVERVIEW.md index f206730..99a88d2 100644 --- a/doc/README.md +++ b/OVERVIEW.md @@ -1,10 +1,8 @@ - - -# elli # +# Elli - Overview Copyright (c) 2012-2016 Knut Nesheim, 2016-2018 elli-lib team -__Version:__ 3.0.0 +__Version:__ 3.3.0 __Authors:__ Knut Nesheim, elli-lib team. @@ -18,7 +16,7 @@ Here's the features Elli _does_ have: complete request and returns a complete response. There's no messaging, no receiving data directly from the socket, no writing responses directly to the socket. It's a very simple and - straightforward API. Have a look at [`elli_example_callback`](elli_example_callback.md) + straightforward API. Have a look at [`elli_example_callback`](elli_example_callback.html) for examples. * Middlewares allow you to add useful features like compression, @@ -62,33 +60,37 @@ interfaces, etc. For high volume, you should probably go with nginx, stunnel or ELB if you're on AWS. * Implement your own connection handling, for WebSockets, streaming - uploads, etc. See [`elli_example_callback_handover`](elli_example_callback_handover.md). + uploads, etc. See [`elli_example_callback_handover`](elli_example_callback_handover.html). ## Extensions +Here's some ready-to-use extensions for Elli. + * [elli_access_log](https://github.com/elli-lib/elli_access_log): -Access log +Access log * [elli_basicauth](https://github.com/elli-lib/elli_basicauth): -Basic auth +Basic auth * [elli_chatterbox](https://github.com/elli-lib/elli_chatterbox): -HTTP/2 support +HTTP/2 support * [elli_cloudfront](https://github.com/elli-lib/elli_cloudfront): -CloudFront signed URLs +CloudFront signed URLs * [elli_cookie](https://github.com/elli-lib/elli_cookie): -Cookies +Cookies * [elli_date](https://github.com/elli-lib/elli_date): -"Date" header +"Date" header * [elli_fileserve](https://github.com/elli-lib/elli_fileserve): -Static content +Static content * [elli_prometheus](https://github.com/elli-lib/elli_prometheus): -Prometheus +Prometheus * [elli_stats](https://github.com/elli-lib/elli_stats): -Real-time statistics dashboard +Real-time statistics dashboard * [elli_websockets](https://github.com/elli-lib/elli_websocket): -WebSockets +WebSockets * [elli_xpblfe](https://github.com/elli-lib/elli_xpblfe): X-Powered-By LFE +You can also find a more complete list at . + ## About From operating and debugging high-volume, low-latency apps we have @@ -102,7 +104,7 @@ With this in mind we looked at the big names in the Erlang community: [Yaws][], [Mochiweb][], [Misultin][] and [Cowboy][]. We found [Mochiweb][] to be the best match. However, we also wanted to see if we could take the architecture of [Mochiweb][] and improve on -it. `elli` takes the acceptor-turns-into-request-handler idea found +it. Elli takes the acceptor-turns-into-request-handler idea found in [Mochiweb][], the binaries-only idea from [Cowboy][] and the request-response idea from [WSGI][]/[Rack][] (with chunked transfer being an exception). @@ -116,7 +118,7 @@ dashboard and chaining multiple request handlers. There are a few very mature and robust projects with steady development, one recently ceased development and one new kid on the -block with lots of interest. As `elli` is not a general purpose +block with lots of interest. As Elli is not a general purpose webserver, but more of a specialized tool, we believe it has a very different target audience and would not attract effort or users away from the big names. @@ -163,22 +165,3 @@ about benchmarking HTTP servers. [Cowboy]: https://github.com/ninenines/cowboy [WSGI]: https://www.python.org/dev/peps/pep-3333/ [Rack]: https://github.com/rack/rack - - -## Modules ## - - - - - - - - - - - - - - -
elli
elli_example_callback
elli_example_callback_handover
elli_handler
elli_http
elli_middleware
elli_middleware_compress
elli_request
elli_sendfile
elli_tcp
elli_test
elli_util
- diff --git a/README.md b/README.md index 32ffe90..ea41d15 100644 --- a/README.md +++ b/README.md @@ -1,59 +1,45 @@ -# elli - Erlang web server for HTTP APIs +# Elli - Erlang web server for HTTP APIs -[![Hex.pm][hex badge]][hex package] -[![Documentation][doc badge]][docs] -[![Erlang][erlang badge]][erlang downloads] +[![Hex.pm](https://img.shields.io/hexpm/v/elli.svg)](https://hex.pm/packages/elli) +[![Documentation](https://img.shields.io/badge/docs-edown-green.svg)](https://hexdocs.pm/elli/) +[![Erlang](https://img.shields.io/badge/erlang-%E2%89%A520.0-red.svg)](http://www.erlang.org/downloads) ![Common Test](https://github.com/elli-lib/elli/workflows/Common%20Test/badge.svg) -[![Coverage Status][coveralls badge]][coveralls link] -[![MIT License][license badge]](LICENSE) - -[hex badge]: https://img.shields.io/hexpm/v/elli.svg -[hex package]: https://hex.pm/packages/elli -[latest release]: https://github.com/elli-lib/elli/releases/latest -[erlang badge]: https://img.shields.io/badge/erlang-%E2%89%A520.0-red.svg -[erlang downloads]: http://www.erlang.org/downloads -[doc badge]: https://img.shields.io/badge/docs-edown-green.svg -[docs]: doc/README.md -[coveralls badge]: https://coveralls.io/repos/github/elli-lib/elli/badge.svg?branch=develop -[coveralls link]: https://coveralls.io/github/elli-lib/elli?branch=develop -[license badge]: https://img.shields.io/badge/license-MIT-blue.svg +[![Coverage Status](https://coveralls.io/repos/github/elli-lib/elli/badge.svg?branch=develop)](https://coveralls.io/github/elli-lib/elli?branch=develop) +[![MIT License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) Elli is a webserver you can run inside your Erlang application to -expose an HTTP API. Elli is aimed exclusively at building +expose an HTTP API. It is aimed exclusively at building high-throughput, low-latency HTTP APIs. If robustness and performance -is more important than general purpose features, then `elli` might be +is more important to you than general purpose features, then Elli might be for you. If you find yourself digging into the implementation of a -webserver, `elli` might be for you. If you're building web services, -not web sites, then `elli` might be for you. - -Elli is used in production at Wooga and Game Analytics. Elli requires -OTP 18.0 or newer. +webserver, then Elli might be for you. If you're building web services, +not web sites, then Elli might be for you. +Elli requires OTP 22.0 or newer. ## Installation -To use `elli` you will need a working installation of Erlang 18.0 (or later). - -Add `elli` to your application by adding it as a dependency to your -[`rebar.config`](http://www.rebar3.org/docs/configuration): +Add `elli` to your application as a dependency to your +[`rebar.config`](https://www.rebar3.org/docs/configuration): ```erlang {deps, [ - %% ... - {elli, "3.0.0"} + {elli, "3.3.0"} ]}. ``` -Afterwards you can run: +Afterwards, to compile it, you can run: -```sh -$ rebar3 compile +```console +rebar3 compile ``` - ## Usage -```sh -$ rebar3 shell + +To boot Elli inside an Erlang shell, run: + +```console +rebar3 shell ``` ```erlang @@ -66,71 +52,80 @@ $ rebar3 shell ### Callback Module The best source to learn how to write a callback module -is [src/elli_example_callback.erl](src/elli_example_callback.erl) and -its [generated documentation](doc/elli_example_callback.md). There are a bunch +is [`elli_example_callback`](elli_example_callback.html). +There are also a bunch of examples used in the tests as well as descriptions of all the events. -A minimal callback module could look like this: +A minimal callback module looks something like this: ```erlang -module(elli_minimal_callback). --export([handle/2, handle_event/3]). +-behaviour(elli_handler). -include_lib("elli/include/elli.hrl"). --behaviour(elli_handler). + +-export([handle/2, handle_event/3]). handle(Req, _Args) -> %% Delegate to our handler function - handle(Req#req.method, elli_request:path(Req), Req). + Method = Req#req.method, + Path = elli_request:path(Req), + handle(Method, Path, Req). -handle('GET',[<<"hello">>, <<"world">>], _Req) -> +handle('GET' = _Method, [<<"hello">>, <<"world">>] = _Path, _Req) -> %% Reply with a normal response. `ok' can be used instead of `200' %% to signal success. - {ok, [], <<"Hello World!">>}; + StatusCode = ok, + Headers = [], + Body = <<"Hello World!">>, + {StatusCode, Headers, Body}; -handle(_, _, _Req) -> +handle(_Method, _Path, _Req) -> {404, [], <<"Not Found">>}. -%% @doc Handle request events, like request completed, exception +%% @doc Handle request events: request completed, exception %% thrown, client timeout, etc. Must return `ok'. handle_event(_Event, _Data, _Args) -> ok. ``` - -### Supervisor Childspec +### Supervisor ChildSpec To add `elli` to a supervisor you can use the following example and adapt it to your needs. ```erlang --module(fancyapi_sup). +-module(elli_minimal_sup). -behaviour(supervisor). --export([start_link/0]). --export([init/1]). -start_link() -> - supervisor:start_link({local, ?MODULE}, ?MODULE, []). +-export([start_link/0, init/1]). -init([]) -> - ElliOpts = [{callback, fancyapi_callback}, {port, 3000}], +start_link() -> + SupName = {local, ?MODULE}, + Module = ?MODULE, + Args = [], + supervisor:start_link(SupName, Module, Args). + +init([] = _Args) -> + ElliOpts = [ + {callback, elli_minimal_callback}, + {port, 3000} + ], ElliSpec = { - fancy_http, - {elli, start_link, [ElliOpts]}, - permanent, - 5000, - worker, - [elli]}, - - {ok, { {one_for_one, 5, 10}, [ElliSpec]} }. + _Id = elli_minimal_http, + _Start = {elli, start_link, [ElliOpts]}, + _Restart = permanent, + _Shutdown = 5000, + _Worker = worker, + _Modules = [elli]}, + + {ok, {{_Strategy = one_for_one, _Intensity = 5, _Period = 10}, [ElliSpec]} }. ``` +## Further reading -## Further Reading - -For more information about the features and design philosophy of `elli` check -out the [overview](doc/README.md). - +For more information about the features and design philosophy of Elli check +out the [`overview`](overview.html). ## License diff --git a/doc/Makefile b/doc/Makefile deleted file mode 100644 index 5d72b03..0000000 --- a/doc/Makefile +++ /dev/null @@ -1,21 +0,0 @@ -SOURCE_DOCS := $(wildcard *.md) - -EXPORTED_DOCS=\ - $(SOURCE_DOCS:.md=.html) - -RM=/bin/rm - -PANDOC=pandoc - -PANDOC_HTML_OPTIONS=--standalone --highlight-style=tango --template tpl.html -f gfm --to html5 - -%.html : %.md - $(PANDOC) $(PANDOC_HTML_OPTIONS) -o $@ $< - - -.PHONY: all clean - -all : $(EXPORTED_DOCS) - -clean: - - $(RM) -f $(EXPORTED_DOCS) diff --git a/doc/build.sh b/doc/build.sh deleted file mode 100755 index 188a5ee..0000000 --- a/doc/build.sh +++ /dev/null @@ -1,21 +0,0 @@ -#!/bin/bash - -cd $(dirname $(realpath $0)) - -make clean -make - -# fix internal doc links -sed -i 's/https:\/\/github\.com\/doc\/\([a-zA-Z_-]*\)\.md/\1.html/g' *.html -sed -i 's/\"\([a-zA-Z_-]*\)\.md\([a-zA-Z_-#]*\)\"/\"\1.html\2\"/g' *.html -sed -i 's/\"doc\/\([a-zA-Z_-]*\)\.md\"/\"\1.html\"/g' *.html -sed -i 's/\"\([a-zA-Z_-]*\)\.md\"/\"\1.html\"/g' *.html - -# fix external doc links -sed -i 's/maps\.html\#/http:\/\/erlang.org\/doc\/man\/maps\.html#/g' *.html -sed -i 's/unicode\.html\#/http:\/\/erlang.org\/doc\/man\/unicode\.html#/g' *.html -sed -i 's/maps\.md\#/http:\/\/erlang.org\/doc\/man\/maps\.html#/g' *.html -sed -i 's/unicode\.md\#/http:\/\/erlang.org\/doc\/man\/unicode\.html#/g' *.html - -# cleans up the indentation of code blocks -sed -i 's/ - -## Description ## -This gen_server owns the listen socket and manages the processes -accepting on that socket. When a process waiting for accept gets a -request, it notifies this gen_server so we can start up another -acceptor. - - - -## Data Types ## - - - - -### body() ### - - -__abstract datatype__: `body()` - -A binary or iolist. - - - -### header() ### - - -

-header() = {Key::binary(), Value::binary() | string()}
-
- - - - -### headers() ### - - -

-headers() = [header()]
-
- - - - -### http_method() ### - - -__abstract datatype__: `http_method()` - -An uppercase atom representing a known HTTP verb or a -binary for other verbs. - - - -### req() ### - - -__abstract datatype__: `req()` - -A record representing an HTTP request. - - - -### response_code() ### - - -

-response_code() = 100..999
-
- - - - -### state() ### - - -__abstract datatype__: `state()` - -Internal state. - - - -## Function Index ## - - -
get_acceptors/1
get_open_reqs/1Equivalent to get_open_reqs(S, 5000).
get_open_reqs/2
set_callback/3
start_link/0Create an Elli server process as part of a supervision tree, using the -default configuration.
start_link/1
stop/1Stop Server.
- - - - -## Function Details ## - - - -### get_acceptors/1 ### - -

-get_acceptors(S::atom()) -> {reply, {ok, [ets:tid()]}, state()}
-
-
- - - -### get_open_reqs/1 ### - -

-get_open_reqs(S::atom()) -> {reply, {ok, non_neg_integer()}, state()}
-
-
- -Equivalent to [`get_open_reqs(S, 5000)`](#get_open_reqs-2). - - - -### get_open_reqs/2 ### - -

-get_open_reqs(S::atom(), Timeout::non_neg_integer()) -> Reply
-
- -
  • Reply = {reply, {ok, non_neg_integer()}, state()}
- - - -### set_callback/3 ### - -

-set_callback(S, Callback, CallbackArgs) -> Reply
-
- - - - - -### start_link/0 ### - -

-start_link() -> Result
-
- -
  • Result = {ok, Pid} | ignore | {error, Error}
  • Pid = pid()
  • Error = {already_started, Pid} | term()
- -Equivalent to [`start_link([{callback, elli_example_callback},{callback_args, []}])`](#start_link-1). - -Create an Elli server process as part of a supervision tree, using the -default configuration. - - - -### start_link/1 ### - -

-start_link(Opts) -> Result
-
- -
  • Opts = [{term(), term()}]
  • Result = {ok, Pid} | ignore | {error, Error}
  • Pid = pid()
  • Error = {already_started, Pid} | term()
- - - -### stop/1 ### - -

-stop(Server::atom()) -> {stop, normal, ok, state()}
-
-
- -Stop `Server`. - diff --git a/doc/elli_example_callback.md b/doc/elli_example_callback.md deleted file mode 100644 index ea4e6f4..0000000 --- a/doc/elli_example_callback.md +++ /dev/null @@ -1,125 +0,0 @@ - - -# Module elli_example_callback # -* [Description](#description) -* [Function Index](#index) -* [Function Details](#functions) - -Elli example callback. - -__Behaviours:__ [`elli_handler`](elli_handler.md). - - - -## Description ## -Your callback needs to implement two functions, [`handle/2`](#handle-2) and -[`handle_event/3`](#handle_event-3). For every request, Elli will call your handle -function with the request. When an event happens, like Elli -completed a request, there was a parsing error or your handler -threw an error, [`handle_event/3`](#handle_event-3) is called. - -## Function Index ## - - -
chunk_loop/1Send 10 separate chunks to the client.
handle/2Handle a Request.
handle_event/3Handle Elli events, fired throughout processing a request.
- - - - -## Function Details ## - - - -### chunk_loop/1 ### - -`chunk_loop(Ref) -> any()` - -Equivalent to [`chunk_loop(Ref, 10)`](#chunk_loop-2). - -Send 10 separate chunks to the client. - - - -### handle/2 ### - -

-handle(Req, _Args) -> Result
-
- - - -Handle a `Req`uest. -Delegate to our handler function. - -__See also:__ [handle/3](#handle-3). - - - -### handle_event/3 ### - -

-handle_event(Event, Args, Config) -> ok
-
- - - -Handle Elli events, fired throughout processing a request. - -`elli_startup` is sent when Elli is starting up. If you are -implementing a middleware, you can use it to spawn processes, -create ETS tables or start supervised processes in a supervisor -tree. - -`request_complete` fires *after* Elli has sent the response to the -client. `Timings` contains timestamps (native units) of events like when the -connection was accepted, when headers/body parsing finished, when the -user callback returns, response sent, etc. `Sizes` contains response sizes -like response headers size, response body or file size. -This allows you to collect performance statistics for monitoring your app. - -`request_throw`, `request_error` and `request_exit` events are sent if -the user callback code throws an exception, has an error or -exits. After triggering this event, a generated response is sent to -the user. - -`invalid_return` is sent if the user callback code returns a term not -understood by elli, see [`elli_http:execute_callback/1`](elli_http.md#execute_callback-1). -After triggering this event, a generated response is sent to the user. - -`chunk_complete` fires when a chunked response is completely -sent. It's identical to the `request_complete` event, except instead -of the response body you get the atom `client` or `server` -depending on who closed the connection. `Sizes` will have the key `chunks`, -which is the total size of all chunks plus encoding overhead. - -`request_closed` is sent if the client closes the connection when -Elli is waiting for the next request on a keep alive connection. - -`request_timeout` is sent if the client times out when -Elli is waiting for the request. - -`request_parse_error` fires if the request is invalid and cannot be parsed by -[`erlang:decode_packet/3`][decode_packet/3] or it contains a path Elli cannot -parse or does not support. - -[decode_packet/3]: http://erlang.org/doc/man/erlang.html#decode_packet-3 - -`client_closed` can be sent from multiple parts of the request -handling. It's sent when the client closes the connection or if for -any reason the socket is closed unexpectedly. The `Where` atom -tells you in which part of the request processing the closed socket -was detected: `receiving_headers`, `receiving_body` or `before_response`. - -`client_timeout` can as with `client_closed` be sent from multiple -parts of the request handling. If Elli tries to receive data from -the client socket and does not receive anything within a timeout, -this event fires and the socket is closed. - -`bad_request` is sent when Elli detects a request is not well -formatted or does not conform to the configured limits. Currently -the `Reason` variable can be `{too_many_headers, Headers}` -or `{body_size, ContentLength}`. - -`file_error` is sent when the user wants to return a file as a -response, but for some reason it cannot be opened. - diff --git a/doc/elli_example_callback_handover.md b/doc/elli_example_callback_handover.md deleted file mode 100644 index 9c6bd8c..0000000 --- a/doc/elli_example_callback_handover.md +++ /dev/null @@ -1,40 +0,0 @@ - - -# Module elli_example_callback_handover # -* [Function Index](#index) -* [Function Details](#functions) - -__Behaviours:__ [`elli_handler`](elli_handler.md). - - - -## Function Index ## - - -
handle/2
init/2Return {ok, handover} if Req's path is /hello/world, -otherwise ignore.
- - - - -## Function Details ## - - - -### handle/2 ### - -

-handle(Req, Args) -> Result
-
- - - - - -### init/2 ### - -`init(Req, Args) -> any()` - -Return `{ok, handover}` if `Req`'s path is `/hello/world`, -otherwise `ignore`. - diff --git a/doc/elli_example_middleware.md b/doc/elli_example_middleware.md deleted file mode 100644 index 1ee9405..0000000 --- a/doc/elli_example_middleware.md +++ /dev/null @@ -1,38 +0,0 @@ - - -# Module elli_example_middleware # -* [Function Index](#index) -* [Function Details](#functions) - -__Behaviours:__ [`elli_handler`](elli_handler.md). - - - -## Function Index ## - - -
do_handle/1*
handle/2
handle_event/3
- - - - -## Function Details ## - - - -### do_handle/1 * ### - -`do_handle(X1) -> any()` - - - -### handle/2 ### - -`handle(Req, Args) -> any()` - - - -### handle_event/3 ### - -`handle_event(Event, Data, Args) -> any()` - diff --git a/doc/elli_handler.md b/doc/elli_handler.md deleted file mode 100644 index 5ba6bc5..0000000 --- a/doc/elli_handler.md +++ /dev/null @@ -1,58 +0,0 @@ - - -# Module elli_handler # -* [Data Types](#types) - -__This module defines the `elli_handler` behaviour.__
Required callback functions: `handle/2`, `handle_event/3`. - - - -## Data Types ## - - - - -### callback() ### - - -__abstract datatype__: `callback()` - -A tuple of a callback_mod() and callback_args(). - - - -### callback_args() ### - - -__abstract datatype__: `callback_args()` - -Arguments to pass to a callback_mod(). - - - -### callback_mod() ### - - -__abstract datatype__: `callback_mod()` - -A callback module. - - - -### event() ### - - -__abstract datatype__: `event()` - -Fired throughout processing a request. -See [`elli_example_callback:handle_event/3`](elli_example_callback.md#handle_event-3) for descriptions. - - - -### result() ### - - -

-result() = {elli:response_code() | ok, elli:headers(), {file, file:name_all()} | {file, file:name_all(), elli_util:range()}} | {elli:response_code() | ok, elli:headers(), elli:body()} | {elli:response_code() | ok, elli:body()} | {chunk, elli:headers()} | {chunk, elli:headers(), elli:body()} | ignore
-
- diff --git a/doc/elli_http.md b/doc/elli_http.md deleted file mode 100644 index 72f5a78..0000000 --- a/doc/elli_http.md +++ /dev/null @@ -1,143 +0,0 @@ - - -# Module elli_http # -* [Description](#description) -* [Data Types](#types) -* [Function Index](#index) -* [Function Details](#functions) - -Elli HTTP request implementation. - - - -## Description ## -An elli_http process blocks in elli_tcp:accept/2 until a client -connects. It then handles requests on that connection until it's -closed either by the client timing out or explicitly by the user. - - -## Data Types ## - - - - -### version() ### - - -__abstract datatype__: `version()` - -HTTP version as a tuple, i.e. `{0, 9} | {1, 0} | {1, 1}`. - - - -## Function Index ## - - -
accept/4Accept on the socket until a client connects.
chunk_loop/1
handle_request/4Handle a HTTP request that will possibly come on the socket.
keepalive_loop/3Handle multiple requests on the same connection, i.e.
keepalive_loop/5
mk_req/10
mk_req/7
parse_path/1
send_response/4Generate a HTTP response and send it to the client.
split_args/1Split the URL arguments into a proplist.
start_link/4
- - - - -## Function Details ## - - - -### accept/4 ### - -

-accept(Server, ListenSocket, Options, Callback) -> ok
-
- - - -Accept on the socket until a client connects. -Handle the request, then loop if we're using keep alive or chunked transfer. -If [`elli_tcp:accept/3`](elli_tcp.md#accept-3) doesn't return a socket within a configurable -timeout, loop to allow code upgrades of this module. - - - -### chunk_loop/1 ### - -`chunk_loop(Socket) -> any()` - - - -### handle_request/4 ### - -

-handle_request(Socket, PrevBin, Options, Callback) -> ConnToken
-
- - - -Handle a HTTP request that will possibly come on the socket. -Returns the appropriate connection token and any buffer containing (parts of) -the next request. - - - -### keepalive_loop/3 ### - -`keepalive_loop(Socket, Options, Callback) -> any()` - -Handle multiple requests on the same connection, i.e. `"keep alive"`. - - - -### keepalive_loop/5 ### - -`keepalive_loop(Socket, NumRequests, Buffer, Options, Callback) -> any()` - - - -### mk_req/10 ### - -`mk_req(Method, Scheme, Host, Port, PathTuple, Headers, Body, V, Socket, Callback) -> any()` - - - -### mk_req/7 ### - -

-mk_req(Method, PathTuple, Headers, Body, V, Socket, Callback) -> Req
-
- - - - - -### parse_path/1 ### - -`parse_path(X1) -> any()` - - - -### send_response/4 ### - -`send_response(Req, Code, Headers, UserBody) -> any()` - -Generate a HTTP response and send it to the client. - - - -### split_args/1 ### - -

-split_args(Qs::binary()) -> [{binary(), binary() | true}]
-
-
- -Split the URL arguments into a proplist. -Lifted from `cowboy_http:x_www_form_urlencoded/2`. - - - -### start_link/4 ### - -

-start_link(Server, ListenSocket, Options, Callback) -> pid()
-
- - - diff --git a/doc/elli_middleware.md b/doc/elli_middleware.md deleted file mode 100644 index 48e9ffc..0000000 --- a/doc/elli_middleware.md +++ /dev/null @@ -1,43 +0,0 @@ - - -# Module elli_middleware # -* [Description](#description) - -HTTP request processing middleware. - -__Behaviours:__ [`elli_handler`](elli_handler.md). - - - -## Description ## - -This module offers both pre-processing of requests and post-processing of -responses. It can also be used to allow multiple handlers, where the first -handler to return a response short-circuits the request. -It is implemented as a plain elli handler. - -Usage: - -``` - Config = [ - {mods, [ - {elli_example_middleware, []}, - {elli_middleware_compress, []}, - {elli_example_callback, []} - ]} - ], - elli:start_link([ - %% ..., - {callback, elli_middleware}, - {callback_args, Config} - ]). -``` - -The configured modules may implement the elli behaviour, in which case all -the callbacks will be used as normal. If [`handle/2`](#handle-2) returns `ignore`, -elli will continue on to the next callback in the list. - -Pre-processing and post-processing is implemented in [`preprocess/2`](#preprocess-2) -and [`postprocess/3`](#postprocess-3). [`preprocess/2`](#preprocess-2) is called for each -middleware in the order specified, while [`postprocess/3`](#postprocess-3) is called in -the reverse order. diff --git a/doc/elli_middleware_compress.md b/doc/elli_middleware_compress.md deleted file mode 100644 index 1937765..0000000 --- a/doc/elli_middleware_compress.md +++ /dev/null @@ -1,35 +0,0 @@ - - -# Module elli_middleware_compress # -* [Description](#description) -* [Function Index](#index) -* [Function Details](#functions) - -Response compression as Elli middleware. - - - -## Function Index ## - - -
postprocess/3Postprocess all requests and compress bodies larger than -compress_byte_size (1024 by default).
- - - - -## Function Details ## - - - -### postprocess/3 ### - -

-postprocess(Req, Result, Config) -> Result
-
- - - -Postprocess all requests and compress bodies larger than -`compress_byte_size` (`1024` by default). - diff --git a/doc/elli_request.md b/doc/elli_request.md deleted file mode 100644 index 0ecd08d..0000000 --- a/doc/elli_request.md +++ /dev/null @@ -1,295 +0,0 @@ - - -# Module elli_request # -* [Data Types](#types) -* [Function Index](#index) -* [Function Details](#functions) - - - -## Data Types ## - - - - -### http_range() ### - - -

-http_range() = {First::non_neg_integer(), Last::non_neg_integer()} | {offset, Offset::non_neg_integer()} | {suffix, Length::pos_integer()}
-
- - - -## Function Index ## - - -
async_send_chunk/2Send a chunk asynchronously.
body/1Return the body.
body_qs/1Parse application/x-www-form-urlencoded body into a proplist.
chunk_ref/1Return a reference that can be used to send chunks to the client.
close_chunk/1Explicitly close the chunked connection.
get_arg/2Equivalent to get_arg(Key, Req, undefined).
get_arg/3Equivalent to proplists:get_value(Key, Args, Default).
get_arg_decoded/2Equivalent to get_arg_decoded(Key, Req, undefined).
get_arg_decoded/3
get_args/1Return a proplist of keys and values of the original query string.
get_args_decoded/1
get_header/2Equivalent to proplists:get_value(Key, Headers).
get_header/3Equivalent to proplists:get_value(Key, Headers, Default).
get_range/1Parse the Range header from the request.
headers/1Return the headers.
host/1Return the host.
is_request/1
method/1Return the method.
path/1Return path split into binary parts.
peer/1
port/1Return the port.
post_arg/2Equivalent to post_arg(Key, Req, undefined).
post_arg/3
post_arg_decoded/2Equivalent to post_arg_decoded(Key, Req, undefined).
post_arg_decoded/3
post_args/1
post_args_decoded/1
query_str/1Calculate the query string associated with a given Request -as a binary.
raw_path/1Return the raw_path, i.e.
scheme/1Return the scheme.
send_chunk/2Send a chunk synchronously.
to_proplist/1Serialize the Request record to a proplist.
- - - - -## Function Details ## - - - -### async_send_chunk/2 ### - -`async_send_chunk(Ref, Data) -> any()` - -Send a chunk asynchronously. - - - -### body/1 ### - -`body(Req) -> any()` - -Return the `body`. - - - -### body_qs/1 ### - -`body_qs(Req) -> any()` - -Parse `application/x-www-form-urlencoded` body into a proplist. - - - -### chunk_ref/1 ### - -`chunk_ref(Req) -> any()` - -Return a reference that can be used to send chunks to the client. -If the protocol does not support it, return `{error, not_supported}`. - - - -### close_chunk/1 ### - -`close_chunk(Ref) -> any()` - -Equivalent to [`send_chunk(Ref, close)`](#send_chunk-2). - -Explicitly close the chunked connection. -Return `{error, closed}` if the client already closed the connection. - - - -### get_arg/2 ### - -`get_arg(Key, Req) -> any()` - -Equivalent to [`get_arg(Key, Req, undefined)`](#get_arg-3). - - - -### get_arg/3 ### - -`get_arg(Key, Req, Default) -> any()` - -Equivalent to [`proplists:get_value(Key, Args, Default)`](proplists.md#get_value-3). - - - -### get_arg_decoded/2 ### - -`get_arg_decoded(Key, Req) -> any()` - -Equivalent to [`get_arg_decoded(Key, Req, undefined)`](#get_arg_decoded-3). - - - -### get_arg_decoded/3 ### - -`get_arg_decoded(Key, Req, Default) -> any()` - - - -### get_args/1 ### - -

-get_args(Req::elli:req()) -> QueryArgs::proplists:proplist()
-
-
- -Return a proplist of keys and values of the original query string. -Both keys and values in the returned proplists will be binaries or the atom -`true` in case no value was supplied for the query value. - - - -### get_args_decoded/1 ### - -`get_args_decoded(Req) -> any()` - - - -### get_header/2 ### - -`get_header(Key, Req) -> any()` - -Equivalent to [`proplists:get_value(Key, Headers)`](proplists.md#get_value-2). - - - -### get_header/3 ### - -`get_header(Key, Req, Default) -> any()` - -Equivalent to [`proplists:get_value(Key, Headers, Default)`](proplists.md#get_value-3). - - - -### get_range/1 ### - -

-get_range(Req::elli:req()) -> [http_range()] | parse_error
-
-
- -Parse the `Range` header from the request. -The result is either a `byte_range_set()` or the atom `parse_error`. -Use [`elli_util:normalize_range/2`](elli_util.md#normalize_range-2) to get a validated, normalized range. - - - -### headers/1 ### - -`headers(Req) -> any()` - -Return the `headers`. - - - -### host/1 ### - -`host(Req) -> any()` - -Return the `host`. - - - -### is_request/1 ### - -`is_request(Req) -> any()` - - - -### method/1 ### - -`method(Req) -> any()` - -Return the `method`. - - - -### path/1 ### - -`path(Req) -> any()` - -Return `path` split into binary parts. - - - -### peer/1 ### - -`peer(Req) -> any()` - - - -### port/1 ### - -`port(Req) -> any()` - -Return the `port`. - - - -### post_arg/2 ### - -`post_arg(Key, Req) -> any()` - -Equivalent to [`post_arg(Key, Req, undefined)`](#post_arg-3). - - - -### post_arg/3 ### - -`post_arg(Key, Req, Default) -> any()` - - - -### post_arg_decoded/2 ### - -`post_arg_decoded(Key, Req) -> any()` - -Equivalent to [`post_arg_decoded(Key, Req, undefined)`](#post_arg_decoded-3). - - - -### post_arg_decoded/3 ### - -`post_arg_decoded(Key, Req, Default) -> any()` - - - -### post_args/1 ### - -`post_args(Req) -> any()` - - - -### post_args_decoded/1 ### - -`post_args_decoded(Req) -> any()` - - - -### query_str/1 ### - -

-query_str(Req::elli:req()) -> QueryStr::binary()
-
-
- -Calculate the query string associated with a given `Request` -as a binary. - - - -### raw_path/1 ### - -`raw_path(Req) -> any()` - -Return the `raw_path`, i.e. not split or parsed for query params. - - - -### scheme/1 ### - -`scheme(Req) -> any()` - -Return the `scheme`. - - - -### send_chunk/2 ### - -`send_chunk(Ref, Data) -> any()` - -Send a chunk synchronously. -If the referenced process is dead, return early with `{error, closed}`, -instead of timing out. - - - -### to_proplist/1 ### - -`to_proplist(Req) -> any()` - -Serialize the `Req`uest record to a proplist. -Useful for logging. - diff --git a/doc/elli_sendfile.md b/doc/elli_sendfile.md deleted file mode 100644 index 2d0125d..0000000 --- a/doc/elli_sendfile.md +++ /dev/null @@ -1,47 +0,0 @@ - - -# Module elli_sendfile # -* [Data Types](#types) -* [Function Index](#index) -* [Function Details](#functions) - - - -## Data Types ## - - - - -### sendfile_opts() ### - - -

-sendfile_opts() = [{chunk_size, non_neg_integer()}]
-
- - - -## Function Index ## - - -
sendfile/5Send part of a file on a socket.
- - - - -## Function Details ## - - - -### sendfile/5 ### - -

-sendfile(RawFile::file:fd(), Socket::elli_tcp:socket(), Offset::non_neg_integer(), Bytes::non_neg_integer(), Opts::sendfile_opts()) -> {ok, non_neg_integer()} | {error, atom()}
-
-
- -Send part of a file on a socket. - -Basically, @see file:sendfile/5 but for ssl (i.e. not raw OS sockets). -Originally from https://github.com/ninenines/ranch/pull/41/files - diff --git a/doc/elli_tcp.md b/doc/elli_tcp.md deleted file mode 100644 index 762002b..0000000 --- a/doc/elli_tcp.md +++ /dev/null @@ -1,88 +0,0 @@ - - -# Module elli_tcp # -* [Description](#description) -* [Data Types](#types) -* [Function Index](#index) -* [Function Details](#functions) - -Wrapper for plain and SSL sockets. - - - -## Description ## -Based on `mochiweb_socket.erl`. - - -## Data Types ## - - - - -### socket() ### - - -

-socket() = {plain, inet:socket()} | {ssl, ssl:sslsocket()}
-
- - - -## Function Index ## - - -
accept/3
close/1
listen/3
peername/1
recv/3
send/2
sendfile/5
setopts/2
- - - - -## Function Details ## - - - -### accept/3 ### - -`accept(X1, Server, Timeout) -> any()` - - - -### close/1 ### - -`close(X1) -> any()` - - - -### listen/3 ### - -`listen(X1, Port, Opts) -> any()` - - - -### peername/1 ### - -`peername(X1) -> any()` - - - -### recv/3 ### - -`recv(X1, Size, Timeout) -> any()` - - - -### send/2 ### - -`send(X1, Data) -> any()` - - - -### sendfile/5 ### - -`sendfile(Fd, X2, Offset, Length, Opts) -> any()` - - - -### setopts/2 ### - -`setopts(X1, Opts) -> any()` - diff --git a/doc/elli_test.md b/doc/elli_test.md deleted file mode 100644 index 12f4df9..0000000 --- a/doc/elli_test.md +++ /dev/null @@ -1,39 +0,0 @@ - - -# Module elli_test # -* [Description](#description) -* [Function Index](#index) -* [Function Details](#functions) - -Helper for calling your Elli callback in unit tests. - -__Authors:__ Andreas Hasselberg ([`andreas.hasselberg@gmail.com`](mailto:andreas.hasselberg@gmail.com)). - - - -## Description ## -Only the callback specified is actually run. Elli's response handling is not -used, so the headers will for example not include a content length and the -return format is not standardized. -The unit tests below test `elli_example_callback`. - -## Function Index ## - - -
call/5
- - - - -## Function Details ## - - - -### call/5 ### - -

-call(Method, Path, Headers, Body, Opts) -> elli_handler:result()
-
- - - diff --git a/doc/elli_util.md b/doc/elli_util.md deleted file mode 100644 index de10432..0000000 --- a/doc/elli_util.md +++ /dev/null @@ -1,73 +0,0 @@ - - -# Module elli_util # -* [Data Types](#types) -* [Function Index](#index) -* [Function Details](#functions) - - - -## Data Types ## - - - - -### range() ### - - -

-range() = {Offset::non_neg_integer(), Length::non_neg_integer()}
-
- - - -## Function Index ## - - -
encode_range/2 Encode Range to a Content-Range value.
file_size/1 Get the size in bytes of the file.
normalize_range/2 If a valid byte-range, or byte-range-set of size 1 -is supplied, returns a normalized range in the format -{Offset, Length}.
- - - - -## Function Details ## - - - -### encode_range/2 ### - -

-encode_range(Range::range() | invalid_range, Size::non_neg_integer()) -> ByteRange::iolist()
-
-
- -Encode Range to a Content-Range value. - - - -### file_size/1 ### - -

-file_size(Filename) -> Size | {error, Reason}
-
- - - -Get the size in bytes of the file. - - - -### normalize_range/2 ### - -

-normalize_range(RangeOrSet, Size) -> Normalized
-
- -
  • RangeOrSet = any()
  • Size = integer()
  • Normalized = range() | undefined | invalid_range
- -If a valid byte-range, or byte-range-set of size 1 -is supplied, returns a normalized range in the format -{Offset, Length}. Returns undefined when an empty byte-range-set -is supplied and the atom `invalid_range` in all other cases. - diff --git a/doc/erlang.png b/doc/erlang.png deleted file mode 100644 index 987a618..0000000 Binary files a/doc/erlang.png and /dev/null differ diff --git a/doc/github-pandoc.css b/doc/github-pandoc.css deleted file mode 100644 index 0607023..0000000 --- a/doc/github-pandoc.css +++ /dev/null @@ -1,424 +0,0 @@ -/*! normalize.css v2.1.3 | MIT License | git.io/normalize */ - -/* ========================================================================== - HTML5 display definitions - ========================================================================== */ - -/** - * Correct `block` display not defined in IE 8/9. - */ - -article, -aside, -details, -figcaption, -figure, -footer, -header, -hgroup, -main, -nav, -section, -summary { - display: block; -} - -/** - * Correct `inline-block` display not defined in IE 8/9. - */ - -audio, -canvas, -video { - display: inline-block; -} - -/** - * Prevent modern browsers from displaying `audio` without controls. - * Remove excess height in iOS 5 devices. - */ - -audio:not([controls]) { - display: none; - height: 0; -} - -/** - * Address `[hidden]` styling not present in IE 8/9. - * Hide the `template` element in IE, Safari, and Firefox < 22. - */ - -[hidden], -template { - display: none; -} - -/* ========================================================================== - Base - ========================================================================== */ - -/** - * 1. Set default font family to sans-serif. - * 2. Prevent iOS text size adjust after orientation change, without disabling - * user zoom. - */ - -html { - font-family: sans-serif; /* 1 */ - -ms-text-size-adjust: 100%; /* 2 */ - -webkit-text-size-adjust: 100%; /* 2 */ -} - -/** - * Remove default margin. - */ - -body { - margin: 0; -} - -/* ========================================================================== - Links - ========================================================================== */ - -/** - * Remove the gray background color from active links in IE 10. - */ - -a { - background: transparent; -} - -/** - * Address `outline` inconsistency between Chrome and other browsers. - */ - -a:focus { - outline: thin dotted; -} - -/** - * Improve readability when focused and also mouse hovered in all browsers. - */ - -a:active, -a:hover { - outline: 0; -} - -/* ========================================================================== - Typography - ========================================================================== */ - -/** - * Address variable `h1` font-size and margin within `section` and `article` - * contexts in Firefox 4+, Safari 5, and Chrome. - */ - -h1 { - font-size: 2em; - margin: 0.67em 0; -} - -/** - * Address styling not present in IE 8/9, Safari 5, and Chrome. - */ - -abbr[title] { - border-bottom: 1px dotted; -} - -/** - * Address style set to `bolder` in Firefox 4+, Safari 5, and Chrome. - */ - -b, -strong { - font-weight: bold; -} - -/** - * Address styling not present in Safari 5 and Chrome. - */ - -dfn { - font-style: italic; -} - -/** - * Address differences between Firefox and other browsers. - */ - -hr { - -moz-box-sizing: content-box; - box-sizing: content-box; - height: 0; -} - -/** - * Address styling not present in IE 8/9. - */ - -mark { - background: #ff0; - color: #000; -} - -/** - * Correct font family set oddly in Safari 5 and Chrome. - */ - -code, -kbd, -pre, -samp { - font-family: monospace, serif; - font-size: 1em; -} - -/** - * Improve readability of pre-formatted text in all browsers. - */ - -pre { - white-space: pre-wrap; -} - -/** - * Set consistent quote types. - */ - -q { - quotes: "\201C" "\201D" "\2018" "\2019"; -} - -/** - * Address inconsistent and variable font size in all browsers. - */ - -small { - font-size: 80%; -} - -/** - * Prevent `sub` and `sup` affecting `line-height` in all browsers. - */ - -sub, -sup { - font-size: 75%; - line-height: 0; - position: relative; - vertical-align: baseline; -} - -sup { - top: -0.5em; -} - -sub { - bottom: -0.25em; -} - -/* ========================================================================== - Embedded content - ========================================================================== */ - -/** - * Remove border when inside `a` element in IE 8/9. - */ - -img { - border: 0; -} - -/** - * Correct overflow displayed oddly in IE 9. - */ - -svg:not(:root) { - overflow: hidden; -} - -/* ========================================================================== - Figures - ========================================================================== */ - -/** - * Address margin not present in IE 8/9 and Safari 5. - */ - -figure { - margin: 0; -} - -/* ========================================================================== - Forms - ========================================================================== */ - -/** - * Define consistent border, margin, and padding. - */ - -fieldset { - border: 1px solid #c0c0c0; - margin: 0 2px; - padding: 0.35em 0.625em 0.75em; -} - -/** - * 1. Correct `color` not being inherited in IE 8/9. - * 2. Remove padding so people aren't caught out if they zero out fieldsets. - */ - -legend { - border: 0; /* 1 */ - padding: 0; /* 2 */ -} - -/** - * 1. Correct font family not being inherited in all browsers. - * 2. Correct font size not being inherited in all browsers. - * 3. Address margins set differently in Firefox 4+, Safari 5, and Chrome. - */ - -button, -input, -select, -textarea { - font-family: inherit; /* 1 */ - font-size: 100%; /* 2 */ - margin: 0; /* 3 */ -} - -/** - * Address Firefox 4+ setting `line-height` on `input` using `!important` in - * the UA stylesheet. - */ - -button, -input { - line-height: normal; -} - -/** - * Address inconsistent `text-transform` inheritance for `button` and `select`. - * All other form control elements do not inherit `text-transform` values. - * Correct `button` style inheritance in Chrome, Safari 5+, and IE 8+. - * Correct `select` style inheritance in Firefox 4+ and Opera. - */ - -button, -select { - text-transform: none; -} - -/** - * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` - * and `video` controls. - * 2. Correct inability to style clickable `input` types in iOS. - * 3. Improve usability and consistency of cursor style between image-type - * `input` and others. - */ - -button, -html input[type="button"], /* 1 */ -input[type="reset"], -input[type="submit"] { - -webkit-appearance: button; /* 2 */ - cursor: pointer; /* 3 */ -} - -/** - * Re-set default cursor for disabled elements. - */ - -button[disabled], -html input[disabled] { - cursor: default; -} - -/** - * 1. Address box sizing set to `content-box` in IE 8/9/10. - * 2. Remove excess padding in IE 8/9/10. - */ - -input[type="checkbox"], -input[type="radio"] { - box-sizing: border-box; /* 1 */ - padding: 0; /* 2 */ -} - -/** - * 1. Address `appearance` set to `searchfield` in Safari 5 and Chrome. - * 2. Address `box-sizing` set to `border-box` in Safari 5 and Chrome - * (include `-moz` to future-proof). - */ - -input[type="search"] { - -webkit-appearance: textfield; /* 1 */ - -moz-box-sizing: content-box; - -webkit-box-sizing: content-box; /* 2 */ - box-sizing: content-box; -} - -/** - * Remove inner padding and search cancel button in Safari 5 and Chrome - * on OS X. - */ - -input[type="search"]::-webkit-search-cancel-button, -input[type="search"]::-webkit-search-decoration { - -webkit-appearance: none; -} - -/** - * Remove inner padding and border in Firefox 4+. - */ - -button::-moz-focus-inner, -input::-moz-focus-inner { - border: 0; - padding: 0; -} - -/** - * 1. Remove default vertical scrollbar in IE 8/9. - * 2. Improve readability and alignment in all browsers. - */ - -textarea { - overflow: auto; /* 1 */ - vertical-align: top; /* 2 */ -} - -/* ========================================================================== - Tables - ========================================================================== */ - -/** - * Remove most spacing between table cells. - */ - -table { - border-collapse: collapse; - border-spacing: 0; -} - -.go-top { -position: fixed; -bottom: 2em; -right: 2em; -text-decoration: none; -background-color: #E0E0E0; -font-size: 12px; -padding: 1em; -display: inline; -} - -/* Github css */ - -html,body{ margin: auto; - padding-right: 1em; - padding-left: 1em; - max-width: 44em; color:black;}*:not('#mkdbuttons'){margin:0;padding:0}body{font:13.34px helvetica,arial,freesans,clean,sans-serif;-webkit-font-smoothing:subpixel-antialiased;line-height:1.4;padding:3px;background:#fff;border-radius:3px;-moz-border-radius:3px;-webkit-border-radius:3px}p{margin:1em 0}a{color:#4183c4;text-decoration:none}body{background-color:#fff;padding:30px;margin:15px;font-size:14px;line-height:1.6}body>*:first-child{margin-top:0!important}body>*:last-child{margin-bottom:0!important}@media screen{body{box-shadow:0 0 0 1px #cacaca,0 0 0 4px #eee}}h1,h2,h3,h4,h5,h6{margin:20px 0 10px;padding:0;font-weight:bold;-webkit-font-smoothing:subpixel-antialiased;cursor:text}h1{font-size:28px;color:#000}h2{font-size:24px;border-bottom:1px solid #ccc;color:#000}h3{font-size:18px;color:#333}h4{font-size:16px;color:#333}h5{font-size:14px;color:#333}h6{color:#777;font-size:14px}p,blockquote,table,pre{margin:15px 0}ul{padding-left:30px}ol{padding-left:30px}ol li ul:first-of-type{margin-top:0}hr{background:transparent url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAYAAAAECAYAAACtBE5DAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYwIDYxLjEzNDc3NywgMjAxMC8wMi8xMi0xNzozMjowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNSBNYWNpbnRvc2giIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6OENDRjNBN0E2NTZBMTFFMEI3QjRBODM4NzJDMjlGNDgiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6OENDRjNBN0I2NTZBMTFFMEI3QjRBODM4NzJDMjlGNDgiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo4Q0NGM0E3ODY1NkExMUUwQjdCNEE4Mzg3MkMyOUY0OCIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo4Q0NGM0E3OTY1NkExMUUwQjdCNEE4Mzg3MkMyOUY0OCIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PqqezsUAAAAfSURBVHjaYmRABcYwBiM2QSA4y4hNEKYDQxAEAAIMAHNGAzhkPOlYAAAAAElFTkSuQmCC) repeat-x 0 0;border:0 none;color:#ccc;height:4px;padding:0}body>h2:first-child{margin-top:0;padding-top:0}body>h1:first-child{margin-top:0;padding-top:0}body>h1:first-child+h2{margin-top:0;padding-top:0}body>h3:first-child,body>h4:first-child,body>h5:first-child,body>h6:first-child{margin-top:0;padding-top:0}a:first-child h1,a:first-child h2,a:first-child h3,a:first-child h4,a:first-child h5,a:first-child h6{margin-top:0;padding-top:0}h1+p,h2+p,h3+p,h4+p,h5+p,h6+p,ul li>:first-child,ol li>:first-child{margin-top:0}dl{padding:0}dl dt{font-size:14px;font-weight:bold;font-style:italic;padding:0;margin:15px 0 5px}dl dt:first-child{padding:0}dl dt>:first-child{margin-top:0}dl dt>:last-child{margin-bottom:0}dl dd{margin:0 0 15px;padding:0 15px}dl dd>:first-child{margin-top:0}dl dd>:last-child{margin-bottom:0}blockquote{border-left:4px solid #DDD;padding:0 15px;color:#777}blockquote>:first-child{margin-top:0}blockquote>:last-child{margin-bottom:0}table{border-collapse:collapse;border-spacing:0;font-size:100%;font:inherit}table th{font-weight:bold;border:1px solid #ccc;padding:6px 13px}table td{border:1px solid #ccc;padding:6px 13px}table tr{border-top:1px solid #ccc;background-color:#fff}table tr:nth-child(2n){background-color:#f8f8f8}img{max-width:100%}code,tt{margin:0 2px;padding:0 5px;white-space:nowrap;border:1px solid #eaeaea;background-color:#f8f8f8;border-radius:3px;font-family:Consolas,'Liberation Mono',Courier,monospace;font-size:12px;color:#333}pre>code{margin:0;padding:0;white-space:pre;border:0;background:transparent}.highlight pre{background-color:#f8f8f8;border:1px solid #ccc;font-size:13px;line-height:19px;overflow:auto;padding:6px 10px;border-radius:3px}pre{background-color:#f8f8f8;border:1px solid #ccc;font-size:13px;line-height:19px;overflow:auto;padding:6px 10px;border-radius:3px}pre code,pre tt{background-color:transparent;border:0}.poetry pre{font-family:Georgia,Garamond,serif!important;font-style:italic;font-size:110%!important;line-height:1.6em;display:block;margin-left:1em}.poetry pre code{font-family:Georgia,Garamond,serif!important;word-break:break-all;word-break:break-word;-webkit-hyphens:auto;-moz-hyphens:auto;hyphens:auto;white-space:pre-wrap}sup,sub,a.footnote{font-size:1.4ex;height:0;line-height:1;vertical-align:super;position:relative}sub{vertical-align:sub;top:-1px}@media print{body{background:#fff}img,pre,blockquote,table,figure{page-break-inside:avoid}body{background:#fff;border:0}code{background-color:#fff;color:#333!important;padding:0 .2em;border:1px solid #dedede}pre{background:#fff}pre code{background-color:white!important;overflow:visible}}@media screen{body.inverted{color:#eee!important;border-color:#555;box-shadow:none}.inverted body,.inverted hr .inverted p,.inverted td,.inverted li,.inverted h1,.inverted h2,.inverted h3,.inverted h4,.inverted h5,.inverted h6,.inverted th,.inverted .math,.inverted caption,.inverted dd,.inverted dt,.inverted blockquote{color:#eee!important;border-color:#555;box-shadow:none}.inverted td,.inverted th{background:#333}.inverted h2{border-color:#555}.inverted hr{border-color:#777;border-width:1px!important}::selection{background:rgba(157,193,200,0.5)}h1::selection{background-color:rgba(45,156,208,0.3)}h2::selection{background-color:rgba(90,182,224,0.3)}h3::selection,h4::selection,h5::selection,h6::selection,li::selection,ol::selection{background-color:rgba(133,201,232,0.3)}code::selection{background-color:rgba(0,0,0,0.7);color:#eee}code span::selection{background-color:rgba(0,0,0,0.7)!important;color:#eee!important}a::selection{background-color:rgba(255,230,102,0.2)}.inverted a::selection{background-color:rgba(255,230,102,0.6)}td::selection,th::selection,caption::selection{background-color:rgba(180,237,95,0.5)}.inverted{background:#0b2531;background:#252a2a}.inverted body{background:#252a2a}.inverted a{color:#acd1d5}}.highlight .c{color:#998;font-style:italic}.highlight .err{color:#a61717;background-color:#e3d2d2}.highlight .k,.highlight .o{font-weight:bold}.highlight .cm{color:#998;font-style:italic}.highlight .cp{color:#999;font-weight:bold}.highlight .c1{color:#998;font-style:italic}.highlight .cs{color:#999;font-weight:bold;font-style:italic}.highlight .gd{color:#000;background-color:#fdd}.highlight .gd .x{color:#000;background-color:#faa}.highlight .ge{font-style:italic}.highlight .gr{color:#a00}.highlight .gh{color:#999}.highlight .gi{color:#000;background-color:#dfd}.highlight .gi .x{color:#000;background-color:#afa}.highlight .go{color:#888}.highlight .gp{color:#555}.highlight .gs{font-weight:bold}.highlight .gu{color:#800080;font-weight:bold}.highlight .gt{color:#a00}.highlight .kc,.highlight .kd,.highlight .kn,.highlight .kp,.highlight .kr{font-weight:bold}.highlight .kt{color:#458;font-weight:bold}.highlight .m{color:#099}.highlight .s{color:#d14}.highlight .na{color:#008080}.highlight .nb{color:#0086b3}.highlight .nc{color:#458;font-weight:bold}.highlight .no{color:#008080}.highlight .ni{color:#800080}.highlight .ne,.highlight .nf{color:#900;font-weight:bold}.highlight .nn{color:#555}.highlight .nt{color:#000080}.highlight .nv{color:#008080}.highlight .ow{font-weight:bold}.highlight .w{color:#bbb}.highlight .mf,.highlight .mh,.highlight .mi,.highlight .mo{color:#099}.highlight .sb,.highlight .sc,.highlight .sd,.highlight .s2,.highlight .se,.highlight .sh,.highlight .si,.highlight .sx{color:#d14}.highlight .sr{color:#009926}.highlight .s1{color:#d14}.highlight .ss{color:#990073}.highlight .bp{color:#999}.highlight .vc,.highlight .vg,.highlight .vi{color:#008080}.highlight .il{color:#099}.highlight .gc{color:#999;background-color:#eaf2f5}.type-csharp .highlight .k,.type-csharp .highlight .kt{color:#00F}.type-csharp .highlight .nf{color:#000;font-weight:normal}.type-csharp .highlight .nc{color:#2b91af}.type-csharp .highlight .nn{color:#000}.type-csharp .highlight .s,.type-csharp .highlight .sc{color:#a31515} diff --git a/doc/index.md b/doc/index.md deleted file mode 100644 index 2f1d9e6..0000000 --- a/doc/index.md +++ /dev/null @@ -1,305 +0,0 @@ -# elli - Erlang web server for HTTP APIs - -[![Hex.pm][hex badge]][hex package] -[![Documentation][doc badge]][docs] -[![Erlang][erlang badge]][erlang downloads] -[![Travis CI][travis badge]][travis builds] -[![Coverage Status][coveralls badge]][coveralls link] -[![MIT License][license badge]](LICENSE) - -[travis builds]: https://travis-ci.org/elli-lib/elli -[travis badge]: https://travis-ci.org/elli-lib/elli.svg -[hex badge]: https://img.shields.io/hexpm/v/elli.svg -[hex package]: https://hex.pm/packages/elli -[latest release]: https://github.com/elli-lib/elli/releases/latest -[erlang badge]: https://img.shields.io/badge/erlang-%E2%89%A518.0-red.svg -[erlang downloads]: http://www.erlang.org/downloads -[doc badge]: https://img.shields.io/badge/docs-edown-green.svg -[docs]: doc/README.md -[coveralls badge]: https://coveralls.io/repos/github/elli-lib/elli/badge.svg?branch=develop -[coveralls link]: https://coveralls.io/github/elli-lib/elli?branch=develop -[license badge]: https://img.shields.io/badge/license-MIT-blue.svg - -Elli is a webserver you can run inside your Erlang application to -expose an HTTP API. Elli is a aimed exclusively at building -high-throughput, low-latency HTTP APIs. If robustness and performance -is more important than general purpose features, then `elli` might be -for you. If you find yourself digging into the implementation of a -webserver, `elli` might be for you. If you're building web services, -not web sites, then `elli` might be for you. - -Elli is used in production at Wooga and Game Analytics. Elli requires -OTP 18.0 or newer. - - -## Installation - -To use `elli` you will need a working installation of Erlang 18.0 (or later). - -Add `elli` to your application by adding it as a dependency to your -[`rebar.config`](http://www.rebar3.org/docs/configuration): - -```erlang -{deps, [elli]}. -``` - -Afterwards you can run: - -```sh -$ rebar3 compile -``` - - -## Usage -```sh -$ rebar3 shell -``` - -```erlang -%% starting elli -1> {ok, Pid} = elli:start_link([{callback, elli_example_callback}, {port, 3000}]). -``` - -## Examples - -### Callback Module - -The best source to learn how to write a callback module -is [src/elli_example_callback.erl](src/elli_example_callback.erl) and -its [generated documentation](doc/elli_example_callback.md). There are a bunch -of examples used in the tests as well as descriptions of all the events. - -A minimal callback module could look like this: - -```erlang --module(elli_minimal_callback). --export([handle/2, handle_event/3]). - --include_lib("elli/include/elli.hrl"). --behaviour(elli_handler). - -handle(Req, _Args) -> - %% Delegate to our handler function - handle(Req#req.method, elli_request:path(Req), Req). - -handle('GET',[<<"hello">>, <<"world">>], _Req) -> - %% Reply with a normal response. `ok' can be used instead of `200' - %% to signal success. - {ok, [], <<"Hello World!">>}; - -handle(_, _, _Req) -> - {404, [], <<"Not Found">>}. - -%% @doc Handle request events, like request completed, exception -%% thrown, client timeout, etc. Must return `ok'. -handle_event(_Event, _Data, _Args) -> - ok. -``` - - -### Supervisor Childspec - -To add `elli` to a supervisor you can use the following example and adapt it to -your needs. - -```erlang --module(fancyapi_sup). --behaviour(supervisor). --export([start_link/0]). --export([init/1]). - -start_link() -> - supervisor:start_link({local, ?MODULE}, ?MODULE, []). - -init([]) -> - ElliOpts = [{callback, fancyapi_callback}, {port, 3000}], - ElliSpec = { - fancy_http, - {elli, start_link, [ElliOpts]}, - permanent, - 5000, - worker, - [elli]}, - - {ok, { {one_for_one, 5, 10}, [ElliSpec]} }. -``` - - -## Features - -Here's the features Elli _does_ have: - -* [Rack][]-style request-response. Your handler function gets a - complete request and returns a complete response. There's no - messaging, no receiving data directly from the socket, no writing - responses directly to the socket. It's a very simple and - straightforward API. Have a look at [`elli_example_callback`](elli_example_callback.md) -for examples. - -* Middlewares allow you to add useful features like compression, -encoding, stats, but only have it used when needed. No features you -don't use on the critical path. - -* Short-circuiting of responses using exceptions, allows you to use - "assertions" that return for example 403 permission - denied. `is_allowed(Req) orelse throw({403, [], <<"Permission - denied">>})`. - -* Every client connection gets its own process, isolating the failure -of a request from another. For the duration of the connection, only -one process is involved, resulting in very robust and efficient -code. - -* Binaries everywhere for strings. - -* Instrumentation inside the core of the webserver, triggering user - callbacks. For example when a request completes, the user callback - gets the `request_complete` event which contains timings of all the -different parts of handling a request. There's also events for -clients unexpectedly closing a connection, crashes in the user -callback, etc. - -* Keep alive, using one Erlang process per connection only active -when there is a request from the client. Number of connections is -only limited by RAM and CPU. - -* Chunked transfer in responses for real-time push to clients - -* Basic pipelining. HTTP verbs that does not have side-effects(`GET` - and `HEAD`) can be pipelined, ie. a client supporting pipelining -can send multiple requests down the line and expect the responses -to appear in the same order as requests. Elli processes the -requests one at a time in order, future work could make it possible -to process them in parallel. - -* SSL using built-in Erlang/OTP ssl, nice for low volume admin -interfaces, etc. For high volume, you should probably go with -nginx, stunnel or ELB if you're on AWS. - -* Implement your own connection handling, for WebSockets, streaming - uploads, etc. See [`elli_example_callback_handover`](elli_example_callback_handover.md). - -## Extensions - -* [elli_access_log](https://github.com/elli-lib/elli_access_log): -Access log -* [elli_basicauth](https://github.com/elli-lib/elli_basicauth): -Basic auth -* [elli_chatterbox](https://github.com/elli-lib/elli_chatterbox): -HTTP/2 support -* [elli_cloudfront](https://github.com/elli-lib/elli_cloudfront): -CloudFront signed URLs -* [elli_cookie](https://github.com/elli-lib/elli_cookie): -Cookies -* [elli_date](https://github.com/elli-lib/elli_date): -"Date" header -* [elli_fileserve](https://github.com/elli-lib/elli_fileserve): -Static content -* [elli_prometheus](https://github.com/elli-lib/elli_prometheus): -Prometheus -* [elli_stats](https://github.com/elli-lib/elli_stats): -Real-time statistics dashboard -* [elli_websockets](https://github.com/elli-lib/elli_websocket): -WebSockets -* [elli_xpblfe](https://github.com/elli-lib/elli_xpblfe): -X-Powered-By LFE - -## About - -From operating and debugging high-volume, low-latency apps we have -gained some valuable insight into what we want from a webserver. We -want simplicity, robustness, performance, ease of debugging, -visibility into strange client behaviour, really good instrumentation -and good tests. We are willing to sacrifice almost everything, even -basic features to achieve this. - -With this in mind we looked at the big names in the Erlang -community: [Yaws][], [Mochiweb][], [Misultin][] and [Cowboy][]. We -found [Mochiweb][] to be the best match. However, we also wanted to -see if we could take the architecture of [Mochiweb][] and improve on -it. `elli` takes the acceptor-turns-into-request-handler idea found -in [Mochiweb][], the binaries-only idea from [Cowboy][] and the -request-response idea from [WSGI][]/[Rack][] (with chunked transfer -being an exception). - -On top of this we built a handler that allows us to write HTTP -middleware modules to add practical features, like compression of -responses, HTTP access log with timings, a real-time statistics -dashboard and chaining multiple request handlers. - -## Aren't there enough webservers in the Erlang community already? - -There are a few very mature and robust projects with steady -development, one recently ceased development and one new kid on the -block with lots of interest. As `elli` is not a general purpose -webserver, but more of a specialized tool, we believe it has a very -different target audience and would not attract effort or users away -from the big names. - -## Why another webserver? Isn't this just the NIH syndrome? - -[Yaws][], [Mochiweb][], [Misultin][], and [Cowboy][] are great -projects, hardened over time and full of very useful features for web -development. If you value developer productivity, [Yaws][] is an -excellent choice. If you want a fast and lightweight -server, [Mochiweb][] and [Cowboy][] are excellent choices. - -Having used and studied all of these projects, we believed that if we -merged some of the existing ideas and added some ideas from other -communities, we could create a core that was better for our use cases. - -It started out as an experiment to see if it is at all possible to -significantly improve and it turns out that for our particular use -cases, there is enough improvement to warrant a new project. - -## What makes Elli different? - -Elli has a very simple architecture. It avoids using more processes -and messages than absolutely necessary. It uses binaries for -strings. The request-response programming model allows middlewares to -do much heavy lifting, so the core can stay very simple. It has been -instrumented so as a user you can understand where time is spent. When -things go wrong, like the client closed the connection before you -could send a response, you are notified about these things so you can -better understand your client behaviour. - -## Performance - -"Hello World!" micro-benchmarks are really useful when measuring the -performance of the webserver itself, but the numbers usually do more -harm than good when released. I encourage you to run your own -benchmarks, on your own hardware. Mark Nottingham has some -[very good pointers](http://www.mnot.net/blog/2011/05/18/http_benchmark_rules) -about benchmarking HTTP servers. - -[Yaws]: https://github.com/klacke/yaws -[Mochiweb]: https://github.com/mochi/mochiweb -[Misultin]: https://github.com/ostinelli/misultin -[Cowboy]: https://github.com/ninenines/cowboy -[WSGI]: https://www.python.org/dev/peps/pep-3333/ -[Rack]: https://github.com/rack/rack - - -## Modules ## - - - - - - - - - - - - - - -
elli
elli_example_callback
elli_example_callback_handover
elli_handler
elli_http
elli_middleware
elli_middleware_compress
elli_request
elli_sendfile
elli_tcp
elli_test
elli_util
- - -## License - -Elli is licensed under [The MIT License](LICENSE). - -Copyright (c) 2012-2016 Knut Nesheim, 2016-2018 elli-lib team diff --git a/doc/overview.edoc b/doc/overview.edoc deleted file mode 100644 index be8cd8c..0000000 --- a/doc/overview.edoc +++ /dev/null @@ -1,167 +0,0 @@ -@author Knut Nesheim -@author elli-lib team -@copyright 2012-2016 Knut Nesheim, 2016-2018 elli-lib team -@version 3.0.0 -@title elli -@doc Erlang web server for HTTP APIs - -## Features - -Here's the features Elli does have: - - * [Rack][]-style request-response. Your handler function gets a - complete request and returns a complete response. There's no - messaging, no receiving data directly from the socket, no writing - responses directly to the socket. It's a very simple and - straightforward API. Have a look at {@link elli_example_callback} - for examples. - - * Middlewares allow you to add useful features like compression, - encoding, stats, but only have it used when needed. No features you - don't use on the critical path. - - * Short-circuiting of responses using exceptions, allows you to use - "assertions" that return for example 403 permission - denied. `is_allowed(Req) orelse throw({403, [], <<"Permission - denied">>})'. - - * Every client connection gets its own process, isolating the failure - of a request from another. For the duration of the connection, only - one process is involved, resulting in very robust and efficient - code. - - * Binaries everywhere for strings. - - * Instrumentation inside the core of the webserver, triggering user - callbacks. For example when a request completes, the user callback - gets the `request_complete' event which contains timings of all the - different parts of handling a request. There's also events for - clients unexpectedly closing a connection, crashes in the user - callback, etc. - - * Keep alive, using one Erlang process per connection only active - when there is a request from the client. Number of connections is - only limited by RAM and CPU. - - * Chunked transfer in responses for real-time push to clients - - * Basic pipelining. HTTP verbs that does not have side-effects(`GET' - and `HEAD') can be pipelined, ie. a client supporting pipelining - can send multiple requests down the line and expect the responses - to appear in the same order as requests. Elli processes the - requests one at a time in order, future work could make it possible - to process them in parallel. - - * SSL using built-in Erlang/OTP ssl, nice for low volume admin - interfaces, etc. For high volume, you should probably go with - nginx, stunnel or ELB if you're on AWS. - - * Implement your own connection handling, for WebSockets, streaming - uploads, etc. See {@link elli_example_callback_handover}. - - -## Extensions - - * [elli_access_log](https://github.com/elli-lib/elli_access_log): - Access log - * [elli_basicauth](https://github.com/elli-lib/elli_basicauth): - Basic auth - * [elli_chatterbox](https://github.com/elli-lib/elli_chatterbox): - HTTP/2 support - * [elli_cloudfront](https://github.com/elli-lib/elli_cloudfront): - CloudFront signed URLs - * [elli_cookie](https://github.com/elli-lib/elli_cookie): - Cookies - * [elli_date](https://github.com/elli-lib/elli_date): - "Date" header - * [elli_fileserve](https://github.com/elli-lib/elli_fileserve): - Static content - * [elli_prometheus](https://github.com/elli-lib/elli_prometheus): - Prometheus - * [elli_stats](https://github.com/elli-lib/elli_stats): - Real-time statistics dashboard - * [elli_websockets](https://github.com/elli-lib/elli_websocket): - WebSockets - * [elli_xpblfe](https://github.com/elli-lib/elli_xpblfe): - X-Powered-By LFE - - -## About - -From operating and debugging high-volume, low-latency apps we have -gained some valuable insight into what we want from a webserver. We -want simplicity, robustness, performance, ease of debugging, -visibility into strange client behaviour, really good instrumentation -and good tests. We are willing to sacrifice almost everything, even -basic features to achieve this. - -With this in mind we looked at the big names in the Erlang -community: [Yaws][], [Mochiweb][], [Misultin][] and [Cowboy][]. We -found [Mochiweb][] to be the best match. However, we also wanted to -see if we could take the architecture of [Mochiweb][] and improve on -it. `elli' takes the acceptor-turns-into-request-handler idea found -in [Mochiweb][], the binaries-only idea from [Cowboy][] and the -request-response idea from [WSGI][]/[Rack][] (with chunked transfer -being an exception). - -On top of this we built a handler that allows us to write HTTP -middleware modules to add practical features, like compression of -responses, HTTP access log with timings, a real-time statistics -dashboard and chaining multiple request handlers. - - -## Aren't there enough webservers in the Erlang community already? - -There are a few very mature and robust projects with steady -development, one recently ceased development and one new kid on the -block with lots of interest. As `elli' is not a general purpose -webserver, but more of a specialized tool, we believe it has a very -different target audience and would not attract effort or users away -from the big names. - - -## Why another webserver? Isn't this just the NIH syndrome? - -[Yaws][], [Mochiweb][], [Misultin][], and [Cowboy][] are great -projects, hardened over time and full of very useful features for web -development. If you value developer productivity, [Yaws][] is an -excellent choice. If you want a fast and lightweight -server, [Mochiweb][] and [Cowboy][] are excellent choices. - -Having used and studied all of these projects, we believed that if we -merged some of the existing ideas and added some ideas from other -communities, we could create a core that was better for our use cases. - -It started out as an experiment to see if it is at all possible to -significantly improve and it turns out that for our particular use -cases, there is enough improvement to warrant a new project. - - -## What makes Elli different? - -Elli has a very simple architecture. It avoids using more processes -and messages than absolutely necessary. It uses binaries for -strings. The request-response programming model allows middlewares to -do much heavy lifting, so the core can stay very simple. It has been -instrumented so as a user you can understand where time is spent. When -things go wrong, like the client closed the connection before you -could send a response, you are notified about these things so you can -better understand your client behaviour. - -## Performance - -"Hello World!" micro-benchmarks are really useful when measuring the -performance of the webserver itself, but the numbers usually do more -harm than good when released. I encourage you to run your own -benchmarks, on your own hardware. Mark Nottingham has some -[very good pointers](http://www.mnot.net/blog/2011/05/18/http_benchmark_rules) -about benchmarking HTTP servers. - - - -[Yaws]: https://github.com/klacke/yaws -[Mochiweb]: https://github.com/mochi/mochiweb -[Misultin]: https://github.com/ostinelli/misultin -[Cowboy]: https://github.com/ninenines/cowboy -[WSGI]: https://www.python.org/dev/peps/pep-3333/ -[Rack]: https://github.com/rack/rack diff --git a/doc/stylesheet.css b/doc/stylesheet.css deleted file mode 100644 index ab170c0..0000000 --- a/doc/stylesheet.css +++ /dev/null @@ -1,55 +0,0 @@ -/* standard EDoc style sheet */ -body { - font-family: Verdana, Arial, Helvetica, sans-serif; - margin-left: .25in; - margin-right: .2in; - margin-top: 0.2in; - margin-bottom: 0.2in; - color: #000000; - background-color: #ffffff; -} -h1,h2 { - margin-left: -0.2in; -} -div.navbar { - background-color: #add8e6; - padding: 0.2em; -} -h2.indextitle { - padding: 0.4em; - background-color: #add8e6; -} -h3.function,h3.typedecl { - background-color: #add8e6; - padding-left: 1em; -} -div.spec { - margin-left: 2em; - background-color: #eeeeee; -} -a.module { - text-decoration:none -} -a.module:hover { - background-color: #eeeeee; -} -ul.definitions { - list-style-type: none; -} -ul.index { - list-style-type: none; - background-color: #eeeeee; -} - -/* - * Minor style tweaks - */ -ul { - list-style-type: square; -} -table { - border-collapse: collapse; -} -td { - padding: 3 -} diff --git a/doc/tpl.html b/doc/tpl.html deleted file mode 100644 index 506b5f2..0000000 --- a/doc/tpl.html +++ /dev/null @@ -1,86 +0,0 @@ - - - - - - - index - - - - - - - $body$ - - diff --git a/rebar.config b/rebar.config index 8143042..5af85b3 100644 --- a/rebar.config +++ b/rebar.config @@ -5,16 +5,6 @@ {deps, []}. {xref_checks, [deprecated_function_calls,undefined_function_calls,locals_not_used]}. {profiles, [ - {docs, [ - {deps, [{edown, "0.9.1"}]}, - {edoc_opts, [ - {preprocess, true}, - {def, [ - {'EXAMPLE_CONF',"[{callback,elli_example_callback},{callback_args,[]}]"} - ]}, - {doclet, edown_doclet} - ]} - ]}, {test, [ {deps, [{hackney, "1.20.1"}]}, {extra_src_dirs, [ @@ -37,10 +27,21 @@ {project_plugins, [ {covertool, "2.0.6"}, - {rebar3_lint, "3.2.5"} + {rebar3_lint, "3.2.5"}, + {rebar3_ex_doc, "0.2.23"} +]}. +{ex_doc, [ + {extras, [ + "README.md", + "OVERVIEW.md", + "CHANGELOG.md", + "LICENSE" + ]}, + {main, "README.md"}, + {source_url, "https://github.com/elli-lib/elli"} +]}. +{hex, [ + {doc, #{provider => ex_doc}} ]}. -{provider_hooks, [{pre, [{eunit, lint}]}]}. {dialyzer, [{plt_extra_apps, [ssl]}, {warnings, [unknown]}]}. - -{post_hooks, [{edoc, "doc/build.sh"}]}. diff --git a/src/elli.erl b/src/elli.erl index cb6deb4..baa6806 100644 --- a/src/elli.erl +++ b/src/elli.erl @@ -1,4 +1,4 @@ -%% @doc: Elli acceptor manager +%% @doc Elli acceptor manager %% %% This gen_server owns the listen socket and manages the processes %% accepting on that socket. When a process waiting for accept gets a @@ -26,17 +26,17 @@ -export_type([req/0, http_method/0, body/0, headers/0, response_code/0]). -%% @type req(). A record representing an HTTP request. -type req() :: #req{}. + -elvis([{elvis_style, private_data_types, disable}]). -%% @type http_method(). An uppercase atom representing a known HTTP verb or a -%% binary for other verbs. +%% A record representing an HTTP request. -type http_method() :: 'OPTIONS' | 'GET' | 'HEAD' | 'POST' | 'PUT' | 'DELETE' | 'TRACE' | binary(). +%% An uppercase atom representing a known HTTP verb or a binary for other verbs. -%% @type body(). A binary or iolist. -type body() :: binary() | iolist(). +%% A binary or iolist. -type header() :: {Key::binary(), Value::binary() | string()}. -type headers() :: [header()]. @@ -49,8 +49,8 @@ options = [] :: [{_, _}], % TODO: refine callback :: elli_handler:callback() }). -%% @type state(). Internal state. -opaque state() :: #state{}. +%% Internal state. -export_type([state/0]). @@ -58,13 +58,13 @@ %%% API %%%=================================================================== +%% @doc Create an Elli server process as part of a supervision tree, using the +%% default configuration. +%% The same as `start_link({callback, elli_example_callback}, {callback_args, []})'. -spec start_link() -> Result when Result :: {ok, Pid} | ignore | {error, Error}, Pid :: pid(), Error :: {already_started, Pid} | term(). -%% @equiv start_link({@EXAMPLE_CONF}) -%% @doc Create an Elli server process as part of a supervision tree, using the -%% default configuration. start_link() -> start_link(?EXAMPLE_CONF). -spec start_link(Opts) -> Result when @@ -87,8 +87,8 @@ start_link(Opts) -> get_acceptors(S) -> gen_server:call(S, get_acceptors). +%% The same as `get_open_reqs(S, 5000)' -spec get_open_reqs(S :: atom()) -> {reply, {ok, non_neg_integer()}, state()}. -%% @equiv get_open_reqs(S, 5000) get_open_reqs(S) -> get_open_reqs(S, 5000). @@ -116,7 +116,7 @@ stop(S) -> %%% gen_server callbacks %%%=================================================================== -%% @hidden +%% @private -spec init([Opts :: [{_, _}]]) -> {ok, state()}. init([Opts]) -> %% Use the exit signal from the acceptor processes to know when @@ -176,7 +176,7 @@ http_start(Socket, Options, Callback, CallbackArgs, Acceptors) -> Pid = elli_http:start_link(self(), Socket, Options, {Callback, CallbackArgs}), ets:insert(Acceptors, {Pid}). -%% @hidden +%% @private -spec handle_call(get_acceptors, {pid(), _Tag}, state()) -> {reply, {ok, [ets:tid()]}, state()}; (get_open_reqs, {pid(), _Tag}, state()) -> @@ -200,7 +200,7 @@ handle_call({set_callback, Callback, CallbackArgs}, _From, State) -> handle_call(stop, _From, State) -> {stop, normal, ok, State}. -%% @hidden +%% @private -spec handle_cast(accepted | _Msg, State0) -> {noreply, State1} when State0 :: state(), State1 :: state(). @@ -211,7 +211,7 @@ handle_cast(_Msg, State) -> {noreply, State}. -%% @hidden +%% @private -spec handle_info({'EXIT', _Pid, Reason}, State0) -> Result when State0 :: state(), Reason :: {error, emfile}, @@ -229,13 +229,17 @@ handle_info({'EXIT', Pid, Reason}, State) -> {noreply, remove_acceptor(State, Pid)}. -%% @hidden +%% @private -spec terminate(_Reason, _State) -> ok. terminate(_Reason, _State) -> ok. -%% @hidden --spec code_change(_OldVsn, State, _Extra) -> {ok, State} when State :: state(). +%% @private +-spec code_change(OldVsn, State, Extra) -> {ok, State} + when OldVsn :: Vsn | {down, Vsn}, + Vsn :: term(), + State :: state(), + Extra :: term(). code_change(_OldVsn, State, _Extra) -> {ok, State}. diff --git a/src/elli_example_callback.erl b/src/elli_example_callback.erl index 5fb1928..8d1b1e3 100644 --- a/src/elli_example_callback.erl +++ b/src/elli_example_callback.erl @@ -1,10 +1,10 @@ -%%% @doc: Elli example callback +%%% @doc Elli example callback %%% -%%% Your callback needs to implement two functions, {@link handle/2} and -%%% {@link handle_event/3}. For every request, Elli will call your handle +%%% Your callback needs to implement two functions, `handle/2' and +%%% `handle_event/3'. For every request, Elli will call your handle %%% function with the request. When an event happens, like Elli %%% completed a request, there was a parsing error or your handler -%%% threw an error, {@link handle_event/3} is called. +%%% threw an error, `handle_event/3' is called. -module(elli_example_callback). -export([handle/2, handle_event/3]). @@ -22,7 +22,7 @@ %% @doc Handle a `Req'uest. %% Delegate to our handler function. -%% @see handle/3 +%% See `handle/3' -spec handle(Req, _Args) -> Result when Req :: elli:req(), _Args :: elli_handler:callback_args(), @@ -38,31 +38,31 @@ handle(Req, _Args) -> handle(Req#req.method, elli_request:path(Req), Req). %% If you return any of the following HTTP headers, you can %% override the default behaviour of Elli: %% -%% * **Connection**: By default Elli will use `keep-alive' if the protocol -%% supports it, setting `<<"close">>' will close the -%% connection immediately after Elli has sent the -%% response. If the client has already sent pipelined -%% requests, these will be discarded. +%% * `**Connection**': By default Elli will use `keep-alive' if the protocol +%% supports it, setting `<<"close">>' will close the +%% connection immediately after Elli has sent the +%% response. If the client has already sent pipelined +%% requests, these will be discarded. %% -%% * **Content-Length**: By default Elli looks at the size of the body you -%% returned to determine the `Content-Length' header. -%% Explicitly including your own `Content-Length' (with -%% the value as `integer()', `binary()' or `list()') -%% allows you to return an empty body. Useful for -%% implementing the `"304 Not Modified"' response. +%% * `**Content-Length**': By default Elli looks at the size of the body you +%% returned to determine the `Content-Length' header. +%% Explicitly including your own `Content-Length' (with +%% the value as `integer()', `binary()' or `list()') +%% allows you to return an empty body. Useful for +%% implementing the `"304 Not Modified"' response. %% -%% @see elli_request:get_arg/3 -%% @see elli_request:post_arg/3 -%% @see elli_request:post_arg_decoded/3 -%% @see elli_request:get_header/3 -%% @see elli_request:get_arg_decoded/3 -%% @see elli_request:get_args_decoded/1 -%% @see elli_util:file_size/1 -%% @see elli_request:get_range/1 -%% @see elli_request:normalize_range/2 -%% @see elli_request:encode_range/2 -%% @see elli_request:chunk_ref/1 -%% @see chunk_loop/1 +%% See `elli_request:get_arg/3' +%% See `elli_request:post_arg/3' +%% See `elli_request:post_arg_decoded/3' +%% See `elli_request:get_header/3' +%% See `elli_request:get_arg_decoded/3' +%% See `elli_request:get_args_decoded/1' +%% See `elli_util:file_size/1' +%% See `elli_request:get_range/1' +%% See `elli_request:normalize_range/2' +%% See `elli_request:encode_range/2' +%% See `elli_request:chunk_ref/1' +%% See `chunk_loop/1' -spec handle(Method, Path, Req) -> elli_handler:result() when Method :: elli:http_method(), Path :: [binary()], @@ -211,14 +211,14 @@ handle(_, _, _Req) -> %% @doc Send 10 separate chunks to the client. -%% @equiv chunk_loop(Ref, 10) +%% The same as `chunk_loop(Ref, 10)' chunk_loop(Ref) -> chunk_loop(Ref, 10). %% @doc If `N > 0', send a chunk to the client, checking for errors, %% as the user might have disconnected. -%% When `N == 0', call {@link elli_request:close_chunk/1. -%% elli_request:close_chunk(Ref)}. +%% When `N == 0', call `elli_request:close_chunk/1'. +%% `elli_request:close_chunk(Ref)'. chunk_loop(Ref, 0) -> elli_request:close_chunk(Ref); chunk_loop(Ref, N) -> @@ -257,7 +257,7 @@ chunk_loop(Ref, N) -> %% the user. %% %% `invalid_return' is sent if the user callback code returns a term not -%% understood by elli, see {@link elli_http:execute_callback/1}. +%% understood by elli, see `elli_http:execute_callback/1'. %% After triggering this event, a generated response is sent to the user. %% %% `chunk_complete' fires when a chunked response is completely @@ -273,11 +273,9 @@ chunk_loop(Ref, N) -> %% Elli is waiting for the request. %% %% `request_parse_error' fires if the request is invalid and cannot be parsed by -%% [`erlang:decode_packet/3`][decode_packet/3] or it contains a path Elli cannot +%% `erlang:decode_packet/3' or it contains a path Elli cannot %% parse or does not support. %% -%% [decode_packet/3]: http://erlang.org/doc/man/erlang.html#decode_packet-3 -%% %% `client_closed' can be sent from multiple parts of the request %% handling. It's sent when the client closes the connection or if for %% any reason the socket is closed unexpectedly. The `Where' atom diff --git a/src/elli_example_callback_handover.erl b/src/elli_example_callback_handover.erl index 052f43e..97f7f4a 100644 --- a/src/elli_example_callback_handover.erl +++ b/src/elli_example_callback_handover.erl @@ -37,6 +37,6 @@ handle('GET', [<<"hello">>], Req, _Args) -> {ok, [], <<"Hello ", Name/binary>>}. -%% @hidden +%% @private handle_event(_, _, _) -> ok. diff --git a/src/elli_example_middleware.erl b/src/elli_example_middleware.erl index aec9af8..1aaf536 100644 --- a/src/elli_example_middleware.erl +++ b/src/elli_example_middleware.erl @@ -1,4 +1,4 @@ -%% @hidden +%% @private -module(elli_example_middleware). -export([handle/2, handle_event/3]). -behaviour(elli_handler). diff --git a/src/elli_handler.erl b/src/elli_handler.erl index 481b051..47ea59c 100644 --- a/src/elli_handler.erl +++ b/src/elli_handler.erl @@ -4,18 +4,15 @@ -export_type([callback/0, callback_mod/0, callback_args/0, event/0, result/0]). -%% @type callback(). A tuple of a {@type callback_mod()} and {@type -%% callback_args()}. -type callback() :: {callback_mod(), callback_args()}. +%% A tuple of a `t:callback_mod()' and `t:callback_args()'. -%% @type callback_mod(). A callback module. -type callback_mod() :: module(). +%% A callback module. -%% @type callback_args(). Arguments to pass to a {@type callback_mod()}. -type callback_args() :: list(). +%% Arguments to pass to a `t:callback_mod()' -%% @type event(). Fired throughout processing a request. -%% See {@link elli_example_callback:handle_event/3} for descriptions. -type event() :: elli_startup | bad_request | file_error | chunk_complete | request_complete @@ -23,6 +20,8 @@ | request_closed | request_parse_error | client_closed | client_timeout | invalid_return. +%% Fired throughout processing a request. +%% See `elli_example_callback:handle_event/3' for descriptions. -type result() :: {elli:response_code() | ok, elli:headers(), diff --git a/src/elli_http.erl b/src/elli_http.erl index aaa131d..8b086a7 100644 --- a/src/elli_http.erl +++ b/src/elli_http.erl @@ -1,6 +1,6 @@ -%% @doc: Elli HTTP request implementation +%% @doc Elli HTTP request implementation %% -%% An elli_http process blocks in elli_tcp:accept/2 until a client +%% An elli_http process blocks in `elli_tcp:accept/3' until a client %% connects. It then handles requests on that connection until it's %% closed either by the client timing out or explicitly by the user. -module(elli_http). @@ -25,14 +25,14 @@ %% operating in handler mode. -export([close_or_keepalive/2]). --export_type([version/0]). - -ifdef(TEST). -export([get_body/5]). -endif. -%% @type version(). HTTP version as a tuple, i.e. `{0, 9} | {1, 0} | {1, 1}'. +-export_type([version/0]). + -type version() :: {0, 9} | {1, 0} | {1, 1}. +% HTTP version as a tuple, i.e. `{0, 9} | {1, 0} | {1, 1}'. -define(CONTENT_LENGTH_HEADER, <<"content-length">>). @@ -56,7 +56,7 @@ start_link(Server, ListenSocket, Options, Callback) -> %% @doc Accept on the socket until a client connects. %% Handle the request, then loop if we're using keep alive or chunked transfer. -%% If {@link elli_tcp:accept/3} doesn't return a socket within a configurable +%% If `elli_tcp:accept/3' doesn't return a socket within a configurable %% timeout, loop to allow code upgrades of this module. -spec accept(Server, ListenSocket, Options, Callback) -> ok when Server :: pid(), @@ -288,7 +288,7 @@ do_send_file(Fd, {Offset, Length}, #req{callback={Mod, Args}} = Req, Headers) -> end. %% @doc To send a response, we must first have received everything the -%% client is sending. If this is not the case, {@link send_bad_request/1} +%% client is sending. If this is not the case, `send_bad_request/1' %% might reset the client connection. send_bad_request(Socket) -> send_rescue_response(Socket, 400, <<"Bad Request">>). diff --git a/src/elli_middleware.erl b/src/elli_middleware.erl index 3eaadca..85ed9de 100644 --- a/src/elli_middleware.erl +++ b/src/elli_middleware.erl @@ -23,12 +23,12 @@ %%% ''' %%% %%% The configured modules may implement the elli behaviour, in which case all -%%% the callbacks will be used as normal. If {@link handle/2} returns `ignore', +%%% the callbacks will be used as normal. If `link handle/2' returns `ignore', %%% elli will continue on to the next callback in the list. %%% -%%% Pre-processing and post-processing is implemented in {@link preprocess/2} -%%% and {@link postprocess/3}. {@link preprocess/2} is called for each -%%% middleware in the order specified, while {@link postprocess/3} is called in +%%% Pre-processing and post-processing is implemented in `preprocess/2' +%%% and `postprocess/3'. `preprocess/2' is called for each +%%% middleware in the order specified, while `postprocess/3' is called in %%% the reverse order. %%% %%% TODO: Don't call all postprocess middlewares when a middleware @@ -48,7 +48,7 @@ %% ELLI CALLBACKS %% -%% @hidden +%% @private -spec init(Req, Args) -> {ok, standard | handover} when Req :: elli:req(), Args :: elli_handler:callback_args(). @@ -56,7 +56,7 @@ init(Req, Args) -> do_init(Req, callbacks(Args)). -%% @hidden +%% @private -spec handle(Req :: elli:req(), Config :: [tuple()]) -> elli_handler:result(). handle(CleanReq, Config) -> Callbacks = callbacks(Config), @@ -65,7 +65,7 @@ handle(CleanReq, Config) -> postprocess(PreReq, Res, lists:reverse(Callbacks)). -%% @hidden +%% @private -spec handle_event(Event, Args, Config) -> ok when Event :: elli_handler:event(), Args :: elli_handler:callback_args(), diff --git a/src/elli_request.erl b/src/elli_request.erl index bc235ed..b444a44 100644 --- a/src/elli_request.erl +++ b/src/elli_request.erl @@ -47,7 +47,7 @@ -elvis([{elvis_style, god_modules, disable}]). %% -%% Helpers for working with a #req{} +%% Helpers for working with a `#req{}' %% @@ -87,15 +87,15 @@ get_header(Key, #req{headers = Headers}, Default) -> proplists:get_value(CaseFoldedKey, Headers, Default). -%% @equiv get_arg(Key, Req, undefined) +%% The same as `get_arg(Key, Req, undefined)' get_arg(Key, #req{} = Req) -> get_arg(Key, Req, undefined). -%% @equiv proplists:get_value(Key, Args, Default) +%% The same as `proplists:get_value(Key, Args, Default)' get_arg(Key, #req{args = Args}, Default) -> proplists:get_value(Key, Args, Default). -%% @equiv get_arg_decoded(Key, Req, undefined) +%% The same as `get_arg_decoded(Key, Req, undefined)' get_arg_decoded(Key, #req{} = Req) -> get_arg_decoded(Key, Req, undefined). @@ -119,14 +119,14 @@ body_qs(#req{body = Body} = Req) -> erlang:error(badarg) end. -%% @equiv post_arg(Key, Req, undefined) +%% The same as `post_arg(Key, Req, undefined)' post_arg(Key, #req{} = Req) -> post_arg(Key, Req, undefined). post_arg(Key, #req{} = Req, Default) -> proplists:get_value(Key, body_qs(Req), Default). -%% @equiv post_arg_decoded(Key, Req, undefined) +%% The same as `post_arg_decoded(Key, Req, undefined)' post_arg_decoded(Key, #req{} = Req) -> post_arg_decoded(Key, Req, undefined). @@ -174,8 +174,8 @@ query_str(#req{raw_path = Path}) -> %% @doc Parse the `Range' header from the request. -%% The result is either a `byte_range_set()' or the atom `parse_error'. -%% Use {@link elli_util:normalize_range/2} to get a validated, normalized range. +%% The result is either a `[http_range()]' or the atom `parse_error'. +%% Use `elli_util:normalize_range/2' to get a validated, normalized range. -spec get_range(elli:req()) -> [http_range()] | parse_error. get_range(#req{headers = Headers}) -> case proplists:get_value(<<"range">>, Headers) of @@ -242,7 +242,7 @@ chunk_ref(#req{}) -> %% @doc Explicitly close the chunked connection. %% Return `{error, closed}' if the client already closed the connection. -%% @equiv send_chunk(Ref, close) +%% The same as `send_chunk(Ref, close)' close_chunk(Ref) -> send_chunk(Ref, close). diff --git a/src/elli_sendfile.erl b/src/elli_sendfile.erl index 5a11a67..6020170 100644 --- a/src/elli_sendfile.erl +++ b/src/elli_sendfile.erl @@ -10,10 +10,8 @@ %% @doc Send part of a file on a socket. %% -%% Basically, @see file:sendfile/5 but for ssl (i.e. not raw OS sockets). +%% Basically, see `file:sendfile/5' but for ssl (i.e. not raw OS sockets). %% Originally from https://github.com/ninenines/ranch/pull/41/files -%% -%% @end -spec sendfile(file:fd(), inet:socket() | ssl:sslsocket(), non_neg_integer(), non_neg_integer(), sendfile_opts()) -> {ok, non_neg_integer()} | {error, atom()}. diff --git a/src/elli_util.erl b/src/elli_util.erl index b8aa5cf..5ab03ce 100644 --- a/src/elli_util.erl +++ b/src/elli_util.erl @@ -13,14 +13,14 @@ -type range() :: {Offset::non_neg_integer(), Length::non_neg_integer()}. +%% @doc If a valid byte-range, or byte-range-set of size 1, +%% is supplied, returns a normalized range in the format +%% `{Offset, Length}'. Returns `undefined' when an empty byte-range-set +%% is supplied and the atom `invalid_range' in all other cases. -spec normalize_range(RangeOrSet, Size) -> Normalized when RangeOrSet :: any(), Size :: integer(), Normalized :: range() | undefined | invalid_range. -%% @doc: If a valid byte-range, or byte-range-set of size 1 -%% is supplied, returns a normalized range in the format -%% {Offset, Length}. Returns undefined when an empty byte-range-set -%% is supplied and the atom `invalid_range' in all other cases. normalize_range({suffix, Length}, Size) when is_integer(Length), Length > 0 -> Length0 = erlang:min(Length, Size), @@ -42,9 +42,9 @@ normalize_range([], _Size) -> undefined; normalize_range(_, _Size) -> invalid_range. +%% @doc Encode `Range' to a `Content-Range' value. -spec encode_range(Range::range() | invalid_range, Size::non_neg_integer()) -> ByteRange::iolist(). -%% @doc: Encode Range to a Content-Range value. encode_range(Range, Size) -> [<<"bytes ">>, encode_range_bytes(Range), <<"/">>, integer_to_binary(Size)]. @@ -56,11 +56,11 @@ encode_range_bytes({Offset, Length}) -> encode_range_bytes(invalid_range) -> <<"*">>. +%% @doc Get the size, in bytes, of the file. -spec file_size(Filename) -> Size | {error, Reason} when Filename :: file:name_all(), Size :: non_neg_integer(), Reason :: file:posix() | badarg | invalid_file. -%% @doc: Get the size in bytes of the file. file_size(Filename) -> case file:read_file_info(Filename) of {ok, #file_info{type = regular, access = Perm, size = Size}}