aboutsummaryrefslogblamecommitdiffstats
path: root/lib/inets/test/httpd_bench_SUITE.erl
blob: 087516f56c1d5deb38fa7dcb99e7c6df4f62601a (plain) (tree)
1
2
3
4


                   
                                                        















                                                                           
 

                           




                                                  










                                                                      

                                                    































                                                  
 
    
















                                                                       
 





































                                                                                         
 

                                   
 






                                                                                
 















































                                                                                  



                                                     











                                                                       
 


























                                                                  
            



























































                                                                                        




                                                 
                                                                                   

                           

             


                                                  

        






























































































































































































                                                                                                                         

                                                             

















                                                                                                                      

                                                            








                                                                                            
 









                                                                             
 





















































































































                                                                                                             
 














































                                                                            
 


















                                                                    
 



                           
 

                                                                                
 

                                                                           
 

                                                                          
 






















                                                                       
 




                                     
 






































































                                                                      
%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 2012-2018. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%%     http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%
%% %CopyrightEnd%
%%


%%
-module(httpd_bench_SUITE).
-compile(export_all).

-include_lib("common_test/include/ct.hrl").
-include_lib("common_test/include/ct_event.hrl").
-include_lib("public_key/include/public_key.hrl").
-include_lib("kernel/include/file.hrl").

-define(remote_host, "NETMARKS_REMOTE_HOST").
-define(LF, [10]).
-define(CR, [13]).
-define(CRLF, ?CR ++ ?LF).

%%--------------------------------------------------------------------
%% Common Test interface functions -----------------------------------
%%--------------------------------------------------------------------
suite() -> 
    [{timetrap, {minutes, 1}},
     {ct_hooks,[{ts_install_cth,[{nodenames,2}]}]}].

all() -> 
    [
     {group, http_dummy},
     {group, http_inets},
     {group, http_nginx},
     {group, https_inets},
     {group, https_dummy},
     {group, https_nginx},
     {group, http_dummy_keep_alive},
     {group, http_inets_keep_alive},
     {group, http_nginx_keep_alive},
     {group, https_inets_keep_alive},
     {group, https_dummy_keep_alive},
     {group, https_nginx_keep_alive}
    ].

groups() -> 
    [
     {http_dummy, [],  client_tests()},
     {http_inets, [],   client_tests()},
     {http_nginx, [],   client_tests()},
     {https_dummy, [],  client_tests()}, 
     {https_inets, [],  client_tests()},
     {https_nginx, [],  client_tests()},
     {http_dummy_keep_alive, [],  client_tests()},
     {http_inets_keep_alive, [],  client_tests()},
     {http_nginx_keep_alive, [],  client_tests()},
     {https_dummy_keep_alive, [], client_tests()},
     {https_inets_keep_alive, [], client_tests()},
     {https_nginx_keep_alive, [], client_tests()}
    ].

    
client_tests() ->
    [wget_small,
     erl_dummy_small,
     httpc_small,
     wget_big,
     erl_dummy_big, 
     httpc_big
    ].

init_per_suite(Config) -> 
    try	
	{Node, Host} = setup(Config, node()),
	init_ssl(Config),
	[{iter, 10}, {server_node, Node}, {server_host, Host} | Config]
    catch _:_ ->
	    {skipped, "Benchmark machines only"}
    end.

end_per_suite(_Config) -> 
    [application:stop(App) || App <- [asn1, crypto, public_key, ssl, inets]].

init_per_group(Group, Config) when Group == http_dummy_keep_alive; 
				   Group == https_dummy_keep_alive;
				   Group == http_inets_keep_alive; 
				   Group == https_inets_keep_alive;
				   Group == http_nginx_keep_alive;
				   Group == https_nginx_keep_alive ->
    Version = http_version(Group),
    start_web_server(Group,
		     [{keep_alive, true}, 
		      {reuse_sessions, false},
		      {http_version, Version},
		      {http_opts,[{version, Version}]},
		      {http_headers, [{"connection", "keep-alive"}]},
		      {httpc_opts, [{keep_alive_timeout, 1500}, 
				    {max_keep_alive_length, ?config(iter, Config)}]}
		      | Config]);
