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

305 行
12 KiB

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