Skip to content
Browse files

Make #http_req{} opaque and remove include/http.hrl

  • Loading branch information...
1 parent 35be9e6 commit e6d75cbb23fb363be1d0599c49b686b85a3a5ea1 Loïc Hoguin committed Jun 4, 2012
Showing with 553 additions and 345 deletions.
  1. +0 −58 include/http.hrl
  2. +160 −130 src/cowboy_protocol.erl
  3. +245 −23 src/cowboy_req.erl
  4. +48 −45 src/cowboy_rest.erl
  5. +15 −15 src/cowboy_static.erl
  6. +85 −74 src/cowboy_websocket.erl
View
58 include/http.hrl
@@ -1,58 +0,0 @@
-%% Copyright (c) 2011-2012, Loïc Hoguin <essen@ninenines.eu>
-%% Copyright (c) 2011, Anthony Ramine <nox@dev-extend.eu>
-%%
-%% Permission to use, copy, modify, and/or distribute this software for any
-%% purpose with or without fee is hereby granted, provided that the above
-%% copyright notice and this permission notice appear in all copies.
-%%
-%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
-%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
-%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
-%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
-%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
-%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
-%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-
--record(http_req, {
- %% Transport.
- socket = undefined :: undefined | inet:socket(),
- transport = undefined :: undefined | module(),
- connection = keepalive :: keepalive | close,
-
- %% Request.
- pid = undefined :: pid(),
- method = 'GET' :: cowboy_http:method(),
- version = {1, 1} :: cowboy_http:version(),
- peer = undefined :: undefined |
- {inet:ip_address(), inet:port_number()},
- host = undefined :: undefined | cowboy_dispatcher:tokens(),
- host_info = undefined :: undefined | cowboy_dispatcher:tokens(),
- raw_host = undefined :: undefined | binary(),
- port = undefined :: undefined | inet:port_number(),
- path = undefined :: undefined | '*' | cowboy_dispatcher:tokens(),
- path_info = undefined :: undefined | cowboy_dispatcher:tokens(),
- raw_path = undefined :: undefined | binary(),
- qs_vals = undefined :: undefined | list({binary(), binary() | true}),
- raw_qs = undefined :: undefined | binary(),
- bindings = undefined :: undefined | cowboy_dispatcher:bindings(),
- headers = [] :: cowboy_http:headers(),
- p_headers = [] :: [any()], %% @todo Improve those specs.
- cookies = undefined :: undefined | [{binary(), binary()}],
- meta = [] :: [{atom(), any()}],
-
- %% Request body.
- body_state = waiting :: waiting | done | {stream, fun(), any(), fun()}
- | {multipart, non_neg_integer(), fun()},
- buffer = <<>> :: binary(),
-
- %% Response.
- resp_state = waiting :: locked | waiting | chunks | done,
- resp_headers = [] :: cowboy_http:headers(),
- resp_body = <<>> :: iodata() | {non_neg_integer(),
- fun(() -> {sent, non_neg_integer()})},
-
- %% Functions.
- onresponse = undefined :: undefined | fun((cowboy_http:status(),
- cowboy_http:headers(), #http_req{}) -> #http_req{}),
- urldecode :: {fun((binary(), T) -> binary()), T}
-}).
View
290 src/cowboy_protocol.erl
@@ -42,29 +42,43 @@
-export([parse_request/1]).
-export([handler_loop/3]).
--include("http.hrl").
-include_lib("eunit/include/eunit.hrl").
+-type onrequest_fun() :: fun((Req) -> Req). %% when Req::req()
+-type onresponse_fun() :: fun((cowboy_http:status(),
+ cowboy_http:headers(), Req) -> Req). %% when Req::req()
+-type urldecode_fun() :: {fun((binary(), T) -> binary()), T}.
+
+-export_type([onrequest_fun/0]).
+-export_type([onresponse_fun/0]).
+-export_type([urldecode_fun/0]).
+
-record(state, {
+ %% Socket.
listener :: pid(),
socket :: inet:socket(),
transport :: module(),
- dispatch :: cowboy_dispatcher:dispatch_rules(),
- handler :: {module(), any()},
- onrequest :: undefined | fun((#http_req{}) -> #http_req{}),
- onresponse = undefined :: undefined | fun((cowboy_http:status(),
- cowboy_http:headers(), #http_req{}) -> #http_req{}),
- urldecode :: {fun((binary(), T) -> binary()), T},
- req_empty_lines = 0 :: integer(),
- max_empty_lines :: integer(),
- req_keepalive = 1 :: integer(),
- max_keepalive :: integer(),
- max_line_length :: integer(),
- timeout :: timeout(),
+
+ %% States and buffers.
buffer = <<>> :: binary(),
+ handler :: {module(), any()},
hibernate = false :: boolean(),
loop_timeout = infinity :: timeout(),
- loop_timeout_ref :: undefined | reference()
+ loop_timeout_ref :: undefined | reference(),
+ req_empty_lines = 0 :: integer(),
+ req_keepalive = 1 :: integer(),
+
+ %% Configuration.
+ dispatch = undefined :: cowboy_dispatcher:dispatch_rules(),
+ max_empty_lines = undefined :: integer(),
+ max_keepalive = undefined :: integer(),
+ max_line_length = undefined :: integer(),
+ timeout = undefined :: timeout(),
+
+ %% Callbacks.
+ onrequest = undefined :: undefined | onrequest_fun(),
+ onresponse = undefined :: undefined | onresponse_fun(),
+ urldecode = undefined :: urldecode_fun()
}).
%% API.
@@ -87,8 +101,8 @@ init(ListenerPid, Socket, Transport, Opts) ->
OnRequest = proplists:get_value(onrequest, Opts),
OnResponse = proplists:get_value(onresponse, Opts),
Timeout = proplists:get_value(timeout, Opts, 5000),
- URLDecDefault = {fun cowboy_http:urldecode/2, crash},
- URLDec = proplists:get_value(urldecode, Opts, URLDecDefault),
+ URLDec = proplists:get_value(urldecode, Opts,
+ {fun cowboy_http:urldecode/2, crash}),
ok = ranch:accept_ack(ListenerPid),
wait_request(#state{listener=ListenerPid, socket=Socket, transport=Transport,
dispatch=Dispatch, max_empty_lines=MaxEmptyLines,
@@ -132,25 +146,23 @@ request({http_request, Method, {abs_path, AbsPath}, Version},
req_keepalive=Keepalive, max_keepalive=MaxKeepalive,
onresponse=OnResponse, urldecode={URLDecFun, URLDecArg}=URLDec}) ->
URLDecode = fun(Bin) -> URLDecFun(Bin, URLDecArg) end,
- {Path, RawPath, Qs} = cowboy_dispatcher:split_path(AbsPath, URLDecode),
+ {Path, RawPath, RawQs} = cowboy_dispatcher:split_path(AbsPath, URLDecode),
ConnAtom = if Keepalive < MaxKeepalive -> version_to_connection(Version);
true -> close
end,
- parse_header(#http_req{socket=Socket, transport=Transport,
- connection=ConnAtom, pid=self(), method=Method, version=Version,
- path=Path, raw_path=RawPath, raw_qs=Qs, onresponse=OnResponse,
- urldecode=URLDec}, State);
+ Req = cowboy_req:new(Socket, Transport, ConnAtom,
+ Method, Version, Path, RawPath, RawQs, OnResponse, URLDec),
+ parse_header(Req, State);
request({http_request, Method, '*', Version},
State=#state{socket=Socket, transport=Transport,
req_keepalive=Keepalive, max_keepalive=MaxKeepalive,
onresponse=OnResponse, urldecode=URLDec}) ->
ConnAtom = if Keepalive < MaxKeepalive -> version_to_connection(Version);
true -> close
end,
- parse_header(#http_req{socket=Socket, transport=Transport,
- connection=ConnAtom, pid=self(), method=Method, version=Version,
- path='*', raw_path= <<"*">>, raw_qs= <<>>, onresponse=OnResponse,
- urldecode=URLDec}, State);
+ Req = cowboy_req:new(Socket, Transport, ConnAtom,
+ Method, Version, '*', <<"*">>, <<>>, OnResponse, URLDec),
+ parse_header(Req, State);
request({http_request, _Method, _URI, _Version}, State) ->
error_terminate(501, State);
request({http_error, <<"\r\n">>},
@@ -161,102 +173,108 @@ request({http_error, <<"\r\n">>}, State=#state{req_empty_lines=N}) ->
request(_Any, State) ->
error_terminate(400, State).
--spec parse_header(#http_req{}, #state{}) -> ok.
+-spec parse_header(cowboy_req:req(), #state{}) -> ok.
parse_header(Req, State=#state{buffer=Buffer, max_line_length=MaxLength}) ->
case erlang:decode_packet(httph_bin, Buffer, []) of
{ok, Header, Rest} -> header(Header, Req, State#state{buffer=Rest});
{more, _Length} when byte_size(Buffer) > MaxLength ->
- error_terminate(413, State);
+ error_terminate(413, Req, State);
{more, _Length} -> wait_header(Req, State);
- {error, _Reason} -> error_terminate(400, State)
+ {error, _Reason} -> error_terminate(400, Req, State)
end.
--spec wait_header(#http_req{}, #state{}) -> ok.
+-spec wait_header(cowboy_req:req(), #state{}) -> ok.
wait_header(Req, State=#state{socket=Socket,
transport=Transport, timeout=T, buffer=Buffer}) ->
case Transport:recv(Socket, 0, T) of
{ok, Data} -> parse_header(Req, State#state{
buffer= << Buffer/binary, Data/binary >>});
- {error, timeout} -> error_terminate(408, State);
+ {error, timeout} -> error_terminate(408, Req, State);
{error, closed} -> terminate(State)
end.
-spec header({http_header, integer(), cowboy_http:header(), any(), binary()}
- | http_eoh, #http_req{}, #state{}) -> ok.
-header({http_header, _I, 'Host', _R, RawHost}, Req=#http_req{
- transport=Transport, host=undefined}, State) ->
- RawHost2 = cowboy_bstr:to_lower(RawHost),
- case catch cowboy_dispatcher:split_host(RawHost2) of
- {Host, RawHost3, undefined} ->
- Port = default_port(Transport:name()),
- parse_header(Req#http_req{
- host=Host, raw_host=RawHost3, port=Port,
- headers=[{'Host', RawHost3}|Req#http_req.headers]}, State);
- {Host, RawHost3, Port} ->
- parse_header(Req#http_req{
- host=Host, raw_host=RawHost3, port=Port,
- headers=[{'Host', RawHost3}|Req#http_req.headers]}, State);
- {'EXIT', _Reason} ->
- error_terminate(400, State)
+ | http_eoh, cowboy_req:req(), #state{}) -> ok.
+header({http_header, _I, 'Host', _R, RawHost}, Req,
+ State=#state{transport=Transport}) ->
+ case cowboy_req:get_raw_host(Req) of
+ undefined ->
+ RawHost2 = cowboy_bstr:to_lower(RawHost),
+ case catch cowboy_dispatcher:split_host(RawHost2) of
+ {Host, RawHost3, Port} ->
+ Port2 = case Port of
+ undefined -> default_port(Transport:name());
+ Port -> Port
+ end,
+ Req2 = cowboy_req:set_host(Host, RawHost3, Port2, Req),
+ Req3 = cowboy_req:add_header('Host', RawHost3, Req2),
+ parse_header(Req3, State);
+ {'EXIT', _} ->
+ error_terminate(400, Req, State)
+ end;
+ %% Ignore the Host header if we already have it.
+ _ ->
+ parse_header(Req, State)
end;
-%% Ignore Host headers if we already have it.
-header({http_header, _I, 'Host', _R, _V}, Req, State) ->
- parse_header(Req, State);
header({http_header, _I, 'Connection', _R, Connection},
- Req=#http_req{headers=Headers}, State=#state{
+ Req, State=#state{
req_keepalive=Keepalive, max_keepalive=MaxKeepalive})
when Keepalive < MaxKeepalive ->
- Req2 = Req#http_req{headers=[{'Connection', Connection}|Headers]},
- {ConnTokens, Req3}
- = cowboy_req:parse_header('Connection', Req2),
+ Req2 = cowboy_req:add_header('Connection', Connection, Req),
+ {ConnTokens, Req3} = cowboy_req:parse_header('Connection', Req2),
ConnAtom = cowboy_http:connection_to_atom(ConnTokens),
- parse_header(Req3#http_req{connection=ConnAtom}, State);
+ Req4 = cowboy_req:set_connection(ConnAtom, Req3),
+ parse_header(Req4, State);
header({http_header, _I, Field, _R, Value}, Req, State) ->
- Field2 = format_header(Field),
- parse_header(Req#http_req{headers=[{Field2, Value}|Req#http_req.headers]},
- State);
-%% The Host header is required in HTTP/1.1.
-header(http_eoh, #http_req{version={1, 1}, host=undefined}, State) ->
- error_terminate(400, State);
-%% It is however optional in HTTP/1.0.
-header(http_eoh, Req=#http_req{version={1, 0}, transport=Transport,
- host=undefined}, State=#state{buffer=Buffer}) ->
- Port = default_port(Transport:name()),
- onrequest(Req#http_req{host=[], raw_host= <<>>,
- port=Port, buffer=Buffer}, State#state{buffer= <<>>});
-header(http_eoh, Req, State=#state{buffer=Buffer}) ->
- onrequest(Req#http_req{buffer=Buffer}, State#state{buffer= <<>>});
-header(_Any, _Req, State) ->
- error_terminate(400, State).
+ Req2 = cowboy_req:add_header(format_header(Field), Value, Req),
+ parse_header(Req2, State);
+header(http_eoh, Req, State=#state{transport=Transport, buffer=Buffer}) ->
+ Host = cowboy_req:get_raw_host(Req),
+ Version = cowboy_req:get_version(Req),
+ %% The Host header is required in HTTP/1.1.
+ if Version =:= {1, 1}, Host =:= undefined ->
+ error_terminate(400, Req, State);
+ %% It is however optional in HTTP/1.0.
+ Version =:= {1, 0}, Host =:= undefined ->
+ Port = default_port(Transport:name()),
+ Req2 = cowboy_req:set_host([], <<>>, Port, Req),
+ Req3 = cowboy_req:set_buffer(Buffer, Req2),
+ onrequest(Req3, State#state{buffer= <<>>});
+ true ->
+ Req2 = cowboy_req:set_buffer(Buffer, Req),
+ onrequest(Req2, State#state{buffer= <<>>})
+ end;
+header(_, Req, State) ->
+ error_terminate(400, Req, State).
%% 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(#http_req{}, #state{}) -> ok.
+-spec onrequest(cowboy_req:req(), #state{}) -> ok.
onrequest(Req, State=#state{onrequest=undefined}) ->
dispatch(Req, State);
onrequest(Req, State=#state{onrequest=OnRequest}) ->
Req2 = OnRequest(Req),
- case Req2#http_req.resp_state of
+ case cowboy_req:get_resp_state(Req2) of
waiting -> dispatch(Req2, State);
_ -> next_request(Req2, State, ok)
end.
--spec dispatch(#http_req{}, #state{}) -> ok.
-dispatch(Req=#http_req{host=Host, path=Path},
- State=#state{dispatch=Dispatch}) ->
- case cowboy_dispatcher:match(Host, Path, Dispatch) of
- {ok, Handler, Opts, Binds, HostInfo, PathInfo} ->
- handler_init(Req#http_req{host_info=HostInfo, path_info=PathInfo,
- bindings=Binds}, State#state{handler={Handler, Opts}});
+-spec dispatch(cowboy_req:req(), #state{}) -> ok.
+dispatch(Req, State=#state{dispatch=Dispatch}) ->
+ case cowboy_dispatcher:match(cowboy_req:get_host(Req),
+ cowboy_req:get_path(Req), Dispatch) of
+ {ok, Handler, Opts, Bindings, HostInfo, PathInfo} ->
+ Req2 = cowboy_req:set_bindings(Bindings, HostInfo, PathInfo, Req),
+ handler_init(Req2, State#state{handler={Handler, Opts}});
{error, notfound, host} ->
- error_terminate(400, State);
+ error_terminate(400, Req, State);
{error, notfound, path} ->
- error_terminate(404, State)
+ error_terminate(404, Req, State)
end.
--spec handler_init(#http_req{}, #state{}) -> ok.
+-spec handler_init(cowboy_req:req(), #state{}) -> ok.
handler_init(Req, State=#state{transport=Transport,
handler={Handler, Opts}}) ->
try Handler:init({Transport:name(), http}, Req, Opts) of
@@ -279,45 +297,44 @@ handler_init(Req, State=#state{transport=Transport,
{upgrade, protocol, Module} ->
upgrade_protocol(Req, State, Module)
catch Class:Reason ->
- error_terminate(500, State),
- PLReq = lists:zip(record_info(fields, http_req), tl(tuple_to_list(Req))),
+ error_terminate(500, Req, State),
error_logger:error_msg(
"** Handler ~p terminating in init/3~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()])
+ [Handler, Class, Reason, Opts,
+ cowboy_req:to_proplist(Req), erlang:get_stacktrace()])
end.
--spec upgrade_protocol(#http_req{}, #state{}, atom()) -> ok.
+-spec upgrade_protocol(cowboy_req:req(), #state{}, atom()) -> ok.
upgrade_protocol(Req, State=#state{listener=ListenerPid,
handler={Handler, Opts}}, Module) ->
case Module:upgrade(ListenerPid, Handler, Opts, Req) of
{UpgradeRes, Req2} -> next_request(Req2, State, UpgradeRes);
_Any -> terminate(State)
end.
--spec handler_handle(any(), #http_req{}, #state{}) -> ok.
+-spec handler_handle(any(), cowboy_req:req(), #state{}) -> ok.
handler_handle(HandlerState, Req, State=#state{handler={Handler, Opts}}) ->
try Handler:handle(Req, HandlerState) of
{ok, Req2, HandlerState2} ->
terminate_request(HandlerState2, Req2, State)
catch Class:Reason ->
- PLReq = lists:zip(record_info(fields, http_req), tl(tuple_to_list(Req))),
error_logger:error_msg(
"** Handler ~p terminating in handle/2~n"
" for the reason ~p:~p~n"
"** Options were ~p~n** Handler state was ~p~n"
"** Request was ~p~n** Stacktrace: ~p~n~n",
- [Handler, Class, Reason, Opts,
- HandlerState, PLReq, erlang:get_stacktrace()]),
+ [Handler, Class, Reason, Opts, HandlerState,
+ cowboy_req:to_proplist(Req), erlang:get_stacktrace()]),
handler_terminate(HandlerState, Req, State),
- error_terminate(500, State)
+ error_terminate(500, Req, State)
end.
%% We don't listen for Transport closes because that would force us
%% to receive data and buffer it indefinitely.
--spec handler_before_loop(any(), #http_req{}, #state{}) -> ok.
+-spec handler_before_loop(any(), cowboy_req:req(), #state{}) -> ok.
handler_before_loop(HandlerState, Req, State=#state{hibernate=true}) ->
State2 = handler_loop_timeout(State),
catch erlang:hibernate(?MODULE, handler_loop,
@@ -338,7 +355,7 @@ handler_loop_timeout(State=#state{loop_timeout=Timeout,
TRef = erlang:start_timer(Timeout, self(), ?MODULE),
State#state{loop_timeout_ref=TRef}.
--spec handler_loop(any(), #http_req{}, #state{}) -> ok.
+-spec handler_loop(any(), cowboy_req:req(), #state{}) -> ok.
handler_loop(HandlerState, Req, State=#state{loop_timeout_ref=TRef}) ->
receive
{timeout, TRef, ?MODULE} ->
@@ -349,7 +366,7 @@ handler_loop(HandlerState, Req, State=#state{loop_timeout_ref=TRef}) ->
handler_call(HandlerState, Req, State, Message)
end.
--spec handler_call(any(), #http_req{}, #state{}, any()) -> ok.
+-spec handler_call(any(), cowboy_req:req(), #state{}, any()) -> ok.
handler_call(HandlerState, Req, State=#state{handler={Handler, Opts}},
Message) ->
try Handler:info(Message, Req, HandlerState) of
@@ -361,43 +378,41 @@ handler_call(HandlerState, Req, State=#state{handler={Handler, Opts}},
handler_before_loop(HandlerState2, Req2,
State#state{hibernate=true})
catch Class:Reason ->
- PLReq = lists:zip(record_info(fields, http_req), tl(tuple_to_list(Req))),
error_logger:error_msg(
"** Handler ~p terminating in info/3~n"
" for the reason ~p:~p~n"
"** Options were ~p~n** Handler state was ~p~n"
"** Request was ~p~n** Stacktrace: ~p~n~n",
- [Handler, Class, Reason, Opts,
- HandlerState, PLReq, erlang:get_stacktrace()]),
+ [Handler, Class, Reason, Opts, HandlerState,
+ cowboy_req:to_proplist(Req), erlang:get_stacktrace()]),
handler_terminate(HandlerState, Req, State),
- error_terminate(500, State)
+ error_terminate(500, Req, State)
end.
--spec handler_terminate(any(), #http_req{}, #state{}) -> ok.
+-spec handler_terminate(any(), cowboy_req:req(), #state{}) -> ok.
handler_terminate(HandlerState, Req, #state{handler={Handler, Opts}}) ->
try
- Handler:terminate(Req#http_req{resp_state=locked}, HandlerState)
+ Handler:terminate(cowboy_req:lock(Req), HandlerState)
catch Class:Reason ->
- PLReq = lists:zip(record_info(fields, http_req), tl(tuple_to_list(Req))),
error_logger:error_msg(
"** Handler ~p terminating in terminate/2~n"
" for the reason ~p:~p~n"
"** Options were ~p~n** Handler state was ~p~n"
"** Request was ~p~n** Stacktrace: ~p~n~n",
- [Handler, Class, Reason, Opts,
- HandlerState, PLReq, erlang:get_stacktrace()])
+ [Handler, Class, Reason, Opts, HandlerState,
+ cowboy_req:to_proplist(Req), erlang:get_stacktrace()])
end.
--spec terminate_request(any(), #http_req{}, #state{}) -> ok.
+-spec terminate_request(any(), cowboy_req:req(), #state{}) -> ok.
terminate_request(HandlerState, Req, State) ->
HandlerRes = handler_terminate(HandlerState, Req, State),
next_request(Req, State, HandlerRes).
--spec next_request(#http_req{}, #state{}, any()) -> ok.
-next_request(Req=#http_req{connection=Conn}, State=#state{
- req_keepalive=Keepalive}, HandlerRes) ->
+-spec next_request(cowboy_req:req(), #state{}, any()) -> ok.
+next_request(Req, State=#state{req_keepalive=Keepalive}, HandlerRes) ->
RespRes = ensure_response(Req),
{BodyRes, Buffer} = ensure_body_processed(Req),
+ Conn = cowboy_req:get_connection(Req),
%% Flush the resp_sent message before moving on.
receive {cowboy_req, resp_sent} -> ok after 0 -> ok end,
case {HandlerRes, BodyRes, RespRes, Conn} of
@@ -409,47 +424,62 @@ next_request(Req=#http_req{connection=Conn}, State=#state{
terminate(State)
end.
--spec ensure_body_processed(#http_req{}) -> {ok | close, binary()}.
-ensure_body_processed(#http_req{body_state=done, buffer=Buffer}) ->
- {ok, Buffer};
-ensure_body_processed(Req=#http_req{body_state=waiting}) ->
+-spec ensure_body_processed(cowboy_req:req()) -> {ok | close, binary()}.
+ensure_body_processed(Req) ->
+ ensure_body_processed(cowboy_req:get_body_state(Req), Req).
+
+-spec ensure_body_processed(done | waiting | {multipart, _, _, _},
+ cowboy_req:req()) -> {ok | close, binary()}.
+ensure_body_processed(done, Req) ->
+ {ok, cowboy_req:get_buffer(Req)};
+ensure_body_processed(waiting, Req) ->
case cowboy_req:skip_body(Req) of
- {ok, Req2} -> {ok, Req2#http_req.buffer};
+ {ok, Req2} -> {ok, cowboy_req:get_buffer(Req2)};
{error, _Reason} -> {close, <<>>}
end;
-ensure_body_processed(Req=#http_req{body_state={multipart, _, _}}) ->
+ensure_body_processed({multipart, _, _}, Req) ->
{ok, Req2} = cowboy_req:multipart_skip(Req),
ensure_body_processed(Req2).
--spec ensure_response(#http_req{}) -> ok.
+-spec ensure_response(cowboy_req:req()) -> ok.
+ensure_response(Req) ->
+ ensure_response(cowboy_req:get_resp_state(Req),
+ cowboy_req:get_method(Req), Req).
+
+-spec ensure_response(done | waiting | chunks, cowboy_http:method(),
+ cowboy_req:req()) -> ok.
%% The handler has already fully replied to the client.
-ensure_response(#http_req{resp_state=done}) ->
+ensure_response(done, _, _) ->
ok;
%% No response has been sent but everything apparently went fine.
%% Reply with 204 No Content to indicate this.
-ensure_response(Req=#http_req{resp_state=waiting}) ->
- _ = cowboy_req:reply(204, [], [], Req),
+ensure_response(waiting, _, Req) ->
+ _ = cowboy_req:reply(204, Req),
ok;
%% Terminate the chunked body for HTTP/1.1 only.
-ensure_response(#http_req{method='HEAD', resp_state=chunks}) ->
- ok;
-ensure_response(#http_req{version={1, 0}, resp_state=chunks}) ->
- ok;
-ensure_response(#http_req{socket=Socket, transport=Transport,
- resp_state=chunks}) ->
- Transport:send(Socket, <<"0\r\n\r\n">>),
- ok.
+ensure_response(chunks, Method, Req) ->
+ Version = cowboy_req:get_version(Req),
+ if Method =:= 'HEAD'; Version =:= {1, 0} ->
+ ok;
+ true ->
+ {ok, Transport, Socket} = cowboy_req:transport(Req),
+ Transport:send(Socket, <<"0\r\n\r\n">>),
+ ok
+ end.
-%% 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,
- onresponse=OnResponse}) ->
+ onresponse=OnResponse, urldecode=URLDec}) ->
+ Req = cowboy_req:new(Socket, Transport, close, OnResponse, URLDec),
+ error_terminate(Code, Req, State).
+
+%% 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, #http_req{
- socket=Socket, transport=Transport, onresponse=OnResponse,
- connection=close, pid=self(), resp_state=waiting}),
+ _ = cowboy_req:reply(Code, Req),
ok
end,
terminate(State).
View
268 src/cowboy_req.erl
@@ -66,6 +66,7 @@
%% Response API.
-export([set_resp_cookie/4]).
-export([set_resp_header/3]).
+-export([delete_resp_header/2]).
-export([set_resp_body/2]).
-export([set_resp_body_fun/3]).
-export([has_resp_header/2]).
@@ -77,15 +78,79 @@
-export([chunked_reply/3]).
-export([chunk/2]).
-export([upgrade_reply/3]).
+-export([transport/1]).
%% Misc API.
-export([compact/1]).
--export([transport/1]).
-
--include("http.hrl").
-
-%% @todo opaque
--type req() :: #http_req{}.
+-export([to_proplist/1]).
+
+%% Private getter/setter API.
+-export([new/5]).
+-export([new/10]).
+-export([lock/1]).
+-export([add_header/3]).
+-export([get_body_state/1]).
+-export([get_buffer/1]).
+-export([get_connection/1]).
+-export([get_host/1]).
+-export([get_method/1]).
+-export([get_path/1]).
+-export([get_port/1]).
+-export([get_raw_host/1]).
+-export([get_raw_path/1]).
+-export([get_raw_qs/1]).
+-export([get_resp_state/1]).
+-export([get_version/1]).
+-export([set_bindings/4]).
+-export([set_buffer/2]).
+-export([set_connection/2]).
+-export([set_host/4]).
+-export([set_meta/3]).
+
+-record(http_req, {
+ %% Transport.
+ socket = undefined :: undefined | inet:socket(),
+ transport = undefined :: undefined | module(),
+ connection = keepalive :: keepalive | close,
+
+ %% Request.
+ pid = undefined :: pid(),
+ method = 'GET' :: cowboy_http:method(),
+ version = {1, 1} :: cowboy_http:version(),
+ peer = undefined :: undefined |
+ {inet:ip_address(), inet:port_number()},
+ host = undefined :: undefined | cowboy_dispatcher:tokens(),
+ host_info = undefined :: undefined | cowboy_dispatcher:tokens(),
+ raw_host = undefined :: undefined | binary(),
+ port = undefined :: undefined | inet:port_number(),
+ path = undefined :: undefined | '*' | cowboy_dispatcher:tokens(),
+ path_info = undefined :: undefined | cowboy_dispatcher:tokens(),
+ raw_path = undefined :: undefined | binary(),
+ qs_vals = undefined :: undefined | list({binary(), binary() | true}),
+ raw_qs = undefined :: undefined | binary(),
+ bindings = undefined :: undefined | cowboy_dispatcher:bindings(),
+ headers = [] :: cowboy_http:headers(),
+ p_headers = [] :: [any()], %% @todo Improve those specs.
+ cookies = undefined :: undefined | [{binary(), binary()}],
+ meta = [] :: [{atom(), any()}],
+
+ %% Request body.
+ body_state = waiting :: waiting | done | {stream, fun(), any(), fun()}
+ | {multipart, non_neg_integer(), fun()},
+ buffer = <<>> :: binary(),
+
+ %% Response.
+ resp_state = waiting :: locked | waiting | chunks | done,
+ resp_headers = [] :: cowboy_http:headers(),
+ resp_body = <<>> :: iodata() | {non_neg_integer(),
+ fun(() -> {sent, non_neg_integer()})},
+
+ %% Callbacks.
+ onresponse = undefined :: undefined | cowboy_protocol:onresponse_fun(),
+ urldecode :: cowboy_protocol:urldecode_fun()
+}).
+
+-opaque req() :: #http_req{}.
-export_type([req/0]).
%% Request API.
@@ -133,7 +198,8 @@ peer_addr(Req = #http_req{}) ->
{PeerAddr, Req3}.
%% @doc Return the tokens for the hostname requested.
--spec host(Req) -> {cowboy_dispatcher:tokens(), Req} when Req::req().
+-spec host(Req) -> {undefined | cowboy_dispatcher:tokens(), Req}
+ when Req::req().
host(Req) ->
{Req#http_req.host, Req}.
@@ -667,6 +733,13 @@ set_resp_header(Name, Value, Req=#http_req{resp_headers=RespHeaders}) ->
NameBin = header_to_binary(Name),
{ok, Req#http_req{resp_headers=[{NameBin, Value}|RespHeaders]}}.
+%% @doc Remove a header from the pre-set response.
+-spec delete_resp_header(cowboy_http:header(), Req) -> {ok, Req}
+ when Req::req().
+delete_resp_header(Name, Req=#http_req{resp_headers=RespHeaders}) ->
+ RespHeaders2 = lists:keydelete(Name, 1, RespHeaders),
+ {ok, Req#http_req{resp_headers=RespHeaders2}}.
+
%% @doc Add a body to the response.
%%
%% The body set here is ignored if the response is later sent using
@@ -696,13 +769,13 @@ set_resp_body_fun(StreamLen, StreamFun, Req) ->
{ok, Req#http_req{resp_body={StreamLen, StreamFun}}}.
%% @doc Return whether the given header has been set for the response.
--spec has_resp_header(cowboy_http:header(), #http_req{}) -> boolean().
+-spec has_resp_header(cowboy_http:header(), req()) -> boolean().
has_resp_header(Name, #http_req{resp_headers=RespHeaders}) ->
NameBin = header_to_binary(Name),
lists:keymember(NameBin, 1, RespHeaders).
%% @doc Return whether a body has been set for the response.
--spec has_resp_body(#http_req{}) -> boolean().
+-spec has_resp_body(req()) -> boolean().
has_resp_body(#http_req{resp_body={Length, _}}) ->
Length > 0;
has_resp_body(#http_req{resp_body=RespBody}) ->
@@ -776,7 +849,7 @@ chunked_reply(Status, Headers, Req=#http_req{
%% @doc Send a chunk of data.
%%
%% A chunked reply must have been initiated before calling this function.
--spec chunk(iodata(), #http_req{}) -> ok | {error, atom()}.
+-spec chunk(iodata(), req()) -> ok | {error, atom()}.
chunk(_Data, #http_req{socket=_Socket, transport=_Transport, method='HEAD'}) ->
ok;
chunk(Data, #http_req{socket=Socket, transport=Transport, version={1, 0}}) ->
@@ -789,13 +862,24 @@ chunk(Data, #http_req{socket=Socket, transport=Transport, resp_state=chunks}) ->
%% @private
-spec upgrade_reply(cowboy_http:status(), cowboy_http:headers(), Req)
-> {ok, Req} when Req::req().
-upgrade_reply(Status, Headers, Req=#http_req{
- resp_state=waiting, resp_headers=RespHeaders}) ->
+upgrade_reply(Status, Headers, Req=#http_req{resp_headers=RespHeaders}) ->
{_, Req2} = response(Status, Headers, RespHeaders, [
{<<"Connection">>, <<"Upgrade">>}
], Req),
{ok, Req2#http_req{resp_state=done, resp_headers=[], resp_body= <<>>}}.
+%% @doc Return the transport module and socket associated with a request.
+%%
+%% This exposes the same socket interface used internally by the HTTP protocol
+%% implementation to developers that needs low level access to the socket.
+%%
+%% It is preferred to use this in conjuction with the stream function support
+%% in `set_resp_body_fun/3' if this is used to write a response body directly
+%% to the socket. This ensures that the response headers are set correctly.
+-spec transport(req()) -> {ok, module(), inet:socket()}.
+transport(#http_req{transport=Transport, socket=Socket}) ->
+ {ok, Transport, Socket}.
+
%% Misc API.
%% @doc Compact the request data by removing all non-system information.
@@ -810,17 +894,155 @@ compact(Req) ->
bindings=undefined, headers=[],
p_headers=[], cookies=[]}.
-%% @doc Return the transport module and socket associated with a request.
-%%
-%% This exposes the same socket interface used internally by the HTTP protocol
-%% implementation to developers that needs low level access to the socket.
-%%
-%% It is preferred to use this in conjuction with the stream function support
-%% in `set_resp_body_fun/3' if this is used to write a response body directly
-%% to the socket. This ensures that the response headers are set correctly.
--spec transport(#http_req{}) -> {ok, module(), inet:socket()}.
-transport(#http_req{transport=Transport, socket=Socket}) ->
- {ok, Transport, Socket}.
+%% @doc Convert the request object to a proplist.
+-spec to_proplist(req()) -> [{atom(), any()}].
+to_proplist(Req) ->
+ lists:zip(record_info(fields, http_req), tl(tuple_to_list(Req))).
+
+%% Private getter/setter API.
+
+%% @doc Create an empty request object.
+%% @private
+-spec new(inet:socket(), module(), keepalive | close,
+ cowboy_protocol:onresponse_fun(), cowboy_protocol:urldecode_fun())
+ -> req().
+new(Socket, Transport, ConnAtom, OnResponse, URLDec) ->
+ #http_req{socket=Socket, transport=Transport, connection=ConnAtom,
+ pid=self(), onresponse=OnResponse, urldecode=URLDec}.
+
+%% @doc Create a new request object from request line data.
+%% @private
+-spec new(inet:socket(), module(), keepalive | close,
+ cowboy_http:method(), cowboy_http:version(),
+ '*' | cowboy_dispatcher:tokens(), binary(), binary(),
+ cowboy_protocol:onresponse_fun(), cowboy_protocol:urldecode_fun())
+ -> req().
+new(Socket, Transport, ConnAtom, Method, Version,
+ Path, RawPath, RawQs, OnResponse, URLDec) ->
+ #http_req{socket=Socket, transport=Transport, connection=ConnAtom,
+ pid=self(), method=Method, version=Version,
+ path=Path, raw_path=RawPath, raw_qs=RawQs,
+ onresponse=OnResponse, urldecode=URLDec}.
+
+%% @doc Lock the request to prevent any response.
+%% @private
+-spec lock(Req) -> Req when Req::req().
+lock(Req) ->
+ Req#http_req{resp_state=locked}.
+
+%% @doc Add a request header to the object.
+%% @private
+-spec add_header(cowboy_http:header(), iodata(), Req) -> Req when Req::req().
+add_header(Name, Value, Req=#http_req{headers=Headers}) ->
+ Req#http_req{headers=[{Name, Value}|Headers]}.
+
+%% @doc Return the request body state.
+%% @private
+-spec get_body_state(Req::req()) -> waiting | done
+ | {stream, fun(), any(), fun()} | {multipart, non_neg_integer(), fun()}.
+get_body_state(#http_req{body_state=BodyState}) ->
+ BodyState.
+
+%% @doc Return the buffer for this request.
+%% @private
+-spec get_buffer(req()) -> binary().
+get_buffer(#http_req{buffer=Buffer}) ->
+ Buffer.
+
+%% @doc Return the connection mode for this request.
+%% @private
+-spec get_connection(req()) -> keepalive | close.
+get_connection(#http_req{connection=Connection}) ->
+ Connection.
+
+%% @doc Return the host for this request.
+%% @private
+-spec get_host(req()) -> cowboy_dispatcher:tokens().
+get_host(#http_req{host=Host}) ->
+ Host.
+
+%% @doc Return the method for this request.
+%% @private
+-spec get_method(req()) -> cowboy_http:method().
+get_method(#http_req{method=Method}) ->
+ Method.
+
+%% @doc Return the path for this request.
+%% @private
+-spec get_path(req()) -> '*' | cowboy_dispatcher:tokens().
+get_path(#http_req{path=Path}) ->
+ Path.
+
+%% @doc Return the port for this request.
+%% @private
+-spec get_port(req()) -> inet:port_number().
+get_port(#http_req{port=Port}) ->
+ Port.
+
+%% @doc Return the raw host for this request.
+%% @private
+-spec get_raw_host(req()) -> undefined | binary().
+get_raw_host(#http_req{raw_host=RawHost}) ->
+ RawHost.
+
+%% @doc Return the raw path for this request.
+%% @private
+-spec get_raw_path(req()) -> undefined | binary().
+get_raw_path(#http_req{raw_path=RawPath}) ->
+ RawPath.
+
+%% @doc Return the raw host for this request.
+%% @private
+-spec get_raw_qs(req()) -> undefined | binary().
+get_raw_qs(#http_req{raw_qs=RawQs}) ->
+ RawQs.
+
+%% @doc Return the response state.
+%% @private
+-spec get_resp_state(Req::req()) -> locked | waiting | chunks | done.
+get_resp_state(#http_req{resp_state=RespState}) ->
+ RespState.
+
+%% @doc Return the version for this request.
+%% @private
+-spec get_version(req()) -> cowboy_http:version().
+get_version(#http_req{version=Version}) ->
+ Version.
+
+%% @doc Set the bindings found during dispatching.
+%% @private
+-spec set_bindings(cowboy_dispatcher:bindings(),
+ undefined | cowboy_dispatcher:tokens(),
+ undefined | cowboy_dispatcher:tokens(),
+ Req) -> Req when Req::req().
+set_bindings(Bindings, HostInfo, PathInfo, Req) ->
+ Req#http_req{bindings=Bindings, host_info=HostInfo, path_info=PathInfo}.
+
+%% @doc Set the buffer read from the socket.
+%% @private
+-spec set_buffer(binary(), Req) -> Req when Req::req().
+set_buffer(Buffer, Req) ->
+ Req#http_req{buffer=Buffer}.
+
+%% @doc Set the connection mode for this request.
+%% @private
+-spec set_connection(keepalive | close, Req) -> Req when Req::req().
+set_connection(Connection, Req) ->
+ Req#http_req{connection=Connection}.
+
+%% @doc Set the host information for the request.
+%% @private
+-spec set_host(cowboy_dispatcher:tokens(), binary(), inet:port_number(),
+ Req) -> Req when Req::req().
+set_host(Host, RawHost, Port, Req) ->
+ Req#http_req{host=Host, raw_host=RawHost, port=Port}.
+
+%% @doc Set metadata information about the request.
+%% @private
+-spec set_meta(atom(), any(), Req) -> Req when Req::req().
+set_meta(Name, Value, Req=#http_req{meta=Meta}) ->
+ Meta2 = lists:keydelete(Name, 1, Meta),
+ Req#http_req{meta=[{Name, Value}|Meta2]}.
%% Internal.
View
93 src/cowboy_rest.erl
@@ -26,6 +26,9 @@
handler :: atom(),
handler_state :: any(),
+ %% Request method.
+ method = undefined :: cowboy_http:method(),
+
%% Media type.
content_types_p = [] ::
[{{binary(), binary(), [{binary(), binary()}]}, atom()}],
@@ -46,34 +49,31 @@
expires :: undefined | no_call | calendar:datetime()
}).
--include("http.hrl").
-
%% @doc Upgrade a HTTP request to the REST protocol.
%%
%% 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(), #http_req{})
- -> {ok, #http_req{}} | close.
+-spec upgrade(pid(), module(), any(), Req)
+ -> {ok, Req} | close when Req::cowboy_req:req().
upgrade(_ListenerPid, Handler, Opts, Req) ->
try
+ {Method, Req2} = cowboy_req:method(Req),
case erlang:function_exported(Handler, rest_init, 2) of
true ->
- case Handler:rest_init(Req, Opts) of
- {ok, Req2, HandlerState} ->
- service_available(Req2, #state{handler=Handler,
- handler_state=HandlerState})
- end;
+ {ok, Req3, HandlerState} = Handler:rest_init(Req2, Opts),
+ service_available(Req3, #state{handler=Handler,
+ handler_state=HandlerState, method=Method});
false ->
- service_available(Req, #state{handler=Handler})
+ service_available(Req2, #state{handler=Handler, method=Method})
end
catch Class:Reason ->
- PLReq = lists:zip(record_info(fields, http_req), tl(tuple_to_list(Req))),
error_logger:error_msg(
"** Handler ~p terminating in rest_init/3~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()]),
+ [Handler, Class, Reason, Opts,
+ cowboy_req:to_proplist(Req), erlang:get_stacktrace()]),
{ok, _Req2} = cowboy_req:reply(500, Req),
close
end.
@@ -82,7 +82,7 @@ service_available(Req, State) ->
expect(Req, State, service_available, true, fun known_methods/2, 503).
%% known_methods/2 should return a list of atoms or binary methods.
-known_methods(Req=#http_req{method=Method}, State) ->
+known_methods(Req, State=#state{method=Method}) ->
case call(Req, State, known_methods) of
no_call when Method =:= 'HEAD'; Method =:= 'GET'; Method =:= 'POST';
Method =:= 'PUT'; Method =:= 'DELETE'; Method =:= 'TRACE';
@@ -104,7 +104,7 @@ uri_too_long(Req, State) ->
expect(Req, State, uri_too_long, false, fun allowed_methods/2, 414).
%% allowed_methods/2 should return a list of atoms or binary methods.
-allowed_methods(Req=#http_req{method=Method}, State) ->
+allowed_methods(Req, State=#state{method=Method}) ->
case call(Req, State, allowed_methods) of
no_call when Method =:= 'HEAD'; Method =:= 'GET' ->
next(Req, State, fun malformed_request/2);
@@ -169,7 +169,7 @@ valid_entity_length(Req, State) ->
%% If you need to add additional headers to the response at this point,
%% you should do it directly in the options/2 call using set_resp_headers.
-options(Req=#http_req{method='OPTIONS'}, State) ->
+options(Req, State=#state{method='OPTIONS'}) ->
case call(Req, State, options) of
{halt, Req2, HandlerState} ->
terminate(Req2, State#state{handler_state=HandlerState});
@@ -194,7 +194,7 @@ options(Req, State) ->
%% resources a little more readable, this is a lot less efficient. An example
%% of such a return value would be:
%% {<<"text/html">>, to_html}
-content_types_provided(Req=#http_req{meta=Meta}, State) ->
+content_types_provided(Req, State) ->
case call(Req, State, content_types_provided) of
no_call ->
not_acceptable(Req, State);
@@ -210,8 +210,8 @@ content_types_provided(Req=#http_req{meta=Meta}, State) ->
case Accept of
undefined ->
{PMT, _Fun} = HeadCTP = hd(CTP2),
- languages_provided(
- Req3#http_req{meta=[{media_type, PMT}|Meta]},
+ Req4 = cowboy_req:set_meta(media_type, PMT, Req3),
+ languages_provided(Req4,
State2#state{content_type_a=HeadCTP});
Accept ->
Accept2 = prioritize_accept(Accept),
@@ -274,13 +274,13 @@ match_media_type(Req, State, Accept,
match_media_type(Req, State, Accept, [_Any|Tail], MediaType) ->
match_media_type(Req, State, Accept, Tail, MediaType).
-match_media_type_params(Req=#http_req{meta=Meta}, State, Accept,
+match_media_type_params(Req, State, Accept,
[Provided = {PMT = {_TP, _STP, Params_P}, _Fun}|Tail],
MediaType = {{_TA, _STA, Params_A}, _QA, _APA}) ->
case lists:sort(Params_P) =:= lists:sort(Params_A) of
true ->
- languages_provided(Req#http_req{meta=[{media_type, PMT}|Meta]},
- State#state{content_type_a=Provided});
+ Req2 = cowboy_req:set_meta(media_type, PMT, Req),
+ languages_provided(Req2, State#state{content_type_a=Provided});
false ->
match_media_type(Req, State, Accept, Tail, MediaType)
end.
@@ -346,10 +346,11 @@ match_language(Req, State, Accept, [Provided|Tail],
match_language(Req, State, Accept, Tail, Language)
end.
-set_language(Req=#http_req{meta=Meta}, State=#state{language_a=Language}) ->
+set_language(Req, State=#state{language_a=Language}) ->
{ok, Req2} = cowboy_req:set_resp_header(
<<"Content-Language">>, Language, Req),
- charsets_provided(Req2#http_req{meta=[{language, Language}|Meta]}, State).
+ Req3 = cowboy_req:set_meta(language, Language, Req2),
+ charsets_provided(Req3, State).
%% charsets_provided should return a list of binary values indicating
%% which charsets are accepted by the resource.
@@ -403,7 +404,7 @@ match_charset(Req, State, _Accept, [Provided|_Tail],
match_charset(Req, State, Accept, [_Provided|Tail], Charset) ->
match_charset(Req, State, Accept, Tail, Charset).
-set_content_type(Req=#http_req{meta=Meta}, State=#state{
+set_content_type(Req, State=#state{
content_type_a={{Type, SubType, Params}, _Fun},
charset_a=Charset}) ->
ParamsBin = set_content_type_build_params(Params, []),
@@ -414,7 +415,8 @@ set_content_type(Req=#http_req{meta=Meta}, State=#state{
end,
{ok, Req2} = cowboy_req:set_resp_header(
<<"Content-Type">>, ContentType2, Req),
- encodings_provided(Req2#http_req{meta=[{charset, Charset}|Meta]}, State).
+ Req3 = cowboy_req:set_meta(charset, Charset, Req2),
+ encodings_provided(Req3, State).
set_content_type_build_params([], []) ->
<<>>;
@@ -540,7 +542,7 @@ if_none_match(Req, State, EtagsList) ->
end
end.
-precondition_is_head_get(Req=#http_req{method=Method}, State)
+precondition_is_head_get(Req, State=#state{method=Method})
when Method =:= 'HEAD'; Method =:= 'GET' ->
not_modified(Req, State);
precondition_is_head_get(Req, State) ->
@@ -574,17 +576,16 @@ if_modified_since(Req, State, IfModifiedSince) ->
end
end.
-not_modified(Req=#http_req{resp_headers=RespHeaders}, State) ->
- RespHeaders2 = lists:keydelete(<<"Content-Type">>, 1, RespHeaders),
- Req2 = Req#http_req{resp_headers=RespHeaders2},
+not_modified(Req, State) ->
+ {ok, 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).
precondition_failed(Req, State) ->
respond(Req, State, 412).
-is_put_to_missing_resource(Req=#http_req{method='PUT'}, State) ->
+is_put_to_missing_resource(Req, State=#state{method='PUT'}) ->
moved_permanently(Req, State, fun is_conflict/2);
is_put_to_missing_resource(Req, State) ->
previously_existed(Req, State).
@@ -626,19 +627,19 @@ moved_temporarily(Req, State) ->
is_post_to_missing_resource(Req, State, 410)
end.
-is_post_to_missing_resource(Req=#http_req{method='POST'}, State, OnFalse) ->
+is_post_to_missing_resource(Req, State=#state{method='POST'}, OnFalse) ->
allow_missing_post(Req, State, OnFalse);
is_post_to_missing_resource(Req, State, OnFalse) ->
respond(Req, State, OnFalse).
allow_missing_post(Req, State, OnFalse) ->
expect(Req, State, allow_missing_post, true, fun post_is_create/2, OnFalse).
-method(Req=#http_req{method='DELETE'}, State) ->
+method(Req, State=#state{method='DELETE'}) ->
delete_resource(Req, State);
-method(Req=#http_req{method='POST'}, State) ->
+method(Req, State=#state{method='POST'}) ->
post_is_create(Req, State);
-method(Req=#http_req{method='PUT'}, State) ->
+method(Req, State=#state{method='PUT'}) ->
is_conflict(Req, State);
method(Req, State) ->
set_resp_body(Req, State).
@@ -658,7 +659,7 @@ post_is_create(Req, State) ->
%% When the POST method can create new resources, create_path/2 will be called
%% and is expected to return the full path to the new resource
%% (including the leading /).
-create_path(Req=#http_req{meta=Meta}, State) ->
+create_path(Req, State) ->
case call(Req, State, create_path) of
{halt, Req2, HandlerState} ->
terminate(Req2, State#state{handler_state=HandlerState});
@@ -667,12 +668,14 @@ create_path(Req=#http_req{meta=Meta}, State) ->
State2 = State#state{handler_state=HandlerState},
{ok, Req3} = cowboy_req:set_resp_header(
<<"Location">>, Location, Req2),
- put_resource(Req3#http_req{meta=[{put_path, Path}|Meta]},
- State2, 303)
+ Req4 = cowboy_req:set_meta(put_path, Path, Req3),
+ put_resource(Req4, State2, 303)
end.
-create_path_location(#http_req{transport=Transport, raw_host=Host,
- port=Port}, Path) ->
+create_path_location(Req, Path) ->
+ {ok, Transport, _} = cowboy_req:transport(Req),
+ {Host, _} = cowboy_req:raw_host(Req),
+ {Port, _} = cowboy_req:port(Req),
TransportName = Transport:name(),
<< (create_path_location_protocol(TransportName))/binary, "://",
Host/binary, (create_path_location_port(TransportName, Port))/binary,
@@ -705,9 +708,10 @@ process_post(Req, State) ->
is_conflict(Req, State) ->
expect(Req, State, is_conflict, false, fun put_resource/2, 409).
-put_resource(Req=#http_req{raw_path=RawPath, meta=Meta}, State) ->
- Req2 = Req#http_req{meta=[{put_path, RawPath}|Meta]},
- put_resource(Req2, State, fun is_new_resource/2).
+put_resource(Req, State) ->
+ {RawPath, Req2} = cowboy_req:raw_path(Req),
+ Req3 = cowboy_req:set_meta(put_path, RawPath, Req2),
+ put_resource(Req3, State, fun is_new_resource/2).
%% content_types_accepted should return a list of media types and their
%% associated callback functions in the same format as content_types_provided.
@@ -769,8 +773,7 @@ has_resp_body(Req, State) ->
%% 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=#http_req{method=Method},
- State=#state{content_type_a={_Type, Fun}})
+set_resp_body(Req, State=#state{method=Method, content_type_a={_Type, Fun}})
when Method =:= 'GET'; Method =:= 'HEAD' ->
{Req2, State2} = set_resp_etag(Req, State),
{LastModified, Req3, State3} = last_modified(Req2, State2),
@@ -913,7 +916,7 @@ respond(Req, State, StatusCode) ->
terminate(Req, #state{handler=Handler, handler_state=HandlerState}) ->
case erlang:function_exported(Handler, rest_terminate, 2) of
true -> ok = Handler:rest_terminate(
- Req#http_req{resp_state=locked}, HandlerState);
+ cowboy_req:lock(Req), HandlerState);
false -> ok
end,
{ok, Req}.
View
30 src/cowboy_static.erl
@@ -170,7 +170,6 @@
-module(cowboy_static).
%% include files
--include("http.hrl").
-include_lib("kernel/include/file.hrl").
%% cowboy_protocol callbacks
@@ -210,7 +209,7 @@ init({_Transport, http}, _Req, _Opts) ->
{upgrade, protocol, cowboy_rest}.
%% @private Set up initial state of REST handler.
--spec rest_init(#http_req{}, list()) -> {ok, #http_req{}, #state{}}.
+-spec rest_init(Req, list()) -> {ok, Req, #state{}} when Req::cowboy_req:req().
rest_init(Req, Opts) ->
Directory = proplists:get_value(directory, Opts),
Directory1 = directory_path(Directory),
@@ -244,22 +243,22 @@ rest_init(Req, Opts) ->
{ok, Req1, State}.
%% @private Only allow GET and HEAD requests on files.
--spec allowed_methods(#http_req{}, #state{}) ->
- {[atom()], #http_req{}, #state{}}.
+-spec allowed_methods(Req, #state{}) -> {[atom()], Req, #state{}}
+ when Req::cowboy_req:req().
allowed_methods(Req, State) ->
{['GET', 'HEAD'], Req, State}.
%% @private
--spec malformed_request(#http_req{}, #state{}) ->
- {boolean(), #http_req{}, #state{}}.
+-spec malformed_request(Req, #state{}) -> {boolean(), Req, #state{}}
+ when Req::cowboy_req:req().
malformed_request(Req, #state{filepath=error}=State) ->
{true, Req, State};
malformed_request(Req, State) ->
{false, Req, State}.
%% @private Check if the resource exists under the document root.
--spec resource_exists(#http_req{}, #state{}) ->
- {boolean(), #http_req{}, #state{}}.
+-spec resource_exists(Req, #state{}) -> {boolean(), Req, #state{}}
+ when Req::cowboy_req:req().
resource_exists(Req, #state{fileinfo={error, _}}=State) ->
{false, Req, State};
resource_exists(Req, #state{fileinfo={ok, Fileinfo}}=State) ->
@@ -268,7 +267,8 @@ resource_exists(Req, #state{fileinfo={ok, Fileinfo}}=State) ->
%% @private
%% Access to a file resource is forbidden if it exists and the local node does
%% not have permission to read it. Directory listings are always forbidden.
--spec forbidden(#http_req{}, #state{}) -> {boolean(), #http_req{}, #state{}}.
+-spec forbidden(Req, #state{}) -> {boolean(), Req, #state{}}
+ when Req::cowboy_req:req().
forbidden(Req, #state{fileinfo={_, #file_info{type=directory}}}=State) ->
{true, Req, State};
forbidden(Req, #state{fileinfo={error, eacces}}=State) ->
@@ -279,16 +279,16 @@ forbidden(Req, #state{fileinfo={ok, #file_info{access=Access}}}=State) ->
{not (Access =:= read orelse Access =:= read_write), Req, State}.
%% @private Read the time a file system system object was last modified.
--spec last_modified(#http_req{}, #state{}) ->
- {calendar:datetime(), #http_req{}, #state{}}.
+-spec last_modified(Req, #state{}) -> {calendar:datetime(), Req, #state{}}
+ when Req::cowboy_req:req().
last_modified(Req, #state{fileinfo={ok, #file_info{mtime=Modified}}}=State) ->
{Modified, Req, State}.
%% @private Generate the ETag header value for this file.
%% The ETag header value is only generated if the resource is a file that
%% exists in document root.
--spec generate_etag(#http_req{}, #state{}) ->
- {undefined | binary(), #http_req{}, #state{}}.
+-spec generate_etag(Req, #state{}) -> {undefined | binary(), Req, #state{}}
+ when Req::cowboy_req:req().
generate_etag(Req, #state{fileinfo={_, #file_info{type=regular, inode=INode,
mtime=Modified, size=Filesize}}, filepath=Filepath,
etag_fun={ETagFun, ETagData}}=State) ->
@@ -300,15 +300,15 @@ generate_etag(Req, State) ->
{undefined, Req, State}.
%% @private Return the content type of a file.
--spec content_types_provided(#http_req{}, #state{}) -> tuple().
+-spec content_types_provided(cowboy_req:req(), #state{}) -> tuple().
content_types_provided(Req, #state{filepath=Filepath,
mimetypes={MimetypesFun, MimetypesData}}=State) ->
Mimetypes = [{T, file_contents}
|| T <- MimetypesFun(Filepath, MimetypesData)],
{Mimetypes, Req, State}.
%% @private Return a function that writes a file directly to the socket.
--spec file_contents(#http_req{}, #state{}) -> tuple().
+-spec file_contents(cowboy_req:req(), #state{}) -> tuple().
file_contents(Req, #state{filepath=Filepath,
fileinfo={ok, #file_info{size=Filesize}}}=State) ->
{ok, Transport, Socket} = cowboy_req:transport(Req),
View
159 src/cowboy_websocket.erl
@@ -19,10 +19,12 @@
%% is no need for concern as crypto is already included.
-module(cowboy_websocket).
--export([upgrade/4]). %% API.
--export([handler_loop/4]). %% Internal.
+%% API.
+-export([upgrade/4]).
+
+%% Internal.
+-export([handler_loop/4]).
--include("http.hrl").
-include_lib("eunit/include/eunit.hrl").
-type opcode() :: 0 | 1 | 2 | 8 | 9 | 10.
@@ -55,15 +57,16 @@
%% You do not need to call this function manually. To upgrade to the WebSocket
%% 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(), #http_req{}) -> closed.
+-spec upgrade(pid(), module(), any(), cowboy_req:req()) -> closed.
upgrade(ListenerPid, Handler, Opts, Req) ->
ranch_listener:move_connection(ListenerPid, websocket, self()),
case catch websocket_upgrade(#state{handler=Handler, opts=Opts}, Req) of
{ok, State, Req2} -> handler_init(State, Req2);
{'EXIT', _Reason} -> upgrade_error(Req)
end.
--spec websocket_upgrade(#state{}, #http_req{}) -> {ok, #state{}, #http_req{}}.
+-spec websocket_upgrade(State, Req) -> {ok, State, Req}
+ when State::#state{}, Req::cowboy_req:req().
websocket_upgrade(State, Req) ->
{ConnTokens, Req2}
= cowboy_req:parse_header('Connection', Req),
@@ -75,36 +78,37 @@ websocket_upgrade(State, Req) ->
%% @todo Handle the Sec-Websocket-Protocol header.
%% @todo Reply a proper error, don't die, if a required header is undefined.
--spec websocket_upgrade(undefined | <<_:8>>, #state{}, #http_req{})
- -> {ok, #state{}, #http_req{}}.
+-spec websocket_upgrade(undefined | <<_:8>>, State, Req) -> {ok, State, Req}
+ when State::#state{}, Req::cowboy_req:req().
%% No version given. Assuming hixie-76 draft.
%%
%% We need to wait to send a reply back before trying to read the
%% third part of the challenge key, because proxies will wait for
%% a reply before sending it. Therefore we calculate the challenge
%% key only in websocket_handshake/3.
-websocket_upgrade(undefined, State, Req=#http_req{meta=Meta}) ->
+websocket_upgrade(undefined, State, Req) ->
{Origin, Req2} = cowboy_req:header(<<"Origin">>, Req),
{Key1, Req3} = cowboy_req:header(<<"Sec-Websocket-Key1">>, Req2),
{Key2, Req4} = cowboy_req:header(<<"Sec-Websocket-Key2">>, Req3),
false = lists:member(undefined, [Origin, Key1, Key2]),
EOP = binary:compile_pattern(<< 255 >>),
+ Req5 = cowboy_req:set_meta(websocket_version, 0, Req4),
{ok, State#state{version=0, origin=Origin, challenge={Key1, Key2},
- eop=EOP}, Req4#http_req{meta=[{websocket_version, 0}|Meta]}};
+ eop=EOP}, Req5};
%% Versions 7 and 8. Implementation follows the hybi 7 through 17 drafts.
-websocket_upgrade(Version, State, Req=#http_req{meta=Meta})
+websocket_upgrade(Version, State, Req)
when Version =:= <<"7">>; Version =:= <<"8">>;
Version =:= <<"13">> ->
{Key, Req2} = cowboy_req:header(<<"Sec-Websocket-Key">>, Req),
false = Key =:= undefined,
Challenge = hybi_challenge(Key),
IntVersion = list_to_integer(binary_to_list(Version)),
- {ok, State#state{version=IntVersion, challenge=Challenge},
- Req2#http_req{meta=[{websocket_version, IntVersion}|Meta]}}.
+ Req3 = cowboy_req:set_meta(websocket_version, IntVersion, Req2),
+ {ok, State#state{version=IntVersion, challenge=Challenge}, Req3}.
--spec handler_init(#state{}, #http_req{}) -> closed.
-handler_init(State=#state{handler=Handler, opts=Opts},
- Req=#http_req{transport=Transport}) ->
+-spec handler_init(#state{}, cowboy_req:req()) -> closed.
+handler_init(State=#state{handler=Handler, opts=Opts}, Req) ->
+ {ok, Transport, _} = cowboy_req:transport(Req),
try Handler:websocket_init(Transport:name(), Req, Opts) of
{ok, Req2, HandlerState} ->
websocket_handshake(State, Req2, HandlerState);
@@ -121,46 +125,53 @@ handler_init(State=#state{handler=Handler, opts=Opts},
upgrade_denied(Req2)
catch Class:Reason ->
upgrade_error(Req),
- PLReq = lists:zip(record_info(fields, http_req), tl(tuple_to_list(Req))),
error_logger:error_msg(
"** Handler ~p terminating in websocket_init/3~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()])
+ [Handler, Class, Reason, Opts,
+ cowboy_req:to_proplist(Req), erlang:get_stacktrace()])
end.
--spec upgrade_error(#http_req{}) -> closed.
+-spec upgrade_error(cowboy_req:req()) -> closed.
upgrade_error(Req) ->
- {ok, _Req2} = cowboy_req:reply(400, [], [],
- Req#http_req{resp_state=waiting}),
+ {ok, _} = cowboy_req:reply(400, Req),
closed.
%% @see cowboy_protocol:ensure_response/1
--spec upgrade_denied(#http_req{}) -> closed.
-upgrade_denied(#http_req{resp_state=done}) ->
+-spec upgrade_denied(cowboy_req:req()) -> closed.
+upgrade_denied(Req) ->
+ upgrade_denied(cowboy_req:get_resp_state(Req),
+ cowboy_req:get_method(Req), Req).
+
+-spec upgrade_denied(done | waiting | chunks, cowboy_http:method(),
+ cowboy_req:req()) -> closed.
+upgrade_denied(done, _, _) ->
closed;
-upgrade_denied(Req=#http_req{resp_state=waiting}) ->
- {ok, _Req2} = cowboy_req:reply(400, [], [], Req),
+upgrade_denied(waiting, _, Req) ->
+ {ok, _} = cowboy_req:reply(400, Req),
closed;
-upgrade_denied(#http_req{method='HEAD', resp_state=chunks}) ->
+upgrade_denied(chunks, 'HEAD', _) ->
closed;
-upgrade_denied(#http_req{socket=Socket, transport=Transport,
- resp_state=chunks}) ->
- Transport:send(Socket, <<"0\r\n\r\n">>),
+upgrade_denied(chunks, _, Req) ->
+ send(Req, <<"0\r\n\r\n">>),
closed.
--spec websocket_handshake(#state{}, #http_req{}, any()) -> closed.
+-spec websocket_handshake(#state{}, cowboy_req:req(), any()) -> closed.
websocket_handshake(State=#state{version=0, origin=Origin,
- challenge={Key1, Key2}}, Req=#http_req{socket=Socket,
- transport=Transport, raw_host=Host, port=Port,
- raw_path=Path, raw_qs=QS}, HandlerState) ->
+ challenge={Key1, Key2}}, Req, HandlerState) ->
+ {ok, Transport, Socket} = cowboy_req:transport(Req),
+ Host = cowboy_req:get_raw_host(Req),
+ Port = cowboy_req:get_port(Req),
+ Path = cowboy_req:get_raw_path(Req),
+ QS = cowboy_req:get_raw_qs(Req),
Location = hixie76_location(Transport:name(), Host, Port, Path, QS),
{ok, Req2} = cowboy_req:upgrade_reply(
<<"101 WebSocket Protocol Handshake">>,
[{<<"Upgrade">>, <<"WebSocket">>},
{<<"Sec-Websocket-Location">>, Location},
{<<"Sec-Websocket-Origin">>, Origin}],
- Req#http_req{resp_state=waiting}),
+ Req),
%% Flush the resp_sent message before moving on.
receive {cowboy_req, resp_sent} -> ok after 0 -> ok end,
%% We replied with a proper response. Proxies should be happy enough,
@@ -181,33 +192,34 @@ websocket_handshake(State=#state{version=0, origin=Origin,
_Any ->
closed %% If an error happened reading the body, stop there.
end;
-websocket_handshake(State=#state{challenge=Challenge},
- Req=#http_req{transport=Transport}, HandlerState) ->
+websocket_handshake(State=#state{challenge=Challenge}, Req, HandlerState) ->
{ok, Req2} = cowboy_req:upgrade_reply(
101,
[{<<"Upgrade">>, <<"websocket">>},
{<<"Sec-Websocket-Accept">>, Challenge}],
- Req#http_req{resp_state=waiting}),
+ Req),
%% Flush the resp_sent message before moving on.
receive {cowboy_req, resp_sent} -> ok after 0 -> ok end,
+ {ok, Transport, _} = cowboy_req:transport(Req),
handler_before_loop(State#state{messages=Transport:messages()},
Req2, HandlerState, <<>>).
--spec handler_before_loop(#state{}, #http_req{}, any(), binary()) -> closed.
+-spec handler_before_loop(#state{}, cowboy_req:req(), any(), binary()) -> closed.
handler_before_loop(State=#state{hibernate=true},
- Req=#http_req{socket=Socket, transport=Transport},
- HandlerState, SoFar) ->
+ Req, HandlerState, SoFar) ->
+ {ok, Transport, Socket} = cowboy_req:transport(Req),
Transport:setopts(Socket, [{active, once}]),
State2 = handler_loop_timeout(State),
catch erlang:hibernate(?MODULE, handler_loop,
[State2#state{hibernate=false}, Req, HandlerState, SoFar]),
closed;
-handler_before_loop(State, Req=#http_req{socket=Socket, transport=Transport},
- HandlerState, SoFar) ->
+handler_before_loop(State, Req, HandlerState, SoFar) ->
+ {ok, Transport, Socket} = cowboy_req:transport(Req),
Transport:setopts(Socket, [{active, once}]),
State2 = handler_loop_timeout(State),
handler_loop(State2, Req, HandlerState, SoFar).
+
-spec handler_loop_timeout(#state{}) -> #state{}.
handler_loop_timeout(State=#state{timeout=infinity}) ->
State#state{timeout_ref=undefined};
@@ -218,9 +230,10 @@ handler_loop_timeout(State=#state{timeout=Timeout, timeout_ref=PrevRef}) ->
State#state{timeout_ref=TRef}.
%% @private
--spec handler_loop(#state{}, #http_req{}, any(), binary()) -> closed.
+-spec handler_loop(#state{}, cowboy_req:req(), any(), binary()) -> closed.
handler_loop(State=#state{messages={OK, Closed, Error}, timeout_ref=TRef},
- Req=#http_req{socket=Socket}, HandlerState, SoFar) ->
+ Req, HandlerState, SoFar) ->
+ {ok, _, Socket} = cowboy_req:transport(Req),
receive
{OK, Socket, Data} ->
websocket_data(State, Req, HandlerState,
@@ -238,7 +251,7 @@ handler_loop(State=#state{messages={OK, Closed, Error}, timeout_ref=TRef},
SoFar, websocket_info, Message, fun handler_before_loop/4)
end.
--spec websocket_data(#state{}, #http_req{}, any(), binary()) -> closed.
+-spec websocket_data(#state{}, cowboy_req:req(), any(), binary()) -> closed.
%% No more data.
websocket_data(State, Req, HandlerState, <<>>) ->
handler_before_loop(State, Req, HandlerState, <<>>);
@@ -295,7 +308,7 @@ websocket_data(State, Req, HandlerState,
websocket_data(State, Req, HandlerState, _Data) ->
websocket_close(State, Req, HandlerState, {error, badframe}).
--spec websocket_data(#state{}, #http_req{}, any(), non_neg_integer(),
+-spec websocket_data(#state{}, cowboy_req:req(), any(), non_neg_integer(),
non_neg_integer(), non_neg_integer(), non_neg_integer(),
non_neg_integer(), binary(), binary()) -> closed.
%% A fragmented message MUST start a non-zero opcode.
@@ -350,7 +363,7 @@ websocket_data(State, Req, HandlerState, _Fin, _Rsv, _Opcode, _Mask,
websocket_close(State, Req, HandlerState, {error, badframe}).
%% hybi routing depending on whether unmasking is needed.
--spec websocket_before_unmask(#state{}, #http_req{}, any(), binary(),
+-spec websocket_before_unmask(#state{}, cowboy_req:req(), any(), binary(),
binary(), opcode(), 0 | 1, non_neg_integer() | undefined) -> closed.
websocket_before_unmask(State, Req, HandlerState, Data,
Rest, Opcode, Mask, PayloadLen) ->
@@ -367,14 +380,14 @@ websocket_before_unmask(State, Req, HandlerState, Data,
end.
%% hybi unmasking.
--spec websocket_unmask(#state{}, #http_req{}, any(), binary(),
+-spec websocket_unmask(#state{}, cowboy_req:req(), any(), binary(),
opcode(), binary(), mask_key()) -> closed.
websocket_unmask(State, Req, HandlerState, RemainingData,
Opcode, Payload, MaskKey) ->
websocket_unmask(State, Req, HandlerState, RemainingData,
Opcode, Payload, MaskKey, <<>>).
--spec websocket_unmask(#state{}, #http_req{}, any(), binary(),
+-spec websocket_unmask(#state{}, cowboy_req:req(), any(), binary(),
opcode(), binary(), mask_key(), binary()) -> closed.
websocket_unmask(State, Req, HandlerState, RemainingData,
Opcode, << O:32, Rest/bits >>, MaskKey, Acc) ->
@@ -405,7 +418,7 @@ websocket_unmask(State, Req, HandlerState, RemainingData,
Opcode, Acc).
%% hybi dispatching.
--spec websocket_dispatch(#state{}, #http_req{}, any(), binary(),
+-spec websocket_dispatch(#state{}, cowboy_req:req(), any(), binary(),
opcode(), binary()) -> closed.
%% First frame of a fragmented message unmasked. Expect intermediate or last.
websocket_dispatch(State=#state{frag_state={nofin, Opcode}}, Req, HandlerState,
@@ -436,18 +449,17 @@ websocket_dispatch(State, Req, HandlerState, RemainingData, 2, Payload) ->
websocket_dispatch(State, Req, HandlerState, _RemainingData, 8, _Payload) ->
websocket_close(State, Req, HandlerState, {normal, closed});
%% Ping control frame. Send a pong back and forward the ping to the handler.
-websocket_dispatch(State, Req=#http_req{socket=Socket, transport=Transport},
- HandlerState, RemainingData, 9, Payload) ->
+websocket_dispatch(State, Req, HandlerState, RemainingData, 9, Payload) ->
Len = hybi_payload_length(byte_size(Payload)),
- Transport:send(Socket, << 1:1, 0:3, 10:4, 0:1, Len/bits, Payload/binary >>),
+ send(Req, << 1:1, 0:3, 10:4, 0:1, Len/bits, Payload/binary >>),
handler_call(State, Req, HandlerState, RemainingData,
websocket_handle, {ping, Payload}, fun websocket_data/4);
%% Pong control frame.
websocket_dispatch(State, Req, HandlerState, RemainingData, 10, Payload) ->
handler_call(State, Req, HandlerState, RemainingData,
websocket_handle, {pong, Payload}, fun websocket_data/4).
--spec handler_call(#state{}, #http_req{}, any(), binary(),
+-spec handler_call(#state{}, cowboy_req:req(), any(), binary(),
atom(), any(), fun()) -> closed.
handler_call(State=#state{handler=Handler, opts=Opts}, Req, HandlerState,
RemainingData, Callback, Message, NextState) ->
@@ -467,63 +479,62 @@ handler_call(State=#state{handler=Handler, opts=Opts}, Req, HandlerState,
{shutdown, Req2, HandlerState2} ->
websocket_close(State, Req2, HandlerState2, {normal, shutdown})
catch Class:Reason ->
- PLReq = lists:zip(record_info(fields, http_req), tl(tuple_to_list(Req))),
error_logger:error_msg(
"** Handler ~p terminating in ~p/3~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, Class, Reason, Message, Opts,
- HandlerState, PLReq, erlang:get_stacktrace()]),
+ [Handler, Callback, Class, Reason, Message, Opts, HandlerState,
+ cowboy_req:to_proplist(Req), erlang:get_stacktrace()]),
websocket_close(State, Req, HandlerState, {error, handler})
end.
--spec websocket_send(binary(), #state{}, #http_req{}) -> closed | ignore.
+-spec websocket_send(binary(), #state{}, cowboy_req:req()) -> closed | ignore.
%% hixie-76 text frame.
-websocket_send({text, Payload}, #state{version=0},
- #http_req{socket=Socket, transport=Transport}) ->
+websocket_send({text, Payload}, #state{version=0}, Req) ->
+ {ok, Transport, Socket} = cowboy_req:transport(Req),
Transport:send(Socket, [0, Payload, 255]);
%% Ignore all unknown frame types for compatibility with hixie 76.
websocket_send(_Any, #state{version=0}, _Req) ->
ignore;
-websocket_send({Type, Payload}, _State,
- #http_req{socket=Socket, transport=Transport}) ->
+websocket_send({Type, Payload}, _State, Req) ->
Opcode = case Type of
text -> 1;
binary -> 2;
ping -> 9;
pong -> 10
end,
Len = hybi_payload_length(iolist_size(Payload)),
- Transport:send(Socket, [<< 1:1, 0:3, Opcode:4, 0:1, Len/bits >>,
- Payload]).
+ send(Req, [<< 1:1, 0:3, Opcode:4, 0:1, Len/bits >>, Payload]).
--spec websocket_close(#state{}, #http_req{}, any(), {atom(), atom()}) -> closed.
-websocket_close(State=#state{version=0}, Req=#http_req{socket=Socket,
- transport=Transport}, HandlerState, Reason) ->
- Transport:send(Socket, << 255, 0 >>),
+-spec websocket_close(#state{}, cowboy_req:req(), any(), {atom(), atom()}) -> closed.
+websocket_close(State=#state{version=0}, Req, HandlerState, Reason) ->
+ send(Req, << 255, 0 >>),
handler_terminate(State, Req, HandlerState, Reason);
%% @todo Send a Payload? Using Reason is usually good but we're quite careless.
-websocket_close(State, Req=#http_req{socket=Socket,
- transport=Transport}, HandlerState, Reason) ->
- Transport:send(Socket, << 1:1, 0:3, 8:4, 0:8 >>),
+websocket_close(State, Req, HandlerState, Reason) ->
+ send(Req, << 1:1, 0:3, 8:4, 0:8 >>),
handler_terminate(State, Req, HandlerState, Reason).
--spec handler_terminate(#state{}, #http_req{},
+-spec send(cowboy_req:req(), iodata()) -> ok.
+send(Req, Data) ->
+ {ok, Transport, Socket} = cowboy_req:transport(Req),
+ Transport:send(Socket, Data).
+
+-spec handler_terminate(#state{}, cowboy_req:req(),
any(), atom() | {atom(), atom()}) -> closed.
handler_terminate(#state{handler=Handler, opts=Opts},
Req, HandlerState, TerminateReason) ->
try
Handler:websocket_terminate(TerminateReason, Req, HandlerState)
catch Class:Reason ->
- PLReq = lists:zip(record_info(fields, http_req), tl(tuple_to_list(Req))),
error_logger:error_msg(
"** Handler ~p terminating in websocket_terminate/3~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, Class, Reason, TerminateReason, Opts,
- HandlerState, PLReq, erlang:get_stacktrace()])
+ [Handler, Class, Reason, TerminateReason, Opts, HandlerState,
+ cowboy_req:to_proplist(Req), erlang:get_stacktrace()])
end,
closed.

0 comments on commit e6d75cb

Please sign in to comment.
Something went wrong with that request. Please try again.