%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 2004-2010. All Rights Reserved.
%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
%% compliance with the License. You should have received a copy of the
%% Erlang Public License along with this software. If not, it can be
%% retrieved online at http://www.erlang.org/.
%%
%% Software distributed under the License is distributed on an "AS IS"
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%% the License for the specific language governing rights and limitations
%% under the License.
%%
%% %CopyrightEnd%
%%
%%
%%
%% ts:run(inets, httpc_SUITE, [batch]).
%%
-module(httpc_SUITE).
-include("test_server.hrl").
-include("test_server_line.hrl").
-include_lib("kernel/include/file.hrl").
%% Note: This directive should only be used in test suites.
-compile(export_all).
%% Test server specific exports
-define(PROXY_URL, "http://www.erlang.org").
-define(PROXY, "www-proxy.ericsson.se").
-define(PROXY_PORT, 8080).
-define(IP_PORT, 8998).
-define(SSL_PORT, 8999).
-define(NOT_IN_USE_PORT, 8997).
-define(LOCAL_HOST, {127,0,0,1}).
-define(IPV6_LOCAL_HOST, "0:0:0:0:0:0:0:1").
-define(URL_START, "http://localhost:").
-define(SSL_URL_START, "https://localhost:").
-define(CR, $\r).
-define(LF, $\n).
-define(HTTP_MAX_HEADER_SIZE, 10240).
%%--------------------------------------------------------------------
%% all(Arg) -> [Doc] | [Case] | {skip, Comment}
%% Arg - doc | suite
%% Doc - string()
%% Case - atom()
%% Name of a test case function.
%% Comment - string()
%% Description: Returns documentation/test cases in this test suite
%% or a skip tuple if the platform is not supported.
%%--------------------------------------------------------------------
all(doc) ->
["Test the http client in the intes application."];
all(suite) ->
[
proxy_options,
proxy_head,
proxy_get,
proxy_trace,
proxy_post,
proxy_put,
proxy_delete,
proxy_auth,
proxy_headers,
proxy_emulate_lower_versions,
http_options,
http_head,
http_get,
http_post,
http_dummy_pipe,
http_inets_pipe,
http_trace,
http_async,
http_save_to_file,
http_save_to_file_async,
http_headers,
http_headers_dummy,
http_bad_response,
ssl_head,
ssl_get,
ssl_trace,
http_redirect,
http_redirect_loop,
http_internal_server_error,
http_userinfo,
http_cookie,
http_server_does_not_exist,
http_invalid_http,
http_emulate_lower_versions,
http_relaxed,
page_does_not_exist,
proxy_page_does_not_exist,
proxy_https_not_supported,
http_stream,
http_stream_once,
proxy_stream,
parse_url,
options,
ipv6,
headers_as_is,
tickets
].
%%--------------------------------------------------------------------
%% Function: init_per_suite(Config) -> Config
%% Config - [tuple()]
%% A list of key/value pairs, holding the test case configuration.
%% Description: Initiation before the whole suite
%%
%% Note: This function is free to add any key/value pairs to the Config
%% variable, but should NOT alter/remove any existing entries.
%%--------------------------------------------------------------------
init_per_suite(Config) ->
PrivDir = ?config(priv_dir, Config),
DataDir = ?config(data_dir, Config),
ServerRoot = filename:join(PrivDir, "server_root"),
DocRoot = filename:join(ServerRoot, "htdocs"),
IpConfFile = integer_to_list(?IP_PORT) ++ ".conf",
SslConfFile = integer_to_list(?SSL_PORT) ++ ".conf",
setup_server_dirs(ServerRoot, DocRoot, DataDir),
create_config(IpConfFile, ip_comm, ?IP_PORT, PrivDir, ServerRoot,
DocRoot, DataDir),
create_config(SslConfFile, ssl, ?SSL_PORT, PrivDir, ServerRoot,
DocRoot, DataDir),
Cgi = case test_server:os_type() of
{win32, _} ->
filename:join([ServerRoot, "cgi-bin", "cgi_echo.exe"]);
_ ->
filename:join([ServerRoot, "cgi-bin", "cgi_echo"])
end,
{ok, FileInfo} = file:read_file_info(Cgi),
ok = file:write_file_info(Cgi, FileInfo#file_info{mode = 8#00755}),
[{server_root, ServerRoot},
{doc_root, DocRoot},
{local_port, ?IP_PORT},
{local_ssl_port, ?SSL_PORT} | Config].
%%--------------------------------------------------------------------
%% Function: end_per_suite(Config) -> _
%% Config - [tuple()]
%% A list of key/value pairs, holding the test case configuration.
%% Description: Cleanup after the whole suite
%%--------------------------------------------------------------------
end_per_suite(Config) ->
PrivDir = ?config(priv_dir, Config),
inets_test_lib:del_dirs(PrivDir),
application:stop(inets),
application:stop(ssl),
ok.
%%--------------------------------------------------------------------
%% Function: init_per_testcase(Case, Config) -> Config
%% Case - atom()
%% Name of the test case that is about to be run.
%% Config - [tuple()]
%% A list of key/value pairs, holding the test case configuration.
%%
%% Description: Initiation before each test case
%%
%% Note: This function is free to add any key/value pairs to the Config
%% variable, but should NOT alter/remove any existing entries.
%%--------------------------------------------------------------------
init_per_testcase(otp_8154_1 = Case, Config) ->
init_per_testcase(Case, 5, Config);
init_per_testcase(Case, Config) ->
init_per_testcase(Case, 2, Config).
init_per_testcase(Case, Timeout, Config) ->
io:format(user, "~n~n*** INIT ~w:~w[~w] ***~n~n",
[?MODULE, Timeout, Case]),
PrivDir = ?config(priv_dir, Config),
application:stop(inets),
Dog = test_server:timetrap(inets_test_lib:minutes(Timeout)),
TmpConfig = lists:keydelete(watchdog, 1, Config),
IpConfFile = integer_to_list(?IP_PORT) ++ ".conf",
SslConfFile = integer_to_list(?SSL_PORT) ++ ".conf",
NewConfig =
case atom_to_list(Case) of
"ssl" ++ _ ->
application:stop(ssl),
TmpConfig2 =
lists:keydelete(local_ssl_server, 1, TmpConfig),
%% Will start inets
Server =
inets_test_lib:start_http_server(
filename:join(PrivDir, SslConfFile)),
[{watchdog, Dog}, {local_ssl_server, Server} | TmpConfig2];
"proxy" ++ Rest ->
case Rest of
"_https_not_supported" ->
inets:start(),
case (catch application:start(ssl)) of
ok ->
[{watchdog, Dog} | TmpConfig];
_ ->
[{skip,
"SSL does not seem to be supported"}
| TmpConfig]
end;
_ ->
case is_proxy_available(?PROXY, ?PROXY_PORT) of
true ->
inets:start(),
[{watchdog, Dog} | TmpConfig];
false ->
[{skip, "Failed to contact proxy"} |
TmpConfig]
end
end;
_ ->
TmpConfig2 = lists:keydelete(local_server, 1, TmpConfig),
Server =
%% Will start inets
inets_test_lib:start_http_server(
filename:join(PrivDir, IpConfFile)),
[{watchdog, Dog}, {local_server, Server} | TmpConfig2]
end,
http:set_options([{proxy, {{?PROXY, ?PROXY_PORT},
["localhost", ?IPV6_LOCAL_HOST]}}]),
inets:enable_trace(max, io, httpc),
%% inets:enable_trace(max, io, all),
%% snmp:set_trace([gen_tcp, inet_tcp, prim_inet]),
NewConfig.
%%--------------------------------------------------------------------
%% Function: end_per_testcase(Case, Config) -> _
%% Case - atom()
%% Name of the test case that is about to be run.
%% Config - [tuple()]
%% A list of key/value pairs, holding the test case configuration.
%% Description: Cleanup after each test case
%%--------------------------------------------------------------------
end_per_testcase(http_save_to_file, Config) ->
PrivDir = ?config(priv_dir, Config),
FullPath = filename:join(PrivDir, "dummy.html"),
file:delete(FullPath),
finish(Config);
end_per_testcase(_, Config) ->
finish(Config).
finish(Config) ->
Dog = ?config(watchdog, Config),
case Dog of
undefined ->
ok;
_ ->
test_server:timetrap_cancel(Dog)
end.
%%-------------------------------------------------------------------------
%% Test cases starts here.
%%-------------------------------------------------------------------------
tickets(doc) ->
["."];
tickets(suite) ->
[
hexed_query_otp_6191,
empty_body_otp_6243,
empty_response_header_otp_6830,
transfer_encoding_otp_6807,
proxy_not_modified_otp_6821,
no_content_204_otp_6982,
missing_CR_otp_7304,
otp_7883,
otp_8154,
otp_8106,
otp_8056,
otp_8352,
otp_8371
].
%%-------------------------------------------------------------------------
http_options(doc) ->
["Test http options request against local server."];
http_options(suite) ->
[];
http_options(Config) when is_list(Config) ->
{skip, "Not supported by httpd"}.
http_head(doc) ->
["Test http head request against local server."];
http_head(suite) ->
[];
http_head(Config) when is_list(Config) ->
case ?config(local_server, Config) of
ok ->
Port = ?config(local_port, Config),
URL = ?URL_START ++ integer_to_list(Port) ++ "/dummy.html",
case http:request(head, {URL, []}, [], []) of
{ok, {{_,200,_}, [_ | _], []}} ->
ok;
{ok, WrongReply} ->
tsf({wrong_reply, WrongReply});
Error ->
tsf({failed, Error})
end;
_ ->
{skip, "Failed to start local http-server"}
end.
%%-------------------------------------------------------------------------
http_get(doc) ->
["Test http get request against local server"];
http_get(suite) ->
[];
http_get(Config) when is_list(Config) ->
tsp("http_get -> entry with"
"~n Config: ~p", [Config]),
case ?config(local_server, Config) of
ok ->
tsp("local-server running"),
Method = get,
Port = ?config(local_port, Config),
URL = ?URL_START ++ integer_to_list(Port) ++ "/dummy.html",
Request = {URL, []},
Timeout = timer:seconds(1),
ConnTimeout = Timeout + timer:seconds(1),
HttpOptions1 = [{timeout, Timeout}, {connect_timeout, ConnTimeout}],
Options1 = [],
Body =
case http:request(Method, Request, HttpOptions1, Options1) of
{ok, {{_,200,_}, [_ | _], ReplyBody = [_ | _]}} ->
ReplyBody;
{ok, UnexpectedReply1} ->
tsf({unexpected_reply, UnexpectedReply1});
{error, _} = Error1 ->
tsf({bad_reply, Error1})
end,
%% eqvivivalent to http:request(get, {URL, []}, [], []),
inets_test_lib:check_body(Body),
HttpOptions2 = [],
Options2 = [{body_format, binary}],
case http:request(Method, Request, HttpOptions2, Options2) of
{ok, {{_,200,_}, [_ | _], Bin}} when is_binary(Bin) ->
ok;
{ok, {{_,200,_}, [_ | _], BadBin}} ->
tsf({body_format_not_binary, BadBin});
{ok, UnexpectedReply2} ->
tsf({unexpected_reply, UnexpectedReply2});
{error, _} = Error2 ->
tsf({bad_reply, Error2})
end;
_ ->
{skip, "Failed to start local http-server"}
end.
%%-------------------------------------------------------------------------
http_post(doc) ->
["Test http post request against local server. We do in this case"
" only care about the client side of the the post. The server"
" script will not actually use the post data."];
http_post(suite) ->
[];
http_post(Config) when is_list(Config) ->
case ?config(local_server, Config) of
ok ->
Port = ?config(local_port, Config),
URL = case test_server:os_type() of
{win32, _} ->
?URL_START ++ integer_to_list(Port) ++
"/cgi-bin/cgi_echo.exe";
_ ->
?URL_START ++ integer_to_list(Port) ++
"/cgi-bin/cgi_echo"
end,
%% Cgi-script expects the body length to be 100
Body = lists:duplicate(100, "1"),
{ok, {{_,200,_}, [_ | _], [_ | _]}} =
http:request(post, {URL, [{"expect","100-continue"}],
"text/plain", Body}, [], []),
{ok, {{_,504,_}, [_ | _], []}} =
http:request(post, {URL, [{"expect","100-continue"}],
"text/plain", "foobar"}, [], []);
_ ->
{skip, "Failed to start local http-server"}
end.
%%-------------------------------------------------------------------------
http_emulate_lower_versions(doc) ->
["Perform request as 0.9 and 1.0 clients."];
http_emulate_lower_versions(suite) ->
[];
http_emulate_lower_versions(Config) when is_list(Config) ->
case ?config(local_server, Config) of
ok ->
Port = ?config(local_port, Config),
URL = ?URL_START ++ integer_to_list(Port) ++ "/dummy.html",
{ok, Body0} =
http:request(get, {URL, []}, [{version, "HTTP/0.9"}], []),
inets_test_lib:check_body(Body0),
{ok, {{"HTTP/1.0", 200, _}, [_ | _], Body1 = [_ | _]}} =
http:request(get, {URL, []}, [{version, "HTTP/1.0"}], []),
inets_test_lib:check_body(Body1),
{ok, {{"HTTP/1.1", 200, _}, [_ | _], Body2 = [_ | _]}} =
http:request(get, {URL, []}, [{version, "HTTP/1.1"}], []),
inets_test_lib:check_body(Body2);
_->
{skip, "Failed to start local http-server"}
end.
%%-------------------------------------------------------------------------
http_relaxed(doc) ->
["Test relaxed mode"];
http_relaxed(suite) ->
[];
http_relaxed(Config) when is_list(Config) ->
ok = http:set_options([{ipv6, disabled}]), % also test the old option
%% ok = http:set_options([{ipfamily, inet}]),
{DummyServerPid, Port} = dummy_server(self(), ipv4),
URL = ?URL_START ++ integer_to_list(Port) ++
"/missing_reason_phrase.html",
{error, Reason} =
http:request(get, {URL, []}, [{relaxed, false}], []),
test_server:format("Not relaxed: ~p~n", [Reason]),
{ok, {{_, 200, _}, [_ | _], [_ | _]}} =
http:request(get, {URL, []}, [{relaxed, true}], []),
DummyServerPid ! stop,
ok = http:set_options([{ipv6, enabled}]),
%% ok = http:set_options([{ipfamily, inet6fb4}]), % ********** ipfamily = inet6 *************
ok.
%%-------------------------------------------------------------------------
http_dummy_pipe(doc) ->
["Test pipelining code."];
http_dummy_pipe(suite) ->
[];
http_dummy_pipe(Config) when is_list(Config) ->
ok = http:set_options([{ipfamily, inet}]),
{DummyServerPid, Port} = dummy_server(self(), ipv4),
URL = ?URL_START ++ integer_to_list(Port) ++ "/foobar.html",
test_pipeline(URL),
DummyServerPid ! stop,
ok = http:set_options([{ipfamily, inet6fb4}]), % ********** ipfamily = inet6 *************
ok.
http_inets_pipe(doc) ->
["Test pipelining code."];
http_inets_pipe(suite) ->
[];
http_inets_pipe(Config) when is_list(Config) ->
case ?config(local_server, Config) of
ok ->
Port = ?config(local_port, Config),
URL = ?URL_START ++ integer_to_list(Port) ++ "/dummy.html",
test_pipeline(URL);
_ ->
{skip, "Failed to start local http-server"}
end.
test_pipeline(URL) ->
p("test_pipeline -> entry with"
"~n URL: ~p", [URL]),
http:set_options([{pipeline_timeout, 50000}]),
p("test_pipeline -> issue (async) request 1"),
{ok, RequestId1} =
http:request(get, {URL, []}, [], [{sync, false}]),
test_server:format("RequestId1: ~p~n", [RequestId1]),
p("test_pipeline -> RequestId1: ~p", [RequestId1]),
%% Make sure pipeline is initiated
p("test_pipeline -> sleep some", []),
test_server:sleep(4000),
p("test_pipeline -> issue (async) request 2"),
{ok, RequestId2} =
http:request(get, {URL, []}, [], [{sync, false}]),
tsp("RequestId2: ~p", [RequestId2]),
p("test_pipeline -> RequestId2: ~p", [RequestId2]),
p("test_pipeline -> issue (sync) request 3"),
{ok, {{_,200,_}, [_ | _], [_ | _]}} =
http:request(get, {URL, []}, [], []),
p("test_pipeline -> expect reply for (async) request 1 or 2"),
receive
{http, {RequestId1, {{_, 200, _}, _, _}}} ->
p("test_pipeline -> received reply for (async) request 1 - now wait for 2"),
receive
{http, {RequestId2, {{_, 200, _}, _, _}}} ->
p("test_pipeline -> received reply for (async) request 2"),
ok;
{http, Msg1} ->
test_server:fail(Msg1)
end;
{http, {RequestId2, {{_, 200, _}, _, _}}} ->
io:format("test_pipeline -> received reply for (async) request 2 - now wait for 1"),
receive
{http, {RequestId1, {{_, 200, _}, _, _}}} ->
io:format("test_pipeline -> received reply for (async) request 1"),
ok;
{http, Msg2} ->
test_server:fail(Msg2)
end;
{http, Msg3} ->
test_server:fail(Msg3)
after 60000 ->
receive Any1 ->
tsp("received crap after timeout: ~n ~p", [Any1]),
test_server:fail({error, {timeout, Any1}})
end
end,
p("test_pipeline -> sleep some"),
test_server:sleep(4000),
p("test_pipeline -> issue (async) request 4"),
{ok, RequestId3} =
http:request(get, {URL, []}, [], [{sync, false}]),
tsp("RequestId3: ~p", [RequestId3]),
p("test_pipeline -> RequestId3: ~p", [RequestId3]),
p("test_pipeline -> issue (async) request 5"),
{ok, RequestId4} =
http:request(get, {URL, []}, [], [{sync, false}]),
tsp("RequestId4: ~p~n", [RequestId4]),
p("test_pipeline -> RequestId4: ~p", [RequestId4]),
p("test_pipeline -> cancel (async) request 4"),
ok = http:cancel_request(RequestId3),
p("test_pipeline -> expect *no* reply for cancelled (async) request 4 (for 3 secs)"),
receive
{http, {RequestId3, _}} ->
test_server:fail(http_cancel_request_failed)
after 3000 ->
ok
end,
p("test_pipeline -> expect reply for (async) request 4"),
Body =
receive
{http, {RequestId4, {{_, 200, _}, _, BinBody4}}} = Res ->
p("test_pipeline -> received reply for (async) request 5"),
tsp("Receive : ~p", [Res]),
BinBody4;
{http, Msg4} ->
test_server:fail(Msg4)
after 60000 ->
receive Any2 ->
tsp("received crap after timeout: ~n ~p", [Any2]),
test_server:fail({error, {timeout, Any2}})
end
end,
p("test_pipeline -> check reply for (async) request 5"),
inets_test_lib:check_body(binary_to_list(Body)),
p("test_pipeline -> ensure no unexpected incomming"),
receive
{http, Any} ->
test_server:fail({unexpected_message, Any})
after 500 ->
ok
end,
p("test_pipeline -> done"),
ok.
%%-------------------------------------------------------------------------
http_trace(doc) ->
["Perform a TRACE request that goes through a proxy."];
http_trace(suite) ->
[];
http_trace(Config) when is_list(Config) ->
case ?config(local_server, Config) of
ok ->
Port = ?config(local_port, Config),
URL = ?URL_START ++ integer_to_list(Port) ++ "/dummy.html",
case http:request(trace, {URL, []}, [], []) of
{ok, {{_,200,_}, [_ | _], "TRACE /dummy.html" ++ _}} ->
ok;
{ok, {{_,200,_}, [_ | _], WrongBody}} ->
test_server:fail({wrong_body, WrongBody});
{ok, WrongReply} ->
test_server:fail({wrong_reply, WrongReply});
Error ->
test_server:fail({failed, Error})
end;
_ ->
{skip, "Failed to start local http-server"}
end.
%%-------------------------------------------------------------------------
http_async(doc) ->
["Test an asynchrony http request."];
http_async(suite) ->
[];
http_async(Config) when is_list(Config) ->
case ?config(local_server, Config) of
ok ->
Port = ?config(local_port, Config),
URL = ?URL_START ++ integer_to_list(Port) ++ "/dummy.html",
{ok, RequestId} =
http:request(get, {URL, []}, [], [{sync, false}]),
Body =
receive
{http, {RequestId, {{_, 200, _}, _, BinBody}}} ->
BinBody;
{http, Msg} ->
test_server:fail(Msg)
end,
inets_test_lib:check_body(binary_to_list(Body)),
{ok, NewRequestId} =
http:request(get, {URL, []}, [], [{sync, false}]),
ok = http:cancel_request(NewRequestId),
receive
{http, {NewRequestId, _NewResult}} ->
test_server:fail(http_cancel_request_failed)
after 3000 ->
ok
end;
_ ->
{skip, "Failed to start local http-server"}
end.
%%-------------------------------------------------------------------------
http_save_to_file(doc) ->
["Test to save the http body to a file"];
http_save_to_file(suite) ->
[];
http_save_to_file(Config) when is_list(Config) ->
case ?config(local_server, Config) of
ok ->
PrivDir = ?config(priv_dir, Config),
FilePath = filename:join(PrivDir, "dummy.html"),
Port = ?config(local_port, Config),
URL = ?URL_START ++ integer_to_list(Port) ++ "/dummy.html",
{ok, saved_to_file}
= http:request(get, {URL, []}, [], [{stream, FilePath}]),
{ok, Bin} = file:read_file(FilePath),
{ok, {{_,200,_}, [_ | _], Body}} = http:request(URL),
Bin == Body;
_ ->
{skip, "Failed to start local http-server"}
end.
%%-------------------------------------------------------------------------
http_save_to_file_async(doc) ->
["Test to save the http body to a file"];
http_save_to_file_async(suite) ->
[];
http_save_to_file_async(Config) when is_list(Config) ->
case ?config(local_server, Config) of
ok ->
PrivDir = ?config(priv_dir, Config),
FilePath = filename:join(PrivDir, "dummy.html"),
Port = ?config(local_port, Config),
URL = ?URL_START ++ integer_to_list(Port) ++ "/dummy.html",
{ok, RequestId} = http:request(get, {URL, []}, [],
[{stream, FilePath},
{sync, false}]),
receive
{http, {RequestId, saved_to_file}} ->
ok;
{http, Msg} ->
test_server:fail(Msg)
end,
{ok, Bin} = file:read_file(FilePath),
{ok, {{_,200,_}, [_ | _], Body}} = http:request(URL),
Bin == Body;
_ ->
{skip, "Failed to start local http-server"}
end.
%%-------------------------------------------------------------------------
http_headers(doc) ->
["Use as many request headers as possible not used in proxy_headers"];
http_headers(suite) ->
[];
http_headers(Config) when is_list(Config) ->
case ?config(local_server, Config) of
ok ->
Port = ?config(local_port, Config),
URL = ?URL_START ++ integer_to_list(Port) ++ "/dummy.html",
DocRoot = ?config(doc_root, Config),
{ok, FileInfo} =
file:read_file_info(filename:join([DocRoot,"dummy.html"])),
CreatedSec =
calendar:datetime_to_gregorian_seconds(
FileInfo#file_info.mtime),
Mod = httpd_util:rfc1123_date(
calendar:gregorian_seconds_to_datetime(
CreatedSec-1)),
Date = httpd_util:rfc1123_date({date(), time()}),
{ok, {{_,200,_}, [_ | _], [_ | _]}} =
http:request(get, {URL, [{"If-Modified-Since",
Mod},
{"From","[email protected]"},
{"Date", Date}
]}, [], []),
Mod1 = httpd_util:rfc1123_date(
calendar:gregorian_seconds_to_datetime(
CreatedSec+1)),
{ok, {{_,200,_}, [_ | _], [_ | _]}} =
http:request(get, {URL, [{"If-UnModified-Since",
Mod1}
]}, [], []),
Tag = httpd_util:create_etag(FileInfo),
{ok, {{_,200,_}, [_ | _], [_ | _]}} =
http:request(get, {URL, [{"If-Match",
Tag}
]}, [], []),
{ok, {{_,200,_}, [_ | _], _}} =
http:request(get, {URL, [{"If-None-Match",
"NotEtag,NeihterEtag"},
{"Connection", "Close"}
]}, [], []),
ok;
_ ->
{skip, "Failed to start local http-server"}
end.
%%-------------------------------------------------------------------------
http_headers_dummy(doc) ->
["Test the code for handling headers we do not want/can send "
"to a real server. Note it is not logical to send"
"all of these headers together, we only want to test that"
"the code for handling headers will not crash."];
http_headers_dummy(suite) ->
[];
http_headers_dummy(Config) when is_list(Config) ->
ok = http:set_options([{ipfamily, inet}]),
{DummyServerPid, Port} = dummy_server(self(), ipv4),
URL = ?URL_START ++ integer_to_list(Port) ++ "/dummy_headers.html",
Foo = http_chunk:encode("foobar") ++
binary_to_list(http_chunk:encode_last()),
FooBar = Foo ++ "\r\n\r\nOther:inets_test\r\n\r\n",
UserPasswd = base64:encode_to_string("Alladin:Sesame"),
Auth = "Basic " ++ UserPasswd,
%% The dummy server will ignore the headers, we only want to test
%% that the client header-handling code. This would not
%% be a vaild http-request!
{ok, {{_,200,_}, [_ | _], [_|_]}} =
http:request(post,
{URL,
[{"Via",
"1.0 fred, 1.1 nowhere.com (Apache/1.1)"},
{"Warning","1#pseudonym foobar"},
{"Vary","*"},
{"Upgrade","HTTP/2.0"},
{"Pragma", "1#no-cache"},
{"Cache-Control", "no-cache"},
{"Connection", "close"},
{"Date", "Sat, 29 Oct 1994 19:43:31 GMT"},
{"Accept", " text/plain; q=0.5, text/html"},
{"Accept-Language", "en"},
{"Accept-Encoding","chunked"},
{"Accept-Charset", "ISO8859-1"},
{"Authorization", Auth},
{"Expect", "1#100-continue"},
{"User-Agent","inets"},
{"Transfer-Encoding","chunked"},
{"Range", " bytes=0-499"},
{"If-Range", "Sat, 29 Oct 1994 19:43:31 GMT"},
{"If-Match", "*"},
{"Content-Type", "text/plain"},
{"Content-Encoding", "chunked"},
{"Content-Length", "6"},
{"Content-Language", "en"},
{"Content-Location", "http://www.foobar.se"},
{"Content-MD5",
"104528739076276072743283077410617235478"},
{"Content-Range", "bytes 0-499/1234"},
{"Allow", "GET"},
{"Proxy-Authorization", Auth},
{"Expires", "Sat, 29 Oct 1994 19:43:31 GMT"},
{"Upgrade", "HTTP/2.0"},
{"Last-Modified", "Sat, 29 Oct 1994 19:43:31 GMT"},
{"Trailer","1#User-Agent"}
], "text/plain", FooBar},
[], []),
DummyServerPid ! stop,
ok = http:set_options([{ipfamily, inet6fb4}]), % ********** ipfamily = inet6 *************
ok.
%%-------------------------------------------------------------------------
http_bad_response(doc) ->
["Test what happens when the server does not follow the protocol"];
http_bad_response(suite) ->
[];
http_bad_response(Config) when is_list(Config) ->
ok = http:set_options([{ipfamily, inet}]),
{DummyServerPid, Port} = dummy_server(self(), ipv4),
URL = ?URL_START ++ integer_to_list(Port) ++ "/missing_crlf.html",
URL1 = ?URL_START ++ integer_to_list(Port) ++ "/wrong_statusline.html",
{error, timeout} = http:request(get, {URL, []}, [{timeout, 400}], []),
{error, Reason} = http:request(URL1),
test_server:format("Wrong Statusline: ~p~n", [Reason]),
DummyServerPid ! stop,
ok = http:set_options([{ipfamily, inet6fb4}]), % ********** ipfamily = inet6 *************
ok.
%%-------------------------------------------------------------------------
ssl_head(doc) ->
["Same as http_head/1 but over ssl sockets."];
ssl_head(suite) ->
[];
ssl_head(Config) when is_list(Config) ->
case ?config(local_ssl_server, Config) of
ok ->
DataDir = ?config(data_dir, Config),
Port = ?config(local_ssl_port, Config),
URL = ?SSL_URL_START ++ integer_to_list(Port) ++ "/dummy.html",
CertFile = filename:join(DataDir, "ssl_client_cert.pem"),
SSLOptions = [{certfile, CertFile}, {keyfile, CertFile}],
{ok, {{_,200, _}, [_ | _], []}} =
http:request(head, {URL, []}, [{ssl, SSLOptions}], []);
{ok, _} ->
{skip, "Failed to start local http-server"};
_ ->
{skip, "Failed to start SSL"}
end.
%%-------------------------------------------------------------------------
ssl_get(doc) ->
["Same as http_get/1 but over ssl sockets."];
ssl_get(suite) ->
[];
ssl_get(Config) when is_list(Config) ->
case ?config(local_ssl_server, Config) of
ok ->
DataDir = ?config(data_dir, Config),
Port = ?config(local_ssl_port, Config),
URL = ?SSL_URL_START ++ integer_to_list(Port) ++ "/dummy.html",
CertFile = filename:join(DataDir, "ssl_client_cert.pem"),
SSLOptions = [{certfile, CertFile}, {keyfile, CertFile}],
{ok, {{_,200, _}, [_ | _], Body = [_ | _]}} =
http:request(get, {URL, []}, [{ssl, SSLOptions}], []),
inets_test_lib:check_body(Body);
{ok, _} ->
{skip, "Failed to start local http-server"};
_ ->
{skip, "Failed to start SSL"}
end.
%%-------------------------------------------------------------------------
ssl_trace(doc) ->
["Same as http_trace/1 but over ssl sockets."];
ssl_trace(suite) ->
[];
ssl_trace(Config) when is_list(Config) ->
case ?config(local_ssl_server, Config) of
ok ->
DataDir = ?config(data_dir, Config),
Port = ?config(local_ssl_port, Config),
URL = ?SSL_URL_START ++ integer_to_list(Port) ++ "/dummy.html",
CertFile = filename:join(DataDir, "ssl_client_cert.pem"),
SSLOptions = [{certfile, CertFile}, {keyfile, CertFile}],
case http:request(trace, {URL, []}, [{ssl, SSLOptions}], []) of
{ok, {{_,200, _}, [_ | _], "TRACE /dummy.html" ++ _}} ->
ok;
{ok, {{_,200,_}, [_ | _], WrongBody}} ->
test_server:fail({wrong_body, WrongBody});
{ok, WrongReply} ->
test_server:fail({wrong_reply, WrongReply});
Error ->
test_server:fail({failed, Error})
end;
{ok, _} ->
{skip, "Failed to start local http-server"};
_ ->
{skip, "Failed to start SSL"}
end.
%%-------------------------------------------------------------------------
http_redirect(doc) ->
["Test redirect with dummy server as httpd does not implement"
" server redirect"];
http_redirect(suite) ->
[];
http_redirect(Config) when is_list(Config) ->
tsp("http_redirect -> entry with"
"~n Config: ~p", [Config]),
case ?config(local_server, Config) of
ok ->
tsp("http_redirect -> set ipfamily option to inet"),
ok = http:set_options([{ipfamily, inet}]),
tsp("http_redirect -> start dummy server inet"),
{DummyServerPid, Port} = dummy_server(self(), ipv4),
tsp("http_redirect -> server port = ~p", [Port]),
URL300 = ?URL_START ++ integer_to_list(Port) ++ "/300.html",
tsp("http_redirect -> issue request 1: "
"~n ~p", [URL300]),
{ok, {{_,200,_}, [_ | _], [_|_]}}
= http:request(get, {URL300, []}, [], []),
tsp("http_redirect -> issue request 2: "
"~n ~p", [URL300]),
{ok, {{_,300,_}, [_ | _], _}} =
http:request(get, {URL300, []}, [{autoredirect, false}], []),
URL301 = ?URL_START ++ integer_to_list(Port) ++ "/301.html",
tsp("http_redirect -> issue request 3: "
"~n ~p", [URL301]),
{ok, {{_,200,_}, [_ | _], [_|_]}}
= http:request(get, {URL301, []}, [], []),
tsp("http_redirect -> issue request 4: "
"~n ~p", [URL301]),
{ok, {{_,200,_}, [_ | _], []}}
= http:request(head, {URL301, []}, [], []),
tsp("http_redirect -> issue request 5: "
"~n ~p", [URL301]),
{ok, {{_,301,_}, [_ | _], [_|_]}}
= http:request(post, {URL301, [],"text/plain", "foobar"},
[], []),
URL302 = ?URL_START ++ integer_to_list(Port) ++ "/302.html",
tsp("http_redirect -> issue request 6: "
"~n ~p", [URL302]),
{ok, {{_,200,_}, [_ | _], [_|_]}}
= http:request(get, {URL302, []}, [], []),
case http:request(get, {URL302, []}, [], []) of
{ok, Reply7} ->
case Reply7 of
{{_,200,_}, [_ | _], [_|_]} ->
tsp("http_redirect -> "
"expected reply for request 7"),
ok;
{StatusLine, Headers, Body} ->
tsp("http_redirect -> "
"unexpected reply for request 7: "
"~n StatusLine: ~p"
"~n Headers: ~p"
"~n Body: ~p",
[StatusLine, Headers, Body]),
tsf({unexpected_reply, Reply7})
end;
Error7 ->
tsp("http_redirect -> "
"unexpected result for request 7: "
"~n Error7: ~p",
[Error7]),
tsf({unexpected_result, Error7})
end,
tsp("http_redirect -> issue request 7: "
"~n ~p", [URL302]),
{ok, {{_,200,_}, [_ | _], []}}
= http:request(head, {URL302, []}, [], []),
tsp("http_redirect -> issue request 8: "
"~n ~p", [URL302]),
{ok, {{_,302,_}, [_ | _], [_|_]}}
= http:request(post, {URL302, [],"text/plain", "foobar"},
[], []),
URL307 = ?URL_START ++ integer_to_list(Port) ++ "/307.html",
tsp("http_redirect -> issue request 9: "
"~n ~p", [URL307]),
{ok, {{_,200,_}, [_ | _], [_|_]}}
= http:request(get, {URL307, []}, [], []),
tsp("http_redirect -> issue request 10: "
"~n ~p", [URL307]),
{ok, {{_,200,_}, [_ | _], []}}
= http:request(head, {URL307, []}, [], []),
tsp("http_redirect -> issue request 11: "
"~n ~p", [URL307]),
{ok, {{_,307,_}, [_ | _], [_|_]}}
= http:request(post, {URL307, [],"text/plain", "foobar"},
[], []),
tsp("http_redirect -> stop dummy server"),
DummyServerPid ! stop,
tsp("http_redirect -> reset ipfamily option (to inet6fb4)"),
ok = http:set_options([{ipfamily, inet6fb4}]), % ********** ipfamily = inet6 *************
tsp("http_redirect -> done"),
ok;
_ ->
{skip, "Failed to start local http-server"}
end.
%%-------------------------------------------------------------------------
http_redirect_loop(doc) ->
["Test redirect loop detection"];
http_redirect_loop(suite) ->
[];
http_redirect_loop(Config) when is_list(Config) ->
ok = http:set_options([{ipfamily, inet}]),
{DummyServerPid, Port} = dummy_server(self(), ipv4),
URL = ?URL_START ++ integer_to_list(Port) ++ "/redirectloop.html",
{ok, {{_,300,_}, [_ | _], _}}
= http:request(get, {URL, []}, [], []),
DummyServerPid ! stop,
ok = http:set_options([{ipfamily, inet6fb4}]), % ********** ipfamily = inet6 *************
ok.
%%-------------------------------------------------------------------------
http_internal_server_error(doc) ->
["Test 50X codes"];
http_internal_server_error(suite) ->
[];
http_internal_server_error(Config) when is_list(Config) ->
ok = http:set_options([{ipfamily, inet}]),
{DummyServerPid, Port} = dummy_server(self(), ipv4),
URL500 = ?URL_START ++ integer_to_list(Port) ++ "/500.html",
{ok, {{_,500,_}, [_ | _], _}}
= http:request(get, {URL500, []}, [], []),
URL503 = ?URL_START ++ integer_to_list(Port) ++ "/503.html",
%% Used to be able to make the service available after retry.
ets:new(unavailable, [named_table, public, set]),
ets:insert(unavailable, {503, unavailable}),
{ok, {{_,200, _}, [_ | _], [_|_]}} =
http:request(get, {URL503, []}, [], []),
ets:insert(unavailable, {503, long_unavailable}),
{ok, {{_,503, _}, [_ | _], [_|_]}} =
http:request(get, {URL503, []}, [], []),
ets:delete(unavailable),
DummyServerPid ! stop,
ok = http:set_options([{ipfamily, inet6fb4}]), % ********** ipfamily = inet6 *************
ok.
%%-------------------------------------------------------------------------
http_userinfo(doc) ->
["Test user info e.i. http://user:passwd@host:port/"];
http_userinfo(suite) ->
[];
http_userinfo(Config) when is_list(Config) ->
ok = http:set_options([{ipfamily, inet}]),
{DummyServerPid, Port} = dummy_server(self(), ipv4),
URLAuth = "http://alladin:sesame@localhost:"
++ integer_to_list(Port) ++ "/userinfo.html",
{ok, {{_,200,_}, [_ | _], _}}
= http:request(get, {URLAuth, []}, [], []),
URLUnAuth = "http://alladin:foobar@localhost:"
++ integer_to_list(Port) ++ "/userinfo.html",
{ok, {{_,401, _}, [_ | _], _}} =
http:request(get, {URLUnAuth, []}, [], []),
DummyServerPid ! stop,
ok = http:set_options([{ipfamily, inet6fb4}]), % ********** ipfamily = inet6 *************
ok.
%%-------------------------------------------------------------------------
http_cookie(doc) ->
["Test cookies."];
http_cookie(suite) ->
[];
http_cookie(Config) when is_list(Config) ->
ok = http:set_options([{cookies, enabled}, {ipfamily, inet}]),
{DummyServerPid, Port} = dummy_server(self(), ipv4),
URLStart = ?URL_START
++ integer_to_list(Port),
URLCookie = URLStart ++ "/cookie.html",
{ok, {{_,200,_}, [_ | _], [_|_]}}
= http:request(get, {URLCookie, []}, [], []),
ets:new(cookie, [named_table, public, set]),
ets:insert(cookie, {cookies, true}),
{ok, {{_,200,_}, [_ | _], [_|_]}}
= http:request(get, {URLStart ++ "/", []}, [], []),
ets:delete(cookie),
ok = http:set_options([{cookies, disabled}, {ipfamily, inet6fb4}]), % ********** ipfamily = inet6 *************
DummyServerPid ! stop,
ok = http:set_options([{ipfamily, inet6fb4}]), % ********** ipfamily = inet6************
ok.
%%-------------------------------------------------------------------------
proxy_options(doc) ->
["Perform a OPTIONS request that goes through a proxy."];
proxy_options(suite) ->
[];
proxy_options(Config) when is_list(Config) ->
case ?config(skip, Config) of
undefined ->
case http:request(options, {?PROXY_URL, []}, [], []) of
{ok, {{_,200,_}, Headers, _}} ->
case lists:keysearch("allow", 1, Headers) of
{value, {"allow", _}} ->
ok;
_ ->
test_server:fail(http_options_request_failed)
end;
Unexpected ->
test_server:fail({unexpected_result, Unexpected})
end;
Reason ->
{skip, Reason}
end.
%%-------------------------------------------------------------------------
proxy_head(doc) ->
["Perform a HEAD request that goes through a proxy."];
proxy_head(suite) ->
[];
proxy_head(Config) when is_list(Config) ->
case ?config(skip, Config) of
undefined ->
case http:request(head, {?PROXY_URL, []}, [], []) of
{ok, {{_,200, _}, [_ | _], []}} ->
ok;
Unexpected ->
test_server:fail({unexpected_result, Unexpected})
end;
Reason ->
{skip, Reason}
end.
%%-------------------------------------------------------------------------
proxy_get(doc) ->
["Perform a GET request that goes through a proxy."];
proxy_get(suite) ->
[];
proxy_get(Config) when is_list(Config) ->
case ?config(skip, Config) of
undefined ->
case http:request(get, {?PROXY_URL, []}, [], []) of
{ok, {{_,200,_}, [_ | _], Body = [_ | _]}} ->
inets_test_lib:check_body(Body);
Unexpected ->
test_server:fail({unexpected_result, Unexpected})
end;
Reason ->
{skip, Reason}
end.
%%-------------------------------------------------------------------------
proxy_emulate_lower_versions(doc) ->
["Perform requests as 0.9 and 1.0 clients."];
proxy_emulate_lower_versions(suite) ->
[];
proxy_emulate_lower_versions(Config) when is_list(Config) ->
case ?config(skip, Config) of
undefined ->
Result09 = pelv_get("HTTP/0.9"),
case Result09 of
{ok, [_| _] = Body0} ->
inets_test_lib:check_body(Body0),
ok;
_ ->
tsf({unexpected_result, "HTTP/0.9", Result09})
end,
%% We do not check the version here as many servers
%% do not behave according to the rfc and send
%% 1.1 in its response.
Result10 = pelv_get("HTTP/1.0"),
case Result10 of
{ok,{{_, 200, _}, [_ | _], Body1 = [_ | _]}} ->
inets_test_lib:check_body(Body1),
ok;
_ ->
tsf({unexpected_result, "HTTP/1.0", Result10})
end,
Result11 = pelv_get("HTTP/1.1"),
case Result11 of
{ok, {{"HTTP/1.1", 200, _}, [_ | _], Body2 = [_ | _]}} ->
inets_test_lib:check_body(Body2);
_ ->
tsf({unexpected_result, "HTTP/1.1", Result11})
end;
Reason ->
{skip, Reason}
end.
pelv_get(Version) ->
http:request(get, {?PROXY_URL, []}, [{version, Version}], []).
%%-------------------------------------------------------------------------
proxy_trace(doc) ->
["Perform a TRACE request that goes through a proxy."];
proxy_trace(suite) ->
[];
proxy_trace(Config) when is_list(Config) ->
%%{ok, {{_,200,_}, [_ | _], "TRACE " ++ _}} =
%% http:request(trace, {?PROXY_URL, []}, [], []),
{skip, "HTTP TRACE is no longer allowed on the ?PROXY_URL server due "
"to security reasons"}.
%%-------------------------------------------------------------------------
proxy_post(doc) ->
["Perform a POST request that goes through a proxy. Note the server"
" will reject the request this is a test of the sending of the"
" request."];
proxy_post(suite) ->
[];
proxy_post(Config) when is_list(Config) ->
case ?config(skip, Config) of
undefined ->
case http:request(post, {?PROXY_URL, [],
"text/plain", "foobar"}, [],[]) of
{ok, {{_,405,_}, [_ | _], [_ | _]}} ->
ok;
Unexpected ->
test_server:fail({unexpected_result, Unexpected})
end;
Reason ->
{skip, Reason}
end.
%%-------------------------------------------------------------------------
proxy_put(doc) ->
["Perform a PUT request that goes through a proxy. Note the server"
" will reject the request this is a test of the sending of the"
" request."];
proxy_put(suite) ->
[];
proxy_put(Config) when is_list(Config) ->
case ?config(skip, Config) of
undefined ->
case http:request(put, {"http://www.erlang.org/foobar.html", [],
"html", "<html> <body><h1> foo </h1>"
"<p>bar</p> </body></html>"}, [], []) of
{ok, {{_,405,_}, [_ | _], [_ | _]}} ->
ok;
Unexpected ->
test_server:fail({unexpected_result, Unexpected})
end;
Reason ->
{skip, Reason}
end.
%%-------------------------------------------------------------------------
proxy_delete(doc) ->
["Perform a DELETE request that goes through a proxy. Note the server"
" will reject the request this is a test of the sending of the"
" request. But as the file does not exist the return code will"
" be 404 not found."];
proxy_delete(suite) ->
[];
proxy_delete(Config) when is_list(Config) ->
case ?config(skip, Config) of
undefined ->
URL = ?PROXY_URL ++ "/foobar.html",
case http:request(delete, {URL, []}, [], []) of
{ok, {{_,404,_}, [_ | _], [_ | _]}} ->
ok;
Unexpected ->
test_server:fail({unexpected_result, Unexpected})
end;
Reason ->
{skip, Reason}
end.
%%-------------------------------------------------------------------------
proxy_headers(doc) ->
["Use as many request headers as possible"];
proxy_headers(suite) ->
[];
proxy_headers(Config) when is_list(Config) ->
case ?config(skip, Config) of
undefined ->
{ok, {{_,200,_}, [_ | _], [_ | _]}}
= http:request(get, {?PROXY_URL,
[
{"Accept",
"text/*, text/html,"
" text/html;level=1,"
" */*"},
{"Accept-Charset",
"iso-8859-5, unicode-1-1;"
"q=0.8"},
{"Accept-Encoding", "*"},
{"Accept-Language",
"sv, en-gb;q=0.8,"
" en;q=0.7"},
{"User-Agent", "inets"},
{"Max-Forwards","5"},
{"Referer",
"http://otp.ericsson.se:8000"
"/product/internal"}
]}, [], []),
ok;
Reason ->
{skip, Reason}
end.
%%-------------------------------------------------------------------------
proxy_auth(doc) ->
["Test the code for sending of proxy authorization."];
proxy_auth(suite) ->
[];
proxy_auth(Config) when is_list(Config) ->
%% Our proxy seems to ignore the header, however our proxy
%% does not requirer an auth header, but we want to know
%% atleast the code for sending the header does not crash!
case ?config(skip, Config) of
undefined ->
case http:request(get, {?PROXY_URL, []},
[{proxy_auth, {"foo", "bar"}}], []) of
{ok, {{_,200, _}, [_ | _], [_|_]}} ->
ok;
Unexpected ->
test_server:fail({unexpected_result, Unexpected})
end;
Reason ->
{skip, Reason}
end.
%%-------------------------------------------------------------------------
http_server_does_not_exist(doc) ->
["Test that we get an error message back when the server "
"does note exist."];
http_server_does_not_exist(suite) ->
[];
http_server_does_not_exist(Config) when is_list(Config) ->
{error, _} =
http:request(get, {"http://localhost:" ++
integer_to_list(?NOT_IN_USE_PORT)
++ "/", []},[], []),
ok.
%%-------------------------------------------------------------------------
page_does_not_exist(doc) ->
["Test that we get a 404 when the page is not found."];
page_does_not_exist(suite) ->
[];
page_does_not_exist(Config) when is_list(Config) ->
Port = ?config(local_port, Config),
URL = ?URL_START ++ integer_to_list(Port) ++ "/doesnotexist.html",
{ok, {{_,404,_}, [_ | _], [_ | _]}}
= http:request(get, {URL, []}, [], []),
ok.
%%-------------------------------------------------------------------------
proxy_page_does_not_exist(doc) ->
["Test that we get a 404 when the page is not found."];
proxy_page_does_not_exist(suite) ->
[];
proxy_page_does_not_exist(Config) when is_list(Config) ->
case ?config(skip, Config) of
undefined ->
URL = ?PROXY_URL ++ "/doesnotexist.html",
{ok, {{_,404,_}, [_ | _], [_ | _]}} =
http:request(get, {URL, []}, [], []),
ok;
Reason ->
{skip, Reason}
end.
%%-------------------------------------------------------------------------
proxy_https_not_supported(doc) ->
[];
proxy_https_not_supported(suite) ->
[];
proxy_https_not_supported(Config) when is_list(Config) ->
Result = http:request(get, {"https://login.yahoo.com", []}, [], []),
case Result of
{error, Reason} ->
%% ok so far
case Reason of
{failed_connecting, Why} ->
%% ok, now check why
case Why of
https_through_proxy_is_not_currently_supported ->
ok;
_ ->
tsf({unexpected_why, Why})
end;
_ ->
tsf({unexpected_reason, Reason})
end;
_ ->
tsf({unexpected_result, Result})
end,
ok.
%%-------------------------------------------------------------------------
http_stream(doc) ->
["Test the option stream for asynchrony requests"];
http_stream(suite) ->
[];
http_stream(Config) when is_list(Config) ->
Port = ?config(local_port, Config),
URL = ?URL_START ++ integer_to_list(Port) ++ "/dummy.html",
{ok, {{_,200,_}, [_ | _], Body}} =
http:request(get, {URL, []}, [], []),
{ok, RequestId} =
http:request(get, {URL, []}, [], [{sync, false},
{stream, self}]),
receive
{http, {RequestId, stream_start, _Headers}} ->
ok;
{http, Msg} ->
test_server:fail(Msg)
end,
StreamedBody = receive_streamed_body(RequestId, <<>>),
Body == binary_to_list(StreamedBody).
%%-------------------------------------------------------------------------
http_stream_once(doc) ->
["Test the option stream for asynchrony requests"];
http_stream_once(suite) ->
[];
http_stream_once(Config) when is_list(Config) ->
p("http_stream_once -> entry with"
"~n Config: ~p", [Config]),
p("http_stream_once -> set ipfamily to inet", []),
ok = http:set_options([{ipfamily, inet}]),
p("http_stream_once -> start dummy server", []),
{DummyServerPid, Port} = dummy_server(self(), ipv4),
PortStr = integer_to_list(Port),
p("http_stream_once -> once", []),
once(?URL_START ++ PortStr ++ "/once.html"),
p("http_stream_once -> once_chunked", []),
once(?URL_START ++ PortStr ++ "/once_chunked.html"),
p("http_stream_once -> dummy", []),
once(?URL_START ++ PortStr ++ "/dummy.html"),
p("http_stream_once -> stop dummy server", []),
DummyServerPid ! stop,
p("http_stream_once -> set ipfamily to inet6fb4", []),
ok = http:set_options([{ipfamily, inet6fb4}]), % ********** ipfamily = inet6 *************
p("http_stream_once -> done", []),
ok.
once(URL) ->
p("once -> issue sync request for ~p", [URL]),
{ok, {{_,200,_}, [_ | _], Body}} =
http:request(get, {URL, []}, [], []),
p("once -> issue async (self stream) request for ~p", [URL]),
{ok, RequestId} =
http:request(get, {URL, []}, [], [{sync, false},
{stream, {self, once}}]),
p("once -> await stream_start reply for (async) request ~p", [RequestId]),
NewPid =
receive
{http, {RequestId, stream_start, _Headers, Pid}} ->
p("once -> received stream_start reply for (async) request ~p: ~p",
[RequestId, Pid]),
Pid;
{http, Msg} ->
test_server:fail(Msg)
end,
tsp("once -> request handler: ~p", [NewPid]),
p("once -> await stream reply for (async) request ~p", [RequestId]),
BodyPart =
receive
{http, {RequestId, stream, BinBodyPart}} ->
p("once -> received stream reply for (async) request ~p: "
"~n~p", [RequestId, binary_to_list(BinBodyPart)]),
BinBodyPart
end,
tsp("once -> first body part '~p' received", [binary_to_list(BodyPart)]),
StreamedBody = receive_streamed_body(RequestId, BinBodyPart, NewPid),
Body = binary_to_list(StreamedBody),
p("once -> done when Bode: ~p", [Body]),
ok.
%%-------------------------------------------------------------------------
proxy_stream(doc) ->
["Test the option stream for asynchrony requests"];
proxy_stream(suite) ->
[];
proxy_stream(Config) when is_list(Config) ->
case ?config(skip, Config) of
undefined ->
{ok, {{_,200,_}, [_ | _], Body}} =
http:request(get, {?PROXY_URL, []}, [], []),
{ok, RequestId} =
http:request(get, {?PROXY_URL, []}, [],
[{sync, false}, {stream, self}]),
receive
{http, {RequestId, stream_start, _Headers}} ->
ok;
{http, Msg} ->
test_server:fail(Msg)
end,
StreamedBody = receive_streamed_body(RequestId, <<>>),
Body == binary_to_list(StreamedBody);
Reason ->
{skip, Reason}
end.
%%-------------------------------------------------------------------------
parse_url(doc) ->
["Test that an url is parsed correctly"];
parse_url(suite) ->
[];
parse_url(Config) when is_list(Config) ->
%% ipv6
{http,[],"2010:836B:4179::836B:4179",80,"/foobar.html",[]}
= http_uri:parse("http://[2010:836B:4179::836B:4179]/foobar.html"),
{error,
{malformed_url,"http://2010:836B:4179::836B:4179/foobar.html"}} =
http_uri:parse("http://2010:836B:4179::836B:4179/foobar.html"),
%% ipv4
{http,[],"127.0.0.1",80,"/foobar.html",[]} =
http_uri:parse("http://127.0.0.1/foobar.html"),
%% host
{http,[],"localhost",8888,"/foobar.html",[]} =
http_uri:parse("http://localhost:8888/foobar.html"),
%% Userinfo
{http,"nisse:foobar","localhost",8888,"/foobar.html",[]} =
http_uri:parse("http://nisse:foobar@localhost:8888/foobar.html"),
%% Scheme error
{error,no_scheme} = http_uri:parse("localhost/foobar.html"),
{error,{not_supported_scheme,localhost}} =
http_uri:parse("localhost:8888/foobar.html"),
%% Query
{http,[],"localhost",8888,"/foobar.html","?foo=bar&foobar=42"} =
http_uri:parse("http://localhost:8888/foobar.html?foo=bar&foobar=42"),
%% Esc chars
{http,[],"www.somedomain.com",80,"/%2Eabc",[]} =
http_uri:parse("http://www.somedomain.com/%2Eabc"),
{http,[],"www.somedomain.com",80,"/%252Eabc",[]} =
http_uri:parse("http://www.somedomain.com/%252Eabc"),
{http,[],"www.somedomain.com",80,"/%25abc",[]} =
http_uri:parse("http://www.somedomain.com/%25abc"),
{http,[],"www.somedomain.com",80,"/%25abc", "?foo=bar"} =
http_uri:parse("http://www.somedomain.com/%25abc?foo=bar"),
ok.
%%-------------------------------------------------------------------------
ipv6(doc) ->
["Test ipv6."];
ipv6(suite) ->
[];
ipv6(Config) when is_list(Config) ->
{ok, Hostname} = inet:gethostname(),
case lists:member(list_to_atom(Hostname),
?config(ipv6_hosts, Config)) of
true ->
{DummyServerPid, Port} = dummy_server(self(), ipv6),
URL = "http://[" ++ ?IPV6_LOCAL_HOST ++ "]:" ++
integer_to_list(Port) ++ "/foobar.html",
{ok, {{_,200,_}, [_ | _], [_|_]}} =
http:request(get, {URL, []}, [], []),
DummyServerPid ! stop,
ok;
false ->
{skip, "Host does not support IPv6"}
end.
%%-------------------------------------------------------------------------
headers_as_is(doc) ->
["Test the option headers_as_is"];
headers_as_is(suite) ->
[];
headers_as_is(Config) when is_list(Config) ->
Port = ?config(local_port, Config),
URL = ?URL_START ++ integer_to_list(Port) ++ "/dummy.html",
{ok, {{_,200,_}, [_|_], [_|_]}} =
http:request(get, {URL, [{"Host", "localhost"},{"Te", ""}]},
[], [{headers_as_is, true}]),
{ok, {{_,400,_}, [_|_], [_|_]}} =
http:request(get, {URL, [{"Te", ""}]},[], [{headers_as_is, true}]),
ok.
%%-------------------------------------------------------------------------
options(doc) ->
["Test the option parameters."];
options(suite) ->
[];
options(Config) when is_list(Config) ->
case ?config(local_server, Config) of
ok ->
Port = ?config(local_port, Config),
URL = ?URL_START ++ integer_to_list(Port) ++ "/dummy.html",
{ok, {{_,200,_}, [_ | _], Bin}}
= http:request(get, {URL, []}, [{foo, bar}],
%% Ignore unknown options
[{body_format, binary}, {foo, bar}]),
true = is_binary(Bin),
{ok, {200, [_|_]}}
= http:request(get, {URL, []}, [{timeout, infinity}],
[{full_result, false}]);
_ ->
{skip, "Failed to start local http-server"}
end.
%%-------------------------------------------------------------------------
http_invalid_http(doc) ->
["Test parse error"];
http_invalid_http(suite) ->
[];
http_invalid_http(Config) when is_list(Config) ->
ok = http:set_options([{ipfamily, inet}]),
{DummyServerPid, Port} = dummy_server(self(), ipv4),
URL = ?URL_START ++ integer_to_list(Port) ++ "/invalid_http.html",
{error, {could_not_parse_as_http, _} = Reason} =
http:request(get, {URL, []}, [], []),
test_server:format("Parse error: ~p ~n", [Reason]),
DummyServerPid ! stop,
ok = http:set_options([{ipfamily, inet6fb4}]), % ********** ipfamily = inet6 *************
ok.
%%-------------------------------------------------------------------------
hexed_query_otp_6191(doc) ->
[];
hexed_query_otp_6191(suite) ->
[];
hexed_query_otp_6191(Config) when is_list(Config) ->
Google = "www.google.com",
GoogleSearch = "http://" ++ Google ++ "/search",
Search1 = "?hl=en&q=a%D1%85%D1%83%D0%B9&btnG=Google+Search",
URI1 = GoogleSearch ++ Search1,
Search2 = "?hl=en&q=%25%25",
URI2 = GoogleSearch ++ Search2,
Search3 = "?hl=en&q=%foo",
URI3 = GoogleSearch ++ Search3,
{http, [], Google, 80, "/search", _} = http_uri:parse(URI1),
{http, [], Google, 80, "/search", _} = http_uri:parse(URI2),
{http, [], Google, 80, "/search", _} = http_uri:parse(URI3),
ok.
%%-------------------------------------------------------------------------
empty_body_otp_6243(doc) ->
["An empty body was not returned directly. There was a delay for several"
"seconds."];
empty_body_otp_6243(suite) ->
[];
empty_body_otp_6243(Config) when is_list(Config) ->
Port = ?config(local_port, Config),
URL = ?URL_START ++ integer_to_list(Port) ++ "/empty.html",
{ok, {{_,200,_}, [_ | _], []}} =
http:request(get, {URL, []}, [{timeout, 500}], []).
%%-------------------------------------------------------------------------
transfer_encoding_otp_6807(doc) ->
["Transfer encoding is case insensitive"];
transfer_encoding_otp_6807(suite) ->
[];
transfer_encoding_otp_6807(Config) when is_list(Config) ->
ok = http:set_options([{ipfamily, inet}]),
{DummyServerPid, Port} = dummy_server(self(), ipv4),
URL = ?URL_START ++ integer_to_list(Port) ++
"/capital_transfer_encoding.html",
{ok, {{_,200,_}, [_|_], [_ | _]}} = http:request(URL),
DummyServerPid ! stop,
ok = http:set_options([{ipfamily, inet6fb4}]), % ********** ipfamily = inet6 *************
ok.
%%-------------------------------------------------------------------------
proxy_not_modified_otp_6821(doc) ->
["If unmodified no body should be returned"];
proxy_not_modified_otp_6821(suite) ->
[];
proxy_not_modified_otp_6821(Config) when is_list(Config) ->
case ?config(skip, Config) of
undefined ->
provocate_not_modified_bug(?PROXY_URL);
Reason ->
{skip, Reason}
end.
%%-------------------------------------------------------------------------
empty_response_header_otp_6830(doc) ->
["Test the case that the HTTP server does not send any headers"];
empty_response_header_otp_6830(suite) ->
[];
empty_response_header_otp_6830(Config) when is_list(Config) ->
ok = http:set_options([{ipfamily, inet}]),
{DummyServerPid, Port} = dummy_server(self(), ipv4),
URL = ?URL_START ++ integer_to_list(Port) ++ "/no_headers.html",
{ok, {{_,200,_}, [], [_ | _]}} = http:request(URL),
DummyServerPid ! stop,
ok = http:set_options([{ipfamily, inet6fb4}]), % ********** ipfamily = inet6 *************
ok.
%%-------------------------------------------------------------------------
no_content_204_otp_6982(doc) ->
["Test the case that the HTTP 204 no content header"];
no_content_204_otp_6982(suite) ->
[];
no_content_204_otp_6982(Config) when is_list(Config) ->
ok = http:set_options([{ipfamily, inet}]),
{DummyServerPid, Port} = dummy_server(self(), ipv4),
URL = ?URL_START ++ integer_to_list(Port) ++ "/no_content.html",
{ok, {{_,204,_}, [], []}} = http:request(URL),
DummyServerPid ! stop,
ok = http:set_options([{ipfamily, inet6fb4}]), % ********** ipfamily = inet6 *************
ok.
%%-------------------------------------------------------------------------
missing_CR_otp_7304(doc) ->
["Test the case that the HTTP server uses only LF instead of CRLF"
"as delimitor"];
missing_CR_otp_7304(suite) ->
[];
missing_CR_otp_7304(Config) when is_list(Config) ->
ok = http:set_options([{ipfamily, inet}]),
{DummyServerPid, Port} = dummy_server(self(), ipv4),
URL = ?URL_START ++ integer_to_list(Port) ++ "/missing_CR.html",
{ok, {{_,200,_}, _, [_ | _]}} = http:request(URL),
DummyServerPid ! stop,
ok = http:set_options([{ipfamily, inet6fb4}]), % ********** ipfamily = inet6 *************
ok.
%%-------------------------------------------------------------------------
otp_7883(suite) ->
[otp_7883_1, otp_7883_2].
otp_7883_1(doc) ->
["OTP-7883-sync"];
otp_7883_1(suite) ->
[];
otp_7883_1(Config) when is_list(Config) ->
ok = http:set_options([{ipfamily, inet}]),
{DummyServerPid, Port} = dummy_server(self(), ipv4),
URL = ?URL_START ++ integer_to_list(Port) ++ "/just_close.html",
{error, socket_closed_remotely} = http:request(URL),
DummyServerPid ! stop,
ok = http:set_options([{ipfamily, inet6fb4}]), % ********** ipfamily = inet6 *************
ok.
otp_7883_2(doc) ->
["OTP-7883-async"];
otp_7883_2(suite) ->
[];
otp_7883_2(Config) when is_list(Config) ->
ok = http:set_options([{ipfamily, inet}]),
{DummyServerPid, Port} = dummy_server(self(), ipv4),
URL = ?URL_START ++ integer_to_list(Port) ++ "/just_close.html",
Method = get,
Request = {URL, []},
HttpOptions = [],
Options = [{sync, false}],
Profile = http:default_profile(),
{ok, RequestId} =
http:request(Method, Request, HttpOptions, Options, Profile),
ok =
receive
{http, {RequestId, {error, socket_closed_remotely}}} ->
ok
end,
DummyServerPid ! stop,
ok = http:set_options([{ipfamily, inet6fb4}]), % ********** ipfamily = inet6 *************
ok.
%%-------------------------------------------------------------------------
otp_8154(suite) ->
[otp_8154_1].
otp_8154_1(doc) ->
["OTP-8154"];
otp_8154_1(suite) ->
[];
otp_8154_1(Config) when is_list(Config) ->
start_inets(),
ReqSeqNumServer = start_sequence_number_server(),
RespSeqNumServer = start_sequence_number_server(),
{ok, Server, Port} = start_slow_server(RespSeqNumServer),
Clients = run_clients(105, Port, ReqSeqNumServer),
%% ok = wait_for_clients(Clients),
ok = wait4clients(Clients, timer:minutes(3)),
Server ! shutdown,
RespSeqNumServer ! shutdown,
ReqSeqNumServer ! shutdown,
ok.
start_inets() ->
inets:start(),
ok.
%% -----------------------------------------------------
%% A sequence number handler
%% The purpose is to be able to pair requests with responses.
start_sequence_number_server() ->
proc_lib:spawn(fun() -> loop_sequence_number(1) end).
loop_sequence_number(N) ->
receive
shutdown ->
ok;
{From, get_next} ->
From ! {next_is, N},
loop_sequence_number(N + 1)
end.
get_next_sequence_number(SeqNumServer) ->
SeqNumServer ! {self(), get_next},
receive {next_is, N} -> N end.
%% -----------------------------------------------------
%% Client part
%% Sends requests randomly parallel
run_clients(NumClients, ServerPort, SeqNumServer) ->
io:format("start clients when"
"~n NumClients: ~w"
"~n ServerPort: ~w"
"~n SeqNumServer: ~w"
"~n", [NumClients, ServerPort, SeqNumServer]),
set_random_seed(),
lists:map(
fun(Id) ->
io:format("starting client ~w~n", [Id]),
Req = f("req~3..0w", [get_next_sequence_number(SeqNumServer)]),
Url = f(?URL_START ++ "~w/~s", [ServerPort, Req]),
Pid = proc_lib:spawn(
fun() ->
io:format("[~w] client started - "
"issue request~n", [Id]),
case http:request(Url) of
{ok, {{_,200,_}, _, Resp}} ->
io:format("[~w] 200 response: "
"~p~n", [Id, Resp]),
case lists:prefix(Req++"->", Resp) of
true -> exit(normal);
false -> exit({bad_resp,Req,Resp})
end;
{ok, {{_,EC,Reason},_,Resp}} ->
io:format("[~w] ~w response: "
"~s~n~s~n",
[Id, EC, Reason, Resp]),
exit({bad_resp,Req,Resp});
Crap ->
io:format("[~w] bad response: ~p",
[Id, Crap]),
exit({bad_resp, Req, Crap})
end
end),
MRef = erlang:monitor(process, Pid),
timer:sleep(10 + random:uniform(1334)),
{Id, Pid, MRef}
end,
lists:seq(1, NumClients)).
%% wait_for_clients(Clients) ->
%% lists:foreach(
%% fun({Id, Pid, MRef}) ->
%% io:format("waiting for client ~w termination~n", [Id]),
%% receive
%% {'DOWN', MRef, process, Pid, normal} ->
%% io:format("waiting for clients: "
%% "normal exit from ~w (~p)~n",
%% [Id, Pid]),
%% ok;
%% {'DOWN', MRef, process, Pid, Reason} ->
%% io:format("waiting for clients: "
%% "unexpected exit from ~w (~p):"
%% "~n Reason: ~p"
%% "~n", [Id, Pid, Reason]),
%% erlang:error(Reason)
%% end
%% end,
%% Clients).
wait4clients([], _Timeout) ->
ok;
wait4clients(Clients, Timeout) when Timeout > 0 ->
io:format("wait4clients -> entry with"
"~n length(Clients): ~w"
"~n Timeout: ~w"
"~n", [length(Clients), Timeout]),
T = t(),
receive
{'DOWN', _MRef, process, Pid, normal} ->
case lists:keysearch(Pid, 2, Clients) of
{value, {Id, _, _}} ->
io:format("receive normal exit message "
"from client ~p (~p)", [Id, Pid]),
NewClients =
lists:keydelete(Id, 1, Clients),
wait4clients(NewClients,
Timeout - (t() - T));
false ->
io:format("receive normal exit message "
"from unknown process: ~p", [Pid]),
wait4clients(Clients, Timeout - (t() - T))
end;
{'DOWN', _MRef, process, Pid, Reason} ->
case lists:keysearch(Pid, 2, Clients) of
{value, {Id, _, _}} ->
io:format("receive bad exit message "
"from client ~p (~p):"
"~n ~p", [Id, Pid, Reason]),
erlang:error({bad_client_termination, Id, Reason});
false ->
io:format("receive normal exit message "
"from unknown process: ~p", [Pid]),
wait4clients(Clients, Timeout - (t() - T))
end
after Timeout ->
erlang:error({client_timeout, Clients})
end;
wait4clients(Clients, _) ->
erlang:error({client_timeout, Clients}).
%% Time in milli seconds
t() ->
{A,B,C} = erlang:now(),
A*1000000000+B*1000+(C div 1000).
%% -----------------------------------------------------
%% Webserver part:
%% Implements a web server that sends responses one character
%% at a time, with random delays between the characters.
start_slow_server(SeqNumServer) ->
io:format("start slow server when"
"~n SeqNumServer: ~w"
"~n", [SeqNumServer]),
proc_lib:start(
erlang, apply, [fun() -> init_slow_server(SeqNumServer) end, []]).
init_slow_server(SeqNumServer) ->
io:format("[webserver ~w] init slow server"
"~n", [SeqNumServer]),
{ok, LSock} = gen_tcp:listen(0, [binary, {packet,0}, {active,true},
{backlog, 100}]),
io:format("[webserver ~w] LSock: ~p"
"~n", [SeqNumServer, LSock]),
{ok, {_IP, Port}} = inet:sockname(LSock),
io:format("[webserver ~w] Port: ~w"
"~n", [SeqNumServer, Port]),
proc_lib:init_ack({ok, self(), Port}),
loop_slow_server(LSock, SeqNumServer).
loop_slow_server(LSock, SeqNumServer) ->
io:format("[webserver ~w] entry with"
"~n LSock: ~p"
"~n", [SeqNumServer, LSock]),
Master = self(),
Acceptor = proc_lib:spawn(
fun() -> client_handler(Master, LSock, SeqNumServer) end),
io:format("[webserver ~w] acceptor started"
"~n Acceptor: ~p"
"~n", [SeqNumServer, Acceptor]),
receive
{accepted, Acceptor} ->
io:format("[webserver ~w] accepted"
"~n", [SeqNumServer]),
loop_slow_server(LSock, SeqNumServer);
shutdown ->
gen_tcp:close(LSock),
exit(Acceptor, kill)
end.
%% Handle one client connection
client_handler(Master, LSock, SeqNumServer) ->
io:format("[acceptor ~w] await accept"
"~n", [SeqNumServer]),
{ok, CSock} = gen_tcp:accept(LSock),
io:format("[acceptor ~w] accepted"
"~n CSock: ~p"
"~n", [SeqNumServer, CSock]),
Master ! {accepted, self()},
set_random_seed(),
loop_client(1, CSock, SeqNumServer).
loop_client(N, CSock, SeqNumServer) ->
%% Await request, don't bother parsing it too much,
%% assuming the entire request arrives in one packet.
io:format("[acceptor ~w] await request"
"~n N: ~p"
"~n", [SeqNumServer, N]),
receive
{tcp, CSock, Req} ->
ReqNum = parse_req_num(Req),
RespSeqNum = get_next_sequence_number(SeqNumServer),
Response = f("~s->resp~3..0w/~2..0w", [ReqNum, RespSeqNum, N]),
Txt = f("Slow server (~p) got ~p, answering with ~p",
[self(), Req, Response]),
io:format("~s...~n", [Txt]),
slowly_send_response(CSock, Response),
case parse_connection_type(Req) of
keep_alive ->
io:format("~s...done~n", [Txt]),
loop_client(N+1, CSock, SeqNumServer);
close ->
io:format("~s...done (closing)~n", [Txt]),
gen_tcp:close(CSock)
end
end.
slowly_send_response(CSock, Answer) ->
Response = f("HTTP/1.1 200 OK\r\nContent-Length: ~w\r\n\r\n~s",
[length(Answer), Answer]),
lists:foreach(
fun(Char) ->
timer:sleep(random:uniform(500)),
gen_tcp:send(CSock, <<Char>>)
end,
Response).
parse_req_num(Request) ->
Opts = [caseless,{capture,all_but_first,list}],
{match, [ReqNum]} = re:run(Request, "GET /(.*) HTTP", Opts),
ReqNum.
parse_connection_type(Request) ->
Opts = [caseless,{capture,all_but_first,list}],
{match,[CType]} = re:run(Request, "connection: *(keep-alive|close)", Opts),
case string:to_lower(CType) of
"close" -> close;
"keep-alive" -> keep_alive
end.
set_random_seed() ->
{_, _, Micros} = now(),
A = erlang:phash2([make_ref(), self(), Micros]),
random:seed(A, A, A).
f(F, A) -> lists:flatten(io_lib:format(F,A)).
%%-------------------------------------------------------------------------
otp_8106(suite) ->
[
otp_8106_pid,
otp_8106_fun,
otp_8106_mfa
].
otp_8106_pid(doc) ->
["OTP-8106 - deliver reply info using \"other\" pid"];
otp_8106_pid(suite) ->
[];
otp_8106_pid(Config) when is_list(Config) ->
case ?config(local_server, Config) of
ok ->
ReceiverPid = create_receiver(pid),
Receiver = ReceiverPid,
otp8106(ReceiverPid, Receiver, Config),
stop_receiver(ReceiverPid),
ok;
_ ->
{skip, "Failed to start local http-server"}
end.
otp_8106_fun(doc) ->
["OTP-8106 - deliver reply info using fun"];
otp_8106_fun(suite) ->
[];
otp_8106_fun(Config) when is_list(Config) ->
case ?config(local_server, Config) of
ok ->
ReceiverPid = create_receiver(function),
Receiver = otp_8106_deliver_fun(ReceiverPid),
otp8106(ReceiverPid, Receiver, Config),
stop_receiver(ReceiverPid),
ok;
_ ->
{skip, "Failed to start local http-server"}
end.
otp_8106_mfa(doc) ->
["OTP-8106 - deliver reply info using mfa callback"];
otp_8106_mfa(suite) ->
[];
otp_8106_mfa(Config) when is_list(Config) ->
case ?config(local_server, Config) of
ok ->
ReceiverPid = create_receiver(mfa),
Receiver = {?MODULE, otp_8106_deliver, [mfa, ReceiverPid]},
otp8106(ReceiverPid, Receiver, Config),
stop_receiver(ReceiverPid),
ok;
_ ->
{skip, "Failed to start local http-server"}
end.
otp8106(ReceiverPid, Receiver, Config) ->
Port = ?config(local_port, Config),
URL = ?URL_START ++ integer_to_list(Port) ++ "/dummy.html",
Request = {URL, []},
HTTPOptions = [],
Options = [{sync, false}, {receiver, Receiver}],
{ok, RequestId} =
httpc:request(get, Request, HTTPOptions, Options),
Body =
receive
{reply, ReceiverPid, {RequestId, {{_, 200, _}, _, B}}} ->
B;
{reply, ReceiverPid, Msg} ->
tsf(Msg);
{bad_reply, ReceiverPid, Msg} ->
tsf(Msg)
end,
inets_test_lib:check_body(binary_to_list(Body)),
ok.
create_receiver(Type) ->
Parent = self(),
Receiver = fun() -> receiver(Type, Parent) end,
spawn_link(Receiver).
stop_receiver(Pid) ->
Pid ! {stop, self()}.
receiver(Type, Parent) ->
receive
{stop, Parent} ->
exit(normal);
{http, ReplyInfo} when (Type =:= pid) ->
Parent ! {reply, self(), ReplyInfo},
receiver(Type, Parent);
{Type, ReplyInfo} ->
Parent ! {reply, self(), ReplyInfo},
receiver(Type, Parent);
Crap ->
Parent ! {reply, self(), {bad_reply, Crap}},
receiver(Type, Parent)
end.
otp_8106_deliver_fun(ReceiverPid) ->
fun(ReplyInfo) -> otp_8106_deliver(ReplyInfo, function, ReceiverPid) end.
otp_8106_deliver(ReplyInfo, Type, ReceiverPid) ->
ReceiverPid ! {Type, ReplyInfo},
ok.
%%-------------------------------------------------------------------------
otp_8056(doc) ->
"OTP-8056";
otp_8056(suite) ->
[];
otp_8056(Config) when is_list(Config) ->
Method = get,
Port = ?config(local_port, Config),
URL = ?URL_START ++ integer_to_list(Port) ++ "/dummy.html",
Request = {URL, []},
HTTPOptions = [],
Options1 = [{sync, true}, {stream, {self, once}}],
Options2 = [{sync, true}, {stream, self}],
{error, streaming_error} = httpc:request(Method, Request,
HTTPOptions, Options1),
tsp("request 1 failed as expected"),
{error, streaming_error} = httpc:request(Method, Request,
HTTPOptions, Options2),
tsp("request 2 failed as expected"),
ok.
%%-------------------------------------------------------------------------
otp_8352(doc) ->
"OTP-8352";
otp_8352(suite) ->
[];
otp_8352(Config) when is_list(Config) ->
tsp("otp_8352 -> entry with"
"~n Config: ~p", [Config]),
case ?config(local_server, Config) of
ok ->
tsp("local-server running"),
tsp("initial profile info(1): ~p", [httpc:info()]),
MaxSessions = 5,
MaxKeepAlive = 10,
KeepAliveTimeout = timer:minutes(2),
ConnOptions = [{max_sessions, MaxSessions},
{max_keep_alive_length, MaxKeepAlive},
{keep_alive_timeout, KeepAliveTimeout}],
http:set_options(ConnOptions),
Method = get,
Port = ?config(local_port, Config),
URL = ?URL_START ++ integer_to_list(Port) ++ "/dummy.html",
Request = {URL, []},
Timeout = timer:seconds(1),
ConnTimeout = Timeout + timer:seconds(1),
HttpOptions1 = [{timeout, Timeout}, {connect_timeout, ConnTimeout}],
Options1 = [{socket_opts, [{tos, 87},
{recbuf, 16#FFFF},
{sndbuf, 16#FFFF}]}],
case http:request(Method, Request, HttpOptions1, Options1) of
{ok, {{_,200,_}, [_ | _], ReplyBody1 = [_ | _]}} ->
%% equivaliant to http:request(get, {URL, []}, [], []),
inets_test_lib:check_body(ReplyBody1);
{ok, UnexpectedReply1} ->
tsf({unexpected_reply, UnexpectedReply1});
{error, _} = Error1 ->
tsf({bad_reply, Error1})
end,
tsp("profile info (2): ~p", [httpc:info()]),
HttpOptions2 = [],
Options2 = [{socket_opts, [{tos, 84},
{recbuf, 32#1FFFF},
{sndbuf, 32#1FFFF}]}],
case http:request(Method, Request, HttpOptions2, Options2) of
{ok, {{_,200,_}, [_ | _], ReplyBody2 = [_ | _]}} ->
%% equivaliant to http:request(get, {URL, []}, [], []),
inets_test_lib:check_body(ReplyBody2);
{ok, UnexpectedReply2} ->
tsf({unexpected_reply, UnexpectedReply2});
{error, _} = Error2 ->
tsf({bad_reply, Error2})
end,
tsp("profile info (3): ~p", [httpc:info()]),
ok;
_ ->
{skip, "Failed to start local http-server"}
end.
%%-------------------------------------------------------------------------
otp_8371(doc) ->
["OTP-8371"];
otp_8371(suite) ->
[];
otp_8371(Config) when is_list(Config) ->
ok = http:set_options([{ipv6, disabled}]), % also test the old option
{DummyServerPid, Port} = dummy_server(self(), ipv4),
URL = ?URL_START ++ integer_to_list(Port) ++
"/ensure_host_header_with_port.html",
case http:request(get, {URL, []}, [], []) of
{ok, Result} ->
case Result of
{{_, 200, _}, _Headers, Body} ->
tsp("expected response with"
"~n Body: ~p", [Body]),
ok;
{StatusLine, Headers, Body} ->
tsp("expected response with"
"~n StatusLine: ~p"
"~n Headers: ~p"
"~n Body: ~p", [StatusLine, Headers, Body]),
tsf({unexpected_result,
[{status_line, StatusLine},
{headers, Headers},
{body, Body}]});
_ ->
tsf({unexpected_result, Result})
end;
Error ->
tsf({request_failed, Error})
end,
DummyServerPid ! stop,
ok = http:set_options([{ipv6, enabled}]),
ok.
%%--------------------------------------------------------------------
%% Internal functions
%%--------------------------------------------------------------------
setup_server_dirs(ServerRoot, DocRoot, DataDir) ->
ConfDir = filename:join(ServerRoot, "conf"),
CgiDir = filename:join(ServerRoot, "cgi-bin"),
ok = file:make_dir(ServerRoot),
ok = file:make_dir(DocRoot),
ok = file:make_dir(ConfDir),
ok = file:make_dir(CgiDir),
{ok, Files} = file:list_dir(DataDir),
lists:foreach(fun(File) -> case lists:suffix(".html", File) of
true ->
inets_test_lib:copy_file(File,
DataDir,
DocRoot);
false ->
ok
end
end, Files),
Cgi = case test_server:os_type() of
{win32, _} ->
"cgi_echo.exe";
_ ->
"cgi_echo"
end,
inets_test_lib:copy_file(Cgi, DataDir, CgiDir),
inets_test_lib:copy_file("mime.types", DataDir, ConfDir).
create_config(FileName, ComType, Port, PrivDir, ServerRoot, DocRoot,
SSLDir) ->
MaxHdrSz = io_lib:format("~p", [256]),
MaxHdrAct = io_lib:format("~p", [close]),
SSL =
case ComType of
ssl ->
[cline(["SSLCertificateFile ",
filename:join(SSLDir, "ssl_server_cert.pem")]),
cline(["SSLCertificateKeyFile ",
filename:join(SSLDir, "ssl_server_cert.pem")]),
cline(["SSLVerifyClient 0"])];
_ ->
[]
end,
Mod_order = "Modules mod_alias mod_auth mod_esi mod_actions mod_cgi"
" mod_include mod_dir mod_get mod_head"
" mod_log mod_disk_log mod_trace",
HttpConfig = [
cline(["Port ", integer_to_list(Port)]),
cline(["ServerName ", "httpc_test"]),
cline(["SocketType ", atom_to_list(ComType)]),
cline([Mod_order]),
cline(["ServerRoot ", ServerRoot]),
cline(["DocumentRoot ", DocRoot]),
cline(["MaxHeaderSize ",MaxHdrSz]),
cline(["MaxHeaderAction ",MaxHdrAct]),
cline(["DirectoryIndex ", "index.html "]),
cline(["DefaultType ", "text/plain"]),
cline(["ScriptAlias /cgi-bin/ ",
filename:join(ServerRoot, "cgi-bin"), "/"]),
SSL],
ConfigFile = filename:join([PrivDir,FileName]),
{ok, Fd} = file:open(ConfigFile, [write]),
ok = file:write(Fd, lists:flatten(HttpConfig)),
ok = file:close(Fd).
cline(List) ->
lists:flatten([List, "\r\n"]).
is_proxy_available(Proxy, Port) ->
case gen_tcp:connect(Proxy, Port, []) of
{ok, Socket} ->
gen_tcp:close(Socket),
true;
_ ->
false
end.
receive_streamed_body(RequestId, Body) ->
receive
{http, {RequestId, stream, BinBodyPart}} ->
receive_streamed_body(RequestId,
<<Body/binary, BinBodyPart/binary>>);
{http, {RequestId, stream_end, _Headers}} ->
Body;
{http, Msg} ->
test_server:fail(Msg)
end.
receive_streamed_body(RequestId, Body, Pid) ->
http:stream_next(Pid),
test_server:format("~p:receive_streamed_body -> requested next stream ~n", [?MODULE]),
receive
{http, {RequestId, stream, BinBodyPart}} ->
receive_streamed_body(RequestId,
<<Body/binary, BinBodyPart/binary>>,
Pid);
{http, {RequestId, stream_end, _Headers}} ->
Body;
{http, Msg} ->
test_server:fail(Msg)
end.
dummy_server(Caller, IpV) ->
Pid = spawn(httpc_SUITE, dummy_server_init, [Caller, IpV]),
receive
{port, Port} ->
{Pid, Port}
end.
dummy_server_init(Caller, IpV) ->
{ok, ListenSocket} =
case IpV of
ipv4 ->
gen_tcp:listen(0, [binary, inet, {packet, 0},
{reuseaddr,true},
{active, false}]);
ipv6 ->
gen_tcp:listen(0, [binary, inet6, {packet, 0},
{reuseaddr,true},
{active, false}])
end,
{ok, Port} = inet:port(ListenSocket),
tsp("dummy_server_init -> Port: ~p", [Port]),
Caller ! {port, Port},
dummy_server_loop({httpd_request, parse, [?HTTP_MAX_HEADER_SIZE]},
[], ListenSocket).
dummy_server_loop(MFA, Handlers, ListenSocket) ->
receive
stop ->
lists:foreach(fun(Handler) -> Handler ! stop end, Handlers)
after 0 ->
{ok, Socket} = gen_tcp:accept(ListenSocket),
HandlerPid = dummy_request_handler(MFA, Socket),
gen_tcp:controlling_process(Socket, HandlerPid),
HandlerPid ! controller,
dummy_server_loop(MFA, [HandlerPid | Handlers],
ListenSocket)
end.
dummy_request_handler(MFA, Socket) ->
spawn(httpc_SUITE, dummy_request_handler_init, [MFA, Socket]).
dummy_request_handler_init(MFA, Socket) ->
receive
controller ->
inet:setopts(Socket, [{active, true}])
end,
dummy_request_handler_loop(MFA, Socket).
dummy_request_handler_loop({Module, Function, Args}, Socket) ->
tsp("dummy_request_handler_loop -> entry with"
"~n Module: ~p"
"~n Function: ~p"
"~n Args: ~p", [Module, Function, Args]),
receive
{tcp, _, Data} ->
tsp("dummy_request_handler_loop -> Data ~p", [Data]),
case handle_request(Module, Function, [Data | Args], Socket) of
stop ->
gen_tcp:close(Socket);
NewMFA ->
dummy_request_handler_loop(NewMFA, Socket)
end;
stop ->
gen_tcp:close(Socket)
end.
handle_request(Module, Function, Args, Socket) ->
tsp("handle_request -> entry with"
"~n Module: ~p"
"~n Function: ~p"
"~n Args: ~p", [Module, Function, Args]),
case Module:Function(Args) of
{ok, Result} ->
tsp("handle_request -> ok"
"~n Result: ~p", [Result]),
case (catch handle_http_msg(Result, Socket)) of
stop ->
stop;
<<>> ->
tsp("handle_request -> empty data"),
{httpd_request, parse, [[<<>>, ?HTTP_MAX_HEADER_SIZE]]};
Data ->
handle_request(httpd_request, parse,
[Data |[?HTTP_MAX_HEADER_SIZE]], Socket)
end;
NewMFA ->
tsp("handle_request -> "
"~n NewMFA: ~p", [NewMFA]),
NewMFA
end.
handle_http_msg({_, RelUri, _, {_, Headers}, Body}, Socket) ->
tsp("handle_http_msg -> entry with: "
"~n RelUri: ~p"
"~n Headers: ~p"
"~n Body: ~p", [RelUri, Headers, Body]),
NextRequest =
case RelUri of
"/dummy_headers.html" ->
<<>>;
"/no_headers.html" ->
stop;
"/just_close.html" ->
stop;
_ ->
ContentLength = content_length(Headers),
case size(Body) - ContentLength of
0 ->
<<>>;
_ ->
<<_BodyThisReq:ContentLength/binary,
Next/binary>> = Body,
Next
end
end,
tsp("handle_http_msg -> NextRequest: ~p", [NextRequest]),
case (catch ets:lookup(cookie, cookies)) of
[{cookies, true}]->
tsp("handle_http_msg -> check cookies ~p", []),
check_cookie(Headers);
_ ->
ok
end,
DefaultResponse = "HTTP/1.1 200 ok\r\n" ++
"Content-Length:32\r\n\r\n"
"<HTML><BODY>foobar</BODY></HTML>",
Msg =
case RelUri of
"/just_close.html" ->
close;
"/no_content.html" ->
"HTTP/1.0 204 No Content\r\n\r\n";
"/no_headers.html" ->
"HTTP/1.0 200 OK\r\n\r\nTEST";
"/ensure_host_header_with_port.html" ->
%% tsp("handle_http_msg -> validate host with port"),
case ensure_host_header_with_port(Headers) of
true ->
B =
"<HTML><BODY>" ++
"host with port" ++
"</BODY></HTML>",
Len = integer_to_list(length(B)),
"HTTP/1.1 200 ok\r\n" ++
"Content-Length:" ++ Len ++ "\r\n\r\n" ++ B;
false ->
B =
"<HTML><BODY>" ++
"Internal Server Error - host without port" ++
"</BODY></HTML>",
Len = integer_to_list(length(B)),
"HTTP/1.1 500 Internal Server Error\r\n" ++
"Content-Length:" ++ Len ++ "\r\n\r\n" ++ B
end;
"/300.html" ->
NewUri = ?URL_START ++
integer_to_list(?IP_PORT) ++ "/dummy.html",
"HTTP/1.1 300 Multiple Choices\r\n" ++
"Location:" ++ NewUri ++ "\r\n" ++
"Content-Length:0\r\n\r\n";
"/301.html" ->
NewUri = ?URL_START ++
integer_to_list(?IP_PORT) ++ "/dummy.html",
"HTTP/1.1 301 Moved Permanently\r\n" ++
"Location:" ++ NewUri ++ "\r\n" ++
"Content-Length:80\r\n\r\n" ++
"<HTML><BODY><a href=" ++ NewUri ++
">New place</a></BODY></HTML>";
"/302.html" ->
NewUri = ?URL_START ++
integer_to_list(?IP_PORT) ++ "/dummy.html",
"HTTP/1.1 302 Found \r\n" ++
"Location:" ++ NewUri ++ "\r\n" ++
"Content-Length:80\r\n\r\n" ++
"<HTML><BODY><a href=" ++ NewUri ++
">New place</a></BODY></HTML>";
"/307.html" ->
NewUri = ?URL_START ++
integer_to_list(?IP_PORT) ++ "/dummy.html",
"HTTP/1.1 307 Temporary Rediect \r\n" ++
"Location:" ++ NewUri ++ "\r\n" ++
"Content-Length:80\r\n\r\n" ++
"<HTML><BODY><a href=" ++ NewUri ++
">New place</a></BODY></HTML>";
"/500.html" ->
"HTTP/1.1 500 Internal Server Error\r\n" ++
"Content-Length:47\r\n\r\n" ++
"<HTML><BODY>Internal Server Error</BODY></HTML>";
"/503.html" ->
case ets:lookup(unavailable, 503) of
[{503, unavailable}] ->
ets:insert(unavailable, {503, available}),
"HTTP/1.1 503 Service Unavailable\r\n" ++
"Retry-After:5\r\n" ++
"Content-Length:47\r\n\r\n" ++
"<HTML><BODY>Internal Server Error</BODY></HTML>";
[{503, available}] ->
DefaultResponse;
[{503, long_unavailable}] ->
"HTTP/1.1 503 Service Unavailable\r\n" ++
"Retry-After:120\r\n" ++
"Content-Length:47\r\n\r\n" ++
"<HTML><BODY>Internal Server Error</BODY></HTML>"
end;
"/redirectloop.html" -> %% Create a potential endless loop!
{ok, Port} = inet:port(Socket),
NewUri = ?URL_START ++
integer_to_list(Port) ++ "/redirectloop.html",
"HTTP/1.1 300 Multiple Choices\r\n" ++
"Location:" ++ NewUri ++ "\r\n" ++
"Content-Length:0\r\n\r\n";
"/userinfo.html" ->
Challange = "HTTP/1.1 401 Unauthorized \r\n" ++
"WWW-Authenticate:Basic" ++"\r\n" ++
"Content-Length:0\r\n\r\n",
case auth_header(Headers) of
{ok, Value} ->
handle_auth(Value, Challange, DefaultResponse);
_ ->
Challange
end;
"/dummy_headers.html" ->
%% The client will only care about the Transfer-Encoding
%% header the rest of these headers are left to the
%% user to evaluate. This is not a valid response
%% it only tests that the header handling code works.
Head = "HTTP/1.1 200 ok\r\n" ++
"Content-Length:32\r\n" ++
"Pragma:1#no-cache\r\n" ++
"Via:1.0 fred, 1.1 nowhere.com (Apache/1.1)\r\n" ++
"Warning:1#pseudonym foobar\r\n" ++
"Vary:*\r\n" ++
"Trailer:Other:inets_test\r\n" ++
"Upgrade:HTTP/2.0\r\n" ++
"Age:4711\r\n" ++
"Transfer-Encoding:chunked\r\n" ++
"Content-Encoding:foo\r\n" ++
"Content-Language:en\r\n" ++
"Content-Location:http://www.foobar.se\r\n" ++
"Content-MD5:104528739076276072743283077410617235478\r\n"
++
"Content-Range:Sat, 29 Oct 1994 19:43:31 GMT\r\n" ++
"Expires:Sat, 29 Oct 1994 19:43:31 GMT\r\n" ++
"Proxy-Authenticate:#1Basic" ++
"\r\n\r\n",
gen_tcp:send(Socket, Head),
gen_tcp:send(Socket, http_chunk:encode("<HTML><BODY>fo")),
gen_tcp:send(Socket, http_chunk:encode("obar</BODY></HTML>")),
http_chunk:encode_last();
"/capital_transfer_encoding.html" ->
Head = "HTTP/1.1 200 ok\r\n" ++
"Transfer-Encoding:Chunked\r\n\r\n",
gen_tcp:send(Socket, Head),
gen_tcp:send(Socket, http_chunk:encode("<HTML><BODY>fo")),
gen_tcp:send(Socket, http_chunk:encode("obar</BODY></HTML>")),
http_chunk:encode_last();
"/cookie.html" ->
"HTTP/1.1 200 ok\r\n" ++
"set-cookie:" ++ "test_cookie=true; path=/;" ++
"max-age=60000\r\n" ++
"Content-Length:32\r\n\r\n"++
"<HTML><BODY>foobar</BODY></HTML>";
"/missing_crlf.html" ->
"HTTP/1.1 200 ok" ++
"Content-Length:32\r\n" ++
"<HTML><BODY>foobar</BODY></HTML>";
"/wrong_statusline.html" ->
"ok 200 HTTP/1.1\r\n\r\n" ++
"Content-Length:32\r\n\r\n" ++
"<HTML><BODY>foobar</BODY></HTML>";
"/once_chunked.html" ->
Head = "HTTP/1.1 200 ok\r\n" ++
"Transfer-Encoding:Chunked\r\n\r\n",
gen_tcp:send(Socket, Head),
gen_tcp:send(Socket, http_chunk:encode("<HTML><BODY>fo")),
gen_tcp:send(Socket,
http_chunk:encode("obar</BODY></HTML>")),
http_chunk:encode_last();
"/once.html" ->
Head = "HTTP/1.1 200 ok\r\n" ++
"Content-Length:32\r\n\r\n",
gen_tcp:send(Socket, Head),
gen_tcp:send(Socket, "<HTML><BODY>fo"),
test_server:sleep(1000),
gen_tcp:send(Socket, "ob"),
test_server:sleep(1000),
gen_tcp:send(Socket, "ar</BODY></HTML>");
"/invalid_http.html" ->
"HTTP/1.1 301\r\nDate:Sun, 09 Dec 2007 13:04:18 GMT\r\n" ++
"Transfer-Encoding:chunked\r\n\r\n";
"/missing_reason_phrase.html" ->
"HTTP/1.1 200\r\n" ++
"Content-Length: 32\r\n\r\n"
"<HTML><BODY>foobar</BODY></HTML>";
"/missing_CR.html" ->
"HTTP/1.1 200 ok\n" ++
"Content-Length:32\r\n\n"
"<HTML><BODY>foobar</BODY></HTML>";
_ ->
DefaultResponse
end,
tsp("handle_http_msg -> Msg: ~p", [Msg]),
case Msg of
ok ->
%% Previously, this resulted in an {error, einval}. Now what?
ok;
close ->
%% Nothing to send, just close
gen_tcp:close(Socket);
_ when is_list(Msg) orelse is_binary(Msg) ->
gen_tcp:send(Socket, Msg)
end,
tsp("handle_http_msg -> done"),
NextRequest.
ensure_host_header_with_port([]) ->
false;
ensure_host_header_with_port(["host: " ++ Host| _]) ->
case string:tokens(Host, [$:]) of
[ActualHost, Port] ->
tsp("ensure_host_header_with_port -> "
"~n ActualHost: ~p"
"~n Port: ~p", [ActualHost, Port]),
true;
_ ->
false
end;
ensure_host_header_with_port([_|T]) ->
ensure_host_header_with_port(T).
auth_header([]) ->
auth_header_not_found;
auth_header(["authorization:" ++ Value | _]) ->
{ok, string:strip(Value)};
auth_header([_ | Tail]) ->
auth_header(Tail).
handle_auth("Basic " ++ UserInfo, Challange, DefaultResponse) ->
case string:tokens(base64:decode_to_string(UserInfo), ":") of
["alladin", "sesame"] = Auth ->
test_server:format("Auth: ~p~n", [Auth]),
DefaultResponse;
Other ->
test_server:format("UnAuth: ~p~n", [Other]),
Challange
end.
check_cookie([]) ->
test_server:fail(no_cookie_header);
check_cookie(["cookie:" ++ _Value | _]) ->
ok;
check_cookie([_Head | Tail]) ->
check_cookie(Tail).
content_length([]) ->
0;
content_length(["content-length:" ++ Value | _]) ->
list_to_integer(string:strip(Value));
content_length([_Head | Tail]) ->
content_length(Tail).
provocate_not_modified_bug(Url) ->
Timeout = 15000, %% 15s should be plenty
{ok, {{_, 200, _}, ReplyHeaders, _Body}} =
http:request(get, {Url, []}, [{timeout, Timeout}], []),
Etag = pick_header(ReplyHeaders, "ETag"),
Last = pick_header(ReplyHeaders, "last-modified"),
case http:request(get, {Url, [{"If-None-Match", Etag},
{"If-Modified-Since", Last}]},
[{timeout, 15000}],
[]) of
{ok, {{_, 304, _}, _, _}} -> %% The expected reply
page_unchanged;
{ok, {{_, 200, _}, _, _}} ->
%% If the page has changed since the
%% last request we retry to
%% trigger the bug
provocate_not_modified_bug(Url);
{error, timeout} ->
%% Not what we expected. Tcpdump can be used to
%% verify that we receive the complete http-reply
%% but still time out.
incorrect_result
end.
pick_header(Headers, Name) ->
case lists:keysearch(string:to_lower(Name), 1,
[{string:to_lower(X), Y} || {X, Y} <- Headers]) of
false ->
[];
{value, {_Key, Val}} ->
Val
end.
not_implemented_yet() ->
exit(not_implemented_yet).
p(F) ->
p(F, []).
p(F, A) ->
io:format("~p ~w:" ++ F ++ "~n", [self(), ?MODULE | A]).
tsp(F) ->
tsp(F, []).
tsp(F, A) ->
test_server:format("~p ~p:" ++ F ++ "~n", [self(), ?MODULE | A]).
tsf(Reason) ->
test_server:fail(Reason).