HTTP Server With Erlang & Cowboy
Tags: erlang, cowboy, rebar3
Published: 2023-08-18 02:27:03

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](https://rebar3.org/docs/getting-started/) (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.

```console
$ 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.

```erlang
{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:

```console
$ 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.

```erlang
-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:

1. Defining a list of routes in the `Routes` variable

    a. We have created only one route here.

    b. Whenever the user sends a request to `"/"` the `hello_world_h` module will be used to handle that request.

2. Compiling the routes.

3. 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.

```erlang
-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.

1. Defining the HTTP status code to be `200`.

    a. A status code of `200` means that the request was processed successfully.

2. Setting the `Content-Type` HTTP header to `text/html`.

    a. This is to tell the client that the body of the HTTP response will be an HTML document.

3. Creating the body of the HTTP response as a binary.

4. Creating a reply for the request.

5. Returning that reply.

## Let's start our server!

Ok our application is ready! Let's run it.

```console
$ rebar3 compile
$ rebar3 shell
```

At this point you should be greeted with a long error message. Quite disappointing.

```erlang
{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.

```erlang
{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.

```console
$ rebar3 compile
$ rebar3 shell
```

You shouldn't see any error messages anymore. Head over to `http://localhost:8080` in your browser.


![A screenshot of a webpage that says "Hello from cowboy!" under the URL http://localhost:8080. This is the expected output when we visit our cowboy HTTP server.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/f0cjoylzyt6wu2azx0qe.png)

Congratulations! You have created your first HTTP server in Erlang using cowboy!