Skip to content

Commit

Permalink
implement basic HTTP API
Browse files Browse the repository at this point in the history
  • Loading branch information
marianoguerra committed Nov 26, 2017
1 parent da55d0d commit c6a8c4a
Show file tree
Hide file tree
Showing 6 changed files with 184 additions and 4 deletions.
123 changes: 123 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -471,3 +471,126 @@ since the API doesn't know a thing about the way we register/lookup namespaces
we can explore different alternatives later.

You can view the source code for akvs module here: `akvs <http://akvs>`_ and the tests here `akvs_SUITE <http://akvs_SUITE>`_.

An HTTP API for our key value stores
------------------------------------

We are at the point where we can expose our APIs to the world, we are going to
do it by exposing a really basic HTTP API for it.

The API will look like this:

.. code:: http
# set key in namespace to the binary value sent in body
# return status: 201
POST /kv/<namespace>/<key>
<body>
# get key in namespace
# return status:
# 200: if found
# 404: if not found
GET /kv/<namespace>/<key>
# delete key from namespace
# return status: 200
DELETE /kv/<namespace>/<key>
To create an HTTP API we need an HTTP server, in this case we will use `Cowboy 2 <https://ninenines.eu/docs/en/cowboy/2.1/guide/>`_.

First we need to add it as a dependency in our rebar.config file in the deps
section and in the release dependencies section.

Then we need to setup the routes in our application initialization code.

We are going to have only one route and handler, we are going to use a basic
HTTP to keep it simple, you can read the handler's code here: `akvs_h_kv <http://todo>`_.

Now we can test it by building a release, starting it and playing with the API using curl:

.. code:: sh
rebar3 release
_build/default/rel/akvs/bin/akvs console
In another shell:

.. code:: sh
curl http://localhost:8080/kv/foo/bar
Not Found
curl -X POST http://localhost:8080/kv/foo/bar -d "hello world"
Created
curl http://localhost:8080/kv/foo/bar
hello world
curl -X DELETE http://localhost:8080/kv/foo/bar
OK
curl http://localhost:8080/kv/foo/bar
Not Found
curl -X PUT http://localhost:8080/kv/foo/bar -d "hello world"
Method Not Allowed
Seems to work fine.

Now we can build a production release and try it:

.. code:: sh
rebar3 as prod release
cd _build/prod/rel
tar -czf akvs.tar.gz akvs
cd -
mv _build/prod/rel/akvs.tar.gz /tmp
cd /tmp
tar -xzf akvs.tar.gz
cd akvs
./bin/akvs start
The application is started, you can check it's running by pinging it:

