Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.

256 рядки
9.9 KiB

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