diff options
-rw-r--r-- | CONTRIBUTING.md | 20 | ||||
-rw-r--r-- | Makefile | 2 | ||||
-rw-r--r-- | README.md | 6 | ||||
-rw-r--r-- | erlang.mk | 102 | ||||
-rw-r--r-- | examples/elixir_hello_world/mix.exs | 2 | ||||
-rw-r--r-- | manual/cowboy_websocket.md | 6 | ||||
-rw-r--r-- | rebar.config | 2 | ||||
-rw-r--r-- | src/cowboy_handler.erl | 86 | ||||
-rw-r--r-- | src/cowboy_protocol.erl | 29 | ||||
-rw-r--r-- | src/cowboy_req.erl | 14 | ||||
-rw-r--r-- | src/cowboy_rest.erl | 184 | ||||
-rw-r--r-- | src/cowboy_spdy.erl | 18 | ||||
-rw-r--r-- | src/cowboy_websocket.erl | 88 |
13 files changed, 279 insertions, 280 deletions
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9e2fa32..abab6ae 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -127,6 +127,26 @@ Committing You MUST ensure that all commits pass all tests and do not have extra Dialyzer warnings. +Running tests is fairly straightforward. + +``` bash +make tests +``` + +Running Dialyzer requires some initial setup. You need to build the PLT +file that Dialyzer will use for its analysis. This is a one-time operation. +Dialyzer will take care of updating that file when needed. + +``` bash +make build-plt +``` + +Once that is done, you can run Dialyzer. + +``` bash +make dialyze +``` + You MUST put all the related work in a single commit. Fixing a bug is one commit, adding a feature is one commit, adding two features is two commits. @@ -12,7 +12,7 @@ PLT_APPS = crypto public_key ssl DEPS = ranch TEST_DEPS = ct_helper -dep_ranch = https://github.com/extend/ranch.git 0.8.4 +dep_ranch = https://github.com/extend/ranch.git 0.8.5 dep_ct_helper = https://github.com/extend/ct_helper.git master # Standard targets. @@ -18,6 +18,12 @@ Because it uses Ranch for managing connections, Cowboy can easily be No parameterized module. No process dictionary. **Clean** Erlang code. +Sponsors +-------- + +The SPDY protocol development is sponsored +by [LeoFS Cloud Storage](http://www.leofs.org). + Getting Started --------------- @@ -12,6 +12,21 @@ # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +# Project. + +PROJECT ?= $(notdir $(CURDIR)) + +# Packages database file. + +PKG_FILE ?= $(CURDIR)/.erlang.mk.packages.v1 +export PKG_FILE + +PKG_FILE_URL ?= https://raw.github.com/extend/erlang.mk/master/packages.v1.tsv + +define get_pkg_file + wget -O $(PKG_FILE) $(PKG_FILE_URL) +endef + # Verbosity and tweaks. V ?= 0 @@ -19,9 +34,12 @@ V ?= 0 appsrc_verbose_0 = @echo " APP " $(PROJECT).app.src; appsrc_verbose = $(appsrc_verbose_$(V)) -erlc_verbose_0 = @echo " ERLC " $(filter-out %.dtl,$(?F)); +erlc_verbose_0 = @echo " ERLC " $(filter %.erl %.core,$(?F)); erlc_verbose = $(erlc_verbose_$(V)) +xyrl_verbose_0 = @echo " XYRL " $(filter %.xrl %.yrl,$(?F)); +xyrl_verbose = $(xyrl_verbose_$(V)) + dtl_verbose_0 = @echo " DTL " $(filter %.dtl,$(?F)); dtl_verbose = $(dtl_verbose_$(V)) @@ -36,11 +54,17 @@ gen_verbose = $(gen_verbose_$(V)) DEPS_DIR ?= $(CURDIR)/deps export DEPS_DIR +REBAR_DEPS_DIR = $(DEPS_DIR) +export REBAR_DEPS_DIR + ALL_DEPS_DIRS = $(addprefix $(DEPS_DIR)/,$(DEPS)) ALL_TEST_DEPS_DIRS = $(addprefix $(DEPS_DIR)/,$(TEST_DEPS)) # Application. +ERL_LIBS ?= $(DEPS_DIR) +export ERL_LIBS + ERLC_OPTS ?= -Werror +debug_info +warn_export_all +warn_export_vars \ +warn_shadow_vars +warn_obsolete_guard # +bin_opt_info +warn_missing_spec COMPILE_FIRST ?= @@ -52,19 +76,25 @@ clean-all: clean clean-deps clean-docs $(gen_verbose) rm -rf .$(PROJECT).plt $(DEPS_DIR) logs app: ebin/$(PROJECT).app - $(eval MODULES := $(shell find ebin -name \*.beam \ + $(eval MODULES := $(shell find ebin -type f -name \*.beam \ | sed 's/ebin\///;s/\.beam/,/' | sed '$$s/.$$//')) $(appsrc_verbose) cat src/$(PROJECT).app.src \ | sed 's/{modules, \[\]}/{modules, \[$(MODULES)\]}/' \ > ebin/$(PROJECT).app define compile_erl - $(erlc_verbose) ERL_LIBS=deps erlc -v $(ERLC_OPTS) -o ebin/ -pa ebin/ \ - $(COMPILE_FIRST_PATHS) $(1) + $(erlc_verbose) erlc -v $(ERLC_OPTS) -o ebin/ \ + -pa ebin/ -I include/ $(COMPILE_FIRST_PATHS) $(1) +endef + +define compile_xyrl + $(xyrl_verbose) erlc -v -o ebin/ $(1) + $(xyrl_verbose) erlc $(ERLC_OPTS) -o ebin/ ebin/*.erl + @rm ebin/*.erl endef define compile_dtl - $(dtl_verbose) erl -noshell -pa ebin/ deps/erlydtl/ebin/ -eval ' \ + $(dtl_verbose) erl -noshell -pa ebin/ $(DEPS_DIR)/erlydtl/ebin/ -eval ' \ Compile = fun(F) -> \ Module = list_to_atom( \ string:to_lower(filename:basename(F, ".dtl")) ++ "_dtl"), \ @@ -74,10 +104,16 @@ define compile_dtl init:stop()' endef -ebin/$(PROJECT).app: src/*.erl $(wildcard src/*.core) $(wildcard templates/*.dtl) +ebin/$(PROJECT).app: $(shell find src -type f -name \*.erl) \ + $(shell find src -type f -name \*.core) \ + $(shell find src -type f -name \*.xrl) \ + $(shell find src -type f -name \*.yrl) \ + $(shell find templates -type f -name \*.dtl 2>/dev/null) @mkdir -p ebin/ - $(if $(strip $(filter-out %.dtl,$?)), \ - $(call compile_erl,$(filter-out %.dtl,$?))) + $(if $(strip $(filter %.erl %.core,$?)), \ + $(call compile_erl,$(filter %.erl %.core,$?))) + $(if $(strip $(filter %.xrl %.yrl,$?)), \ + $(call compile_xyrl,$(filter %.xrl %.yrl,$?))) $(if $(strip $(filter %.dtl,$?)), \ $(call compile_dtl,$(filter %.dtl,$?))) @@ -88,7 +124,14 @@ clean: define get_dep @mkdir -p $(DEPS_DIR) +ifeq (,$(findstring pkg://,$(word 1,$(dep_$(1))))) git clone -n -- $(word 1,$(dep_$(1))) $(DEPS_DIR)/$(1) +else + @if [ ! -f $(PKG_FILE) ]; then $(call get_pkg_file); fi + git clone -n -- `awk 'BEGIN { FS = "\t" }; \ + $$$$1 == "$(subst pkg://,,$(word 1,$(dep_$(1))))" { print $$$$2 }' \ + $(PKG_FILE)` $(DEPS_DIR)/$(1) +endif cd $(DEPS_DIR)/$(1) ; git checkout -q $(word 2,$(dep_$(1))) endef @@ -100,7 +143,13 @@ endef $(foreach dep,$(DEPS),$(eval $(call dep_target,$(dep)))) deps: $(ALL_DEPS_DIRS) - @for dep in $(ALL_DEPS_DIRS) ; do $(MAKE) -C $$dep; done + @for dep in $(ALL_DEPS_DIRS) ; do \ + if [ -f $$dep/Makefile ] ; then \ + $(MAKE) -C $$dep ; \ + else \ + echo "include $(CURDIR)/erlang.mk" | $(MAKE) -f - -C $$dep ; \ + fi ; \ + done clean-deps: @for dep in $(ALL_DEPS_DIRS) ; do $(MAKE) -C $$dep clean; done @@ -122,13 +171,13 @@ build-test-deps: $(ALL_TEST_DEPS_DIRS) @for dep in $(ALL_TEST_DEPS_DIRS) ; do $(MAKE) -C $$dep; done build-tests: build-test-deps - $(gen_verbose) ERL_LIBS=deps erlc -v $(ERLC_OPTS) -o test/ \ + $(gen_verbose) erlc -v $(ERLC_OPTS) -o test/ \ $(wildcard test/*.erl test/*/*.erl) -pa ebin/ CT_RUN = ct_run \ -no_auto_compile \ -noshell \ - -pa ebin $(DEPS_DIR)/*/ebin \ + -pa $(realpath ebin) $(DEPS_DIR)/*/ebin \ -dir test \ -logdir logs # -cover test/cover.spec @@ -138,8 +187,11 @@ CT_SUITES_FULL = $(addsuffix _SUITE,$(CT_SUITES)) tests: ERLC_OPTS += -DTEST=1 +'{parse_transform, eunit_autoexport}' tests: clean deps app build-tests - @mkdir -p logs/ - @$(CT_RUN) -suite $(CT_SUITES_FULL) + @if [ -d "test" ] ; \ + then \ + mkdir -p logs/ ; \ + $(CT_RUN) -suite $(CT_SUITES_FULL) ; \ + fi $(gen_verbose) rm -f test/*.beam # Dialyzer. @@ -154,3 +206,27 @@ build-plt: deps app dialyze: @dialyzer --src src --plt .$(PROJECT).plt --no_native $(DIALYZER_OPTS) + +# Packages. + +$(PKG_FILE): + @$(call get_pkg_file) + +pkg-list: $(PKG_FILE) + @cat $(PKG_FILE) | awk 'BEGIN { FS = "\t" }; { print \ + "Name:\t\t" $$1 "\n" \ + "Repository:\t" $$2 "\n" \ + "Website:\t" $$3 "\n" \ + "Description:\t" $$4 "\n" }' + +ifdef q +pkg-search: $(PKG_FILE) + @cat $(PKG_FILE) | grep -i ${q} | awk 'BEGIN { FS = "\t" }; { print \ + "Name:\t\t" $$1 "\n" \ + "Repository:\t" $$2 "\n" \ + "Website:\t" $$3 "\n" \ + "Description:\t" $$4 "\n" }' +else +pkg-search: + @echo "Usage: make pkg-search q=STRING" +endif diff --git a/examples/elixir_hello_world/mix.exs b/examples/elixir_hello_world/mix.exs index 9055175..00297bf 100644 --- a/examples/elixir_hello_world/mix.exs +++ b/examples/elixir_hello_world/mix.exs @@ -14,7 +14,7 @@ defmodule ElixirHelloWorld.Mixfile do end defp deps do - [ {:ranch, github: "extend/ranch", tag: "0.8.4"}, + [ {:ranch, github: "extend/ranch", tag: "0.8.5"}, {:cowboy, github: "extend/cowboy"} ] end end diff --git a/manual/cowboy_websocket.md b/manual/cowboy_websocket.md index ae3ca1b..9a81878 100644 --- a/manual/cowboy_websocket.md +++ b/manual/cowboy_websocket.md @@ -22,6 +22,12 @@ Types Meta values ----------- +### websocket_compress + +> Type: true | false +> +> Whether a websocket compression extension in in use. + ### websocket_version > Type: 7 | 8 | 13 diff --git a/rebar.config b/rebar.config index 443949a..edd3948 100644 --- a/rebar.config +++ b/rebar.config @@ -1,3 +1,3 @@ {deps, [ - {ranch, ".*", {git, "git://github.com/extend/ranch.git", "0.8.4"}} + {ranch, ".*", {git, "git://github.com/extend/ranch.git", "0.8.5"}} ]}. diff --git a/src/cowboy_handler.erl b/src/cowboy_handler.erl index 2074b4e..fcbfe55 100644 --- a/src/cowboy_handler.erl +++ b/src/cowboy_handler.erl @@ -90,15 +90,14 @@ handler_init(Req, State, Handler, HandlerOpts) -> {upgrade, protocol, Module, Req2, HandlerOpts2} -> upgrade_protocol(Req2, State, Handler, HandlerOpts2, Module) catch Class:Reason -> - 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, init, 3, Class, Reason, HandlerOpts, - cowboy_req:to_list(Req), erlang:get_stacktrace()]), - error_terminate(Req, State) + cowboy_req:maybe_reply(500, Req), + erlang:Class([ + {reason, Reason}, + {mfa, {Handler, init, 3}}, + {stacktrace, erlang:get_stacktrace()}, + {req, cowboy_req:to_list(Req)}, + {opts, HandlerOpts} + ]) end. -spec upgrade_protocol(Req, #state{}, module(), any(), module()) @@ -121,16 +120,15 @@ handler_handle(Req, State, Handler, HandlerState) -> terminate_request(Req2, State, Handler, HandlerState2, {normal, shutdown}) catch Class:Reason -> - error_logger:error_msg( - "** Cowboy handler ~p terminating in ~p/~p~n" - " for the reason ~p:~p~n" - "** Handler state was ~p~n" - "** Request was ~p~n" - "** Stacktrace: ~p~n~n", - [Handler, handle, 2, Class, Reason, HandlerState, - cowboy_req:to_list(Req), erlang:get_stacktrace()]), + cowboy_req:maybe_reply(500, Req), handler_terminate(Req, Handler, HandlerState, Reason), - error_terminate(Req, State) + erlang:Class([ + {reason, Reason}, + {mfa, {Handler, handle, 2}}, + {stacktrace, erlang:get_stacktrace()}, + {req, cowboy_req:to_list(Req)}, + {state, HandlerState} + ]) end. %% Update the state if the response was sent in the callback. @@ -195,7 +193,8 @@ handler_loop(Req, State=#state{loop_buffer_size=NbBytes, if NbBytes2 > Threshold -> _ = handler_terminate(Req, Handler, HandlerState, {error, overflow}), - error_terminate(Req, State); + cowboy_req:maybe_reply(500, Req), + exit(normal); true -> Req2 = cowboy_req:append_buffer(Data, Req), State2 = handler_loop_timeout(State#state{ @@ -232,7 +231,8 @@ handler_loop(Req, State=#state{loop_buffer_size=NbBytes, -> {ok, Req, cowboy_middleware:env()} | {error, 500, Req} | {suspend, module(), atom(), [any()]} when Req::cowboy_req:req(). -handler_call(Req, State, Handler, HandlerState, Message) -> +handler_call(Req, State=#state{resp_sent=RespSent}, + Handler, HandlerState, Message) -> try Handler:info(Message, Req, HandlerState) of {ok, Req2, HandlerState2} -> handler_after_loop(Req2, State, Handler, HandlerState2, @@ -243,16 +243,19 @@ handler_call(Req, State, Handler, HandlerState, Message) -> handler_after_callback(Req2, State#state{hibernate=true}, Handler, HandlerState2) catch Class:Reason -> - error_logger:error_msg( - "** Cowboy handler ~p terminating in ~p/~p~n" - " for the reason ~p:~p~n" - "** Handler state was ~p~n" - "** Request was ~p~n" - "** Stacktrace: ~p~n~n", - [Handler, info, 3, Class, Reason, HandlerState, - cowboy_req:to_list(Req), erlang:get_stacktrace()]), + if RespSent -> + ok; + true -> + cowboy_req:maybe_reply(500, Req) + end, handler_terminate(Req, Handler, HandlerState, Reason), - error_terminate(Req, State) + erlang:Class([ + {reason, Reason}, + {mfa, {Handler, info, 3}}, + {stacktrace, erlang:get_stacktrace()}, + {req, cowboy_req:to_list(Req)}, + {state, HandlerState} + ]) end. %% It is sometimes important to make a socket passive as it was initially @@ -287,21 +290,12 @@ handler_terminate(Req, Handler, HandlerState, Reason) -> try Handler:terminate(Reason, cowboy_req:lock(Req), HandlerState) catch Class:Reason2 -> - error_logger:error_msg( - "** Cowboy handler ~p terminating in ~p/~p~n" - " for the reason ~p:~p~n" - "** Handler state was ~p~n" - "** Request was ~p~n" - "** Stacktrace: ~p~n~n", - [Handler, terminate, 3, Class, Reason2, HandlerState, - cowboy_req:to_list(Req), erlang:get_stacktrace()]) + erlang:Class([ + {reason, Reason2}, + {mfa, {Handler, terminate, 3}}, + {stacktrace, erlang:get_stacktrace()}, + {req, cowboy_req:to_list(Req)}, + {state, HandlerState}, + {terminate_reason, Reason} + ]) end. - -%% Only send an error reply if there is no resp_sent message. --spec error_terminate(Req, #state{}) - -> {error, 500, Req} | {halt, Req} when Req::cowboy_req:req(). -error_terminate(Req, #state{resp_sent=true}) -> - %% Close the connection, but do not attempt sending a reply. - {halt, cowboy_req:set([{connection, close}, {resp_state, done}], Req)}; -error_terminate(Req, _) -> - {error, 500, Req}. diff --git a/src/cowboy_protocol.erl b/src/cowboy_protocol.erl index b42f524..40be2c0 100644 --- a/src/cowboy_protocol.erl +++ b/src/cowboy_protocol.erl @@ -573,29 +573,16 @@ next_request(Req, State=#state{req_keepalive=Keepalive, timeout=Timeout}, end end. -%% Only send an error reply if there is no resp_sent message. --spec error_terminate(cowboy:http_status(), cowboy_req:req(), #state{}) -> ok. -error_terminate(Code, Req, State) -> - receive - {cowboy_req, resp_sent} -> ok - after 0 -> - _ = cowboy_req:reply(Code, Req), - ok - end, - terminate(State). - -%% Only send an error reply if there is no resp_sent message. -spec error_terminate(cowboy:http_status(), #state{}) -> ok. -error_terminate(Code, State=#state{socket=Socket, transport=Transport, +error_terminate(Status, State=#state{socket=Socket, transport=Transport, compress=Compress, onresponse=OnResponse}) -> - receive - {cowboy_req, resp_sent} -> ok - after 0 -> - _ = cowboy_req:reply(Code, cowboy_req:new(Socket, Transport, - undefined, <<"GET">>, <<>>, <<>>, 'HTTP/1.1', [], <<>>, - undefined, <<>>, false, Compress, OnResponse)), - ok - end, + error_terminate(Status, cowboy_req:new(Socket, Transport, + undefined, <<"GET">>, <<>>, <<>>, 'HTTP/1.1', [], <<>>, + undefined, <<>>, false, Compress, OnResponse), State). + +-spec error_terminate(cowboy:http_status(), cowboy_req:req(), #state{}) -> ok. +error_terminate(Status, Req, State) -> + cowboy_req:maybe_reply(Status, Req), terminate(State). -spec terminate(#state{}) -> ok. diff --git a/src/cowboy_req.erl b/src/cowboy_req.erl index 66abcf8..32ff7b0 100644 --- a/src/cowboy_req.erl +++ b/src/cowboy_req.erl @@ -101,6 +101,7 @@ -export([chunked_reply/3]). -export([chunk/2]). -export([upgrade_reply/3]). +-export([maybe_reply/2]). -export([ensure_response/2]). %% Private setter/getter API. @@ -1120,6 +1121,19 @@ upgrade_reply(Status, Headers, Req=#http_req{transport=Transport, ], <<>>, Req), {ok, Req2#http_req{resp_state=done, resp_headers=[], resp_body= <<>>}}. +%% @doc Send a reply if one hasn't been sent already. +%% +%% Meant to be used internally for sending errors after crashes. +%% @private +-spec maybe_reply(cowboy:http_status(), req()) -> ok. +maybe_reply(Status, Req) -> + receive + {cowboy_req, resp_sent} -> ok + after 0 -> + _ = cowboy_req:reply(Status, Req), + ok + end. + %% @doc Ensure the response has been sent fully. %% @private -spec ensure_response(req(), cowboy:http_status()) -> ok. diff --git a/src/cowboy_rest.erl b/src/cowboy_rest.erl index 34bfce1..862ebbf 100644 --- a/src/cowboy_rest.erl +++ b/src/cowboy_rest.erl @@ -66,30 +66,26 @@ -> {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 -> - try Handler:rest_init(Req, HandlerOpts) of - {ok, Req2, HandlerState} -> - service_available(Req2, #state{env=Env, method=Method, - handler=Handler, handler_state=HandlerState}) - catch Class:Reason -> - 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, HandlerOpts, - cowboy_req:to_list(Req), erlang:get_stacktrace()]), - {error, 500, Req} - end; - false -> - service_available(Req, #state{env=Env, method=Method, - handler=Handler}) - end - catch - throw:{?MODULE, error} -> - {error, 500, Req} + Method = cowboy_req:get(method, Req), + case erlang:function_exported(Handler, rest_init, 2) of + true -> + try Handler:rest_init(Req, HandlerOpts) of + {ok, Req2, HandlerState} -> + service_available(Req2, #state{env=Env, method=Method, + handler=Handler, handler_state=HandlerState}) + catch Class:Reason -> + cowboy_req:maybe_reply(500, Req), + erlang:Class([ + {reason, Reason}, + {mfa, {Handler, rest_init, 2}}, + {stacktrace, erlang:get_stacktrace()}, + {req, cowboy_req:to_list(Req)}, + {opts, HandlerOpts} + ]) + end; + false -> + service_available(Req, #state{env=Env, method=Method, + handler=Handler}) end. service_available(Req, State) -> @@ -516,14 +512,7 @@ variances(Req, State=#state{content_types_p=CTP, resource_exists(Req3, State2) end catch Class:Reason -> - error_logger:error_msg( - "** Cowboy handler ~p terminating in ~p/~p~n" - " for the reason ~p:~p~n** Handler state was ~p~n" - "** Request was ~p~n** Stacktrace: ~p~n~n", - [State#state.handler, variances, 2, - Class, Reason, State#state.handler_state, - cowboy_req:to_list(Req), erlang:get_stacktrace()]), - error_terminate(Req, State) + error_terminate(Req, State, Class, Reason, variances) end. variances(Req, State, Variances) -> @@ -559,14 +548,7 @@ if_match(Req, State, EtagsList) -> false -> precondition_failed(Req2, State2) end catch Class:Reason -> - error_logger:error_msg( - "** Cowboy handler ~p terminating in ~p/~p~n" - " for the reason ~p:~p~n** Handler state was ~p~n" - "** Request was ~p~n** Stacktrace: ~p~n~n", - [State#state.handler, generate_etag, 2, - Class, Reason, State#state.handler_state, - cowboy_req:to_list(Req), erlang:get_stacktrace()]), - error_terminate(Req, State) + error_terminate(Req, State, Class, Reason, generate_etag) end. if_match_must_not_exist(Req, State) -> @@ -594,14 +576,7 @@ if_unmodified_since(Req, State, IfUnmodifiedSince) -> false -> if_none_match_exists(Req2, State2) end catch Class:Reason -> - error_logger:error_msg( - "** Cowboy handler ~p terminating in ~p/~p~n" - " for the reason ~p:~p~n** Handler state was ~p~n" - "** Request was ~p~n** Stacktrace: ~p~n~n", - [State#state.handler, last_modified, 2, - Class, Reason, State#state.handler_state, - cowboy_req:to_list(Req), erlang:get_stacktrace()]), - error_terminate(Req, State) + error_terminate(Req, State, Class, Reason, last_modified) end. if_none_match_exists(Req, State) -> @@ -627,14 +602,7 @@ if_none_match(Req, State, EtagsList) -> end end catch Class:Reason -> - error_logger:error_msg( - "** Cowboy handler ~p terminating in ~p/~p~n" - " for the reason ~p:~p~n** Handler state was ~p~n" - "** Request was ~p~n** Stacktrace: ~p~n~n", - [State#state.handler, generate_etag, 2, - Class, Reason, State#state.handler_state, - cowboy_req:to_list(Req), erlang:get_stacktrace()]), - error_terminate(Req, State) + error_terminate(Req, State, Class, Reason, generate_etag) end. precondition_is_head_get(Req, State=#state{method=Method}) @@ -669,14 +637,7 @@ if_modified_since(Req, State, IfModifiedSince) -> false -> not_modified(Req2, State2) end catch Class:Reason -> - error_logger:error_msg( - "** Cowboy handler ~p terminating in ~p/~p~n" - " for the reason ~p:~p~n** Handler state was ~p~n" - "** Request was ~p~n** Stacktrace: ~p~n~n", - [State#state.handler, last_modified, 2, - Class, Reason, State#state.handler_state, - cowboy_req:to_list(Req), erlang:get_stacktrace()]), - error_terminate(Req, State) + error_terminate(Req, State, Class, Reason, last_modified) end. not_modified(Req, State) -> @@ -687,24 +648,10 @@ not_modified(Req, State) -> {Req4, State3} -> respond(Req4, State3, 304) catch Class:Reason -> - error_logger:error_msg( - "** Cowboy handler ~p terminating in ~p/~p~n" - " for the reason ~p:~p~n** Handler state was ~p~n" - "** Request was ~p~n** Stacktrace: ~p~n~n", - [State#state.handler, expires, 2, - Class, Reason, State#state.handler_state, - cowboy_req:to_list(Req), erlang:get_stacktrace()]), - error_terminate(Req2, State) + error_terminate(Req, State, Class, Reason, expires) end catch Class:Reason -> - error_logger:error_msg( - "** Cowboy handler ~p terminating in ~p/~p~n" - " for the reason ~p:~p~n** Handler state was ~p~n" - "** Request was ~p~n** Stacktrace: ~p~n~n", - [State#state.handler, generate_etag, 2, - Class, Reason, State#state.handler_state, - cowboy_req:to_list(Req), erlang:get_stacktrace()]), - error_terminate(Req2, State) + error_terminate(Req, State, Class, Reason, generate_etag) end. precondition_failed(Req, State) -> @@ -829,17 +776,8 @@ choose_content_type(Req, State, {Type, SubType, Param}, choose_content_type(Req, State, ContentType, [_Any|Tail]) -> choose_content_type(Req, State, ContentType, Tail). -process_content_type(Req, State=#state{method=Method, - handler=Handler, handler_state=HandlerState, - exists=Exists}, Fun) -> - case call(Req, State, Fun) of - no_call -> - error_logger:error_msg( - "** Cowboy handler ~p terminating; " - "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]), - {error, 500, Req}; +process_content_type(Req, State=#state{method=Method, exists=Exists}, Fun) -> + try case call(Req, State, Fun) of {halt, Req2, HandlerState2} -> terminate(Req2, State#state{handler_state=HandlerState2}); {true, Req2, HandlerState2} when Exists -> @@ -859,6 +797,8 @@ process_content_type(Req, State=#state{method=Method, Exists -> respond(Req3, State2, 303); true -> respond(Req3, State2, 201) end + end catch Class:Reason = {case_clause, no_call} -> + error_terminate(Req, State, Class, Reason, Fun) end. %% If the resource is new and has been created at another location @@ -881,14 +821,7 @@ set_resp_body_etag(Req, State) -> {Req2, State2} -> set_resp_body_last_modified(Req2, State2) catch Class:Reason -> - error_logger:error_msg( - "** Cowboy handler ~p terminating in ~p/~p~n" - " for the reason ~p:~p~n** Handler state was ~p~n" - "** Request was ~p~n** Stacktrace: ~p~n~n", - [State#state.handler, generate_etag, 2, - Class, Reason, State#state.handler_state, - cowboy_req:to_list(Req), erlang:get_stacktrace()]), - error_terminate(Req, State) + error_terminate(Req, State, Class, Reason, generate_etag) end. %% Set the Last-Modified header if any for the response provided. @@ -905,14 +838,7 @@ set_resp_body_last_modified(Req, State) -> set_resp_body_expires(Req3, State2) end catch Class:Reason -> - error_logger:error_msg( - "** Cowboy handler ~p terminating in ~p/~p~n" - " for the reason ~p:~p~n** Handler state was ~p~n" - "** Request was ~p~n** Stacktrace: ~p~n~n", - [State#state.handler, last_modified, 2, - Class, Reason, State#state.handler_state, - cowboy_req:to_list(Req), erlang:get_stacktrace()]), - error_terminate(Req, State) + error_terminate(Req, State, Class, Reason, last_modified) end. %% Set the Expires header if any for the response provided. @@ -921,29 +847,14 @@ set_resp_body_expires(Req, State) -> {Req2, State2} -> set_resp_body(Req2, State2) catch Class:Reason -> - error_logger:error_msg( - "** Cowboy handler ~p terminating in ~p/~p~n" - " for the reason ~p:~p~n** Handler state was ~p~n" - "** Request was ~p~n** Stacktrace: ~p~n~n", - [State#state.handler, expires, 2, - Class, Reason, State#state.handler_state, - cowboy_req:to_list(Req), erlang:get_stacktrace()]), - error_terminate(Req, State) + error_terminate(Req, State, Class, Reason, expires) end. %% Set the response headers and call the callback found using %% content_types_provided/2 to obtain the request body and add %% it to the response. -set_resp_body(Req, State=#state{handler=Handler, handler_state=HandlerState, - content_type_a={_Type, Callback}}) -> - case call(Req, State, Callback) of - no_call -> - error_logger:error_msg( - "** Cowboy handler ~p terminating; " - "function ~p/~p was not exported~n" - "** Request was ~p~n** State was ~p~n~n", - [Handler, Callback, 2, cowboy_req:to_list(Req), HandlerState]), - {error, 500, Req}; +set_resp_body(Req, State=#state{content_type_a={_, Callback}}) -> + try case call(Req, State, Callback) of {halt, Req2, HandlerState2} -> terminate(Req2, State#state{handler_state=HandlerState2}); {Body, Req2, HandlerState2} -> @@ -959,6 +870,8 @@ set_resp_body(Req, State=#state{handler=Handler, handler_state=HandlerState, cowboy_req:set_resp_body(Body, Req2) end, multiple_choices(Req3, State2) + end catch Class:Reason = {case_clause, no_call} -> + error_terminate(Req, State, Class, Reason, Callback) end. multiple_choices(Req, State) -> @@ -1057,13 +970,7 @@ call(Req, State=#state{handler=Handler, handler_state=HandlerState}, try Handler:Callback(Req, HandlerState) catch Class:Reason -> - error_logger:error_msg( - "** Cowboy handler ~p terminating in ~p/~p~n" - " for the reason ~p:~p~n** Handler state was ~p~n" - "** Request was ~p~n** Stacktrace: ~p~n~n", - [Handler, Callback, 2, Class, Reason, HandlerState, - cowboy_req:to_list(Req), erlang:get_stacktrace()]), - error_terminate(Req, State) + error_terminate(Req, State, Class, Reason, Callback) end; false -> no_call @@ -1089,10 +996,17 @@ terminate(Req, State=#state{env=Env}) -> rest_terminate(Req, State), {ok, Req, [{result, ok}|Env]}. --spec error_terminate(cowboy_req:req(), #state{}) -> no_return(). -error_terminate(Req, State) -> +error_terminate(Req, State=#state{handler=Handler, handler_state=HandlerState}, + Class, Reason, Callback) -> rest_terminate(Req, State), - erlang:raise(throw, {?MODULE, error}, erlang:get_stacktrace()). + cowboy_req:maybe_reply(500, Req), + erlang:Class([ + {reason, Reason}, + {mfa, {Handler, Callback, 2}}, + {stacktrace, erlang:get_stacktrace()}, + {req, cowboy_req:to_list(Req)}, + {state, HandlerState} + ]). rest_terminate(Req, #state{handler=Handler, handler_state=HandlerState}) -> case erlang:function_exported(Handler, rest_terminate, 2) of diff --git a/src/cowboy_spdy.erl b/src/cowboy_spdy.erl index 182e6da..cc4d867 100644 --- a/src/cowboy_spdy.erl +++ b/src/cowboy_spdy.erl @@ -520,8 +520,8 @@ execute(Req, Env, [Middleware|Tail]) -> [Env, Tail, Module, Function, Args]); {halt, Req2} -> cowboy_req:ensure_response(Req2, 204); - {error, Code, Req2} -> - error_terminate(Code, Req2) + {error, Status, Req2} -> + cowboy_req:maybe_reply(Status, Req2) end. %% @private @@ -536,18 +536,8 @@ resume(Env, Tail, Module, Function, Args) -> [Env, Tail, Module2, Function2, Args2]); {halt, Req2} -> cowboy_req:ensure_response(Req2, 204); - {error, Code, Req2} -> - error_terminate(Code, Req2) - end. - -%% Only send an error reply if there is no resp_sent message. --spec error_terminate(cowboy:http_status(), cowboy_req:req()) -> ok. -error_terminate(Code, Req) -> - receive - {cowboy_req, resp_sent} -> ok - after 0 -> - _ = cowboy_req:reply(Code, Req), - ok + {error, Status, Req2} -> + cowboy_req:maybe_reply(Status, Req2) end. %% Reply functions used by cowboy_req. diff --git a/src/cowboy_websocket.erl b/src/cowboy_websocket.erl index 75c55da..40046e1 100644 --- a/src/cowboy_websocket.erl +++ b/src/cowboy_websocket.erl @@ -48,7 +48,6 @@ socket = undefined :: inet:socket(), transport = undefined :: module(), handler :: module(), - handler_opts :: any(), key = undefined :: undefined | binary(), timeout = infinity :: timeout(), timeout_ref = undefined :: undefined | reference(), @@ -75,10 +74,13 @@ upgrade(Req, Env, Handler, HandlerOpts) -> ranch:remove_connection(Ref), [Socket, Transport] = cowboy_req:get([socket, transport], Req), State = #state{env=Env, socket=Socket, transport=Transport, - handler=Handler, handler_opts=HandlerOpts}, - case catch websocket_upgrade(State, Req) of - {ok, State2, Req2} -> handler_init(State2, Req2); - {'EXIT', _Reason} -> upgrade_error(Req, Env) + handler=Handler}, + try websocket_upgrade(State, Req) of + {ok, State2, Req2} -> + handler_init(State2, Req2, HandlerOpts) + catch _:_ -> + cowboy_req:maybe_reply(400, Req), + exit(normal) end. -spec websocket_upgrade(#state{}, Req) @@ -121,20 +123,20 @@ websocket_extensions(State, Req) -> deflate_frame = true, inflate_state = Inflate, deflate_state = Deflate - }, Req2}; + }, cowboy_req:set_meta(websocket_compress, true, Req2)}; _ -> - {ok, State, Req2} + {ok, State, cowboy_req:set_meta(websocket_compress, false, Req2)} end; _ -> - {ok, State, Req} + {ok, State, cowboy_req:set_meta(websocket_compress, false, Req)} end. --spec handler_init(#state{}, Req) +-spec handler_init(#state{}, Req, any()) -> {ok, Req, cowboy_middleware:env()} | {error, 400, Req} | {suspend, module(), atom(), [any()]} when Req::cowboy_req:req(). handler_init(State=#state{env=Env, transport=Transport, - handler=Handler, handler_opts=HandlerOpts}, Req) -> + handler=Handler}, Req, HandlerOpts) -> try Handler:websocket_init(Transport:name(), Req, HandlerOpts) of {ok, Req2, HandlerState} -> websocket_handshake(State, Req2, HandlerState); @@ -151,24 +153,14 @@ handler_init(State=#state{env=Env, transport=Transport, cowboy_req:ensure_response(Req2, 400), {ok, Req2, [{result, closed}|Env]} catch Class:Reason -> - 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, websocket_init, 3, Class, Reason, HandlerOpts, - cowboy_req:to_list(Req),erlang:get_stacktrace()]), - upgrade_error(Req, Env) - end. - -%% Only send an error reply if there is no resp_sent message. --spec upgrade_error(Req, Env) -> {ok, Req, Env} | {error, 400, Req} - when Req::cowboy_req:req(), Env::cowboy_middleware:env(). -upgrade_error(Req, Env) -> - receive - {cowboy_req, resp_sent} -> - {ok, Req, [{result, closed}|Env]} - after 0 -> - {error, 400, Req} + cowboy_req:maybe_reply(400, Req), + erlang:Class([ + {reason, Reason}, + {mfa, {Handler, websocket_init, 3}}, + {stacktrace, erlang:get_stacktrace()}, + {req, cowboy_req:to_list(Req)}, + {opts, HandlerOpts} + ]) end. -spec websocket_handshake(#state{}, Req, any()) @@ -601,8 +593,8 @@ websocket_dispatch(State, Req, HandlerState, RemainingData, 10, Payload) -> -> {ok, Req, cowboy_middleware:env()} | {suspend, module(), atom(), [any()]} when Req::cowboy_req:req(). -handler_call(State=#state{handler=Handler, handler_opts=HandlerOpts}, Req, - HandlerState, RemainingData, Callback, Message, NextState) -> +handler_call(State=#state{handler=Handler}, Req, HandlerState, + RemainingData, Callback, Message, NextState) -> try Handler:Callback(Message, Req, HandlerState) of {ok, Req2, HandlerState2} -> NextState(State, Req2, HandlerState2, RemainingData); @@ -656,15 +648,15 @@ handler_call(State=#state{handler=Handler, handler_opts=HandlerOpts}, Req, {shutdown, Req2, HandlerState2} -> websocket_close(State, Req2, HandlerState2, {normal, shutdown}) 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** Message was ~p~n" - "** Options were ~p~n** Handler state was ~p~n" - "** Request was ~p~n** Stacktrace: ~p~n~n", - [Handler, Callback, 3, Class, Reason, Message, HandlerOpts, - HandlerState, PLReq, erlang:get_stacktrace()]), - websocket_close(State, Req, HandlerState, {error, handler}) + _ = websocket_close(State, Req, HandlerState, {error, handler}), + erlang:Class([ + {reason, Reason}, + {mfa, {Handler, Callback, 3}}, + {stacktrace, erlang:get_stacktrace()}, + {msg, Message}, + {req, cowboy_req:to_list(Req)}, + {state, HandlerState} + ]) end. websocket_opcode(text) -> 1; @@ -763,19 +755,19 @@ websocket_close(State=#state{socket=Socket, transport=Transport}, -spec handler_terminate(#state{}, Req, any(), atom() | {atom(), atom()}) -> {ok, Req, cowboy_middleware:env()} when Req::cowboy_req:req(). -handler_terminate(#state{env=Env, handler=Handler, handler_opts=HandlerOpts}, +handler_terminate(#state{env=Env, handler=Handler}, Req, HandlerState, TerminateReason) -> try Handler:websocket_terminate(TerminateReason, Req, HandlerState) 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** Initial reason was ~p~n" - "** Options were ~p~n** Handler state was ~p~n" - "** Request was ~p~n** Stacktrace: ~p~n~n", - [Handler, websocket_terminate, 3, Class, Reason, TerminateReason, - HandlerOpts, HandlerState, PLReq, erlang:get_stacktrace()]) + erlang:Class([ + {reason, Reason}, + {mfa, {Handler, websocket_terminate, 3}}, + {stacktrace, erlang:get_stacktrace()}, + {req, cowboy_req:to_list(Req)}, + {state, HandlerState}, + {terminate_reason, TerminateReason} + ]) end, {ok, Req, [{result, closed}|Env]}. |