blob: a9a4ec083732cbc03ca7b454f0b3ee34f9063085 [file] [log] [blame]
%% Trivial web storage app. It's available over both HTTP (port 8442)
%% and HTTPS (port 8443). You use a PUT to store items, a GET to
%% retrieve them and DELETE to delete them. The HTTP POST method is
%% invalid for this application. Example (using HTTPS transport):
%%
%% $ curl -k --verbose https://localhost:8443/flintstones
%% ...
%% 404 Not Found
%% ...
%% $ echo -e "Fred\nWilma\nBarney" |
%% curl -k --verbose https://localhost:8443/flintstones \
%% -X PUT -H "Content-Type: text/plain" --data-binary @-
%% ...
%% 201 Created
%% ...
%% $ curl -k --verbose https://localhost:8443/flintstones
%% ...
%% Fred
%% Wilma
%% Barney
%% ...
%% $ curl -k --verbose https://localhost:8443/flintstones -X DELETE
%% ...
%% 200 OK
%% ...
%% $ curl -k --verbose https://localhost:8443/flintstones
%% ...
%% 404 Not Found
%% ...
%%
%% All submitted data is stored in memory (in an ets table). Could be
%% useful for ad-hoc testing.
-module(https_store).
-export([dispatch/1, loop/1, start/0, stop/0]).
-define(HTTP_OPTS,
[{loop, {?MODULE, dispatch}}, {port, 8442},
{name, http_8442}]).
-define(HTTPS_OPTS,
[{loop, {?MODULE, dispatch}}, {port, 8443},
{name, https_8443}, {ssl, true},
{ssl_opts,
[{certfile, "server_cert.pem"},
{keyfile, "server_key.pem"}]}]).
-record(sd, {http, https}).
-record(resource, {type, data}).
start() ->
{ok, Http} = mochiweb_http:start(?HTTP_OPTS),
{ok, Https} = mochiweb_http:start(?HTTPS_OPTS),
SD = #sd{http = Http, https = Https},
Pid = spawn_link(fun () ->
ets:new(?MODULE, [named_table]), loop(SD)
end),
register(http_store, Pid),
ok.
stop() -> http_store ! stop, ok.
dispatch(Req) ->
case mochiweb_request:get(method, Req) of
'GET' -> get_resource(Req);
'PUT' -> put_resource(Req);
'DELETE' -> delete_resource(Req);
_ ->
Headers = [{"Allow", "GET,PUT,DELETE"}],
mochiweb_request:respond({405, Headers,
"405 Method Not Allowed\r\n"},
Req)
end.
get_resource(Req) ->
Path = mochiweb_request:get(path, Req),
case ets:lookup(?MODULE, Path) of
[{Path, #resource{type = Type, data = Data}}] ->
mochiweb_request:ok({Type, Data}, Req);
[] ->
mochiweb_request:respond({404, [], "404 Not Found\r\n"},
Req)
end.
put_resource(Req) ->
ContentType = case
mochiweb_request:get_header_value("Content-Type", Req)
of
undefined -> "application/octet-stream";
S -> S
end,
Resource = #resource{type = ContentType,
data = mochiweb_request:recv_body(Req)},
http_store !
{self(),
{put, mochiweb_request:get(path, Req), Resource}},
Pid = whereis(http_store),
receive
{Pid, created} ->
mochiweb_request:respond({201, [], "201 Created\r\n"},
Req);
{Pid, updated} ->
mochiweb_request:respond({200, [], "200 OK\r\n"}, Req)
end.
delete_resource(Req) ->
http_store !
{self(), {delete, mochiweb_request:get(path, Req)}},
Pid = whereis(http_store),
receive
{Pid, ok} ->
mochiweb_request:respond({200, [], "200 OK\r\n"}, Req)
end.
loop(#sd{http = Http, https = Https} = SD) ->
receive
stop ->
ok = mochiweb_http:stop(Http),
ok = mochiweb_http:stop(Https),
exit(normal);
{From, {put, Key, Val}} ->
Exists = ets:member(?MODULE, Key),
ets:insert(?MODULE, {Key, Val}),
case Exists of
true -> From ! {self(), updated};
false -> From ! {self(), created}
end;
{From, {delete, Key}} ->
ets:delete(?MODULE, Key), From ! {self(), ok};
_ -> ignore
end,
(?MODULE):loop(SD).