init_per_group(Group, Config)  when Group == http_dummy; 
				    Group == https_dummy;
				    Group == http_inets; 
				    Group == https_inets;
				    Group == http_nginx;
				    Group == https_nginx ->
    Version = http_version(Group),
    start_web_server(Group, 
		     [{keep_alive, false}, 
		      {reuse_sessions, false},
		      {http_version, Version},
		      {http_headers, [{"connection", "close"}]},
		      {http_opts,[{version, Version}]},
		      {httpc_opts, [{keep_alive_timeout, 0}, {max_keep_alive_length, 0}]}
		      | Config]);


init_per_group(_, Config) ->
    Config.

end_per_group(Group, Config) ->
    stop_web_server(Group, Config).

init_per_testcase(TestCase, Config) when TestCase == httpc_small;
					 TestCase == httpc_big
					 -> 
    Opts = ?config(httpc_opts, Config),
    inets:start(httpc, [{profile, TestCase}, {socket_opts, [{nodelay, true}]}]),
    httpc:set_options(Opts, TestCase),
    [{profile, TestCase} | proplists:delete(profile, Config)];

init_per_testcase(_, Config) -> 
    Config.
end_per_testcase(TestCase, _Config) when TestCase == httpc_small;
					 TestCase == httpc_big ->	
    ok = inets:stop(httpc, TestCase);
end_per_testcase(_TestCase, Config) ->	
    Config.
%%--------------------------------------------------------------------
%% Test Cases --------------------------------------------------------
%%--------------------------------------------------------------------

erl_dummy_small(Config) when is_list(Config) -> 
    {ok, Result} = run_test(httpd_lib_client, "1k_file", Config),
    notify(Result, Config, "erl_1k_file"). 

erl_dummy_big(Config)  when is_list(Config) -> 
    {ok, Result} = run_test(httpd_lib_client, "1M_file", Config),
    notify(Result, Config, "erl_1M_file"). 

wget_small(Config) when is_list(Config) -> 
    {ok, Result} = run_test(wget_client, "1k_file", Config),
    notify(Result, Config, "wget_1k_file"). 

wget_big(Config)  when is_list(Config) -> 
    {ok, Result} = run_test(wget_client, "1M_file", Config),
    notify(Result, Config, "wget_1M_file"). 

httpc_small(Config) when is_list(Config) -> 
    {ok, Result} = run_test(httpc_client, "1k_file", Config),
    notify(Result, Config, "httpc_1k_file"). 

httpc_big(Config)  when is_list(Config) -> 
    {ok, Result} = run_test(httpc_client, "1M_file", Config),
    notify(Result, Config, "httpc_1M_file"). 

%%--------------------------------------------------------------------
%% Internal functions ------------------------------------------------
%%--------------------------------------------------------------------

%%--------------------------------------------------------------------
%% Report benchmark results  ------------------------------------------------
%%--------------------------------------------------------------------

notify({TestPerSec, _MBps}, Config, Suffix) ->
    Name = lists:concat([?config(protocol,Config), " ", 
			 server_name(Config, [dummy_pid, httpd_pid, nginx_port]), 
			 "", Suffix]),
    ct:comment("~p tps", [TestPerSec]),
    ct_event:notify(#event{name = benchmark_data,
			   data=[{value, TestPerSec},
				 {suite, ?MODULE},
				 {name, Name}]}),
			 ok.
%%--------------------------------------------------------------------
%% Setup erlang nodes  ------------------------------------------------
%%--------------------------------------------------------------------

server_name(Config, [Server | Rest]) ->
    case proplists:get_value(Server, Config) of
	undefined ->
	    server_name(Config, Rest);
	_ ->
	    server_name(Server)
    end.

server_name(httpd_pid) ->   
   "inets";
server_name(nginx_port) -> 
    "nginx";
server_name(dummy_pid) ->
    "erlang".

setup(_Config, nonode@nohost) ->
    exit(dist_not_enabled);
setup(_Config, _LocalNode) ->
    Host = case os:getenv(?remote_host) of
	       false ->
		   {ok, This} = inet:gethostname(),
		   This;
	       RemHost ->
		   RemHost
	   end,
    Node = list_to_atom("inets_perf_server@" ++ Host),
    SlaveArgs = case init:get_argument(pa) of
	       {ok, PaPaths} ->
		   lists:append([" -pa " ++ P || [P] <- PaPaths]);
	       _ -> []
	   end,
    Prog =
	case os:find_executable("erl") of
	    false -> "erl";
	    P -> P
	end,
    case net_adm:ping(Node) of
	pong -> ok;
	pang ->
	    {ok, Node} = slave:start(Host, inets_perf_server, SlaveArgs, no_link, Prog)
    end,
    Path = code:get_path(),
    true = rpc:call(Node, code, set_path, [Path]),
    [ensure_started(Node, App) || App <- [asn1, crypto, public_key, ssl, inets]],
    [ensure_started(node(), App) || App <- [asn1, crypto, public_key, ssl, inets]],
    (Node =:= node()) andalso restrict_schedulers(client),
    {Node, Host}.

