Skip to content

Commit

Permalink
Add support of the 'Vary' header in response
Browse files Browse the repository at this point in the history
With this patch, Yaws will add 'Accept-Encoding' in 'Vary' header if the
support of gzip compression is enabled or if the response is compressed.
The 'Vary' header can be set using 'yaws:outh_set_vary(Fields)' or by
returning '{header, {vary, Fields}}' from scripts (where Fields is a list
of header names).
  • Loading branch information
Christopher Faulet committed Jan 3, 2013
1 parent f5f62b6 commit d748f92
Show file tree
Hide file tree
Showing 5 changed files with 126 additions and 6 deletions.
47 changes: 45 additions & 2 deletions doc/yaws.tex
Expand Up @@ -1653,8 +1653,51 @@ \section{All out/1 return values}
stream data, in this case, the response is not chunked encoded.


\item \verb+{header, H}+ Accumulates a HTTP header. Used by for
example the \verb+yaws_api:setcookie/2-6+ function.
\item \verb+{header, H}+ Accumulates a HTTP header. The trailing CRNL which is
supposed to end all HTTP headers must NOT be added. It is added by the server.
The following list of headers are given special treatment.

\begin{itemize}
\item \verb+{connection, What}+ - This sets the Connection: header. If
\textit{What} is the special value \textit{\"close\"}, the connection will
be closed once the yaws page is delivered to the client.
\item \verb+{server, What}+ - Sets the \textit{Server:} header. By setting
this header, the server's signature will be dynamically overloaded.
\item \verb+{location, What}+ - Sets the \textit{Location:} header. This
header is typically combined with the \textit{\{status, 302\}} return
value.
\item \verb+{cache_control, What}+ - Sets the \textit{Cache-Control:}
header.
\item \verb+{expires, What}+ - Sets the \textit{Expires:} header.
\item \verb+{date, What}+ - Sets the \textit{Date:} header.
\item \verb+{allow, What}+ - Sets the \textit{Allow:} header.
\item \verb+{last_modified, What}+ - Sets the \textit{Last-Modified:}
header.
\item \verb+{etag, What}+ - Sets the \textit{Etag:} header.
\item \verb+{set_cookie, What}+ - Prepends a \textit{Set-Cookie:} header to
the list of previously set \textit{Set-Cookie} headers.
\item \verb+{content_range, What}+ - Sets the \textit{Content-Range:}
header.
\item \verb+{content_type, What}+ - Sets the \textit{Content-Type:} header.
\item \verb+{content_encoding, What}+ - Sets the \textit{Content-Encoding:}
header. If this header is defined, no deflate is performed by \Yaws\. So
you can compress data by yourself.
\item \verb+{content_length, What}+ - Normally \Yaws\ will ship Yaws pages
using \textit{Transfer-Encoding: chunked}. This is because we generally
can't know how long a yaws page will be. If we for some reason want to
force a \textit{Content-Length:} header (and we actually do know the
length of the content, we can force yaws to not ship the page chunked.
\item \verb+{transfer_encoding, What}+ - Sets the
\textit{Transfer-Encoding:} header.
\item \verb+{www_authenticate, What}+ - Sets the \textit{WWW-Authenticate:}
header.
\item \verb+{vary, What}+ - Sets the \textit{Vary:} header.
\end{itemize}

All other headers must be added using the normal HTTP syntax. Example:
\begin{verbatim}
{header, {"My-X-Header", "gadong"}} or {header, "My-X-Header: gadong"}
\end{verbatim}

\item \verb+{header, {H, erase}}+ A specific case of the previous
directive; use this to remove a specific header from a response. For
Expand Down
1 change: 1 addition & 0 deletions include/yaws.hrl
Expand Up @@ -347,6 +347,7 @@
content_encoding,
transfer_encoding,
www_authenticate,
vary,
other % misc other headers
}).

Expand Down
4 changes: 4 additions & 0 deletions man/yaws_api.5
Expand Up @@ -866,6 +866,10 @@ Sets the Transfer-Encoding: header.

Sets the WWW-Authenticate: header.

\fI{vary, What}\fR

Sets the Vary: header.


All other headers must be added using the normal HTTP syntax.
Example:
Expand Down
51 changes: 49 additions & 2 deletions src/yaws.erl
Expand Up @@ -51,6 +51,7 @@
outh_set_dcc/2,
outh_set_transfer_encoding_off/0,
outh_set_auth/1,
outh_set_vary/1,
outh_clear_headers/0,
outh_fix_doclose/0,
dcc/2]).
Expand All @@ -68,7 +69,8 @@
make_www_authenticate_header/1,
make_etag/1,
make_content_type_header/1,
make_date_header/0]).
make_date_header/0,
make_vary_header/1]).

-export([outh_get_status_code/0,
outh_get_contlen/0,
Expand All @@ -79,6 +81,7 @@
outh_get_content_encoding/0,
outh_get_content_encoding_header/0,
outh_get_content_type/0,
outh_get_vary_fields/0,
outh_serialize/0]).

