選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

375 行
14 KiB

9年前
9年前
9年前
9年前
9年前
  1. %%% File : ibrowse_test_server.erl
  2. %%% Author : Chandrashekhar Mullaparthi <chandrashekhar.mullaparthi@t-mobile.co.uk>
  3. %%% Description : A server to simulate various test scenarios
  4. %%% Created : 17 Oct 2010 by Chandrashekhar Mullaparthi <chandrashekhar.mullaparthi@t-mobile.co.uk>
  5. -module(ibrowse_test_server).
  6. -export([
  7. start_server/0,
  8. start_server/2,
  9. stop_server/1,
  10. get_conn_pipeline_depth/0
  11. ]).
  12. -ifdef(new_rand).
  13. -define(RAND, rand).
  14. -else.
  15. -define(RAND, random).
  16. -endif.
  17. -record(request, {method, uri, version, headers = [], body = [], state}).
  18. -define(dec2hex(X), erlang:integer_to_list(X, 16)).
  19. -define(ACCEPT_TIMEOUT_MS, 10000).
  20. -define(CONN_PIPELINE_DEPTH, conn_pipeline_depth).
  21. start_server() ->
  22. start_server(8181, tcp).
  23. start_server(Port, Sock_type) ->
  24. Fun = fun() ->
  25. Proc_name = server_proc_name(Port),
  26. case whereis(Proc_name) of
  27. undefined ->
  28. register(Proc_name, self()),
  29. ets:new(?CONN_PIPELINE_DEPTH, [named_table, public, set]),
  30. case do_listen(Sock_type, Port, [{active, false},
  31. {reuseaddr, true},
  32. {nodelay, true},
  33. {packet, http}]) of
  34. {ok, Sock} ->
  35. do_trace("Server listening on port: ~p~n", [Port]),
  36. accept_loop(Sock, Sock_type);
  37. Err ->
  38. erlang:error(
  39. lists:flatten(
  40. io_lib:format(
  41. "Failed to start server on port ~p. ~p~n",
  42. [Port, Err]))),
  43. exit({listen_error, Err})
  44. end;
  45. _X ->
  46. ok
  47. end
  48. end,
  49. spawn_link(Fun),
  50. ibrowse_socks_server:start(8282, 0), %% No auth
  51. ibrowse_socks_server:start(8383, 2). %% Username/Password auth
  52. stop_server(Port) ->
  53. catch server_proc_name(Port) ! stop,
  54. ibrowse_socks_server:stop(8282),
  55. ibrowse_socks_server:stop(8383),
  56. timer:sleep(2000), % wait for server to receive msg and unregister
  57. ok.
  58. get_conn_pipeline_depth() ->
  59. ets:tab2list(?CONN_PIPELINE_DEPTH).
  60. server_proc_name(Port) ->
  61. list_to_atom("ibrowse_test_server_"++integer_to_list(Port)).
  62. do_listen(tcp, Port, Opts) ->
  63. gen_tcp:listen(Port, Opts);
  64. do_listen(ssl, Port, Opts) ->
  65. application:start(crypto),
  66. application:start(ssl),
  67. ssl:listen(Port, Opts).
  68. do_accept(tcp, Listen_sock) ->
  69. gen_tcp:accept(Listen_sock, ?ACCEPT_TIMEOUT_MS);
  70. do_accept(ssl, Listen_sock) ->
  71. handleshake(Listen_sock, ?ACCEPT_TIMEOUT_MS).
  72. -ifdef(OTP_RELEASE).
  73. handleshake(Listen_sock, Timeout) ->
  74. ssl:handshake(Listen_sock, Timeout).
  75. -else.
  76. handleshake(Listen_sock, Timeout) ->
  77. ssl:ssl_accept(Listen_sock, Timeout).
  78. -endif.
  79. accept_loop(Sock, Sock_type) ->
  80. case do_accept(Sock_type, Sock) of
  81. {ok, Conn} ->
  82. Pid = spawn_link(fun() -> connection(Conn, Sock_type) end),
  83. set_controlling_process(Conn, Sock_type, Pid),
  84. accept_loop(Sock, Sock_type);
  85. ok ->
  86. Pid = spawn_link(fun() -> connection(Sock, Sock_type) end),
  87. set_controlling_process(Sock, Sock_type, Pid),
  88. accept_loop(Sock, Sock_type);
  89. {error, timeout} ->
  90. receive
  91. stop ->
  92. ok
  93. after 10 ->
  94. accept_loop(Sock, Sock_type)
  95. end;
  96. Err ->
  97. Err
  98. end.
  99. connection(Conn, Sock_type) ->
  100. catch ets:insert(?CONN_PIPELINE_DEPTH, {self(), 0}),
  101. try
  102. inet:setopts(Conn, [{packet, http}, {active, true}]),
  103. server_loop(Conn, Sock_type, #request{})
  104. after
  105. catch ets:delete(?CONN_PIPELINE_DEPTH, self())
  106. end.
  107. set_controlling_process(Sock, tcp, Pid) ->
  108. gen_tcp:controlling_process(Sock, Pid);
  109. set_controlling_process(Sock, ssl, Pid) ->
  110. ssl:controlling_process(Sock, Pid).
  111. setopts(Sock, tcp, Opts) ->
  112. inet:setopts(Sock, Opts);
  113. setopts(Sock, ssl, Opts) ->
  114. ssl:setopts(Sock, Opts).
  115. server_loop(Sock, Sock_type, #request{headers = Headers} = Req) ->
  116. receive
  117. {http, Sock, {http_request, HttpMethod, HttpUri, HttpVersion}} ->
  118. catch ets:update_counter(?CONN_PIPELINE_DEPTH, self(), 1),
  119. server_loop(Sock, Sock_type, Req#request{method = HttpMethod,
  120. uri = HttpUri,
  121. version = HttpVersion});
  122. {http, Sock, {http_header, _, _, _, _} = H} ->
  123. server_loop(Sock, Sock_type, Req#request{headers = [H | Headers]});
  124. {http, Sock, http_eoh} ->
  125. case process_request(Sock, Sock_type, Req) of
  126. close_connection ->
  127. gen_tcp:shutdown(Sock, read_write);
  128. not_done ->
  129. ok;
  130. collect_body ->
  131. server_loop(Sock, Sock_type, Req#request{state = collect_body});
  132. _ ->
  133. catch ets:update_counter(?CONN_PIPELINE_DEPTH, self(), -1)
  134. end,
  135. server_loop(Sock, Sock_type, #request{});
  136. {http, Sock, {http_error, Packet}} when Req#request.state == collect_body ->
  137. Req_1 = Req#request{body = list_to_binary([Packet, Req#request.body])},
  138. case process_request(Sock, Sock_type, Req_1) of
  139. close_connection ->
  140. gen_tcp:shutdown(Sock, read_write);
  141. ok ->
  142. server_loop(Sock, Sock_type, #request{});
  143. collect_body ->
  144. server_loop(Sock, Sock_type, Req_1)
  145. end;
  146. {http, Sock, {http_error, Err}} ->
  147. io:format("Error parsing HTTP request:~n"
  148. "Req so far : ~p~n"
  149. "Err : ~p", [Req, Err]),
  150. exit({http_error, Err});
  151. {setopts, Opts} ->
  152. setopts(Sock, Sock_type, Opts),
  153. server_loop(Sock, Sock_type, Req);
  154. {tcp_closed, Sock} ->
  155. do_trace("Client closed connection~n", []),
  156. ok;
  157. Other ->
  158. io:format("Recvd unknown msg: ~p~n", [Other]),
  159. exit({unknown_msg, Other})
  160. after 120000 ->
  161. do_trace("Timing out client connection~n", []),
  162. ok
  163. end.
  164. do_trace(Fmt, Args) ->
  165. do_trace(get(my_trace_flag), Fmt, Args).
  166. do_trace(true, Fmt, Args) ->
  167. io:format("~s -- " ++ Fmt, [ibrowse_lib:printable_date() | Args]);
  168. do_trace(_, _, _) ->
  169. ok.
  170. process_request(Sock, Sock_type,
  171. #request{method='GET',
  172. headers = Headers,
  173. uri = {abs_path, "/ibrowse_stream_once_chunk_pipeline_test"}} = Req) ->
  174. Req_id = case lists:keysearch("X-Ibrowse-Request-Id", 3, Headers) of
  175. false ->
  176. "";
  177. {value, {http_header, _, _, _, Req_id_1}} ->
  178. Req_id_1
  179. end,
  180. Req_id_header = ["x-ibrowse-request-id: ", Req_id, "\r\n"],
  181. do_trace("Recvd req: ~p~n", [Req]),
  182. Body = string:join([integer_to_list(X) || X <- lists:seq(1,100)], "-"),
  183. Chunked_body = chunk_request_body(Body, 50),
  184. Resp_1 = [<<"HTTP/1.1 200 OK\r\n">>,
  185. Req_id_header,
  186. <<"Transfer-Encoding: chunked\r\n\r\n">>],
  187. Resp_2 = Chunked_body,
  188. do_send(Sock, Sock_type, Resp_1),
  189. timer:sleep(100),
  190. do_send(Sock, Sock_type, Resp_2);
  191. process_request(Sock, Sock_type,
  192. #request{method='GET',
  193. headers = _Headers,
  194. uri = {abs_path, "/ibrowse_inac_timeout_test"}} = Req) ->
  195. do_trace("Recvd req: ~p. Sleeping for 30 secs...~n", [Req]),
  196. timer:sleep(3000),
  197. do_trace("...Sending response now.~n", []),
  198. Resp = <<"HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n">>,
  199. do_send(Sock, Sock_type, Resp);
  200. process_request(Sock, Sock_type,
  201. #request{method='HEAD',
  202. headers = _Headers,
  203. uri = {abs_path, "/ibrowse_head_transfer_enc"}}) ->
  204. 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">>,
  205. do_send(Sock, Sock_type, Resp);
  206. process_request(Sock, Sock_type,
  207. #request{method='POST',
  208. headers = Headers,
  209. uri = {abs_path, "/echo_body"},
  210. body = Body}) ->
  211. Content_len = get_content_length(Headers),
  212. case iolist_size(Body) == Content_len of
  213. true ->
  214. Resp = [<<"HTTP/1.1 200 OK\r\nContent-Length: ">>, integer_to_list(Content_len), <<"\r\nServer: ibrowse_test_server\r\n\r\n">>, Body],
  215. do_send(Sock, Sock_type, list_to_binary(Resp));
  216. false ->
  217. collect_body
  218. end;
  219. process_request(Sock, Sock_type,
  220. #request{method='GET',
  221. headers = Headers,
  222. uri = {abs_path, "/ibrowse_echo_header"}}) ->
  223. Tag = "x-binary",
  224. Headers_1 = [{to_lower(X), to_lower(Y)} || {http_header, _, X, _, Y} <- Headers],
  225. X_binary_header_val = case lists:keysearch(Tag, 1, Headers_1) of
  226. false ->
  227. "not_found";
  228. {value, {_, V}} ->
  229. V
  230. end,
  231. Resp = [<<"HTTP/1.1 200 OK\r\n">>,
  232. <<"Server: ibrowse_test\r\n">>,
  233. Tag, ": ", X_binary_header_val, "\r\n",
  234. <<"Content-Length: 0\r\n\r\n">>],
  235. do_send(Sock, Sock_type, Resp);
  236. process_request(Sock, Sock_type,
  237. #request{method='HEAD',
  238. headers = _Headers,
  239. uri = {abs_path, "/ibrowse_head_test"}}) ->
  240. Resp = <<"HTTP/1.1 200 OK\r\nServer: Apache-Coyote/1.1\r\Date: Wed, 04 Apr 2012 16:53:49 GMT\r\nConnection: close\r\n\r\n">>,
  241. do_send(Sock, Sock_type, Resp);
  242. process_request(Sock, Sock_type,
  243. #request{method='POST',
  244. headers = _Headers,
  245. uri = {abs_path, "/ibrowse_303_no_body_test"}}) ->
  246. Resp = <<"HTTP/1.1 303 See Other\r\nLocation: http://example.org\r\n\r\n">>,
  247. do_send(Sock, Sock_type, Resp);
  248. process_request(Sock, Sock_type,
  249. #request{method='POST',
  250. headers = _Headers,
  251. uri = {abs_path, "/ibrowse_303_with_body_test"}}) ->
  252. Resp = <<"HTTP/1.1 303 See Other\r\nLocation: http://example.org\r\nContent-Length: 5\r\n\r\nabcde">>,
  253. do_send(Sock, Sock_type, Resp);
  254. process_request(Sock, Sock_type,
  255. #request{method='GET',
  256. headers = _Headers,
  257. uri = {abs_path, "/ibrowse_preserve_status_line"}}) ->
  258. Resp = <<"HTTP/1.1 200 OKBlah\r\nContent-Length: 5\r\n\r\nabcde">>,
  259. do_send(Sock, Sock_type, Resp);
  260. process_request(Sock, Sock_type,
  261. #request{method='GET',
  262. headers = _Headers,
  263. uri = {abs_path, "/ibrowse_handle_one_request_only_with_delay"}}) ->
  264. timer:sleep(2000),
  265. Resp = <<"HTTP/1.1 200 OK\r\nServer: Apache-Coyote/1.1\r\nDate: Wed, 04 Apr 2012 16:53:49 GMT\r\nConnection: close\r\n\r\n">>,
  266. do_send(Sock, Sock_type, Resp),
  267. close_connection;
  268. process_request(Sock, Sock_type,
  269. #request{method='GET',
  270. headers = _Headers,
  271. uri = {abs_path, "/ibrowse_handle_one_request_only"}}) ->
  272. Resp = <<"HTTP/1.1 200 OK\r\nServer: Apache-Coyote/1.1\r\nDate: Wed, 04 Apr 2012 16:53:49 GMT\r\nConnection: close\r\n\r\n">>,
  273. do_send(Sock, Sock_type, Resp),
  274. close_connection;
  275. process_request(Sock, Sock_type,
  276. #request{method='GET',
  277. headers = _Headers,
  278. uri = {abs_path, "/ibrowse_send_file_conn_close"}}) ->
  279. Resp = <<"HTTP/1.1 200 OK\r\nServer: Apache-Coyote/1.1\r\nDate: Wed, 04 Apr 2012 16:53:49 GMT\r\nConnection: close\r\n\r\nblahblah-">>,
  280. do_send(Sock, Sock_type, Resp),
  281. timer:sleep(1000),
  282. do_send(Sock, Sock_type, <<"blahblah">>),
  283. close_connection;
  284. process_request(_Sock, _Sock_type, #request{uri = {abs_path, "/never_respond"} } ) ->
  285. not_done;
  286. process_request(Sock, Sock_type, Req) ->
  287. do_trace("Recvd req: ~p~n", [Req]),
  288. Resp = <<"HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n">>,
  289. do_send(Sock, Sock_type, Resp),
  290. timer:sleep(?RAND:uniform(100)).
  291. do_send(Sock, tcp, Resp) ->
  292. gen_tcp:send(Sock, Resp);
  293. do_send(Sock, ssl, Resp) ->
  294. ssl:send(Sock, Resp).
  295. %%------------------------------------------------------------------------------
  296. %% Utility functions
  297. %%------------------------------------------------------------------------------
  298. chunk_request_body(Body, _ChunkSize) when is_tuple(Body) orelse
  299. is_function(Body) ->
  300. Body;
  301. chunk_request_body(Body, ChunkSize) ->
  302. chunk_request_body(Body, ChunkSize, []).
  303. chunk_request_body(Body, _ChunkSize, Acc) when Body == <<>>; Body == [] ->
  304. LastChunk = "0\r\n",
  305. lists:reverse(["\r\n", LastChunk | Acc]);
  306. chunk_request_body(Body, ChunkSize, Acc) when is_binary(Body),
  307. size(Body) >= ChunkSize ->
  308. <<ChunkBody:ChunkSize/binary, Rest/binary>> = Body,
  309. Chunk = [?dec2hex(ChunkSize),"\r\n",
  310. ChunkBody, "\r\n"],
  311. chunk_request_body(Rest, ChunkSize, [Chunk | Acc]);
  312. chunk_request_body(Body, _ChunkSize, Acc) when is_binary(Body) ->
  313. BodySize = size(Body),
  314. Chunk = [?dec2hex(BodySize),"\r\n",
  315. Body, "\r\n"],
  316. LastChunk = "0\r\n",
  317. lists:reverse(["\r\n", LastChunk, Chunk | Acc]);
  318. chunk_request_body(Body, ChunkSize, Acc) when length(Body) >= ChunkSize ->
  319. {ChunkBody, Rest} = split_list_at(Body, ChunkSize),
  320. Chunk = [?dec2hex(ChunkSize),"\r\n",
  321. ChunkBody, "\r\n"],
  322. chunk_request_body(Rest, ChunkSize, [Chunk | Acc]);
  323. chunk_request_body(Body, _ChunkSize, Acc) when is_list(Body) ->
  324. BodySize = length(Body),
  325. Chunk = [?dec2hex(BodySize),"\r\n",
  326. Body, "\r\n"],
  327. LastChunk = "0\r\n",
  328. lists:reverse(["\r\n", LastChunk, Chunk | Acc]).
  329. split_list_at(List, N) ->
  330. split_list_at(List, N, []).
  331. split_list_at([], _, Acc) ->
  332. {lists:reverse(Acc), []};
  333. split_list_at(List2, 0, List1) ->
  334. {lists:reverse(List1), List2};
  335. split_list_at([H | List2], N, List1) ->
  336. split_list_at(List2, N-1, [H | List1]).
  337. to_lower(X) when is_atom(X) ->
  338. list_to_atom(to_lower(atom_to_list(X)));
  339. to_lower(X) when is_list(X) ->
  340. string:to_lower(X).
  341. get_content_length([{http_header, _, 'Content-Length', _, V} | _]) ->
  342. list_to_integer(V);
  343. get_content_length([{http_header, _, _X, _, _Y} | T]) ->
  344. get_content_length(T);
  345. get_content_length([]) ->
  346. undefined.