aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--README.md12
-rw-r--r--guide/toc.md3
-rw-r--r--src/cowboy.app.src3
-rw-r--r--src/cowboy_dispatcher.erl39
-rw-r--r--src/cowboy_protocol.erl14
-rw-r--r--src/cowboy_req.erl38
-rw-r--r--src/cowboy_rest.erl45
-rw-r--r--src/cowboy_static.erl4
-rw-r--r--src/cowboy_websocket.erl42
-rw-r--r--test/ws_SUITE.erl11
10 files changed, 137 insertions, 74 deletions
diff --git a/README.md b/README.md
index 418ed57..826f271 100644
--- a/README.md
+++ b/README.md
@@ -21,16 +21,16 @@ No parameterized module. No process dictionary. **Clean** Erlang code.
Quick start
-----------
-* Add Cowboy as a rebar dependency to your application.
-* Start Cowboy and add one or more listeners.
-* Write handlers for your application.
+ * Add Cowboy as a rebar dependency to your application
+ * Start Cowboy and add one or more listeners
+ * Write handlers for your application
Getting Started
---------------
-* [Read the guide](https://github.com/extend/cowboy/blob/master/guide/toc.md)
-* Look at the examples in the ```examples/``` directory
-* Build API documentation with ```make docs```; open ```doc/index.html```
+ * [Read the guide](http://ninenines.eu/docs/en/cowboy/HEAD/guide/introduction)
+ * Look at the examples in the `examples/` directory
+ * Build API documentation with `make docs`; open `doc/index.html`
diff --git a/guide/toc.md b/guide/toc.md
index ea543d1..b57b92e 100644
--- a/guide/toc.md
+++ b/guide/toc.md
@@ -8,9 +8,10 @@ Cowboy User Guide
* Getting started
* Routing
* Purpose
- * Dispatch rule
+ * Dispatch list
* Match rules
* Bindings
+ * Constraints
* Handlers
* Purpose
* Protocol upgrades
diff --git a/src/cowboy.app.src b/src/cowboy.app.src
index b68ef14..d32262e 100644
--- a/src/cowboy.app.src
+++ b/src/cowboy.app.src
@@ -13,7 +13,10 @@
%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
{application, cowboy, [
+ {id, "Cowboy"},
{description, "Small, fast, modular HTTP server."},
+ {sub_description, "Cowboy is also a socket acceptor pool, "
+ "able to accept connections for any kind of TCP protocol."},
{vsn, "0.7.0"},
{modules, []},
{registered, [cowboy_clock, cowboy_sup]},
diff --git a/src/cowboy_dispatcher.erl b/src/cowboy_dispatcher.erl
index cfb8fb6..fae18fc 100644
--- a/src/cowboy_dispatcher.erl
+++ b/src/cowboy_dispatcher.erl
@@ -21,7 +21,7 @@
-type bindings() :: [{atom(), binary()}].
-type tokens() :: [binary()].
--type match_rule() :: '_' | '*' | [binary() | '_' | '...' | atom()].
+-type match_rule() :: '_' | <<_:8>> | [binary() | '_' | '...' | atom()].
-type dispatch_path() :: [{match_rule(), module(), any()}].
-type dispatch_rule() :: {Host::match_rule(), Path::dispatch_path()}.
-type dispatch_rules() :: [dispatch_rule()].
@@ -45,9 +45,10 @@
%% <em>PathRules</em> being a list of <em>{Path, HandlerMod, HandlerOpts}</em>.
%%
%% <em>Hostname</em> and <em>Path</em> are match rules and can be either the
-%% atom <em>'_'</em>, which matches everything for a single token, the atom
-%% <em>'*'</em>, which matches everything for the rest of the tokens, or a
-%% list of tokens. Each token can be either a binary, the atom <em>'_'</em>,
+%% atom <em>'_'</em>, which matches everything, <<"*">>, which match the
+%% wildcard path, or a list of tokens.
+%%
+%% Each token can be either a binary, the atom <em>'_'</em>,
%% the atom '...' or a named atom. A binary token must match exactly,
%% <em>'_'</em> matches everything for a single token, <em>'...'</em> matches
%% everything for the rest of the tokens and a named atom will bind the
@@ -67,7 +68,8 @@
-> {ok, module(), any(), bindings(),
HostInfo::undefined | tokens(),
PathInfo::undefined | tokens()}
- | {error, notfound, host} | {error, notfound, path}.
+ | {error, notfound, host} | {error, notfound, path}
+ | {error, badrequest, path}.
match([], _, _) ->
{error, notfound, host};
match([{'_', PathMatchs}|_Tail], _, Path) ->
@@ -91,12 +93,12 @@ match(Dispatch, Host, Path) ->
-> {ok, module(), any(), bindings(),
HostInfo::undefined | tokens(),
PathInfo::undefined | tokens()}
- | {error, notfound, path}.
+ | {error, notfound, path} | {error, badrequest, path}.
match_path([], _, _, _) ->
{error, notfound, path};
match_path([{'_', Handler, Opts}|_Tail], HostInfo, _, Bindings) ->
{ok, Handler, Opts, Bindings, HostInfo, undefined};
-match_path([{'*', Handler, Opts}|_Tail], HostInfo, '*', Bindings) ->
+match_path([{<<"*">>, Handler, Opts}|_Tail], HostInfo, <<"*">>, Bindings) ->
{ok, Handler, Opts, Bindings, HostInfo, undefined};
match_path([{PathMatch, Handler, Opts}|Tail], HostInfo, Tokens,
Bindings) when is_list(Tokens) ->
@@ -106,6 +108,8 @@ match_path([{PathMatch, Handler, Opts}|Tail], HostInfo, Tokens,
{true, PathBinds, PathInfo} ->
{ok, Handler, Opts, Bindings ++ PathBinds, HostInfo, PathInfo}
end;
+match_path(_Dispatch, _HostInfo, badrequest, _Bindings) ->
+ {error, badrequest, path};
match_path(Dispatch, HostInfo, Path, Bindings) ->
match_path(Dispatch, HostInfo, split_path(Path), Bindings).
@@ -138,14 +142,19 @@ split_path(<< $/, Path/bits >>) ->
split_path(Path, []).
split_path(Path, Acc) ->
- case binary:match(Path, <<"/">>) of
- nomatch when Path =:= <<>> ->
- lists:reverse([cowboy_http:urldecode(S) || S <- Acc]);
- nomatch ->
- lists:reverse([cowboy_http:urldecode(S) || S <- [Path|Acc]]);
- {Pos, _} ->
- << Segment:Pos/binary, _:8, Rest/bits >> = Path,
- split_path(Rest, [Segment|Acc])
+ try
+ case binary:match(Path, <<"/">>) of
+ nomatch when Path =:= <<>> ->
+ lists:reverse([cowboy_http:urldecode(S) || S <- Acc]);
+ nomatch ->
+ lists:reverse([cowboy_http:urldecode(S) || S <- [Path|Acc]]);
+ {Pos, _} ->
+ << Segment:Pos/binary, _:8, Rest/bits >> = Path,
+ split_path(Rest, [Segment|Acc])
+ end
+ catch
+ error:badarg ->
+ badrequest
end.
-spec list_match(tokens(), match_rule(), bindings())
diff --git a/src/cowboy_protocol.erl b/src/cowboy_protocol.erl
index c5ea561..f759d88 100644
--- a/src/cowboy_protocol.erl
+++ b/src/cowboy_protocol.erl
@@ -440,19 +440,19 @@ request(Buffer, State=#state{socket=Socket, transport=Transport,
Req = cowboy_req:new(Socket, Transport, Method, Path, Query, Fragment,
Version, Headers, Host, Port, Buffer, ReqKeepalive < MaxKeepalive,
OnResponse),
- onrequest(Req, State, Host, Path).
+ onrequest(Req, State, Host).
%% Call the global onrequest callback. The callback can send a reply,
%% in which case we consider the request handled and move on to the next
%% one. Note that since we haven't dispatched yet, we don't know the
%% handler, host_info, path_info or bindings yet.
--spec onrequest(cowboy_req:req(), #state{}, binary(), binary()) -> ok.
-onrequest(Req, State=#state{onrequest=undefined}, Host, Path) ->
- dispatch(Req, State, Host, Path);
-onrequest(Req, State=#state{onrequest=OnRequest}, Host, Path) ->
+-spec onrequest(cowboy_req:req(), #state{}, binary()) -> ok.
+onrequest(Req, State=#state{onrequest=undefined}, Host) ->
+ dispatch(Req, State, Host, cowboy_req:get(path, Req));
+onrequest(Req, State=#state{onrequest=OnRequest}, Host) ->
Req2 = OnRequest(Req),
case cowboy_req:get(resp_state, Req2) of
- waiting -> dispatch(Req2, State, Host, Path);
+ waiting -> dispatch(Req2, State, Host, cowboy_req:get(path, Req2));
_ -> next_request(Req2, State, ok)
end.
@@ -464,6 +464,8 @@ dispatch(Req, State=#state{dispatch=Dispatch}, Host, Path) ->
handler_init(Req2, State, Handler, Opts);
{error, notfound, host} ->
error_terminate(400, State);
+ {error, badrequest, path} ->
+ error_terminate(400, State);
{error, notfound, path} ->
error_terminate(404, State)
end.
diff --git a/src/cowboy_req.erl b/src/cowboy_req.erl
index 67d2a0f..2d45a59 100644
--- a/src/cowboy_req.erl
+++ b/src/cowboy_req.erl
@@ -183,15 +183,13 @@ new(Socket, Transport, Method, Path, Query, Fragment,
method=Method, path=Path, qs=Query, fragment=Fragment, version=Version,
headers=Headers, host=Host, port=Port, buffer=Buffer,
onresponse=OnResponse},
- case CanKeepalive of
+ case CanKeepalive and (Version =:= {1, 1}) of
false ->
Req#http_req{connection=close};
true ->
case lists:keyfind(<<"connection">>, 1, Headers) of
- false when Version =:= {1, 1} ->
- Req; %% keepalive
false ->
- Req#http_req{connection=close};
+ Req; %% keepalive
{_, ConnectionHeader} ->
Tokens = parse_connection_before(ConnectionHeader, []),
Connection = connection_to_atom(Tokens),
@@ -1138,8 +1136,18 @@ response_merge_headers(Headers, RespHeaders, DefaultHeaders) ->
-spec merge_headers(cowboy_http:headers(), cowboy_http:headers())
-> cowboy_http:headers().
+
+%% Merge headers by prepending the tuples in the second list to the
+%% first list. It also handles Set-Cookie properly, which supports
+%% duplicated entries. Notice that, while the RFC2109 does allow more
+%% than one cookie to be set per Set-Cookie header, we are following
+%% the implementation of common web servers and applications which
+%% return many distinct headers per each Set-Cookie entry to avoid
+%% issues with clients/browser which may not support it.
merge_headers(Headers, []) ->
Headers;
+merge_headers(Headers, [{<<"set-cookie">>, Value}|Tail]) ->
+ merge_headers([{<<"set-cookie">>, Value}|Headers], Tail);
merge_headers(Headers, [{Name, Value}|Tail]) ->
Headers2 = case lists:keymember(Name, 1, Headers) of
true -> Headers;
@@ -1333,4 +1341,26 @@ connection_to_atom_test_() ->
[{lists:flatten(io_lib:format("~p", [T])),
fun() -> R = connection_to_atom(T) end} || {T, R} <- Tests].
+merge_headers_test() ->
+ Left0 = [{<<"content-length">>,<<"13">>},{<<"server">>,<<"Cowboy">>}],
+ Right0 = [{<<"set-cookie">>,<<"foo=bar">>},{<<"content-length">>,<<"11">>}],
+
+ ?assertMatch(
+ [{<<"set-cookie">>,<<"foo=bar">>},
+ {<<"content-length">>,<<"13">>},
+ {<<"server">>,<<"Cowboy">>}],
+ merge_headers(Left0, Right0)),
+
+ Left1 = [{<<"content-length">>,<<"13">>},{<<"server">>,<<"Cowboy">>}],
+ Right1 = [{<<"set-cookie">>,<<"foo=bar">>},{<<"set-cookie">>,<<"bar=baz">>}],
+
+ ?assertMatch(
+ [{<<"set-cookie">>,<<"bar=baz">>},
+ {<<"set-cookie">>,<<"foo=bar">>},
+ {<<"content-length">>,<<"13">>},
+ {<<"server">>,<<"Cowboy">>}],
+ merge_headers(Left1, Right1)),
+
+ ok.
+
-endif.
diff --git a/src/cowboy_rest.erl b/src/cowboy_rest.erl
index ff3d186..2b1bda5 100644
--- a/src/cowboy_rest.erl
+++ b/src/cowboy_rest.erl
@@ -73,7 +73,7 @@ upgrade(_ListenerPid, Handler, Opts, Req) ->
catch Class:Reason ->
PLReq = cowboy_req:to_list(Req),
error_logger:error_msg(
- "** Handler ~p terminating in rest_init/3~n"
+ "** Handler ~p terminating in rest_init/2~n"
" for the reason ~p:~p~n** Options were ~p~n"
"** Request was ~p~n** Stacktrace: ~p~n~n",
[Handler, Class, Reason, Opts, PLReq, erlang:get_stacktrace()]),
@@ -126,7 +126,7 @@ allowed_methods(Req, State=#state{method=Method}) ->
method_not_allowed(Req, State, Methods) ->
Req2 = cowboy_req:set_resp_header(
- <<"Allow">>, method_not_allowed_build(Methods, []), Req),
+ <<"allow">>, method_not_allowed_build(Methods, []), Req),
respond(Req2, State, 405).
method_not_allowed_build([], []) ->
@@ -153,7 +153,7 @@ is_authorized(Req, State) ->
forbidden(Req2, State#state{handler_state=HandlerState});
{{false, AuthHead}, Req2, HandlerState} ->
Req3 = cowboy_req:set_resp_header(
- <<"Www-Authenticate">>, AuthHead, Req2),
+ <<"www-authenticate">>, AuthHead, Req2),
respond(Req3, State#state{handler_state=HandlerState}, 401)
end.
@@ -166,7 +166,7 @@ valid_content_headers(Req, State) ->
known_content_type(Req, State) ->
expect(Req, State, known_content_type, true,
- fun valid_entity_length/2, 413).
+ fun valid_entity_length/2, 415).
valid_entity_length(Req, State) ->
expect(Req, State, valid_entity_length, true, fun options/2, 413).
@@ -195,8 +195,9 @@ options(Req, State) ->
%%
%% Note that it is also possible to return a binary content type that will
%% then be parsed by Cowboy. However note that while this may make your
-%% resources a little more readable, this is a lot less efficient. An example
-%% of such a return value would be:
+%% resources a little more readable, this is a lot less efficient.
+%%
+%% An example of such return value would be:
%% {<<"text/html">>, to_html}
content_types_provided(Req, State) ->
case call(Req, State, content_types_provided) of
@@ -351,7 +352,7 @@ match_language(Req, State, Accept, [Provided|Tail],
end.
set_language(Req, State=#state{language_a=Language}) ->
- Req2 = cowboy_req:set_resp_header(<<"Content-Language">>, Language, Req),
+ Req2 = cowboy_req:set_resp_header(<<"content-language">>, Language, Req),
charsets_provided(cowboy_req:set_meta(language, Language, Req2), State).
%% charsets_provided should return a list of binary values indicating
@@ -415,7 +416,7 @@ set_content_type(Req, State=#state{
undefined -> ContentType;
Charset -> [ContentType, <<"; charset=">>, Charset]
end,
- Req2 = cowboy_req:set_resp_header(<<"Content-Type">>, ContentType2, Req),
+ Req2 = cowboy_req:set_resp_header(<<"content-type">>, ContentType2, Req),
encodings_provided(cowboy_req:set_meta(charset, Charset, Req2), State).
set_content_type_build_params([], []) ->
@@ -446,17 +447,17 @@ variances(Req, State=#state{content_types_p=CTP,
Variances = case CTP of
[] -> [];
[_] -> [];
- [_|_] -> [<<"Accept">>]
+ [_|_] -> [<<"accept">>]
end,
Variances2 = case LP of
[] -> Variances;
[_] -> Variances;
- [_|_] -> [<<"Accept-Language">>|Variances]
+ [_|_] -> [<<"accept-language">>|Variances]
end,
Variances3 = case CP of
[] -> Variances2;
[_] -> Variances2;
- [_|_] -> [<<"Accept-Charset">>|Variances2]
+ [_|_] -> [<<"accept-charset">>|Variances2]
end,
{Variances4, Req3, State2} = case call(Req, State, variances) of
no_call ->
@@ -470,13 +471,13 @@ variances(Req, State=#state{content_types_p=CTP,
resource_exists(Req3, State2);
[[<<", ">>, H]|Variances5] ->
Req4 = cowboy_req:set_resp_header(
- <<"Vary">>, [H|Variances5], Req3),
+ <<"vary">>, [H|Variances5], Req3),
resource_exists(Req4, State2)
end.
resource_exists(Req, State) ->
expect(Req, State, resource_exists, true,
- fun if_match_exists/2, fun if_match_musnt_exist/2).
+ fun if_match_exists/2, fun if_match_must_not_exist/2).
if_match_exists(Req, State) ->
case cowboy_req:parse_header(<<"if-match">>, Req) of
@@ -496,7 +497,7 @@ if_match(Req, State, EtagsList) ->
false -> precondition_failed(Req2, State2)
end.
-if_match_musnt_exist(Req, State) ->
+if_match_must_not_exist(Req, State) ->
case cowboy_req:header(<<"if-match">>, Req) of
{undefined, Req2} -> is_put_to_missing_resource(Req2, State);
{_Any, Req2} -> precondition_failed(Req2, State)
@@ -577,7 +578,7 @@ if_modified_since(Req, State, IfModifiedSince) ->
end.
not_modified(Req, State) ->
- Req2 = cowboy_req:delete_resp_header(<<"Content-Type">>, Req),
+ Req2 = cowboy_req:delete_resp_header(<<"content-type">>, Req),
{Req3, State2} = set_resp_etag(Req2, State),
{Req4, State3} = set_resp_expires(Req3, State2),
respond(Req4, State3, 304).
@@ -596,7 +597,7 @@ moved_permanently(Req, State, OnFalse) ->
case call(Req, State, moved_permanently) of
{{true, Location}, Req2, HandlerState} ->
Req3 = cowboy_req:set_resp_header(
- <<"Location">>, Location, Req2),
+ <<"location">>, Location, Req2),
respond(Req3, State#state{handler_state=HandlerState}, 301);
{false, Req2, HandlerState} ->
OnFalse(Req2, State#state{handler_state=HandlerState});
@@ -617,7 +618,7 @@ moved_temporarily(Req, State) ->
case call(Req, State, moved_temporarily) of
{{true, Location}, Req2, HandlerState} ->
Req3 = cowboy_req:set_resp_header(
- <<"Location">>, Location, Req2),
+ <<"location">>, Location, Req2),
respond(Req3, State#state{handler_state=HandlerState}, 307);
{false, Req2, HandlerState} ->
is_post_to_missing_resource(Req2, State#state{handler_state=HandlerState}, 410);
@@ -670,7 +671,7 @@ create_path(Req, State) ->
{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),
+ <<"location">>, << HostURL/binary, Path/binary >>, Req3),
put_resource(cowboy_req:set_meta(put_path, Path, Req4),
State2, 303)
end.
@@ -744,7 +745,7 @@ choose_content_type(Req, State, OnTrue, ContentType, [_Any|Tail]) ->
%% This is easily testable because we would have set the Location
%% header by this point if we did so.
is_new_resource(Req, State) ->
- case cowboy_req:has_resp_header(<<"Location">>, Req) of
+ case cowboy_req:has_resp_header(<<"location">>, Req) of
true -> respond(Req, State, 201);
false -> has_resp_body(Req, State)
end.
@@ -767,7 +768,7 @@ set_resp_body(Req, State=#state{content_type_a={_Type, Fun}}) ->
LastModified ->
LastModifiedStr = httpd_util:rfc1123_date(LastModified),
Req4 = cowboy_req:set_resp_header(
- <<"Last-Modified">>, LastModifiedStr, Req3)
+ <<"last-modified">>, LastModifiedStr, Req3)
end,
{Req5, State4} = set_resp_expires(Req4, State3),
case call(Req5, State4, Fun) of
@@ -796,7 +797,7 @@ set_resp_etag(Req, State) ->
{Req2, State2};
Etag ->
Req3 = cowboy_req:set_resp_header(
- <<"ETag">>, encode_etag(Etag), Req2),
+ <<"etag">>, encode_etag(Etag), Req2),
{Req3, State2}
end.
@@ -812,7 +813,7 @@ set_resp_expires(Req, State) ->
Expires ->
ExpiresStr = httpd_util:rfc1123_date(Expires),
Req3 = cowboy_req:set_resp_header(
- <<"Expires">>, ExpiresStr, Req2),
+ <<"expires">>, ExpiresStr, Req2),
{Req3, State2}
end.
diff --git a/src/cowboy_static.erl b/src/cowboy_static.erl
index 3b63afe..1b4ff89 100644
--- a/src/cowboy_static.erl
+++ b/src/cowboy_static.erl
@@ -158,7 +158,7 @@
%% {file, <<"index.html">>}]}
%%
%% %% Serve cowboy/priv/www/page.html under http://example.com/*/page
-%% {['*', <<"page">>], cowboy_static,
+%% {['_', <<"page">>], cowboy_static,
%% [{directory, {priv_dir, cowboy, [<<"www">>]}}
%% {file, <<"page.html">>}]}.
%%
@@ -451,7 +451,7 @@ path_to_mimetypes(Filepath, Extensions) when is_binary(Filepath) ->
-spec path_to_mimetypes_(binary(), [{binary(), [mimedef()]}]) -> [mimedef()].
path_to_mimetypes_(Ext, Extensions) ->
- case lists:keyfind(Ext, 1, Extensions) of
+ case lists:keyfind(cowboy_bstr:to_lower(Ext), 1, Extensions) of
{_, MTs} -> MTs;
_Unknown -> default_mimetype()
end.
diff --git a/src/cowboy_websocket.erl b/src/cowboy_websocket.erl
index 1c6d20c..515c6d4 100644
--- a/src/cowboy_websocket.erl
+++ b/src/cowboy_websocket.erl
@@ -452,22 +452,38 @@ handler_call(State=#state{handler=Handler, opts=Opts}, Req, HandlerState,
Req2, HandlerState2, RemainingData);
{reply, Payload, Req2, HandlerState2}
when is_tuple(Payload) ->
- ok = websocket_send(Payload, State),
- NextState(State, Req2, HandlerState2, RemainingData);
+ case websocket_send(Payload, State) of
+ ok ->
+ NextState(State, Req2, HandlerState2, RemainingData);
+ {error, _} = Error ->
+ handler_terminate(State, Req2, HandlerState2, Error)
+ end;
{reply, Payload, Req2, HandlerState2, hibernate}
when is_tuple(Payload) ->
- ok = websocket_send(Payload, State),
- NextState(State#state{hibernate=true},
- Req2, HandlerState2, RemainingData);
+ case websocket_send(Payload, State) of
+ ok ->
+ NextState(State#state{hibernate=true},
+ Req2, HandlerState2, RemainingData);
+ {error, _} = Error ->
+ handler_terminate(State, Req2, HandlerState2, Error)
+ end;
{reply, Payload, Req2, HandlerState2}
when is_list(Payload) ->
- ok = websocket_send_many(Payload, State),
- NextState(State, Req2, HandlerState2, RemainingData);
+ case websocket_send_many(Payload, State) of
+ ok ->
+ NextState(State, Req2, HandlerState2, RemainingData);
+ {error, _} = Error ->
+ handler_terminate(State, Req2, HandlerState2, Error)
+ end;
{reply, Payload, Req2, HandlerState2, hibernate}
when is_list(Payload) ->
- ok = websocket_send_many(Payload, State),
- NextState(State#state{hibernate=true},
- Req2, HandlerState2, RemainingData);
+ case websocket_send_many(Payload, State) of
+ ok ->
+ NextState(State#state{hibernate=true},
+ Req2, HandlerState2, RemainingData);
+ {error, _} = Error ->
+ handler_terminate(State, Req2, HandlerState2, Error)
+ end;
{shutdown, Req2, HandlerState2} ->
websocket_close(State, Req2, HandlerState2, {normal, shutdown})
catch Class:Reason ->
@@ -507,8 +523,10 @@ websocket_send({Type, Payload}, #state{socket=Socket, transport=Transport}) ->
websocket_send_many([], _) ->
ok;
websocket_send_many([Frame|Tail], State) ->
- ok = websocket_send(Frame, State),
- websocket_send_many(Tail, State).
+ case websocket_send(Frame, State) of
+ ok -> websocket_send_many(Tail, State);
+ Error -> Error
+ end.
-spec websocket_close(#state{}, cowboy_req:req(), any(), {atom(), atom()})
-> closed.
diff --git a/test/ws_SUITE.erl b/test/ws_SUITE.erl
index 9741329..7f1a18f 100644
--- a/test/ws_SUITE.erl
+++ b/test/ws_SUITE.erl
@@ -332,12 +332,11 @@ ws_send_many(Config) ->
{'Upgrade', "websocket"} = lists:keyfind('Upgrade', 1, Headers),
{"sec-websocket-accept", "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="}
= lists:keyfind("sec-websocket-accept", 1, Headers),
- {ok, << 1:1, 0:3, 1:4, 0:1, 3:7, "one" >>}
- = gen_tcp:recv(Socket, 0, 6000),
- {ok, << 1:1, 0:3, 1:4, 0:1, 3:7, "two" >>}
- = gen_tcp:recv(Socket, 0, 6000),
- {ok, << 1:1, 0:3, 1:4, 0:1, 6:7, "seven!" >>}
- = gen_tcp:recv(Socket, 0, 6000),
+ %% We catch all frames at once and check them directly.
+ {ok, Many} = gen_tcp:recv(Socket, 18, 6000),
+ << 1:1, 0:3, 1:4, 0:1, 3:7, "one",
+ 1:1, 0:3, 1:4, 0:1, 3:7, "two",
+ 1:1, 0:3, 1:4, 0:1, 6:7, "seven!" >> = Many,
ok = gen_tcp:send(Socket, << 1:1, 0:3, 8:4, 0:8 >>), %% close
{ok, << 1:1, 0:3, 8:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000),
{error, closed} = gen_tcp:recv(Socket, 0, 6000),