Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Newer
Older
100644 495 lines (455 sloc) 19.96 kb
da72255 Initial commit.
Loïc Hoguin authored
1 %% Copyright (c) 2011, Loïc Hoguin <essen@dev-extend.eu>
0ca8f13 @nox Implement path_info feature
nox authored
2 %% Copyright (c) 2011, Anthony Ramine <nox@dev-extend.eu>
da72255 Initial commit.
Loïc Hoguin authored
3 %%
4 %% Permission to use, copy, modify, and/or distribute this software for any
5 %% purpose with or without fee is hereby granted, provided that the above
6 %% copyright notice and this permission notice appear in all copies.
7 %%
8 %% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 %% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 %% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 %% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 %% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15
108a491 Add documentation for the public interface.
Loïc Hoguin authored
16 %% @doc HTTP protocol handler.
17 %%
18 %% The available options are:
19 %% <dl>
20 %% <dt>dispatch</dt><dd>The dispatch list for this protocol.</dd>
21 %% <dt>max_empty_lines</dt><dd>Max number of empty lines before a request.
22 %% Defaults to 5.</dd>
148fb94 Small doc clarification
Loïc Hoguin authored
23 %% <dt>timeout</dt><dd>Time in milliseconds before an idle
108a491 Add documentation for the public interface.
Loïc Hoguin authored
24 %% connection is closed. Defaults to 5000 milliseconds.</dd>
c747efb replace quoted:from_url with cowboy_http:urldecode
Magnus Klaar authored
25 %% <dt>urldecode</dt><dd>Function and options argument to use when decoding
26 %% URL encoded strings. Defaults to `{fun cowboy_http:urldecode/2, crash}'.
27 %% </dd>
108a491 Add documentation for the public interface.
Loïc Hoguin authored
28 %% </dl>
29 %%
30 %% Note that there is no need to monitor these processes when using Cowboy as
31 %% an application as it already supervises them under the listener supervisor.
32 %%
33 %% @see cowboy_dispatcher
34 %% @see cowboy_http_handler
da72255 Initial commit.
Loïc Hoguin authored
35 -module(cowboy_http_protocol).
d8e841c @hakvroot Add cowboy_protocol behaviour
hakvroot authored
36 -behaviour(cowboy_protocol).
108a491 Add documentation for the public interface.
Loïc Hoguin authored
37
43d14b5 Give the ListenerPid to the protocol on startup
Loïc Hoguin authored
38 -export([start_link/4]). %% API.
5e006be Add support for loops in standard HTTP handlers
Loïc Hoguin authored
39 -export([init/4, parse_request/1, handler_loop/3]). %% FSM.
da72255 Initial commit.
Loïc Hoguin authored
40
d9212c2 Remove the redundant include/ from -include("http.hrl")
Loïc Hoguin authored
41 -include("http.hrl").
24bf2c5 Ensure header names are handled in a case insensitive manner
Loïc Hoguin authored
42 -include_lib("eunit/include/eunit.hrl").
da72255 Initial commit.
Loïc Hoguin authored
43
44 -record(state, {
43d14b5 Give the ListenerPid to the protocol on startup
Loïc Hoguin authored
45 listener :: pid(),
15dc645 Use the inet:socket() type instead of the user-defined one.
Loïc Hoguin authored
46 socket :: inet:socket(),
da72255 Initial commit.
Loïc Hoguin authored
47 transport :: module(),
2beb5c8 Move the dispatcher related types into cowboy_dispatcher.
Loïc Hoguin authored
48 dispatch :: cowboy_dispatcher:dispatch_rules(),
3e55cb6 Refresh the type specifications.
Loïc Hoguin authored
49 handler :: {module(), any()},
8e2cc3d Add an 'onrequest' hook for HTTP
Loïc Hoguin authored
50 onrequest :: undefined | fun((#http_req{}) -> #http_req{}),
c747efb replace quoted:from_url with cowboy_http:urldecode
Magnus Klaar authored
51 urldecode :: {fun((binary(), T) -> binary()), T},
6ec20b7 Limit the number of empty lines to allow before the request-line.
Loïc Hoguin authored
52 req_empty_lines = 0 :: integer(),
53 max_empty_lines :: integer(),
72d9158 Add a max_keepalive HTTP protocol option
Loïc Hoguin authored
54 req_keepalive = 1 :: integer(),
55 max_keepalive :: integer(),
fe5f0ca Add a max_line_length to the HTTP protocol
Loïc Hoguin authored
56 max_line_length :: integer(),
da72255 Initial commit.
Loïc Hoguin authored
57 timeout :: timeout(),
5e006be Add support for loops in standard HTTP handlers
Loïc Hoguin authored
58 buffer = <<>> :: binary(),
59 hibernate = false :: boolean(),
60 loop_timeout = infinity :: timeout(),
61 loop_timeout_ref :: undefined | reference()
da72255 Initial commit.
Loïc Hoguin authored
62 }).
63
64 %% API.
65
108a491 Add documentation for the public interface.
Loïc Hoguin authored
66 %% @doc Start an HTTP protocol process.
43d14b5 Give the ListenerPid to the protocol on startup
Loïc Hoguin authored
67 -spec start_link(pid(), inet:socket(), module(), any()) -> {ok, pid()}.
68 start_link(ListenerPid, Socket, Transport, Opts) ->
69 Pid = spawn_link(?MODULE, init, [ListenerPid, Socket, Transport, Opts]),
da72255 Initial commit.
Loïc Hoguin authored
70 {ok, Pid}.
71
72 %% FSM.
73
108a491 Add documentation for the public interface.
Loïc Hoguin authored
74 %% @private
062db95 Apply a trick to the erlang:hibernate calls to suppress dialyzer warning...
Loïc Hoguin authored
75 -spec init(pid(), inet:socket(), module(), any()) -> ok.
43d14b5 Give the ListenerPid to the protocol on startup
Loïc Hoguin authored
76 init(ListenerPid, Socket, Transport, Opts) ->
da72255 Initial commit.
Loïc Hoguin authored
77 Dispatch = proplists:get_value(dispatch, Opts, []),
6ec20b7 Limit the number of empty lines to allow before the request-line.
Loïc Hoguin authored
78 MaxEmptyLines = proplists:get_value(max_empty_lines, Opts, 5),
72d9158 Add a max_keepalive HTTP protocol option
Loïc Hoguin authored
79 MaxKeepalive = proplists:get_value(max_keepalive, Opts, infinity),
fe5f0ca Add a max_line_length to the HTTP protocol
Loïc Hoguin authored
80 MaxLineLength = proplists:get_value(max_line_length, Opts, 4096),
8e2cc3d Add an 'onrequest' hook for HTTP
Loïc Hoguin authored
81 OnRequest = proplists:get_value(onrequest, Opts),
da72255 Initial commit.
Loïc Hoguin authored
82 Timeout = proplists:get_value(timeout, Opts, 5000),
c747efb replace quoted:from_url with cowboy_http:urldecode
Magnus Klaar authored
83 URLDecDefault = {fun cowboy_http:urldecode/2, crash},
84 URLDec = proplists:get_value(urldecode, Opts, URLDecDefault),
e550ba7 Add cowboy:accept_ack/1 for a cleaner handling of the shoot message
Loïc Hoguin authored
85 ok = cowboy:accept_ack(ListenerPid),
43d14b5 Give the ListenerPid to the protocol on startup
Loïc Hoguin authored
86 wait_request(#state{listener=ListenerPid, socket=Socket, transport=Transport,
fe5f0ca Add a max_line_length to the HTTP protocol
Loïc Hoguin authored
87 dispatch=Dispatch, max_empty_lines=MaxEmptyLines,
72d9158 Add a max_keepalive HTTP protocol option
Loïc Hoguin authored
88 max_keepalive=MaxKeepalive, max_line_length=MaxLineLength,
8e2cc3d Add an 'onrequest' hook for HTTP
Loïc Hoguin authored
89 timeout=Timeout, onrequest=OnRequest, urldecode=URLDec}).
da72255 Initial commit.
Loïc Hoguin authored
90
108a491 Add documentation for the public interface.
Loïc Hoguin authored
91 %% @private
062db95 Apply a trick to the erlang:hibernate calls to suppress dialyzer warning...
Loïc Hoguin authored
92 -spec parse_request(#state{}) -> ok.
fe5f0ca Add a max_line_length to the HTTP protocol
Loïc Hoguin authored
93 %% We limit the length of the Request-line to MaxLength to avoid endlessly
94 %% reading from the socket and eventually crashing.
95 parse_request(State=#state{buffer=Buffer, max_line_length=MaxLength}) ->
29e71cf Switch the HTTP protocol to use binary packets instead of lists.
Loïc Hoguin authored
96 case erlang:decode_packet(http_bin, Buffer, []) of
97 {ok, Request, Rest} -> request(Request, State#state{buffer=Rest});
fe5f0ca Add a max_line_length to the HTTP protocol
Loïc Hoguin authored
98 {more, _Length} when byte_size(Buffer) > MaxLength ->
99 error_terminate(413, State);
29e71cf Switch the HTTP protocol to use binary packets instead of lists.
Loïc Hoguin authored
100 {more, _Length} -> wait_request(State);
547107c Close connection on all errors
Loïc Hoguin authored
101 {error, _Reason} -> error_terminate(400, State)
29e71cf Switch the HTTP protocol to use binary packets instead of lists.
Loïc Hoguin authored
102 end.
103
062db95 Apply a trick to the erlang:hibernate calls to suppress dialyzer warning...
Loïc Hoguin authored
104 -spec wait_request(#state{}) -> ok.
29e71cf Switch the HTTP protocol to use binary packets instead of lists.
Loïc Hoguin authored
105 wait_request(State=#state{socket=Socket, transport=Transport,
106 timeout=T, buffer=Buffer}) ->
da72255 Initial commit.
Loïc Hoguin authored
107 case Transport:recv(Socket, 0, T) of
29e71cf Switch the HTTP protocol to use binary packets instead of lists.
Loïc Hoguin authored
108 {ok, Data} -> parse_request(State#state{
109 buffer= << Buffer/binary, Data/binary >>});
d25c307 Do not send a 408 response if the Request-Line wasn't fully received
Loïc Hoguin authored
110 {error, _Reason} -> terminate(State)
da72255 Initial commit.
Loïc Hoguin authored
111 end.
112
314483a Rename the type http_uri/0 to cowboy_http:uri/0
Loïc Hoguin authored
113 -spec request({http_request, cowboy_http:method(), cowboy_http:uri(),
062db95 Apply a trick to the erlang:hibernate calls to suppress dialyzer warning...
Loïc Hoguin authored
114 cowboy_http:version()}, #state{}) -> ok.
da72255 Initial commit.
Loïc Hoguin authored
115 request({http_request, _Method, _URI, Version}, State)
116 when Version =/= {1, 0}, Version =/= {1, 1} ->
117 error_terminate(505, State);
e7b6e2a Added absoluteURI support
David Kelly authored
118 %% We still receive the original Host header.
119 request({http_request, Method, {absoluteURI, _Scheme, _Host, _Port, Path},
120 Version}, State) ->
121 request({http_request, Method, {abs_path, Path}, Version}, State);
da72255 Initial commit.
Loïc Hoguin authored
122 request({http_request, Method, {abs_path, AbsPath}, Version},
c747efb replace quoted:from_url with cowboy_http:urldecode
Magnus Klaar authored
123 State=#state{socket=Socket, transport=Transport,
124 urldecode={URLDecFun, URLDecArg}=URLDec}) ->
125 URLDecode = fun(Bin) -> URLDecFun(Bin, URLDecArg) end,
126 {Path, RawPath, Qs} = cowboy_dispatcher:split_path(AbsPath, URLDecode),
6fad3f7 Default the connection to keep-alive on HTTP/1.1 and close on 1.0.
Loïc Hoguin authored
127 ConnAtom = version_to_connection(Version),
29e71cf Switch the HTTP protocol to use binary packets instead of lists.
Loïc Hoguin authored
128 parse_header(#http_req{socket=Socket, transport=Transport,
fd211d3 Fix handler crashes handling
Loïc Hoguin authored
129 connection=ConnAtom, pid=self(), method=Method, version=Version,
c747efb replace quoted:from_url with cowboy_http:urldecode
Magnus Klaar authored
130 path=Path, raw_path=RawPath, raw_qs=Qs, urldecode=URLDec}, State);
a4f8bb6 Add support for the '*' path.
Loïc Hoguin authored
131 request({http_request, Method, '*', Version},
c747efb replace quoted:from_url with cowboy_http:urldecode
Magnus Klaar authored
132 State=#state{socket=Socket, transport=Transport, urldecode=URLDec}) ->
6fad3f7 Default the connection to keep-alive on HTTP/1.1 and close on 1.0.
Loïc Hoguin authored
133 ConnAtom = version_to_connection(Version),
29e71cf Switch the HTTP protocol to use binary packets instead of lists.
Loïc Hoguin authored
134 parse_header(#http_req{socket=Socket, transport=Transport,
fd211d3 Fix handler crashes handling
Loïc Hoguin authored
135 connection=ConnAtom, pid=self(), method=Method, version=Version,
c747efb replace quoted:from_url with cowboy_http:urldecode
Magnus Klaar authored
136 path='*', raw_path= <<"*">>, raw_qs= <<>>, urldecode=URLDec}, State);
673c7e2 Reply with error 501 on all non absolute path URIs for now.
Loïc Hoguin authored
137 request({http_request, _Method, _URI, _Version}, State) ->
138 error_terminate(501, State);
29e71cf Switch the HTTP protocol to use binary packets instead of lists.
Loïc Hoguin authored
139 request({http_error, <<"\r\n">>},
6ec20b7 Limit the number of empty lines to allow before the request-line.
Loïc Hoguin authored
140 State=#state{req_empty_lines=N, max_empty_lines=N}) ->
141 error_terminate(400, State);
29e71cf Switch the HTTP protocol to use binary packets instead of lists.
Loïc Hoguin authored
142 request({http_error, <<"\r\n">>}, State=#state{req_empty_lines=N}) ->
143 parse_request(State#state{req_empty_lines=N + 1});
0201f7f cowboy_http_protocol shouldn't crash if the client sends HTTP responses
Loïc Hoguin authored
144 request(_Any, State) ->
7ef67d0 Reply with error 400 on all bad Request-Lines received.
Loïc Hoguin authored
145 error_terminate(400, State).
da72255 Initial commit.
Loïc Hoguin authored
146
062db95 Apply a trick to the erlang:hibernate calls to suppress dialyzer warning...
Loïc Hoguin authored
147 -spec parse_header(#http_req{}, #state{}) -> ok.
fe5f0ca Add a max_line_length to the HTTP protocol
Loïc Hoguin authored
148 parse_header(Req, State=#state{buffer=Buffer, max_line_length=MaxLength}) ->
29e71cf Switch the HTTP protocol to use binary packets instead of lists.
Loïc Hoguin authored
149 case erlang:decode_packet(httph_bin, Buffer, []) of
150 {ok, Header, Rest} -> header(Header, Req, State#state{buffer=Rest});
fe5f0ca Add a max_line_length to the HTTP protocol
Loïc Hoguin authored
151 {more, _Length} when byte_size(Buffer) > MaxLength ->
152 error_terminate(413, State);
29e71cf Switch the HTTP protocol to use binary packets instead of lists.
Loïc Hoguin authored
153 {more, _Length} -> wait_header(Req, State);
547107c Close connection on all errors
Loïc Hoguin authored
154 {error, _Reason} -> error_terminate(400, State)
29e71cf Switch the HTTP protocol to use binary packets instead of lists.
Loïc Hoguin authored
155 end.
156
062db95 Apply a trick to the erlang:hibernate calls to suppress dialyzer warning...
Loïc Hoguin authored
157 -spec wait_header(#http_req{}, #state{}) -> ok.
da72255 Initial commit.
Loïc Hoguin authored
158 wait_header(Req, State=#state{socket=Socket,
29e71cf Switch the HTTP protocol to use binary packets instead of lists.
Loïc Hoguin authored
159 transport=Transport, timeout=T, buffer=Buffer}) ->
da72255 Initial commit.
Loïc Hoguin authored
160 case Transport:recv(Socket, 0, T) of
29e71cf Switch the HTTP protocol to use binary packets instead of lists.
Loïc Hoguin authored
161 {ok, Data} -> parse_header(Req, State#state{
162 buffer= << Buffer/binary, Data/binary >>});
da72255 Initial commit.
Loïc Hoguin authored
163 {error, timeout} -> error_terminate(408, State);
164 {error, closed} -> terminate(State)
165 end.
166
a297d5e Rename the type http_header/0 to cowboy_http:header/0
Loïc Hoguin authored
167 -spec header({http_header, integer(), cowboy_http:header(), any(), binary()}
062db95 Apply a trick to the erlang:hibernate calls to suppress dialyzer warning...
Loïc Hoguin authored
168 | http_eoh, #http_req{}, #state{}) -> ok.
6c1f73c Add cowboy_http_req:port/1.
Loïc Hoguin authored
169 header({http_header, _I, 'Host', _R, RawHost}, Req=#http_req{
170 transport=Transport, host=undefined}, State) ->
9a775cc Move a few binary string handling functions to cowboy_bstr
Loïc Hoguin authored
171 RawHost2 = cowboy_bstr:to_lower(RawHost),
6c1f73c Add cowboy_http_req:port/1.
Loïc Hoguin authored
172 case catch cowboy_dispatcher:split_host(RawHost2) of
173 {Host, RawHost3, undefined} ->
174 Port = default_port(Transport:name()),
8e2cc3d Add an 'onrequest' hook for HTTP
Loïc Hoguin authored
175 parse_header(Req#http_req{
89ae3c8 'Host' header is optional in HTTP/1.0
Loïc Hoguin authored
176 host=Host, raw_host=RawHost3, port=Port,
6c1f73c Add cowboy_http_req:port/1.
Loïc Hoguin authored
177 headers=[{'Host', RawHost3}|Req#http_req.headers]}, State);
178 {Host, RawHost3, Port} ->
8e2cc3d Add an 'onrequest' hook for HTTP
Loïc Hoguin authored
179 parse_header(Req#http_req{
89ae3c8 'Host' header is optional in HTTP/1.0
Loïc Hoguin authored
180 host=Host, raw_host=RawHost3, port=Port,
6c1f73c Add cowboy_http_req:port/1.
Loïc Hoguin authored
181 headers=[{'Host', RawHost3}|Req#http_req.headers]}, State);
182 {'EXIT', _Reason} ->
183 error_terminate(400, State)
786a05a Run the dispatcher as early as possible to quickly dismiss 404 errors.
Loïc Hoguin authored
184 end;
ebe6381 Ignore all extra Host values sent in the request.
Loïc Hoguin authored
185 %% Ignore Host headers if we already have it.
186 header({http_header, _I, 'Host', _R, _V}, Req, State) ->
29e71cf Switch the HTTP protocol to use binary packets instead of lists.
Loïc Hoguin authored
187 parse_header(Req, State);
bf5c271 Parse 'Connection' headers as a list of tokens
Loïc Hoguin authored
188 header({http_header, _I, 'Connection', _R, Connection},
189 Req=#http_req{headers=Headers}, State) ->
190 Req2 = Req#http_req{headers=[{'Connection', Connection}|Headers]},
c605c4f Add 'Accept' header parsing
Loïc Hoguin authored
191 {ConnTokens, Req3}
bf5c271 Parse 'Connection' headers as a list of tokens
Loïc Hoguin authored
192 = cowboy_http_req:parse_header('Connection', Req2),
193 ConnAtom = cowboy_http:connection_to_atom(ConnTokens),
194 parse_header(Req3#http_req{connection=ConnAtom}, State);
da72255 Initial commit.
Loïc Hoguin authored
195 header({http_header, _I, Field, _R, Value}, Req, State) ->
24bf2c5 Ensure header names are handled in a case insensitive manner
Loïc Hoguin authored
196 Field2 = format_header(Field),
197 parse_header(Req#http_req{headers=[{Field2, Value}|Req#http_req.headers]},
da72255 Initial commit.
Loïc Hoguin authored
198 State);
89ae3c8 'Host' header is optional in HTTP/1.0
Loïc Hoguin authored
199 %% The Host header is required in HTTP/1.1.
200 header(http_eoh, #http_req{version={1, 1}, host=undefined}, State) ->
da72255 Initial commit.
Loïc Hoguin authored
201 error_terminate(400, State);
89ae3c8 'Host' header is optional in HTTP/1.0
Loïc Hoguin authored
202 %% It is however optional in HTTP/1.0.
203 header(http_eoh, Req=#http_req{version={1, 0}, transport=Transport,
204 host=undefined}, State=#state{buffer=Buffer}) ->
205 Port = default_port(Transport:name()),
8e2cc3d Add an 'onrequest' hook for HTTP
Loïc Hoguin authored
206 onrequest(Req#http_req{host=[], raw_host= <<>>,
89ae3c8 'Host' header is optional in HTTP/1.0
Loïc Hoguin authored
207 port=Port, buffer=Buffer}, State#state{buffer= <<>>});
29e71cf Switch the HTTP protocol to use binary packets instead of lists.
Loïc Hoguin authored
208 header(http_eoh, Req, State=#state{buffer=Buffer}) ->
8e2cc3d Add an 'onrequest' hook for HTTP
Loïc Hoguin authored
209 onrequest(Req#http_req{buffer=Buffer}, State#state{buffer= <<>>});
f81cb89 Reply status 400 if we receive an unexpected value or error for headers
Loïc Hoguin authored
210 header(_Any, _Req, State) ->
211 error_terminate(400, State).
da72255 Initial commit.
Loïc Hoguin authored
212
8e2cc3d Add an 'onrequest' hook for HTTP
Loïc Hoguin authored
213 %% Call the global onrequest callback. The callback can send a reply,
214 %% in which case we consider the request handled and move on to the next
215 %% one. Note that since we haven't dispatched yet, we don't know the
216 %% handler, host_info, path_info or bindings yet.
217 -spec onrequest(#http_req{}, #state{}) -> ok.
218 onrequest(Req, State=#state{onrequest=undefined}) ->
219 dispatch(Req, State);
220 onrequest(Req, State=#state{onrequest=OnRequest}) ->
221 Req2 = OnRequest(Req),
222 case Req2#http_req.resp_state of
223 waiting -> dispatch(Req2, State);
224 _ -> next_request(Req2, State, ok)
225 end.
226
227 -spec dispatch(#http_req{}, #state{}) -> ok.
228 dispatch(Req=#http_req{host=Host, path=Path},
6c1f73c Add cowboy_http_req:port/1.
Loïc Hoguin authored
229 State=#state{dispatch=Dispatch}) ->
230 case cowboy_dispatcher:match(Host, Path, Dispatch) of
0ca8f13 @nox Implement path_info feature
nox authored
231 {ok, Handler, Opts, Binds, HostInfo, PathInfo} ->
8e2cc3d Add an 'onrequest' hook for HTTP
Loïc Hoguin authored
232 handler_init(Req#http_req{host_info=HostInfo, path_info=PathInfo,
89ae3c8 'Host' header is optional in HTTP/1.0
Loïc Hoguin authored
233 bindings=Binds}, State#state{handler={Handler, Opts}});
6c1f73c Add cowboy_http_req:port/1.
Loïc Hoguin authored
234 {error, notfound, host} ->
235 error_terminate(400, State);
236 {error, notfound, path} ->
237 error_terminate(404, State)
238 end.
239
062db95 Apply a trick to the erlang:hibernate calls to suppress dialyzer warning...
Loïc Hoguin authored
240 -spec handler_init(#http_req{}, #state{}) -> ok.
8d2102f Allow HTTP protocol upgrades to use keepalive
Loïc Hoguin authored
241 handler_init(Req, State=#state{transport=Transport,
242 handler={Handler, Opts}}) ->
4c4030a Send a meaningful error to error_logger on handler crashes.
Loïc Hoguin authored
243 try Handler:init({Transport:name(), http}, Req, Opts) of
73b120b Fix a pattern matching bug in cowboy_http_protocol:handler_init/2.
Loïc Hoguin authored
244 {ok, Req2, HandlerState} ->
5e006be Add support for loops in standard HTTP handlers
Loïc Hoguin authored
245 handler_handle(HandlerState, Req2, State);
bf1b84e Fix a misnamed variable for {loop, ...} returns in init/3
Loïc Hoguin authored
246 {loop, Req2, HandlerState} ->
247 handler_before_loop(HandlerState, Req2, State);
248 {loop, Req2, HandlerState, hibernate} ->
249 handler_before_loop(HandlerState, Req2,
5e006be Add support for loops in standard HTTP handlers
Loïc Hoguin authored
250 State#state{hibernate=true});
bf1b84e Fix a misnamed variable for {loop, ...} returns in init/3
Loïc Hoguin authored
251 {loop, Req2, HandlerState, Timeout} ->
252 handler_before_loop(HandlerState, Req2,
5e006be Add support for loops in standard HTTP handlers
Loïc Hoguin authored
253 State#state{loop_timeout=Timeout});
bf1b84e Fix a misnamed variable for {loop, ...} returns in init/3
Loïc Hoguin authored
254 {loop, Req2, HandlerState, Timeout, hibernate} ->
255 handler_before_loop(HandlerState, Req2,
5e006be Add support for loops in standard HTTP handlers
Loïc Hoguin authored
256 State#state{hibernate=true, loop_timeout=Timeout});
138cccb Allow HTTP handlers to skip the handle/2 step in init/3
Loïc Hoguin authored
257 {shutdown, Req2, HandlerState} ->
258 handler_terminate(HandlerState, Req2, State);
9fe8141 Allow Handler:init/3 to request a protocol upgrade.
Loïc Hoguin authored
259 %% @todo {upgrade, transport, Module}
260 {upgrade, protocol, Module} ->
8d2102f Allow HTTP protocol upgrades to use keepalive
Loïc Hoguin authored
261 upgrade_protocol(Req, State, Module)
4c4030a Send a meaningful error to error_logger on handler crashes.
Loïc Hoguin authored
262 catch Class:Reason ->
263 error_terminate(500, State),
5a7040e Convert request to proplist when logging
Magnus Klaar authored
264 PLReq = lists:zip(record_info(fields, http_req), tl(tuple_to_list(Req))),
4c4030a Send a meaningful error to error_logger on handler crashes.
Loïc Hoguin authored
265 error_logger:error_msg(
eff9477 Improve the error message for HTTP handlers
Loïc Hoguin authored
266 "** Handler ~p terminating in init/3~n"
267 " for the reason ~p:~p~n"
268 "** Options were ~p~n"
269 "** Request was ~p~n** Stacktrace: ~p~n~n",
5a7040e Convert request to proplist when logging
Magnus Klaar authored
270 [Handler, Class, Reason, Opts, PLReq, erlang:get_stacktrace()])
f532355 Introduce Handler:init/2 for initializing the handler state.
Loïc Hoguin authored
271 end.
272
062db95 Apply a trick to the erlang:hibernate calls to suppress dialyzer warning...
Loïc Hoguin authored
273 -spec upgrade_protocol(#http_req{}, #state{}, atom()) -> ok.
8d2102f Allow HTTP protocol upgrades to use keepalive
Loïc Hoguin authored
274 upgrade_protocol(Req, State=#state{listener=ListenerPid,
275 handler={Handler, Opts}}, Module) ->
276 case Module:upgrade(ListenerPid, Handler, Opts, Req) of
277 {UpgradeRes, Req2} -> next_request(Req2, State, UpgradeRes);
278 _Any -> terminate(State)
279 end.
280
062db95 Apply a trick to the erlang:hibernate calls to suppress dialyzer warning...
Loïc Hoguin authored
281 -spec handler_handle(any(), #http_req{}, #state{}) -> ok.
5e006be Add support for loops in standard HTTP handlers
Loïc Hoguin authored
282 handler_handle(HandlerState, Req, State=#state{handler={Handler, Opts}}) ->
138cccb Allow HTTP handlers to skip the handle/2 step in init/3
Loïc Hoguin authored
283 try Handler:handle(Req, HandlerState) of
408f167 Move the reply function to cowboy_http_req.
Loïc Hoguin authored
284 {ok, Req2, HandlerState2} ->
8d2102f Allow HTTP protocol upgrades to use keepalive
Loïc Hoguin authored
285 terminate_request(HandlerState2, Req2, State)
4c4030a Send a meaningful error to error_logger on handler crashes.
Loïc Hoguin authored
286 catch Class:Reason ->
5a7040e Convert request to proplist when logging
Magnus Klaar authored
287 PLReq = lists:zip(record_info(fields, http_req), tl(tuple_to_list(Req))),
4c4030a Send a meaningful error to error_logger on handler crashes.
Loïc Hoguin authored
288 error_logger:error_msg(
eff9477 Improve the error message for HTTP handlers
Loïc Hoguin authored
289 "** Handler ~p terminating in handle/2~n"
290 " for the reason ~p:~p~n"
4c4030a Send a meaningful error to error_logger on handler crashes.
Loïc Hoguin authored
291 "** Options were ~p~n** Handler state was ~p~n"
292 "** Request was ~p~n** Stacktrace: ~p~n~n",
293 [Handler, Class, Reason, Opts,
5a7040e Convert request to proplist when logging
Magnus Klaar authored
294 HandlerState, PLReq, erlang:get_stacktrace()]),
474f4e0 Call Handler:terminate/2 even on error in Handler:handle/2
Loïc Hoguin authored
295 handler_terminate(HandlerState, Req, State),
fd211d3 Fix handler crashes handling
Loïc Hoguin authored
296 error_terminate(500, State)
c6ad027 Introduce Handler:terminate to cleanup the handler's state.
Loïc Hoguin authored
297 end.
298
5e006be Add support for loops in standard HTTP handlers
Loïc Hoguin authored
299 %% We don't listen for Transport closes because that would force us
300 %% to receive data and buffer it indefinitely.
062db95 Apply a trick to the erlang:hibernate calls to suppress dialyzer warning...
Loïc Hoguin authored
301 -spec handler_before_loop(any(), #http_req{}, #state{}) -> ok.
5e006be Add support for loops in standard HTTP handlers
Loïc Hoguin authored
302 handler_before_loop(HandlerState, Req, State=#state{hibernate=true}) ->
303 State2 = handler_loop_timeout(State),
062db95 Apply a trick to the erlang:hibernate calls to suppress dialyzer warning...
Loïc Hoguin authored
304 catch erlang:hibernate(?MODULE, handler_loop,
305 [HandlerState, Req, State2#state{hibernate=false}]),
306 ok;
5e006be Add support for loops in standard HTTP handlers
Loïc Hoguin authored
307 handler_before_loop(HandlerState, Req, State) ->
308 State2 = handler_loop_timeout(State),
309 handler_loop(HandlerState, Req, State2).
310
311 %% Almost the same code can be found in cowboy_http_websocket.
312 -spec handler_loop_timeout(#state{}) -> #state{}.
313 handler_loop_timeout(State=#state{loop_timeout=infinity}) ->
314 State#state{loop_timeout_ref=undefined};
315 handler_loop_timeout(State=#state{loop_timeout=Timeout,
316 loop_timeout_ref=PrevRef}) ->
317 _ = case PrevRef of undefined -> ignore; PrevRef ->
318 erlang:cancel_timer(PrevRef) end,
319 TRef = make_ref(),
320 erlang:send_after(Timeout, self(), {?MODULE, timeout, TRef}),
321 State#state{loop_timeout_ref=TRef}.
322
062db95 Apply a trick to the erlang:hibernate calls to suppress dialyzer warning...
Loïc Hoguin authored
323 -spec handler_loop(any(), #http_req{}, #state{}) -> ok.
5e006be Add support for loops in standard HTTP handlers
Loïc Hoguin authored
324 handler_loop(HandlerState, Req, State=#state{loop_timeout_ref=TRef}) ->
325 receive
326 {?MODULE, timeout, TRef} ->
8d2102f Allow HTTP protocol upgrades to use keepalive
Loïc Hoguin authored
327 terminate_request(HandlerState, Req, State);
5e006be Add support for loops in standard HTTP handlers
Loïc Hoguin authored
328 {?MODULE, timeout, OlderTRef} when is_reference(OlderTRef) ->
329 handler_loop(HandlerState, Req, State);
330 Message ->
331 handler_call(HandlerState, Req, State, Message)
332 end.
333
062db95 Apply a trick to the erlang:hibernate calls to suppress dialyzer warning...
Loïc Hoguin authored
334 -spec handler_call(any(), #http_req{}, #state{}, any()) -> ok.
5e006be Add support for loops in standard HTTP handlers
Loïc Hoguin authored
335 handler_call(HandlerState, Req, State=#state{handler={Handler, Opts}},
336 Message) ->
337 try Handler:info(Message, Req, HandlerState) of
338 {ok, Req2, HandlerState2} ->
8d2102f Allow HTTP protocol upgrades to use keepalive
Loïc Hoguin authored
339 terminate_request(HandlerState2, Req2, State);
5e006be Add support for loops in standard HTTP handlers
Loïc Hoguin authored
340 {loop, Req2, HandlerState2} ->
341 handler_before_loop(HandlerState2, Req2, State);
342 {loop, Req2, HandlerState2, hibernate} ->
343 handler_before_loop(HandlerState2, Req2,
344 State#state{hibernate=true})
345 catch Class:Reason ->
5a7040e Convert request to proplist when logging
Magnus Klaar authored
346 PLReq = lists:zip(record_info(fields, http_req), tl(tuple_to_list(Req))),
5e006be Add support for loops in standard HTTP handlers
Loïc Hoguin authored
347 error_logger:error_msg(
348 "** Handler ~p terminating in info/3~n"
349 " for the reason ~p:~p~n"
350 "** Options were ~p~n** Handler state was ~p~n"
351 "** Request was ~p~n** Stacktrace: ~p~n~n",
352 [Handler, Class, Reason, Opts,
5a7040e Convert request to proplist when logging
Magnus Klaar authored
353 HandlerState, PLReq, erlang:get_stacktrace()]),
fd211d3 Fix handler crashes handling
Loïc Hoguin authored
354 handler_terminate(HandlerState, Req, State),
355 error_terminate(500, State)
5e006be Add support for loops in standard HTTP handlers
Loïc Hoguin authored
356 end.
357
358 -spec handler_terminate(any(), #http_req{}, #state{}) -> ok.
474f4e0 Call Handler:terminate/2 even on error in Handler:handle/2
Loïc Hoguin authored
359 handler_terminate(HandlerState, Req, #state{handler={Handler, Opts}}) ->
360 try
aa0a667 Move recursion out of a try .. catch block.
Loïc Hoguin authored
361 Handler:terminate(Req#http_req{resp_state=locked}, HandlerState)
4c4030a Send a meaningful error to error_logger on handler crashes.
Loïc Hoguin authored
362 catch Class:Reason ->
5a7040e Convert request to proplist when logging
Magnus Klaar authored
363 PLReq = lists:zip(record_info(fields, http_req), tl(tuple_to_list(Req))),
4c4030a Send a meaningful error to error_logger on handler crashes.
Loïc Hoguin authored
364 error_logger:error_msg(
eff9477 Improve the error message for HTTP handlers
Loïc Hoguin authored
365 "** Handler ~p terminating in terminate/2~n"
366 " for the reason ~p:~p~n"
4c4030a Send a meaningful error to error_logger on handler crashes.
Loïc Hoguin authored
367 "** Options were ~p~n** Handler state was ~p~n"
368 "** Request was ~p~n** Stacktrace: ~p~n~n",
369 [Handler, Class, Reason, Opts,
5a7040e Convert request to proplist when logging
Magnus Klaar authored
370 HandlerState, PLReq, erlang:get_stacktrace()])
474f4e0 Call Handler:terminate/2 even on error in Handler:handle/2
Loïc Hoguin authored
371 end.
372
062db95 Apply a trick to the erlang:hibernate calls to suppress dialyzer warning...
Loïc Hoguin authored
373 -spec terminate_request(any(), #http_req{}, #state{}) -> ok.
8d2102f Allow HTTP protocol upgrades to use keepalive
Loïc Hoguin authored
374 terminate_request(HandlerState, Req, State) ->
474f4e0 Call Handler:terminate/2 even on error in Handler:handle/2
Loïc Hoguin authored
375 HandlerRes = handler_terminate(HandlerState, Req, State),
8d2102f Allow HTTP protocol upgrades to use keepalive
Loïc Hoguin authored
376 next_request(Req, State, HandlerRes).
377
062db95 Apply a trick to the erlang:hibernate calls to suppress dialyzer warning...
Loïc Hoguin authored
378 -spec next_request(#http_req{}, #state{}, any()) -> ok.
4b93c2d Fix a case where request body wouldn't get cleaned up on keepalive
Loïc Hoguin authored
379 next_request(Req=#http_req{connection=Conn},
72d9158 Add a max_keepalive HTTP protocol option
Loïc Hoguin authored
380 State=#state{req_keepalive=Keepalive, max_keepalive=MaxKeepalive},
381 HandlerRes) ->
547107c Close connection on all errors
Loïc Hoguin authored
382 RespRes = ensure_response(Req),
4b93c2d Fix a case where request body wouldn't get cleaned up on keepalive
Loïc Hoguin authored
383 {BodyRes, Buffer} = ensure_body_processed(Req),
fd211d3 Fix handler crashes handling
Loïc Hoguin authored
384 %% Flush the resp_sent message before moving on.
385 receive {cowboy_http_req, resp_sent} -> ok after 0 -> ok end,
ee77ece Remove the connection information from the HTTP protocol state
Loïc Hoguin authored
386 case {HandlerRes, BodyRes, RespRes, Conn} of
72d9158 Add a max_keepalive HTTP protocol option
Loïc Hoguin authored
387 {ok, ok, ok, keepalive} when Keepalive < MaxKeepalive ->
b669b1b Reset the max number of empty lines between keepalive requests
Loïc Hoguin authored
388 ?MODULE:parse_request(State#state{
72d9158 Add a max_keepalive HTTP protocol option
Loïc Hoguin authored
389 buffer=Buffer, req_empty_lines=0,
390 req_keepalive=Keepalive + 1});
aa0a667 Move recursion out of a try .. catch block.
Loïc Hoguin authored
391 _Closed ->
392 terminate(State)
786a05a Run the dispatcher as early as possible to quickly dismiss 404 errors.
Loïc Hoguin authored
393 end.
da72255 Initial commit.
Loïc Hoguin authored
394
4b93c2d Fix a case where request body wouldn't get cleaned up on keepalive
Loïc Hoguin authored
395 -spec ensure_body_processed(#http_req{}) -> {ok | close, binary()}.
396 ensure_body_processed(#http_req{body_state=done, buffer=Buffer}) ->
397 {ok, Buffer};
8b02992 Skip the request body if it hasn't been read by the handler.
Loïc Hoguin authored
398 ensure_body_processed(Req=#http_req{body_state=waiting}) ->
95e05d8 Add chunked transfer encoding support and rework the body reading API
Loïc Hoguin authored
399 case cowboy_http_req:skip_body(Req) of
400 {ok, Req2} -> {ok, Req2#http_req.buffer};
401 {error, _Reason} -> {close, <<>>}
528507c @nox Add multipart support
nox authored
402 end;
403 ensure_body_processed(Req=#http_req{body_state={multipart, _, _}}) ->
404 {ok, Req2} = cowboy_http_req:multipart_skip(Req),
405 ensure_body_processed(Req2).
8b02992 Skip the request body if it hasn't been read by the handler.
Loïc Hoguin authored
406
547107c Close connection on all errors
Loïc Hoguin authored
407 -spec ensure_response(#http_req{}) -> ok.
e3dc9b2 Add specs to ensure_response and change the clauses order.
Loïc Hoguin authored
408 %% The handler has already fully replied to the client.
547107c Close connection on all errors
Loïc Hoguin authored
409 ensure_response(#http_req{resp_state=done}) ->
e3dc9b2 Add specs to ensure_response and change the clauses order.
Loïc Hoguin authored
410 ok;
e40001a Ensure a response is sent when the handler doesn't reply anything.
Loïc Hoguin authored
411 %% No response has been sent but everything apparently went fine.
412 %% Reply with 204 No Content to indicate this.
547107c Close connection on all errors
Loïc Hoguin authored
413 ensure_response(Req=#http_req{resp_state=waiting}) ->
414 _ = cowboy_http_req:reply(204, [], [], Req),
415 ok;
7e74582 Don't close requests when the replied body is chunked
Loïc Hoguin authored
416 %% Terminate the chunked body for HTTP/1.1 only.
c2be0f2 Remove the 'HEAD' chunked_reply/3 clause
Loïc Hoguin authored
417 ensure_response(#http_req{method='HEAD', resp_state=chunks}) ->
7e74582 Don't close requests when the replied body is chunked
Loïc Hoguin authored
418 ok;
36a6823 Do not send chunked Transfer-Encoding replies for HTTP/1.0
Loïc Hoguin authored
419 ensure_response(#http_req{version={1, 0}, resp_state=chunks}) ->
7e74582 Don't close requests when the replied body is chunked
Loïc Hoguin authored
420 ok;
420f5ba Add chunked reply support.
Loïc Hoguin authored
421 ensure_response(#http_req{socket=Socket, transport=Transport,
547107c Close connection on all errors
Loïc Hoguin authored
422 resp_state=chunks}) ->
420f5ba Add chunked reply support.
Loïc Hoguin authored
423 Transport:send(Socket, <<"0\r\n\r\n">>),
7e74582 Don't close requests when the replied body is chunked
Loïc Hoguin authored
424 ok.
da72255 Initial commit.
Loïc Hoguin authored
425
fd211d3 Fix handler crashes handling
Loïc Hoguin authored
426 %% Only send an error reply if there is no resp_sent message.
16d3cb7 Rename the type http_status/0 to cowboy_http:status/0
Loïc Hoguin authored
427 -spec error_terminate(cowboy_http:status(), #state{}) -> ok.
547107c Close connection on all errors
Loïc Hoguin authored
428 error_terminate(Code, State=#state{socket=Socket, transport=Transport}) ->
fd211d3 Fix handler crashes handling
Loïc Hoguin authored
429 receive
430 {cowboy_http_req, resp_sent} -> ok
431 after 0 ->
432 _ = cowboy_http_req:reply(Code, #http_req{
433 socket=Socket, transport=Transport,
434 connection=close, pid=self(), resp_state=waiting}),
435 ok
436 end,
e40001a Ensure a response is sent when the handler doesn't reply anything.
Loïc Hoguin authored
437 terminate(State).
438
3e55cb6 Refresh the type specifications.
Loïc Hoguin authored
439 -spec terminate(#state{}) -> ok.
da72255 Initial commit.
Loïc Hoguin authored
440 terminate(#state{socket=Socket, transport=Transport}) ->
441 Transport:close(Socket),
442 ok.
443
444 %% Internal.
445
8622dff Rename the type http_version/0 to cowboy_http:version/0
Loïc Hoguin authored
446 -spec version_to_connection(cowboy_http:version()) -> keepalive | close.
6fad3f7 Default the connection to keep-alive on HTTP/1.1 and close on 1.0.
Loïc Hoguin authored
447 version_to_connection({1, 1}) -> keepalive;
448 version_to_connection(_Any) -> close.
449
3e55cb6 Refresh the type specifications.
Loïc Hoguin authored
450 -spec default_port(atom()) -> 80 | 443.
6c1f73c Add cowboy_http_req:port/1.
Loïc Hoguin authored
451 default_port(ssl) -> 443;
452 default_port(_) -> 80.
453
24bf2c5 Ensure header names are handled in a case insensitive manner
Loïc Hoguin authored
454 %% @todo While 32 should be enough for everybody, we should probably make
455 %% this configurable or something.
456 -spec format_header(atom()) -> atom(); (binary()) -> binary().
457 format_header(Field) when is_atom(Field) ->
458 Field;
459 format_header(Field) when byte_size(Field) =< 20; byte_size(Field) > 32 ->
460 Field;
461 format_header(Field) ->
462 format_header(Field, true, <<>>).
463
464 -spec format_header(binary(), boolean(), binary()) -> binary().
465 format_header(<<>>, _Any, Acc) ->
466 Acc;
467 %% Replicate a bug in OTP for compatibility reasons when there's a - right
468 %% after another. Proper use should always be 'true' instead of 'not Bool'.
469 format_header(<< $-, Rest/bits >>, Bool, Acc) ->
470 format_header(Rest, not Bool, << Acc/binary, $- >>);
471 format_header(<< C, Rest/bits >>, true, Acc) ->
9a775cc Move a few binary string handling functions to cowboy_bstr
Loïc Hoguin authored
472 format_header(Rest, false, << Acc/binary, (cowboy_bstr:char_to_upper(C)) >>);
24bf2c5 Ensure header names are handled in a case insensitive manner
Loïc Hoguin authored
473 format_header(<< C, Rest/bits >>, false, Acc) ->
9a775cc Move a few binary string handling functions to cowboy_bstr
Loïc Hoguin authored
474 format_header(Rest, false, << Acc/binary, (cowboy_bstr:char_to_lower(C)) >>).
24bf2c5 Ensure header names are handled in a case insensitive manner
Loïc Hoguin authored
475
476 %% Tests.
477
478 -ifdef(TEST).
479
480 format_header_test_() ->
481 %% {Header, Result}
482 Tests = [
483 {<<"Sec-Websocket-Version">>, <<"Sec-Websocket-Version">>},
484 {<<"Sec-WebSocket-Version">>, <<"Sec-Websocket-Version">>},
485 {<<"sec-websocket-version">>, <<"Sec-Websocket-Version">>},
486 {<<"SEC-WEBSOCKET-VERSION">>, <<"Sec-Websocket-Version">>},
487 %% These last tests ensures we're formatting headers exactly like OTP.
488 %% Even though it's dumb, it's better for compatibility reasons.
489 {<<"Sec-WebSocket--Version">>, <<"Sec-Websocket--version">>},
490 {<<"Sec-WebSocket---Version">>, <<"Sec-Websocket---Version">>}
491 ],
492 [{H, fun() -> R = format_header(H) end} || {H, R} <- Tests].
493
494 -endif.
Something went wrong with that request. Please try again.