Non puoi selezionare più di 25 argomenti Gli argomenti devono iniziare con una lettera o un numero, possono includere trattini ('-') e possono essere lunghi fino a 35 caratteri.

760 righe
28 KiB

  1. %%%-------------------------------------------------------------------
  2. %%% File : ibrowse.erl
  3. %%% Author : Chandrashekhar Mullaparthi <chandrashekhar.mullaparthi@t-mobile.co.uk>
  4. %%% Description : Load balancer process for HTTP client connections.
  5. %%%
  6. %%% Created : 11 Oct 2003 by Chandrashekhar Mullaparthi <chandrashekhar.mullaparthi@t-mobile.co.uk>
  7. %%%-------------------------------------------------------------------
  8. %% @author Chandrashekhar Mullaparthi <chandrashekhar dot mullaparthi at gmail dot com>
  9. %% @copyright 2005-2009 Chandrashekhar Mullaparthi
  10. %% @version 1.5.2
  11. %% @doc The ibrowse application implements an HTTP 1.1 client. This
  12. %% module implements the API of the HTTP client. There is one named
  13. %% process called 'ibrowse' which assists in load balancing and maintaining configuration. There is one load balancing process per unique webserver. There is
  14. %% one process to handle one TCP connection to a webserver
  15. %% (implemented in the module ibrowse_http_client). Multiple connections to a
  16. %% webserver are setup based on the settings for each webserver. The
  17. %% ibrowse process also determines which connection to pipeline a
  18. %% certain request on. The functions to call are send_req/3,
  19. %% send_req/4, send_req/5, send_req/6.
  20. %%
  21. %% <p>Here are a few sample invocations.</p>
  22. %%
  23. %% <code>
  24. %% ibrowse:send_req("http://intranet/messenger/", [], get).
  25. %% <br/><br/>
  26. %%
  27. %% ibrowse:send_req("http://www.google.com/", [], get, [],
  28. %% [{proxy_user, "XXXXX"},
  29. %% {proxy_password, "XXXXX"},
  30. %% {proxy_host, "proxy"},
  31. %% {proxy_port, 8080}], 1000).
  32. %% <br/><br/>
  33. %%
  34. %%ibrowse:send_req("http://www.erlang.org/download/otp_src_R10B-3.tar.gz", [], get, [],
  35. %% [{proxy_user, "XXXXX"},
  36. %% {proxy_password, "XXXXX"},
  37. %% {proxy_host, "proxy"},
  38. %% {proxy_port, 8080},
  39. %% {save_response_to_file, true}], 1000).
  40. %% <br/><br/>
  41. %%
  42. %% ibrowse:send_req("http://www.erlang.org", [], head).
  43. %%
  44. %% <br/><br/>
  45. %% ibrowse:send_req("http://www.sun.com", [], options).
  46. %%
  47. %% <br/><br/>
  48. %% ibrowse:send_req("http://www.bbc.co.uk", [], trace).
  49. %%
  50. %% <br/><br/>
  51. %% ibrowse:send_req("http://www.google.com", [], get, [],
  52. %% [{stream_to, self()}]).
  53. %% </code>
  54. %%
  55. %% <p>A driver exists which implements URL encoding in C, but the
  56. %% speed achieved using only erlang has been good enough, so the
  57. %% driver isn't actually used.</p>
  58. -module(ibrowse).
  59. -vsn('$Id: ibrowse.erl,v 1.8 2009/07/01 22:43:19 chandrusf Exp $ ').
  60. -behaviour(gen_server).
  61. %%--------------------------------------------------------------------
  62. %% Include files
  63. %%--------------------------------------------------------------------
  64. %%--------------------------------------------------------------------
  65. %% External exports
  66. -export([start_link/0, start/0, stop/0]).
  67. %% gen_server callbacks
  68. -export([init/1, handle_call/3, handle_cast/2, handle_info/2,
  69. terminate/2, code_change/3]).
  70. %% API interface
  71. -export([
  72. rescan_config/0,
  73. rescan_config/1,
  74. get_config_value/1,
  75. get_config_value/2,
  76. spawn_worker_process/2,
  77. spawn_link_worker_process/2,
  78. stop_worker_process/1,
  79. send_req/3,
  80. send_req/4,
  81. send_req/5,
  82. send_req/6,
  83. send_req_direct/4,
  84. send_req_direct/5,
  85. send_req_direct/6,
  86. send_req_direct/7,
  87. stream_next/1,
  88. set_max_sessions/3,
  89. set_max_pipeline_size/3,
  90. set_dest/3,
  91. trace_on/0,
  92. trace_off/0,
  93. trace_on/2,
  94. trace_off/2,
  95. all_trace_off/0,
  96. show_dest_status/0,
  97. show_dest_status/2
  98. ]).
  99. -ifdef(debug).
  100. -compile(export_all).
  101. -endif.
  102. -import(ibrowse_lib, [
  103. parse_url/1,
  104. get_value/3,
  105. do_trace/2
  106. ]).
  107. -record(state, {trace = false}).
  108. -include("ibrowse.hrl").
  109. -include_lib("stdlib/include/ms_transform.hrl").
  110. -define(DEF_MAX_SESSIONS,10).
  111. -define(DEF_MAX_PIPELINE_SIZE,10).
  112. %%====================================================================
  113. %% External functions
  114. %%====================================================================
  115. %%--------------------------------------------------------------------
  116. %% Function: start_link/0
  117. %% Description: Starts the server
  118. %%--------------------------------------------------------------------
  119. %% @doc Starts the ibrowse process linked to the calling process. Usually invoked by the supervisor ibrowse_sup
  120. %% @spec start_link() -> {ok, pid()}
  121. start_link() ->
  122. gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
  123. %% @doc Starts the ibrowse process without linking. Useful when testing using the shell
  124. start() ->
  125. gen_server:start({local, ?MODULE}, ?MODULE, [], [{debug, []}]).
  126. %% @doc Stop the ibrowse process. Useful when testing using the shell.
  127. stop() ->
  128. catch gen_server:call(ibrowse, stop).
  129. %% @doc This is the basic function to send a HTTP request.
  130. %% The Status return value indicates the HTTP status code returned by the webserver
  131. %% @spec send_req(Url::string(), Headers::headerList(), Method::method()) -> response()
  132. %% headerList() = [{header(), value()}]
  133. %% header() = atom() | string()
  134. %% value() = term()
  135. %% method() = get | post | head | options | put | delete | trace | mkcol | propfind | proppatch | lock | unlock | move | copy
  136. %% Status = string()
  137. %% ResponseHeaders = [respHeader()]
  138. %% respHeader() = {headerName(), headerValue()}
  139. %% headerName() = string()
  140. %% headerValue() = string()
  141. %% response() = {ok, Status, ResponseHeaders, ResponseBody} | {ibrowse_req_id, req_id() } | {error, Reason}
  142. %% req_id() = term()
  143. %% ResponseBody = string() | {file, Filename}
  144. %% Reason = term()
  145. send_req(Url, Headers, Method) ->
  146. send_req(Url, Headers, Method, [], []).
  147. %% @doc Same as send_req/3.
  148. %% If a list is specified for the body it has to be a flat list. The body can also be a fun/0 or a fun/1. <br/>
  149. %% If fun/0, the connection handling process will repeatdely call the fun until it returns an error or eof. <pre>Fun() = {ok, Data} | eof</pre><br/>
  150. %% If fun/1, the connection handling process will repeatedly call the fun with the supplied state until it returns an error or eof. <pre>Fun(State) = {ok, Data} | {ok, Data, NewState} | eof</pre>
  151. %% @spec send_req(Url, Headers, Method::method(), Body::body()) -> response()
  152. %% body() = [] | string() | binary() | fun_arity_0() | {fun_arity_1(), initial_state()}
  153. %% initial_state() = term()
  154. send_req(Url, Headers, Method, Body) ->
  155. send_req(Url, Headers, Method, Body, []).
  156. %% @doc Same as send_req/4.
  157. %% For a description of SSL Options, look in the <a href="http://www.erlang.org/doc/apps/ssl/index.html">ssl</a> manpage. If the
  158. %% HTTP Version to use is not specified, the default is 1.1.
  159. %% <br/>
  160. %% <p>The <code>host_header</code> option is useful in the case where ibrowse is
  161. %% connecting to a component such as <a
  162. %% href="http://www.stunnel.org">stunnel</a> which then sets up a
  163. %% secure connection to a webserver. In this case, the URL supplied to
  164. %% ibrowse must have the stunnel host/port details, but that won't
  165. %% make sense to the destination webserver. This option can then be
  166. %% used to specify what should go in the <code>Host</code> header in
  167. %% the request.</p>
  168. %% <ul>
  169. %% <li>The <code>stream_to</code> option can be used to have the HTTP
  170. %% response streamed to a process as messages as data arrives on the
  171. %% socket. If the calling process wishes to control the rate at which
  172. %% data is received from the server, the option <code>{stream_to,
  173. %% {process(), once}}</code> can be specified. The calling process
  174. %% will have to invoke <code>ibrowse:stream_next(Request_id)</code> to
  175. %% receive the next packet.</li>
  176. %%
  177. %% <li>When both the options <code>save_response_to_file</code> and <code>stream_to</code>
  178. %% are specified, the former takes precedence.</li>
  179. %%
  180. %% <li>For the <code>save_response_to_file</code> option, the response body is saved to
  181. %% file only if the status code is in the 200-299 range. If not, the response body is returned
  182. %% as a string.</li>
  183. %% <li>Whenever an error occurs in the processing of a request, ibrowse will return as much
  184. %% information as it has, such as HTTP Status Code and HTTP Headers. When this happens, the response
  185. %% is of the form <code>{error, {Reason, {stat_code, StatusCode}, HTTP_headers}}</code></li>
  186. %%
  187. %% <li>The <code>inactivity_timeout</code> option is useful when
  188. %% dealing with large response bodies and/or slow links. In these
  189. %% cases, it might be hard to estimate how long a request will take to
  190. %% complete. In such cases, the client might want to timeout if no
  191. %% data has been received on the link for a certain time interval.</li>
  192. %%
  193. %% <li>
  194. %% The <code>connect_timeout</code> option is to specify how long the
  195. %% client process should wait for connection establishment. This is
  196. %% useful in scenarios where connections to servers are usually setup
  197. %% very fast, but responses might take much longer compared to
  198. %% connection setup. In such cases, it is better for the calling
  199. %% process to timeout faster if there is a problem (DNS lookup
  200. %% delays/failures, network routing issues, etc). The total timeout
  201. %% value specified for the request will enforced. To illustrate using
  202. %% an example:
  203. %% <code>
  204. %% ibrowse:send_req("http://www.example.com/cgi-bin/request", [], get, [], [{connect_timeout, 100}], 1000).
  205. %% </code>
  206. %% In the above invocation, if the connection isn't established within
  207. %% 100 milliseconds, the request will fail with
  208. %% <code>{error, conn_failed}</code>.<br/>
  209. %% If connection setup succeeds, the total time allowed for the
  210. %% request to complete will be 1000 milliseconds minus the time taken
  211. %% for connection setup.
  212. %% </li>
  213. %% </ul>
  214. %%
  215. %% @spec send_req(Url::string(), Headers::headerList(), Method::method(), Body::body(), Options::optionList()) -> response()
  216. %% optionList() = [option()]
  217. %% option() = {max_sessions, integer()} |
  218. %% {response_format,response_format()}|
  219. %% {stream_chunk_size, integer()} |
  220. %% {max_pipeline_size, integer()} |
  221. %% {trace, boolean()} |
  222. %% {is_ssl, boolean()} |
  223. %% {ssl_options, [SSLOpt]} |
  224. %% {pool_name, atom()} |
  225. %% {proxy_host, string()} |
  226. %% {proxy_port, integer()} |
  227. %% {proxy_user, string()} |
  228. %% {proxy_password, string()} |
  229. %% {use_absolute_uri, boolean()} |
  230. %% {basic_auth, {username(), password()}} |
  231. %% {cookie, string()} |
  232. %% {content_length, integer()} |
  233. %% {content_type, string()} |
  234. %% {save_response_to_file, srtf()} |
  235. %% {stream_to, stream_to()} |
  236. %% {http_vsn, {MajorVsn, MinorVsn}} |
  237. %% {host_header, string()} |
  238. %% {inactivity_timeout, integer()} |
  239. %% {connect_timeout, integer()} |
  240. %% {transfer_encoding, {chunked, ChunkSize}}
  241. %%
  242. %% stream_to() = process() | {process(), once}
  243. %% process() = pid() | atom()
  244. %% username() = string()
  245. %% password() = string()
  246. %% SSLOpt = term()
  247. %% ChunkSize = integer()
  248. %% srtf() = boolean() | filename()
  249. %% filename() = string()
  250. %% response_format() = list | binary
  251. send_req(Url, Headers, Method, Body, Options) ->
  252. send_req(Url, Headers, Method, Body, Options, 30000).
  253. %% @doc Same as send_req/5.
  254. %% All timeout values are in milliseconds.
  255. %% @spec send_req(Url, Headers::headerList(), Method::method(), Body::body(), Options::optionList(), Timeout) -> response()
  256. %% Timeout = integer() | infinity
  257. send_req(Url, Headers, Method, Body, Options, Timeout) ->
  258. case catch parse_url(Url) of
  259. #url{host = Host,
  260. port = Port,
  261. protocol = Protocol} = Parsed_url ->
  262. Lb_pid = case ets:lookup(ibrowse_lb, {Host, Port}) of
  263. [] ->
  264. get_lb_pid(Parsed_url);
  265. [#lb_pid{pid = Lb_pid_1}] ->
  266. Lb_pid_1
  267. end,
  268. Max_sessions = get_max_sessions(Host, Port, Options),
  269. Max_pipeline_size = get_max_pipeline_size(Host, Port, Options),
  270. Options_1 = merge_options(Host, Port, Options),
  271. {SSLOptions, IsSSL} =
  272. case (Protocol == https) orelse
  273. get_value(is_ssl, Options_1, false) of
  274. false -> {[], false};
  275. true -> {get_value(ssl_options, Options_1, []), true}
  276. end,
  277. case ibrowse_lb:spawn_connection(Lb_pid, Parsed_url,
  278. Max_sessions,
  279. Max_pipeline_size,
  280. {SSLOptions, IsSSL}) of
  281. {ok, Conn_Pid} ->
  282. do_send_req(Conn_Pid, Parsed_url, Headers,
  283. Method, Body, Options_1, Timeout);
  284. Err ->
  285. Err
  286. end;
  287. Err ->
  288. {error, {url_parsing_failed, Err}}
  289. end.
  290. merge_options(Host, Port, Options) ->
  291. Config_options = get_config_value({options, Host, Port}, []),
  292. lists:foldl(
  293. fun({Key, Val}, Acc) ->
  294. case lists:keysearch(Key, 1, Options) of
  295. false ->
  296. [{Key, Val} | Acc];
  297. _ ->
  298. Acc
  299. end
  300. end, Options, Config_options).
  301. get_lb_pid(Url) ->
  302. gen_server:call(?MODULE, {get_lb_pid, Url}).
  303. get_max_sessions(Host, Port, Options) ->
  304. get_value(max_sessions, Options,
  305. get_config_value({max_sessions, Host, Port}, ?DEF_MAX_SESSIONS)).
  306. get_max_pipeline_size(Host, Port, Options) ->
  307. get_value(max_pipeline_size, Options,
  308. get_config_value({max_pipeline_size, Host, Port}, ?DEF_MAX_PIPELINE_SIZE)).
  309. %% @doc Deprecated. Use set_max_sessions/3 and set_max_pipeline_size/3
  310. %% for achieving the same effect.
  311. set_dest(Host, Port, [{max_sessions, Max} | T]) ->
  312. set_max_sessions(Host, Port, Max),
  313. set_dest(Host, Port, T);
  314. set_dest(Host, Port, [{max_pipeline_size, Max} | T]) ->
  315. set_max_pipeline_size(Host, Port, Max),
  316. set_dest(Host, Port, T);
  317. set_dest(Host, Port, [{trace, Bool} | T]) when Bool == true; Bool == false ->
  318. ibrowse ! {trace, true, Host, Port},
  319. set_dest(Host, Port, T);
  320. set_dest(_Host, _Port, [H | _]) ->
  321. exit({invalid_option, H});
  322. set_dest(_, _, []) ->
  323. ok.
  324. %% @doc Set the maximum number of connections allowed to a specific Host:Port.
  325. %% @spec set_max_sessions(Host::string(), Port::integer(), Max::integer()) -> ok
  326. set_max_sessions(Host, Port, Max) when is_integer(Max), Max > 0 ->
  327. gen_server:call(?MODULE, {set_config_value, {max_sessions, Host, Port}, Max}).
  328. %% @doc Set the maximum pipeline size for each connection to a specific Host:Port.
  329. %% @spec set_max_pipeline_size(Host::string(), Port::integer(), Max::integer()) -> ok
  330. set_max_pipeline_size(Host, Port, Max) when is_integer(Max), Max > 0 ->
  331. gen_server:call(?MODULE, {set_config_value, {max_pipeline_size, Host, Port}, Max}).
  332. do_send_req(Conn_Pid, Parsed_url, Headers, Method, Body, Options, Timeout) ->
  333. case catch ibrowse_http_client:send_req(Conn_Pid, Parsed_url,
  334. Headers, Method, ensure_bin(Body),
  335. Options, Timeout) of
  336. {'EXIT', {timeout, _}} ->
  337. {error, req_timedout};
  338. {'EXIT', Reason} ->
  339. {error, {'EXIT', Reason}};
  340. {ok, St_code, Headers, Body} = Ret when is_binary(Body) ->
  341. case get_value(response_format, Options, list) of
  342. list ->
  343. {ok, St_code, Headers, binary_to_list(Body)};
  344. binary ->
  345. Ret
  346. end;
  347. Ret ->
  348. Ret
  349. end.
  350. ensure_bin(L) when is_list(L) -> list_to_binary(L);
  351. ensure_bin(B) when is_binary(B) -> B;
  352. ensure_bin(Fun) when is_function(Fun) -> Fun;
  353. ensure_bin({Fun}) when is_function(Fun) -> Fun;
  354. ensure_bin({Fun, _} = Body) when is_function(Fun) -> Body.
  355. %% @doc Creates a HTTP client process to the specified Host:Port which
  356. %% is not part of the load balancing pool. This is useful in cases
  357. %% where some requests to a webserver might take a long time whereas
  358. %% some might take a very short time. To avoid getting these quick
  359. %% requests stuck in the pipeline behind time consuming requests, use
  360. %% this function to get a handle to a connection process. <br/>
  361. %% <b>Note:</b> Calling this function only creates a worker process. No connection
  362. %% is setup. The connection attempt is made only when the first
  363. %% request is sent via any of the send_req_direct/4,5,6,7 functions.<br/>
  364. %% <b>Note:</b> It is the responsibility of the calling process to control
  365. %% pipeline size on such connections.
  366. %%
  367. %% @spec spawn_worker_process(Host::string(), Port::integer()) -> {ok, pid()}
  368. spawn_worker_process(Host, Port) ->
  369. ibrowse_http_client:start({Host, Port}).
  370. %% @doc Same as spawn_worker_process/2 except the the calling process
  371. %% is linked to the worker process which is spawned.
  372. spawn_link_worker_process(Host, Port) ->
  373. ibrowse_http_client:start_link({Host, Port}).
  374. %% @doc Terminate a worker process spawned using
  375. %% spawn_worker_process/2 or spawn_link_worker_process/2. Requests in
  376. %% progress will get the error response <pre>{error, closing_on_request}</pre>
  377. %% @spec stop_worker_process(Conn_pid::pid()) -> ok
  378. stop_worker_process(Conn_pid) ->
  379. ibrowse_http_client:stop(Conn_pid).
  380. %% @doc Same as send_req/3 except that the first argument is the PID
  381. %% returned by spawn_worker_process/2 or spawn_link_worker_process/2
  382. send_req_direct(Conn_pid, Url, Headers, Method) ->
  383. send_req_direct(Conn_pid, Url, Headers, Method, [], []).
  384. %% @doc Same as send_req/4 except that the first argument is the PID
  385. %% returned by spawn_worker_process/2 or spawn_link_worker_process/2
  386. send_req_direct(Conn_pid, Url, Headers, Method, Body) ->
  387. send_req_direct(Conn_pid, Url, Headers, Method, Body, []).
  388. %% @doc Same as send_req/5 except that the first argument is the PID
  389. %% returned by spawn_worker_process/2 or spawn_link_worker_process/2
  390. send_req_direct(Conn_pid, Url, Headers, Method, Body, Options) ->
  391. send_req_direct(Conn_pid, Url, Headers, Method, Body, Options, 30000).
  392. %% @doc Same as send_req/6 except that the first argument is the PID
  393. %% returned by spawn_worker_process/2 or spawn_link_worker_process/2
  394. send_req_direct(Conn_pid, Url, Headers, Method, Body, Options, Timeout) ->
  395. case catch parse_url(Url) of
  396. #url{host = Host,
  397. port = Port} = Parsed_url ->
  398. Options_1 = merge_options(Host, Port, Options),
  399. case do_send_req(Conn_pid, Parsed_url, Headers, Method, Body, Options_1, Timeout) of
  400. {error, {'EXIT', {noproc, _}}} ->
  401. {error, worker_is_dead};
  402. Ret ->
  403. Ret
  404. end;
  405. Err ->
  406. {error, {url_parsing_failed, Err}}
  407. end.
  408. %% @doc Tell ibrowse to stream the next chunk of data to the
  409. %% caller. Should be used in conjunction with the
  410. %% <code>stream_to</code> option
  411. %% @spec stream_next(Req_id :: req_id()) -> ok | {error, unknown_req_id}
  412. stream_next(Req_id) ->
  413. case ets:lookup(ibrowse_stream, {req_id_pid, Req_id}) of
  414. [] ->
  415. {error, unknown_req_id};
  416. [{_, Pid}] ->
  417. catch Pid ! {stream_next, Req_id},
  418. ok
  419. end.
  420. %% @doc Turn tracing on for the ibrowse process
  421. trace_on() ->
  422. ibrowse ! {trace, true}.
  423. %% @doc Turn tracing off for the ibrowse process
  424. trace_off() ->
  425. ibrowse ! {trace, false}.
  426. %% @doc Turn tracing on for all connections to the specified HTTP
  427. %% server. Host is whatever is specified as the domain name in the URL
  428. %% @spec trace_on(Host, Port) -> ok
  429. %% Host = string()
  430. %% Port = integer()
  431. trace_on(Host, Port) ->
  432. ibrowse ! {trace, true, Host, Port},
  433. ok.
  434. %% @doc Turn tracing OFF for all connections to the specified HTTP
  435. %% server.
  436. %% @spec trace_off(Host, Port) -> ok
  437. trace_off(Host, Port) ->
  438. ibrowse ! {trace, false, Host, Port},
  439. ok.
  440. %% @doc Turn Off ALL tracing
  441. %% @spec all_trace_off() -> ok
  442. all_trace_off() ->
  443. ibrowse ! all_trace_off,
  444. ok.
  445. show_dest_status() ->
  446. Dests = lists:filter(fun({lb_pid, {Host, Port}, _}) when is_list(Host),
  447. is_integer(Port) ->
  448. true;
  449. (_) ->
  450. false
  451. end, ets:tab2list(ibrowse_lb)),
  452. All_ets = ets:all(),
  453. io:format("~-40.40s | ~-5.5s | ~-10.10s | ~s~n",
  454. ["Server:port", "ETS", "Num conns", "LB Pid"]),
  455. io:format("~80.80.=s~n", [""]),
  456. lists:foreach(fun({lb_pid, {Host, Port}, Lb_pid}) ->
  457. case lists:dropwhile(
  458. fun(Tid) ->
  459. ets:info(Tid, owner) /= Lb_pid
  460. end, All_ets) of
  461. [] ->
  462. io:format("~40.40s | ~-5.5s | ~-5.5s | ~s~n",
  463. [Host ++ ":" ++ integer_to_list(Port),
  464. "",
  465. "",
  466. io_lib:format("~p", [Lb_pid])]
  467. );
  468. [Tid | _] ->
  469. catch (
  470. begin
  471. Size = ets:info(Tid, size),
  472. io:format("~40.40s | ~-5.5s | ~-5.5s | ~s~n",
  473. [Host ++ ":" ++ integer_to_list(Port),
  474. integer_to_list(Tid),
  475. integer_to_list(Size),
  476. io_lib:format("~p", [Lb_pid])]
  477. )
  478. end
  479. )
  480. end
  481. end, Dests).
  482. %% @doc Shows some internal information about load balancing to a
  483. %% specified Host:Port. Info about workers spawned using
  484. %% spawn_worker_process/2 or spawn_link_worker_process/2 is not
  485. %% included.
  486. show_dest_status(Host, Port) ->
  487. case ets:lookup(ibrowse_lb, {Host, Port}) of
  488. [] ->
  489. no_active_processes;
  490. [#lb_pid{pid = Lb_pid}] ->
  491. io:format("Load Balancer Pid : ~p~n", [Lb_pid]),
  492. io:format("LB process msg q size : ~p~n", [(catch process_info(Lb_pid, message_queue_len))]),
  493. case lists:dropwhile(
  494. fun(Tid) ->
  495. ets:info(Tid, owner) /= Lb_pid
  496. end, ets:all()) of
  497. [] ->
  498. io:format("Couldn't locate ETS table for ~p~n", [Lb_pid]);
  499. [Tid | _] ->
  500. First = ets:first(Tid),
  501. Last = ets:last(Tid),
  502. Size = ets:info(Tid, size),
  503. io:format("LB ETS table id : ~p~n", [Tid]),
  504. io:format("Num Connections : ~p~n", [Size]),
  505. case Size of
  506. 0 ->
  507. ok;
  508. _ ->
  509. {First_p_sz, _} = First,
  510. {Last_p_sz, _} = Last,
  511. io:format("Smallest pipeline : ~1000.p~n", [First_p_sz]),
  512. io:format("Largest pipeline : ~1000.p~n", [Last_p_sz])
  513. end
  514. end
  515. end.
  516. %% @doc Clear current configuration for ibrowse and load from the file
  517. %% ibrowse.conf in the IBROWSE_EBIN/../priv directory. Current
  518. %% configuration is cleared only if the ibrowse.conf file is readable
  519. %% using file:consult/1
  520. rescan_config() ->
  521. gen_server:call(?MODULE, rescan_config).
  522. %% Clear current configuration for ibrowse and load from the specified
  523. %% file. Current configuration is cleared only if the specified
  524. %% file is readable using file:consult/1
  525. rescan_config(File) when is_list(File) ->
  526. gen_server:call(?MODULE, {rescan_config, File}).
  527. %%====================================================================
  528. %% Server functions
  529. %%====================================================================
  530. %%--------------------------------------------------------------------
  531. %% Function: init/1
  532. %% Description: Initiates the server
  533. %% Returns: {ok, State} |
  534. %% {ok, State, Timeout} |
  535. %% ignore |
  536. %% {stop, Reason}
  537. %%--------------------------------------------------------------------
  538. init(_) ->
  539. process_flag(trap_exit, true),
  540. State = #state{},
  541. put(my_trace_flag, State#state.trace),
  542. put(ibrowse_trace_token, "ibrowse"),
  543. ets:new(ibrowse_lb, [named_table, public, {keypos, 2}]),
  544. ets:new(ibrowse_conf, [named_table, protected, {keypos, 2}]),
  545. ets:new(ibrowse_stream, [named_table, public]),
  546. import_config(),
  547. {ok, #state{}}.
  548. import_config() ->
  549. case code:priv_dir(ibrowse) of
  550. {error, _} = Err ->
  551. Err;
  552. PrivDir ->
  553. Filename = filename:join(PrivDir, "ibrowse.conf"),
  554. import_config(Filename)
  555. end.
  556. import_config(Filename) ->
  557. case file:consult(Filename) of
  558. {ok, Terms} ->
  559. ets:delete_all_objects(ibrowse_conf),
  560. Fun = fun({dest, Host, Port, MaxSess, MaxPipe, Options})
  561. when is_list(Host), is_integer(Port),
  562. is_integer(MaxSess), MaxSess > 0,
  563. is_integer(MaxPipe), MaxPipe > 0, is_list(Options) ->
  564. I = [{{max_sessions, Host, Port}, MaxSess},
  565. {{max_pipeline_size, Host, Port}, MaxPipe},
  566. {{options, Host, Port}, Options}],
  567. lists:foreach(
  568. fun({X, Y}) ->
  569. ets:insert(ibrowse_conf,
  570. #ibrowse_conf{key = X,
  571. value = Y})
  572. end, I);
  573. ({K, V}) ->
  574. ets:insert(ibrowse_conf,
  575. #ibrowse_conf{key = K,
  576. value = V});
  577. (X) ->
  578. io:format("Skipping unrecognised term: ~p~n", [X])
  579. end,
  580. lists:foreach(Fun, Terms);
  581. Err ->
  582. Err
  583. end.
  584. %% @doc Internal export
  585. get_config_value(Key) ->
  586. [#ibrowse_conf{value = V}] = ets:lookup(ibrowse_conf, Key),
  587. V.
  588. %% @doc Internal export
  589. get_config_value(Key, DefVal) ->
  590. case ets:lookup(ibrowse_conf, Key) of
  591. [] ->
  592. DefVal;
  593. [#ibrowse_conf{value = V}] ->
  594. V
  595. end.
  596. set_config_value(Key, Val) ->
  597. ets:insert(ibrowse_conf, #ibrowse_conf{key = Key, value = Val}).
  598. %%--------------------------------------------------------------------
  599. %% Function: handle_call/3
  600. %% Description: Handling call messages
  601. %% Returns: {reply, Reply, State} |
  602. %% {reply, Reply, State, Timeout} |
  603. %% {noreply, State} |
  604. %% {noreply, State, Timeout} |
  605. %% {stop, Reason, Reply, State} | (terminate/2 is called)
  606. %% {stop, Reason, State} (terminate/2 is called)
  607. %%--------------------------------------------------------------------
  608. handle_call({get_lb_pid, #url{host = Host, port = Port} = Url}, _From, State) ->
  609. Pid = do_get_connection(Url, ets:lookup(ibrowse_lb, {Host, Port})),
  610. {reply, Pid, State};
  611. handle_call(stop, _From, State) ->
  612. do_trace("IBROWSE shutting down~n", []),
  613. {stop, normal, ok, State};
  614. handle_call({set_config_value, Key, Val}, _From, State) ->
  615. set_config_value(Key, Val),
  616. {reply, ok, State};
  617. handle_call(rescan_config, _From, State) ->
  618. Ret = (catch import_config()),
  619. {reply, Ret, State};
  620. handle_call({rescan_config, File}, _From, State) ->
  621. Ret = (catch import_config(File)),
  622. {reply, Ret, State};
  623. handle_call(Request, _From, State) ->
  624. Reply = {unknown_request, Request},
  625. {reply, Reply, State}.
  626. %%--------------------------------------------------------------------
  627. %% Function: handle_cast/2
  628. %% Description: Handling cast messages
  629. %% Returns: {noreply, State} |
  630. %% {noreply, State, Timeout} |
  631. %% {stop, Reason, State} (terminate/2 is called)
  632. %%--------------------------------------------------------------------
  633. handle_cast(_Msg, State) ->
  634. {noreply, State}.
  635. %%--------------------------------------------------------------------
  636. %% Function: handle_info/2
  637. %% Description: Handling all non call/cast messages
  638. %% Returns: {noreply, State} |
  639. %% {noreply, State, Timeout} |
  640. %% {stop, Reason, State} (terminate/2 is called)
  641. %%--------------------------------------------------------------------
  642. handle_info(all_trace_off, State) ->
  643. Mspec = [{{ibrowse_conf,{trace,'$1','$2'},true},[],[{{'$1','$2'}}]}],
  644. Trace_on_dests = ets:select(ibrowse_conf, Mspec),
  645. Fun = fun(#lb_pid{host_port = {H, P}, pid = Pid}, _) ->
  646. case lists:member({H, P}, Trace_on_dests) of
  647. false ->
  648. ok;
  649. true ->
  650. catch Pid ! {trace, false}
  651. end;
  652. (_, Acc) ->
  653. Acc
  654. end,
  655. ets:foldl(Fun, undefined, ibrowse_lb),
  656. ets:select_delete(ibrowse_conf, [{{ibrowse_conf,{trace,'$1','$2'},true},[],['true']}]),
  657. {noreply, State};
  658. handle_info({trace, Bool}, State) ->
  659. put(my_trace_flag, Bool),
  660. {noreply, State};
  661. handle_info({trace, Bool, Host, Port}, State) ->
  662. Fun = fun(#lb_pid{host_port = {H, P}, pid = Pid}, _)
  663. when H == Host,
  664. P == Port ->
  665. catch Pid ! {trace, Bool};
  666. (_, Acc) ->
  667. Acc
  668. end,
  669. ets:foldl(Fun, undefined, ibrowse_lb),
  670. ets:insert(ibrowse_conf, #ibrowse_conf{key = {trace, Host, Port},
  671. value = Bool}),
  672. {noreply, State};
  673. handle_info(_Info, State) ->
  674. {noreply, State}.
  675. %%--------------------------------------------------------------------
  676. %% Function: terminate/2
  677. %% Description: Shutdown the server
  678. %% Returns: any (ignored by gen_server)
  679. %%--------------------------------------------------------------------
  680. terminate(_Reason, _State) ->
  681. ok.
  682. %%--------------------------------------------------------------------
  683. %% Func: code_change/3
  684. %% Purpose: Convert process state when code is changed
  685. %% Returns: {ok, NewState}
  686. %%--------------------------------------------------------------------
  687. code_change(_OldVsn, State, _Extra) ->
  688. {ok, State}.
  689. %%--------------------------------------------------------------------
  690. %%% Internal functions
  691. %%--------------------------------------------------------------------
  692. do_get_connection(#url{host = Host, port = Port}, []) ->
  693. {ok, Pid} = ibrowse_lb:start_link([Host, Port]),
  694. ets:insert(ibrowse_lb, #lb_pid{host_port = {Host, Port}, pid = Pid}),
  695. Pid;
  696. do_get_connection(_Url, [#lb_pid{pid = Pid}]) ->
  697. Pid.