aboutsummaryrefslogtreecommitdiffstats
path: root/src/cowboy_req.erl
diff options
context:
space:
mode:
Diffstat (limited to 'src/cowboy_req.erl')
-rw-r--r--src/cowboy_req.erl527
1 files changed, 302 insertions, 225 deletions
diff --git a/src/cowboy_req.erl b/src/cowboy_req.erl
index 151b4dc..cdd1e06 100644
--- a/src/cowboy_req.erl
+++ b/src/cowboy_req.erl
@@ -42,7 +42,7 @@
-module(cowboy_req).
%% Request API.
--export([new/9]).
+-export([new/13]).
-export([method/1]).
-export([version/1]).
-export([peer/1]).
@@ -56,6 +56,7 @@
-export([qs_val/2]).
-export([qs_val/3]).
-export([qs_vals/1]).
+-export([fragment/1]).
-export([host_url/1]).
-export([url/1]).
-export([binding/2]).
@@ -103,14 +104,9 @@
-export([ensure_response/2]).
%% Private setter/getter API.
--export([set_host/4]).
--export([set_connection/2]).
--export([add_header/3]).
--export([set_buffer/2]).
+-export([get/2]).
+-export([set/2]).
-export([set_bindings/4]).
--export([get_resp_state/1]).
--export([get_buffer/1]).
--export([get_connection/1]).
%% Misc API.
-export([compact/1]).
@@ -122,6 +118,8 @@
-include_lib("eunit/include/eunit.hrl").
-endif.
+-type resp_body_fun() :: fun(() -> {sent, non_neg_integer()}).
+
-record(http_req, {
%% Transport.
socket = undefined :: undefined | inet:socket(),
@@ -130,7 +128,7 @@
%% Request.
pid = undefined :: pid(),
- method = 'GET' :: cowboy_http:method(),
+ method = <<"GET">> :: binary(),
version = {1, 1} :: cowboy_http:version(),
peer = undefined :: undefined | {inet:ip_address(), inet:port_number()},
host = undefined :: undefined | binary(),
@@ -140,6 +138,7 @@
path_info = undefined :: undefined | cowboy_dispatcher:tokens(),
qs = undefined :: binary(),
qs_vals = undefined :: undefined | list({binary(), binary() | true}),
+ fragment = undefined :: binary(),
bindings = undefined :: undefined | cowboy_dispatcher:bindings(),
headers = [] :: cowboy_http:headers(),
p_headers = [] :: [any()], %% @todo Improve those specs.
@@ -154,12 +153,10 @@
%% Response.
resp_state = waiting :: locked | waiting | chunks | done,
resp_headers = [] :: cowboy_http:headers(),
- resp_body = <<>> :: iodata()
- | {non_neg_integer(), fun(() -> {sent, non_neg_integer()})},
+ resp_body = <<>> :: iodata() | {non_neg_integer(), resp_body_fun()},
%% Functions.
- onresponse = undefined :: undefined | cowboy_protocol:onresponse_fun(),
- urldecode :: {fun((binary(), T) -> binary()), T}
+ onresponse = undefined :: undefined | cowboy_protocol:onresponse_fun()
}).
-opaque req() :: #http_req{}.
@@ -171,18 +168,40 @@
%%
%% This function takes care of setting the owner's pid to self().
%% @private
--spec new(inet:socket(), module(), keepalive | close,
- cowboy_http:method(), cowboy_http:version(), binary(), binary(),
- undefined | fun(), undefined | {fun(), atom()})
+%%
+%% Since we always need to parse the Connection header, we do it
+%% in an optimized way and add the parsed value to p_headers' cache.
+-spec new(inet:socket(), module(), binary(), binary(), binary(), binary(),
+ cowboy_http:version(), cowboy_http:headers(), binary(),
+ inet:port_number() | undefined, binary(), boolean(),
+ undefined | cowboy_protocol:onresponse_fun())
-> req().
-new(Socket, Transport, Connection, Method, Version, Path, Qs,
- OnResponse, URLDecode) ->
- #http_req{socket=Socket, transport=Transport, connection=Connection,
- pid=self(), method=Method, version=Version, path=Path, qs=Qs,
- onresponse=OnResponse, urldecode=URLDecode}.
+new(Socket, Transport, Method, Path, Query, Fragment,
+ Version, Headers, Host, Port, Buffer, CanKeepalive,
+ OnResponse) ->
+ Req = #http_req{socket=Socket, transport=Transport, pid=self(),
+ method=Method, path=Path, qs=Query, fragment=Fragment, version=Version,
+ headers=Headers, host=Host, port=Port, buffer=Buffer,
+ onresponse=OnResponse},
+ case CanKeepalive of
+ false ->
+ Req#http_req{connection=close};
+ true ->
+ case lists:keyfind(<<"connection">>, 1, Headers) of
+ false when Version =:= {1, 1} ->
+ Req; %% keepalive
+ false ->
+ Req#http_req{connection=close};
+ {_, ConnectionHeader} ->
+ Tokens = parse_connection_before(ConnectionHeader, []),
+ Connection = connection_to_atom(Tokens),
+ Req#http_req{connection=Connection,
+ p_headers=[{<<"connection">>, Tokens}]}
+ end
+ end.
%% @doc Return the HTTP method of the request.
--spec method(Req) -> {cowboy_http:method(), Req} when Req::req().
+-spec method(Req) -> {binary(), Req} when Req::req().
method(Req) ->
{Req#http_req.method, Req}.
@@ -203,8 +222,8 @@ peer(Req) ->
%% @doc Returns the peer address calculated from headers.
-spec peer_addr(Req) -> {inet:ip_address(), Req} when Req::req().
peer_addr(Req = #http_req{}) ->
- {RealIp, Req1} = header(<<"X-Real-Ip">>, Req),
- {ForwardedForRaw, Req2} = header(<<"X-Forwarded-For">>, Req1),
+ {RealIp, Req1} = header(<<"x-real-ip">>, Req),
+ {ForwardedForRaw, Req2} = header(<<"x-forwarded-for">>, Req1),
{{PeerIp, _PeerPort}, Req3} = peer(Req2),
ForwardedFor = case ForwardedForRaw of
undefined ->
@@ -267,10 +286,9 @@ qs_val(Name, Req) when is_binary(Name) ->
%% missing.
-spec qs_val(binary(), Req, Default)
-> {binary() | true | Default, Req} when Req::req(), Default::any().
-qs_val(Name, Req=#http_req{qs=RawQs, qs_vals=undefined,
- urldecode={URLDecFun, URLDecArg}}, Default) when is_binary(Name) ->
- QsVals = cowboy_http:x_www_form_urlencoded(
- RawQs, fun(Bin) -> URLDecFun(Bin, URLDecArg) end),
+qs_val(Name, Req=#http_req{qs=RawQs, qs_vals=undefined}, Default)
+ when is_binary(Name) ->
+ QsVals = cowboy_http:x_www_form_urlencoded(RawQs),
qs_val(Name, Req#http_req{qs_vals=QsVals}, Default);
qs_val(Name, Req, Default) ->
case lists:keyfind(Name, 1, Req#http_req.qs_vals) of
@@ -280,14 +298,17 @@ qs_val(Name, Req, Default) ->
%% @doc Return the full list of query string values.
-spec qs_vals(Req) -> {list({binary(), binary() | true}), Req} when Req::req().
-qs_vals(Req=#http_req{qs=RawQs, qs_vals=undefined,
- urldecode={URLDecFun, URLDecArg}}) ->
- QsVals = cowboy_http:x_www_form_urlencoded(
- RawQs, fun(Bin) -> URLDecFun(Bin, URLDecArg) end),
+qs_vals(Req=#http_req{qs=RawQs, qs_vals=undefined}) ->
+ QsVals = cowboy_http:x_www_form_urlencoded(RawQs),
qs_vals(Req#http_req{qs_vals=QsVals});
qs_vals(Req=#http_req{qs_vals=QsVals}) ->
{QsVals, Req}.
+%% @doc Return the raw fragment directly taken from the request.
+-spec fragment(Req) -> {binary(), Req} when Req::req().
+fragment(Req) ->
+ {Req#http_req.fragment, Req}.
+
%% @doc Return the request URL as a binary without the path and query string.
%%
%% The URL includes the scheme, host and port only.
@@ -308,15 +329,19 @@ host_url(Req=#http_req{transport=Transport, host=Host, port=Port}) ->
%% @doc Return the full request URL as a binary.
%%
-%% The URL includes the scheme, host, port, path and query string.
+%% The URL includes the scheme, host, port, path, query string and fragment.
-spec url(Req) -> {binary(), Req} when Req::req().
-url(Req=#http_req{path=Path, qs=QS}) ->
+url(Req=#http_req{path=Path, qs=QS, fragment=Fragment}) ->
{HostURL, Req2} = host_url(Req),
QS2 = case QS of
<<>> -> <<>>;
_ -> << "?", QS/binary >>
end,
- {<< HostURL/binary, Path/binary, QS2/binary >>, Req2}.
+ Fragment2 = case Fragment of
+ <<>> -> <<>>;
+ _ -> << "#", Fragment/binary >>
+ end,
+ {<< HostURL/binary, Path/binary, QS2/binary, Fragment2/binary >>, Req2}.
%% @equiv binding(Name, Req, undefined)
-spec binding(atom(), Req) -> {binary() | undefined, Req} when Req::req().
@@ -339,15 +364,15 @@ bindings(Req) ->
{Req#http_req.bindings, Req}.
%% @equiv header(Name, Req, undefined)
--spec header(atom() | binary(), Req)
+-spec header(binary(), Req)
-> {binary() | undefined, Req} when Req::req().
-header(Name, Req) when is_atom(Name) orelse is_binary(Name) ->
+header(Name, Req) ->
header(Name, Req, undefined).
%% @doc Return the header value for the given key, or a default if missing.
--spec header(atom() | binary(), Req, Default)
+-spec header(binary(), Req, Default)
-> {binary() | Default, Req} when Req::req(), Default::any().
-header(Name, Req, Default) when is_atom(Name) orelse is_binary(Name) ->
+header(Name, Req, Default) ->
case lists:keyfind(Name, 1, Req#http_req.headers) of
{Name, Value} -> {Value, Req};
false -> {Default, Req}
@@ -363,7 +388,7 @@ headers(Req) ->
%% When the value isn't found, a proper default value for the type
%% returned is used as a return value.
%% @see parse_header/3
--spec parse_header(cowboy_http:header(), Req)
+-spec parse_header(binary(), Req)
-> {ok, any(), Req} | {undefined, binary(), Req}
| {error, badarg} when Req::req().
parse_header(Name, Req=#http_req{p_headers=PHeaders}) ->
@@ -373,76 +398,72 @@ parse_header(Name, Req=#http_req{p_headers=PHeaders}) ->
end.
%% @doc Default values for semantic header parsing.
--spec parse_header_default(cowboy_http:header()) -> any().
-parse_header_default('Connection') -> [];
-parse_header_default('Transfer-Encoding') -> [<<"identity">>];
+-spec parse_header_default(binary()) -> any().
+parse_header_default(<<"connection">>) -> [];
+parse_header_default(<<"transfer-encoding">>) -> [<<"identity">>];
parse_header_default(_Name) -> undefined.
%% @doc Semantically parse headers.
%%
%% When the header is unknown, the value is returned directly without parsing.
--spec parse_header(cowboy_http:header(), Req, any())
+-spec parse_header(binary(), Req, any())
-> {ok, any(), Req} | {undefined, binary(), Req}
| {error, badarg} when Req::req().
-parse_header(Name, Req, Default) when Name =:= 'Accept' ->
+parse_header(Name, Req, Default) when Name =:= <<"accept">> ->
parse_header(Name, Req, Default,
fun (Value) ->
cowboy_http:list(Value, fun cowboy_http:media_range/2)
end);
-parse_header(Name, Req, Default) when Name =:= 'Accept-Charset' ->
+parse_header(Name, Req, Default) when Name =:= <<"accept-charset">> ->
parse_header(Name, Req, Default,
fun (Value) ->
cowboy_http:nonempty_list(Value, fun cowboy_http:conneg/2)
end);
-parse_header(Name, Req, Default) when Name =:= 'Accept-Encoding' ->
+parse_header(Name, Req, Default) when Name =:= <<"accept-encoding">> ->
parse_header(Name, Req, Default,
fun (Value) ->
cowboy_http:list(Value, fun cowboy_http:conneg/2)
end);
-parse_header(Name, Req, Default) when Name =:= 'Accept-Language' ->
+parse_header(Name, Req, Default) when Name =:= <<"accept-language">> ->
parse_header(Name, Req, Default,
fun (Value) ->
cowboy_http:nonempty_list(Value, fun cowboy_http:language_range/2)
end);
-parse_header(Name, Req, Default) when Name =:= 'Connection' ->
- parse_header(Name, Req, Default,
- fun (Value) ->
- cowboy_http:nonempty_list(Value, fun cowboy_http:token_ci/2)
- end);
-parse_header(Name, Req, Default) when Name =:= 'Content-Length' ->
+parse_header(Name, Req, Default) when Name =:= <<"content-length">> ->
parse_header(Name, Req, Default,
fun (Value) ->
cowboy_http:digits(Value)
end);
-parse_header(Name, Req, Default) when Name =:= 'Content-Type' ->
+parse_header(Name, Req, Default) when Name =:= <<"content-type">> ->
parse_header(Name, Req, Default,
fun (Value) ->
cowboy_http:content_type(Value)
end);
-parse_header(Name, Req, Default) when Name =:= <<"Expect">> ->
+parse_header(Name, Req, Default) when Name =:= <<"expect">> ->
parse_header(Name, Req, Default,
fun (Value) ->
cowboy_http:nonempty_list(Value, fun cowboy_http:expectation/2)
end);
parse_header(Name, Req, Default)
- when Name =:= 'If-Match'; Name =:= 'If-None-Match' ->
+ when Name =:= <<"if-match">>; Name =:= <<"if-none-match">> ->
parse_header(Name, Req, Default,
fun (Value) ->
cowboy_http:entity_tag_match(Value)
end);
parse_header(Name, Req, Default)
- when Name =:= 'If-Modified-Since'; Name =:= 'If-Unmodified-Since' ->
+ when Name =:= <<"if-modified-since">>;
+ Name =:= <<"if-unmodified-since">> ->
parse_header(Name, Req, Default,
fun (Value) ->
cowboy_http:http_date(Value)
end);
%% @todo Extension parameters.
-parse_header(Name, Req, Default) when Name =:= 'Transfer-Encoding' ->
+parse_header(Name, Req, Default) when Name =:= <<"transfer-encoding">> ->
parse_header(Name, Req, Default,
fun (Value) ->
cowboy_http:nonempty_list(Value, fun cowboy_http:token_ci/2)
end);
-parse_header(Name, Req, Default) when Name =:= 'Upgrade' ->
+parse_header(Name, Req, Default) when Name =:= <<"upgrade">> ->
parse_header(Name, Req, Default,
fun (Value) ->
cowboy_http:nonempty_list(Value, fun cowboy_http:token_ci/2)
@@ -475,7 +496,7 @@ cookie(Name, Req) when is_binary(Name) ->
-spec cookie(binary(), Req, Default)
-> {binary() | true | Default, Req} when Req::req(), Default::any().
cookie(Name, Req=#http_req{cookies=undefined}, Default) when is_binary(Name) ->
- case header('Cookie', Req) of
+ case header(<<"cookie">>, Req) of
{undefined, Req2} ->
{Default, Req2#http_req{cookies=[]}};
{RawCookie, Req2} ->
@@ -491,7 +512,7 @@ cookie(Name, Req, Default) ->
%% @doc Return the full list of cookie values.
-spec cookies(Req) -> {list({binary(), binary() | true}), Req} when Req::req().
cookies(Req=#http_req{cookies=undefined}) ->
- case header('Cookie', Req) of
+ case header(<<"cookie">>, Req) of
{undefined, Req2} ->
{[], Req2#http_req{cookies=[]}};
{RawCookie, Req2} ->
@@ -532,8 +553,8 @@ set_meta(Name, Value, Req=#http_req{meta=Meta}) ->
%% @doc Return whether the request message has a body.
-spec has_body(Req) -> {boolean(), Req} when Req::req().
has_body(Req) ->
- Has = lists:keymember('Content-Length', 1, Req#http_req.headers) orelse
- lists:keymember('Transfer-Encoding', 1, Req#http_req.headers),
+ Has = lists:keymember(<<"content-length">>, 1, Req#http_req.headers) orelse
+ lists:keymember(<<"transfer-encoding">>, 1, Req#http_req.headers),
{Has, Req}.
%% @doc Return the request message body length, if known.
@@ -542,11 +563,11 @@ has_body(Req) ->
%% and the body hasn't been read at the time of the call.
-spec body_length(Req) -> {undefined | non_neg_integer(), Req} when Req::req().
body_length(Req) ->
- case lists:keymember('Transfer-Encoding', 1, Req#http_req.headers) of
+ case lists:keymember(<<"transfer-encoding">>, 1, Req#http_req.headers) of
true ->
{undefined, Req};
false ->
- {ok, Length, Req2} = parse_header('Content-Length', Req, 0),
+ {ok, Length, Req2} = parse_header(<<"content-length">>, Req, 0),
{Length, Req2}
end.
@@ -586,7 +607,7 @@ init_stream(TransferDecode, TransferState, ContentDecode, Req) ->
| {done, Req} | {error, atom()} when Req::req().
stream_body(Req=#http_req{body_state=waiting,
version=Version, transport=Transport, socket=Socket}) ->
- case parse_header(<<"Expect">>, Req) of
+ case parse_header(<<"expect">>, Req) of
{ok, [<<"100-continue">>], Req1} ->
HTTPVer = cowboy_http:version_to_binary(Version),
Transport:send(Socket,
@@ -594,7 +615,7 @@ stream_body(Req=#http_req{body_state=waiting,
{ok, undefined, Req1} ->
ok
end,
- case parse_header('Transfer-Encoding', Req1) of
+ case parse_header(<<"transfer-encoding">>, Req1) of
{ok, [<<"chunked">>], Req2} ->
stream_body(Req2#http_req{body_state=
{stream, fun cowboy_http:te_chunked/2, {0, 0},
@@ -657,13 +678,13 @@ transfer_decode(Data, Req=#http_req{
-> Req when Req::req().
transfer_decode_done(Length, Rest, Req=#http_req{
headers=Headers, p_headers=PHeaders}) ->
- Headers2 = lists:keystore('Content-Length', 1, Headers,
- {'Content-Length', list_to_binary(integer_to_list(Length))}),
+ Headers2 = lists:keystore(<<"content-length">>, 1, Headers,
+ {<<"content-length">>, list_to_binary(integer_to_list(Length))}),
%% At this point we just assume TEs were all decoded.
- Headers3 = lists:keydelete('Transfer-Encoding', 1, Headers2),
- PHeaders2 = lists:keystore('Content-Length', 1, PHeaders,
- {'Content-Length', Length}),
- PHeaders3 = lists:keydelete('Transfer-Encoding', 1, PHeaders2),
+ Headers3 = lists:keydelete(<<"transfer-encoding">>, 1, Headers2),
+ PHeaders2 = lists:keystore(<<"content-length">>, 1, PHeaders,
+ {<<"content-length">>, Length}),
+ PHeaders3 = lists:keydelete(<<"transfer-encoding">>, 1, PHeaders2),
Req#http_req{buffer=Rest, body_state=done,
headers=Headers3, p_headers=PHeaders3}.
@@ -718,11 +739,10 @@ skip_body(Req) ->
-spec body_qs(Req)
-> {ok, [{binary(), binary() | true}], Req} | {error, atom()}
when Req::req().
-body_qs(Req=#http_req{urldecode={URLDecFun, URLDecArg}}) ->
+body_qs(Req) ->
case body(Req) of
{ok, Body, Req2} ->
- {ok, cowboy_http:x_www_form_urlencoded(
- Body, fun(Bin) -> URLDecFun(Bin, URLDecArg) end), Req2};
+ {ok, cowboy_http:x_www_form_urlencoded(Body), Req2};
{error, Reason} ->
{error, Reason}
end.
@@ -743,9 +763,9 @@ body_qs(Req=#http_req{urldecode={URLDecFun, URLDecArg}}) ->
| {end_of_part | eof, Req} when Req::req().
multipart_data(Req=#http_req{body_state=waiting}) ->
{ok, {<<"multipart">>, _SubType, Params}, Req2} =
- parse_header('Content-Type', Req),
+ parse_header(<<"content-type">>, Req),
{_, Boundary} = lists:keyfind(<<"boundary">>, 1, Params),
- {ok, Length, Req3} = parse_header('Content-Length', Req2),
+ {ok, Length, Req3} = parse_header(<<"content-length">>, Req2),
multipart_data(Req3, Length, {more, cowboy_multipart:parser(Boundary)});
multipart_data(Req=#http_req{multipart={Length, Cont}}) ->
multipart_data(Req, Length, Cont());
@@ -796,11 +816,10 @@ set_resp_cookie(Name, Value, Options, Req) ->
set_resp_header(HeaderName, HeaderValue, Req).
%% @doc Add a header to the response.
--spec set_resp_header(cowboy_http:header(), iodata(), Req)
+-spec set_resp_header(binary(), iodata(), Req)
-> Req when Req::req().
set_resp_header(Name, Value, Req=#http_req{resp_headers=RespHeaders}) ->
- NameBin = header_to_binary(Name),
- Req#http_req{resp_headers=[{NameBin, Value}|RespHeaders]}.
+ Req#http_req{resp_headers=[{Name, Value}|RespHeaders]}.
%% @doc Add a body to the response.
%%
@@ -825,16 +844,15 @@ set_resp_body(Body, Req) ->
%% `reply/3'.
%%
%% @see cowboy_req:transport/1.
--spec set_resp_body_fun(non_neg_integer(),
- fun(() -> {sent, non_neg_integer()}), Req) -> Req when Req::req().
+-spec set_resp_body_fun(non_neg_integer(), resp_body_fun(), Req)
+ -> Req when Req::req().
set_resp_body_fun(StreamLen, StreamFun, Req) ->
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(), req()) -> boolean().
+-spec has_resp_header(binary(), req()) -> boolean().
has_resp_header(Name, #http_req{resp_headers=RespHeaders}) ->
- NameBin = header_to_binary(Name),
- lists:keymember(NameBin, 1, RespHeaders).
+ lists:keymember(Name, 1, RespHeaders).
%% @doc Return whether a body has been set for the response.
-spec has_resp_body(req()) -> boolean().
@@ -844,7 +862,7 @@ has_resp_body(#http_req{resp_body=RespBody}) ->
iolist_size(RespBody) > 0.
%% Remove a header previously set for the response.
--spec delete_resp_header(cowboy_http:header(), Req)
+-spec delete_resp_header(binary(), Req)
-> Req when Req::req().
delete_resp_header(Name, Req=#http_req{resp_headers=RespHeaders}) ->
RespHeaders2 = lists:keydelete(Name, 1, RespHeaders),
@@ -862,29 +880,35 @@ reply(Status, Headers, Req=#http_req{resp_body=Body}) ->
reply(Status, Headers, Body, Req).
%% @doc Send a reply to the client.
--spec reply(cowboy_http:status(), cowboy_http:headers(), iodata(), Req)
+-spec reply(cowboy_http:status(), cowboy_http:headers(),
+ iodata() | {non_neg_integer() | resp_body_fun()}, Req)
-> {ok, Req} when Req::req().
-reply(Status, Headers, Body, Req=#http_req{socket=Socket, transport=Transport,
+reply(Status, Headers, Body, Req=#http_req{
version=Version, connection=Connection,
method=Method, resp_state=waiting, resp_headers=RespHeaders}) ->
RespConn = response_connection(Headers, Connection),
- ContentLen = case Body of {CL, _} -> CL; _ -> iolist_size(Body) end,
HTTP11Headers = case Version of
- {1, 1} -> [{<<"Connection">>, atom_to_connection(Connection)}];
+ {1, 1} -> [{<<"connection">>, atom_to_connection(Connection)}];
_ -> []
end,
- {ReplyType, Req2} = response(Status, Headers, RespHeaders, [
- {<<"Content-Length">>, integer_to_list(ContentLen)},
- {<<"Date">>, cowboy_clock:rfc1123()},
- {<<"Server">>, <<"Cowboy">>}
- |HTTP11Headers], Req),
- if Method =:= 'HEAD' -> ok;
- ReplyType =:= hook -> ok; %% Hook replied for us, stop there.
- true ->
- case Body of
- {_, StreamFun} -> StreamFun();
- _ -> Transport:send(Socket, Body)
- end
+ case Body of
+ {ContentLength, BodyFun} ->
+ {RespType, Req2} = response(Status, Headers, RespHeaders, [
+ {<<"content-length">>, integer_to_list(ContentLength)},
+ {<<"date">>, cowboy_clock:rfc1123()},
+ {<<"server">>, <<"Cowboy">>}
+ |HTTP11Headers], <<>>, Req),
+ if RespType =/= hook, Method =/= <<"HEAD">> -> BodyFun();
+ true -> ok
+ end;
+ _ ->
+ {_, Req2} = response(Status, Headers, RespHeaders, [
+ {<<"content-length">>, integer_to_list(iolist_size(Body))},
+ {<<"date">>, cowboy_clock:rfc1123()},
+ {<<"server">>, <<"Cowboy">>}
+ |HTTP11Headers],
+ case Method of <<"HEAD">> -> <<>>; _ -> Body end,
+ Req)
end,
{ok, Req2#http_req{connection=RespConn, resp_state=done,
resp_headers=[], resp_body= <<>>}}.
@@ -904,14 +928,14 @@ chunked_reply(Status, Headers, Req=#http_req{
RespConn = response_connection(Headers, Connection),
HTTP11Headers = case Version of
{1, 1} -> [
- {<<"Connection">>, atom_to_connection(Connection)},
- {<<"Transfer-Encoding">>, <<"chunked">>}];
+ {<<"connection">>, atom_to_connection(Connection)},
+ {<<"transfer-encoding">>, <<"chunked">>}];
_ -> []
end,
{_, Req2} = response(Status, Headers, RespHeaders, [
- {<<"Date">>, cowboy_clock:rfc1123()},
- {<<"Server">>, <<"Cowboy">>}
- |HTTP11Headers], Req),
+ {<<"date">>, cowboy_clock:rfc1123()},
+ {<<"server">>, <<"Cowboy">>}
+ |HTTP11Headers], <<>>, Req),
{ok, Req2#http_req{connection=RespConn, resp_state=chunks,
resp_headers=[], resp_body= <<>>}}.
@@ -919,7 +943,7 @@ chunked_reply(Status, Headers, Req=#http_req{
%%
%% A chunked reply must have been initiated before calling this function.
-spec chunk(iodata(), req()) -> ok | {error, atom()}.
-chunk(_Data, #http_req{socket=_Socket, transport=_Transport, method='HEAD'}) ->
+chunk(_Data, #http_req{method= <<"HEAD">>}) ->
ok;
chunk(Data, #http_req{socket=Socket, transport=Transport, version={1, 0}}) ->
Transport:send(Socket, Data);
@@ -934,8 +958,8 @@ chunk(Data, #http_req{socket=Socket, transport=Transport, resp_state=chunks}) ->
upgrade_reply(Status, Headers, Req=#http_req{
resp_state=waiting, resp_headers=RespHeaders}) ->
{_, Req2} = response(Status, Headers, RespHeaders, [
- {<<"Connection">>, <<"Upgrade">>}
- ], Req),
+ {<<"connection">>, <<"Upgrade">>}
+ ], <<>>, Req),
{ok, Req2#http_req{resp_state=done, resp_headers=[], resp_body= <<>>}}.
%% @doc Ensure the response has been sent fully.
@@ -950,7 +974,7 @@ ensure_response(Req=#http_req{resp_state=waiting}, Status) ->
_ = reply(Status, [], [], Req),
ok;
%% Terminate the chunked body for HTTP/1.1 only.
-ensure_response(#http_req{method='HEAD', resp_state=chunks}, _) ->
+ensure_response(#http_req{method= <<"HEAD">>, resp_state=chunks}, _) ->
ok;
ensure_response(#http_req{version={1, 0}, resp_state=chunks}, _) ->
ok;
@@ -962,29 +986,70 @@ ensure_response(#http_req{socket=Socket, transport=Transport,
%% Private setter/getter API.
%% @private
--spec set_host(binary(), inet:port_number(), binary(), Req)
- -> Req when Req::req().
-set_host(Host, Port, RawHost, Req=#http_req{headers=Headers}) ->
- Req#http_req{host=Host, port=Port, headers=[{'Host', RawHost}|Headers]}.
+-spec get(atom(), req()) -> any(); ([atom()], req()) -> any().
+get(List, Req) when is_list(List) ->
+ [g(Atom, Req) || Atom <- List];
+get(Atom, Req) when is_atom(Atom) ->
+ g(Atom, Req).
+
+g(bindings, #http_req{bindings=Ret}) -> Ret;
+g(body_state, #http_req{body_state=Ret}) -> Ret;
+g(buffer, #http_req{buffer=Ret}) -> Ret;
+g(connection, #http_req{connection=Ret}) -> Ret;
+g(cookies, #http_req{cookies=Ret}) -> Ret;
+g(fragment, #http_req{fragment=Ret}) -> Ret;
+g(headers, #http_req{headers=Ret}) -> Ret;
+g(host, #http_req{host=Ret}) -> Ret;
+g(host_info, #http_req{host_info=Ret}) -> Ret;
+g(meta, #http_req{meta=Ret}) -> Ret;
+g(method, #http_req{method=Ret}) -> Ret;
+g(multipart, #http_req{multipart=Ret}) -> Ret;
+g(onresponse, #http_req{onresponse=Ret}) -> Ret;
+g(p_headers, #http_req{p_headers=Ret}) -> Ret;
+g(path, #http_req{path=Ret}) -> Ret;
+g(path_info, #http_req{path_info=Ret}) -> Ret;
+g(peer, #http_req{peer=Ret}) -> Ret;
+g(pid, #http_req{pid=Ret}) -> Ret;
+g(port, #http_req{port=Ret}) -> Ret;
+g(qs, #http_req{qs=Ret}) -> Ret;
+g(qs_vals, #http_req{qs_vals=Ret}) -> Ret;
+g(resp_body, #http_req{resp_body=Ret}) -> Ret;
+g(resp_headers, #http_req{resp_headers=Ret}) -> Ret;
+g(resp_state, #http_req{resp_state=Ret}) -> Ret;
+g(socket, #http_req{socket=Ret}) -> Ret;
+g(transport, #http_req{transport=Ret}) -> Ret;
+g(version, #http_req{version=Ret}) -> Ret.
%% @private
--spec set_connection(binary(), Req) -> Req when Req::req().
-set_connection(RawConnection, Req=#http_req{headers=Headers}) ->
- Req2 = Req#http_req{headers=[{'Connection', RawConnection}|Headers]},
- {ok, ConnTokens, Req3} = parse_header('Connection', Req2),
- ConnAtom = cowboy_http:connection_to_atom(ConnTokens),
- Req3#http_req{connection=ConnAtom}.
-
-%% @private
--spec add_header(cowboy_http:header(), binary(), Req)
- -> Req when Req::req().
-add_header(Name, Value, Req=#http_req{headers=Headers}) ->
- Req#http_req{headers=[{Name, Value}|Headers]}.
-
-%% @private
--spec set_buffer(binary(), Req) -> Req when Req::req().
-set_buffer(Buffer, Req) ->
- Req#http_req{buffer=Buffer}.
+-spec set([{atom(), any()}], Req) -> Req when Req::req().
+set([], Req) -> Req;
+set([{bindings, Val}|Tail], Req) -> set(Tail, Req#http_req{bindings=Val});
+set([{body_state, Val}|Tail], Req) -> set(Tail, Req#http_req{body_state=Val});
+set([{buffer, Val}|Tail], Req) -> set(Tail, Req#http_req{buffer=Val});
+set([{connection, Val}|Tail], Req) -> set(Tail, Req#http_req{connection=Val});
+set([{cookies, Val}|Tail], Req) -> set(Tail, Req#http_req{cookies=Val});
+set([{fragment, Val}|Tail], Req) -> set(Tail, Req#http_req{fragment=Val});
+set([{headers, Val}|Tail], Req) -> set(Tail, Req#http_req{headers=Val});
+set([{host, Val}|Tail], Req) -> set(Tail, Req#http_req{host=Val});
+set([{host_info, Val}|Tail], Req) -> set(Tail, Req#http_req{host_info=Val});
+set([{meta, Val}|Tail], Req) -> set(Tail, Req#http_req{meta=Val});
+set([{method, Val}|Tail], Req) -> set(Tail, Req#http_req{method=Val});
+set([{multipart, Val}|Tail], Req) -> set(Tail, Req#http_req{multipart=Val});
+set([{onresponse, Val}|Tail], Req) -> set(Tail, Req#http_req{onresponse=Val});
+set([{p_headers, Val}|Tail], Req) -> set(Tail, Req#http_req{p_headers=Val});
+set([{path, Val}|Tail], Req) -> set(Tail, Req#http_req{path=Val});
+set([{path_info, Val}|Tail], Req) -> set(Tail, Req#http_req{path_info=Val});
+set([{peer, Val}|Tail], Req) -> set(Tail, Req#http_req{peer=Val});
+set([{pid, Val}|Tail], Req) -> set(Tail, Req#http_req{pid=Val});
+set([{port, Val}|Tail], Req) -> set(Tail, Req#http_req{port=Val});
+set([{qs, Val}|Tail], Req) -> set(Tail, Req#http_req{qs=Val});
+set([{qs_vals, Val}|Tail], Req) -> set(Tail, Req#http_req{qs_vals=Val});
+set([{resp_body, Val}|Tail], Req) -> set(Tail, Req#http_req{resp_body=Val});
+set([{resp_headers, Val}|Tail], Req) -> set(Tail, Req#http_req{resp_headers=Val});
+set([{resp_state, Val}|Tail], Req) -> set(Tail, Req#http_req{resp_state=Val});
+set([{socket, Val}|Tail], Req) -> set(Tail, Req#http_req{socket=Val});
+set([{transport, Val}|Tail], Req) -> set(Tail, Req#http_req{transport=Val});
+set([{version, Val}|Tail], Req) -> set(Tail, Req#http_req{version=Val}).
%% @private
-spec set_bindings(cowboy_dispatcher:tokens(), cowboy_dispatcher:tokens(),
@@ -993,21 +1058,6 @@ set_bindings(HostInfo, PathInfo, Bindings, Req) ->
Req#http_req{host_info=HostInfo, path_info=PathInfo,
bindings=Bindings}.
-%% @private
--spec get_resp_state(req()) -> locked | waiting | chunks | done.
-get_resp_state(#http_req{resp_state=RespState}) ->
- RespState.
-
-%% @private
--spec get_buffer(req()) -> binary().
-get_buffer(#http_req{buffer=Buffer}) ->
- Buffer.
-
-%% @private
--spec get_connection(req()) -> keepalive | close.
-get_connection(#http_req{connection=Connection}) ->
- Connection.
-
%% Misc API.
%% @doc Compact the request data by removing all non-system information.
@@ -1050,9 +1100,9 @@ transport(#http_req{transport=Transport, socket=Socket}) ->
%% Internal.
-spec response(cowboy_http:status(), cowboy_http:headers(),
- cowboy_http:headers(), cowboy_http:headers(), Req)
+ cowboy_http:headers(), cowboy_http:headers(), iodata(), Req)
-> {normal | hook, Req} when Req::req().
-response(Status, Headers, RespHeaders, DefaultHeaders, Req=#http_req{
+response(Status, Headers, RespHeaders, DefaultHeaders, Body, Req=#http_req{
socket=Socket, transport=Transport, version=Version,
pid=ReqPid, onresponse=OnResponse}) ->
FullHeaders = response_merge_headers(Headers, RespHeaders, DefaultHeaders),
@@ -1070,7 +1120,7 @@ response(Status, Headers, RespHeaders, DefaultHeaders, Req=#http_req{
(status(Status))/binary, "\r\n" >>,
HeaderLines = [[Key, <<": ">>, Value, <<"\r\n">>]
|| {Key, Value} <- FullHeaders],
- Transport:send(Socket, [StatusLine, HeaderLines, <<"\r\n">>]),
+ Transport:send(Socket, [StatusLine, HeaderLines, <<"\r\n">>, Body]),
ReqPid ! {?MODULE, resp_sent},
normal;
_ ->
@@ -1084,25 +1134,19 @@ response_connection([], Connection) ->
Connection;
response_connection([{Name, Value}|Tail], Connection) ->
case Name of
- 'Connection' -> response_connection_parse(Value);
- Name when is_atom(Name) -> response_connection(Tail, Connection);
- Name ->
- Name2 = cowboy_bstr:to_lower(Name),
- case Name2 of
- <<"connection">> -> response_connection_parse(Value);
- _Any -> response_connection(Tail, Connection)
- end
+ <<"connection">> -> response_connection_parse(Value);
+ _ -> response_connection(Tail, Connection)
end.
-spec response_connection_parse(binary()) -> keepalive | close.
response_connection_parse(ReplyConn) ->
Tokens = cowboy_http:nonempty_list(ReplyConn, fun cowboy_http:token/2),
- cowboy_http:connection_to_atom(Tokens).
+ connection_to_atom(Tokens).
-spec response_merge_headers(cowboy_http:headers(), cowboy_http:headers(),
cowboy_http:headers()) -> cowboy_http:headers().
response_merge_headers(Headers, RespHeaders, DefaultHeaders) ->
- Headers2 = [{header_to_binary(Key), Value} || {Key, Value} <- Headers],
+ Headers2 = [{Key, Value} || {Key, Value} <- Headers],
merge_headers(
merge_headers(Headers2, RespHeaders),
DefaultHeaders).
@@ -1114,7 +1158,7 @@ merge_headers(Headers, []) ->
merge_headers(Headers, [{Name, Value}|Tail]) ->
Headers2 = case lists:keymember(Name, 1, Headers) of
true -> Headers;
- false -> Headers ++ [{Name, Value}]
+ false -> [{Name, Value}|Headers]
end,
merge_headers(Headers2, Tail).
@@ -1125,6 +1169,74 @@ atom_to_connection(keepalive) ->
atom_to_connection(close) ->
<<"close">>.
+%% Optimized parsing functions for the Connection header.
+parse_connection_before(<<>>, Acc) ->
+ lists:reverse(Acc);
+parse_connection_before(<< C, Rest/bits >>, Acc)
+ when C =:= $,; C =:= $\s; C =:= $\t ->
+ parse_connection_before(Rest, Acc);
+parse_connection_before(Buffer, Acc) ->
+ parse_connection(Buffer, Acc, <<>>).
+
+%% An evil block of code appeared!
+parse_connection(<<>>, Acc, <<>>) ->
+ lists:reverse(Acc);
+parse_connection(<<>>, Acc, Token) ->
+ lists:reverse([Token|Acc]);
+parse_connection(<< C, Rest/bits >>, Acc, Token)
+ when C =:= $,; C =:= $\s; C =:= $\t ->
+ parse_connection_after(Rest, [Token|Acc]);
+parse_connection(<< C, Rest/bits >>, Acc, Token) ->
+ case C of
+ $A -> parse_connection(Rest, Acc, << Token/binary, $a >>);
+ $B -> parse_connection(Rest, Acc, << Token/binary, $b >>);
+ $C -> parse_connection(Rest, Acc, << Token/binary, $c >>);
+ $D -> parse_connection(Rest, Acc, << Token/binary, $d >>);
+ $E -> parse_connection(Rest, Acc, << Token/binary, $e >>);
+ $F -> parse_connection(Rest, Acc, << Token/binary, $f >>);
+ $G -> parse_connection(Rest, Acc, << Token/binary, $g >>);
+ $H -> parse_connection(Rest, Acc, << Token/binary, $h >>);
+ $I -> parse_connection(Rest, Acc, << Token/binary, $i >>);
+ $J -> parse_connection(Rest, Acc, << Token/binary, $j >>);
+ $K -> parse_connection(Rest, Acc, << Token/binary, $k >>);
+ $L -> parse_connection(Rest, Acc, << Token/binary, $l >>);
+ $M -> parse_connection(Rest, Acc, << Token/binary, $m >>);
+ $N -> parse_connection(Rest, Acc, << Token/binary, $n >>);
+ $O -> parse_connection(Rest, Acc, << Token/binary, $o >>);
+ $P -> parse_connection(Rest, Acc, << Token/binary, $p >>);
+ $Q -> parse_connection(Rest, Acc, << Token/binary, $q >>);
+ $R -> parse_connection(Rest, Acc, << Token/binary, $r >>);
+ $S -> parse_connection(Rest, Acc, << Token/binary, $s >>);
+ $T -> parse_connection(Rest, Acc, << Token/binary, $t >>);
+ $U -> parse_connection(Rest, Acc, << Token/binary, $u >>);
+ $V -> parse_connection(Rest, Acc, << Token/binary, $v >>);
+ $W -> parse_connection(Rest, Acc, << Token/binary, $w >>);
+ $X -> parse_connection(Rest, Acc, << Token/binary, $x >>);
+ $Y -> parse_connection(Rest, Acc, << Token/binary, $y >>);
+ $Z -> parse_connection(Rest, Acc, << Token/binary, $z >>);
+ C -> parse_connection(Rest, Acc, << Token/binary, C >>)
+ end.
+
+parse_connection_after(<<>>, Acc) ->
+ lists:reverse(Acc);
+parse_connection_after(<< $,, Rest/bits >>, Acc) ->
+ parse_connection_before(Rest, Acc);
+parse_connection_after(<< C, Rest/bits >>, Acc)
+ when C =:= $\s; C =:= $\t ->
+ parse_connection_after(Rest, Acc).
+
+%% @doc Walk through a tokens list and return whether
+%% the connection is keepalive or closed.
+%%
+%% We don't match on "keep-alive" since it is the default value.
+-spec connection_to_atom([binary()]) -> keepalive | close.
+connection_to_atom([]) ->
+ keepalive;
+connection_to_atom([<<"close">>|_]) ->
+ close;
+connection_to_atom([_|Tail]) ->
+ connection_to_atom(Tail).
+
-spec status(cowboy_http:status()) -> binary().
status(100) -> <<"100 Continue">>;
status(101) -> <<"101 Switching Protocols">>;
@@ -1185,61 +1297,6 @@ status(510) -> <<"510 Not Extended">>;
status(511) -> <<"511 Network Authentication Required">>;
status(B) when is_binary(B) -> B.
--spec header_to_binary(cowboy_http:header()) -> binary().
-header_to_binary('Cache-Control') -> <<"Cache-Control">>;
-header_to_binary('Connection') -> <<"Connection">>;
-header_to_binary('Date') -> <<"Date">>;
-header_to_binary('Pragma') -> <<"Pragma">>;
-header_to_binary('Transfer-Encoding') -> <<"Transfer-Encoding">>;
-header_to_binary('Upgrade') -> <<"Upgrade">>;
-header_to_binary('Via') -> <<"Via">>;
-header_to_binary('Accept') -> <<"Accept">>;
-header_to_binary('Accept-Charset') -> <<"Accept-Charset">>;
-header_to_binary('Accept-Encoding') -> <<"Accept-Encoding">>;
-header_to_binary('Accept-Language') -> <<"Accept-Language">>;
-header_to_binary('Authorization') -> <<"Authorization">>;
-header_to_binary('From') -> <<"From">>;
-header_to_binary('Host') -> <<"Host">>;
-header_to_binary('If-Modified-Since') -> <<"If-Modified-Since">>;
-header_to_binary('If-Match') -> <<"If-Match">>;
-header_to_binary('If-None-Match') -> <<"If-None-Match">>;
-header_to_binary('If-Range') -> <<"If-Range">>;
-header_to_binary('If-Unmodified-Since') -> <<"If-Unmodified-Since">>;
-header_to_binary('Max-Forwards') -> <<"Max-Forwards">>;
-header_to_binary('Proxy-Authorization') -> <<"Proxy-Authorization">>;
-header_to_binary('Range') -> <<"Range">>;
-header_to_binary('Referer') -> <<"Referer">>;
-header_to_binary('User-Agent') -> <<"User-Agent">>;
-header_to_binary('Age') -> <<"Age">>;
-header_to_binary('Location') -> <<"Location">>;
-header_to_binary('Proxy-Authenticate') -> <<"Proxy-Authenticate">>;
-header_to_binary('Public') -> <<"Public">>;
-header_to_binary('Retry-After') -> <<"Retry-After">>;
-header_to_binary('Server') -> <<"Server">>;
-header_to_binary('Vary') -> <<"Vary">>;
-header_to_binary('Warning') -> <<"Warning">>;
-header_to_binary('Www-Authenticate') -> <<"Www-Authenticate">>;
-header_to_binary('Allow') -> <<"Allow">>;
-header_to_binary('Content-Base') -> <<"Content-Base">>;
-header_to_binary('Content-Encoding') -> <<"Content-Encoding">>;
-header_to_binary('Content-Language') -> <<"Content-Language">>;
-header_to_binary('Content-Length') -> <<"Content-Length">>;
-header_to_binary('Content-Location') -> <<"Content-Location">>;
-header_to_binary('Content-Md5') -> <<"Content-Md5">>;
-header_to_binary('Content-Range') -> <<"Content-Range">>;
-header_to_binary('Content-Type') -> <<"Content-Type">>;
-header_to_binary('Etag') -> <<"Etag">>;
-header_to_binary('Expires') -> <<"Expires">>;
-header_to_binary('Last-Modified') -> <<"Last-Modified">>;
-header_to_binary('Accept-Ranges') -> <<"Accept-Ranges">>;
-header_to_binary('Set-Cookie') -> <<"Set-Cookie">>;
-header_to_binary('Set-Cookie2') -> <<"Set-Cookie2">>;
-header_to_binary('X-Forwarded-For') -> <<"X-Forwarded-For">>;
-header_to_binary('Cookie') -> <<"Cookie">>;
-header_to_binary('Keep-Alive') -> <<"Keep-Alive">>;
-header_to_binary('Proxy-Connection') -> <<"Proxy-Connection">>;
-header_to_binary(B) when is_binary(B) -> B.
-
%% Tests.
-ifdef(TEST).
@@ -1247,25 +1304,45 @@ header_to_binary(B) when is_binary(B) -> B.
url_test() ->
{<<"http://localhost/path">>, _ } =
url(#http_req{transport=ranch_tcp, host= <<"localhost">>, port=80,
- path= <<"/path">>, qs= <<>>, pid=self()}),
+ path= <<"/path">>, qs= <<>>, fragment= <<>>, pid=self()}),
{<<"http://localhost:443/path">>, _} =
url(#http_req{transport=ranch_tcp, host= <<"localhost">>, port=443,
- path= <<"/path">>, qs= <<>>, pid=self()}),
+ path= <<"/path">>, qs= <<>>, fragment= <<>>, pid=self()}),
{<<"http://localhost:8080/path">>, _} =
url(#http_req{transport=ranch_tcp, host= <<"localhost">>, port=8080,
- path= <<"/path">>, qs= <<>>, pid=self()}),
+ path= <<"/path">>, qs= <<>>, fragment= <<>>, pid=self()}),
{<<"http://localhost:8080/path?dummy=2785">>, _} =
url(#http_req{transport=ranch_tcp, host= <<"localhost">>, port=8080,
- path= <<"/path">>, qs= <<"dummy=2785">>, pid=self()}),
+ path= <<"/path">>, qs= <<"dummy=2785">>, fragment= <<>>,
+ pid=self()}),
+ {<<"http://localhost:8080/path?dummy=2785#fragment">>, _} =
+ url(#http_req{transport=ranch_tcp, host= <<"localhost">>, port=8080,
+ path= <<"/path">>, qs= <<"dummy=2785">>, fragment= <<"fragment">>,
+ pid=self()}),
{<<"https://localhost/path">>, _} =
url(#http_req{transport=ranch_ssl, host= <<"localhost">>, port=443,
- path= <<"/path">>, qs= <<>>, pid=self()}),
+ path= <<"/path">>, qs= <<>>, fragment= <<>>, pid=self()}),
{<<"https://localhost:8443/path">>, _} =
url(#http_req{transport=ranch_ssl, host= <<"localhost">>, port=8443,
- path= <<"/path">>, qs= <<>>, pid=self()}),
+ path= <<"/path">>, qs= <<>>, fragment= <<>>, pid=self()}),
{<<"https://localhost:8443/path?dummy=2785">>, _} =
url(#http_req{transport=ranch_ssl, host= <<"localhost">>, port=8443,
- path= <<"/path">>, qs= <<"dummy=2785">>, pid=self()}),
+ path= <<"/path">>, qs= <<"dummy=2785">>, fragment= <<>>,
+ pid=self()}),
+ {<<"https://localhost:8443/path?dummy=2785#fragment">>, _} =
+ url(#http_req{transport=ranch_ssl, host= <<"localhost">>, port=8443,
+ path= <<"/path">>, qs= <<"dummy=2785">>, fragment= <<"fragment">>,
+ pid=self()}),
ok.
+connection_to_atom_test_() ->
+ %% {Tokens, Result}
+ Tests = [
+ {[<<"close">>], close},
+ {[<<"keep-alive">>], keepalive},
+ {[<<"keep-alive">>, <<"upgrade">>], keepalive}
+ ],
+ [{lists:flatten(io_lib:format("~p", [T])),
+ fun() -> R = connection_to_atom(T) end} || {T, R} <- Tests].
+
-endif.