I am learning Erlang and wanted to build an HTTP server. I read about the in-built inets
application and the httpd
service and upon using them I quickly realized that they weren't what I wanted.
I want to be able to handle requests myself. The httpd
service is great if you want to simply respond to requests with static files but I wanted to respond dynamically. That's when I read about cowboy.
There are a lot of moving pieces and as a beginner I got stuck for a while but I finally was able to create an HTTP server using cowboy. Here's how I did it.
Getting started
Install rebar3 (if you haven't already done so) and create a new erlang application. Let's call it cowboy_hello_world
.
rebar3 is a build tool and a package management tool for Erlang.
$ rebar3 new app cowboy_hello_world
$ cd cowboy_hello_world
You will see that there is a rebar.config
file and three files under the src/
directory. We will attend to these now.
Before we can use cowboy we need to specify it as a dependency. Add it to your rebar.config
file. It should look like this.
{erl_opts, [debug_info]}.
{deps, [
{cowboy, "2.10.0"}
]}.
{shell, [
% {config, "config/sys.config"},
{apps, [cowboy_hello_world]}
]}.
Dependencies aren't automatically installed so you will need to run the following:
$ rebar3 compile
The command above also compiles and builds your code.
Defining the server
Now at this point we can use cowboy and make our own HTTP server. Let's open the src/cowboy_hello_world_app.erl
file and start the HTTP server.
-module(cowboy_hello_world_app).
-behaviour(application).
-export([start/2, stop/1]).
start(_StartType, _StartArgs) ->
Routes = [
{"/", hello_world_h, []}
],
ServerConfig = [
{port, 8080}
],
Dispatch = cowboy_router:compile([
{'_', Routes}
]),
{ok, _} = cowboy:start_clear(httpd, ServerConfig, #{
env => #{dispatch => Dispatch}
}),
cowboy_hello_world_sup:start_link().
stop(_State) ->
ok.
This is what we are doing:
-
Defining a list of routes in the
Routes
variablea. We have created only one route here.
b. Whenever the user sends a request to
"/"
thehello_world_h
module will be used to handle that request. Compiling the routes.
-
Starting a cowboy HTTP server with some server configuration and the routes that we compiled.
a. We have configured our server to use port
8080
.b. Therefore the server will be listening at
http://localhost:8080
.
Since in the routes definition we have mentioned the hello_world_h
module we need to create it. Create a new file src/hello_world_h
and fill it up as such.
-module(hello_world_h).
-export([init/2]).
init(Request, Opts) ->
StatusCode = 200,
Headers = #{
<<"content-type">> => <<"text/html">>
},
Body = <<"<h1>Hello from cowboy!</h1>">>,
Response = cowboy_req:reply(StatusCode, Headers, Body, Request),
{ok, Response, Opts}.
Cowboy will automatically call the init/2
function with the request and expect a response. Here's what we are doing.
-
Defining the HTTP status code to be
200
.a. A status code of
200
means that the request was processed successfully. -
Setting the
Content-Type
HTTP header totext/html
.a. This is to tell the client that the body of the HTTP response will be an HTML document.
Creating the body of the HTTP response as a binary.
Creating a reply for the request.
Returning that reply.
Let's start our server!
Ok our application is ready! Let's run it.
$ rebar3 compile
$ rebar3 shell
At this point you should be greeted with a long error message. Quite disappointing.
{bad_return,
{{cowboy_hello_world_app,start,[normal,[]]},
{'EXIT',
{noproc,
{gen_server,call,
[ranch_sup,
{start_child,
{{ranch_listener_sup,httpd},
{ranch_listener_sup,start_link,
[httpd,ranch_tcp,
#{socket_opts => [{port,8080}],
connection_type => supervisor},
cowboy_clear,
#{env =>
#{dispatch =>
[{'_',[],[{[],[],hello_world_h,[]}]}]},
connection_type => supervisor}]},
permanent,infinity,supervisor,
[ranch_listener_sup]}},
infinity]}}}}}
What does this error message mean? Notice that towards the end of the error message it mentions a ranch_listener_sup
. Cowboy depends on the ranch application and ranch must be started before cowboy. To do this we need to make a small change to our src/cowboy_hello_world.app.src
file.
{application, cowboy_hello_world,
[{description, "An OTP application"},
{vsn, "0.1.0"},
{registered, []},
{mod, {cowboy_hello_world_app, []}},
{applications,
[kernel,
stdlib,
ranch
]},
{env,[]},
{modules, []},
{licenses, ["Apache-2.0"]},
{links, []}
]}.
Notice that we have added ranch
as an application after kernel
and stdlib
.
Let's compile and run our application again.
$ rebar3 compile
$ rebar3 shell
You shouldn't see any error messages anymore. Head over to http://localhost:8080
in your browser.
Congratulations! You have created your first HTTP server in Erlang using cowboy!
Top comments (0)