.. code:: sh
./bin/akvs ping
In case you need, you can attach to it (you should exit with Ctrl+D, using q()
won't only detach your console but also stop the system!):

.. code:: sh
./bin/akvs attach
You can try it again:

.. code:: sh
curl http://localhost:8080/kv/foo/bar
curl -X POST http://localhost:8080/kv/foo/bar -d "hello world"
curl http://localhost:8080/kv/foo/bar
curl -X DELETE http://localhost:8080/kv/foo/bar
curl http://localhost:8080/kv/foo/bar
curl -X PUT http://localhost:8080/kv/foo/bar -d "hello world"
When you are finished, you can stop it:

.. code:: sh
./bin/akvs stop
Now you can upload akvs.tar.gz to any bare server and start akvs there, as long
as the operating system is similar (better if the same) as the one where you
built the release, this is because when building the release we bundle the
erlang runtime for simplicity, this assumes specific versions of libraries like
libssl which may not be available on the target system if it's too different.

Another way is to build the release without bundling the erlang runtime and
having it available on the target system, just make sure that the erlang
runtime in the target system has the same version you used to build it,
otherwise you may experience errors due to modules/functions not being
available or bytecode incompatibility if the target runtime is older than the
one used for the release.
1 change: 1 addition & 0 deletions apps/akvs/src/akvs.app.src
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
{mod, { akvs_app, []}},
{applications,
[kernel,
cowboy,
stdlib
]},
{env,[]},
Expand Down
12 changes: 12 additions & 0 deletions apps/akvs/src/akvs_app.erl
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

start(_StartType, _StartArgs) ->
akvs:init(),
start_http_api(),
akvs_sup:start_link().

%%--------------------------------------------------------------------
Expand All @@ -25,3 +26,14 @@ stop(_State) ->
%%====================================================================
%% Internal functions
%%====================================================================

start_http_api() ->
Dispatch = cowboy_router:compile([{'_',
[{"/kv/:namespace/:key", akvs_h_kv, []}]}]),
HttpPort = 8080,
HttpAcceptors = 100,

{ok, _} = cowboy:start_clear(akvs_http_listener,
[{port, HttpPort}, {num_acceptors, HttpAcceptors}],
#{env => #{dispatch => Dispatch}}),
ok.
36 changes: 36 additions & 0 deletions apps/akvs/src/akvs_h_kv.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
-module(akvs_h_kv).
-export([init/2]).

init(ReqIn=#{method := <<"POST">>}, State) ->
{Ns, Key} = get_url_vars(ReqIn),
{ok, Body, Req1} = cowboy_req:read_body(ReqIn),
ok = akvs:set(Ns, Key, Body),
ReqOut = cowboy_req:reply(201, #{}, <<"Created">>, Req1),
{ok, ReqOut, State};

init(ReqIn=#{method := <<"GET">>}, State) ->
{Ns, Key} = get_url_vars(ReqIn),
ReqOut = case akvs:get(Ns, Key) of
{ok, Value} ->
cowboy_req:reply(200, #{}, Value, ReqIn);
{error, {not_found, _, _}} ->
cowboy_req:reply(404, #{}, <<"Not Found">>, ReqIn)
end,
{ok, ReqOut, State};

init(ReqIn=#{method := <<"DELETE">>}, State) ->
{Ns, Key} = get_url_vars(ReqIn),
ok = akvs:del(Ns, Key),
ReqOut = cowboy_req:reply(200, #{}, <<"OK">>, ReqIn),
{ok, ReqOut, State};

init(ReqIn, State) ->
ReqOut = cowboy_req:reply(405, #{}, <<"Method Not Allowed">>, ReqIn),
{ok, ReqOut, State}.

%% Private functions

get_url_vars(Req) ->
Ns = cowboy_req:binding(namespace, Req),
Key = cowboy_req:binding(key, Req),
{Ns, Key}.
5 changes: 2 additions & 3 deletions rebar.config
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
{erl_opts, [debug_info]}.
{deps, []}.
{deps, [{cowboy, "2.1.0"}]}.

{relx, [{release, { akvs, "0.1.0" },
[akvs,
sasl]},
[akvs, cowboy, sasl]},

{sys_config, "./config/sys.config"},
{vm_args, "./config/vm.args"},
Expand Down
11 changes: 10 additions & 1 deletion rebar.lock
Original file line number Diff line number Diff line change
@@ -1 +1,10 @@
[].
{"1.1.0",
[{<<"cowboy">>,{pkg,<<"cowboy">>,<<"2.1.0">>},0},
{<<"cowlib">>,{pkg,<<"cowlib">>,<<"2.0.1">>},1},
{<<"ranch">>,{pkg,<<"ranch">>,<<"1.4.0">>},1}]}.
[
{pkg_hash,[
{<<"cowboy">>, <<"69F9DB3B23C24AB6B3A169A6357130C16B39CDA1A1F8C582F818883EE552589D">>},
{<<"cowlib">>, <<"4DFFFB1DB296EAB9F2E8B95EE3017007F674BC920CE30AEB5A53BBDA82FC38C0">>},
{<<"ranch">>, <<"10272F95DA79340FA7E8774BA7930B901713D272905D0012B06CA6D994F8826B">>}]}
].

0 comments on commit c6a8c4a

Please sign in to comment.