From 579806f367be5aa1f7aef5a0220e024785051871 Mon Sep 17 00:00:00 2001 From: Matt Branton Date: Wed, 21 Oct 2015 23:41:39 -0400 Subject: [PATCH] Erlang web server support --- .gitignore | 2 ++ README.md | 10 +++++++ rebar.config | 7 +++++ src/comment_handler.erl | 64 +++++++++++++++++++++++++++++++++++++++++ src/server.app.src | 14 +++++++++ src/server_app.erl | 41 ++++++++++++++++++++++++++ src/server_sup.erl | 21 ++++++++++++++ 7 files changed, 159 insertions(+) create mode 100644 rebar.config create mode 100644 src/comment_handler.erl create mode 100644 src/server.app.src create mode 100644 src/server_app.erl create mode 100644 src/server_sup.erl diff --git a/.gitignore b/.gitignore index daeba5f9..35c90543 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ *~ node_modules .DS_Store +deps +.rebar diff --git a/README.md b/README.md index f72917ef..870d852a 100644 --- a/README.md +++ b/README.md @@ -52,6 +52,16 @@ go run server.go go get github.com/xyproto/algernon # or brew install algernon algernon server.lua + +``` + +### Erlang + +```sh +rebar get-deps +rebar compile +rebar shell +application:ensure_all_started(server). ``` And visit . Try opening multiple tabs! diff --git a/rebar.config b/rebar.config new file mode 100644 index 00000000..3a9531d4 --- /dev/null +++ b/rebar.config @@ -0,0 +1,7 @@ +{sub_dirs, ["apps/", "rel"]}. + +{deps_dir, ["deps"]}. +{erl_opts, [debug_info, fail_on_warning]}. + +{deps, [{cowboy, "1.0.3", {git, "https://github.com/ninenines/cowboy.git", {tag, "1.0.3"}}}, + {jiffy, ".*", {git, "git://github.com/davisp/jiffy.git"}}]}. diff --git a/src/comment_handler.erl b/src/comment_handler.erl new file mode 100644 index 00000000..11f73ed9 --- /dev/null +++ b/src/comment_handler.erl @@ -0,0 +1,64 @@ +%% This file provided by Facebook is for non-commercial testing and evaluation +%% purposes only. Facebook reserves all rights not expressly granted. +%% +%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +%% FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +%% ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +%% WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +%% + +%% @doc POST echo handler. +-module(comment_handler). + +-define(FilePath, "./comments.json"). + +-export([init/3]). +-export([handle/2]). +-export([terminate/3]). + +init(_Transport, Req, []) -> + {ok, Req, undefined}. + +handle(Req, State) -> + {Method, Req2} = cowboy_req:method(Req), + HasBody = cowboy_req:has_body(Req2), + {ok, Req3} = maybe_comment(Method, HasBody, Req2), + {ok, Req3, State}. + +maybe_comment(<<"POST">>, true, Req) -> + {ok, PostVals, Req2} = cowboy_req:body_qs(Req), + comment(write_comments(read_comments(), PostVals), Req2); +maybe_comment(<<"POST">>, false, Req) -> + cowboy_req:reply(400, [], <<"Missing body.">>, Req); +maybe_comment(<<"GET">>, _, Req) -> + comment(read_raw_comments(), Req); +maybe_comment(_, _, Req) -> + %% Method not allowed. + cowboy_req:reply(405, Req). + +comment(Comments, Req) -> + %% always return json + cowboy_req:reply(200, [ + {<<"content-type">>, <<"application/json; charset=utf-8">>}, + {<<"cache-control">>, <<"no-cache">>} + ], Comments, Req). + +terminate(_Reason, _Req, _State) -> + ok. + +%% Private functions + +read_raw_comments() -> + {ok, F} = file:read_file(?FilePath), + F. + +read_comments() -> + jiffy:decode(read_raw_comments()). + +write_comments(Comments, NextComment) -> + C = jiffy:encode(Comments ++ [{NextComment}], + [force_utf8, pretty]), + ok = file:write_file(?FilePath, C), + C. diff --git a/src/server.app.src b/src/server.app.src new file mode 100644 index 00000000..8d5ab54f --- /dev/null +++ b/src/server.app.src @@ -0,0 +1,14 @@ +{application, server, + [ + {description, "React tutorial server"}, + {vsn, "1"}, + {registered, [server]}, + {applications, [ + kernel, + stdlib, + crypto, + cowboy, + jiffy]}, + {mod, { server_app, []}}, + {env, []} + ]}. diff --git a/src/server_app.erl b/src/server_app.erl new file mode 100644 index 00000000..5dfdc225 --- /dev/null +++ b/src/server_app.erl @@ -0,0 +1,41 @@ +%% This file provided by Facebook is for non-commercial testing and evaluation +%% purposes only. Facebook reserves all rights not expressly granted. +%% +%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +%% FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +%% ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +%% WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +%% +%% +%% @private +-module(server_app). +-behaviour(application). + +-export([start/2]). +-export([stop/1]). + +start(_Type, _Args) -> + Port = default_port(os:getenv("PORT")), + Dispatch = cowboy_router:compile([ + {'_', [ + {"/api/comments", comment_handler, []}, + {"/", cowboy_static, {file, "./public/index.html"}}, + {"/[...]", cowboy_static, {dir, "./public", + [{mimetypes, cow_mimetypes, all}]}} + ]} + ]), + {ok, _} = cowboy:start_http(http, 10, [{port, Port}], [ + {env, [{dispatch, Dispatch}]} + ]), + server_sup:start_link(). + +stop(_State) -> + ok. + + +%% Utility + +default_port(false) -> 3000; +default_port(N) -> N. diff --git a/src/server_sup.erl b/src/server_sup.erl new file mode 100644 index 00000000..b0850a83 --- /dev/null +++ b/src/server_sup.erl @@ -0,0 +1,21 @@ +%% @private +-module(server_sup). +-behaviour(supervisor). + +%% API. +-export([start_link/0]). + +%% supervisor. +-export([init/1]). + +%% API. + +-spec start_link() -> {ok, pid()}. +start_link() -> + supervisor:start_link({local, ?MODULE}, ?MODULE, []). + +%% supervisor. + +init([]) -> + Procs = [], + {ok, {{one_for_one, 10, 10}, Procs}}.