12 |
7u83 |
1 |
-module(mochiweb_test_util).
|
|
|
2 |
-export([with_server/3, client_request/4, sock_fun/2,
|
|
|
3 |
read_server_headers/1, drain_reply/3, ssl_client_opts/1]).
|
|
|
4 |
-include("mochiweb_test_util.hrl").
|
|
|
5 |
-include_lib("eunit/include/eunit.hrl").
|
|
|
6 |
|
|
|
7 |
ssl_cert_opts() ->
|
|
|
8 |
EbinDir = filename:dirname(code:which(?MODULE)),
|
|
|
9 |
CertDir = filename:join([EbinDir, "..", "support", "test-materials"]),
|
|
|
10 |
CertFile = filename:join(CertDir, "test_ssl_cert.pem"),
|
|
|
11 |
KeyFile = filename:join(CertDir, "test_ssl_key.pem"),
|
|
|
12 |
[{certfile, CertFile}, {keyfile, KeyFile}].
|
|
|
13 |
|
|
|
14 |
with_server(Transport, ServerFun, ClientFun) ->
|
|
|
15 |
ServerOpts0 = [{ip, "127.0.0.1"}, {port, 0}, {loop, ServerFun}],
|
|
|
16 |
ServerOpts = case Transport of
|
|
|
17 |
plain ->
|
|
|
18 |
ServerOpts0;
|
|
|
19 |
ssl ->
|
|
|
20 |
ServerOpts0 ++ [{ssl, true}, {ssl_opts, ssl_cert_opts()}]
|
|
|
21 |
end,
|
|
|
22 |
{ok, Server} = mochiweb_http:start_link(ServerOpts),
|
|
|
23 |
Port = mochiweb_socket_server:get(Server, port),
|
|
|
24 |
Res = (catch ClientFun(Transport, Port)),
|
|
|
25 |
mochiweb_http:stop(Server),
|
|
|
26 |
Res.
|
|
|
27 |
|
|
|
28 |
-ifdef(sni_unavailable).
|
|
|
29 |
ssl_client_opts(Opts) ->
|
|
|
30 |
[{ssl_imp, new} | Opts].
|
|
|
31 |
-else.
|
|
|
32 |
ssl_client_opts(Opts) ->
|
|
|
33 |
[{server_name_indication, disable} | Opts].
|
|
|
34 |
-endif.
|
|
|
35 |
|
|
|
36 |
sock_fun(Transport, Port) ->
|
|
|
37 |
Opts = [binary, {active, false}, {packet, http}],
|
|
|
38 |
case Transport of
|
|
|
39 |
plain ->
|
|
|
40 |
{ok, Socket} = gen_tcp:connect("127.0.0.1", Port, Opts),
|
|
|
41 |
fun (recv) ->
|
|
|
42 |
gen_tcp:recv(Socket, 0);
|
|
|
43 |
({recv, Length}) ->
|
|
|
44 |
gen_tcp:recv(Socket, Length);
|
|
|
45 |
({send, Data}) ->
|
|
|
46 |
gen_tcp:send(Socket, Data);
|
|
|
47 |
({setopts, L}) ->
|
|
|
48 |
inet:setopts(Socket, L);
|
|
|
49 |
(get) ->
|
|
|
50 |
Socket
|
|
|
51 |
end;
|
|
|
52 |
ssl ->
|
|
|
53 |
{ok, Socket} = ssl:connect("127.0.0.1", Port, ssl_client_opts(Opts)),
|
|
|
54 |
fun (recv) ->
|
|
|
55 |
ssl:recv(Socket, 0);
|
|
|
56 |
({recv, Length}) ->
|
|
|
57 |
ssl:recv(Socket, Length);
|
|
|
58 |
({send, Data}) ->
|
|
|
59 |
ssl:send(Socket, Data);
|
|
|
60 |
({setopts, L}) ->
|
|
|
61 |
ssl:setopts(Socket, L);
|
|
|
62 |
(get) ->
|
|
|
63 |
{ssl, Socket}
|
|
|
64 |
end
|
|
|
65 |
end.
|
|
|
66 |
|
|
|
67 |
client_request(Transport, Port, Method, TestReqs) ->
|
|
|
68 |
client_request(sock_fun(Transport, Port), Method, TestReqs).
|
|
|
69 |
|
|
|
70 |
client_request(SockFun, _Method, []) ->
|
|
|
71 |
{the_end, {error, closed}} = {the_end, SockFun(recv)},
|
|
|
72 |
ok;
|
|
|
73 |
client_request(SockFun, Method,
|
|
|
74 |
[#treq{path=Path, body=Body, xreply=ExReply, xheaders=ExHeaders} | Rest]) ->
|
|
|
75 |
Request = [atom_to_list(Method), " ", Path, " HTTP/1.1\r\n",
|
|
|
76 |
client_headers(Body, Rest =:= []),
|
|
|
77 |
"\r\n",
|
|
|
78 |
Body],
|
|
|
79 |
ok = SockFun({setopts, [{packet, http}]}),
|
|
|
80 |
ok = SockFun({send, Request}),
|
|
|
81 |
case Method of
|
|
|
82 |
'GET' ->
|
|
|
83 |
{ok, {http_response, {1,1}, 200, "OK"}} = SockFun(recv);
|
|
|
84 |
'POST' ->
|
|
|
85 |
{ok, {http_response, {1,1}, 201, "Created"}} = SockFun(recv);
|
|
|
86 |
'CONNECT' ->
|
|
|
87 |
{ok, {http_response, {1,1}, 200, "OK"}} = SockFun(recv)
|
|
|
88 |
end,
|
|
|
89 |
Headers = read_server_headers(SockFun),
|
|
|
90 |
?assertMatch("MochiWeb" ++ _, mochiweb_headers:get_value("Server", Headers)),
|
|
|
91 |
?assert(mochiweb_headers:get_value("Date", Headers) =/= undefined),
|
|
|
92 |
?assert(mochiweb_headers:get_value("Content-Type", Headers) =/= undefined),
|
|
|
93 |
ContentLength = list_to_integer(mochiweb_headers:get_value("Content-Length", Headers)),
|
|
|
94 |
EHeaders = mochiweb_headers:make(ExHeaders),
|
|
|
95 |
lists:foreach(
|
|
|
96 |
fun (K) ->
|
|
|
97 |
?assertEqual(mochiweb_headers:get_value(K, EHeaders),
|
|
|
98 |
mochiweb_headers:get_value(K, Headers))
|
|
|
99 |
end,
|
|
|
100 |
%% Assumes implementation details of the headers
|
|
|
101 |
gb_trees:keys(EHeaders)),
|
|
|
102 |
{payload, ExReply} = {payload, drain_reply(SockFun, ContentLength, <<>>)},
|
|
|
103 |
client_request(SockFun, Method, Rest).
|
|
|
104 |
|
|
|
105 |
read_server_headers(SockFun) ->
|
|
|
106 |
ok = SockFun({setopts, [{packet, httph}]}),
|
|
|
107 |
Headers = read_server_headers(SockFun, mochiweb_headers:empty()),
|
|
|
108 |
ok = SockFun({setopts, [{packet, raw}]}),
|
|
|
109 |
Headers.
|
|
|
110 |
|
|
|
111 |
read_server_headers(SockFun, Headers) ->
|
|
|
112 |
case SockFun(recv) of
|
|
|
113 |
{ok, http_eoh} ->
|
|
|
114 |
Headers;
|
|
|
115 |
{ok, {http_header, _, Header, _, Value}} ->
|
|
|
116 |
read_server_headers(
|
|
|
117 |
SockFun,
|
|
|
118 |
mochiweb_headers:insert(Header, Value, Headers))
|
|
|
119 |
end.
|
|
|
120 |
|
|
|
121 |
client_headers(Body, IsLastRequest) ->
|
|
|
122 |
["Host: localhost\r\n",
|
|
|
123 |
case Body of
|
|
|
124 |
<<>> ->
|
|
|
125 |
"";
|
|
|
126 |
_ ->
|
|
|
127 |
["Content-Type: application/octet-stream\r\n",
|
|
|
128 |
"Content-Length: ", integer_to_list(byte_size(Body)), "\r\n"]
|
|
|
129 |
end,
|
|
|
130 |
case IsLastRequest of
|
|
|
131 |
true ->
|
|
|
132 |
"Connection: close\r\n";
|
|
|
133 |
false ->
|
|
|
134 |
""
|
|
|
135 |
end].
|
|
|
136 |
|
|
|
137 |
drain_reply(_SockFun, 0, Acc) ->
|
|
|
138 |
Acc;
|
|
|
139 |
drain_reply(SockFun, Length, Acc) ->
|
|
|
140 |
Sz = erlang:min(Length, 1024),
|
|
|
141 |
{ok, B} = SockFun({recv, Sz}),
|
|
|
142 |
drain_reply(SockFun, Length - Sz, <<Acc/bytes, B/bytes>>).
|