You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

357 lines
14 KiB

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