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

363 行
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. ssl:ssl_accept(Listen_sock, ?ACCEPT_TIMEOUT_MS).
  72. accept_loop(Sock, Sock_type) ->
  73. case do_accept(Sock_type, Sock) of
  74. {ok, Conn} ->
  75. Pid = spawn_link(fun() -> connection(Conn, Sock_type) end),
  76. set_controlling_process(Conn, Sock_type, Pid),
  77. accept_loop(Sock, Sock_type);
  78. {error, timeout} ->
  79. receive
  80. stop ->
  81. ok
  82. after 10 ->
  83. accept_loop(Sock, Sock_type)
  84. end;
  85. Err ->
  86. Err
  87. end.
  88. connection(Conn, Sock_type) ->
  89. catch ets:insert(?CONN_PIPELINE_DEPTH, {self(), 0}),
  90. try
  91. inet:setopts(Conn, [{packet, http}, {active, true}]),
  92. server_loop(Conn, Sock_type, #request{})
  93. after
  94. catch ets:delete(?CONN_PIPELINE_DEPTH, self())
  95. end.
  96. set_controlling_process(Sock, tcp, Pid) ->
  97. gen_tcp:controlling_process(Sock, Pid);
  98. set_controlling_process(Sock, ssl, Pid) ->
  99. ssl:controlling_process(Sock, Pid).
  100. setopts(Sock, tcp, Opts) ->
  101. inet:setopts(Sock, Opts);
  102. setopts(Sock, ssl, Opts) ->
  103. ssl:setopts(Sock, Opts).
  104. server_loop(Sock, Sock_type, #request{headers = Headers} = Req) ->
  105. receive
  106. {http, Sock, {http_request, HttpMethod, HttpUri, HttpVersion}} ->
  107. catch ets:update_counter(?CONN_PIPELINE_DEPTH, self(), 1),
  108. server_loop(Sock, Sock_type, Req#request{method = HttpMethod,
  109. uri = HttpUri,
  110. version = HttpVersion});
  111. {http, Sock, {http_header, _, _, _, _} = H} ->
  112. server_loop(Sock, Sock_type, Req#request{headers = [H | Headers]});
  113. {http, Sock, http_eoh} ->
  114. case process_request(Sock, Sock_type, Req) of
  115. close_connection ->
  116. gen_tcp:shutdown(Sock, read_write);
  117. not_done ->
  118. ok;
  119. collect_body ->
  120. server_loop(Sock, Sock_type, Req#request{state = collect_body});
  121. _ ->
  122. catch ets:update_counter(?CONN_PIPELINE_DEPTH, self(), -1)
  123. end,
  124. server_loop(Sock, Sock_type, #request{});
  125. {http, Sock, {http_error, Packet}} when Req#request.state == collect_body ->
  126. Req_1 = Req#request{body = list_to_binary([Packet, Req#request.body])},
  127. case process_request(Sock, Sock_type, Req_1) of
  128. close_connection ->
  129. gen_tcp:shutdown(Sock, read_write);
  130. ok ->
  131. server_loop(Sock, Sock_type, #request{});
  132. collect_body ->
  133. server_loop(Sock, Sock_type, Req_1)
  134. end;
  135. {http, Sock, {http_error, Err}} ->
  136. io:format("Error parsing HTTP request:~n"
  137. "Req so far : ~p~n"
  138. "Err : ~p", [Req, Err]),
  139. exit({http_error, Err});
  140. {setopts, Opts} ->
  141. setopts(Sock, Sock_type, Opts),
  142. server_loop(Sock, Sock_type, Req);
  143. {tcp_closed, Sock} ->
  144. do_trace("Client closed connection~n", []),
  145. ok;
  146. Other ->
  147. io:format("Recvd unknown msg: ~p~n", [Other]),
  148. exit({unknown_msg, Other})
  149. after 120000 ->
  150. do_trace("Timing out client connection~n", []),
  151. ok
  152. end.
  153. do_trace(Fmt, Args) ->
  154. do_trace(get(my_trace_flag), Fmt, Args).
  155. do_trace(true, Fmt, Args) ->
  156. io:format("~s -- " ++ Fmt, [ibrowse_lib:printable_date() | Args]);
  157. do_trace(_, _, _) ->
  158. ok.
  159. process_request(Sock, Sock_type,
  160. #request{method='GET',
  161. headers = Headers,
  162. uri = {abs_path, "/ibrowse_stream_once_chunk_pipeline_test"}} = Req) ->
  163. Req_id = case lists:keysearch("X-Ibrowse-Request-Id", 3, Headers) of
  164. false ->
  165. "";
  166. {value, {http_header, _, _, _, Req_id_1}} ->
  167. Req_id_1
  168. end,
  169. Req_id_header = ["x-ibrowse-request-id: ", Req_id, "\r\n"],
  170. do_trace("Recvd req: ~p~n", [Req]),
  171. Body = string:join([integer_to_list(X) || X <- lists:seq(1,100)], "-"),
  172. Chunked_body = chunk_request_body(Body, 50),
  173. Resp_1 = [<<"HTTP/1.1 200 OK\r\n">>,
  174. Req_id_header,
  175. <<"Transfer-Encoding: chunked\r\n\r\n">>],
  176. Resp_2 = Chunked_body,
  177. do_send(Sock, Sock_type, Resp_1),
  178. timer:sleep(100),
  179. do_send(Sock, Sock_type, Resp_2);
  180. process_request(Sock, Sock_type,
  181. #request{method='GET',
  182. headers = _Headers,
  183. uri = {abs_path, "/ibrowse_inac_timeout_test"}} = Req) ->
  184. do_trace("Recvd req: ~p. Sleeping for 30 secs...~n", [Req]),
  185. timer:sleep(3000),
  186. do_trace("...Sending response now.~n", []),
  187. Resp = <<"HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n">>,
  188. do_send(Sock, Sock_type, Resp);
  189. process_request(Sock, Sock_type,
  190. #request{method='HEAD',
  191. headers = _Headers,
  192. uri = {abs_path, "/ibrowse_head_transfer_enc"}}) ->
  193. 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">>,
  194. do_send(Sock, Sock_type, Resp);
  195. process_request(Sock, Sock_type,
  196. #request{method='POST',
  197. headers = Headers,
  198. uri = {abs_path, "/echo_body"},
  199. body = Body}) ->
  200. Content_len = get_content_length(Headers),
  201. case iolist_size(Body) == Content_len of
  202. true ->
  203. Resp = [<<"HTTP/1.1 200 OK\r\nContent-Length: ">>, integer_to_list(Content_len), <<"\r\nServer: ibrowse_test_server\r\n\r\n">>, Body],
  204. do_send(Sock, Sock_type, list_to_binary(Resp));
  205. false ->
  206. collect_body
  207. end;
  208. process_request(Sock, Sock_type,
  209. #request{method='GET',
  210. headers = Headers,
  211. uri = {abs_path, "/ibrowse_echo_header"}}) ->
  212. Tag = "x-binary",
  213. Headers_1 = [{to_lower(X), to_lower(Y)} || {http_header, _, X, _, Y} <- Headers],
  214. X_binary_header_val = case lists:keysearch(Tag, 1, Headers_1) of
  215. false ->
  216. "not_found";
  217. {value, {_, V}} ->
  218. V
  219. end,
  220. Resp = [<<"HTTP/1.1 200 OK\r\n">>,
  221. <<"Server: ibrowse_test\r\n">>,
  222. Tag, ": ", X_binary_header_val, "\r\n",
  223. <<"Content-Length: 0\r\n\r\n">>],
  224. do_send(Sock, Sock_type, Resp);
  225. process_request(Sock, Sock_type,
  226. #request{method='HEAD',
  227. headers = _Headers,
  228. uri = {abs_path, "/ibrowse_head_test"}}) ->
  229. 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">>,
  230. do_send(Sock, Sock_type, Resp);
  231. process_request(Sock, Sock_type,
  232. #request{method='POST',
  233. headers = _Headers,
  234. uri = {abs_path, "/ibrowse_303_no_body_test"}}) ->
  235. Resp = <<"HTTP/1.1 303 See Other\r\nLocation: http://example.org\r\n\r\n">>,
  236. do_send(Sock, Sock_type, Resp);
  237. process_request(Sock, Sock_type,
  238. #request{method='POST',
  239. headers = _Headers,
  240. uri = {abs_path, "/ibrowse_303_with_body_test"}}) ->
  241. Resp = <<"HTTP/1.1 303 See Other\r\nLocation: http://example.org\r\nContent-Length: 5\r\n\r\nabcde">>,
  242. do_send(Sock, Sock_type, Resp);
  243. process_request(Sock, Sock_type,
  244. #request{method='GET',
  245. headers = _Headers,
  246. uri = {abs_path, "/ibrowse_preserve_status_line"}}) ->
  247. Resp = <<"HTTP/1.1 200 OKBlah\r\nContent-Length: 5\r\n\r\nabcde">>,
  248. do_send(Sock, Sock_type, Resp);
  249. process_request(Sock, Sock_type,
  250. #request{method='GET',
  251. headers = _Headers,
  252. uri = {abs_path, "/ibrowse_handle_one_request_only_with_delay"}}) ->
  253. timer:sleep(2000),
  254. 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">>,
  255. do_send(Sock, Sock_type, Resp),
  256. close_connection;
  257. process_request(Sock, Sock_type,
  258. #request{method='GET',
  259. headers = _Headers,
  260. uri = {abs_path, "/ibrowse_handle_one_request_only"}}) ->
  261. 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">>,
  262. do_send(Sock, Sock_type, Resp),
  263. close_connection;
  264. process_request(Sock, Sock_type,
  265. #request{method='GET',
  266. headers = _Headers,
  267. uri = {abs_path, "/ibrowse_send_file_conn_close"}}) ->
  268. 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-">>,
  269. do_send(Sock, Sock_type, Resp),
  270. timer:sleep(1000),
  271. do_send(Sock, Sock_type, <<"blahblah">>),
  272. close_connection;
  273. process_request(_Sock, _Sock_type, #request{uri = {abs_path, "/never_respond"} } ) ->
  274. not_done;
  275. process_request(Sock, Sock_type, Req) ->
  276. do_trace("Recvd req: ~p~n", [Req]),
  277. Resp = <<"HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n">>,
  278. do_send(Sock, Sock_type, Resp),
  279. timer:sleep(?RAND:uniform(100)).
  280. do_send(Sock, tcp, Resp) ->
  281. gen_tcp:send(Sock, Resp);
  282. do_send(Sock, ssl, Resp) ->
  283. ssl:send(Sock, Resp).
  284. %%------------------------------------------------------------------------------
  285. %% Utility functions
  286. %%------------------------------------------------------------------------------
  287. chunk_request_body(Body, _ChunkSize) when is_tuple(Body) orelse
  288. is_function(Body) ->
  289. Body;
  290. chunk_request_body(Body, ChunkSize) ->
  291. chunk_request_body(Body, ChunkSize, []).
  292. chunk_request_body(Body, _ChunkSize, Acc) when Body == <<>>; Body == [] ->
  293. LastChunk = "0\r\n",
  294. lists:reverse(["\r\n", LastChunk | Acc]);
  295. chunk_request_body(Body, ChunkSize, Acc) when is_binary(Body),
  296. size(Body) >= ChunkSize ->
  297. <<ChunkBody:ChunkSize/binary, Rest/binary>> = Body,
  298. Chunk = [?dec2hex(ChunkSize),"\r\n",
  299. ChunkBody, "\r\n"],
  300. chunk_request_body(Rest, ChunkSize, [Chunk | Acc]);
  301. chunk_request_body(Body, _ChunkSize, Acc) when is_binary(Body) ->
  302. BodySize = size(Body),
  303. Chunk = [?dec2hex(BodySize),"\r\n",
  304. Body, "\r\n"],
  305. LastChunk = "0\r\n",
  306. lists:reverse(["\r\n", LastChunk, Chunk | Acc]);
  307. chunk_request_body(Body, ChunkSize, Acc) when length(Body) >= ChunkSize ->
  308. {ChunkBody, Rest} = split_list_at(Body, ChunkSize),
  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_list(Body) ->
  313. BodySize = length(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. split_list_at(List, N) ->
  319. split_list_at(List, N, []).
  320. split_list_at([], _, Acc) ->
  321. {lists:reverse(Acc), []};
  322. split_list_at(List2, 0, List1) ->
  323. {lists:reverse(List1), List2};
  324. split_list_at([H | List2], N, List1) ->
  325. split_list_at(List2, N-1, [H | List1]).
  326. to_lower(X) when is_atom(X) ->
  327. list_to_atom(to_lower(atom_to_list(X)));
  328. to_lower(X) when is_list(X) ->
  329. string:to_lower(X).
  330. get_content_length([{http_header, _, 'Content-Length', _, V} | _]) ->
  331. list_to_integer(V);
  332. get_content_length([{http_header, _, _X, _, _Y} | T]) ->
  333. get_content_length(T);
  334. get_content_length([]) ->
  335. undefined.