blob: 23e24470072003d986bec5f70699be24d2f119a9 [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).
-compile(tuple_calls).
-export([start/0,
stop/0,
dispatch/1,
loop/1
]).
-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 Req:get(method) of
'GET' ->
get_resource(Req);
'PUT' ->
put_resource(Req);
'DELETE' ->
delete_resource(Req);
_ ->
Headers = [{"Allow", "GET,PUT,DELETE"}],
Req:respond({405, Headers, "405 Method Not Allowed\r\n"})
end.
get_resource(Req) ->
Path = Req:get(path),
case ets:lookup(?MODULE, Path) of
[{Path, #resource{type=Type, data=Data}}] ->
Req:ok({Type, Data});
[] ->
Req:respond({404, [], "404 Not Found\r\n"})
end.
put_resource(Req) ->
ContentType = case Req:get_header_value("Content-Type") of
undefined ->
"application/octet-stream";
S ->
S
end,
Resource = #resource{type=ContentType, data=Req:recv_body()},
http_store ! {self(), {put, Req:get(path), Resource}},
Pid = whereis(http_store),
receive
{Pid, created} ->
Req:respond({201, [], "201 Created\r\n"});
{Pid, updated} ->
Req:respond({200, [], "200 OK\r\n"})
end.
delete_resource(Req) ->
http_store ! {self(), {delete, Req:get(path)}},
Pid = whereis(http_store),
receive
{Pid, ok} ->
Req:respond({200, [], "200 OK\r\n"})
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).