Ver código fonte

29-07-2009 - * The ETS table created for load balancing of requests was not

being deleted which led to the node not being able to create
               any more ETS tables if queries were made to many number of
               webservers. ibrowse now deletes the ETS table it creates once the
               last connection to a webserver is dropped.
               Reported by Seth Falcon.
             * Spurious data being returned at end of body in certain cases of
               chunked encoded responses from the server.
               Reported by Chris Newcombe.
pull/16/head
Chandrashekhar Mullaparthi 15 anos atrás
pai
commit
6991be2977
7 arquivos alterados com 113 adições e 21 exclusões
  1. +11
    -1
      README
  2. +8
    -2
      doc/ibrowse.html
  3. +40
    -1
      src/ibrowse.erl
  4. +19
    -8
      src/ibrowse_http_client.erl
  5. +30
    -8
      src/ibrowse_lb.erl
  6. +4
    -0
      src/ibrowse_test.erl
  7. +1
    -1
      vsn.mk

+ 11
- 1
README Ver arquivo

@ -18,12 +18,22 @@ ibrowse is available under two different licenses. LGPL and the BSD license.
Comments to : Chandrashekhar.Mullaparthi@gmail.com Comments to : Chandrashekhar.Mullaparthi@gmail.com
Version : 1.5.1
Version : 1.5.2
Latest version : git://github.com/cmullaparthi/ibrowse.git Latest version : git://github.com/cmullaparthi/ibrowse.git
CONTRIBUTIONS & CHANGE HISTORY CONTRIBUTIONS & CHANGE HISTORY
============================== ==============================
29-07-2009 - * The ETS table created for load balancing of requests was not
being deleted which led to the node not being able to create
any more ETS tables if queries were made to many number of
webservers. ibrowse now deletes the ETS table it creates once the
last connection to a webserver is dropped.
Reported by Seth Falcon.
* Spurious data being returned at end of body in certain cases of
chunked encoded responses from the server.
Reported by Chris Newcombe.
03-07-2009 - Added option {stream_to, {Pid, once}} which allows the caller 03-07-2009 - Added option {stream_to, {Pid, once}} which allows the caller
to control when it wants to receive more data. If this option to control when it wants to receive more data. If this option
is used, the call ibrowse:stream_next(Req_id) should be used is used, the call ibrowse:stream_next(Req_id) should be used

+ 8
- 2
doc/ibrowse.html Ver arquivo

@ -12,7 +12,7 @@
<ul class="index"><li><a href="#description">Description</a></li><li><a href="#index">Function Index</a></li><li><a href="#functions">Function Details</a></li></ul>The ibrowse application implements an HTTP 1.1 client. <ul class="index"><li><a href="#description">Description</a></li><li><a href="#index">Function Index</a></li><li><a href="#functions">Function Details</a></li></ul>The ibrowse application implements an HTTP 1.1 client.
<p>Copyright © 2005-2009 Chandrashekhar Mullaparthi</p> <p>Copyright © 2005-2009 Chandrashekhar Mullaparthi</p>
<p><b>Version:</b> 1.5.1</p>
<p><b>Version:</b> 1.5.2</p>
<p><b>Behaviours:</b> <a href="gen_server.html"><tt>gen_server</tt></a>.</p> <p><b>Behaviours:</b> <a href="gen_server.html"><tt>gen_server</tt></a>.</p>
<p><b>Authors:</b> Chandrashekhar Mullaparthi (<a href="mailto:chandrashekhar dot mullaparthi at gmail dot com"><tt>chandrashekhar dot mullaparthi at gmail dot com</tt></a>).</p> <p><b>Authors:</b> Chandrashekhar Mullaparthi (<a href="mailto:chandrashekhar dot mullaparthi at gmail dot com"><tt>chandrashekhar dot mullaparthi at gmail dot com</tt></a>).</p>
@ -90,6 +90,7 @@ send_req/4, send_req/5, send_req/6.

