Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

302 строки
11 KiB

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