ensure_started(Node, App) ->
     ok = rpc:call(Node, application, ensure_started, [App]).


restrict_schedulers(Type) ->
    %% We expect this to run on 8 core machine
    Extra0 = 1,
    Extra =  if (Type =:= server) -> -Extra0; true -> Extra0 end,
    Scheds = erlang:system_info(schedulers),
    erlang:system_flag(schedulers_online, (Scheds div 2) + Extra).

%%--------------------------------------------------------------------
%% Setup TLS input files  ------------------------------------------------
%%--------------------------------------------------------------------

init_ssl(Config) ->
    DDir = ?config(data_dir, Config),
    PDir = ?config(priv_dir, Config),
    {ok, _} = make_certs:all(DDir,
			     PDir).
cert_opts(Config) ->
    ClientCaCertFile = filename:join([?config(priv_dir, Config), 
				      "client", "cacerts.pem"]),
    ClientCertFile = filename:join([?config(priv_dir, Config), 
				    "client", "cert.pem"]),
    ServerCaCertFile = filename:join([?config(priv_dir, Config), 
				      "server", "cacerts.pem"]),
    ServerCertFile = filename:join([?config(priv_dir, Config), 
				    "server", "cert.pem"]),
    ServerKeyFile = filename:join([?config(priv_dir, Config), 
			     "server", "key.pem"]),
    ClientKeyFile = filename:join([?config(priv_dir, Config), 
				   "client", "key.pem"]),
    [{server_verification_opts, [{reuseaddr, true}, 
				 {cacertfile, ServerCaCertFile},
				 {ciphers, ["ECDHE-RSA-AES256-GCM-SHA384"]},
				 {certfile, ServerCertFile}, {keyfile, ServerKeyFile}]},
     {client_verification_opts, [
				 %%{verify, verify_peer},
				 {cacertfile, ClientCaCertFile}, 
				 {certfile, ClientCertFile},  
				 {keyfile, ClientKeyFile}]}]. 

%%--------------------------------------------------------------------
%% Run clients  ------------------------------------------------
%%--------------------------------------------------------------------

run_test(Client, File, Config) -> 
    Parent = self(),
    Pid = spawn(fun() ->
			receive
			    go -> 
				Parent ! {self(),
					  do_runs(Client, [{file, File} | Config])}
			end
		end),
    Pid ! go,
    receive
	{Pid,{{tps, Tps}, {mbps, MBps}}} ->
	    ct:pal("Tps: ~p  Bps~p", [Tps, MBps]),
	    {ok, {Tps, MBps}}
    end.

do_runs(Client, Config) ->
    N = ?config(iter, Config),
    DataDir = ?config(data_dir, Config),
    File = ?config(file, Config),
    Name = filename:join(DataDir, File), 
    Args = ?MODULE:Client(Config),
    ?MODULE:Client({init, Args}),
    Run = 
	fun() ->
		ok = ?MODULE:Client(Args, N)	
	end,
    {ok, Info} = file:read_file_info(Name, []),
    Length = Info#file_info.size,
    {TimeInMicro, _} = timer:tc(Run),
    ReqPerSecond = (1000000 * N) div TimeInMicro,
    BytesPerSecond = (1000000 * N * Length) div TimeInMicro,
    {{tps, ReqPerSecond}, {mbps, BytesPerSecond}}.


httpc_client({init, [_, Profile, URL, Headers, HTTPOpts]}) ->
     %% Make sure pipelining feature will kick in when appropriate. 
    {ok, {{_ ,200, "OK"}, _,_}} = httpc:request(get,{URL, Headers}, HTTPOpts, 
						[{body_format, binary}, 
						 {socket_opts, [{nodelay, true}]}], Profile),
    ct:sleep(1000);
httpc_client(Config) ->
    File = ?config(file, Config),
    Protocol = ?config(protocol, Config),
    Profile = ?config(profile, Config),
    URL = (?config(urlfun,Config))(File),
    Headers =  ?config(http_headers, Config),
    HTTPOpts = ?config(http_opts, Config),
    [Protocol, Profile, URL, Headers, HTTPOpts].
