From b32f2b6cb4df9ee82b779bf5aa097bf006eca468 Mon Sep 17 00:00:00 2001 From: ILYA Khlopotov Date: Tue, 8 Mar 2016 08:32:24 -0800 Subject: [PATCH 1/5] Extract basic_headers/2 function COUCHDB-2966 --- src/chttpd.erl | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/src/chttpd.erl b/src/chttpd.erl index 56b6445..673465e 100644 --- a/src/chttpd.erl +++ b/src/chttpd.erl @@ -506,11 +506,8 @@ serve_file(Req, RelativePath, DocumentRoot) -> serve_file(#httpd{mochi_req=MochiReq}=Req, RelativePath, DocumentRoot, ExtraHeaders) -> - Headers = server_header() ++ - couch_httpd_auth:cookie_auth_header(Req, []) ++ - ExtraHeaders, - Headers1 = chttpd_cors:headers(Req, Headers), - {ok, MochiReq:serve_file(RelativePath, DocumentRoot, Headers1)}. + Headers = basic_headers(Req, ExtraHeaders), + {ok, MochiReq:serve_file(RelativePath, DocumentRoot, Headers)}. qs_value(Req, Key) -> qs_value(Req, Key, undefined). @@ -660,10 +657,8 @@ verify_is_server_admin(#httpd{user_ctx=#user_ctx{roles=Roles}}) -> start_response_length(#httpd{mochi_req=MochiReq}=Req, Code, Headers0, Length) -> couch_stats:increment_counter([couchdb, httpd_status_codes, Code]), - Headers1 = Headers0 ++ server_header() ++ - couch_httpd_auth:cookie_auth_header(Req, Headers0), - Headers2 = chttpd_cors:headers(Req, Headers1), - Resp = MochiReq:start_response_length({Code, Headers2, Length}), + Headers1 = basic_headers(Req, Headers0), + Resp = MochiReq:start_response_length({Code, Headers1, Length}), case MochiReq:get(method) of 'HEAD' -> throw({http_head_abort, Resp}); _ -> ok @@ -676,10 +671,8 @@ send(Resp, Data) -> start_chunked_response(#httpd{mochi_req=MochiReq}=Req, Code, Headers0) -> couch_stats:increment_counter([couchdb, httpd_status_codes, Code]), - Headers1 = Headers0 ++ server_header() ++ - couch_httpd_auth:cookie_auth_header(Req, Headers0), - Headers2 = chttpd_cors:headers(Req, Headers1), - Resp = MochiReq:respond({Code, Headers2, chunked}), + Headers1 = basic_headers(Req, Headers0), + Resp = MochiReq:respond({Code, Headers1, chunked}), case MochiReq:get(method) of 'HEAD' -> throw({http_head_abort, Resp}); _ -> ok @@ -1068,3 +1061,9 @@ stack_hash(Stack) -> %% dedicated chunk. chunked_response_buffer_size() -> config:get_integer("httpd", "chunked_response_buffer", 1490). + +basic_headers(Req, Headers0) -> + Headers = Headers0 + ++ server_header() + ++ couch_httpd_auth:cookie_auth_header(Req, Headers0), + chttpd_cors:headers(Req, Headers). From 34bd2946074dd1ecd6b1952d315e000b1c85007c Mon Sep 17 00:00:00 2001 From: ILYA Khlopotov Date: Tue, 8 Mar 2016 08:57:55 -0800 Subject: [PATCH 2/5] Extract handle_response function COUCHDB-2966 --- src/chttpd.erl | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/chttpd.erl b/src/chttpd.erl index 673465e..f653a3d 100644 --- a/src/chttpd.erl +++ b/src/chttpd.erl @@ -656,9 +656,8 @@ verify_is_server_admin(#httpd{user_ctx=#user_ctx{roles=Roles}}) -> end. start_response_length(#httpd{mochi_req=MochiReq}=Req, Code, Headers0, Length) -> - couch_stats:increment_counter([couchdb, httpd_status_codes, Code]), Headers1 = basic_headers(Req, Headers0), - Resp = MochiReq:start_response_length({Code, Headers1, Length}), + Resp = handle_response(Req, Code, Headers1, Length, start_response_length), case MochiReq:get(method) of 'HEAD' -> throw({http_head_abort, Resp}); _ -> ok @@ -670,9 +669,8 @@ send(Resp, Data) -> {ok, Resp}. start_chunked_response(#httpd{mochi_req=MochiReq}=Req, Code, Headers0) -> - couch_stats:increment_counter([couchdb, httpd_status_codes, Code]), Headers1 = basic_headers(Req, Headers0), - Resp = MochiReq:respond({Code, Headers1, chunked}), + Resp = handle_response(Req, Code, Headers1, chunked, respond), case MochiReq:get(method) of 'HEAD' -> throw({http_head_abort, Resp}); _ -> ok @@ -1067,3 +1065,12 @@ basic_headers(Req, Headers0) -> ++ server_header() ++ couch_httpd_auth:cookie_auth_header(Req, Headers0), chttpd_cors:headers(Req, Headers). + +handle_response(Req, Code, Headers, Args, Type) -> + couch_stats:increment_counter([couchdb, httpd_status_codes, Code]), + respond_(Req, Code, Headers, Args, Type). + +respond_(#httpd{mochi_req = MochiReq}, Code, Headers, _Args, start_response) -> + MochiReq:start_response({Code, Headers}); +respond_(#httpd{mochi_req = MochiReq}, Code, Headers, Args, Type) -> + MochiReq:Type({Code, Headers, Args}). From 3d6f28189014fcc372930024a173fed3cb028899 Mon Sep 17 00:00:00 2001 From: ILYA Khlopotov Date: Tue, 8 Mar 2016 10:24:49 -0800 Subject: [PATCH 3/5] Introduce chttpd_plugin:before_response/4 EPI hook COUCHDB-2966 --- src/chttpd.erl | 8 +++++--- src/chttpd_plugin.erl | 8 +++++++- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/chttpd.erl b/src/chttpd.erl index f653a3d..6ca1258 100644 --- a/src/chttpd.erl +++ b/src/chttpd.erl @@ -1066,9 +1066,11 @@ basic_headers(Req, Headers0) -> ++ couch_httpd_auth:cookie_auth_header(Req, Headers0), chttpd_cors:headers(Req, Headers). -handle_response(Req, Code, Headers, Args, Type) -> - couch_stats:increment_counter([couchdb, httpd_status_codes, Code]), - respond_(Req, Code, Headers, Args, Type). +handle_response(Req0, Code0, Headers0, Args0, Type) -> + {ok, {Req1, Code1, Headers1, Args1}} = + chttpd_plugin:before_response(Req0, Code0, Headers0, Args0), + couch_stats:increment_counter([couchdb, httpd_status_codes, Code1]), + respond_(Req1, Code1, Headers1, Args1, Type). respond_(#httpd{mochi_req = MochiReq}, Code, Headers, _Args, start_response) -> MochiReq:start_response({Code, Headers}); diff --git a/src/chttpd_plugin.erl b/src/chttpd_plugin.erl index 711d0a9..7286b28 100644 --- a/src/chttpd_plugin.erl +++ b/src/chttpd_plugin.erl @@ -15,7 +15,8 @@ -export([ before_request/1, after_request/2, - handle_error/1 + handle_error/1, + before_response/4 ]). -define(SERVICE_ID, chttpd). @@ -38,6 +39,11 @@ handle_error(Error) -> [Error1] = with_pipe(handle_error, [Error]), Error1. +before_response(HttpReq0, Code0, Headers0, Value0) -> + [HttpReq, Code, Headers, Value] = + with_pipe(before_response, [HttpReq0, Code0, Headers0, Value0]), + {ok, {HttpReq, Code, Headers, Value}}. + %% ------------------------------------------------------------------ %% Internal Function Definitions %% ------------------------------------------------------------------ From 1d7d0f168d0afb3f7bb27c3f9bedfbce5ae5de8b Mon Sep 17 00:00:00 2001 From: ILYA Khlopotov Date: Tue, 8 Mar 2016 10:46:17 -0800 Subject: [PATCH 4/5] Add chttpd_plugin:before_serve_file/5 EPI hook COUCHDB-2966 --- src/chttpd.erl | 6 ++---- src/chttpd_plugin.erl | 9 ++++++++- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/chttpd.erl b/src/chttpd.erl index 6ca1258..6e7682a 100644 --- a/src/chttpd.erl +++ b/src/chttpd.erl @@ -504,10 +504,8 @@ primary_header_value(#httpd{mochi_req=MochiReq}, Key) -> serve_file(Req, RelativePath, DocumentRoot) -> serve_file(Req, RelativePath, DocumentRoot, []). -serve_file(#httpd{mochi_req=MochiReq}=Req, RelativePath, DocumentRoot, - ExtraHeaders) -> - Headers = basic_headers(Req, ExtraHeaders), - {ok, MochiReq:serve_file(RelativePath, DocumentRoot, Headers)}. +serve_file(Req0, RelativePath0, DocumentRoot0, ExtraHeaders) -> + couch_httpd:serve_file(Req0, RelativePath0, DocumentRoot0, ExtraHeaders). qs_value(Req, Key) -> qs_value(Req, Key, undefined). diff --git a/src/chttpd_plugin.erl b/src/chttpd_plugin.erl index 7286b28..7ab4581 100644 --- a/src/chttpd_plugin.erl +++ b/src/chttpd_plugin.erl @@ -16,7 +16,8 @@ before_request/1, after_request/2, handle_error/1, - before_response/4 + before_response/4, + before_serve_file/5 ]). -define(SERVICE_ID, chttpd). @@ -44,6 +45,12 @@ before_response(HttpReq0, Code0, Headers0, Value0) -> with_pipe(before_response, [HttpReq0, Code0, Headers0, Value0]), {ok, {HttpReq, Code, Headers, Value}}. +before_serve_file(Req0, Code0, Headers0, RelativePath0, DocumentRoot0) -> + [HttpReq, Code, Headers, RelativePath, DocumentRoot] = + with_pipe(before_serve_file, [ + Req0, Code0, Headers0, RelativePath0, DocumentRoot0]), + {ok, {HttpReq, Code, Headers, RelativePath, DocumentRoot}}. + %% ------------------------------------------------------------------ %% Internal Function Definitions %% ------------------------------------------------------------------ From 23faa75beedeea08ec9d758c0cd61572ab82db42 Mon Sep 17 00:00:00 2001 From: ILYA Khlopotov Date: Tue, 8 Mar 2016 12:30:00 -0800 Subject: [PATCH 5/5] Add test suite for chttpd_plugin COUCHDB-2966 --- test/chttpd_plugin_tests.erl | 189 +++++++++++++++++++++++++++++++++++ 1 file changed, 189 insertions(+) create mode 100644 test/chttpd_plugin_tests.erl diff --git a/test/chttpd_plugin_tests.erl b/test/chttpd_plugin_tests.erl new file mode 100644 index 0000000..e8d68b5 --- /dev/null +++ b/test/chttpd_plugin_tests.erl @@ -0,0 +1,189 @@ +% 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_plugin_tests). + +-export([ + before_request/1, + after_request/2, + handle_error/1, + before_response/4, + before_serve_file/5 +]). + +-export([ %% couch_epi_plugin behaviour + app/0, + providers/0, + services/0, + data_providers/0, + data_subscriptions/0, + processes/0, + notify/3 +]). + +-include_lib("couch/include/couch_eunit.hrl"). +-include_lib("couch/include/couch_db.hrl"). + +%% couch_epi_plugin behaviour + +app() -> test_app. +providers() -> [{chttpd, ?MODULE}]. +services() -> []. +data_providers() -> []. +data_subscriptions() -> []. +processes() -> []. +notify(_, _, _) -> ok. + +setup() -> + application:stop(couch_epi), % in case it's already running from other tests... + application:unload(couch_epi), + ok = application:load(couch_epi), + ok = application:set_env(couch_epi, plugins, [?MODULE]), % only this plugin + ok = application:start(couch_epi). + +teardown(_) -> + ok = application:stop(couch_epi), + ok = application:unload(couch_epi). + +before_request({true, Id}) -> [{true, [{before_request, Id}]}]; +before_request({false, Id}) -> [{false, Id}]; +before_request({fail, Id}) -> throw({before_request, Id}). + +after_request({true, Id}, A) -> [{true, [{after_request, Id}]}, A]; +after_request({false, Id}, A) -> [{false, Id}, A]; +after_request({fail, Id}, _A) -> throw({after_request, Id}). + +handle_error({true, Id}) -> [{true, [{handle_error, Id}]}]; +handle_error({false, Id}) -> [{false, Id}]; +handle_error({fail, Id}) -> throw({handle_error, Id}). + +before_response({true, Id}, A, B, C) -> + [{true, [{before_response, Id}]}, A, B, C]; +before_response({false, Id}, A, B, C) -> + [{false, Id}, A, B, C]; +before_response({fail, Id}, _A, _B, _C) -> + throw({before_response, Id}). + +before_serve_file({true, Id}, A, B, C, D) -> + [{true, [{before_serve_file, Id}]}, A, B, C, D]; +before_serve_file({false, Id}, A, B, C, D) -> + [{false, Id}, A, B, C, D]; +before_serve_file({fail, _Id}, _A, _B, _C, _D) -> + throw(before_serve_file). + +callback_test_() -> + { + "callback tests", + { + setup, fun setup/0, fun teardown/1, + [ + fun before_request_match/0, + fun before_request_no_match/0, + fun before_request_throw/0, + + fun after_request_match/0, + fun after_request_no_match/0, + fun after_request_throw/0, + + fun handle_error_match/0, + fun handle_error_no_match/0, + fun handle_error_throw/0, + + fun before_response_match/0, + fun before_response_no_match/0, + fun before_response_throw/0, + + fun before_serve_file_match/0, + fun before_serve_file_no_match/0, + fun before_serve_file_throw/0 + ] + } + }. + + +before_request_match() -> + ?assertEqual( + {ok, {true, [{before_request, foo}]}}, + chttpd_plugin:before_request({true, foo})). + +before_request_no_match() -> + ?assertEqual( + {ok, {false, foo}}, + chttpd_plugin:before_request({false, foo})). + +before_request_throw() -> + ?assertThrow( + {before_request, foo}, + chttpd_plugin:before_request({fail, foo})). + + +after_request_match() -> + ?assertEqual( + {ok, bar}, + chttpd_plugin:after_request({true, foo}, bar)). + +after_request_no_match() -> + ?assertEqual( + {ok, bar}, + chttpd_plugin:after_request({false, foo}, bar)). + +after_request_throw() -> + ?assertThrow( + {after_request, foo}, + chttpd_plugin:after_request({fail, foo}, bar)). + + +handle_error_match() -> + ?assertEqual( + {true, [{handle_error, foo}]}, + chttpd_plugin:handle_error({true, foo})). + +handle_error_no_match() -> + ?assertEqual( + {false, foo}, + chttpd_plugin:handle_error({false, foo})). + +handle_error_throw() -> + ?assertThrow( + {handle_error, foo}, + chttpd_plugin:handle_error({fail, foo})). + +before_response_match() -> + ?assertEqual( + {ok, {{true, [{before_response, foo}]}, 1, 2, 3}}, + chttpd_plugin:before_response({true, foo}, 1, 2, 3)). + +before_response_no_match() -> + ?assertEqual( + {ok, {{false, foo}, 1, 2, 3}}, + chttpd_plugin:before_response({false, foo}, 1, 2, 3)). + +before_response_throw() -> + ?assertThrow( + {before_response, foo}, + chttpd_plugin:before_response({fail, foo}, 1, 2, 3)). + + +before_serve_file_match() -> + ?assertEqual( + {ok, {{true, [{before_serve_file, foo}]}, 1, 2, 3, 4}}, + chttpd_plugin:before_serve_file({true, foo}, 1, 2, 3, 4)). + +before_serve_file_no_match() -> + ?assertEqual( + {ok, {{false, foo}, 1, 2, 3, 4}}, + chttpd_plugin:before_serve_file({false, foo}, 1, 2, 3, 4)). + +before_serve_file_throw() -> + ?assertThrow( + before_serve_file, + chttpd_plugin:before_serve_file({fail, foo}, 1, 2, 3, 4)).