<tr><td valign="top"><a href="#set_dest-3">set_dest/3</a></td><td>Deprecated.</td></tr> <tr><td valign="top"><a href="#set_dest-3">set_dest/3</a></td><td>Deprecated.</td></tr>
<tr><td valign="top"><a href="#set_max_pipeline_size-3">set_max_pipeline_size/3</a></td><td>Set the maximum pipeline size for each connection to a specific Host:Port.</td></tr> <tr><td valign="top"><a href="#set_max_pipeline_size-3">set_max_pipeline_size/3</a></td><td>Set the maximum pipeline size for each connection to a specific Host:Port.</td></tr>
<tr><td valign="top"><a href="#set_max_sessions-3">set_max_sessions/3</a></td><td>Set the maximum number of connections allowed to a specific Host:Port.</td></tr> <tr><td valign="top"><a href="#set_max_sessions-3">set_max_sessions/3</a></td><td>Set the maximum number of connections allowed to a specific Host:Port.</td></tr>
<tr><td valign="top"><a href="#show_dest_status-0">show_dest_status/0</a></td><td></td></tr>
<tr><td valign="top"><a href="#show_dest_status-2">show_dest_status/2</a></td><td>Shows some internal information about load balancing to a <tr><td valign="top"><a href="#show_dest_status-2">show_dest_status/2</a></td><td>Shows some internal information about load balancing to a
specified Host:Port.</td></tr> specified Host:Port.</td></tr>
<tr><td valign="top"><a href="#spawn_link_worker_process-2">spawn_link_worker_process/2</a></td><td>Same as spawn_worker_process/2 except the the calling process <tr><td valign="top"><a href="#spawn_link_worker_process-2">spawn_link_worker_process/2</a></td><td>Same as spawn_worker_process/2 except the the calling process
@ -320,6 +321,11 @@ send_req/4, send_req/5, send_req/6.

<p><tt>set_max_sessions(Host::string(), Port::integer(), Max::integer()) -&gt; ok</tt></p> <p><tt>set_max_sessions(Host::string(), Port::integer(), Max::integer()) -&gt; ok</tt></p>
</div><p>Set the maximum number of connections allowed to a specific Host:Port.</p> </div><p>Set the maximum number of connections allowed to a specific Host:Port.</p>
<h3 class="function"><a name="show_dest_status-0">show_dest_status/0</a></h3>
<div class="spec">
<p><tt>show_dest_status() -&gt; any()</tt></p>
</div>
<h3 class="function"><a name="show_dest_status-2">show_dest_status/2</a></h3> <h3 class="function"><a name="show_dest_status-2">show_dest_status/2</a></h3>
<div class="spec"> <div class="spec">
<p><tt>show_dest_status(Host, Port) -&gt; any()</tt></p> <p><tt>show_dest_status(Host, Port) -&gt; any()</tt></p>
@ -411,6 +417,6 @@ send_req/4, send_req/5, send_req/6.

<hr> <hr>
<div class="navbar"><a name="#navbar_bottom"></a><table width="100%" border="0" cellspacing="0" cellpadding="2" summary="navigation bar"><tr><td><a href="overview-summary.html" target="overviewFrame">Overview</a></td><td><a href="http://www.erlang.org/"><img src="erlang.png" align="right" border="0" alt="erlang logo"></a></td></tr></table></div> <div class="navbar"><a name="#navbar_bottom"></a><table width="100%" border="0" cellspacing="0" cellpadding="2" summary="navigation bar"><tr><td><a href="overview-summary.html" target="overviewFrame">Overview</a></td><td><a href="http://www.erlang.org/"><img src="erlang.png" align="right" border="0" alt="erlang logo"></a></td></tr></table></div>
<p><i>Generated by EDoc, Jul 7 2009, 23:13:24.</i></p>
<p><i>Generated by EDoc, Jul 29 2009, 18:43:30.</i></p>
</body> </body>
</html> </html>

+ 40
- 1
src/ibrowse.erl Ver arquivo