httpc_client(_,0) ->
    ok;
httpc_client([Protocol, Profile, URL, Headers, HTTPOpts], N) ->
    {ok, {{_ ,200,"OK"}, _,_}} = httpc:request(get,{URL, Headers}, HTTPOpts, [{body_format, binary},
									     {socket_opts, [{nodelay, true}]}], Profile),
    httpc_client([Protocol, Profile, URL, Headers, HTTPOpts], N-1).

httpd_lib_client({init, [_, Type, Version, Request, Host, Port, Opts]}) ->
    ok = httpd_test_lib:verify_request(Type, Host, 
     				       Port,  
     				       Opts, node(),
     				       Request,
     				       [{statuscode, 200},
     					{version, Version}], infinity),
    ct:sleep(1000);
httpd_lib_client(Config) ->
    File = ?config(file, Config),
    KeepAlive = ?config(keep_alive, Config),
    Host = ?config(server_host, Config),
    Port = ?config(port, Config),
    ReuseSession = ?config(reuse_sessions, Config),
    {Type, Opts} = 
	case ?config(protocol, Config) of
	    "http" ->
		{ip_comm, [{active, true}, {mode, binary},{nodelay, true}]};
	    "https" ->	
		SSLOpts =  proplists:get_value(client_verification_opts, cert_opts(Config)),
		{ssl, [{active, true}, {mode, binary}, {nodelay, true},  
		       {reuse_sessions, ReuseSession} | SSLOpts]}
		    
	end,
    Version = ?config(http_version, Config),
    Request = case KeepAlive of
		  true ->
		      http_request("GET /" ++ File ++ " ", Version, Host, {"connection:keep-alive\r\n", ""});
		  false ->
		      http_request("GET /" ++ File ++ " ", Version, Host)
	      end,
    
    Args = [KeepAlive, Type, Version, Request, Host, Port, Opts],
    httpd_lib_client(Args, 1),
    Args.

httpd_lib_client(_, 0) ->
    ok;
httpd_lib_client([true, Type, Version, Request, Host, Port, Opts], N) ->
    ok = httpd_test_lib:verify_request_N(Type, Host, 
					 Port,  
					 Opts, node(),
					 Request,
					 [{statuscode, 200},
					  {version, Version}], infinity, N);
httpd_lib_client([false, Type, Version, Request, Host, Port, Opts] = List, N) ->
    ok = httpd_test_lib:verify_request(Type, Host, 
				       Port,  
				       Opts, node(),
				       Request,
				       [{statuscode, 200},
					{version, Version}], infinity),
    httpd_lib_client(List, N-1).

wget_client({init,_}) ->
    ok;
wget_client(Config) ->
    File = ?config(file, Config),
    URL = (?config(urlfun,Config))(File),
    KeepAlive = ?config(keep_alive, Config),
    PrivDir = ?config(priv_dir, Config),
    Protocol = ?config(protocol, Config),
    Iter = ?config(iter, Config),
    FileName = filename:join(PrivDir, "wget_req"),
    ProtocolOpts = case Protocol of
		    "http" ->
			   [];
		       "https" ->
			   proplists:get_value(client_verification_opts, cert_opts(Config))
		   end,
    wget_req_file(FileName,URL,Iter),
    [KeepAlive, FileName, URL, Protocol, ProtocolOpts, Iter].
wget_client([KeepAlive, WgetFile, _URL, Protocol, ProtocolOpts, _], _) ->
    process_flag(trap_exit, true),
    Cmd = wget_N(KeepAlive, WgetFile, Protocol, ProtocolOpts),
    %%ct:pal("Wget cmd: ~p", [Cmd]),
    Port = open_port({spawn, Cmd}, [stderr_to_stdout]), 
    wait_for_wget(Port).


%%--------------------------------------------------------------------
%% Start/stop servers  ------------------------------------------------
%%--------------------------------------------------------------------
start_web_server(Group, Config) when Group == http_dummy;
				     Group == http_dummy_keep_alive ->
    start_dummy("http", Config);

start_web_server(Group, Config) when Group == https_dummy;
				     Group == https_dummy_keep_alive ->
    start_dummy("https", Config);

start_web_server(Group, Config) when Group == http_inets;
				     Group == http_inets_keep_alive ->
    start_inets("http", [], Config);
    
