浏览代码

Merge 37fce82846 into ea3305d21f

pull/123/merge
Benjamin Lee 9 年前
父节点
当前提交
e8c6e5dbd5
共有 11 个文件被更改,包括 443 次插入227 次删除
  1. +1
    -0
      .gitignore
  2. +3
    -0
      CONTRIBUTORS
  3. +1
    -1
      README.md
  4. +5
    -0
      include/ibrowse.hrl
  5. 二进制
      rebar
  6. +23
    -23
      src/ibrowse.erl
  7. +33
    -47
      src/ibrowse_http_client.erl
  8. +131
    -108
      src/ibrowse_lb.erl
  9. +171
    -0
      test/ibrowse_functional_tests.erl
  10. +36
    -36
      test/ibrowse_test.erl
  11. +39
    -12
      test/ibrowse_test_server.erl

+ 1
- 0
.gitignore 查看文件

@ -9,3 +9,4 @@ doc/edoc-info
Emakefile
*.bat
.dialyzer_plt
.rebar

+ 3
- 0
CONTRIBUTORS 查看文件

@ -9,9 +9,12 @@ In alphabetical order:
Adam Kocoloski
Andrew Tunnell-Jones
Anthony Molinaro
Benjamin P Lee (https://github.com/benjaminplee)
Benoit Chesneau (https://github.com/benoitc)
Brian Richards (http://github.com/richbria)
Chris Newcombe
Dan Kelley
Dan Schwabe (https://github.com/dfschwabe)
Derek Upham
Eric Merritt
Erik Reitsma

+ 1
- 1
README.md 查看文件

@ -1,4 +1,4 @@
# ibrowse [![Build Status](https://secure.travis-ci.org/johannesh/ibrowse.png)](http://travis-ci.org/johannesh/ibrowse)
# ibrowse [![Build Status](https://secure.travis-ci.org/cmullaparthi/ibrowse.png)](http://travis-ci.org/cmullaparthi/ibrowse)
ibrowse is a HTTP client written in erlang.

+ 5
- 0
include/ibrowse.hrl 查看文件

@ -18,4 +18,9 @@
-record(ibrowse_conf, {key, value}).
-define(CONNECTIONS_LOCAL_TABLE, ibrowse_lb).
-define(LOAD_BALANCER_NAMED_TABLE, ibrowse_lb).
-define(CONF_TABLE, ibrowse_conf).
-define(STREAM_TABLE, ibrowse_stream).
-endif.

二进制
rebar 查看文件


+ 23
- 23
src/ibrowse.erl 查看文件

@ -160,7 +160,7 @@ stop() ->
%% respHeader() = {headerName(), headerValue()}
%% headerName() = string()
%% headerValue() = string()
%% response() = {ok, Status, ResponseHeaders, ResponseBody} | {ok, Status, ResponseHeaders, ResponseBody} | {ibrowse_req_id, req_id() } | {error, Reason}
%% response() = {ok, Status, ResponseHeaders, ResponseBody} | {ibrowse_req_id, req_id() } | {error, Reason}
%% req_id() = term()
%% ResponseBody = string() | {file, Filename}
%% Reason = term()
@ -320,7 +320,7 @@ send_req(Url, Headers, Method, Body, Options, Timeout) ->
#url{host = Host,
port = Port,
protocol = Protocol} = Parsed_url ->
Lb_pid = case ets:lookup(ibrowse_lb, {Host, Port}) of
Lb_pid = case ets:lookup(?LOAD_BALANCER_NAMED_TABLE, {Host, Port}) of
[] ->
get_lb_pid(Parsed_url);
[#lb_pid{pid = Lb_pid_1}] ->
@ -575,7 +575,7 @@ send_req_direct(Conn_pid, Url, Headers, Method, Body, Options, Timeout) ->
%% <code>stream_to</code> option
%% @spec stream_next(Req_id :: req_id()) -> ok | {error, unknown_req_id}
stream_next(Req_id) ->
case ets:lookup(ibrowse_stream, {req_id_pid, Req_id}) of
case ets:lookup(?STREAM_TABLE, {req_id_pid, Req_id}) of
[] ->
{error, unknown_req_id};
[{_, Pid}] ->
@ -590,7 +590,7 @@ stream_next(Req_id) ->
%% error returned.
%% @spec stream_close(Req_id :: req_id()) -> ok | {error, unknown_req_id}
stream_close(Req_id) ->
case ets:lookup(ibrowse_stream, {req_id_pid, Req_id}) of
case ets:lookup(?STREAM_TABLE, {req_id_pid, Req_id}) of
[] ->
{error, unknown_req_id};
[{_, Pid}] ->
@ -676,7 +676,7 @@ get_metrics() ->
true;
(_) ->
false
end, ets:tab2list(ibrowse_lb)),
end, ets:tab2list(?LOAD_BALANCER_NAMED_TABLE)),
All_ets = ets:all(),
lists:map(fun({lb_pid, {Host, Port}, Lb_pid}) ->
case lists:dropwhile(
@ -695,7 +695,7 @@ get_metrics() ->
end, Dests).
get_metrics(Host, Port) ->
case ets:lookup(ibrowse_lb, {Host, Port}) of
case ets:lookup(?LOAD_BALANCER_NAMED_TABLE, {Host, Port}) of
[] ->
no_active_processes;
[#lb_pid{pid = Lb_pid}] ->
@ -763,9 +763,9 @@ init(_) ->
State = #state{},
put(my_trace_flag, State#state.trace),
put(ibrowse_trace_token, "ibrowse"),
ibrowse_lb = ets:new(ibrowse_lb, [named_table, public, {keypos, 2}]),
ibrowse_conf = ets:new(ibrowse_conf, [named_table, protected, {keypos, 2}]),
ibrowse_stream = ets:new(ibrowse_stream, [named_table, public]),
?LOAD_BALANCER_NAMED_TABLE = ets:new(?LOAD_BALANCER_NAMED_TABLE, [named_table, public, {keypos, 2}]),
?CONF_TABLE = ets:new(?CONF_TABLE, [named_table, protected, {keypos, 2}]),
?STREAM_TABLE = ets:new(?STREAM_TABLE, [named_table, public]),
import_config(),
{ok, #state{}}.
@ -787,7 +787,7 @@ import_config(Filename) ->
end.
apply_config(Terms) ->
ets:delete_all_objects(ibrowse_conf),
ets:delete_all_objects(?CONF_TABLE),
insert_config(Terms).
insert_config(Terms) ->
@ -800,12 +800,12 @@ insert_config(Terms) ->
{{options, Host, Port}, Options}],
lists:foreach(
fun({X, Y}) ->
ets:insert(ibrowse_conf,
ets:insert(?CONF_TABLE,
#ibrowse_conf{key = X,
value = Y})
end, I);
({K, V}) ->
ets:insert(ibrowse_conf,
ets:insert(?CONF_TABLE,
#ibrowse_conf{key = K,
value = V});
(X) ->
@ -816,7 +816,7 @@ insert_config(Terms) ->
%% @doc Internal export
get_config_value(Key) ->
try
[#ibrowse_conf{value = V}] = ets:lookup(ibrowse_conf, Key),
[#ibrowse_conf{value = V}] = ets:lookup(?CONF_TABLE, Key),
V
catch
error:badarg ->
@ -826,7 +826,7 @@ get_config_value(Key) ->
%% @doc Internal export
get_config_value(Key, DefVal) ->
try
case ets:lookup(ibrowse_conf, Key) of
case ets:lookup(?CONF_TABLE, Key) of
[] ->
DefVal;
[#ibrowse_conf{value = V}] ->
@ -838,7 +838,7 @@ get_config_value(Key, DefVal) ->
end.
set_config_value(Key, Val) ->
ets:insert(ibrowse_conf, #ibrowse_conf{key = Key, value = Val}).
ets:insert(?CONF_TABLE, #ibrowse_conf{key = Key, value = Val}).
%%--------------------------------------------------------------------
%% Function: handle_call/3
%% Description: Handling call messages
@ -850,7 +850,7 @@ set_config_value(Key, Val) ->
%% {stop, Reason, State} (terminate/2 is called)
%%--------------------------------------------------------------------
handle_call({get_lb_pid, #url{host = Host, port = Port} = Url}, _From, State) ->
Pid = do_get_connection(Url, ets:lookup(ibrowse_lb, {Host, Port})),
Pid = do_get_connection(Url, ets:lookup(?LOAD_BALANCER_NAMED_TABLE, {Host, Port})),
{reply, Pid, State};
handle_call(stop, _From, State) ->
@ -858,7 +858,7 @@ handle_call(stop, _From, State) ->
ets:foldl(fun(#lb_pid{pid = Pid}, Acc) ->
ibrowse_lb:stop(Pid),
Acc
end, [], ibrowse_lb),
end, [], ?LOAD_BALANCER_NAMED_TABLE),
{stop, normal, ok, State};
handle_call({set_config_value, Key, Val}, _From, State) ->
@ -905,7 +905,7 @@ handle_cast(_Msg, State) ->
%%--------------------------------------------------------------------
handle_info(all_trace_off, State) ->
Mspec = [{{ibrowse_conf,{trace,'$1','$2'},true},[],[{{'$1','$2'}}]}],
Trace_on_dests = ets:select(ibrowse_conf, Mspec),
Trace_on_dests = ets:select(?CONF_TABLE, Mspec),
Fun = fun(#lb_pid{host_port = {H, P}, pid = Pid}, _) ->
case lists:member({H, P}, Trace_on_dests) of
false ->
@ -916,8 +916,8 @@ handle_info(all_trace_off, State) ->
(_, Acc) ->
Acc
end,
ets:foldl(Fun, undefined, ibrowse_lb),
ets:select_delete(ibrowse_conf, [{{ibrowse_conf,{trace,'$1','$2'},true},[],['true']}]),
ets:foldl(Fun, undefined, ?LOAD_BALANCER_NAMED_TABLE),
ets:select_delete(?CONF_TABLE, [{{ibrowse_conf,{trace,'$1','$2'},true},[],['true']}]),
{noreply, State};
handle_info({trace, Bool}, State) ->
@ -932,8 +932,8 @@ handle_info({trace, Bool, Host, Port}, State) ->
(_, Acc) ->
Acc
end,
ets:foldl(Fun, undefined, ibrowse_lb),
ets:insert(ibrowse_conf, #ibrowse_conf{key = {trace, Host, Port},
ets:foldl(Fun, undefined, ?LOAD_BALANCER_NAMED_TABLE),
ets:insert(?CONF_TABLE, #ibrowse_conf{key = {trace, Host, Port},
value = Bool}),
{noreply, State};
@ -961,7 +961,7 @@ code_change(_OldVsn, State, _Extra) ->
%%--------------------------------------------------------------------
do_get_connection(#url{host = Host, port = Port}, []) ->
{ok, Pid} = ibrowse_lb:start_link([Host, Port]),
ets:insert(ibrowse_lb, #lb_pid{host_port = {Host, Port}, pid = Pid}),
ets:insert(?LOAD_BALANCER_NAMED_TABLE, #lb_pid{host_port = {Host, Port}, pid = Pid}),
Pid;
do_get_connection(_Url, [#lb_pid{pid = Pid}]) ->
Pid.

+ 33
- 47
src/ibrowse_http_client.erl 查看文件

@ -1,6 +1,9 @@
%%%-------------------------------------------------------------------
%%% File : ibrowse_http_client.erl
%%% Author : Chandrashekhar Mullaparthi <chandrashekhar.mullaparthi@t-mobile.co.uk>
%%% Benjamin Lee <http://github.com/benjaminplee>
%%% Dan Schwabe <http://github.com/dfschwabe>
%%% Brian Richards <http://github.com/richbria>
%%% Description : The name says it all
%%%
%%% Created : 11 Oct 2003 by Chandrashekhar Mullaparthi <chandrashekhar.mullaparthi@t-mobile.co.uk>
@ -19,6 +22,7 @@
start/1,
start/2,
stop/1,
trace/2,
send_req/7
]).
@ -53,7 +57,7 @@
deleted_crlf = false, transfer_encoding,
chunk_size, chunk_size_buffer = <<>>,
recvd_chunk_size, interim_reply_sent = false,
lb_ets_tid, cur_pipeline_size = 0, prev_req_id
lb_ets_tid, prev_req_id
}).
-record(request, {url, method, options, from,
@ -101,6 +105,9 @@ stop(Conn_pid) ->
ok
end.
trace(Conn_pid, Bool) ->
catch Conn_pid ! {trace, Bool}.
send_req(Conn_Pid, Url, Headers, Method, Body, Options, Timeout) ->
gen_server:call(
Conn_Pid,
@ -266,6 +273,7 @@ handle_info(Info, State) ->
%%--------------------------------------------------------------------
terminate(_Reason, State) ->
do_close(State),
shutting_down(State),
ok.
%%--------------------------------------------------------------------
@ -757,11 +765,10 @@ send_req_1(From,
{ok, _Sent_body} ->
trace_request_body(Body_1),
_ = active_once(State_1),
State_1_1 = inc_pipeline_counter(State_1),
State_2 = State_1_1#state{status = get_header,
cur_req = NewReq,
proxy_tunnel_setup = in_progress,
tunnel_setup_queue = [{From, Url, Headers, Method, Body, Options, Timeout}]},
State_2 = State_1#state{status = get_header,
cur_req = NewReq,
proxy_tunnel_setup = in_progress,
tunnel_setup_queue = [{From, Url, Headers, Method, Body, Options, Timeout}]},
State_3 = set_inac_timer(State_2),
{noreply, State_3};
Err ->
@ -801,7 +808,7 @@ send_req_1(From,
{Caller, once} when is_pid(Caller) or
is_atom(Caller) ->
Async_pid_rec = {{req_id_pid, ReqId}, self()},
true = ets:insert(ibrowse_stream, Async_pid_rec),
true = ets:insert(?STREAM_TABLE, Async_pid_rec),
{Caller, true};
undefined ->
{undefined, false};
@ -848,15 +855,14 @@ send_req_1(From,
Raw_req = list_to_binary([Req, Sent_body]),
NewReq_1 = NewReq#request{raw_req = Raw_req},
State_1 = State#state{reqs=queue:in(NewReq_1, State#state.reqs)},
State_2 = inc_pipeline_counter(State_1),
_ = active_once(State_2),
State_3 = case Status of
_ = active_once(State_1),
State_2 = case Status of
idle ->
State_2#state{
State_1#state{
status = get_header,
cur_req = NewReq_1};
_ ->
State_2
State_1
end,
case StreamTo of
undefined ->
@ -870,8 +876,8 @@ send_req_1(From,
catch StreamTo ! {ibrowse_async_raw_req, Raw_req}
end
end,
State_4 = set_inac_timer(State_3),
{noreply, State_4};
State_3 = set_inac_timer(State_2),
{noreply, State_3};
Err ->
shutting_down(State),
do_trace("Send failed... Reason: ~p~n", [Err]),
@ -1810,13 +1816,13 @@ format_response_data(Resp_format, Body) ->
do_reply(State, From, undefined, _, Resp_format, {ok, St_code, Headers, Body}) ->
Msg_1 = {ok, St_code, Headers, format_response_data(Resp_format, Body)},
gen_server:reply(From, Msg_1),
dec_pipeline_counter(State);
report_request_complete(State);
do_reply(State, From, undefined, _, _, Msg) ->
gen_server:reply(From, Msg),
dec_pipeline_counter(State);
report_request_complete(State);
do_reply(#state{prev_req_id = Prev_req_id} = State,
_From, StreamTo, ReqId, Resp_format, {ok, _, _, Body}) ->
State_1 = dec_pipeline_counter(State),
State_1 = report_request_complete(State),
case Body of
[] ->
ok;
@ -1835,10 +1841,10 @@ do_reply(#state{prev_req_id = Prev_req_id} = State,
%% stream_once and sync requests on the same connection, it will
%% take a while for the req_id-pid mapping to get cleared, but it
%% should do no harm.
ets:delete(ibrowse_stream, {req_id_pid, Prev_req_id}),
ets:delete(?STREAM_TABLE, {req_id_pid, Prev_req_id}),
State_1#state{prev_req_id = ReqId};
do_reply(State, _From, StreamTo, ReqId, Resp_format, Msg) ->
State_1 = dec_pipeline_counter(State),
State_1 = report_request_complete(State),
Msg_1 = format_response_data(Resp_format, Msg),
catch StreamTo ! {ibrowse_async_response, ReqId, Msg_1},
State_1.
@ -1853,7 +1859,7 @@ do_error_reply(#state{reqs = Reqs, tunnel_setup_queue = Tun_q} = State, Err) ->
ReqList = queue:to_list(Reqs),
lists:foreach(fun(#request{from=From, stream_to=StreamTo, req_id=ReqId,
response_format = Resp_format}) ->
ets:delete(ibrowse_stream, {req_id_pid, ReqId}),
ets:delete(?STREAM_TABLE, {req_id_pid, ReqId}),
do_reply(State, From, StreamTo, ReqId, Resp_format, {error, Err})
end, ReqList),
lists:foreach(
@ -1943,36 +1949,16 @@ to_lower([], Acc) ->
shutting_down(#state{lb_ets_tid = undefined}) ->
ok;
shutting_down(#state{lb_ets_tid = Tid,
cur_pipeline_size = _Sz}) ->
catch ets:delete(Tid, self()).
shutting_down(#state{lb_ets_tid = Tid}) ->
ibrowse_lb:report_connection_down(Tid).
inc_pipeline_counter(#state{is_closing = true} = State) ->
report_request_complete(#state{is_closing = true} = State) ->
State;
inc_pipeline_counter(#state{lb_ets_tid = undefined} = State) ->
report_request_complete(#state{lb_ets_tid = undefined} = State) ->
State;
inc_pipeline_counter(#state{cur_pipeline_size = Pipe_sz,
lb_ets_tid = Tid} = State) ->
update_counter(Tid, self(), {2,1,99999,9999}),
State#state{cur_pipeline_size = Pipe_sz + 1}.
update_counter(Tid, Key, Args) ->
ets:update_counter(Tid, Key, Args).
dec_pipeline_counter(#state{is_closing = true} = State) ->
State;
dec_pipeline_counter(#state{lb_ets_tid = undefined} = State) ->
State;
dec_pipeline_counter(#state{cur_pipeline_size = Pipe_sz,
lb_ets_tid = Tid} = State) ->
_ = try
update_counter(Tid, self(), {2,-1,0,0}),
update_counter(Tid, self(), {3,-1,0,0})
catch
_:_ ->
ok
end,
State#state{cur_pipeline_size = Pipe_sz - 1}.
report_request_complete(#state{lb_ets_tid = Tid} = State) ->
ibrowse_lb:report_request_complete(Tid),
State.
flatten([H | _] = L) when is_integer(H) ->
L;

+ 131
- 108
src/ibrowse_lb.erl 查看文件

@ -1,23 +1,25 @@
%%%-------------------------------------------------------------------
%%% File : ibrowse_lb.erl
%%% Author : chandru <chandrashekhar.mullaparthi@t-mobile.co.uk>
%%% Benjamin Lee <http://github.com/benjaminplee>
%%% Dan Schwabe <http://github.com/dfschwabe>
%%% Brian Richards <http://github.com/richbria>
%%% Description :
%%%
%%% Created : 6 Mar 2008 by chandru <chandrashekhar.mullaparthi@t-mobile.co.uk>
%%%-------------------------------------------------------------------
-module(ibrowse_lb).
-author(chandru).
-behaviour(gen_server).
%%--------------------------------------------------------------------
%% Include files
%%--------------------------------------------------------------------
%%--------------------------------------------------------------------
%% External exports
-export([
start_link/1,
spawn_connection/6,
stop/1
stop/1,
report_connection_down/1,
report_request_complete/1
]).
%% gen_server callbacks
@ -31,14 +33,18 @@
]).
-record(state, {parent_pid,
ets_tid,
host,
port,
max_sessions,
max_pipeline_size,
num_cur_sessions = 0,
proc_state
}).
ets_tid,
host,
port,
max_sessions,
max_pipeline_size,
proc_state}).
-define(PIPELINE_MAX, 99999).
-define(MAX_RETRIES, 3).
-define(KEY_MATCHSPEC_BY_PID(Pid), [{{{'_', '_', Pid}, '_'}, [], ['$_']}]).
-define(KEY_MATCHSPEC_BY_PID_FOR_DELETE(Pid), [{{{'_', '_', Pid}, '_'}, [], [true]}]).
-define(KEY_MATCHSPEC_FOR_DELETE(Key), [{{Key, '_'}, [], [true]}]).
-include("ibrowse.hrl").
@ -52,10 +58,37 @@
start_link(Args) ->
gen_server:start_link(?MODULE, Args, []).
spawn_connection(Lb_pid,
Url,
Max_sessions,
Max_pipeline_size,
SSL_options,
Process_options)
when is_pid(Lb_pid),
is_record(Url, url),
is_integer(Max_pipeline_size),
is_integer(Max_sessions) ->
gen_server:call(Lb_pid,
{spawn_connection, Url, Max_sessions, Max_pipeline_size, SSL_options, Process_options}).
stop(Lb_pid) ->
case catch gen_server:call(Lb_pid, stop) of
{'EXIT', {timeout, _}} ->
exit(Lb_pid, kill);
ok ->
ok
end.
report_connection_down(Tid) ->
%% Don't cascade errors since Tid is really managed by other process
catch ets:select_delete(Tid, ?KEY_MATCHSPEC_BY_PID_FOR_DELETE(self())).
report_request_complete(Tid) ->
report_request_complete(Tid, ?MAX_RETRIES).
%%====================================================================
%% Server functions
%%====================================================================
%%--------------------------------------------------------------------
%% Function: init/1
%% Description: Initiates the server
@ -66,37 +99,19 @@ start_link(Args) ->
%%--------------------------------------------------------------------
init([Host, Port]) ->
process_flag(trap_exit, true),
Max_sessions = ibrowse:get_config_value({max_sessions, Host, Port}, 10),
Max_pipe_sz = ibrowse:get_config_value({max_pipeline_size, Host, Port}, 10),
put(my_trace_flag, ibrowse_lib:get_trace_status(Host, Port)),
put(ibrowse_trace_token, ["LB: ", Host, $:, integer_to_list(Port)]),
Tid = ets:new(ibrowse_lb, [public, ordered_set]),
{ok, #state{parent_pid = whereis(ibrowse),
host = Host,
port = Port,
ets_tid = Tid,
max_pipeline_size = Max_pipe_sz,
max_sessions = Max_sessions}}.
spawn_connection(Lb_pid, Url,
Max_sessions,
Max_pipeline_size,
SSL_options,
Process_options)
when is_pid(Lb_pid),
is_record(Url, url),
is_integer(Max_pipeline_size),
is_integer(Max_sessions) ->
gen_server:call(Lb_pid,
{spawn_connection, Url, Max_sessions, Max_pipeline_size, SSL_options, Process_options}).
host = Host,
port = Port,
max_pipeline_size = Max_pipe_sz,
max_sessions = Max_sessions}}.
stop(Lb_pid) ->
case catch gen_server:call(Lb_pid, stop) of
{'EXIT', {timeout, _}} ->
exit(Lb_pid, kill);
ok ->
ok
end.
%%--------------------------------------------------------------------
%% Function: handle_call/3
%% Description: Handling call messages
@ -107,40 +122,29 @@ stop(Lb_pid) ->
%% {stop, Reason, Reply, State} | (terminate/2 is called)
%% {stop, Reason, State} (terminate/2 is called)
%%--------------------------------------------------------------------
handle_call(stop, _From, #state{ets_tid = undefined} = State) ->
gen_server:reply(_From, ok),
{stop, normal, State};
handle_call(stop, _From, #state{ets_tid = Tid} = State) ->
ets:foldl(fun({Pid, _, _}, Acc) ->
ibrowse_http_client:stop(Pid),
Acc
end, [], Tid),
for_each_connection_pid(Tid, fun(Pid) -> ibrowse_http_client:stop(Pid) end),
gen_server:reply(_From, ok),
{stop, normal, State};
handle_call(_, _From, #state{proc_state = shutting_down} = State) ->
{reply, {error, shutting_down}, State};
%% Update max_sessions in #state with supplied value
handle_call({spawn_connection, _Url, Max_sess, Max_pipe, _, _}, _From,
#state{num_cur_sessions = Num} = State)
when Num >= Max_sess ->
State_1 = maybe_create_ets(State),
Reply = find_best_connection(State_1#state.ets_tid, Max_pipe),
{reply, Reply, State_1#state{max_sessions = Max_sess,
max_pipeline_size = Max_pipe}};
handle_call({spawn_connection, Url, Max_sess, Max_pipe, SSL_options, Process_options}, _From,
#state{num_cur_sessions = Cur} = State) ->
handle_call({spawn_connection, Url, Max_sess, Max_pipe, SSL_options, Process_options}, _From, State) ->
State_1 = maybe_create_ets(State),
Tid = State_1#state.ets_tid,
{ok, Pid} = ibrowse_http_client:start_link({Tid, Url, SSL_options}, Process_options),
ets:insert(Tid, {Pid, 0, 0}),
{reply, {ok, Pid}, State_1#state{num_cur_sessions = Cur + 1,
max_sessions = Max_sess,
max_pipeline_size = Max_pipe}};
Reply = case num_current_connections(Tid) of
X when X >= Max_sess ->
find_best_connection(Tid, Max_pipe);
_ ->
Result = {ok, Pid} = ibrowse_http_client:start_link({Tid, Url, SSL_options}, Process_options),
record_new_connection(Tid, Pid),
Result
end,
{reply, Reply, State_1#state{max_sessions = Max_sess, max_pipeline_size = Max_pipe}};
handle_call(Request, _From, State) ->
Reply = {unknown_request, Request},
@ -165,43 +169,20 @@ handle_cast(_Msg, State) ->
%%--------------------------------------------------------------------
handle_info({'EXIT', Parent, _Reason}, #state{parent_pid = Parent} = State) ->
{stop, normal, State};
handle_info({'EXIT', _Pid, _Reason}, #state{ets_tid = undefined} = State) ->
{noreply, State};
handle_info({'EXIT', Pid, _Reason},
#state{num_cur_sessions = Cur,
ets_tid = Tid} = State) ->
ets:match_delete(Tid, {{'_', Pid}, '_'}),
Cur_1 = Cur - 1,
case Cur_1 of
0 ->
ets:delete(Tid),
{noreply, State#state{ets_tid = undefined, num_cur_sessions = 0}, 10000};
_ ->
{noreply, State#state{num_cur_sessions = Cur_1}}
end;
handle_info({trace, Bool}, #state{ets_tid = undefined} = State) ->
put(my_trace_flag, Bool),
{noreply, State};
handle_info({trace, Bool}, #state{ets_tid = Tid} = State) ->
ets:foldl(fun({{_, Pid}, _}, Acc) when is_pid(Pid) ->
catch Pid ! {trace, Bool},
Acc;
(_, Acc) ->
Acc
end, undefined, Tid),
for_each_connection_pid(Tid, fun(Pid) -> ibrowse_http_client:trace(Pid, Bool) end),
put(my_trace_flag, Bool),
{noreply, State};
handle_info(timeout, State) ->
%% We can't shutdown the process immediately because a request
%% might be in flight. So we first remove the entry from the
%% ibrowse_lb ets table, and then shutdown a couple of seconds
%% later
ets:delete(ibrowse_lb, {State#state.host, State#state.port}),
%% load balancer named ets table, and then shutdown a couple
%% of seconds later
ets:delete(?LOAD_BALANCER_NAMED_TABLE, {State#state.host, State#state.port}),
erlang:send_after(2000, self(), shutdown),
{noreply, State#state{proc_state = shutting_down}};
@ -219,7 +200,7 @@ handle_info(_Info, State) ->
terminate(_Reason, #state{host = Host, port = Port}) ->
% Use delete_object instead of delete in case another process for this host/port
% has been spawned, in which case will be deleting the wrong record because pid won't match.
ets:delete_object(ibrowse_lb, #lb_pid{host_port = {Host, Port}, pid = self()}),
ets:delete_object(?LOAD_BALANCER_NAMED_TABLE, #lb_pid{host_port = {Host, Port}, pid = self()}),
ok.
%%--------------------------------------------------------------------
@ -233,31 +214,73 @@ code_change(_OldVsn, State, _Extra) ->
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
find_best_connection(Tid, Max_pipe) ->
ets:safe_fixtable(Tid, true),
Res = find_best_connection(ets:first(Tid), Tid, Max_pipe),
ets:safe_fixtable(Tid, false),
Res.
find_best_connection(Tid, Max_pipeline_size) ->
find_best_connection(Tid, Max_pipeline_size, ?MAX_RETRIES).
find_best_connection('$end_of_table', _, _) ->
find_best_connection(_Tid, _Max_pipeline_size, 0) ->
{error, retry_later};
find_best_connection(Pid, Tid, Max_pipe) ->
case ets:lookup(Tid, Pid) of
[{Pid, Cur_sz, Speculative_sz}] when Cur_sz < Max_pipe,
Speculative_sz < Max_pipe ->
case catch ets:update_counter(Tid, Pid, {3, 1, 9999999, 9999999}) of
{'EXIT', _} ->
%% The selected process has shutdown
find_best_connection(ets:next(Tid, Pid), Tid, Max_pipe);
_ ->
{ok, Pid}
find_best_connection(Tid, Max_pipeline_size, RemainingRetries) ->
case ets:first(Tid) of
{Size, _Timestamp, Pid} = Key when Size < Max_pipeline_size ->
case record_request_for_connection(Tid, Key) of
true ->
{ok, Pid};
false ->
find_best_connection(Tid, Max_pipeline_size, RemainingRetries - 1)
end;
_ ->
find_best_connection(ets:next(Tid, Pid), Tid, Max_pipe)
_ ->
{error, retry_later}
end.
maybe_create_ets(#state{ets_tid = undefined} = State) ->
Tid = ets:new(ibrowse_lb, [public, ordered_set]),
Tid = ets:new(?CONNECTIONS_LOCAL_TABLE, [public, ordered_set]),
State#state{ets_tid = Tid};
maybe_create_ets(State) ->
State.
%% Ets connection table utility methods
num_current_connections(Tid) ->
catch ets:info(Tid, size).
record_new_connection(Tid, Pid) ->
catch ets:insert(Tid, {new_key(Pid), undefined}).
record_request_for_connection(Tid, Key) ->
case ets:select_delete(Tid, ?KEY_MATCHSPEC_FOR_DELETE(Key)) of
1 ->
ets:insert(Tid, {incremented(Key), undefined}),
true;
_ ->
false
end.
report_request_complete(_Tid, 0) ->
false;
report_request_complete(Tid, RemainingRetries) ->
%% Don't cascade errors since Tid is really managed by other process
catch case ets:select(Tid, ?KEY_MATCHSPEC_BY_PID(self())) of
[{MatchKey, _}] ->
case ets:select_delete(Tid, ?KEY_MATCHSPEC_FOR_DELETE(MatchKey)) of
1 ->
ets:insert(Tid, {decremented(MatchKey), undefined}),
true;
_ ->
report_request_complete(Tid, RemainingRetries - 1)
end;
_ ->
false
end.
new_key(Pid) ->
{1, os:timestamp(), Pid}.
incremented({Size, Timestamp, Pid}) ->
{Size + 1, Timestamp, Pid}.
decremented({Size, _Timestamp, Pid}) ->
{Size - 1, os:timestamp(), Pid}.
for_each_connection_pid(Tid, Fun) ->
ets:foldl(fun({{_, _, Pid}, _}, _) -> Fun(Pid) end, undefined, Tid),
ok.

+ 171
- 0
test/ibrowse_functional_tests.erl 查看文件

@ -0,0 +1,171 @@
%%% File : ibrowse_functional_tests.erl
%%% Authors : Benjamin Lee <http://github.com/benjaminplee>
%%% Dan Schwabe <http://github.com/dfschwabe>
%%% Brian Richards <http://github.com/richbria>
%%% Description : Functional tests of the ibrowse library using a live test HTTP server
%%% Created : 18 November 2014 by Benjamin Lee <yardspoon@gmail.com>
-module(ibrowse_functional_tests).
-include_lib("eunit/include/eunit.hrl").
-define(PER_TEST_TIMEOUT_SEC, 60).
-define(TIMEDTEST(Desc, Fun), {Desc, {timeout, ?PER_TEST_TIMEOUT_SEC, fun Fun/0}}).
-define(SERVER_PORT, 8181).
-define(BASE_URL, "http://localhost:" ++ integer_to_list(?SERVER_PORT)).
-define(SHORT_TIMEOUT_MS, 5000).
-define(LONG_TIMEOUT_MS, 30000).
-define(PAUSE_FOR_CONNECTIONS_MS, 2000).
setup() ->
application:start(crypto),
application:start(public_key),
application:start(ssl),
ibrowse_test_server:start_server(?SERVER_PORT, tcp),
ibrowse:start(),
ok.
teardown(_) ->
ibrowse:stop(),
ibrowse_test_server:stop_server(?SERVER_PORT),
ok.
running_server_fixture_test_() ->
{foreach,
fun setup/0,
fun teardown/1,
[
?TIMEDTEST("Simple request can be honored", simple_request),
?TIMEDTEST("Slow server causes timeout", slow_server_timeout),
?TIMEDTEST("Pipeline depth goes down with responses", pipeline_depth),
?TIMEDTEST("Pipelines refill", pipeline_refill),
?TIMEDTEST("Timeout closes pipe", closing_pipes),
?TIMEDTEST("Requests are balanced over connections", balanced_connections),
?TIMEDTEST("Pipeline too small signals retries", small_pipeline),
?TIMEDTEST("Dest status can be gathered", status)
]
}.
simple_request() ->
?assertMatch({ok, "200", _, _}, ibrowse:send_req(?BASE_URL, [], get, [], [])).
slow_server_timeout() ->
?assertMatch({error, req_timedout}, ibrowse:send_req(?BASE_URL ++ "/never_respond", [], get, [], [], 5000)).
pipeline_depth() ->
MaxSessions = 2,
MaxPipeline = 2,
RequestsSent = 2,
EmptyPipelineDepth = 0,
?assertEqual([], ibrowse_test_server:get_conn_pipeline_depth()),
Fun = fun() -> ibrowse:send_req(?BASE_URL, [], get, [], [{max_sessions, MaxSessions}, {max_pipeline_size, MaxPipeline}], ?SHORT_TIMEOUT_MS) end,
times(RequestsSent, fun() -> spawn_link(Fun) end),
timer:sleep(?PAUSE_FOR_CONNECTIONS_MS),
Counts = [Count || {_Pid, Count} <- ibrowse_test_server:get_conn_pipeline_depth()],
?assertEqual(MaxSessions, length(Counts)),
?assertEqual(lists:duplicate(MaxSessions, EmptyPipelineDepth), Counts).
pipeline_refill() ->
MaxSessions = 2,
MaxPipeline = 2,
RequestsToFill = MaxSessions * MaxPipeline,
%% Send off enough requests to fill sessions and pipelines in rappid succession
Fun = fun() -> ibrowse:send_req(?BASE_URL, [], get, [], [{max_sessions, MaxSessions}, {max_pipeline_size, MaxPipeline}], ?SHORT_TIMEOUT_MS) end,
times(RequestsToFill, fun() -> spawn_link(Fun) end),
timer:sleep(?PAUSE_FOR_CONNECTIONS_MS),
% Verify that connections properly reported their completed responses and can still accept more
?assertMatch({ok, "200", _, _}, ibrowse:send_req(?BASE_URL, [], get, [], [{max_sessions, MaxSessions}, {max_pipeline_size, MaxPipeline}], ?SHORT_TIMEOUT_MS)),
% and do it again to make sure we really are clear
times(RequestsToFill, fun() -> spawn_link(Fun) end),
timer:sleep(?PAUSE_FOR_CONNECTIONS_MS),
% Verify that connections properly reported their completed responses and can still accept more
?assertMatch({ok, "200", _, _}, ibrowse:send_req(?BASE_URL, [], get, [], [{max_sessions, MaxSessions}, {max_pipeline_size, MaxPipeline}], ?SHORT_TIMEOUT_MS)).
closing_pipes() ->
MaxSessions = 2,
MaxPipeline = 2,
RequestsSent = 2,
BalancedNumberOfRequestsPerConnection = 1,
?assertEqual([], ibrowse_test_server:get_conn_pipeline_depth()),
Fun = fun() -> ibrowse:send_req(?BASE_URL ++ "/never_respond", [], get, [], [{max_sessions, MaxSessions}, {max_pipeline_size, MaxPipeline}], ?SHORT_TIMEOUT_MS) end,
times(RequestsSent, fun() -> spawn_link(Fun) end),
timer:sleep(?PAUSE_FOR_CONNECTIONS_MS),
Counts = [Count || {_Pid, Count} <- ibrowse_test_server:get_conn_pipeline_depth()],
?assertEqual(MaxSessions, length(Counts)),
?assertEqual(lists:duplicate(MaxSessions, BalancedNumberOfRequestsPerConnection), Counts),
timer:sleep(?SHORT_TIMEOUT_MS),
?assertEqual([], ibrowse_test_server:get_conn_pipeline_depth()).
balanced_connections() ->
MaxSessions = 4,
MaxPipeline = 100,
RequestsSent = 80,
BalancedNumberOfRequestsPerConnection = 20,
?assertEqual([], ibrowse_test_server:get_conn_pipeline_depth()),
Fun = fun() -> ibrowse:send_req(?BASE_URL ++ "/never_respond", [], get, [], [{max_sessions, MaxSessions}, {max_pipeline_size, MaxPipeline}], ?LONG_TIMEOUT_MS) end,
times(RequestsSent, fun() -> spawn_link(Fun) end),
timer:sleep(?PAUSE_FOR_CONNECTIONS_MS),
Counts = [Count || {_Pid, Count} <- ibrowse_test_server:get_conn_pipeline_depth()],
?assertEqual(MaxSessions, length(Counts)),
?assertEqual(lists:duplicate(MaxSessions, BalancedNumberOfRequestsPerConnection), Counts).
small_pipeline() ->
MaxSessions = 10,
MaxPipeline = 10,
RequestsSent = 100,
FullRequestsPerConnection = 10,
?assertEqual([], ibrowse_test_server:get_conn_pipeline_depth()),
Fun = fun() -> ibrowse:send_req(?BASE_URL ++ "/never_respond", [], get, [], [{max_sessions, MaxSessions}, {max_pipeline_size, MaxPipeline}], ?SHORT_TIMEOUT_MS) end,
times(RequestsSent, fun() -> spawn(Fun) end),
timer:sleep(?PAUSE_FOR_CONNECTIONS_MS), %% Wait for everyone to get in line
Counts = [Count || {_Pid, Count} <- ibrowse_test_server:get_conn_pipeline_depth()],
?assertEqual(MaxSessions, length(Counts)),
?assertEqual(lists:duplicate(MaxSessions, FullRequestsPerConnection), Counts),
Response = ibrowse:send_req(?BASE_URL ++ "/never_respond", [], get, [], [{max_sessions, MaxSessions}, {max_pipeline_size, MaxPipeline}], ?SHORT_TIMEOUT_MS),
?assertEqual({error, retry_later}, Response).
status() ->
MaxSessions = 10,
MaxPipeline = 10,
RequestsSent = 100,
Fun = fun() -> ibrowse:send_req(?BASE_URL ++ "/never_respond", [], get, [], [{max_sessions, MaxSessions}, {max_pipeline_size, MaxPipeline}], ?SHORT_TIMEOUT_MS) end,
times(RequestsSent, fun() -> spawn(Fun) end),
timer:sleep(?PAUSE_FOR_CONNECTIONS_MS), %% Wait for everyone to get in line
ibrowse:show_dest_status(),
ibrowse:show_dest_status("http://localhost:8181").
times(0, _) ->
ok;
times(X, Fun) ->
Fun(),
times(X - 1, Fun).

+ 36
- 36
test/ibrowse_test.erl 查看文件

@ -208,42 +208,42 @@ dump_errors(Key, Iod) ->
%% Unit Tests
%%------------------------------------------------------------------------------
-define(TEST_LIST, [{"http://intranet/messenger", get},
{"http://www.google.co.uk", get},
{"http://www.google.com", get},
{"http://www.google.com", options},
{"https://mail.google.com", get},
{"http://www.sun.com", get},
{"http://www.oracle.com", get},
{"http://www.bbc.co.uk", get},
{"http://www.bbc.co.uk", trace},
{"http://www.bbc.co.uk", options},
{"http://yaws.hyber.org", get},
{"http://jigsaw.w3.org/HTTP/ChunkedScript", get},
{"http://jigsaw.w3.org/HTTP/TE/foo.txt", get},
{"http://jigsaw.w3.org/HTTP/TE/bar.txt", get},
{"http://jigsaw.w3.org/HTTP/connection.html", get},
{"http://jigsaw.w3.org/HTTP/cc.html", get},
{"http://jigsaw.w3.org/HTTP/cc-private.html", get},
{"http://jigsaw.w3.org/HTTP/cc-proxy-revalidate.html", get},
{"http://jigsaw.w3.org/HTTP/cc-nocache.html", get},
{"http://jigsaw.w3.org/HTTP/h-content-md5.html", get},
{"http://jigsaw.w3.org/HTTP/h-retry-after.html", get},
{"http://jigsaw.w3.org/HTTP/h-retry-after-date.html", get},
{"http://jigsaw.w3.org/HTTP/neg", get},
{"http://jigsaw.w3.org/HTTP/negbad", get},
{"http://jigsaw.w3.org/HTTP/400/toolong/", get},
{"http://jigsaw.w3.org/HTTP/300/", get},
{"http://jigsaw.w3.org/HTTP/Basic/", get, [{basic_auth, {"guest", "guest"}}]},
{"http://jigsaw.w3.org/HTTP/CL/", get},
{"http://www.httpwatch.com/httpgallery/chunked/", get},
{"https://github.com", get, [{ssl_options, [{depth, 2}]}]},
{local_test_fun, test_20122010, []},
{local_test_fun, test_pipeline_head_timeout, []},
{local_test_fun, test_head_transfer_encoding, []},
{local_test_fun, test_head_response_with_body, []},
{local_test_fun, test_303_response_with_a_body, []},
{local_test_fun, test_binary_headers, []}
]).
{"http://www.google.co.uk", get},
{"http://www.google.com", get},
{"http://www.google.com", options},
{"https://mail.google.com", get},
{"http://www.sun.com", get},
{"http://www.oracle.com", get},
{"http://www.bbc.co.uk", get},
{"http://www.bbc.co.uk", trace},
{"http://www.bbc.co.uk", options},
{"http://yaws.hyber.org", get},
{"http://jigsaw.w3.org/HTTP/ChunkedScript", get},
{"http://jigsaw.w3.org/HTTP/TE/foo.txt", get},
{"http://jigsaw.w3.org/HTTP/TE/bar.txt", get},
{"http://jigsaw.w3.org/HTTP/connection.html", get},
{"http://jigsaw.w3.org/HTTP/cc.html", get},
{"http://jigsaw.w3.org/HTTP/cc-private.html", get},
{"http://jigsaw.w3.org/HTTP/cc-proxy-revalidate.html", get},
{"http://jigsaw.w3.org/HTTP/cc-nocache.html", get},
{"http://jigsaw.w3.org/HTTP/h-content-md5.html", get},
{"http://jigsaw.w3.org/HTTP/h-retry-after.html", get},
{"http://jigsaw.w3.org/HTTP/h-retry-after-date.html", get},
{"http://jigsaw.w3.org/HTTP/neg", get},
{"http://jigsaw.w3.org/HTTP/negbad", get},
{"http://jigsaw.w3.org/HTTP/400/toolong/", get},
{"http://jigsaw.w3.org/HTTP/300/", get},
{"http://jigsaw.w3.org/HTTP/Basic/", get, [{basic_auth, {"guest", "guest"}}]},
{"http://jigsaw.w3.org/HTTP/CL/", get},
{"http://www.httpwatch.com/httpgallery/chunked/", get},
{"https://github.com", get, [{ssl_options, [{depth, 2}]}]},
{local_test_fun, test_20122010, []},
{local_test_fun, test_pipeline_head_timeout, []},
{local_test_fun, test_head_transfer_encoding, []},
{local_test_fun, test_head_response_with_body, []},
{local_test_fun, test_303_response_with_a_body, []},
{local_test_fun, test_binary_headers, []}
]).
unit_tests() ->
unit_tests([]).

+ 39
- 12
test/ibrowse_test_server.erl 查看文件

@ -1,22 +1,29 @@
%%% File : ibrowse_test_server.erl
%%% Author : Chandrashekhar Mullaparthi <chandrashekhar.mullaparthi@t-mobile.co.uk>
%%% Benjamin Lee <http://github.com/benjaminplee>
%%% Dan Schwabe <http://github.com/dfschwabe>
%%% Brian Richards <http://github.com/richbria>
%%% Description : A server to simulate various test scenarios
%%% Created : 17 Oct 2010 by Chandrashekhar Mullaparthi <chandrashekhar.mullaparthi@t-mobile.co.uk>
-module(ibrowse_test_server).
-export([
start_server/2,
stop_server/1
stop_server/1,
get_conn_pipeline_depth/0
]).
-record(request, {method, uri, version, headers = [], body = []}).
-define(dec2hex(X), erlang:integer_to_list(X, 16)).
-define(ACCEPT_TIMEOUT_MS, 1000).
-define(CONN_PIPELINE_DEPTH, conn_pipeline_depth).
start_server(Port, Sock_type) ->
Fun = fun() ->
Name = server_proc_name(Port),
register(Name, self()),
ets:new(?CONN_PIPELINE_DEPTH, [named_table, public, set]),
case do_listen(Sock_type, Port, [{active, false},
{reuseaddr, true},
{nodelay, true},
@ -38,8 +45,12 @@ start_server(Port, Sock_type) ->
stop_server(Port) ->
server_proc_name(Port) ! stop,
timer:sleep(2000), % wait for server to receive msg and unregister
ok.
get_conn_pipeline_depth() ->
ets:tab2list(?CONN_PIPELINE_DEPTH).
server_proc_name(Port) ->
list_to_atom("ibrowse_test_server_"++integer_to_list(Port)).
@ -51,24 +62,36 @@ do_listen(ssl, Port, Opts) ->
ssl:listen(Port, Opts).
do_accept(tcp, Listen_sock) ->
gen_tcp:accept(Listen_sock);
gen_tcp:accept(Listen_sock, ?ACCEPT_TIMEOUT_MS);
do_accept(ssl, Listen_sock) ->
ssl:ssl_accept(Listen_sock).
ssl:ssl_accept(Listen_sock, ?ACCEPT_TIMEOUT_MS).
accept_loop(Sock, Sock_type) ->
case do_accept(Sock_type, Sock) of
{ok, Conn} ->
Pid = spawn_link(
fun() ->
server_loop(Conn, Sock_type, #request{})
end),
Pid = spawn_link(fun() -> connection(Conn, Sock_type) end),
set_controlling_process(Conn, Sock_type, Pid),
Pid ! {setopts, [{active, true}]},
accept_loop(Sock, Sock_type);
{error, timeout} ->
receive
stop ->
ok
after 10 ->
accept_loop(Sock, Sock_type)
end;
Err ->
Err
end.
connection(Conn, Sock_type) ->
catch ets:insert(?CONN_PIPELINE_DEPTH, {self(), 0}),
try
server_loop(Conn, Sock_type, #request{})
after
catch ets:delete(?CONN_PIPELINE_DEPTH, self())
end.
set_controlling_process(Sock, tcp, Pid) ->
gen_tcp:controlling_process(Sock, Pid);
set_controlling_process(Sock, ssl, Pid) ->
@ -82,13 +105,19 @@ setopts(Sock, ssl, Opts) ->
server_loop(Sock, Sock_type, #request{headers = Headers} = Req) ->
receive
{http, Sock, {http_request, HttpMethod, HttpUri, HttpVersion}} ->
ets:update_counter(?CONN_PIPELINE_DEPTH, self(), 1),
server_loop(Sock, Sock_type, Req#request{method = HttpMethod,
uri = HttpUri,
version = HttpVersion});
{http, Sock, {http_header, _, _, _, _} = H} ->
server_loop(Sock, Sock_type, Req#request{headers = [H | Headers]});
{http, Sock, http_eoh} ->
process_request(Sock, Sock_type, Req),
case process_request(Sock, Sock_type, Req) of
not_done ->
ok;
_ ->
ets:update_counter(?CONN_PIPELINE_DEPTH, self(), -1)
end,
server_loop(Sock, Sock_type, #request{});
{http, Sock, {http_error, Err}} ->
do_trace("Error parsing HTTP request:~n"
@ -101,8 +130,6 @@ server_loop(Sock, Sock_type, #request{headers = Headers} = Req) ->
{tcp_closed, Sock} ->
do_trace("Client closed connection~n", []),
ok;
stop ->
ok;
Other ->
do_trace("Recvd unknown msg: ~p~n", [Other]),
exit({unknown_msg, Other})
@ -155,7 +182,6 @@ process_request(Sock, Sock_type,
uri = {abs_path, "/ibrowse_head_transfer_enc"}}) ->
Resp = <<"HTTP/1.1 400 Bad Request\r\nServer: Apache-Coyote/1.1\r\nContent-Length:5\r\nDate: Wed, 04 Apr 2012 16:53:49 GMT\r\n\r\nabcde">>,
do_send(Sock, Sock_type, Resp);
process_request(Sock, Sock_type,
#request{method='GET',
headers = Headers,
@ -192,6 +218,8 @@ process_request(Sock, Sock_type,
uri = {abs_path, "/ibrowse_303_with_body_test"}}) ->
Resp = <<"HTTP/1.1 303 See Other\r\nLocation: http://example.org\r\nContent-Length: 5\r\n\r\nabcde">>,
do_send(Sock, Sock_type, Resp);
process_request(_Sock, _Sock_type, #request{uri = {abs_path, "/never_respond"} } ) ->
not_done;
process_request(Sock, Sock_type, Req) ->
do_trace("Recvd req: ~p~n", [Req]),
Resp = <<"HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n">>,
@ -202,7 +230,6 @@ do_send(Sock, tcp, Resp) ->
do_send(Sock, ssl, Resp) ->
ssl:send(Sock, Resp).
%%------------------------------------------------------------------------------
%% Utility functions
%%------------------------------------------------------------------------------

正在加载...
取消
保存