DEV Community

Victor Basumatary
Victor Basumatary

Posted on

HTTP Server With Erlang & Cowboy

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
Enter fullscreen mode Exit fullscreen mode

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]}
]}.
Enter fullscreen mode Exit fullscreen mode

Dependencies aren't automatically installed so you will need to run the following:

$ rebar3 compile
Enter fullscreen mode Exit fullscreen mode

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.
Enter fullscreen mode Exit fullscreen mode

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.

-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}.
Enter fullscreen mode Exit fullscreen mode

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.

$ rebar3 compile
$ rebar3 shell
Enter fullscreen mode Exit fullscreen mode

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]}}}}}
Enter fullscreen mode Exit fullscreen mode

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, []}
 ]}.
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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.

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

Top comments (0)