start_web_server(Group, Config) when Group == https_inets;
				     Group == https_inets_keep_alive ->
    Opts = proplists:get_value(server_verification_opts, cert_opts(Config)),
    ReuseSessions = ?config(reuse_sessions, Config),
    SSLConfHttpd = [{socket_type, {essl,
				   [{nodelay, true}, {reuse_sessions, ReuseSessions} | Opts]}}],
    start_inets("https", SSLConfHttpd, Config);

start_web_server(Group, Config)  when Group == http_nginx;
				      Group == http_nginx_keep_alive ->
    case os:find_executable("nginx") of
	false ->
	    {skip, "nginx not found"};
	_ ->
	    start_nginx("http",  Config)
    end;

start_web_server(Group, Config)  when Group == https_nginx;
				      Group == https_nginx_keep_alive ->
     case os:find_executable("nginx") of
	false ->
	    {skip, "nginx not found"};
	 _ ->
	     start_nginx("https",  cert_opts(Config) ++ Config)  
     end.
   
start_inets(Protocol, ConfHttpd, Config) ->
    PrivDir = ?config(priv_dir, Config),
    DataDir = ?config(data_dir, Config),
    Node = ?config(server_node, Config),
    Host = ?config(server_host, Config),    
    HTTPVersion = ?config(http_version, Config),    
    Conf = [httpd, [{port,0},
		    {http_version, HTTPVersion},
		    {ipfamily, inet},
		    {server_name, "inets_test"},
		    {server_root, PrivDir}, 
		    {document_root, DataDir},
		    {keep_alive, ?config(keep_alive, Config)},
		    {keep_alive_timeout, 360}
		    | ConfHttpd]],
    {ok, Pid} = rpc:call(Node, inets, start, Conf),
    Port = proplists:get_value(port,  rpc:call(Node, httpd, info, [Pid])),
    F = fun(File) -> 
		lists:concat([Protocol,"://",Host,":",Port,"/",File]) 
	end,
    [{httpd_pid,Pid},{urlfun,F},{protocol,Protocol},{port,Port} | Config].

start_dummy("http"= Protocol, Config) ->
    HTTPVersion = ?config(http_version, Config),    
    Node = ?config(server_node, Config),
    %%DataDir= ?config(data_dir, Config),
    Host = ?config(server_host, Config),
    Conf = [
	    %%{big, filename:join(DataDir, "1M_file")},
	    %%{small, filename:join(DataDir, "1k_file")},
	    {big, {gen,  crypto:strong_rand_bytes(1000000)}},
	    {small, {gen,  crypto:strong_rand_bytes(1000)}},
	    {http_version, HTTPVersion},
	    {keep_alive,  ?config(keep_alive, Config)}
	   ],
    {Pid, Port} = rpc:call(Node, http_test_lib, dummy_server, [ip_comm, inet, [{content_cb, ?MODULE}, {conf, Conf}]]),
    F = fun(File) -> 
		lists:concat([Protocol,"://",Host,":",Port,"/",File]) 
	end,
    [{dummy_pid,Pid},{urlfun,F},{protocol, Protocol},{port,Port} | Config];

start_dummy("https" = Protocol, Config) ->
    HTTPVersion = ?config(http_version, Config),    
    Node = ?config(server_node, Config),
    %% DataDir= ?config(data_dir, Config),
    Host = ?config(server_host, Config),
    SSLOpts =  proplists:get_value(server_verification_opts, cert_opts(Config)),
    Opts = [{active, true}, {nodelay, true}, {reuseaddr, true} | SSLOpts],
    Conf = [%%{big, filename:join(DataDir, "1M_file")},
	    %%{small, filename:join(DataDir, "1k_file")},
	    {big, {gen, crypto:strong_rand_bytes(1000000)}},
	    {small, {gen, crypto:strong_rand_bytes(1000)}},
	    {http_version, HTTPVersion},
	    {keep_alive, ?config(keep_alive, Config)}
	   ],
    {Pid, Port} = rpc:call(Node, http_test_lib, dummy_server,
			   [ssl, inet, [{ssl, Opts}, {content_cb, ?MODULE}, {conf, Conf}]]),
    F = fun(File) -> 
		lists:concat([Protocol,"://",Host,":",Port,"/",File]) 
	end,
    [{dummy_pid,Pid},{urlfun,F},{protocol,Protocol},{port,Port} | Config].

