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

256 行
9.9 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. ]).
  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).