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]).