diff options
Diffstat (limited to 'src/cowboy_rest.erl')
-rw-r--r-- | src/cowboy_rest.erl | 114 |
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]}. |