start_nginx(Protocol, Config) ->
    PrivDir = ?config(priv_dir, Config),
    DataDir = ?config(data_dir, Config),
    Host = ?config(server_host, Config),    
    Port = inet_port(node()),
    
    ConfFile = filename:join(PrivDir, "nginx.conf"),
    nginx_conf(ConfFile, [{port, Port}, {protocol, Protocol} | Config]),
    Cmd = "nginx -c " ++ ConfFile, 
    NginxPort =  open_port({spawn, Cmd}, [{cd, DataDir}, stderr_to_stdout]), 

    F = fun(File) -> 
 		lists:concat([Protocol,"://",Host,":",Port,"/",File]) 
	end,
    
    wait_for_nginx_up(Host, Port),
   
    [{port, Port},{nginx_port, NginxPort},{urlfun,F},{protocol, Protocol} | Config ].

stop_nginx(Config)->
    PrivDir = ?config(priv_dir, Config),    
    {ok, Bin} = file:read_file(filename:join(PrivDir, "nginx.pid")),
    Pid = string:strip(binary_to_list(Bin), right, $\n),
    Cmd = "kill " ++ Pid,
    os:cmd(Cmd).
    
stop_web_server(Group, Config) when  Group == http_inets;
				     Group == http_inets_keep_alive;
				     Group == https_inets;
				     Group == https_inets_keep_alive -> 
    ServerNode = ?config(server_node, Config),
    rpc:call(ServerNode, inets, stop, [httpd, ?config(httpd_pid, Config)]);
stop_web_server(Group, Config) when  Group == http_dummy;
				     Group == http_dummy_keep_alive;
				     Group == https_dummy;
				     Group == https_dummy_keep_alive -> 
    stop_dummy_server(Config);
stop_web_server(Group, Config) when  Group == http_nginx;
				     Group == http_nginx_keep_alive;
				     Group == https_nginx;
				     Group == https_nginx_keep_alive -> 
    stop_nginx(Config).

stop_dummy_server(Config) ->    
      case ?config(dummy_pid, Config) of
	  Pid when is_pid(Pid) ->
	      exit(Pid, kill);
	  _ ->
	      ok
      end.

%%--------------------------------------------------------------------
%% Misc  ------------------------------------------------
%%--------------------------------------------------------------------
http_request(Request, "HTTP/1.1" = Version, Host, {Headers, Body}) ->
    Request ++ Version ++ "\r\nhost:" ++ Host ++ "\r\n" ++ Headers ++ "\r\n" ++ Body;
http_request(Request, Version, _, {Headers, Body}) ->
    Request ++ Version ++ "\r\n" ++ Headers  ++ "\r\n" ++ Body.

http_request(Request, "HTTP/1.1" = Version, Host) ->
    Request ++ Version ++ "\r\nhost:" ++ Host  ++ "\r\n\r\n";
http_request(Request, Version, _) ->
    Request ++ Version ++ "\r\n\r\n".

http_version(_) ->
    "HTTP/1.1".

inet_port(Node) ->
    {Port, Socket} = do_inet_port(Node),
     rpc:call(Node, gen_tcp, close, [Socket]),
     Port.

do_inet_port(Node) ->
    {ok, Socket} = rpc:call(Node, gen_tcp, listen, [0, [{reuseaddr, true}]]),
    {ok, Port} = rpc:call(Node, inet, port, [Socket]),
    {Port, Socket}.
 
%%--------------------------------------------------------------------
%% Dummy server callbacks  ------------------------------------------------
%%--------------------------------------------------------------------

handle_request(CB, S, "/1M_file" ++ _, Opts) ->
    Name = proplists:get_value(big, Opts),
    KeepAlive = proplists:get_value(keep_alive, Opts),
    do_handle_request(CB, S, Name, Opts, KeepAlive);
handle_request(CB, S, "/1k_file" ++ _, Opts) ->
    Name = proplists:get_value(small, Opts),
    KeepAlive = proplists:get_value(keep_alive, Opts),
    do_handle_request(CB, S, Name, Opts, KeepAlive).

do_handle_request(CB, S, Name, Opts, KeepAlive) when is_list(Name) ->
    Version = proplists:get_value(http_version, Opts),
    {ok, Fdesc} = file:open(Name, [read, binary]),
    {ok, Info} = file:read_file_info(Name, []),
    Length = Info#file_info.size,
    Response = response_status_line_and_headers(Version, "Content-Length:" 
						++ integer_to_list(Length) ++ ?CRLF, keep_alive(KeepAlive)), 
    CB:send(S, Response),
    send_file(CB, S, Fdesc);
