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 years ago
9 years ago
14 years ago
14 years ago
9 years ago
14 years ago
9 years ago
9 years ago
14 years ago
9 years ago
14 years ago
9 years ago
9 years ago
14 years ago
14 years ago
14 years ago
14 years ago
14 years ago
14 years ago
9 years ago
14 years ago
14 years ago
9 years ago
14 years ago
9 years ago
14 years ago
14 years ago
14 years ago
14 years ago
14 years ago
14 years ago
13 years ago
13 years ago
9 years ago
9 years ago
9 years ago
9 years ago
14 years ago
14 years ago
14 years ago
9 years ago
  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.