Nevar pievienot vairāk kā 25 tēmas Tēmai ir jāsākas ar burtu vai ciparu, tā var saturēt domu zīmes ('-') un var būt līdz 35 simboliem gara.

760 rindas
28 KiB

pirms 17 gadiem
pirms 17 gadiem
pirms 17 gadiem
pirms 17 gadiem
pirms 17 gadiem
pirms 17 gadiem
pirms 17 gadiem
pirms 17 gadiem
pirms 17 gadiem
pirms 17 gadiem
pirms 17 gadiem
pirms 17 gadiem
pirms 17 gadiem
pirms 17 gadiem
pirms 17 gadiem
pirms 17 gadiem
pirms 17 gadiem
pirms 17 gadiem
pirms 17 gadiem
pirms 17 gadiem
pirms 17 gadiem
pirms 17 gadiem
pirms 17 gadiem
pirms 17 gadiem
pirms 17 gadiem
pirms 17 gadiem
pirms 17 gadiem
pirms 17 gadiem
pirms 17 gadiem
pirms 17 gadiem
pirms 17 gadiem
pirms 17 gadiem
pirms 17 gadiem
pirms 17 gadiem
pirms 17 gadiem
pirms 17 gadiem
pirms 17 gadiem
pirms 17 gadiem
pirms 17 gadiem
pirms 17 gadiem
  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.