-export([accumulate_header/1, headers_to_str/1,
Expand Down Expand Up @@ -1120,6 +1123,10 @@ outh_set_auth(Headers) ->
end,
put(outh, H2).

outh_set_vary(Fields) ->
put(outh, (get(outh))#outh{vary = make_vary_header(Fields)}),
ok.

outh_fix_doclose() ->
H = get(outh),
if
Expand Down Expand Up @@ -1307,6 +1314,12 @@ make_date_header() ->
H
end.

make_vary_header(Fields) ->
case lists:member("*", Fields) of
true -> ["Vary: ", "*", "\r\n"];
false -> ["Vary: ", join_sep(Fields, ", "), "\r\n"]
end.



%% access functions into the outh record
Expand Down Expand Up @@ -1346,6 +1359,12 @@ outh_get_content_type() ->
[_, Mime, _] -> Mime
end.

outh_get_vary_fields() ->
case (get(outh))#outh.vary of
undefined -> [];
[_, Fields, _] -> split_sep(Fields, $,)
end.

outh_serialize() ->
H = get(outh),
Code = case H#outh.status of
Expand Down Expand Up @@ -1382,6 +1401,26 @@ outh_serialize() ->
end,
{LM, E, CC}
end,

%% Add 'Accept-Encoding' in the 'Vary:' header if the compression is enabled
%% or if the response is compressed _AND_ if the response has a non-empty
%% body.
SC=get(sc),
Vary = case (?sc_has_deflate(SC) orelse H#outh.encoding == deflate) of
true when H#outh.contlen /= undefined, H#outh.contlen /= 0;
H#outh.act_contlen /= undefined, H#outh.act_contlen /= 0 ->
Fields = outh_get_vary_fields(),
Fun = fun("*") -> true;
(F) -> (to_lower(F) == "accept-encoding")
end,
case lists:any(Fun, Fields) of
true -> H#outh.vary;
false -> make_vary_header(["Accept-Encoding"|Fields])
end;
_ ->
H#outh.vary
end,

Headers = [noundef(H#outh.connection),
noundef(H#outh.server),
noundef(H#outh.location),
Expand All @@ -1398,6 +1437,7 @@ outh_serialize() ->
noundef(H#outh.set_cookie),
noundef(H#outh.transfer_encoding),
noundef(H#outh.www_authenticate),
noundef(Vary),
noundef(H#outh.other)],
{StatusLine, Headers}.

Expand Down Expand Up @@ -1530,6 +1570,11 @@ accumulate_header({www_authenticate, What}) ->
accumulate_header({"WWW-Authenticate", What}) ->
accumulate_header({www_authenticate, What});

accumulate_header({vary, What}) ->
put(outh, (get(outh))#outh{vary = ["Vary: ", What, "\r\n"]});
accumulate_header({"Vary", What}) ->
accumulate_header({vary, What});

%% non-special headers (which may be special in a future Yaws version)
accumulate_header({Name, What}) when is_list(Name) ->
H = get(outh),
Expand Down Expand Up @@ -1589,7 +1634,9 @@ erase_header(transfer_encoding) ->
erase_header(www_authenticate) ->
put(outh, (get(outh))#outh{www_authenticate=undefined});
erase_header(location) ->
put(outh, (get(outh))#outh{location=undefined}).
put(outh, (get(outh))#outh{location=undefined});
erase_header(vary) ->
put(outh, (get(outh))#outh{vary=undefined}).

getuid() ->
case os:type() of
Expand Down
29 changes: 27 additions & 2 deletions test/t5/app_test.erl
Expand Up @@ -24,17 +24,32 @@ start() ->
deflate_disabled() ->
io:format("deflate_disabled\n", []),

%% Static content (and cached)
%% Static content (and cached) - Not supported by server
Uri1 = "http://localhost:8000/1000.txt",
?line {ok, "200", Hdrs1, _} =
ibrowse:send_req(Uri1, [{"Accept-Encoding", "gzip, deflate"}], get),
?line undefined = proplists:get_value("Content-Encoding", Hdrs1),
?line undefined = proplists:get_value("Vary", Hdrs1),

%% Dynamic content
%% Dynamic content - Not supported by server
Uri2 = "http://localhost:8000/index.yaws",
?line {ok, "200", Hdrs2, _} =
ibrowse:send_req(Uri2, [{"Accept-Encoding", "gzip, deflate"}], get),
?line undefined = proplists:get_value("Content-Encoding", Hdrs2),
?line undefined = proplists:get_value("Vary", Hdrs2),

%% Static content (and cached) - Not supported by client
Uri3 = "http://localhost:8001/1000.txt",
?line {ok, "200", Hdrs3, _} =
ibrowse:send_req(Uri3, [], get),
?line undefined = proplists:get_value("Content-Encoding", Hdrs3),
?line "Accept-Encoding" = proplists:get_value("Vary", Hdrs3),

%% Dynamic content - Not supported by client
Uri4 = "http://localhost:8001/index.yaws",
?line {ok, "200", Hdrs4, _} = ibrowse:send_req(Uri4, [], get),
?line undefined = proplists:get_value("Content-Encoding", Hdrs4),
?line "Accept-Encoding" = proplists:get_value("Vary", Hdrs4),
ok.

deflate_enabled() ->
Expand All @@ -45,6 +60,7 @@ deflate_enabled() ->
?line {ok, "200", Hdrs1, Body1} =
ibrowse:send_req(Uri1, [{"Accept-Encoding", "gzip, deflate"}], get),
?line "gzip" = proplists:get_value("Content-Encoding", Hdrs1),
?line "Accept-Encoding" = proplists:get_value("Vary", Hdrs1),
?line true = is_binary(zlib:gunzip(Body1)),

%% Partial content is not compressed for small (and catched) files
Expand All @@ -54,12 +70,14 @@ deflate_enabled() ->
{"Range", "bytes=100-499"}], get),
?line undefined = proplists:get_value("Content-Encoding", Hdrs2),
?line "400" = proplists:get_value("Content-Length", Hdrs2),
?line "Accept-Encoding" = proplists:get_value("Vary", Hdrs2),

%% Dynamic content
Uri3 = "http://localhost:8001/index.yaws",
?line {ok, "200", Hdrs3, Body3} =
ibrowse:send_req(Uri3, [{"Accept-Encoding", "gzip, deflate"}], get),
?line "gzip" = proplists:get_value("Content-Encoding", Hdrs3),
?line "Accept-Encoding" = proplists:get_value("Vary", Hdrs3),
?line true = is_binary(zlib:gunzip(Body3)),
ok.

Expand All @@ -73,25 +91,29 @@ deflate_empty_response() ->
ibrowse:send_req(Uri1, [{"Accept-Encoding", "gzip, deflate"}], get),
?line undefined = proplists:get_value("Content-Encoding", Hdrs1),
?line "0" = proplists:get_value("Content-Length", Hdrs1),
?line undefined = proplists:get_value("Vary", Hdrs1),

Uri2 = "http://localhost:8001/0.txt",
?line {ok, "200", Hdrs2, _} =
ibrowse:send_req(Uri2, [{"Accept-Encoding", "gzip, deflate"}], get),
?line undefined = proplists:get_value("Content-Encoding", Hdrs2),
?line "0" = proplists:get_value("Content-Length", Hdrs2),
?line undefined = proplists:get_value("Vary", Hdrs2),

%% Dynamic content
Uri3 = "http://localhost:8000/emptytest",
?line {ok, "200", Hdrs3, _} =
ibrowse:send_req(Uri3, [{"Accept-Encoding", "gzip, deflate"}], get),
?line undefined = proplists:get_value("Content-Encoding", Hdrs3),
?line "0" = proplists:get_value("Content-Length", Hdrs3),
?line undefined = proplists:get_value("Vary", Hdrs3),

Uri4 = "http://localhost:8001/emptytest",
?line {ok, "200", Hdrs4, _} =
ibrowse:send_req(Uri4, [{"Accept-Encoding", "gzip, deflate"}], get),
?line undefined = proplists:get_value("Content-Encoding", Hdrs4),
?line "0" = proplists:get_value("Content-Length", Hdrs4),
?line undefined = proplists:get_value("Vary", Hdrs4),
ok.


Expand All @@ -105,6 +127,7 @@ deflate_streamcontent() ->
?line "gzip" = proplists:get_value("Content-Encoding", Hdrs1),
?line "chunked" = proplists:get_value("Transfer-Encoding", Hdrs1),
?line undefined = proplists:get_value("Content-Length", Hdrs1),
?line "Accept-Encoding" = proplists:get_value("Vary", Hdrs1),

%% Partial content is not compressed for large files
Uri2 = "http://localhost:8001/10000.txt",
Expand All @@ -114,6 +137,7 @@ deflate_streamcontent() ->
?line undefined = proplists:get_value("Content-Encoding", Hdrs2),
?line undefined = proplists:get_value("Transfer-Encoding", Hdrs2),
?line "100" = proplists:get_value("Content-Length", Hdrs2),
?line "Accept-Encoding" = proplists:get_value("Vary", Hdrs2),

%% Dynamic content (chunked)
Uri3 = "http://localhost:8001/streamtest",
Expand All @@ -122,6 +146,7 @@ deflate_streamcontent() ->
?line "gzip" = proplists:get_value("Content-Encoding", Hdrs3),
?line "chunked" = proplists:get_value("Transfer-Encoding", Hdrs3),
?line undefined = proplists:get_value("Content-Length", Hdrs3),
?line "Accept-Encoding" = proplists:get_value("Vary", Hdrs3),
ok.


Expand Down

0 comments on commit d748f92

Please sign in to comment.