@ -7,7 +7,7 @@
%%%------------------------------------------------------------------- %%%-------------------------------------------------------------------
%% @author Chandrashekhar Mullaparthi <chandrashekhar dot mullaparthi at gmail dot com> %% @author Chandrashekhar Mullaparthi <chandrashekhar dot mullaparthi at gmail dot com>
%% @copyright 2005-2009 Chandrashekhar Mullaparthi %% @copyright 2005-2009 Chandrashekhar Mullaparthi
%% @version 1.5.1
%% @version 1.5.2
%% @doc The ibrowse application implements an HTTP 1.1 client. This %% @doc The ibrowse application implements an HTTP 1.1 client. This
%% module implements the API of the HTTP client. There is one named %% module implements the API of the HTTP client. There is one named
%% process called 'ibrowse' which assists in load balancing and maintaining configuration. There is one load balancing process per unique webserver. There is %% process called 'ibrowse' which assists in load balancing and maintaining configuration. There is one load balancing process per unique webserver. There is
@ -98,6 +98,7 @@
trace_on/2, trace_on/2,
trace_off/2, trace_off/2,
all_trace_off/0, all_trace_off/0,
show_dest_status/0,
show_dest_status/2 show_dest_status/2
]). ]).
@ -480,6 +481,44 @@ all_trace_off() ->
ibrowse ! all_trace_off, ibrowse ! all_trace_off,
ok. ok.
show_dest_status() ->
Dests = lists:filter(fun({lb_pid, {Host, Port}, _}) when is_list(Host),
is_integer(Port) ->
true;
(_) ->
false
end, ets:tab2list(ibrowse_lb)),
All_ets = ets:all(),
io:format("~-40.40s | ~-5.5s | ~-10.10s | ~s~n",
["Server:port", "ETS", "Num conns", "LB Pid"]),
io:format("~80.80.=s~n", [""]),
lists:foreach(fun({lb_pid, {Host, Port}, Lb_pid}) ->
case lists:dropwhile(
fun(Tid) ->
ets:info(Tid, owner) /= Lb_pid
end, All_ets) of
[] ->
io:format("~40.40s | ~-5.5s | ~-5.5s | ~s~n",
[Host ++ ":" ++ integer_to_list(Port),
"",
"",
io_lib:format("~p", [Lb_pid])]
);
[Tid | _] ->
catch (
begin
Size = ets:info(Tid, size),
io:format("~40.40s | ~-5.5s | ~-5.5s | ~s~n",
[Host ++ ":" ++ integer_to_list(Port),
integer_to_list(Tid),
integer_to_list(Size),
io_lib:format("~p", [Lb_pid])]
)
end
)
end
end, Dests).
%% @doc Shows some internal information about load balancing to a %% @doc Shows some internal information about load balancing to a
%% specified Host:Port. Info about workers spawned using %% specified Host:Port. Info about workers spawned using
%% spawn_worker_process/2 or spawn_link_worker_process/2 is not %% spawn_worker_process/2 or spawn_link_worker_process/2 is not

+ 19
- 8
src/ibrowse_http_client.erl Ver arquivo

