您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

302 行
11 KiB

  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).