From 1014684a7d01c19f5f1a7bae5dbd0aee1aed9b87 Mon Sep 17 00:00:00 2001 From: Garren Smith Date: Wed, 21 Jun 2017 09:41:18 +0200 Subject: [PATCH 1/8] Add X-Couch-Exclude-Headers support Adding this header allows the client to request that only Non-Essential or All headers are removed from the response. This will decrease the size of the response. --- src/chttpd/src/chttpd.erl | 5 +- src/chttpd/src/chttpd_exclude_headers.erl | 45 ++++++++++++++++ src/chttpd/test/chttpd_exclude_headers_test.erl | 69 +++++++++++++++++++++++++ src/couch/src/couch_httpd.erl | 6 ++- 4 files changed, 121 insertions(+), 4 deletions(-) create mode 100644 src/chttpd/src/chttpd_exclude_headers.erl create mode 100644 src/chttpd/test/chttpd_exclude_headers_test.erl diff --git a/src/chttpd/src/chttpd.erl b/src/chttpd/src/chttpd.erl index 9423fa9f48..43bcf51783 100644 --- a/src/chttpd/src/chttpd.erl +++ b/src/chttpd/src/chttpd.erl @@ -1113,8 +1113,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_exclude_headers:maybe_exclude_headers(Req, Headers2). handle_response(Req0, Code0, Headers0, Args0, Type) -> {ok, {Req1, Code1, Headers1, Args1}} = diff --git a/src/chttpd/src/chttpd_exclude_headers.erl b/src/chttpd/src/chttpd_exclude_headers.erl new file mode 100644 index 0000000000..de94c2c1ab --- /dev/null +++ b/src/chttpd/src/chttpd_exclude_headers.erl @@ -0,0 +1,45 @@ +% 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_exclude_headers). +-include_lib("couch/include/couch_db.hrl"). + +-export([maybe_exclude_headers/2]). + + + +% List of essential headers for an http request. If we leave out server then Mochiweb adds in its default one +-define(ESSENTIAL_HEADERS, ["Cache-Control", "ETag", "Content-Type", "Content-Length", "Vary", "Server"]). + + + +%Default headers so that Mochiweb doesn't sent its own +-define(DEFAULT_HEADERS, ["Server"]). + + + +maybe_exclude_headers(#httpd{mochi_req = MochiReq}, Headers) -> + case MochiReq:get_header_value("X-Couch-Exclude-Headers") of + "All" -> + filter_headers(Headers, ?DEFAULT_HEADERS); + "Non-Essential" -> + filter_headers(Headers, ?ESSENTIAL_HEADERS); + _ -> + Headers + end. + + + +filter_headers(Headers, IncludeList) -> + lists:filter(fun({HeaderName, _}) -> + lists:member(HeaderName, IncludeList) + end, Headers). diff --git a/src/chttpd/test/chttpd_exclude_headers_test.erl b/src/chttpd/test/chttpd_exclude_headers_test.erl new file mode 100644 index 0000000000..53f48d877c --- /dev/null +++ b/src/chttpd/test/chttpd_exclude_headers_test.erl @@ -0,0 +1,69 @@ +% 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_exclude_headers_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)"} + ]. + + + +only_cache_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_exclude_headers:maybe_exclude_headers(mock_request([]), default_headers()), + ?assertEqual(default_headers(), Headers). + + + +unsupported_exclude_header_test() -> + Req = mock_request([{"X-Couch-Exclude-Headers", "Wrong"}]), + Headers = chttpd_exclude_headers:maybe_exclude_headers(Req, default_headers()), + ?assertEqual(default_headers(), Headers). + + + +all_none_cache_headers_test() -> + Req = mock_request([{"X-Couch-Exclude-Headers", "Non-Essential"}]), + Headers = chttpd_exclude_headers:maybe_exclude_headers(Req, default_headers()), + ?assertEqual(only_cache_headers(), Headers). diff --git a/src/couch/src/couch_httpd.erl b/src/couch/src/couch_httpd.erl index 95b7074476..2b0b80434c 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_exclude_headers:maybe_exclude_headers(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_exclude_headers:maybe_exclude_headers(Req, Headers1). basic_headers(Req, Headers0) -> Headers1 = basic_headers_no_cors(Req, Headers0), From bfa946c50d21c62951375387e0a075a935c5ca1a Mon Sep 17 00:00:00 2001 From: Garren Smith Date: Wed, 21 Jun 2017 14:42:45 +0200 Subject: [PATCH 2/8] fixes from review --- rel/overlay/etc/default.ini | 6 +++ src/chttpd/src/chttpd_exclude_headers.erl | 28 ++++++++------ src/chttpd/test/chttpd_exclude_headers_test.erl | 51 +++++++++++++++++++++++-- 3 files changed, 69 insertions(+), 16 deletions(-) diff --git a/rel/overlay/etc/default.ini b/rel/overlay/etc/default.ini index 39af13ddf5..19abc093bf 100644 --- a/rel/overlay/etc/default.ini +++ b/rel/overlay/etc/default.ini @@ -194,6 +194,12 @@ credentials = false ; List of accepted methods ; methods = +[exclude_headers] +; List of headers that will be kept when the header option X-Couch-Exclude-Headers is included in a request +; 'X-Couch-Exclude-Headers' : "All" +all = Server +minimal = Cache-Control, Content-Length, Content-Type, ETag, Server, Vary + [x_frame_options] ; Settings same-origin will return X-Frame-Options: SAMEORIGIN. ; If same origin is set, it will ignore the hosts setting diff --git a/src/chttpd/src/chttpd_exclude_headers.erl b/src/chttpd/src/chttpd_exclude_headers.erl index de94c2c1ab..e471e2e3b7 100644 --- a/src/chttpd/src/chttpd_exclude_headers.erl +++ b/src/chttpd/src/chttpd_exclude_headers.erl @@ -11,28 +11,21 @@ % the License. -module(chttpd_exclude_headers). --include_lib("couch/include/couch_db.hrl"). - --export([maybe_exclude_headers/2]). - -% List of essential headers for an http request. If we leave out server then Mochiweb adds in its default one --define(ESSENTIAL_HEADERS, ["Cache-Control", "ETag", "Content-Type", "Content-Length", "Vary", "Server"]). - +-export([maybe_exclude_headers/2]). -%Default headers so that Mochiweb doesn't sent its own --define(DEFAULT_HEADERS, ["Server"]). +-include_lib("couch/include/couch_db.hrl"). maybe_exclude_headers(#httpd{mochi_req = MochiReq}, Headers) -> case MochiReq:get_header_value("X-Couch-Exclude-Headers") of "All" -> - filter_headers(Headers, ?DEFAULT_HEADERS); - "Non-Essential" -> - filter_headers(Headers, ?ESSENTIAL_HEADERS); + filter_headers(Headers, get_header_list("all")); + "Minimal" -> + filter_headers(Headers, get_header_list("minimal")); _ -> Headers end. @@ -43,3 +36,14 @@ filter_headers(Headers, IncludeList) -> lists:filter(fun({HeaderName, _}) -> lists:member(HeaderName, IncludeList) end, Headers). + + + +get_header_list(Section) -> + SectionStr = config:get("exclude_headers", Section, []), + split_list(SectionStr). + + + +split_list(S) -> + re:split(S, "\\s*,\\s*", [trim, {return, list}]). diff --git a/src/chttpd/test/chttpd_exclude_headers_test.erl b/src/chttpd/test/chttpd_exclude_headers_test.erl index 53f48d877c..3496ef4cc3 100644 --- a/src/chttpd/test/chttpd_exclude_headers_test.erl +++ b/src/chttpd/test/chttpd_exclude_headers_test.erl @@ -38,7 +38,7 @@ default_headers() -> -only_cache_headers() -> +minimal_options_headers() -> [ {"Cache-Control","must-revalidate"}, {"Content-Type","application/json"}, @@ -50,6 +50,12 @@ only_cache_headers() -> +all_options_headers() -> + [ + {"Server","CouchDB/2.1.0-f1a1d7f1c (Erlang OTP/19)"} + ]. + + default_no_exclude_header_test() -> Headers = chttpd_exclude_headers:maybe_exclude_headers(mock_request([]), default_headers()), ?assertEqual(default_headers(), Headers). @@ -62,8 +68,45 @@ unsupported_exclude_header_test() -> ?assertEqual(default_headers(), Headers). +setup() -> +ok. + + +teardown(_) -> + meck:unload(config). + + + +exclude_headers_test_() -> + { + "Test X-Couch-Exclude-Headers", + { + foreach, fun setup/0, fun teardown/1, + [ + fun minimal_options/1, + fun all_options/1 + ] + } + }. + + + +minimal_options(_) -> + ok = meck:new(config), + ok = meck:expect(config, get, fun("exclude_headers", "minimal", _) -> + "Cache-Control, Content-Length, Content-Type, ETag, Server, Vary" + end), + Req = mock_request([{"X-Couch-Exclude-Headers", "Minimal"}]), + Headers = chttpd_exclude_headers:maybe_exclude_headers(Req, default_headers()), + ?_assertEqual(minimal_options_headers(), Headers). + + -all_none_cache_headers_test() -> - Req = mock_request([{"X-Couch-Exclude-Headers", "Non-Essential"}]), +all_options(_) -> + ok = meck:new(config), + ok = meck:expect(config, get, fun("exclude_headers", "all", _) -> + "Server" + end), + Req = mock_request([{"X-Couch-Exclude-Headers", "All"}]), Headers = chttpd_exclude_headers:maybe_exclude_headers(Req, default_headers()), - ?assertEqual(only_cache_headers(), Headers). + ?_assertEqual(all_options_headers(), Headers). \ No newline at end of file From 5fd51439dd8002b4f44ee8beb60218bfe060781c Mon Sep 17 00:00:00 2001 From: Garren Smith Date: Mon, 26 Jun 2017 11:18:14 +0200 Subject: [PATCH 3/8] add extra headers to default.ini --- rel/overlay/etc/default.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rel/overlay/etc/default.ini b/rel/overlay/etc/default.ini index 19abc093bf..2f95053dc6 100644 --- a/rel/overlay/etc/default.ini +++ b/rel/overlay/etc/default.ini @@ -197,7 +197,7 @@ credentials = false [exclude_headers] ; List of headers that will be kept when the header option X-Couch-Exclude-Headers is included in a request ; 'X-Couch-Exclude-Headers' : "All" -all = Server +all = Content-Length, Content-Type, Server, Transfer-Encoding minimal = Cache-Control, Content-Length, Content-Type, ETag, Server, Vary [x_frame_options] From 9f3e745c2a840af7d33a8af61d323dd204ab2f68 Mon Sep 17 00:00:00 2001 From: Garren Smith Date: Fri, 30 Jun 2017 15:54:10 +0200 Subject: [PATCH 4/8] Use Header Prefer:return=minimal Use the Prefer: return=minimal header options from [rfc7240](https://tools.ietf.org/html/rfc7240). This will reduce the number of headers in the response. The headers to be included is configurable via the config. --- rel/overlay/etc/default.ini | 6 ++-- src/chttpd/src/chttpd.erl | 2 +- ...xclude_headers.erl => chttpd_prefer_header.erl} | 20 ++++++------ ...ders_test.erl => chttpd_prefer_header_test.erl} | 36 ++++++---------------- src/couch/src/couch_httpd.erl | 4 +-- 5 files changed, 25 insertions(+), 43 deletions(-) rename src/chttpd/src/{chttpd_exclude_headers.erl => chttpd_prefer_header.erl} (66%) rename src/chttpd/test/{chttpd_exclude_headers_test.erl => chttpd_prefer_header_test.erl} (67%) diff --git a/rel/overlay/etc/default.ini b/rel/overlay/etc/default.ini index 2f95053dc6..4acb7b19fa 100644 --- a/rel/overlay/etc/default.ini +++ b/rel/overlay/etc/default.ini @@ -194,10 +194,8 @@ credentials = false ; List of accepted methods ; methods = -[exclude_headers] -; List of headers that will be kept when the header option X-Couch-Exclude-Headers is included in a request -; 'X-Couch-Exclude-Headers' : "All" -all = Content-Length, Content-Type, Server, Transfer-Encoding +[prefer_header] +; List of headers that will be kept when the header Prefer: return=minimal is included in a request minimal = Cache-Control, Content-Length, Content-Type, ETag, Server, Vary [x_frame_options] diff --git a/src/chttpd/src/chttpd.erl b/src/chttpd/src/chttpd.erl index 43bcf51783..c397bf791d 100644 --- a/src/chttpd/src/chttpd.erl +++ b/src/chttpd/src/chttpd.erl @@ -1115,7 +1115,7 @@ basic_headers(Req, Headers0) -> ++ couch_httpd_auth:cookie_auth_header(Req, Headers0), Headers1 = chttpd_cors:headers(Req, Headers), Headers2 = chttpd_xframe_options:header(Req, Headers1), - chttpd_exclude_headers:maybe_exclude_headers(Req, Headers2). + 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_exclude_headers.erl b/src/chttpd/src/chttpd_prefer_header.erl similarity index 66% rename from src/chttpd/src/chttpd_exclude_headers.erl rename to src/chttpd/src/chttpd_prefer_header.erl index e471e2e3b7..00e8bdd9c5 100644 --- a/src/chttpd/src/chttpd_exclude_headers.erl +++ b/src/chttpd/src/chttpd_prefer_header.erl @@ -10,22 +10,22 @@ % License for the specific language governing permissions and limitations under % the License. --module(chttpd_exclude_headers). +-module(chttpd_prefer_header). --export([maybe_exclude_headers/2]). +-export([maybe_return_minimal/2]). -include_lib("couch/include/couch_db.hrl"). -maybe_exclude_headers(#httpd{mochi_req = MochiReq}, Headers) -> - case MochiReq:get_header_value("X-Couch-Exclude-Headers") of - "All" -> - filter_headers(Headers, get_header_list("all")); - "Minimal" -> - filter_headers(Headers, get_header_list("minimal")); + + +maybe_return_minimal(#httpd{mochi_req = MochiReq}, Headers) -> + case MochiReq:get_header_value("Prefer") of + "return=minimal" -> + filter_headers(Headers, get_header_list()); _ -> Headers end. @@ -39,8 +39,8 @@ filter_headers(Headers, IncludeList) -> -get_header_list(Section) -> - SectionStr = config:get("exclude_headers", Section, []), +get_header_list() -> + SectionStr = config:get("prefer_header", "minimal", []), split_list(SectionStr). diff --git a/src/chttpd/test/chttpd_exclude_headers_test.erl b/src/chttpd/test/chttpd_prefer_header_test.erl similarity index 67% rename from src/chttpd/test/chttpd_exclude_headers_test.erl rename to src/chttpd/test/chttpd_prefer_header_test.erl index 3496ef4cc3..6baee50548 100644 --- a/src/chttpd/test/chttpd_exclude_headers_test.erl +++ b/src/chttpd/test/chttpd_prefer_header_test.erl @@ -10,7 +10,7 @@ % License for the specific language governing permissions and limitations under % the License. --module(chttpd_exclude_headers_test). +-module(chttpd_prefer_header_test). -include_lib("couch/include/couch_db.hrl"). -include_lib("eunit/include/eunit.hrl"). @@ -50,28 +50,24 @@ minimal_options_headers() -> -all_options_headers() -> - [ - {"Server","CouchDB/2.1.0-f1a1d7f1c (Erlang OTP/19)"} - ]. - - default_no_exclude_header_test() -> - Headers = chttpd_exclude_headers:maybe_exclude_headers(mock_request([]), default_headers()), + Headers = chttpd_prefer_header:maybe_return_minimal(mock_request([]), default_headers()), ?assertEqual(default_headers(), Headers). unsupported_exclude_header_test() -> - Req = mock_request([{"X-Couch-Exclude-Headers", "Wrong"}]), - Headers = chttpd_exclude_headers:maybe_exclude_headers(Req, default_headers()), + Req = mock_request([{"prefer", "Wrong"}]), + Headers = chttpd_prefer_header:maybe_return_minimal(Req, default_headers()), ?assertEqual(default_headers(), Headers). + setup() -> ok. + teardown(_) -> meck:unload(config). @@ -83,8 +79,7 @@ exclude_headers_test_() -> { foreach, fun setup/0, fun teardown/1, [ - fun minimal_options/1, - fun all_options/1 + fun minimal_options/1 ] } }. @@ -93,20 +88,9 @@ exclude_headers_test_() -> minimal_options(_) -> ok = meck:new(config), - ok = meck:expect(config, get, fun("exclude_headers", "minimal", _) -> + ok = meck:expect(config, get, fun("prefer_header", "minimal", _) -> "Cache-Control, Content-Length, Content-Type, ETag, Server, Vary" end), - Req = mock_request([{"X-Couch-Exclude-Headers", "Minimal"}]), - Headers = chttpd_exclude_headers:maybe_exclude_headers(Req, default_headers()), + Req = mock_request([{"Prefer", "return=minimal"}]), + Headers = chttpd_prefer_header:maybe_return_minimal(Req, default_headers()), ?_assertEqual(minimal_options_headers(), Headers). - - - -all_options(_) -> - ok = meck:new(config), - ok = meck:expect(config, get, fun("exclude_headers", "all", _) -> - "Server" - end), - Req = mock_request([{"X-Couch-Exclude-Headers", "All"}]), - Headers = chttpd_exclude_headers:maybe_exclude_headers(Req, default_headers()), - ?_assertEqual(all_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 2b0b80434c..faaf080d9a 100644 --- a/src/couch/src/couch_httpd.erl +++ b/src/couch/src/couch_httpd.erl @@ -755,7 +755,7 @@ 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), - Headers4 = chttpd_exclude_headers:maybe_exclude_headers(Req, Headers3), + Headers4 = chttpd_prefer_header:maybe_return_minimal(Req, Headers3), Resp = handle_response(Req, Code, Headers4, Body, respond), log_response(Code, Body), {ok, Resp}. @@ -1136,7 +1136,7 @@ validate_bind_address(Address) -> add_headers(Req, Headers0) -> Headers = basic_headers(Req, Headers0), Headers1 = http_1_0_keep_alive(Req, Headers), - chttpd_exclude_headers:maybe_exclude_headers(Req, Headers1). + chttpd_prefer_header:maybe_return_minimal(Req, Headers1). basic_headers(Req, Headers0) -> Headers1 = basic_headers_no_cors(Req, Headers0), From f1b6a960e66c92257f1b1bc3a81d306f2114120a Mon Sep 17 00:00:00 2001 From: Garren Smith Date: Mon, 31 Jul 2017 10:00:05 +0200 Subject: [PATCH 5/8] fixes from reviews --- rel/overlay/etc/default.ini | 7 ++--- src/chttpd/src/chttpd_prefer_header.erl | 21 ++++++++----- src/chttpd/test/chttpd_prefer_header_test.erl | 45 +++++++++++++++++---------- 3 files changed, 45 insertions(+), 28 deletions(-) diff --git a/rel/overlay/etc/default.ini b/rel/overlay/etc/default.ini index 4acb7b19fa..781a237522 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-Type, ETag, Server, Vary [database_compaction] ; larger buffer sizes can originate smaller files @@ -194,10 +197,6 @@ credentials = false ; List of accepted methods ; methods = -[prefer_header] -; List of headers that will be kept when the header Prefer: return=minimal is included in a request -minimal = Cache-Control, Content-Length, Content-Type, ETag, Server, Vary - [x_frame_options] ; Settings same-origin will return X-Frame-Options: SAMEORIGIN. ; If same origin is set, it will ignore the hosts setting diff --git a/src/chttpd/src/chttpd_prefer_header.erl b/src/chttpd/src/chttpd_prefer_header.erl index 00e8bdd9c5..6658c23535 100644 --- a/src/chttpd/src/chttpd_prefer_header.erl +++ b/src/chttpd/src/chttpd_prefer_header.erl @@ -13,17 +13,16 @@ -module(chttpd_prefer_header). - --export([maybe_return_minimal/2]). - +-export([ + maybe_return_minimal/2 + ]). -include_lib("couch/include/couch_db.hrl"). - maybe_return_minimal(#httpd{mochi_req = MochiReq}, Headers) -> - case MochiReq:get_header_value("Prefer") of + case get_prefer_header(MochiReq) of "return=minimal" -> filter_headers(Headers, get_header_list()); _ -> @@ -31,6 +30,14 @@ maybe_return_minimal(#httpd{mochi_req = MochiReq}, 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, _}) -> @@ -38,12 +45,10 @@ filter_headers(Headers, IncludeList) -> end, Headers). - get_header_list() -> - SectionStr = config:get("prefer_header", "minimal", []), + 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 index 6baee50548..a8a5b3d262 100644 --- a/src/chttpd/test/chttpd_prefer_header_test.erl +++ b/src/chttpd/test/chttpd_prefer_header_test.erl @@ -15,7 +15,6 @@ -include_lib("eunit/include/eunit.hrl"). - mock_request(ExcludeHeader) -> Headers = mochiweb_headers:make(ExcludeHeader), MochiReq = mochiweb_request:new(nil, 'GET', "/", {1, 1}, Headers), @@ -23,7 +22,6 @@ mock_request(ExcludeHeader) -> #httpd{mochi_req = MochiReq}. - default_headers() -> [ {"Cache-Control","must-revalidate"}, @@ -37,7 +35,6 @@ default_headers() -> ]. - minimal_options_headers() -> [ {"Cache-Control","must-revalidate"}, @@ -49,48 +46,64 @@ minimal_options_headers() -> ]. - default_no_exclude_header_test() -> - Headers = chttpd_prefer_header:maybe_return_minimal(mock_request([]), default_headers()), + 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. - + 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 X-Couch-Exclude-Headers", + "Test Prefer headers", { foreach, fun setup/0, fun teardown/1, [ - fun minimal_options/1 + fun minimal_options/1, + fun minimal_options_check_header_case/1, + fun minimal_options_check_header_value_case/1 ] } }. - minimal_options(_) -> - ok = meck:new(config), - ok = meck:expect(config, get, fun("prefer_header", "minimal", _) -> - "Cache-Control, Content-Length, Content-Type, ETag, Server, Vary" - end), 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 From f05b672e1df222d3c27116034e5a2f3f200e36b9 Mon Sep 17 00:00:00 2001 From: Garren Smith Date: Wed, 2 Aug 2017 11:47:38 +0200 Subject: [PATCH 6/8] add missing headers and fix spacing --- rel/overlay/etc/default.ini | 2 +- src/chttpd/src/chttpd_prefer_header.erl | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/rel/overlay/etc/default.ini b/rel/overlay/etc/default.ini index 781a237522..01a2a5dca0 100644 --- a/rel/overlay/etc/default.ini +++ b/rel/overlay/etc/default.ini @@ -59,7 +59,7 @@ 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-Type, ETag, Server, Vary +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_prefer_header.erl b/src/chttpd/src/chttpd_prefer_header.erl index 6658c23535..64f448b58a 100644 --- a/src/chttpd/src/chttpd_prefer_header.erl +++ b/src/chttpd/src/chttpd_prefer_header.erl @@ -15,7 +15,7 @@ -export([ maybe_return_minimal/2 - ]). +]). -include_lib("couch/include/couch_db.hrl"). From 70fedd6ff6e3cc35da64165384a8a6cab4272f1b Mon Sep 17 00:00:00 2001 From: Garren Smith Date: Wed, 9 Aug 2017 11:31:17 +0200 Subject: [PATCH 7/8] latest review fixes --- rel/overlay/etc/default.ini | 2 +- src/chttpd/src/chttpd_prefer_header.erl | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/rel/overlay/etc/default.ini b/rel/overlay/etc/default.ini index 01a2a5dca0..381fc2f1c6 100644 --- a/rel/overlay/etc/default.ini +++ b/rel/overlay/etc/default.ini @@ -59,7 +59,7 @@ 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 +; 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_prefer_header.erl b/src/chttpd/src/chttpd_prefer_header.erl index 64f448b58a..f550e80e54 100644 --- a/src/chttpd/src/chttpd_prefer_header.erl +++ b/src/chttpd/src/chttpd_prefer_header.erl @@ -46,7 +46,7 @@ filter_headers(Headers, IncludeList) -> get_header_list() -> - SectionStr = config:get("chttpd", "prefer_minimal", []), + SectionStr = config:get("chttpd", "prefer_minimal", ""), split_list(SectionStr). From c603bc746b01850039dcd7a15f44ee06aec3ecc9 Mon Sep 17 00:00:00 2001 From: Garren Smith Date: Wed, 9 Aug 2017 14:28:31 +0200 Subject: [PATCH 8/8] uncomment in default.ini --- rel/overlay/etc/default.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rel/overlay/etc/default.ini b/rel/overlay/etc/default.ini index 381fc2f1c6..01a2a5dca0 100644 --- a/rel/overlay/etc/default.ini +++ b/rel/overlay/etc/default.ini @@ -59,7 +59,7 @@ 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 +prefer_minimal = Cache-Control, Content-Length, Content-Range, Content-Type, ETag, Server, Transfer-Encoding, Vary [database_compaction] ; larger buffer sizes can originate smaller files