1: %%==============================================================================
    2: %% Copyright 2014 Erlang Solutions Ltd.
    3: %%
    4: %% Licensed under the Apache License, Version 2.0 (the "License");
    5: %% you may not use this file except in compliance with the License.
    6: %% You may obtain a copy of the License at
    7: %%
    8: %% http://www.apache.org/licenses/LICENSE-2.0
    9: %%
   10: %% Unless required by applicable law or agreed to in writing, software
   11: %% distributed under the License is distributed on an "AS IS" BASIS,
   12: %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   13: %% See the License for the specific language governing permissions and
   14: %% limitations under the License.
   15: %%==============================================================================
   16: 
   17: -module(revproxy_SUITE).
   18: -compile([export_all, nowarn_export_all]).
   19: 
   20: -include_lib("common_test/include/ct.hrl").
   21: 
   22: -include("mod_revproxy.hrl").
   23: 
   24: -import(ejabberd_helper, [start_ejabberd/1,
   25:                           stop_ejabberd/0,
   26:                           use_config_file/2,
   27:                           start_ejabberd_with_config/2]).
   28: 
   29: %%--------------------------------------------------------------------
   30: %% Suite configuration
   31: %%--------------------------------------------------------------------
   32: 
   33: all() ->
   34:     [{group, compile_routes},
   35:      {group, match_routes},
   36:      {group, generate_upstream},
   37:      {group, requests_http}].
   38: 
   39: groups() ->
   40:     [{compile_routes, [sequence], [compile_example_routes,
   41:                                    example_dynamic_compile]},
   42:      {match_routes, [sequence], [exact_path_match,
   43:                                  remainder_match,
   44:                                  capture_subdomain_match,
   45:                                  method_match,
   46:                                  qs_match,
   47:                                  slash_ending_match]},
   48:      {generate_upstream, [sequence], [upstream_uri,
   49:                                       upstream_host,
   50:                                       upstream_bindings,
   51:                                       upstream_qs,
   52:                                       upstream_slash_path,
   53:                                       upstream_slash_remainder]},
   54:      {requests_http, [sequence], [no_upstreams,
   55:                                   http_upstream,
   56:                                   nomatch_upstream,
   57:                                   https_upstream]}].
   58: 
   59: suite() ->
   60:     [].
   61: 
   62: %%--------------------------------------------------------------------
   63: %% Init & teardown
   64: %%--------------------------------------------------------------------
   65: 
   66: -define(APPS, [crypto, ssl, fusco, ranch, cowlib, cowboy]).
   67: 
   68: init_per_suite(Config) ->
   69:     [application:start(App) || App <- ?APPS],
   70:     {ok, Pid} = create_handler(),
   71:     [{meck_pid, Pid}|Config].
   72: 
   73: end_per_suite(Config) ->
   74:     remove_handler(Config),
   75:     mnesia:stop(),
   76:     mnesia:delete_schema([node()]),
   77:     Config.
   78: 
   79: init_per_group(requests_http, Config) ->
   80:     start_revproxy(),
   81:     Config;
   82: init_per_group(match_routes, Config) ->
   83:     Rules = mod_revproxy:compile_routes(example_routes()),
   84:     [{rules, Rules}|Config];
   85: init_per_group(_GroupName, Config) ->
   86:     Config.
   87: 
   88: end_per_group(requests_http, Config) ->
   89:     stop_revproxy(),
   90:     Config;
   91: end_per_group(_GroupName, Config) ->
   92:     Config.
   93: 
   94: init_per_testcase(http_upstream, Config) ->
   95:     meck:new(inet, [unstick, passthrough]),
   96:     meck:expect(inet, getaddr, fun mock_getaddr/2),
   97:     meck:expect(inet, getaddrs_tm, fun mock_getaddrs_tm/3),
   98: 
   99:     start_http_upstream(),
  100: 
  101:     Config;
  102: init_per_testcase(https_upstream, Config) ->
  103:     start_https_upstream(Config),
  104:     Config;
  105: init_per_testcase(_CaseName, Config) ->
  106:     Config.
  107: 
  108: end_per_testcase(http_upstream, Config) ->
  109:     stop_upstream(http_listener),
  110:     meck:unload(inet),
  111:     Config;
  112: end_per_testcase(https_upstream, Config) ->
  113:     stop_upstream(https_listener),
  114:     Config;
  115: end_per_testcase(_CaseName, Config) ->
  116:     Config.
  117: 
  118: %%--------------------------------------------------------------------
  119: %% Routes compile tests
  120: %%--------------------------------------------------------------------
  121: compile_example_routes(_Config) ->
  122:     %% Given
  123:     Expected = compiled_example_routes(),
  124: 
  125:     %% When
  126:     Compiled = mod_revproxy:compile_routes(example_routes()),
  127: 
  128:     %% Then
  129:     Expected = Compiled.
  130: 
  131: example_dynamic_compile(_Config) ->
  132:     %% Given
  133:     Expected = compiled_example_routes(),
  134: 
  135:     %% When
  136:     ok = mod_revproxy:compile(example_routes()),
  137: 
  138:     %% Then
  139:     Expected = mod_revproxy_dynamic:rules().
  140: 
  141: %%--------------------------------------------------------------------
  142: %% HTTP requests tests
  143: %%--------------------------------------------------------------------
  144: no_upstreams(_Config) ->
  145:     %% Given
  146:     Host = "http://localhost:8080",
  147:     Path = <<"/abc/index.html">>,
  148:     Method = "GET",
  149:     Headers = [{<<"host">>, <<"qwerty.com">>}],
  150:     Body = [],
  151: 
  152:     %% When
  153:     Response = execute_request(Host, Path, Method, Headers, Body),
  154: 
  155:     %% Then
  156:     true = is_status_code(Response, 502).
  157: 
  158: http_upstream(_Config) ->
  159:     %% Given
  160:     Host = "http://localhost:8080",
  161:     QS = <<"ts=1231231232222&st=223232&page=32">>,
  162:     Path = <<"/abc/login.htm?", QS/binary>>,
  163:     Method = "GET",
  164:     Headers = [{<<"host">>, <<"qwerty.com">>}],
  165:     Body = "some example body :)",
  166: 
  167:     %% When
  168:     Response = execute_request(Host, Path, Method, Headers, Body),
  169: 
  170:     %% Then
  171:     true = is_status_code(Response, 200),
  172:     true = does_response_match(Response,
  173:                                <<"qwerty.com">>,
  174:                                <<"domain/qwerty/path/abc/login.htm">>,
  175:                                Method,
  176:                                Body,
  177:                                QS),
  178: 
  179:     assert_contain_header(Response, <<"custom-header-1">>, <<"value">>),
  180:     assert_contain_header(Response, <<"custom-header-2">>, <<"some other value">>).
  181: 
  182: nomatch_upstream(_Config) ->
  183:     %% Given
  184:     Host = "http://localhost:8080",
  185:     Path = <<"/abc/def">>,
  186:     Method = "GET",
  187:     Headers = [{<<"host">>, <<"domain.net">>}],
  188:     Body = [],
  189: 
  190:     %% When
  191:     Response = execute_request(Host, Path, Method, Headers, Body),
  192: 
  193:     %% Then
  194:     true = is_status_code(Response, 404).
  195: 
  196: https_upstream(_Config) ->
  197:     %% Given
  198:     Host = "http://localhost:8080",
  199:     Path = <<"/admin/index.html">>,
  200:     Method = "POST",
  201:     Headers = [{<<"host">>, <<"otherdomain.com">>}],
  202:     Body = [],
  203: 
  204:     %% When
  205:     Response = execute_request(Host, Path, Method, Headers, Body),
  206: 
  207:     %% Then
  208:     assert_status_code(Response, 200),
  209:     true = does_response_match(Response,
  210:                                <<"otherdomain.com">>,
  211:                                <<"secret_admin/otherdomain/index.html">>,
  212:                                Method,
  213:                                Body,
  214:                                <<>>).
  215: 
  216: %%--------------------------------------------------------------------
  217: %% Routes matching tests
  218: %%--------------------------------------------------------------------
  219: exact_path_match(Config) ->
  220:     %% Given
  221:     Rules = ?config(rules, Config),
  222:     Host = <<"domain.com">>,
  223:     Path = <<"/abc">>,
  224:     Method1 = <<"GET">>,
  225:     Method2 = <<"POST">>,
  226: 
  227:     %% When
  228:     Match1 = mod_revproxy:match(Rules, Host, Path, Method1),
  229:     Match2 = mod_revproxy:match(Rules, Host, Path, Method2),
  230: 
  231:     %% Then
  232:     Upstream = #upstream{type = uri,
  233:                          protocol = <<"http://">>,
  234:                          host = [<<"localhost:8080">>]},
  235:     #match{upstream = Upstream} = Match1
  236:                                 = Match2.
  237: 
  238: remainder_match(Config) ->
  239:     %% Given
  240:     Rules = ?config(rules, Config),
  241:     Host = <<"domain.com">>,
  242:     Path1 = <<"/abc/def/ghi/index.html">>,
  243:     Path2 = <<"/def/ghi/index.html">>,
  244:     Method = <<"GET">>,
  245: 
  246:     %% When
  247:     Match1 = mod_revproxy:match(Rules, Host, Path1, Method),
  248:     Match2 = mod_revproxy:match(Rules, Host, Path2, Method),
  249: 
  250:     %% Then
  251:     Upstream1 = #upstream{type = uri,
  252:                           protocol = <<"http://">>,
  253:                           host = [<<"localhost:8080">>]},
  254:     #match{upstream = Upstream1,
  255:            remainder = [<<"def">>, <<"ghi">>, <<"index.html">>],
  256:            path = [<<"abc">>]} = Match1,
  257: 
  258:     Upstream2 = #upstream{type = host,
  259:                           protocol = <<"http://">>,
  260:                           host = [<<"localhost:1234">>]},
  261:     #match{upstream = Upstream2,
  262:            remainder = [<<"def">>, <<"ghi">>, <<"index.html">>],
  263:            path = '_'} = Match2.
  264: 
  265: capture_subdomain_match(Config) ->
  266:     %% Given
  267:     Rules = ?config(rules, Config),
  268:     Host1 = <<"static.domain.com">>,
  269:     Host2 = <<"nonstatic.domain.com">>,
  270:     Path = <<"/a/b/c">>,
  271:     Method = <<"GET">>,
  272: 
  273:     %% When
  274:     Match1 = mod_revproxy:match(Rules, Host1, Path, Method),
  275:     Match2 = mod_revproxy:match(Rules, Host2, Path, Method),
  276: 
  277:     %% Then
  278:     Upstream1 = #upstream{type = uri,
  279:                           protocol = <<"http://">>,
  280:                           host = [<<"localhost:9999">>]},
  281:     #match{upstream = Upstream1,
  282:            remainder = [<<"a">>, <<"b">>, <<"c">>],
  283:            path = '_'} = Match1,
  284: 
  285:     Upstream2 = #upstream{type = uri,
  286:                           protocol = <<"http://">>,
  287:                           host = [<<"localhost:8888">>],
  288:                           path = [whatever, <<>>]},
  289:     #match{upstream = Upstream2,
  290:            remainder = [<<"a">>, <<"b">>, <<"c">>],
  291:            bindings = [{whatever, <<"nonstatic">>}],
  292:            path = []} = Match2.
  293: 
  294: method_match(Config) ->
  295:     %% Given
  296:     Rules = ?config(rules, Config),
  297:     Host = <<"domain.com">>,
  298:     Path = <<"/path/a/b/c">>,
  299:     Method1 = <<"GET">>,
  300:     Method2 = <<"POST">>,
  301: 
  302:     %% When
  303:     Match1 = mod_revproxy:match(Rules, Host, Path, Method1),
  304:     Match2 = mod_revproxy:match(Rules, Host, Path, Method2),
  305: 
  306:     %% Then
  307:     Upstream1 = #upstream{type = host,
  308:                           protocol = <<"http://">>,
  309:                           host = [<<"localhost:1234">>]},
  310:     #match{upstream = Upstream1,
  311:            remainder = [<<"path">>, <<"a">>, <<"b">>, <<"c">>],
  312:            path = '_'} = Match1,
  313: 
  314:     Upstream2 = #upstream{type = uri,
  315:                           protocol = <<"http://">>,
  316:                           host = [<<"localhost:6543">>],
  317:                           path = [<<"detailed_path">>, host, path]},
  318:     #match{upstream = Upstream2,
  319:            remainder = [<<"b">>, <<"c">>],
  320:            bindings = Bindings,
  321:            path = [<<"path">>, path, <<>>]} = Match2,
  322:     <<"domain">> = proplists:get_value(host, Bindings),
  323:     <<"a">> = proplists:get_value(path, Bindings).
  324: 
  325: qs_match(Config) ->
  326:     %% Given
  327:     Rules = ?config(rules, Config),
  328:     Host = <<"dummydomain.com">>,
  329:     QS = <<"login.htm?ts=1231231232222&st=223232&page=32&ap=123442"
  330:             "&whatever=somewordshere">>,
  331:     Path = <<"/a/b/c/", QS/binary>>,
  332:     Method = <<"GET">>,
  333: 
  334:     %% When
  335:     Match = mod_revproxy:match(Rules, Host, Path, Method),
  336: 
  337:     %% Then
  338:     Upstream = #upstream{type = uri,
  339:                          protocol = <<"http://">>,
  340:                          host = [<<"localhost:5678">>],
  341:                          path = [placeholder]},
  342:     #match{upstream = Upstream,
  343:            remainder = [<<"a">>, <<"b">>, <<"c">>, QS],
  344:            bindings = [{placeholder, <<"dummydomain">>}],
  345:            path = '_'} = Match.
  346: 
  347: 
  348: slash_ending_match(Config) ->
  349:     %% Given
  350:     Rules = ?config(rules, Config),
  351:     Host = <<"dummydomain.com">>,
  352:     Path = <<"/a/b/c/">>,
  353:     Method = <<"GET">>,
  354: 
  355:     %% When
  356:     Match = mod_revproxy:match(Rules, Host, Path, Method),
  357: 
  358:     %% Then
  359:     Upstream = #upstream{type = uri,
  360:                          protocol = <<"http://">>,
  361:                          host = [<<"localhost:5678">>],
  362:                          path = [placeholder]},
  363:     #match{upstream = Upstream,
  364:            remainder = [<<"a">>, <<"b">>, <<"c">>, <<>>],
  365:            bindings = [{placeholder, <<"dummydomain">>}],
  366:            path = '_'} = Match.
  367: 
  368: %%--------------------------------------------------------------------
  369: %% Upstream URI generation
  370: %%--------------------------------------------------------------------
  371: upstream_uri(_Config) ->
  372:     %% Given
  373:     Upstream = #upstream{type = uri,
  374:                          protocol = <<"http://">>,
  375:                          host = [<<"localhost:8080">>]},
  376:     Remainder = [<<"def">>, <<"index.html">>],
  377:     Bindings = [{host, <<"domain">>}],
  378:     Path = [<<"host">>, host],
  379:     Match = #match{upstream = Upstream,
  380:                    remainder = Remainder,
  381:                    bindings = Bindings,
  382:                    path = Path},
  383: 
  384:     %% When
  385:     URI = mod_revproxy:upstream_uri(Match),
  386: 
  387:     %% Then
  388:     {"http://localhost:8080", <<"/def/index.html">>} = URI.
  389: 
  390: upstream_host(_Config) ->
  391:     %% Given
  392:     Upstream = #upstream{type = host,
  393:                          protocol = <<"http://">>,
  394:                          host = [<<"localhost:8080">>]},
  395:     Remainder = [<<"def">>, <<"index.html">>],
  396:     Bindings = [],
  397:     Path = [<<"host">>],
  398:     Match = #match{upstream = Upstream,
  399:                    remainder = Remainder,
  400:                    bindings = Bindings,
  401:                    path = Path},
  402: 
  403:     %% When
  404:     URI = mod_revproxy:upstream_uri(Match),
  405: 
  406:     %% Then
  407:     {"http://localhost:8080", <<"/host/def/index.html">>} = URI.
  408: 
  409: upstream_bindings(_Config) ->
  410:     %% Given
  411:     Upstream = #upstream{type = uri,
  412:                          protocol = <<"https://">>,
  413:                          host = [domain, host, <<"localhost:8080">>],
  414:                          path = [<<"host">>, host, <<"domain">>, domain]},
  415:     Remainder = [<<"dir">>, <<"index.html">>],
  416:     Bindings = [{host, <<"test_host">>}, {domain, <<"test_domain">>}],
  417:     Path = '_',
  418:     Match = #match{upstream = Upstream,
  419:                    remainder = Remainder,
  420:                    bindings = Bindings,
  421:                    path = Path},
  422: 
  423:     %% When
  424:     URI = mod_revproxy:upstream_uri(Match),
  425: 
  426:     %% Then
  427:     {"https://test_domain.test_host.localhost:8080",
  428:      <<"/host/test_host/domain/test_domain/dir/index.html">>} = URI.
  429: 
  430: upstream_qs(_Config) ->
  431:     %% Given
  432:     Upstream = #upstream{type = uri,
  433:                          protocol = <<"http://">>,
  434:                          host = [<<"localhost:1234">>],
  435:                          path = [<<"ghi">>]},
  436:     Path = [<<"abc">>],
  437:     QS = <<"login.htm?ts=1231231232222&st=223232&page=32&ap=123442"
  438:             "&whatever=somewordshere">>,
  439:     Remainder = [<<"def">>, QS],
  440:     Match = #match{upstream = Upstream,
  441:                    remainder = Remainder,
  442:                    bindings = [],
  443:                    path = Path},
  444: 
  445:     %% When
  446:     URI = mod_revproxy:upstream_uri(Match),
  447: 
  448:     %% Then
  449:     Resource = <<"/ghi/def/", QS/binary>>,
  450:     {"http://localhost:1234", Resource} = URI.
  451: 
  452: upstream_slash_path(_Config) ->
  453:     %% Given
  454:     Upstream = #upstream{type = host,
  455:                          protocol = <<"http://">>,
  456:                          host = [<<"localhost:1234">>],
  457:                          path = [<<"abc">>]},
  458:     Path = [<<"abc">>, <<"def">>, <<>>],
  459:     Match = #match{upstream = Upstream,
  460:                    remainder = [],
  461:                    bindings = [],
  462:                    path = Path},
  463: 
  464:     %% When
  465:     URI = mod_revproxy:upstream_uri(Match),
  466: 
  467:     %% Then
  468:     {"http://localhost:1234", <<"/abc/abc/def/">>} = URI.
  469: 
  470: upstream_slash_remainder(_Config) ->
  471:     %% Given
  472:     Upstream = #upstream{type = host,
  473:                          protocol = <<"http://">>,
  474:                          host = [<<"localhost:1234">>],
  475:                          path = [<<"abc">>]},
  476:     Path = [<<"abc">>, <<"def">>, <<>>],
  477:     Remainder = [<<"ghi">>, <<"jkl">>, <<>>],
  478:     Match = #match{upstream = Upstream,
  479:                    remainder = Remainder,
  480:                    bindings = [],
  481:                    path = Path},
  482: 
  483:     %% When
  484:     URI = mod_revproxy:upstream_uri(Match),
  485: 
  486:     %% Then
  487:     {"http://localhost:1234", <<"/abc/abc/def/ghi/jkl/">>} = URI.
  488: 
  489: %%--------------------------------------------------------------------
  490: %% Helpers
  491: %%--------------------------------------------------------------------
  492: copy(Src, Dst) ->
  493:     {ok, _} = file:copy(Src, Dst).
  494: 
  495: data(File, Config) ->
  496:     filename:join([?config(data_dir, Config), File]).
  497: 
  498: example_routes() ->
  499:     [{"domain.com", "/abc", "_", "http://localhost:8080/"},
  500:      {"domain.com", get, "http://localhost:1234"},
  501:      {"static.domain.com", get, "http://localhost:9999/"},
  502:      {":host.com", "/path/:path/", "_",
  503:       "http://localhost:6543/detailed_path/:host/:path"},
  504:      {":placeholder.com", get, "http://localhost:5678/:placeholder"},
  505:      {":whatever.domain.com", "/", "_", "http://localhost:8888/:whatever/"}].
  506: 
  507: compiled_example_routes() ->
  508:     [{[<<"com">>, <<"domain">>], [<<"abc">>], '_',
  509:       #upstream{type = uri,
  510:                 protocol = <<"http://">>,
  511:                 host = [<<"localhost:8080">>]}},
  512:      {[<<"com">>, <<"domain">>], '_', <<"GET">>,
  513:       #upstream{type = host,
  514:                 protocol = <<"http://">>,
  515:                 host = [<<"localhost:1234">>]}},
  516:      {[<<"com">>,<<"domain">>,<<"static">>], '_', <<"GET">>,
  517:       #upstream{type = uri,
  518:                 protocol = <<"http://">>,
  519:                 host = [<<"localhost:9999">>]}},
  520:      {[<<"com">>, host], [<<"path">>, path, <<>>], '_',
  521:       #upstream{type = uri,
  522:                 protocol = <<"http://">>,
  523:                 host = [<<"localhost:6543">>],
  524:                 path = [<<"detailed_path">>, host, path]}},
  525:      {[<<"com">>, placeholder], '_', <<"GET">>,
  526:       #upstream{type = uri,
  527:                 protocol = <<"http://">>,
  528:                 host = [<<"localhost:5678">>],
  529:                 path = [placeholder]}},
  530:      {[<<"com">>, <<"domain">>, whatever], [], '_',
  531:       #upstream{type = uri,
  532:                 protocol = <<"http://">>,
  533:                 host = [<<"localhost:8888">>],
  534:                 path = [whatever, <<>>]}}].
  535: 
  536: start_revproxy() ->
  537:     Routes = {routes, [{":domain.com", "/admin", "_",
  538:                         "https://localhost:5678/secret_admin/:domain/"},
  539:                        {":domain.com", "/:path/", get,
  540:                         "http://:domain.localhost:1234/domain/:domain/path/:path/"}]},
  541:     CustomHeaders = [{<<"custom-header-1">>, <<"value">>},
  542:                      {<<"custom-header-2">>, <<"some other value">>}],
  543:     Dispatch = cowboy_router:compile([
  544:                 {'_',
  545:                  [{"/[...]", mod_revproxy, [{custom_headers, CustomHeaders}]}]}
  546:                 ]),
  547:     mod_revproxy:start(nvm, [Routes]),
  548:     cowboy:start_clear(revproxy_listener,
  549:                        [{port, 8080}, {num_acceptors, 20}],
  550:                        #{env => #{dispatch => Dispatch}}).
  551: 
  552: stop_revproxy() ->
  553:     ok = cowboy:stop_listener(revproxy_listener).
  554: 
  555: start_http_upstream() ->
  556:     Dispatch = cowboy_router:compile([
  557:                 {'_', [{"/[...]", revproxy_handler, []}]}
  558:                 ]),
  559:     cowboy:start_clear(http_listener,
  560:                        [{port, 1234}, {num_acceptors, 20}],
  561:                        #{env => #{dispatch => Dispatch}}).
  562: 
  563: start_https_upstream(Config) ->
  564:     Dispatch = cowboy_router:compile([
  565:                 {'_', [{"/[...]", revproxy_handler, []}]}
  566:                 ]),
  567:     Opts = [{port, 5678},
  568:             {keyfile, data("server.key", Config)},
  569:             {certfile, data("server.crt", Config)},
  570:             {num_acceptors, 20}],
  571:     cowboy:start_tls(https_listener,
  572:                      Opts,
  573:                      #{env => #{dispatch => Dispatch}}).
  574: 
  575: stop_upstream(Upstream) ->
  576:     case cowboy:stop_listener(Upstream) of
  577:         ok ->
  578:             ok;
  579:         Other ->
  580:             ct:fail(#{issue => stop_listener_failed,
  581:                       ref => Upstream,
  582:                       reason => Other})
  583:     end.
  584: 
  585: execute_request(Host, Path, Method, Headers, Body) ->
  586:     {ok, Pid} = fusco:start_link(Host, []),
  587:     Response = fusco:request(Pid, Path, Method, Headers, Body, 5000),
  588:     fusco:disconnect(Pid),
  589:     Response.
  590: 
  591: assert_status_code(Result, Code) ->
  592:     case is_status_code(Result, Code) of
  593:         true ->
  594:             ok;
  595:         false ->
  596:             ct:fail(#{issue => assert_status_code,
  597:                       result => Result,
  598:                       expected_code => Code})
  599:     end.
  600: 
  601: is_status_code({ok, {{CodeBin, _}, _, _, _, _}}, Code) ->
  602:     case binary_to_integer(CodeBin) of
  603:         Code -> true;
  604:         _    -> false
  605:     end.
  606: 
  607: does_response_match({ok, {{_, _}, _, Response, _, _}},
  608:                     Host, Path, Method, Body, QS) ->
  609:     ResponseEls = binary:split(Response, <<"\n">>, [global]),
  610:     [RHost,RMethod,RPath,RBody,RQS|_] = ResponseEls,
  611:     PathSegments = binary:split(Path, <<"/">>, [global, trim]),
  612:     RPath = to_formatted_binary(PathSegments),
  613:     RHost = to_formatted_binary(Host),
  614:     RMethod = to_formatted_binary(list_to_binary(Method)),
  615:     RBody = to_formatted_binary(list_to_binary(Body)),
  616:     RQS = to_formatted_binary(QS),
  617:     true.
  618: 
  619: to_formatted_binary(Subject) ->
  620:     iolist_to_binary(io_lib:format("~p", [Subject])).
  621: 
  622: assert_contain_header(Result, Header, Value) ->
  623:     case does_contain_header(Result, Header, Value) of
  624:         true ->
  625:             ok;
  626:         false ->
  627:             ct:fail(#{reason => assert_contain_header,
  628:                       req => Result,
  629:                       header => Header,
  630:                       expected_value => Value})
  631:     end.
  632: 
  633: does_contain_header({ok, {{_, _}, _, Response, _, _}}, Header, Value) ->
  634:     HeaderL = cowboy_bstr:to_lower(Header),
  635:     ValueL = cowboy_bstr:to_lower(Value),
  636:     Match = iolist_to_binary(io_lib:format("~p", [{HeaderL, ValueL}])),
  637:     case binary:match(Response, Match) of
  638:         nomatch -> false;
  639:         _       -> true
  640:     end.
  641: 
  642: %%--------------------------------------------------------------------
  643: %% revproxy handler mock
  644: %%--------------------------------------------------------------------
  645: create_handler() ->
  646:     Owner = self(),
  647:     F = fun() ->
  648:             ok = meck:new(revproxy_handler, [non_strict]),
  649:             ok = meck:expect(revproxy_handler, init, fun handler_init/2),
  650:             ok = meck:expect(revproxy_handler, terminate, fun handler_terminate/3),
  651:             Owner ! ok,
  652:             timer:sleep(infinity)
  653:     end,
  654:     Pid = spawn(F),
  655:     receive
  656:         ok ->
  657:             {ok, Pid}
  658:     after 5000 ->
  659:             {error, timeout}
  660:     end.
  661: 
  662: remove_handler(Config) ->
  663:     meck:unload(revproxy_handler),
  664:     exit(?config(meck_pid, Config), kill).
  665: 
  666: handler_init(Req, _Opts) ->
  667:     handler_handle(Req, no_state).
  668: 
  669: handler_handle(Req, State) ->
  670:     Host = cowboy_req:host(Req),
  671:     PathInfo = cowboy_req:path_info(Req),
  672:     Method = cowboy_req:method(Req),
  673:     Headers = maps:to_list(cowboy_req:headers(Req)),
  674:     ContentType = [{<<"content-type">>, <<"text/plain">>}],
  675:     QS = cowboy_req:qs(Req),
  676:     {Body, Req2} = case cowboy_req:has_body(Req) of
  677:         false ->
  678:             {<<>>, Req};
  679:         true ->
  680:             {ok, Body1, Req1} = cowboy_req:read_body(Req),
  681:             {Body1, Req1}
  682:     end,
  683:     Response = io_lib:format("~p~n~p~n~p~n~p~n~p~n~p",
  684:                              [Host, Method, PathInfo, Body, QS, Headers]),
  685:     Req3 = cowboy_req:reply(200, maps:from_list(ContentType), Response, Req2),
  686:     {ok, Req3, State}.
  687: 
  688: handler_terminate(_Reason, _Req, _State) ->
  689:     ok.
  690: 
  691: mock_getaddr("qwerty.localhost",inet) ->
  692:     {ok, {127,0,0,1}};
  693: mock_getaddr("qwerty.localhost",inet6) ->
  694:     {ok,{0,0,0,0,0,0,0,1}};
  695: mock_getaddr(Host, Opt) ->
  696:     meck:passthrough([Host, Opt]).
  697: 
  698: 
  699: mock_getaddrs_tm("qwerty.localhost",inet,_) ->
  700:     {ok, [{127,0,0,1}]};
  701: mock_getaddrs_tm("qwerty.localhost",inet6,_) ->
  702:     {ok,[{0,0,0,0,0,0,0,1}]};
  703: mock_getaddrs_tm(Host, Opt, Timer) ->
  704:     meck:passthrough([Host, Opt, Timer]).