do_handle_request(CB, S, {gen, Data}, Opts, KeepAlive) ->
    Version = proplists:get_value(http_version, Opts),
    Length = size(Data),
    Response = response_status_line_and_headers(Version, "Content-Length:" 
						++ integer_to_list(Length) ++ ?CRLF, keep_alive(KeepAlive)), 
    CB:send(S, Response),
    send_file(CB, S, {gen, Data}).
    
send_file(CB, S, {gen, Data})  ->
    CB:send(S, Data);
    %% ChunkSize = 64*1024,
    %% case size(Data) of
    %% 	N when N > ChunkSize ->
    %% 	    <<Chunk:N/binary, Rest/binary>> = Data,
    %% 	    %%{Chunk, Rest} = lists:split(N, Data),
    %% 	    CB:send(S, Chunk),
    %% 	    send_file(CB, S, {gen, Rest});
    %% 	_ ->
    %% 	    CB:send(S, Data)
    %% end; 

send_file(CB, S, FileDesc) ->
    case file:read(FileDesc, 64*1024) of
	{ok, Chunk} ->
	    CB:send(S, Chunk),
	    send_file(CB, S, FileDesc);	
	eof ->
	    file:close(FileDesc),
	    ok
    end.

response_status_line_and_headers(Version, Headers,  ConnectionHeader) -> 
    StatusLine = [Version, " ", "200 OK", ?CRLF],
    [StatusLine, Headers, ConnectionHeader, ?CRLF].

keep_alive(true)->
    "Connection:keep-alive\r\n";
keep_alive(false) ->
    "Connection:close\r\n".

handle_http_msg({_Method, RelUri, _, {_, _Headers}, _Body}, Socket, Conf) ->
    handle_request(connect_cb(Socket), Socket, RelUri, Conf),
    case proplists:get_value(keep_alive, Conf) of
	true ->
	    <<>>;
	false ->
	    stop
    end.
    
connect_cb({sslsocket, _, _}) ->
    ssl;
connect_cb(_) ->
    gen_tcp.

%%--------------------------------------------------------------------
%% Setup wget  ------------------------------------------------
%%--------------------------------------------------------------------
wget_req_file(FileName, Url, Iter) ->
    {ok, File} = file:open(FileName, [write]),
    write_urls(File, Url, Iter).

write_urls(File, Url, 1) ->
    file:write(File, Url), 
    file:close(File);
write_urls(File, Url, N) ->
    file:write(File, Url), 
    file:write(File, "\n"),
    write_urls(File, Url, N-1).
    
wait_for_wget(Port) ->
    receive 
	{Port, {data, _Data}} when is_port(Port) ->
	    wait_for_wget(Port);
	{Port, closed} -> 
	    ok;
	{'EXIT', Port, _Reason} ->
	    ok
    end.

wget_N(KeepAlive, WegetFile, "http", _ProtocolOpts) ->
    "wget -i " ++ WegetFile ++ " " ++ wget_keep_alive(KeepAlive) ++ 
	" --no-cache --timeout=120" ;
wget_N(KeepAlive, WegetFile, "https", ProtocolOpts) ->
    
    "wget -i " ++ WegetFile ++ " " ++ wget_keep_alive(KeepAlive) 
	++ wget_cert(ProtocolOpts) ++ wget_key(ProtocolOpts)
	++ wget_cacert(ProtocolOpts) ++ 
	" --no-cache --timeout=120".

wget(KeepAlive, URL, "http", _ProtocolOpts) ->
    "wget " ++ URL ++ " " ++ wget_keep_alive(KeepAlive) ++ 
	" --no-cache --timeout=120" ;
wget(KeepAlive, URL, "https", ProtocolOpts) ->
    
    "wget " ++ URL ++ " " ++ wget_keep_alive(KeepAlive) 
	++ wget_cert(ProtocolOpts) ++ wget_key(ProtocolOpts)
	++ wget_cacert(ProtocolOpts) ++ 
	" --no-cache --timeout=120".

wget_keep_alive(true)->
    "";
wget_keep_alive(false) ->
   "--no-http-keep-alive ".

wget_cacert(ProtocolOpts) ->
    "--ca-certificate=" ++ proplists:get_value(cacertfile, ProtocolOpts) ++ " ".