@ -137,7 +137,7 @@ handle_call({send_req, {Url, Headers, Method, Body, Options, Timeout}},
handle_call(stop, _From, State) -> handle_call(stop, _From, State) ->
do_close(State), do_close(State),
do_error_reply(State, closing_on_request), do_error_reply(State, closing_on_request),
{stop, normal, State};
{stop, normal, ok, State};
handle_call(Request, _From, State) -> handle_call(Request, _From, State) ->
Reply = {unknown_request, Request}, Reply = {unknown_request, Request},
@ -184,6 +184,15 @@ handle_info({ssl_closed, _Sock}, State) ->
handle_sock_closed(State), handle_sock_closed(State),
{stop, normal, State}; {stop, normal, State};
handle_info({tcp_error, _Sock}, State) ->
io:format("Error on connection to ~1000.p:~1000.p~n", [State#state.host, State#state.port]),
handle_sock_closed(State),
{stop, normal, State};
handle_info({ssl_error, _Sock}, State) ->
io:format("Error on SSL connection to ~1000.p:~1000.p~n", [State#state.host, State#state.port]),
handle_sock_closed(State),
{stop, normal, State};
handle_info({req_timedout, From}, State) -> handle_info({req_timedout, From}, State) ->
case lists:keysearch(From, #request.from, queue:to_list(State#state.reqs)) of case lists:keysearch(From, #request.from, queue:to_list(State#state.reqs)) of
false -> false ->
@ -204,6 +213,8 @@ handle_info({trace, Bool}, State) ->
{noreply, State}; {noreply, State};
handle_info(Info, State) -> handle_info(Info, State) ->
io:format("Unknown message recvd for ~1000.p:~1000.p -> ~p~n",
[State#state.host, State#state.port, Info]),
io:format("Recvd unknown message ~p when in state: ~p~n", [Info, State]), io:format("Recvd unknown message ~p when in state: ~p~n", [Info, State]),
{noreply, State}. {noreply, State}.
@ -869,8 +880,8 @@ is_connection_closing(_, _) -> false.
%% This clause determines the chunk size when given data from the beginning of the chunk %% This clause determines the chunk size when given data from the beginning of the chunk
parse_11_response(DataRecvd, parse_11_response(DataRecvd,
#state{transfer_encoding=chunked,
chunk_size=chunk_start,
#state{transfer_encoding = chunked,
chunk_size = chunk_start,
chunk_size_buffer = Chunk_sz_buf chunk_size_buffer = Chunk_sz_buf
} = State) -> } = State) ->
case scan_crlf(Chunk_sz_buf, DataRecvd) of case scan_crlf(Chunk_sz_buf, DataRecvd) of
@ -906,20 +917,20 @@ parse_11_response(DataRecvd,
{yes, _, NextChunk} -> {yes, _, NextChunk} ->
State_1 = State#state{chunk_size = chunk_start, State_1 = State#state{chunk_size = chunk_start,
chunk_size_buffer = <<>>, chunk_size_buffer = <<>>,
%% reply_buffer = Buf_1,
deleted_crlf = true}, deleted_crlf = true},
parse_11_response(NextChunk, State_1); parse_11_response(NextChunk, State_1);
{no, Data_1} -> {no, Data_1} ->
%% State#state{reply_buffer = Data_1, rep_buf_size = size(Data_1)}
State#state{chunk_size_buffer = Data_1} State#state{chunk_size_buffer = Data_1}
end; end;
%% This clause deals with the end of a chunked transfer
%% This clause deals with the end of a chunked transfer. ibrowse does
%% not support Trailers in the Chunked Transfer encoding. Any trailer
%% received is silently discarded.
parse_11_response(DataRecvd, parse_11_response(DataRecvd,
#state{transfer_encoding = chunked, chunk_size = 0, #state{transfer_encoding = chunked, chunk_size = 0,
cur_req = CurReq, cur_req = CurReq,
deleted_crlf = DelCrlf, deleted_crlf = DelCrlf,
reply_buffer = Trailer, reqs = Reqs}=State) ->
chunk_size_buffer = Trailer, reqs = Reqs}=State) ->
do_trace("Detected end of chunked transfer...~n", []), do_trace("Detected end of chunked transfer...~n", []),
DataRecvd_1 = case DelCrlf of DataRecvd_1 = case DelCrlf of
false -> false ->
@ -933,7 +944,7 @@ parse_11_response(DataRecvd,
State_1 = handle_response(CurReq, State#state{reqs = Reqs_1}), State_1 = handle_response(CurReq, State#state{reqs = Reqs_1}),
parse_response(Rem, reset_state(State_1)); parse_response(Rem, reset_state(State_1));
{no, Rem} -> {no, Rem} ->
State#state{reply_buffer = Rem, rep_buf_size = size(Rem), deleted_crlf = false}
State#state{chunk_size_buffer = Rem, deleted_crlf = false}
end; end;
%% This clause extracts a chunk, given the size. %% This clause extracts a chunk, given the size.

+ 30
- 8
src/ibrowse_lb.erl Ver arquivo

@ -108,18 +108,19 @@ spawn_connection(Lb_pid, Url,
%% Update max_sessions in #state with supplied value %% Update max_sessions in #state with supplied value
handle_call({spawn_connection, _Url, Max_sess, Max_pipe, _}, _From, handle_call({spawn_connection, _Url, Max_sess, Max_pipe, _}, _From,
#state{ets_tid = Tid,
num_cur_sessions = Num} = State)
#state{num_cur_sessions = Num} = State)
when Num >= Max_sess -> when Num >= Max_sess ->
Reply = find_best_connection(Tid, Max_pipe),
{reply, Reply, State#state{max_sessions = 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}};
handle_call({spawn_connection, Url, _Max_sess, _Max_pipe, SSL_options}, _From, handle_call({spawn_connection, Url, _Max_sess, _Max_pipe, SSL_options}, _From,
#state{num_cur_sessions = Cur,
ets_tid = Tid} = State) ->
#state{num_cur_sessions = Cur} = State) ->
State_1 = maybe_create_ets(State),
Tid = State_1#state.ets_tid,
{ok, Pid} = ibrowse_http_client:start_link({Tid, Url, SSL_options}), {ok, Pid} = ibrowse_http_client:start_link({Tid, Url, SSL_options}),
ets:insert(Tid, {{1, Pid}, []}), ets:insert(Tid, {{1, Pid}, []}),
{reply, {ok, Pid}, State#state{num_cur_sessions = Cur + 1}};
{reply, {ok, Pid}, State_1#state{num_cur_sessions = Cur + 1}};
handle_call(Request, _From, State) -> handle_call(Request, _From, State) ->
Reply = {unknown_request, Request}, Reply = {unknown_request, Request},
@ -145,11 +146,26 @@ handle_cast(_Msg, State) ->
handle_info({'EXIT', Parent, _Reason}, #state{parent_pid = Parent} = State) -> handle_info({'EXIT', Parent, _Reason}, #state{parent_pid = Parent} = State) ->
{stop, normal, State}; {stop, normal, State};
handle_info({'EXIT', _Pid, _Reason}, #state{ets_tid = undefined} = State) ->
{noreply, State};
handle_info({'EXIT', Pid, _Reason}, handle_info({'EXIT', Pid, _Reason},
#state{num_cur_sessions = Cur, #state{num_cur_sessions = Cur,
ets_tid = Tid} = State) -> ets_tid = Tid} = State) ->
ets:match_delete(Tid, {{'_', Pid}, '_'}), ets:match_delete(Tid, {{'_', Pid}, '_'}),
{noreply, State#state{num_cur_sessions = Cur - 1}};
Cur_1 = Cur - 1,
State_1 = case Cur_1 of
0 ->
ets:delete(Tid),
State#state{ets_tid = undefined};
_ ->
State
end,
{noreply, State_1#state{num_cur_sessions = Cur_1}};
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) -> handle_info({trace, Bool}, #state{ets_tid = Tid} = State) ->
ets:foldl(fun({{_, Pid}, _}, Acc) when is_pid(Pid) -> ets:foldl(fun({{_, Pid}, _}, Acc) when is_pid(Pid) ->
@ -192,3 +208,9 @@ find_best_connection(Tid, Max_pipe) ->
_ -> _ ->
{error, retry_later} {error, retry_later}
end. end.
maybe_create_ets(#state{ets_tid = undefined} = State) ->
Tid = ets:new(ibrowse_lb, [public, ordered_set]),
State#state{ets_tid = Tid};
maybe_create_ets(State) ->
State.

+ 4
- 0
src/ibrowse_test.erl Ver arquivo

@ -231,6 +231,7 @@ unit_tests(Options) ->
{'DOWN', Ref, _, _, Info} -> {'DOWN', Ref, _, _, Info} ->
io:format("Test process crashed: ~p~n", [Info]) io:format("Test process crashed: ~p~n", [Info])
after 60000 -> after 60000 ->
exit(Pid, kill),
io:format("Timed out waiting for tests to complete~n", []) io:format("Timed out waiting for tests to complete~n", [])
end. end.
@ -301,6 +302,9 @@ wait_for_resp(Pid) ->
receive receive
{async_result, Pid, Res} -> {async_result, Pid, Res} ->
Res; Res;
{async_result, Other_pid, _} ->
io:format("~p: Waiting for result from ~p: got from ~p~n", [self(), Pid, Other_pid]),
wait_for_resp(Pid);
{'DOWN', _, _, Pid, Reason} -> {'DOWN', _, _, Pid, Reason} ->
{'EXIT', Reason}; {'EXIT', Reason};
{'DOWN', _, _, _, _} -> {'DOWN', _, _, _, _} ->

+ 1
- 1
vsn.mk Ver arquivo

@ -1,2 +1,2 @@
IBROWSE_VSN = 1.5.1
IBROWSE_VSN = 1.5.2

Carregando…
Cancelar
Salvar