summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorgarren smith <garren.smith@gmail.com>2017-08-09 15:23:32 +0200
committerGitHub <noreply@github.com>2017-08-09 15:23:32 +0200
commit52da19b9c0f75e0afbb3267b8766343c270fdcf7 (patch)
treec7c9ce10bdeee58ddd7293311fb84b12961fc806
parent1ba42081779dfea2d4d09af00744ab4667ffd941 (diff)
downloadcouchdb-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.ini3
-rw-r--r--src/chttpd/src/chttpd.erl5
-rw-r--r--src/chttpd/src/chttpd_prefer_header.erl54
-rw-r--r--src/chttpd/test/chttpd_prefer_header_test.erl109
-rw-r--r--src/couch/src/couch_httpd.erl6
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),