aboutsummaryrefslogtreecommitdiffstats
path: root/src/cowboy_rest.erl
diff options
context:
space:
mode:
Diffstat (limited to 'src/cowboy_rest.erl')
-rw-r--r--src/cowboy_rest.erl114
1 files changed, 78 insertions, 36 deletions
diff --git a/src/cowboy_rest.erl b/src/cowboy_rest.erl
index 06655a4..a49d622 100644
--- a/src/cowboy_rest.erl
+++ b/src/cowboy_rest.erl
@@ -1,4 +1,4 @@
-%% Copyright (c) 2011-2012, Loïc Hoguin <[email protected]>
+%% Copyright (c) 2011-2013, Loïc Hoguin <[email protected]>
%%
%% Permission to use, copy, modify, and/or distribute this software for any
%% purpose with or without fee is hereby granted, provided that the above
@@ -23,6 +23,7 @@
-export([upgrade/4]).
-record(state, {
+ env :: cowboy_middleware:env(),
method = undefined :: binary(),
%% Handler.
@@ -54,31 +55,31 @@
%% You do not need to call this function manually. To upgrade to the REST
%% protocol, you simply need to return <em>{upgrade, protocol, {@module}}</em>
%% in your <em>cowboy_http_handler:init/3</em> handler function.
--spec upgrade(pid(), module(), any(), Req)
- -> {ok, Req} | close when Req::cowboy_req:req().
-upgrade(_ListenerPid, Handler, Opts, Req) ->
+-spec upgrade(Req, Env, module(), any())
+ -> {ok, Req, Env} | {error, 500, Req}
+ when Req::cowboy_req:req(), Env::cowboy_middleware:env().
+upgrade(Req, Env, Handler, HandlerOpts) ->
try
Method = cowboy_req:get(method, Req),
case erlang:function_exported(Handler, rest_init, 2) of
true ->
- case Handler:rest_init(Req, Opts) of
+ case Handler:rest_init(Req, HandlerOpts) of
{ok, Req2, HandlerState} ->
- service_available(Req2, #state{method=Method,
+ service_available(Req2, #state{env=Env, method=Method,
handler=Handler, handler_state=HandlerState})
end;
false ->
- service_available(Req, #state{method=Method,
+ service_available(Req, #state{env=Env, method=Method,
handler=Handler})
end
catch Class:Reason ->
- PLReq = cowboy_req:to_list(Req),
error_logger:error_msg(
"** Cowboy handler ~p terminating in ~p/~p~n"
" for the reason ~p:~p~n** Options were ~p~n"
"** Request was ~p~n** Stacktrace: ~p~n~n",
- [Handler, rest_init, 2, Class, Reason, Opts, PLReq, erlang:get_stacktrace()]),
- {ok, _Req2} = cowboy_req:reply(500, Req),
- close
+ [Handler, rest_init, 2, Class, Reason, HandlerOpts,
+ cowboy_req:to_list(Req), erlang:get_stacktrace()]),
+ {error, 500, Req}
end.
service_available(Req, State) ->
@@ -90,7 +91,8 @@ known_methods(Req, State=#state{method=Method}) ->
no_call when Method =:= <<"HEAD">>; Method =:= <<"GET">>;
Method =:= <<"POST">>; Method =:= <<"PUT">>;
Method =:= <<"DELETE">>; Method =:= <<"TRACE">>;
- Method =:= <<"CONNECT">>; Method =:= <<"OPTIONS">> ->
+ Method =:= <<"CONNECT">>; Method =:= <<"OPTIONS">>;
+ Method =:= <<"PATCH">> ->
next(Req, State, fun uri_too_long/2);
no_call ->
next(Req, State, 501);
@@ -643,6 +645,8 @@ method(Req, State=#state{method= <<"POST">>}) ->
post_is_create(Req, State);
method(Req, State=#state{method= <<"PUT">>}) ->
is_conflict(Req, State);
+method(Req, State=#state{method= <<"PATCH">>}) ->
+ patch_resource(Req, State);
method(Req, State=#state{method=Method})
when Method =:= <<"GET">>; Method =:= <<"HEAD">> ->
set_resp_body(Req, State);
@@ -666,6 +670,8 @@ post_is_create(Req, State) ->
%% (including the leading /).
create_path(Req, State) ->
case call(Req, State, create_path) of
+ no_call ->
+ put_resource(Req, State, fun created_path/2);
{halt, Req2, HandlerState} ->
terminate(Req2, State#state{handler_state=HandlerState});
{Path, Req2, HandlerState} ->
@@ -677,6 +683,23 @@ create_path(Req, State) ->
State2, 303)
end.
+%% Called after content_types_accepted is called for POST methods
+%% when create_path did not exist. Expects the full path to
+%% be returned and MUST exist in the case that create_path
+%% does not.
+created_path(Req, State) ->
+ case call(Req, State, created_path) of
+ {halt, Req2, HandlerState} ->
+ terminate(Req2, State#state{handler_state=HandlerState});
+ {Path, Req2, HandlerState} ->
+ {HostURL, Req3} = cowboy_req:host_url(Req2),
+ State2 = State#state{handler_state=HandlerState},
+ Req4 = cowboy_req:set_resp_header(
+ <<"Location">>, << HostURL/binary, Path/binary >>, Req3),
+ respond(cowboy_req:set_meta(put_path, Path, Req4),
+ State2, 303)
+ end.
+
%% process_post should return true when the POST body could be processed
%% and false when it hasn't, in which case a 500 error is sent.
process_post(Req, State) ->
@@ -707,6 +730,9 @@ put_resource(Req, State) ->
%% may be different from the request path, and is stored as request metadata.
%% It is always defined past this point. It can be retrieved as demonstrated:
%% {PutPath, Req2} = cowboy_req:meta(put_path, Req)
+%%
+%%content_types_accepted SHOULD return a different list
+%% for each HTTP method.
put_resource(Req, State, OnTrue) ->
case call(Req, State, content_types_accepted) of
no_call ->
@@ -721,6 +747,27 @@ put_resource(Req, State, OnTrue) ->
choose_content_type(Req3, State2, OnTrue, ContentType, CTA2)
end.
+%% content_types_accepted should return a list of media types and their
+%% associated callback functions in the same format as content_types_provided.
+%%
+%% The callback will then be called and is expected to process the content
+%% pushed to the resource in the request body.
+%%
+%% content_types_accepted SHOULD return a different list
+%% for each HTTP method.
+patch_resource(Req, State) ->
+ case call(Req, State, content_types_accepted) of
+ no_call ->
+ respond(Req, State, 415);
+ {halt, Req2, HandlerState} ->
+ terminate(Req2, State#state{handler_state=HandlerState});
+ {CTM, Req2, HandlerState} ->
+ State2 = State#state{handler_state=HandlerState},
+ {ok, ContentType, Req3}
+ = cowboy_req:parse_header(<<"content-type">>, Req2),
+ choose_content_type(Req3, State2, 204, ContentType, CTM)
+ end.
+
%% The special content type '*' will always match. It can be used as a
%% catch-all content type for accepting any kind of request content.
%% Note that because it will always match, it should be the last of the
@@ -738,15 +785,14 @@ choose_content_type(Req,
"function ~p/~p was not exported~n"
"** Request was ~p~n** State was ~p~n~n",
[Handler, Fun, 2, cowboy_req:to_list(Req), HandlerState]),
- {ok, _} = cowboy_req:reply(500, Req),
- close;
- {halt, Req2, HandlerState} ->
- terminate(Req2, State#state{handler_state=HandlerState});
- {true, Req2, HandlerState} ->
- State2 = State#state{handler_state=HandlerState},
+ {error, 500, Req};
+ {halt, Req2, HandlerState2} ->
+ terminate(Req2, State#state{handler_state=HandlerState2});
+ {true, Req2, HandlerState2} ->
+ State2 = State#state{handler_state=HandlerState2},
next(Req2, State2, OnTrue);
- {false, Req2, HandlerState} ->
- State2 = State#state{handler_state=HandlerState},
+ {false, Req2, HandlerState2} ->
+ State2 = State#state{handler_state=HandlerState2},
respond(Req2, State2, 422)
end;
choose_content_type(Req, State, OnTrue, ContentType, [_Any|Tail]) ->
@@ -790,15 +836,16 @@ set_resp_body(Req, State=#state{handler=Handler, handler_state=HandlerState,
"function ~p/~p was not exported~n"
"** Request was ~p~n** State was ~p~n~n",
[Handler, Fun, 2, cowboy_req:to_list(Req5), HandlerState]),
- {ok, _} = cowboy_req:reply(500, Req5),
- close;
- {halt, Req6, HandlerState} ->
- terminate(Req6, State4#state{handler_state=HandlerState});
- {Body, Req6, HandlerState} ->
- State5 = State4#state{handler_state=HandlerState},
+ {error, 500, Req5};
+ {halt, Req6, HandlerState2} ->
+ terminate(Req6, State4#state{handler_state=HandlerState2});
+ {Body, Req6, HandlerState2} ->
+ State5 = State4#state{handler_state=HandlerState2},
Req7 = case Body of
- {stream, Len, Fun1} ->
- cowboy_req:set_resp_body_fun(Len, Fun1, Req6);
+ {stream, StreamFun} ->
+ cowboy_req:set_resp_body_fun(StreamFun, Req6);
+ {stream, Len, StreamFun} ->
+ cowboy_req:set_resp_body_fun(Len, StreamFun, Req6);
_Contents ->
cowboy_req:set_resp_body(Body, Req6)
end,
@@ -845,12 +892,6 @@ generate_etag(Req, State=#state{etag=undefined}) ->
case call(Req, State, generate_etag) of
no_call ->
{undefined, Req, State#state{etag=no_call}};
- %% Previously the return value from the generate_etag/2 callback was set
- %% as the value of the ETag header in the response. Therefore the only
- %% valid return type was `binary()'. If a handler returns a `binary()'
- %% it must be mapped to the expected type or it'll always fail to
- %% compare equal to any entity tags present in the request headers.
- %% @todo Remove support for binary return values after 0.6.
{Etag, Req2, HandlerState} when is_binary(Etag) ->
[Etag2] = cowboy_http:entity_tag_match(Etag),
{Etag2, Req2, State#state{handler_state=HandlerState, etag=Etag2}};
@@ -915,10 +956,11 @@ respond(Req, State, StatusCode) ->
{ok, Req2} = cowboy_req:reply(StatusCode, Req),
terminate(Req2, State).
-terminate(Req, #state{handler=Handler, handler_state=HandlerState}) ->
+terminate(Req, #state{env=Env, handler=Handler,
+ handler_state=HandlerState}) ->
case erlang:function_exported(Handler, rest_terminate, 2) of
true -> ok = Handler:rest_terminate(
cowboy_req:lock(Req), HandlerState);
false -> ok
end,
- {ok, Req}.
+ {ok, Req, [{result, ok}|Env]}.