aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--README.md3
-rw-r--r--include/http.hrl28
-rw-r--r--src/cowboy_http_protocol.erl102
-rw-r--r--src/cowboy_http_req.erl91
4 files changed, 116 insertions, 108 deletions
diff --git a/README.md b/README.md
index e1363cc..2390abb 100644
--- a/README.md
+++ b/README.md
@@ -69,7 +69,8 @@ handler could be written like this:
{ok, Req, undefined}.
handle(Req, State) ->
- {reply, 200, [], "Hello World!"}.
+ {ok, Req2} = cowboy_http_req:reply(200, [], "Hello World!", Req),
+ {ok, Req2, State}.
terminate(Req, State) ->
ok.
diff --git a/include/http.hrl b/include/http.hrl
index dc78b34..e66947d 100644
--- a/include/http.hrl
+++ b/include/http.hrl
@@ -13,17 +13,19 @@
%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-record(http_req, {
- listener = undefined :: undefined | atom(), %% todo
- method = 'GET' :: http_method(),
- version = {1, 1} :: http_version(),
- peer = undefined :: undefined | {Address::ip_address(), Port::port_number()},
- host = undefined :: undefined | path_tokens(), %% todo
- raw_host = undefined :: undefined | string(), %% todo
- path = undefined :: undefined | path_tokens(), %% todo
- raw_path = undefined :: undefined | string(), %% todo
- qs_vals = undefined :: undefined | bindings(), %% todo
- raw_qs = undefined :: undefined | string(),
- bindings = undefined :: undefined | bindings(),
- headers = [] :: http_headers()
-%% cookies = undefined :: undefined | http_cookies() %% @todo
+ socket = undefined :: undefined | socket(),
+ transport = undefined :: undefined | module(),
+ connection = keepalive :: keepalive | close,
+ method = 'GET' :: http_method(),
+ version = {1, 1} :: http_version(),
+ peer = undefined :: undefined | {Address::ip_address(), Port::port_number()},
+ host = undefined :: undefined | path_tokens(), %% todo
+ raw_host = undefined :: undefined | string(), %% todo
+ path = undefined :: undefined | path_tokens(), %% todo
+ raw_path = undefined :: undefined | string(), %% todo
+ qs_vals = undefined :: undefined | bindings(), %% todo
+ raw_qs = undefined :: undefined | string(),
+ bindings = undefined :: undefined | bindings(),
+ headers = [] :: http_headers()
+%% cookies = undefined :: undefined | http_cookies() %% @todo
}).
diff --git a/src/cowboy_http_protocol.erl b/src/cowboy_http_protocol.erl
index 5d1e232..6af10fd 100644
--- a/src/cowboy_http_protocol.erl
+++ b/src/cowboy_http_protocol.erl
@@ -66,8 +66,8 @@ request({http_request, Method, {abs_path, AbsPath}, Version},
State=#state{socket=Socket, transport=Transport}) ->
{Path, Qs} = cowboy_dispatcher:split_path(AbsPath),
{ok, Peer} = Transport:peername(Socket),
- wait_header(#http_req{method=Method, version=Version,
- peer=Peer, path=Path, raw_qs=Qs}, State).
+ wait_header(#http_req{socket=Socket, transport=Transport, method=Method,
+ version=Version, peer=Peer, path=Path, raw_qs=Qs}, State).
-spec wait_header(Req::#http_req{}, State::#state{}) -> ok.
%% @todo We don't want to wait T at each header...
@@ -96,9 +96,10 @@ header({http_header, _I, 'Host', _R, Value}, Req=#http_req{path=Path},
error_terminate(404, State)
end;
header({http_header, _I, 'Connection', _R, Connection}, Req, State) ->
- wait_header(Req#http_req{
+ ConnAtom = connection_to_atom(Connection),
+ wait_header(Req#http_req{connection=ConnAtom,
headers=[{'Connection', Connection}|Req#http_req.headers]},
- State#state{connection=connection_to_atom(Connection)});
+ State#state{connection=ConnAtom});
header({http_header, _I, Field, _R, Value}, Req, State) ->
wait_header(Req#http_req{headers=[{Field, Value}|Req#http_req.headers]},
State);
@@ -120,11 +121,8 @@ handler_init(Req, State=#state{handler={Handler, Opts}}) ->
State::#state{}) -> ok.
handler_loop(HandlerState, Req, State=#state{handler={Handler, _Opts}}) ->
case Handler:handle(Req, HandlerState) of
- %% @todo {ok, Req2, HandlerState2} -> and use them in handler_terminate
- %% @todo Move the reply code to the cowboy_http_req module.
- {reply, RCode, RHeaders, RBody} ->
- reply(RCode, RHeaders, RBody, State),
- handler_terminate(HandlerState, Req, State)
+ {ok, Req2, HandlerState2} ->
+ handler_terminate(HandlerState2, Req2, State)
%% @todo {mode, active}
end.
@@ -135,31 +133,23 @@ handler_terminate(HandlerState, Req, State=#state{handler={Handler, _Opts}}) ->
%% @todo We need to check if the Req has been replied to.
%% All requests must have a reply, at worst an error.
%% If a request started but wasn't completed, complete it.
- case Res of
- ok -> next_request(State);
- closed -> terminate(State)
+ case {Res, State#state.connection} of
+ {ok, keepalive} -> next_request(State);
+ _Closed -> terminate(State)
end.
-spec error_terminate(Code::http_status(), State::#state{}) -> ok.
-error_terminate(Code, State) ->
- reply(Code, [], [], State#state{connection=close}).
+error_terminate(Code, State=#state{socket=Socket, transport=Transport,
+ connection=Connection}) ->
+ cowboy_http_req:reply(Code, [], [], #http_req{socket=Socket,
+ transport=Transport, connection=Connection}),
+ terminate(State).
-spec terminate(State::#state{}) -> ok.
terminate(#state{socket=Socket, transport=Transport}) ->
Transport:close(Socket),
ok.
--spec reply(Code::http_status(), Headers::http_headers(), Body::iolist(),
- State::#state{}) -> ok.
-%% @todo Don't be naive about the headers!
-reply(Code, Headers, Body, #state{socket=Socket,
- transport=TransportMod, connection=Connection}) ->
- StatusLine = ["HTTP/1.1 ", status(Code), "\r\n"],
- BaseHeaders = ["Connection: ", atom_to_connection(Connection),
- "\r\nContent-Length: ", integer_to_list(iolist_size(Body)), "\r\n"],
- TransportMod:send(Socket,
- [StatusLine, BaseHeaders, Headers, "\r\n", Body]).
-
-spec next_request(State::#state{}) -> ok.
next_request(State=#state{connection=keepalive}) ->
wait_request(State);
@@ -174,65 +164,3 @@ connection_to_atom(Connection) ->
"close" -> close;
_Any -> keepalive
end.
-
--spec atom_to_connection(Atom::keepalive | close) -> string().
-atom_to_connection(keepalive) ->
- "keep-alive";
-atom_to_connection(close) ->
- "close".
-
--spec status(Code::http_status()) -> string().
-status(100) -> "100 Continue";
-status(101) -> "101 Switching Protocols";
-status(102) -> "102 Processing";
-status(200) -> "200 OK";
-status(201) -> "201 Created";
-status(202) -> "202 Accepted";
-status(203) -> "203 Non-Authoritative Information";
-status(204) -> "204 No Content";
-status(205) -> "205 Reset Content";
-status(206) -> "206 Partial Content";
-status(207) -> "207 Multi-Status";
-status(226) -> "226 IM Used";
-status(300) -> "300 Multiple Choices";
-status(301) -> "301 Moved Permanently";
-status(302) -> "302 Found";
-status(303) -> "303 See Other";
-status(304) -> "304 Not Modified";
-status(305) -> "305 Use Proxy";
-status(306) -> "306 Switch Proxy";
-status(307) -> "307 Temporary Redirect";
-status(400) -> "400 Bad Request";
-status(401) -> "401 Unauthorized";
-status(402) -> "402 Payment Required";
-status(403) -> "403 Forbidden";
-status(404) -> "404 Not Found";
-status(405) -> "405 Method Not Allowed";
-status(406) -> "406 Not Acceptable";
-status(407) -> "407 Proxy Authentication Required";
-status(408) -> "408 Request Timeout";
-status(409) -> "409 Conflict";
-status(410) -> "410 Gone";
-status(411) -> "411 Length Required";
-status(412) -> "412 Precondition Failed";
-status(413) -> "413 Request Entity Too Large";
-status(414) -> "414 Request-URI Too Long";
-status(415) -> "415 Unsupported Media Type";
-status(416) -> "416 Requested Range Not Satisfiable";
-status(417) -> "417 Expectation Failed";
-status(418) -> "418 I'm a teapot";
-status(422) -> "422 Unprocessable Entity";
-status(423) -> "423 Locked";
-status(424) -> "424 Failed Dependency";
-status(425) -> "425 Unordered Collection";
-status(426) -> "426 Upgrade Required";
-status(500) -> "500 Internal Server Error";
-status(501) -> "501 Not Implemented";
-status(502) -> "502 Bad Gateway";
-status(503) -> "503 Service Unavailable";
-status(504) -> "504 Gateway Timeout";
-status(505) -> "505 HTTP Version Not Supported";
-status(506) -> "506 Variant Also Negotiates";
-status(507) -> "507 Insufficient Storage";
-status(510) -> "510 Not Extended";
-status(L) when is_list(L) -> L.
diff --git a/src/cowboy_http_req.erl b/src/cowboy_http_req.erl
index 27583c1..1938bc7 100644
--- a/src/cowboy_http_req.erl
+++ b/src/cowboy_http_req.erl
@@ -13,25 +13,26 @@
%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-module(cowboy_http_req).
+
-export([
- listener/1, method/1, version/1, peer/1,
+ method/1, version/1, peer/1,
host/1, raw_host/1,
path/1, raw_path/1,
qs_val/2, qs_val/3, qs_vals/1, raw_qs/1,
binding/2, binding/3, bindings/1,
header/2, header/3, headers/1
%% cookie/2, cookie/3, cookies/1 @todo
-]). %% API.
+]). %% Request API.
+
+-export([
+ reply/4
+]). %% Response API.
-include("include/types.hrl").
-include("include/http.hrl").
-include_lib("eunit/include/eunit.hrl").
-%% API.
-
--spec listener(Req::#http_req{}) -> {Listener::atom(), Req::#http_req{}}.
-listener(Req) ->
- {Req#http_req.listener, Req}.
+%% Request API.
-spec method(Req::#http_req{}) -> {Method::http_method(), Req::#http_req{}}.
method(Req) ->
@@ -125,6 +126,20 @@ header(Name, Default, Req) ->
headers(Req) ->
{Req#http_req.headers, Req}.
+%% Response API.
+
+-spec reply(Code::http_status(), Headers::http_headers(),
+ Body::iolist(), Req::#http_req{}) -> ok.
+%% @todo Don't be naive about the headers!
+reply(Code, Headers, Body, Req=#http_req{socket=Socket,
+ transport=Transport, connection=Connection}) ->
+ StatusLine = ["HTTP/1.1 ", status(Code), "\r\n"],
+ BaseHeaders = ["Connection: ", atom_to_connection(Connection),
+ "\r\nContent-Length: ", integer_to_list(iolist_size(Body)), "\r\n"],
+ Transport:send(Socket,
+ [StatusLine, BaseHeaders, Headers, "\r\n", Body]),
+ {ok, Req}.
+
%% Internal.
-spec parse_qs(Qs::string()) -> list({Name::string(), Value::string()}).
@@ -138,6 +153,68 @@ parse_qs(Qs) ->
{Name, Value}
end || Token <- Tokens].
+-spec atom_to_connection(Atom::keepalive | close) -> string().
+atom_to_connection(keepalive) ->
+ "keep-alive";
+atom_to_connection(close) ->
+ "close".
+
+-spec status(Code::http_status()) -> string().
+status(100) -> "100 Continue";
+status(101) -> "101 Switching Protocols";
+status(102) -> "102 Processing";
+status(200) -> "200 OK";
+status(201) -> "201 Created";
+status(202) -> "202 Accepted";
+status(203) -> "203 Non-Authoritative Information";
+status(204) -> "204 No Content";
+status(205) -> "205 Reset Content";
+status(206) -> "206 Partial Content";
+status(207) -> "207 Multi-Status";
+status(226) -> "226 IM Used";
+status(300) -> "300 Multiple Choices";
+status(301) -> "301 Moved Permanently";
+status(302) -> "302 Found";
+status(303) -> "303 See Other";
+status(304) -> "304 Not Modified";
+status(305) -> "305 Use Proxy";
+status(306) -> "306 Switch Proxy";
+status(307) -> "307 Temporary Redirect";
+status(400) -> "400 Bad Request";
+status(401) -> "401 Unauthorized";
+status(402) -> "402 Payment Required";
+status(403) -> "403 Forbidden";
+status(404) -> "404 Not Found";
+status(405) -> "405 Method Not Allowed";
+status(406) -> "406 Not Acceptable";
+status(407) -> "407 Proxy Authentication Required";
+status(408) -> "408 Request Timeout";
+status(409) -> "409 Conflict";
+status(410) -> "410 Gone";
+status(411) -> "411 Length Required";
+status(412) -> "412 Precondition Failed";
+status(413) -> "413 Request Entity Too Large";
+status(414) -> "414 Request-URI Too Long";
+status(415) -> "415 Unsupported Media Type";
+status(416) -> "416 Requested Range Not Satisfiable";
+status(417) -> "417 Expectation Failed";
+status(418) -> "418 I'm a teapot";
+status(422) -> "422 Unprocessable Entity";
+status(423) -> "423 Locked";
+status(424) -> "424 Failed Dependency";
+status(425) -> "425 Unordered Collection";
+status(426) -> "426 Upgrade Required";
+status(500) -> "500 Internal Server Error";
+status(501) -> "501 Not Implemented";
+status(502) -> "502 Bad Gateway";
+status(503) -> "503 Service Unavailable";
+status(504) -> "504 Gateway Timeout";
+status(505) -> "505 HTTP Version Not Supported";
+status(506) -> "506 Variant Also Negotiates";
+status(507) -> "507 Insufficient Storage";
+status(510) -> "510 Not Extended";
+status(L) when is_list(L) -> L.
+
%% Tests.
-ifdef(TEST).