mirror of
https://github.com/ninenines/cowboy.git
synced 2025-07-14 12:20:24 +00:00
Update the Getting started chapter to new erlang.mk
Length of the chapter divided by 2! \o/
This commit is contained in:
parent
62de3a62f9
commit
8d436c4744
1 changed files with 126 additions and 263 deletions
|
@ -12,295 +12,158 @@ Cowboy, writing your first application and generating your first
|
||||||
release. At the end of this chapter you should know everything
|
release. At the end of this chapter you should know everything
|
||||||
you need to push your first Cowboy application to production.
|
you need to push your first Cowboy application to production.
|
||||||
|
|
||||||
:: Application skeleton
|
:: Bootstrap
|
||||||
|
|
||||||
Let's start by creating this application. We will simply call it
|
We are going to use the ^"erlang.mk^https://github.com/extend/erlang.mk
|
||||||
`hello_erlang`. This application will have the following directory
|
build system. It also offers bootstrap features allowing us to
|
||||||
structure:
|
quickly get started without having to deal with minute details.
|
||||||
|
|
||||||
``` bash
|
First, let's create the directory for our application.
|
||||||
hello_erlang/
|
|
||||||
src/
|
|
||||||
hello_erlang.app.src
|
|
||||||
hello_erlang_app.erl
|
|
||||||
hello_erlang_sup.erl
|
|
||||||
hello_handler.erl
|
|
||||||
erlang.mk
|
|
||||||
Makefile
|
|
||||||
relx.config
|
|
||||||
```
|
|
||||||
|
|
||||||
Once the release is generated, we will also have the following
|
|
||||||
files added:
|
|
||||||
|
|
||||||
``` bash
|
|
||||||
hello_erlang/
|
|
||||||
ebin/
|
|
||||||
hello_erlang.app
|
|
||||||
hello_erlang_app.beam
|
|
||||||
hello_erlang_sup.beam
|
|
||||||
hello_handler.beam
|
|
||||||
_rel/
|
|
||||||
relx
|
|
||||||
```
|
|
||||||
|
|
||||||
As you can probably guess, the `.app.src` file end up becoming
|
|
||||||
the `.app` file, and the `.erl` files are compiled into `.beam`.
|
|
||||||
Then, the whole release will be copied into the `_rel/` directory.
|
|
||||||
|
|
||||||
The `.app` file contains various informations about the application.
|
|
||||||
It contains its name, a description, a version, a list of modules,
|
|
||||||
default configuration and more.
|
|
||||||
|
|
||||||
Using a build system like ^"erlang.mk^https://github.com/extend/erlang.mk^,
|
|
||||||
the list of modules will be included automatically in the `.app` file,
|
|
||||||
so you don't need to manually put them in your `.app.src` file.
|
|
||||||
|
|
||||||
For generating the release, we will use ^"relx^https://github.com/erlware/relx
|
|
||||||
as it is a much simpler alternative to the tool coming with Erlang.
|
|
||||||
|
|
||||||
First, create the `hello_erlang` directory. It should have the same name
|
|
||||||
as the application within it. Then we create the `src` directory inside
|
|
||||||
it, which will contain the source code for our application.
|
|
||||||
|
|
||||||
``` bash
|
``` bash
|
||||||
$ mkdir hello_erlang
|
$ mkdir hello_erlang
|
||||||
$ cd hello_erlang
|
$ cd hello_erlang
|
||||||
$ mkdir src
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Let's first create the `hello_erlang.app.src` file. It should be pretty
|
Then we need to download `erlang.mk`. Either use the following
|
||||||
straightforward for the most part. You can use the following template
|
command or download it manually.
|
||||||
and change what you like in it.
|
|
||||||
|
|
||||||
``` erlang
|
|
||||||
{application, hello_erlang, [
|
|
||||||
{description, "Hello world with Cowboy!"},
|
|
||||||
{vsn, "0.1.0"},
|
|
||||||
{modules, []},
|
|
||||||
{registered, [hello_erlang_sup]},
|
|
||||||
{applications, [
|
|
||||||
kernel,
|
|
||||||
stdlib,
|
|
||||||
cowboy
|
|
||||||
]},
|
|
||||||
{mod, {hello_erlang_app, []}},
|
|
||||||
{env, []}
|
|
||||||
]}.
|
|
||||||
```
|
|
||||||
|
|
||||||
The `modules` line will be replaced with the list of modules during
|
|
||||||
compilation. Make sure to leave this line even if you do not use it
|
|
||||||
directly.
|
|
||||||
|
|
||||||
The `registered` value indicates which processes are registered by this
|
|
||||||
application. You will often only register the top-level supervisor
|
|
||||||
of the application.
|
|
||||||
|
|
||||||
The `applications` value lists the applications that must be started
|
|
||||||
for this application to work. The Erlang release will start all the
|
|
||||||
applications listed here automatically.
|
|
||||||
|
|
||||||
The `mod` value defines how the application should be started. Erlang
|
|
||||||
will use the `hello_erlang_app` module for starting the application.
|
|
||||||
|
|
||||||
The `hello_erlang_app` module is what we call an application behavior.
|
|
||||||
The application behavior must define two functions: `start/2` and
|
|
||||||
`stop/1`, for starting and stopping the application. A typical
|
|
||||||
application module would look like this:
|
|
||||||
|
|
||||||
``` erlang
|
|
||||||
-module(hello_erlang_app).
|
|
||||||
-behavior(application).
|
|
||||||
|
|
||||||
-export([start/2]).
|
|
||||||
-export([stop/1]).
|
|
||||||
|
|
||||||
start(_Type, _Args) ->
|
|
||||||
hello_erlang_sup:start_link().
|
|
||||||
|
|
||||||
stop(_State) ->
|
|
||||||
ok.
|
|
||||||
```
|
|
||||||
|
|
||||||
That's not enough however. Since we are building a Cowboy based
|
|
||||||
application, we also need to initialize Cowboy when we start our
|
|
||||||
application.
|
|
||||||
|
|
||||||
:: Setting up Cowboy
|
|
||||||
|
|
||||||
Cowboy does nothing by default.
|
|
||||||
|
|
||||||
Cowboy uses Ranch for handling the connections and provides convenience
|
|
||||||
functions to start Ranch listeners.
|
|
||||||
|
|
||||||
The `cowboy:start_http/4` function starts a listener for HTTP connections
|
|
||||||
using the TCP transport. The `cowboy:start_https/4` function starts a
|
|
||||||
listener for HTTPS connections using the SSL transport.
|
|
||||||
|
|
||||||
Listeners are a group of processes that are used to accept and manage
|
|
||||||
connections. The processes used specifically for accepting connections
|
|
||||||
are called acceptors. The number of acceptor processes is unrelated to
|
|
||||||
the maximum number of connections Cowboy can handle. Please refer to
|
|
||||||
the ^"Ranch guide^http://ninenines.eu/docs/en/ranch/HEAD/guide/
|
|
||||||
for in-depth information.
|
|
||||||
|
|
||||||
Listeners are named. They spawn a given number of acceptors, listen for
|
|
||||||
connections using the given transport options and pass along the protocol
|
|
||||||
options to the connection processes. The protocol options must include
|
|
||||||
the dispatch list for routing requests to handlers.
|
|
||||||
|
|
||||||
The dispatch list is explained in greater details in the
|
|
||||||
^"Routing^routing^ chapter. For the purpose of this example
|
|
||||||
we will simply map all URLs to our handler `hello_handler`,
|
|
||||||
using the wildcard `_` for both the hostname and path parts
|
|
||||||
of the URL.
|
|
||||||
|
|
||||||
This is what the `hello_erlang_app:start/2` function looks like
|
|
||||||
with Cowboy initialized.
|
|
||||||
|
|
||||||
``` erlang
|
|
||||||
start(_Type, _Args) ->
|
|
||||||
Dispatch = cowboy_router:compile([
|
|
||||||
%% {URIHost, list({URIPath, Handler, Opts})}
|
|
||||||
{'_', [{'_', hello_handler, []}]}
|
|
||||||
]),
|
|
||||||
%% Name, NbAcceptors, TransOpts, ProtoOpts
|
|
||||||
cowboy:start_http(my_http_listener, 100,
|
|
||||||
[{port, 8080}],
|
|
||||||
[{env, [{dispatch, Dispatch}]}]
|
|
||||||
),
|
|
||||||
hello_erlang_sup:start_link().
|
|
||||||
```
|
|
||||||
|
|
||||||
Do note that we told Cowboy to start listening on port 8080.
|
|
||||||
You can change this value if needed.
|
|
||||||
|
|
||||||
Our application doesn't need to start any process, as Cowboy
|
|
||||||
will automatically start processes for every incoming
|
|
||||||
connections. We are still required to have a top-level supervisor
|
|
||||||
however, albeit a fairly small one.
|
|
||||||
|
|
||||||
``` erlang
|
|
||||||
-module(hello_erlang_sup).
|
|
||||||
-behavior(supervisor).
|
|
||||||
|
|
||||||
-export([start_link/0]).
|
|
||||||
-export([init/1]).
|
|
||||||
|
|
||||||
start_link() ->
|
|
||||||
supervisor:start_link({local, ?MODULE}, ?MODULE, []).
|
|
||||||
|
|
||||||
init([]) ->
|
|
||||||
{ok, {{one_for_one, 10, 10}, []}}.
|
|
||||||
```
|
|
||||||
|
|
||||||
Finally, we need to write the code for handling incoming requests.
|
|
||||||
|
|
||||||
:: Handling HTTP requests
|
|
||||||
|
|
||||||
Cowboy features many kinds of handlers. For this simple example,
|
|
||||||
we will just use the plain HTTP handler, which has three callback
|
|
||||||
functions: `init/3`, `handle/2` and `terminate/3`. You can find more
|
|
||||||
information about the arguments and possible return values of these
|
|
||||||
callbacks in the
|
|
||||||
^"cowboy_http_handler function reference^http://ninenines.eu/docs/en/cowboy/HEAD/manual/cowboy_http_handler^.
|
|
||||||
|
|
||||||
Our handler will only send a friendly hello back to the client.
|
|
||||||
|
|
||||||
``` erlang
|
|
||||||
-module(hello_handler).
|
|
||||||
-behavior(cowboy_http_handler).
|
|
||||||
|
|
||||||
-export([init/3]).
|
|
||||||
-export([handle/2]).
|
|
||||||
-export([terminate/3]).
|
|
||||||
|
|
||||||
init(_Type, Req, _Opts) ->
|
|
||||||
{ok, Req, undefined_state}.
|
|
||||||
|
|
||||||
handle(Req, State) ->
|
|
||||||
{ok, Req2} = cowboy_req:reply(200, [
|
|
||||||
{<<"content-type">>, <<"text/plain">>}
|
|
||||||
], <<"Hello World!">>, Req),
|
|
||||||
{ok, Req2, State}.
|
|
||||||
|
|
||||||
terminate(_Reason, _Req, _State) ->
|
|
||||||
ok.
|
|
||||||
```
|
|
||||||
|
|
||||||
The `Req` variable above is the Req object, which allows the developer
|
|
||||||
to obtain information about the request and to perform a reply.
|
|
||||||
Its usage is documented in the
|
|
||||||
^"cowboy_req function reference^http://ninenines.eu/docs/en/cowboy/HEAD/manual/cowboy_req^.
|
|
||||||
|
|
||||||
The code for our application is ready, so let's build a release!
|
|
||||||
|
|
||||||
:: Compiling
|
|
||||||
|
|
||||||
First we need to download `erlang.mk`.
|
|
||||||
|
|
||||||
``` bash
|
``` bash
|
||||||
$ wget https://raw.github.com/extend/erlang.mk/master/erlang.mk
|
$ wget https://raw.githubusercontent.com/extend/erlang.mk/master/erlang.mk
|
||||||
$ ls
|
|
||||||
src/
|
|
||||||
erlang.mk
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Then we need to create a Makefile that will include `erlang.mk`
|
We can now bootstrap our application. Since we are going to generate
|
||||||
for building our application. We need to define the Cowboy
|
a release, we will also bootstrap it at the same time.
|
||||||
dependency in the Makefile. Thankfully `erlang.mk` already
|
|
||||||
knows where to find Cowboy as it features a package index,
|
``` bash
|
||||||
so we can just tell it to look there.
|
$ make -f erlang.mk bootstrap bootstrap-rel
|
||||||
|
```
|
||||||
|
|
||||||
|
This creates a Makefile, a base application, and the release files
|
||||||
|
necessary for creating the release. We can already build and start
|
||||||
|
this release.
|
||||||
|
|
||||||
|
``` bash
|
||||||
|
$ make
|
||||||
|
...
|
||||||
|
$ ./_rel/hello_erlang_release/bin/hello_erlang_release console
|
||||||
|
...
|
||||||
|
(hello_erlang@127.0.0.1)1>
|
||||||
|
```
|
||||||
|
|
||||||
|
Entering the command `i().` will show the running processes, including
|
||||||
|
one called `hello_erlang_sup`. This is the supervisor for our
|
||||||
|
application.
|
||||||
|
|
||||||
|
The release currently does nothing. In the rest of this chapter we
|
||||||
|
will add Cowboy as a dependency and write a simple "Hello world!"
|
||||||
|
handler.
|
||||||
|
|
||||||
|
:: Cowboy setup
|
||||||
|
|
||||||
|
To add Cowboy as a dependency of your application, we need to modify
|
||||||
|
two files: the Makefile and the application resource file.
|
||||||
|
|
||||||
|
Modifying the Makefile allows the build system to know it needs to
|
||||||
|
fetch and compile Cowboy. To do that we simply need to add one line
|
||||||
|
to our Makefile to make it look like this:
|
||||||
|
|
||||||
``` Makefile
|
``` Makefile
|
||||||
PROJECT = hello_erlang
|
PROJECT = hello_erlang
|
||||||
|
|
||||||
DEPS = cowboy
|
DEPS = cowboy
|
||||||
dep_cowboy = pkg://cowboy master
|
|
||||||
|
|
||||||
include erlang.mk
|
include erlang.mk
|
||||||
```
|
```
|
||||||
|
|
||||||
Note that when creating production nodes you will most likely
|
Modifying the application resource file, `src/hello_erlang.app.src`,
|
||||||
want to use a specific version of Cowboy instead of `master`,
|
allows the build system to know it needs to include Cowboy in the
|
||||||
and properly test your release every time you update Cowboy.
|
release and start it automatically. This is a different step because
|
||||||
|
some dependencies are only needed during development.
|
||||||
|
|
||||||
If you type `make` in a shell now, your application should build
|
We are simply going to add `cowboy` to the list of `applications`,
|
||||||
as expected. If you get compilation errors, double check that you
|
right after `stdlib`. Don't forget the comma separator.
|
||||||
haven't made any typo when creating the previous files.
|
|
||||||
|
|
||||||
``` bash
|
|
||||||
$ make
|
|
||||||
```
|
|
||||||
|
|
||||||
:: Generating the release
|
|
||||||
|
|
||||||
That's not all however, as we want to create a working release.
|
|
||||||
For that purpose, we need to create a `relx.config` file. When
|
|
||||||
this file exists, `erlang.mk` will automatically download `relx`
|
|
||||||
and build the release when you type `make`.
|
|
||||||
|
|
||||||
In the `relx.config` file, we only need to tell `relx` that
|
|
||||||
we want the release to include the `hello_erlang` application,
|
|
||||||
and that we want an extended start script for convenience.
|
|
||||||
`relx` will figure out which other applications are required
|
|
||||||
by looking into the `.app` files for dependencies.
|
|
||||||
|
|
||||||
``` erlang
|
``` erlang
|
||||||
{release, {hello_erlang, "1"}, [hello_erlang]}.
|
{application, hello_erlang, [
|
||||||
{extended_start_script, true}.
|
{description, "Hello Erlang!"},
|
||||||
|
{vsn, "0.1.0"},
|
||||||
|
{modules, []},
|
||||||
|
{registered, []},
|
||||||
|
{applications, [
|
||||||
|
kernel,
|
||||||
|
stdlib,
|
||||||
|
cowboy
|
||||||
|
]},
|
||||||
|
{mod, {hello_erlang_app, []}},
|
||||||
|
{env, []}
|
||||||
|
]}.
|
||||||
```
|
```
|
||||||
|
|
||||||
The `release` value is used to specify the release name, its
|
You may want to set a description for the application while you
|
||||||
version, and the applications to be included.
|
are editing the file.
|
||||||
|
|
||||||
We can now build and start the release.
|
If you run `make` now and start the release, Cowboy will be included
|
||||||
|
and started automatically. This is not enough however, as Cowboy
|
||||||
|
doesn't do anything by default. We still need to tell Cowboy to
|
||||||
|
listen for connections.
|
||||||
|
|
||||||
|
:: Listening for connections
|
||||||
|
|
||||||
|
We will do this when our application starts. It's a two step process.
|
||||||
|
First we need to define and compile the dispatch list, a list of
|
||||||
|
routes that Cowboy will use to map requests to handler modules.
|
||||||
|
Then we tell Cowboy to listen for connections.
|
||||||
|
|
||||||
|
Open the `src/hello_erlang_app.erl` file and add the necessary
|
||||||
|
code to the `start/2` function to make it look like this:
|
||||||
|
|
||||||
|
``` erlang
|
||||||
|
start(_Type, _Args) ->
|
||||||
|
Dispatch = cowboy_router:compile([
|
||||||
|
{'_', [{"/", hello_handler, []}]}
|
||||||
|
]),
|
||||||
|
cowboy:start_http(my_http_listener, 100, [{port, 8080}],
|
||||||
|
[{env, [{dispatch, Dispatch}]}]
|
||||||
|
),
|
||||||
|
hello_erlang_sup:start_link().
|
||||||
|
```
|
||||||
|
|
||||||
|
The dispatch list is explained in great details in the
|
||||||
|
^"Routing^routing^ chapter. For this tutorial we map the
|
||||||
|
path `/` to the handler module `hello_handler`. This module
|
||||||
|
doesn't exist yet, we still have to write it.
|
||||||
|
|
||||||
|
If you build the release, start it and open ^http://localhost:8080
|
||||||
|
now, you will get an error because the module is missing. Any
|
||||||
|
other URL, like ^http://localhost:8080/test^, will result in a
|
||||||
|
404 error.
|
||||||
|
|
||||||
|
:: Handling requests
|
||||||
|
|
||||||
|
Cowboy features different kinds of handlers, including REST
|
||||||
|
and Websocket handlers. For this tutorial we will use a plain
|
||||||
|
HTTP handler.
|
||||||
|
|
||||||
|
First, let's generate a handler from a template.
|
||||||
|
|
||||||
``` bash
|
``` bash
|
||||||
$ make
|
$ make new t=cowboy_http n=hello_handler
|
||||||
$ ./_rel/hello_erlang/bin/hello_erlang console
|
|
||||||
```
|
```
|
||||||
|
|
||||||
If you then access `http://localhost:8080` using your browser,
|
You can then open the `src/hello_handler.erl` file and modify
|
||||||
you should receive a nice greet!
|
the `handle/2` function like this to send a reply.
|
||||||
|
|
||||||
|
``` erlang
|
||||||
|
handle(Req, State=#state{}) ->
|
||||||
|
{ok, Req2} = cowboy_req:reply(200,
|
||||||
|
[{<<"content-type">>, <<"text/plain">>}],
|
||||||
|
<<"Hello Erlang!">>,
|
||||||
|
Req),
|
||||||
|
{ok, Req2, State}.
|
||||||
|
```
|
||||||
|
|
||||||
|
What the above code does is send a `200 OK` reply, with the
|
||||||
|
`content-type` header set to `text/plain` and the response
|
||||||
|
body set to `Hello Erlang!`.
|
||||||
|
|
||||||
|
If you build the release, start it and open ^http://localhost:8080
|
||||||
|
in your browser, you should get a nice `Hello Erlang!` displayed!
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue