diff --git a/src/hackney_url.erl b/src/hackney_url.erl index 5d93d460..9a82d4a8 100644 --- a/src/hackney_url.erl +++ b/src/hackney_url.erl @@ -52,22 +52,24 @@ parse_url(URL) -> parse_url(URL, #hackney_url{transport=hackney_tcp, scheme=http}). parse_url(URL, S) -> - {URL1, Fragment} = parse_fragment(URL), - case binary:split(URL1, <<"/">>) of - [URL1] -> - parse_addr1(URL1, S#hackney_url{raw_path = raw_fragment(Fragment), + {URL1, Fragment} = cut_fragment(URL), + {URL2, Query} = cut_query(URL1), + RawPath = << (raw_query(Query))/binary, (raw_fragment(Fragment))/binary >>, + case binary:split(URL2, <<"/">>) of + [URL2] -> + parse_addr1(URL2, S#hackney_url{raw_path = RawPath, path = <<>>, + qs = Query, fragment = Fragment}); [Addr] -> Path = <<"/">>, - parse_addr1(Addr, S#hackney_url{raw_path = << Path/binary, (raw_fragment(Fragment))/binary >>, + parse_addr1(Addr, S#hackney_url{raw_path = << Path/binary, RawPath/binary >>, path = Path, + qs = Query, fragment = Fragment}); [Addr, Path] -> - RawPath = <<"/", Path/binary, (raw_fragment(Fragment))/binary >>, - {Path1, Query} = parse_path( << "/", Path/binary >>), - parse_addr(Addr, S#hackney_url{raw_path = RawPath, - path = Path1, + parse_addr(Addr, S#hackney_url{raw_path = <<"/", Path/binary, RawPath/binary >>, + path = <<"/", Path/binary >>, qs = Query, fragment = Fragment}) end. @@ -76,6 +78,8 @@ parse_url(URL, S) -> raw_fragment(<<"">>) -> <<"">>; raw_fragment(Fragment) -> <<"#", Fragment/binary>>. +raw_query(<<>>) -> <<>>; +raw_query(Query) -> <<"?", Query/binary>>. property(transport, URL) -> URL#hackney_url.transport; property(scheme, URL) -> URL#hackney_url.scheme; @@ -261,7 +265,7 @@ parse_netloc(Netloc, #hackney_url{transport=Transport}=S) -> end. -parse_path(Path) -> +cut_query(Path) -> case binary:split(Path, <<"?">>) of [_Path] -> {Path, <<>>}; @@ -269,7 +273,7 @@ parse_path(Path) -> {Path1, Query} end. -parse_fragment(S) -> +cut_fragment(S) -> case binary:split(S, <<"#">>) of [_S] -> {S, <<>>}; diff --git a/test/hackney_integration_tests.erl b/test/hackney_integration_tests.erl index cc5a25b8..8b9e242d 100644 --- a/test/hackney_integration_tests.erl +++ b/test/hackney_integration_tests.erl @@ -4,27 +4,27 @@ all_tests() -> - [get_request(), - request_with_body(), - head_request(), - no_content_response(), - not_modified_response(), - basic_auth_request_failed(), - basic_auth_request(), - basic_auth_url_request(), - set_cookie_request(), - send_cookies_request(), - absolute_redirect_request_no_follow(), - absolute_redirect_request_follow(), -% relative_redirect_request_no_follow(), - relative_redirect_request_follow(), - test_duplicate_headers(), - test_custom_host_headers(), - async_request(), - async_head_request(), - async_no_content_request(), - test_frees_manager_ets_when_body_is_in_client(), - test_frees_manager_ets_when_body_is_in_response()]. + [fun get_request/0, + fun request_with_body/0, + fun head_request/0, + fun no_content_response/0, + fun not_modified_response/0, + fun basic_auth_request_failed/0, + fun basic_auth_request/0, + fun basic_auth_url_request/0, + fun set_cookie_request/0, + fun send_cookies_request/0, + fun absolute_redirect_request_no_follow/0, + fun absolute_redirect_request_follow/0, + % fun relative_redirect_request_no_follow/0, + fun relative_redirect_request_follow/0, + fun test_duplicate_headers/0, + fun test_custom_host_headers/0, + fun async_request/0, + fun async_head_request/0, + fun async_no_content_request/0, + fun test_frees_manager_ets_when_body_is_in_client/0, + fun test_frees_manager_ets_when_body_is_in_response/0]. %%all_tests() -> %% case has_unix_socket() of @@ -38,7 +38,7 @@ http_requests_test_() -> fun start/0, fun stop/1, fun(ok) -> - {inparallel, all_tests()} + {inorder, all_tests()} end}. start() -> @@ -50,52 +50,52 @@ stop(ok) -> ok. get_request() -> URL = <<"http://localhost:8000/get">>, {ok, StatusCode, _, _} = hackney:request(get, URL, [], <<>>, []), - ?_assertEqual(200, StatusCode). + ?assertEqual(200, StatusCode). request_with_body() -> URL = <<"http://localhost:8000/robots.txt">>, ExpectedBody = <<"User-agent: *\nDisallow: /deny\n">>, {ok, 200, _, Body} = hackney:request(get, URL, [], <<>>, [{with_body, true}]), - ?_assertEqual(ExpectedBody, Body). + ?assertEqual(ExpectedBody, Body). head_request() -> URL = <<"http://localhost:8000/get">>, {ok, StatusCode, _} = hackney:request(head, URL, [], <<>>, []), - ?_assertEqual(200, StatusCode). + ?assertEqual(200, StatusCode). no_content_response() -> URL = <<"http://localhost:8000/status/204">>, {ok, StatusCode, _, _} = hackney:request(get, URL, [], <<>>, []), - ?_assertEqual(204, StatusCode). + ?assertEqual(204, StatusCode). not_modified_response() -> URL = <<"http://localhost:8000/status/304">>, {ok, StatusCode, _, _} = hackney:request(get, URL, [], <<>>, []), - ?_assertEqual(304, StatusCode). + ?assertEqual(304, StatusCode). basic_auth_request() -> URL = <<"http://localhost:8000/basic-auth/username/password">>, Options = [{basic_auth, {<<"username">>, <<"password">>}}], {ok, StatusCode, _, _} = hackney:request(get, URL, [], <<>>, Options), - ?_assertEqual(200, StatusCode). + ?assertEqual(200, StatusCode). basic_auth_url_request() -> URL = <<"http://username:pass%26word@localhost:8000/basic-auth/username/pass%26word">>, {ok, StatusCode, _, _} = hackney:request(get, URL, [], <<>>, []), - ?_assertEqual(200, StatusCode). + ?assertEqual(200, StatusCode). basic_auth_request_failed() -> URL = <<"http://localhost:8000/basic-auth/username/password">>, Options = [{basic_auth, {<<"wrong">>, <<"auth">>}}], {ok, StatusCode, _, _} = hackney:request(get, URL, [], <<>>, Options), - ?_assertEqual(401, StatusCode). + ?assertEqual(401, StatusCode). set_cookie_request() -> URL = <<"http://localhost:8000/cookies/set?k1=v1">>, {ok, _, Headers, _} = hackney:request(get, URL, [], <<>>, []), Cookies = hackney:cookies(Headers), ExpectedCookies = [{<<"k1">>, [{<<"k1">>,<<"v1">>},{<<"Path">>,<<"/">>}]}], - ?_assertEqual(ExpectedCookies, Cookies). + ?assertEqual(ExpectedCookies, Cookies). send_cookies_request() -> URL = <<"http://localhost:8000/cookies">>, @@ -103,63 +103,63 @@ send_cookies_request() -> {ok, _, _, Client} = hackney:request(get, URL, [], <<>>, Options), {ok, Body} = hackney:body(Client), Match = re:run(Body, <<".*\"SESSION\".*\"123\".*">>), - ?_assertMatch({match, _}, Match). + ?assertMatch({match, _}, Match). absolute_redirect_request_no_follow() -> URL = <<"http://localhost:8000/redirect-to?url=http://localhost:8000/get">>, Options = [{follow_redirect, false}], {ok, StatusCode, _, Client} = hackney:request(get, URL, [], <<>>, Options), Location = hackney:location(Client), - [?_assertEqual(302, StatusCode), - ?_assertEqual(<<"http://localhost:8000/get">>, Location)]. + ?assertEqual(302, StatusCode), + ?assertEqual(<<"http://localhost:8000/get">>, Location). absolute_redirect_request_follow() -> URL = <<"http://localhost:8000/redirect-to?url=http://localhost:8000/get">>, Options = [{follow_redirect, true}], {ok, StatusCode, _, Client} = hackney:request(get, URL, [], <<>>, Options), Location = hackney:location(Client), - [?_assertEqual(200, StatusCode), - ?_assertEqual(<<"http://localhost:8000/get">>, Location)]. + ?assertEqual(200, StatusCode), + ?assertEqual(<<"http://localhost:8000/get">>, Location). %relative_redirect_request_no_follow() -> % URL = <<"http://localhost:8000/relative-redirect/1">>, % Options = [{follow_redirect, false}], % {ok, StatusCode, _, Client} = hackney:request(get, URL, [], <<>>, Options), % Location = hackney:location(Client), -% [?_assertEqual(302, StatusCode), -% ?_assertEqual(Location, <<"/get">>)]. +% [?assertEqual(302, StatusCode), +% ?assertEqual(Location, <<"/get">>)]. relative_redirect_request_follow() -> URL = <<"http://localhost:8000/redirect-to?url=/get">>, Options = [{follow_redirect, true}], {ok, StatusCode, _, Client} = hackney:request(get, URL, [], <<>>, Options), Location = hackney:location(Client), - [?_assertEqual(200, StatusCode), - ?_assertEqual(Location, <<"http://localhost:8000/get">>)]. + ?assertEqual(200, StatusCode), + ?assertEqual(Location, <<"http://localhost:8000/get">>). async_request() -> URL = <<"http://localhost:8000/get">>, Options = [async], {ok, ClientRef} = hackney:get(URL, [], <<>>, Options), {StatusCode, Keys} = receive_response(ClientRef), - [?_assertEqual(200, StatusCode), - ?_assertEqual([body, headers, status], Keys)]. + ?assertEqual(200, StatusCode), + ?assertEqual([body, headers, status], Keys). async_head_request() -> URL = <<"http://localhost:8000/get">>, Options = [async], {ok, ClientRef} = hackney:head(URL, [], <<>>, Options), {StatusCode, Keys} = receive_response(ClientRef), - [?_assertEqual(200, StatusCode), - ?_assertEqual([headers, status], Keys)]. + ?assertEqual(200, StatusCode), + ?assertEqual([headers, status], Keys). async_no_content_request() -> URL = <<"http://localhost:8000/status/204">>, Options = [async], {ok, ClientRef} = hackney:get(URL, [], <<>>, Options), {StatusCode, Keys} = receive_response(ClientRef), - [?_assertEqual(204, StatusCode), - ?_assertEqual([headers, status], Keys)]. + ?assertEqual(204, StatusCode), + ?assertEqual([headers, status], Keys). test_duplicate_headers() -> URL = <<"http://localhost:8000/post">>, @@ -169,7 +169,7 @@ test_duplicate_headers() -> {ok, 200, _H, JsonBody} = hackney:post(URL, Headers, Body, Options), Obj = jsone:decode(JsonBody, [{object_format, proplist}]), ReqHeaders = proplists:get_value(<<"headers">>, Obj), - ?_assertEqual(<<"application/json">>, proplists:get_value(<<"Content-Type">>, ReqHeaders)). + ?assertEqual(<<"application/json">>, proplists:get_value(<<"Content-Type">>, ReqHeaders)). test_custom_host_headers() -> URL = <<"http://localhost:8000/get">>, @@ -178,7 +178,7 @@ test_custom_host_headers() -> {ok, 200, _H, JsonBody} = hackney:get(URL, Headers, <<>>, Options), Obj = jsone:decode(JsonBody, [{object_format, proplist}]), ReqHeaders = proplists:get_value(<<"headers">>, Obj), - ?_assertEqual(<<"myhost.com">>, proplists:get_value(<<"Host">>, ReqHeaders)). + ?assertEqual(<<"myhost.com">>, proplists:get_value(<<"Host">>, ReqHeaders)). test_frees_manager_ets_when_body_is_in_client() -> URL = <<"http://localhost:8000/get">>, @@ -186,9 +186,10 @@ test_frees_manager_ets_when_body_is_in_client() -> {ok, 200, _, Client} = hackney:get(URL), DuringCount = ets:info(hackney_manager_refs, size), {ok, _unusedBody} = hackney:body(Client), + timer:sleep(10), AfterCount = ets:info(hackney_manager_refs, size), - ?_assertEqual(DuringCount, BeforeCount + 1), - ?_assertEqual(BeforeCount, AfterCount). + ?assertEqual(DuringCount, BeforeCount + 1), + ?assertEqual(BeforeCount, AfterCount). test_frees_manager_ets_when_body_is_in_response() -> URL = <<"http://localhost:8000/get">>, @@ -196,14 +197,15 @@ test_frees_manager_ets_when_body_is_in_response() -> Options = [with_body], BeforeCount = ets:info(hackney_manager_refs, size), {ok, 200, _, _} = hackney:get(URL, Headers, <<>>, Options), + timer:sleep(10), AfterCount = ets:info(hackney_manager_refs, size), - ?_assertEqual(BeforeCount, AfterCount). + ?assertEqual(BeforeCount, AfterCount). %%local_socket_request() -> %% URL = <<"http+unix://httpbin.sock/get">>, %% {ok, StatusCode, _, _} = hackney:request(get, URL, [], <<>>, []), -%% ?_assertEqual(200, StatusCode). +%% ?assertEqual(200, StatusCode). %% Helpers diff --git a/test/hackney_url_tests.erl b/test/hackney_url_tests.erl index a0945140..405de45a 100644 --- a/test/hackney_url_tests.erl +++ b/test/hackney_url_tests.erl @@ -286,6 +286,32 @@ parse_url_test_() -> port = 80, user = <<"">>, password = <<"">>} + }, + {<<"https://example.com/foo/bar">>, + #hackney_url{transport = hackney_ssl, + scheme = https, + netloc = <<"example.com">>, + raw_path = <<"/foo/bar">>, + path = <<"/foo/bar">>, + qs = <<>>, + fragment = <<>>, + host = "example.com", + port = 443, + user = <<>>, + password = <<>>} + }, + {<<"http://127.0.0.1?@127.2.2.2/">>, + #hackney_url{transport = hackney_tcp, + scheme = http, + netloc = <<"127.0.0.1">>, + raw_path = <<"?@127.2.2.2/">>, + path = <<>>, + qs = <<"@127.2.2.2/">>, + fragment = <<>>, + host = "127.0.0.1", + port = 80, + user = <<>>, + password = <<>>} } ], [{V, fun() -> R = hackney_url:parse_url(V) end} || {V, R} <- Tests].