diff options
author | garren smith <garren.smith@gmail.com> | 2017-08-09 15:23:32 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-08-09 15:23:32 +0200 |
commit | 52da19b9c0f75e0afbb3267b8766343c270fdcf7 (patch) | |
tree | c7c9ce10bdeee58ddd7293311fb84b12961fc806 | |
parent | 1ba42081779dfea2d4d09af00744ab4667ffd941 (diff) | |
download | couchdb-52da19b9c0f75e0afbb3267b8766343c270fdcf7.tar.gz |
Add Prefer: return=minimal support (#605)
Use the Prefer: return=minimal header options from
[rfc7240](https://tools.ietf.org/html/rfc7240) to reduce the
number of headers in the response. The header is configurable via the config
-rw-r--r-- | rel/overlay/etc/default.ini | 3 | ||||
-rw-r--r-- | src/chttpd/src/chttpd.erl | 5 | ||||
-rw-r--r-- | src/chttpd/src/chttpd_prefer_header.erl | 54 | ||||
-rw-r--r-- | src/chttpd/test/chttpd_prefer_header_test.erl | 109 | ||||
-rw-r--r-- | src/couch/src/couch_httpd.erl | 6 |
5 files changed, 173 insertions, 4 deletions
diff --git a/rel/overlay/etc/default.ini b/rel/overlay/etc/default.ini index d840ef801..30b2efa5c 100644 --- a/rel/overlay/etc/default.ini +++ b/rel/overlay/etc/default.ini @@ -57,6 +57,9 @@ backlog = 512 docroot = {{fauxton_root}} socket_options = [{recbuf, 262144}, {sndbuf, 262144}, {nodelay, true}] require_valid_user = false +; List of headers that will be kept when the header Prefer: return=minimal is included in a request. +; If Server header is left out, Mochiweb will add its own one in. +prefer_minimal = Cache-Control, Content-Length, Content-Range, Content-Type, ETag, Server, Transfer-Encoding, Vary [database_compaction] ; larger buffer sizes can originate smaller files diff --git a/src/chttpd/src/chttpd.erl b/src/chttpd/src/chttpd.erl index cfefb7800..178209bf5 100644 --- a/src/chttpd/src/chttpd.erl +++ b/src/chttpd/src/chttpd.erl @@ -1115,8 +1115,9 @@ basic_headers(Req, Headers0) -> Headers = Headers0 ++ server_header() ++ couch_httpd_auth:cookie_auth_header(Req, Headers0), - Headers1 = chttpd_xframe_options:header(Req, Headers), - chttpd_cors:headers(Req, Headers1). + Headers1 = chttpd_cors:headers(Req, Headers), + Headers2 = chttpd_xframe_options:header(Req, Headers1), + chttpd_prefer_header:maybe_return_minimal(Req, Headers2). handle_response(Req0, Code0, Headers0, Args0, Type) -> {ok, {Req1, Code1, Headers1, Args1}} = diff --git a/src/chttpd/src/chttpd_prefer_header.erl b/src/chttpd/src/chttpd_prefer_header.erl new file mode 100644 index 000000000..f550e80e5 --- /dev/null +++ b/src/chttpd/src/chttpd_prefer_header.erl @@ -0,0 +1,54 @@ +% Licensed under the Apache License, Version 2.0 (the "License"); you may not +% use this file except in compliance with the License. You may obtain a copy of +% the License at +% +% http://www.apache.org/licenses/LICENSE-2.0 +% +% Unless required by applicable law or agreed to in writing, software +% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +% License for the specific language governing permissions and limitations under +% the License. + +-module(chttpd_prefer_header). + + +-export([ + maybe_return_minimal/2 +]). + + +-include_lib("couch/include/couch_db.hrl"). + + +maybe_return_minimal(#httpd{mochi_req = MochiReq}, Headers) -> + case get_prefer_header(MochiReq) of + "return=minimal" -> + filter_headers(Headers, get_header_list()); + _ -> + Headers + end. + + +get_prefer_header(Req) -> + case Req:get_header_value("Prefer") of + Value when is_list(Value) -> + string:to_lower(Value); + undefined -> + undefined + end. + + +filter_headers(Headers, IncludeList) -> + lists:filter(fun({HeaderName, _}) -> + lists:member(HeaderName, IncludeList) + end, Headers). + + +get_header_list() -> + SectionStr = config:get("chttpd", "prefer_minimal", ""), + split_list(SectionStr). + + +split_list(S) -> + re:split(S, "\\s*,\\s*", [trim, {return, list}]). diff --git a/src/chttpd/test/chttpd_prefer_header_test.erl b/src/chttpd/test/chttpd_prefer_header_test.erl new file mode 100644 index 000000000..a8a5b3d26 --- /dev/null +++ b/src/chttpd/test/chttpd_prefer_header_test.erl @@ -0,0 +1,109 @@ +% Licensed under the Apache License, Version 2.0 (the "License"); you may not +% use this file except in compliance with the License. You may obtain a copy of +% the License at +% +% http://www.apache.org/licenses/LICENSE-2.0 +% +% Unless required by applicable law or agreed to in writing, software +% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +% License for the specific language governing permissions and limitations under +% the License. + +-module(chttpd_prefer_header_test). +-include_lib("couch/include/couch_db.hrl"). +-include_lib("eunit/include/eunit.hrl"). + + +mock_request(ExcludeHeader) -> + Headers = mochiweb_headers:make(ExcludeHeader), + MochiReq = mochiweb_request:new(nil, 'GET', "/", {1, 1}, Headers), + MochiReq:cleanup(), + #httpd{mochi_req = MochiReq}. + + +default_headers() -> + [ + {"Cache-Control","must-revalidate"}, + {"Content-Type","application/json"}, + {"Content-Length", "100"}, + {"ETag","\"12343\""}, + {"X-Couch-Request-ID","7bd1adab86"}, + {"X-CouchDB-Body-Time","0"}, + {"Vary", "Accept-Encoding"}, + {"Server","CouchDB/2.1.0-f1a1d7f1c (Erlang OTP/19)"} + ]. + + +minimal_options_headers() -> + [ + {"Cache-Control","must-revalidate"}, + {"Content-Type","application/json"}, + {"Content-Length", "100"}, + {"ETag","\"12343\""}, + {"Vary", "Accept-Encoding"}, + {"Server","CouchDB/2.1.0-f1a1d7f1c (Erlang OTP/19)"} + ]. + + +default_no_exclude_header_test() -> + Headers = chttpd_prefer_header:maybe_return_minimal( + mock_request([]), + default_headers() + ), + ?assertEqual(default_headers(), Headers). + + +unsupported_exclude_header_test() -> + Req = mock_request([{"prefer", "Wrong"}]), + Headers = chttpd_prefer_header:maybe_return_minimal(Req, default_headers()), + ?assertEqual(default_headers(), Headers). + + +empty_header_test() -> + Req = mock_request([{"prefer", ""}]), + Headers = chttpd_prefer_header:maybe_return_minimal(Req, default_headers()), + ?assertEqual(default_headers(), Headers). + +setup() -> + ok = meck:new(config), + ok = meck:expect(config, get, fun("chttpd", "prefer_minimal", _) -> + "Cache-Control, Content-Length, Content-Type, ETag, Server, Vary" + end), + ok. + + +teardown(_) -> + meck:unload(config). + + +exclude_headers_test_() -> + { + "Test Prefer headers", + { + foreach, fun setup/0, fun teardown/1, + [ + fun minimal_options/1, + fun minimal_options_check_header_case/1, + fun minimal_options_check_header_value_case/1 + ] + } + }. + + +minimal_options(_) -> + Req = mock_request([{"Prefer", "return=minimal"}]), + Headers = chttpd_prefer_header:maybe_return_minimal(Req, default_headers()), + ?_assertEqual(minimal_options_headers(), Headers). + + +minimal_options_check_header_case(_) -> + Req = mock_request([{"prefer", "return=minimal"}]), + Headers = chttpd_prefer_header:maybe_return_minimal(Req, default_headers()), + ?_assertEqual(minimal_options_headers(), Headers). + + +minimal_options_check_header_value_case(_) -> + Req = mock_request([{"prefer", "RETURN=MINIMAL"}]), + Headers = chttpd_prefer_header:maybe_return_minimal(Req, default_headers()), + ?_assertEqual(minimal_options_headers(), Headers).
\ No newline at end of file diff --git a/src/couch/src/couch_httpd.erl b/src/couch/src/couch_httpd.erl index 95b707447..faaf080d9 100644 --- a/src/couch/src/couch_httpd.erl +++ b/src/couch/src/couch_httpd.erl @@ -755,7 +755,8 @@ send_response_no_cors(#httpd{mochi_req=MochiReq}=Req, Code, Headers, Body) -> Headers1 = http_1_0_keep_alive(MochiReq, Headers), Headers2 = basic_headers_no_cors(Req, Headers1), Headers3 = chttpd_xframe_options:header(Req, Headers2), - Resp = handle_response(Req, Code, Headers3, Body, respond), + Headers4 = chttpd_prefer_header:maybe_return_minimal(Req, Headers3), + Resp = handle_response(Req, Code, Headers4, Body, respond), log_response(Code, Body), {ok, Resp}. @@ -1134,7 +1135,8 @@ validate_bind_address(Address) -> add_headers(Req, Headers0) -> Headers = basic_headers(Req, Headers0), - http_1_0_keep_alive(Req, Headers). + Headers1 = http_1_0_keep_alive(Req, Headers), + chttpd_prefer_header:maybe_return_minimal(Req, Headers1). basic_headers(Req, Headers0) -> Headers1 = basic_headers_no_cors(Req, Headers0), |