wget_cert(ProtocolOpts) ->
    "--certificate=" ++ proplists:get_value(certfile, ProtocolOpts) ++ " ".

wget_key(ProtocolOpts) ->
    "--private-key=" ++ proplists:get_value(keyfile, ProtocolOpts) ++ " ".

%%--------------------------------------------------------------------
%% Setup nginx  ------------------------------------------------
%%--------------------------------------------------------------------
nginx_conf(ConfFile, Config)->
    Protocol = ?config(protocol, Config),
    file:write_file(ConfFile,
		    [format_nginx_conf(nginx_global(Config)),
		     format_nginx_conf(nginx_events(Config)),
		     format_nginx_conf(nginx_http(Protocol, Config))]).
       
format_nginx_conf(Directives) ->
    lists:map(fun({Key, Value}) ->
			  io_lib:format("~s ~s;\n", [Key, Value]);
		     (Str) ->
			  Str    
		  end, Directives).


nginx_global(Config) ->
    PrivDir = ?config(priv_dir, Config),
    [{"pid", filename:join(PrivDir, "nginx.pid")},
     {"error_log",  filename:join(PrivDir, "nginx.pid")},
     {"worker_processes", "1"}].

nginx_events(_Config) ->
    ["events {\n",
     {"worker_connections",  "1024"},
     "\n}"
    ].

nginx_http("http", Config) ->
    PrivDir = ?config(priv_dir, Config),
    DataDir = ?config(data_dir, Config),
    Port = ?config(port, Config),
    ["http {\n" |
     nginx_defaults(PrivDir) ++
	 [" server {",
	  {root,                DataDir}, 
	  {listen,              integer_to_list(Port)},
	  " location / {\n  try_files $uri $uri/ /index.html;\n}"
	  "}\n", "}\n"
	 ]
    ];
	
nginx_http("https", Config) ->
    PrivDir = ?config(priv_dir, Config),
    DataDir = ?config(data_dir, Config),
    Port = ?config(port, Config),
    SSLOpts = ?config(server_verification_opts, Config),
    Ciphers = proplists:get_value(ciphers, SSLOpts),
    ReuseSession = ?config(reuse_sessions, Config),
    ["http {" |
     nginx_defaults(PrivDir) ++
	 [" server {",
	  {"root",                DataDir}, 
	  {"listen",              integer_to_list(Port) ++ " ssl"},
	  {"ssl_certificate",     ?config(certfile, SSLOpts)},
	  {"ssl_certificate_key", ?config(keyfile, SSLOpts)},
	  {"ssl_protocols",       "TLSv1 TLSv1.1 TLSv1.2"},
	  {"ssl_ciphers",         Ciphers}, 
	  {"ssl_session_cache",    nginx_reuse_session(ReuseSession)},
	  " location / {\n  try_files $uri $uri/ /index.html;\n}"
	  "}\n", "}\n"
	 ]
    ].
	
nginx_defaults(PrivDir) ->
    [
     %% Set temp and cache file options that will otherwise default to
     %% restricted locations accessible only to root.
     {"client_body_temp_path", filename:join(PrivDir, "client_body")},
     {"fastcgi_temp_path",   filename:join(PrivDir, "fastcgi_temp")}, 
     {"proxy_temp_path", filename:join(PrivDir, "proxy_temp")},
     {"scgi_temp_path", filename:join(PrivDir, "scgi_temp")},
     {"uwsgi_temp_path", filename:join(PrivDir, "uwsgi_temp_path")},
     {"access_log",  filename:join(PrivDir, "access.log")},
     {"error_log",   filename:join(PrivDir, "error.log")},
     %% Standard options
     {"sendfile", "on"},
     {"tcp_nopush", "on"},
     {"tcp_nodelay", "on"},
     {"keepalive_timeout",  "360"},
     {"types_hash_max_size", "2048"},
     {"include", "/etc/nginx/mime.types"},
     {"default_type", "application/octet-stream"}
    ].

nginx_reuse_session(true) ->
    "on";
nginx_reuse_session(false) ->
    "off".

wait_for_nginx_up(Host, Port) ->
    case gen_tcp:connect(Host, Port, []) of
	{ok, Socket} ->
	    gen_tcp:close(Socket);
	_  ->
	    ct:sleep(100),
	    wait_for_nginx_up(Host, Port)
    end.