Subversion Repositories SE.SVN

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
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>>).