From 6b0a4d161e624dee5b6d5c24373f16784408f034 Mon Sep 17 00:00:00 2001 From: "Paulo F. Oliveira" Date: Thu, 27 Jul 2023 17:01:22 +0100 Subject: [PATCH] Move overview to its own .md (and reference it from README.md) --- OVERVIEW.md | 167 +++++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 92 ++++++++++++++++------------ rebar.config | 5 +- 3 files changed, 222 insertions(+), 42 deletions(-) create mode 100644 OVERVIEW.md diff --git a/OVERVIEW.md b/OVERVIEW.md new file mode 100644 index 0000000..99a88d2 --- /dev/null +++ b/OVERVIEW.md @@ -0,0 +1,167 @@ +# Elli - Overview + +Copyright (c) 2012-2016 Knut Nesheim, 2016-2018 elli-lib team + +__Version:__ 3.3.0 + +__Authors:__ Knut Nesheim, elli-lib team. + +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 [`elli_example_callback`](elli_example_callback.html) +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.html). + +## Extensions + +Here's some ready-to-use extensions for Elli. + +* [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 + +You can also find a more complete list at . + +## 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/README.md b/README.md index 4f0a047..46cc704 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# elli - Erlang web server for HTTP APIs +# Elli - Erlang web server for HTTP APIs [![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)](doc/README.md) @@ -8,30 +8,27 @@ [![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. +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 20.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: ```console rebar3 compile @@ -39,6 +36,8 @@ rebar3 compile ## Usage +To boot Elli inside an Erlang shell, run: + ```console rebar3 shell ``` @@ -53,67 +52,80 @@ rebar3 shell ### Callback Module The best source to learn how to write a callback module -is [`elli_example_callback.erl`](elli_example_callback.html). 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/rebar.config b/rebar.config index 0335b12..67827cc 100644 --- a/rebar.config +++ b/rebar.config @@ -21,9 +21,10 @@ ]}. {ex_doc, [ {extras, [ + "README.md", + "OVERVIEW.md", "CHANGELOG.md", - "LICENSE", - "README.md" + "LICENSE" ]}, {main, "README.md"}, {source_url, "https://github.com/elli-lib/elli"}