~ubuntu-branches/ubuntu/saucy/rabbitmq-server/saucy-proposed

« back to all changes in this revision

Viewing changes to plugins-src/cowboy-wrapper/cowboy-git/test/http_SUITE.erl

  • Committer: Package Import Robot
  • Author(s): Emile Joubert
  • Date: 2012-11-19 11:42:31 UTC
  • mfrom: (0.2.18) (0.1.32 sid)
  • Revision ID: package-import@ubuntu.com-20121119114231-hvapkn4akng09etr
Tags: 3.0.0-1
New upstream release

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
%% Copyright (c) 2011, Loïc Hoguin <essen@dev-extend.eu>
 
2
%% Copyright (c) 2011, Anthony Ramine <nox@dev-extend.eu>
 
3
%%
 
4
%% Permission to use, copy, modify, and/or distribute this software for any
 
5
%% purpose with or without fee is hereby granted, provided that the above
 
6
%% copyright notice and this permission notice appear in all copies.
 
7
%%
 
8
%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 
9
%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 
10
%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 
11
%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 
12
%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 
13
%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 
14
%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 
15
 
 
16
-module(http_SUITE).
 
17
 
 
18
-include_lib("common_test/include/ct.hrl").
 
19
 
 
20
-export([all/0, groups/0, init_per_suite/1, end_per_suite/1,
 
21
        init_per_group/2, end_per_group/2]). %% ct.
 
22
-export([chunked_response/1, headers_dupe/1, headers_huge/1,
 
23
        keepalive_nl/1, max_keepalive/1, nc_rand/1, nc_zero/1,
 
24
        pipeline/1, raw/1, set_resp_header/1, set_resp_overwrite/1,
 
25
        set_resp_body/1, stream_body_set_resp/1, response_as_req/1,
 
26
        static_mimetypes_function/1, static_attribute_etag/1,
 
27
        static_function_etag/1, multipart/1]). %% http.
 
28
-export([http_200/1, http_404/1, handler_errors/1,
 
29
        file_200/1, file_403/1, dir_403/1, file_404/1,
 
30
        file_400/1]). %% http and https.
 
31
-export([http_10_hostless/1]). %% misc.
 
32
-export([rest_simple/1, rest_keepalive/1, rest_keepalive_post/1]). %% rest.
 
33
 
 
34
%% ct.
 
35
 
 
36
all() ->
 
37
        [{group, http}, {group, https}, {group, misc}, {group, rest}].
 
38
 
 
39
groups() ->
 
40
        BaseTests = [http_200, http_404, handler_errors,
 
41
                file_200, file_403, dir_403, file_404, file_400],
 
42
        [{http, [], [chunked_response, headers_dupe, headers_huge,
 
43
                keepalive_nl, max_keepalive, nc_rand, nc_zero, pipeline, raw,
 
44
                set_resp_header, set_resp_overwrite,
 
45
                set_resp_body, response_as_req, stream_body_set_resp,
 
46
                static_mimetypes_function, static_attribute_etag,
 
47
                static_function_etag, multipart] ++ BaseTests},
 
48
        {https, [], BaseTests},
 
49
        {misc, [], [http_10_hostless]},
 
50
        {rest, [], [rest_simple, rest_keepalive, rest_keepalive_post]}].
 
51
 
 
52
init_per_suite(Config) ->
 
53
        application:start(inets),
 
54
        application:start(cowboy),
 
55
        Config.
 
56
 
 
57
end_per_suite(_Config) ->
 
58
        application:stop(cowboy),
 
59
        application:stop(inets),
 
60
        ok.
 
61
 
 
62
init_per_group(http, Config) ->
 
63
        Port = 33080,
 
64
        Config1 = init_static_dir(Config),
 
65
        cowboy:start_listener(http, 100,
 
66
                cowboy_tcp_transport, [{port, Port}],
 
67
                cowboy_http_protocol, [{max_keepalive, 50},
 
68
                        {dispatch, init_http_dispatch(Config1)}]
 
69
        ),
 
70
        [{scheme, "http"}, {port, Port}|Config1];
 
71
init_per_group(https, Config) ->
 
72
        Port = 33081,
 
73
        Config1 = init_static_dir(Config),
 
74
        application:start(crypto),
 
75
        application:start(public_key),
 
76
        application:start(ssl),
 
77
        DataDir = ?config(data_dir, Config),
 
78
        cowboy:start_listener(https, 100,
 
79
                cowboy_ssl_transport, [
 
80
                        {port, Port}, {certfile, DataDir ++ "cert.pem"},
 
81
                        {keyfile, DataDir ++ "key.pem"}, {password, "cowboy"}],
 
82
                cowboy_http_protocol, [{dispatch, init_https_dispatch(Config1)}]
 
83
        ),
 
84
        [{scheme, "https"}, {port, Port}|Config1];
 
85
init_per_group(misc, Config) ->
 
86
        Port = 33082,
 
87
        cowboy:start_listener(misc, 100,
 
88
                cowboy_tcp_transport, [{port, Port}],
 
89
                cowboy_http_protocol, [{dispatch, [{'_', [
 
90
                        {[], http_handler, []}
 
91
        ]}]}]),
 
92
        [{port, Port}|Config];
 
93
init_per_group(rest, Config) ->
 
94
        Port = 33083,
 
95
        cowboy:start_listener(reset, 100,
 
96
                cowboy_tcp_transport, [{port, Port}],
 
97
                cowboy_http_protocol, [{dispatch, [{'_', [
 
98
                        {[<<"simple">>], rest_simple_resource, []},
 
99
                        {[<<"forbidden_post">>], rest_forbidden_resource, [true]},
 
100
                        {[<<"simple_post">>], rest_forbidden_resource, [false]}
 
101
        ]}]}]),
 
102
        [{port, Port}|Config].
 
103
 
 
104
end_per_group(https, Config) ->
 
105
        cowboy:stop_listener(https),
 
106
        application:stop(ssl),
 
107
        application:stop(public_key),
 
108
        application:stop(crypto),
 
109
        end_static_dir(Config),
 
110
        ok;
 
111
end_per_group(http, Config) ->
 
112
        cowboy:stop_listener(http),
 
113
        end_static_dir(Config);
 
114
end_per_group(Listener, _Config) ->
 
115
        cowboy:stop_listener(Listener),
 
116
        ok.
 
117
 
 
118
%% Dispatch configuration.
 
119
 
 
120
init_http_dispatch(Config) ->
 
121
        [
 
122
                {[<<"localhost">>], [
 
123
                        {[<<"chunked_response">>], chunked_handler, []},
 
124
                        {[<<"init_shutdown">>], http_handler_init_shutdown, []},
 
125
                        {[<<"long_polling">>], http_handler_long_polling, []},
 
126
                        {[<<"headers">>, <<"dupe">>], http_handler,
 
127
                                [{headers, [{<<"Connection">>, <<"close">>}]}]},
 
128
                        {[<<"set_resp">>, <<"header">>], http_handler_set_resp,
 
129
                                [{headers, [{<<"Vary">>, <<"Accept">>}]}]},
 
130
                        {[<<"set_resp">>, <<"overwrite">>], http_handler_set_resp,
 
131
                                [{headers, [{<<"Server">>, <<"DesireDrive/1.0">>}]}]},
 
132
                        {[<<"set_resp">>, <<"body">>], http_handler_set_resp,
 
133
                                [{body, <<"A flameless dance does not equal a cycle">>}]},
 
134
                        {[<<"stream_body">>, <<"set_resp">>], http_handler_stream_body,
 
135
                                [{reply, set_resp}, {body, <<"stream_body_set_resp">>}]},
 
136
                        {[<<"static">>, '...'], cowboy_http_static,
 
137
                                [{directory, ?config(static_dir, Config)},
 
138
                                 {mimetypes, [{<<".css">>, [<<"text/css">>]}]}]},
 
139
                        {[<<"static_mimetypes_function">>, '...'], cowboy_http_static,
 
140
                                [{directory, ?config(static_dir, Config)},
 
141
                                 {mimetypes, {fun(Path, data) when is_binary(Path) ->
 
142
                                        [<<"text/html">>] end, data}}]},
 
143
                        {[<<"handler_errors">>], http_handler_errors, []},
 
144
                        {[<<"static_attribute_etag">>, '...'], cowboy_http_static,
 
145
                                [{directory, ?config(static_dir, Config)},
 
146
                                 {etag, {attributes, [filepath, filesize, inode, mtime]}}]},
 
147
                        {[<<"static_function_etag">>, '...'], cowboy_http_static,
 
148
                                [{directory, ?config(static_dir, Config)},
 
149
                                 {etag, {fun static_function_etag/2, etag_data}}]},
 
150
                        {[<<"multipart">>], http_handler_multipart, []},
 
151
                        {[], http_handler, []}
 
152
                ]}
 
153
        ].
 
154
 
 
155
init_https_dispatch(Config) ->
 
156
        init_http_dispatch(Config).
 
157
 
 
158
 
 
159
init_static_dir(Config) ->
 
160
        Dir = filename:join(?config(priv_dir, Config), "static"),
 
161
        Level1 = fun(Name) -> filename:join(Dir, Name) end,
 
162
        ok = file:make_dir(Dir),
 
163
        ok = file:write_file(Level1("test_file"), "test_file\n"),
 
164
        ok = file:write_file(Level1("test_file.css"), "test_file.css\n"),
 
165
        ok = file:write_file(Level1("test_noread"), "test_noread\n"),
 
166
        ok = file:change_mode(Level1("test_noread"), 8#0333),
 
167
        ok = file:write_file(Level1("test.html"), "test.html\n"),
 
168
        ok = file:make_dir(Level1("test_dir")),
 
169
        [{static_dir, Dir}|Config].
 
170
 
 
171
end_static_dir(Config) ->
 
172
        Dir = ?config(static_dir, Config),
 
173
        Level1 = fun(Name) -> filename:join(Dir, Name) end,
 
174
        ok = file:delete(Level1("test_file")),
 
175
        ok = file:delete(Level1("test_file.css")),
 
176
        ok = file:delete(Level1("test_noread")),
 
177
        ok = file:delete(Level1("test.html")),
 
178
        ok = file:del_dir(Level1("test_dir")),
 
179
        ok = file:del_dir(Dir),
 
180
        Config.
 
181
 
 
182
%% http.
 
183
 
 
184
chunked_response(Config) ->
 
185
        {ok, {{"HTTP/1.1", 200, "OK"}, _Headers, "chunked_handler\r\nworks fine!"}} =
 
186
                httpc:request(build_url("/chunked_response", Config)).
 
187
 
 
188
headers_dupe(Config) ->
 
189
        {port, Port} = lists:keyfind(port, 1, Config),
 
190
        {ok, Socket} = gen_tcp:connect("localhost", Port,
 
191
                [binary, {active, false}, {packet, raw}]),
 
192
        ok = gen_tcp:send(Socket, "GET /headers/dupe HTTP/1.1\r\n"
 
193
                "Host: localhost\r\nConnection: keep-alive\r\n\r\n"),
 
194
        {ok, Data} = gen_tcp:recv(Socket, 0, 6000),
 
195
        {_Start, _Length} = binary:match(Data, <<"Connection: close">>),
 
196
        nomatch = binary:match(Data, <<"Connection: keep-alive">>),
 
197
        {error, closed} = gen_tcp:recv(Socket, 0, 1000).
 
198
 
 
199
headers_huge(Config) ->
 
200
        Cookie = lists:flatten(["whatever_man_biiiiiiiiiiiig_cookie_me_want_77="
 
201
                "Wed Apr 06 2011 10:38:52 GMT-0500 (CDT)" || _N <- lists:seq(1, 40)]),
 
202
        {_Packet, 200} = raw_req(["GET / HTTP/1.0\r\nHost: localhost\r\n"
 
203
                "Set-Cookie: ", Cookie, "\r\n\r\n"], Config).
 
204
 
 
205
keepalive_nl(Config) ->
 
206
        {port, Port} = lists:keyfind(port, 1, Config),
 
207
        {ok, Socket} = gen_tcp:connect("localhost", Port,
 
208
                [binary, {active, false}, {packet, raw}]),
 
209
        ok = keepalive_nl_loop(Socket, 10),
 
210
        ok = gen_tcp:close(Socket).
 
211
 
 
212
keepalive_nl_loop(_Socket, 0) ->
 
213
        ok;
 
214
keepalive_nl_loop(Socket, N) ->
 
215
        ok = gen_tcp:send(Socket, "GET / HTTP/1.1\r\n"
 
216
                "Host: localhost\r\nConnection: keep-alive\r\n\r\n"),
 
217
        {ok, Data} = gen_tcp:recv(Socket, 0, 6000),
 
218
        {0, 12} = binary:match(Data, <<"HTTP/1.1 200">>),
 
219
        nomatch = binary:match(Data, <<"Connection: close">>),
 
220
        ok = gen_tcp:send(Socket, "\r\n"), %% extra nl
 
221
        keepalive_nl_loop(Socket, N - 1).
 
222
 
 
223
max_keepalive(Config) ->
 
224
        {port, Port} = lists:keyfind(port, 1, Config),
 
225
        {ok, Socket} = gen_tcp:connect("localhost", Port,
 
226
                [binary, {active, false}, {packet, raw}]),
 
227
        ok = max_keepalive_loop(Socket, 50),
 
228
        {error, closed} = gen_tcp:recv(Socket, 0, 1000).
 
229
 
 
230
max_keepalive_loop(_Socket, 0) ->
 
231
        ok;
 
232
max_keepalive_loop(Socket, N) ->
 
233
        ok = gen_tcp:send(Socket, "GET / HTTP/1.1\r\n"
 
234
                "Host: localhost\r\nConnection: keep-alive\r\n\r\n"),
 
235
        {ok, Data} = gen_tcp:recv(Socket, 0, 6000),
 
236
        {0, 12} = binary:match(Data, <<"HTTP/1.1 200">>),
 
237
        case N of
 
238
                1 -> {_, _} = binary:match(Data, <<"Connection: close">>);
 
239
                N -> nomatch = binary:match(Data, <<"Connection: close">>)
 
240
        end,
 
241
        keepalive_nl_loop(Socket, N - 1).
 
242
 
 
243
multipart(Config) ->
 
244
        Url = build_url("/multipart", Config),
 
245
        Body = <<
 
246
                "This is a preamble."
 
247
                "\r\n--OHai\r\nX-Name:answer\r\n\r\n42"
 
248
                "\r\n--OHai\r\nServer:Cowboy\r\n\r\nIt rocks!\r\n"
 
249
                "\r\n--OHai--"
 
250
                "This is an epiloque."
 
251
        >>,
 
252
        Request = {Url, [], "multipart/x-makes-no-sense; boundary=OHai", Body},
 
253
        {ok, {{"HTTP/1.1", 200, "OK"}, _Headers, Response}} =
 
254
                httpc:request(post, Request, [], [{body_format, binary}]),
 
255
        Parts = binary_to_term(Response),
 
256
        Parts = [
 
257
                {[{<<"X-Name">>, <<"answer">>}], <<"42">>},
 
258
                {[{'Server', <<"Cowboy">>}], <<"It rocks!\r\n">>}
 
259
        ].
 
260
 
 
261
nc_rand(Config) ->
 
262
        nc_reqs(Config, "/dev/urandom").
 
263
 
 
264
nc_zero(Config) ->
 
265
        nc_reqs(Config, "/dev/zero").
 
266
 
 
267
nc_reqs(Config, Input) ->
 
268
        Cat = os:find_executable("cat"),
 
269
        Nc = os:find_executable("nc"),
 
270
        case {Cat, Nc} of
 
271
                {false, _} ->
 
272
                        {skip, {notfound, cat}};
 
273
                {_, false} ->
 
274
                        {skip, {notfound, nc}};
 
275
                _Good ->
 
276
                        %% Throw garbage at the server then check if it's still up.
 
277
                        {port, Port} = lists:keyfind(port, 1, Config),
 
278
                        [nc_run_req(Port, Input) || _N <- lists:seq(1, 100)],
 
279
                        Packet = "GET / HTTP/1.0\r\nHost: localhost\r\n\r\n",
 
280
                        {Packet, 200} = raw_req(Packet, Config)
 
281
        end.
 
282
 
 
283
nc_run_req(Port, Input) ->
 
284
        os:cmd("cat " ++ Input ++ " | nc localhost " ++ integer_to_list(Port)).
 
285
 
 
286
pipeline(Config) ->
 
287
        {port, Port} = lists:keyfind(port, 1, Config),
 
288
        {ok, Socket} = gen_tcp:connect("localhost", Port,
 
289
                [binary, {active, false}, {packet, raw}]),
 
290
        ok = gen_tcp:send(Socket,
 
291
                "GET / HTTP/1.1\r\nHost: localhost\r\nConnection: keep-alive\r\n\r\n"
 
292
                "GET / HTTP/1.1\r\nHost: localhost\r\nConnection: keep-alive\r\n\r\n"
 
293
                "GET / HTTP/1.1\r\nHost: localhost\r\nConnection: keep-alive\r\n\r\n"
 
294
                "GET / HTTP/1.1\r\nHost: localhost\r\nConnection: keep-alive\r\n\r\n"
 
295
                "GET / HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n"),
 
296
        Data = pipeline_recv(Socket, <<>>),
 
297
        Reqs = binary:split(Data, << "\r\n\r\nhttp_handler" >>, [global, trim]),
 
298
        5 = length(Reqs),
 
299
        pipeline_check(Reqs).
 
300
 
 
301
pipeline_check([]) ->
 
302
        ok;
 
303
pipeline_check([Req|Tail]) ->
 
304
        << "HTTP/1.1 200", _Rest/bits >> = Req,
 
305
        pipeline_check(Tail).
 
306
 
 
307
pipeline_recv(Socket, SoFar) ->
 
308
        case gen_tcp:recv(Socket, 0, 6000) of
 
309
                {ok, Data} ->
 
310
                        pipeline_recv(Socket, << SoFar/binary, Data/binary >>);
 
311
                {error, closed} ->
 
312
                        ok = gen_tcp:close(Socket),
 
313
                        SoFar
 
314
        end.
 
315
 
 
316
raw_req(Packet, Config) ->
 
317
        {port, Port} = lists:keyfind(port, 1, Config),
 
318
        {ok, Socket} = gen_tcp:connect("localhost", Port,
 
319
                [binary, {active, false}, {packet, raw}]),
 
320
        ok = gen_tcp:send(Socket, Packet),
 
321
        Res = case gen_tcp:recv(Socket, 0, 6000) of
 
322
                {ok, << "HTTP/1.1 ", Str:24/bits, _Rest/bits >>} ->
 
323
                        list_to_integer(binary_to_list(Str));
 
324
                {error, Reason} ->
 
325
                        Reason
 
326
        end,
 
327
        gen_tcp:close(Socket),
 
328
        {Packet, Res}.
 
329
 
 
330
%% Send a raw request. Return the response code and the full response.
 
331
raw_resp(Request, Config) ->
 
332
        {port, Port} = lists:keyfind(port, 1, Config),
 
333
        Transport = case ?config(scheme, Config) of
 
334
                "http" -> gen_tcp;
 
335
                "https" -> ssl
 
336
        end,
 
337
        {ok, Socket} = Transport:connect("localhost", Port,
 
338
                [binary, {active, false}, {packet, raw}]),
 
339
        ok = Transport:send(Socket, Request),
 
340
        {StatusCode,  Response} = case recv_loop(Transport, Socket, <<>>) of
 
341
                {ok, << "HTTP/1.1 ", Str:24/bits, _Rest/bits >> = Bin} ->
 
342
                        {list_to_integer(binary_to_list(Str)), Bin};
 
343
                {ok, Bin} ->
 
344
                        {badresp, Bin};
 
345
                {error, Reason} ->
 
346
                        {Reason, <<>>}
 
347
        end,
 
348
        Transport:close(Socket),
 
349
        {Response, StatusCode}.
 
350
 
 
351
recv_loop(Transport, Socket, Acc) ->
 
352
        case Transport:recv(Socket, 0, 6000) of
 
353
                {ok, Data} ->
 
354
                        recv_loop(Transport, Socket, <<Acc/binary, Data/binary>>);
 
355
                {error, closed} ->
 
356
                        ok = Transport:close(Socket),
 
357
                        {ok, Acc};
 
358
                {error, Reason} ->
 
359
                        {error, Reason}
 
360
        end.
 
361
 
 
362
 
 
363
 
 
364
raw(Config) ->
 
365
        Huge = [$0 || _N <- lists:seq(1, 5000)],
 
366
        Tests = [
 
367
                {"\r\n\r\n\r\n\r\n\r\nGET / HTTP/1.1\r\nHost: localhost\r\n\r\n", 200},
 
368
                {"\n", 400},
 
369
                {"Garbage\r\n\r\n", 400},
 
370
                {"\r\n\r\n\r\n\r\n\r\n\r\n", 400},
 
371
                {"GET / HTTP/1.1\r\nHost: dev-extend.eu\r\n\r\n", 400},
 
372
                {"", closed},
 
373
                {"\r\n", closed},
 
374
                {"\r\n\r\n", closed},
 
375
                {"GET / HTTP/1.1", closed},
 
376
                {"GET / HTTP/1.1\r\n", 408},
 
377
                {"GET / HTTP/1.1\r\nHost: localhost", 408},
 
378
                {"GET / HTTP/1.1\r\nHost: localhost\r\n", 408},
 
379
                {"GET / HTTP/1.1\r\nHost: localhost\r\n\r", 408},
 
380
                {"GET http://localhost/ HTTP/1.1\r\n\r\n", 501},
 
381
                {"GET / HTTP/1.2\r\nHost: localhost\r\n\r\n", 505},
 
382
                {"GET /init_shutdown HTTP/1.1\r\nHost: localhost\r\n\r\n", 666},
 
383
                {"GET /long_polling HTTP/1.1\r\nHost: localhost\r\n\r\n", 102},
 
384
                {Huge, 413},
 
385
                {"GET / HTTP/1.1\r\n" ++ Huge, 413}
 
386
        ],
 
387
        [{Packet, StatusCode} = raw_req(Packet, Config)
 
388
                || {Packet, StatusCode} <- Tests].
 
389
 
 
390
set_resp_header(Config) ->
 
391
        {port, Port} = lists:keyfind(port, 1, Config),
 
392
        {ok, Socket} = gen_tcp:connect("localhost", Port,
 
393
                [binary, {active, false}, {packet, raw}]),
 
394
        ok = gen_tcp:send(Socket, "GET /set_resp/header HTTP/1.1\r\n"
 
395
                "Host: localhost\r\nConnection: close\r\n\r\n"),
 
396
        {ok, Data} = gen_tcp:recv(Socket, 0, 6000),
 
397
        {_, _} = binary:match(Data, <<"Vary: Accept">>),
 
398
        {_, _} = binary:match(Data, <<"Set-Cookie: ">>).
 
399
 
 
400
set_resp_overwrite(Config) ->
 
401
        {port, Port} = lists:keyfind(port, 1, Config),
 
402
        {ok, Socket} = gen_tcp:connect("localhost", Port,
 
403
                [binary, {active, false}, {packet, raw}]),
 
404
        ok = gen_tcp:send(Socket, "GET /set_resp/overwrite HTTP/1.1\r\n"
 
405
                "Host: localhost\r\nConnection: close\r\n\r\n"),
 
406
        {ok, Data} = gen_tcp:recv(Socket, 0, 6000),
 
407
        {_Start, _Length} = binary:match(Data, <<"Server: DesireDrive/1.0">>).
 
408
 
 
409
set_resp_body(Config) ->
 
410
        {port, Port} = lists:keyfind(port, 1, Config),
 
411
        {ok, Socket} = gen_tcp:connect("localhost", Port,
 
412
                [binary, {active, false}, {packet, raw}]),
 
413
        ok = gen_tcp:send(Socket, "GET /set_resp/body HTTP/1.1\r\n"
 
414
                "Host: localhost\r\nConnection: close\r\n\r\n"),
 
415
        {ok, Data} = gen_tcp:recv(Socket, 0, 6000),
 
416
        {_Start, _Length} = binary:match(Data, <<"\r\n\r\n"
 
417
                "A flameless dance does not equal a cycle">>).
 
418
 
 
419
response_as_req(Config) ->
 
420
        Packet =
 
421
"HTTP/1.0 302 Found
 
422
Location: http://www.google.co.il/
 
423
Cache-Control: private
 
424
Content-Type: text/html; charset=UTF-8
 
425
Set-Cookie: PREF=ID=568f67013d4a7afa:FF=0:TM=1323014101:LM=1323014101:S=XqctDWC65MzKT0zC; expires=Tue, 03-Dec-2013 15:55:01 GMT; path=/; domain=.google.com
 
426
Date: Sun, 04 Dec 2011 15:55:01 GMT
 
427
Server: gws
 
428
Content-Length: 221
 
429
X-XSS-Protection: 1; mode=block
 
430
X-Frame-Options: SAMEORIGIN
 
431
 
 
432
<HTML><HEAD><meta http-equiv=\"content-type\" content=\"text/html;charset=utf-8\">
 
433
<TITLE>302 Moved</TITLE></HEAD><BODY>
 
434
<H1>302 Moved</H1>
 
435
The document has moved
 
436
<A HREF=\"http://www.google.co.il/\">here</A>.
 
437
</BODY></HTML>",
 
438
        {Packet, 400} = raw_req(Packet, Config).
 
439
 
 
440
stream_body_set_resp(Config) ->
 
441
        {Packet, 200} = raw_resp(
 
442
                "GET /stream_body/set_resp HTTP/1.1\r\n"
 
443
                "Host: localhost\r\nConnection: close\r\n\r\n", Config),
 
444
        {_Start, _Length} = binary:match(Packet, <<"stream_body_set_resp">>).
 
445
 
 
446
static_mimetypes_function(Config) ->
 
447
        TestURL = build_url("/static_mimetypes_function/test.html", Config),
 
448
        {ok, {{"HTTP/1.1", 200, "OK"}, Headers1, "test.html\n"}} =
 
449
                httpc:request(TestURL),
 
450
        "text/html" = ?config("content-type", Headers1).
 
451
 
 
452
handler_errors(Config) ->
 
453
        Request = fun(Case) ->
 
454
                raw_resp(["GET /handler_errors?case=", Case, " HTTP/1.1\r\n",
 
455
                 "Host: localhost\r\n\r\n"], Config) end,
 
456
 
 
457
        {_Packet1, 500} = Request("init_before_reply"),
 
458
 
 
459
        {Packet2, 200} = Request("init_after_reply"),
 
460
        nomatch = binary:match(Packet2, <<"HTTP/1.1 500">>),
 
461
 
 
462
        {Packet3, 200} = Request("init_reply_handle_error"),
 
463
        nomatch = binary:match(Packet3, <<"HTTP/1.1 500">>),
 
464
 
 
465
        {_Packet4, 500} = Request("handle_before_reply"),
 
466
 
 
467
        {Packet5, 200} = Request("handle_after_reply"),
 
468
        nomatch = binary:match(Packet5, <<"HTTP/1.1 500">>),
 
469
 
 
470
        {Packet6, 200} = raw_resp([
 
471
                "GET / HTTP/1.1\r\n",
 
472
                "Host: localhost\r\n",
 
473
                "Connection: keep-alive\r\n\r\n",
 
474
                "GET /handler_errors?case=handle_after_reply\r\n",
 
475
                "Host: localhost\r\n\r\n"], Config),
 
476
        nomatch = binary:match(Packet6, <<"HTTP/1.1 500">>),
 
477
 
 
478
        {Packet7, 200} = raw_resp([
 
479
                "GET / HTTP/1.1\r\n",
 
480
                "Host: localhost\r\n",
 
481
                "Connection: keep-alive\r\n\r\n",
 
482
                "GET /handler_errors?case=handle_before_reply HTTP/1.1\r\n",
 
483
                "Host: localhost\r\n\r\n"], Config),
 
484
        {{_, _}, _} = {binary:match(Packet7, <<"HTTP/1.1 500">>), Packet7},
 
485
 
 
486
        done.
 
487
 
 
488
static_attribute_etag(Config) ->
 
489
        TestURL = build_url("/static_attribute_etag/test.html", Config),
 
490
        {ok, {{"HTTP/1.1", 200, "OK"}, Headers1, "test.html\n"}} =
 
491
                httpc:request(TestURL),
 
492
        false = ?config("etag", Headers1) =:= undefined,
 
493
        {ok, {{"HTTP/1.1", 200, "OK"}, Headers2, "test.html\n"}} =
 
494
                httpc:request(TestURL),
 
495
        true = ?config("etag", Headers1) =:= ?config("etag", Headers2).
 
496
 
 
497
static_function_etag(Config) ->
 
498
        TestURL = build_url("/static_function_etag/test.html", Config),
 
499
        {ok, {{"HTTP/1.1", 200, "OK"}, Headers1, "test.html\n"}} =
 
500
                httpc:request(TestURL),
 
501
        false = ?config("etag", Headers1) =:= undefined,
 
502
        {ok, {{"HTTP/1.1", 200, "OK"}, Headers2, "test.html\n"}} =
 
503
                httpc:request(TestURL),
 
504
        true = ?config("etag", Headers1) =:= ?config("etag", Headers2).
 
505
 
 
506
static_function_etag(Arguments, etag_data) ->
 
507
        {_, Filepath} = lists:keyfind(filepath, 1, Arguments),
 
508
        {_, _Filesize} = lists:keyfind(filesize, 1, Arguments),
 
509
        {_, _INode} = lists:keyfind(inode, 1, Arguments),
 
510
        {_, _Modified} = lists:keyfind(mtime, 1, Arguments),
 
511
        ChecksumCommand = lists:flatten(io_lib:format("sha1sum ~s", [Filepath])),
 
512
        [Checksum|_] = string:tokens(os:cmd(ChecksumCommand), " "),
 
513
        iolist_to_binary(Checksum).
 
514
 
 
515
%% http and https.
 
516
 
 
517
build_url(Path, Config) ->
 
518
        {scheme, Scheme} = lists:keyfind(scheme, 1, Config),
 
519
        {port, Port} = lists:keyfind(port, 1, Config),
 
520
        Scheme ++ "://localhost:" ++ integer_to_list(Port) ++ Path.
 
521
 
 
522
http_200(Config) ->
 
523
        {ok, {{"HTTP/1.1", 200, "OK"}, _Headers, "http_handler"}} =
 
524
                httpc:request(build_url("/", Config)).
 
525
 
 
526
http_404(Config) ->
 
527
        {ok, {{"HTTP/1.1", 404, "Not Found"}, _Headers, _Body}} =
 
528
                httpc:request(build_url("/not/found", Config)).
 
529
 
 
530
file_200(Config) ->
 
531
        {ok, {{"HTTP/1.1", 200, "OK"}, Headers, "test_file\n"}} =
 
532
                httpc:request(build_url("/static/test_file", Config)),
 
533
        "application/octet-stream" = ?config("content-type", Headers),
 
534
 
 
535
        {ok, {{"HTTP/1.1", 200, "OK"}, Headers1, "test_file.css\n"}} =
 
536
                httpc:request(build_url("/static/test_file.css", Config)),
 
537
        "text/css" = ?config("content-type", Headers1).
 
538
 
 
539
file_403(Config) ->
 
540
        {ok, {{"HTTP/1.1", 403, "Forbidden"}, _Headers, _Body}} =
 
541
                httpc:request(build_url("/static/test_noread", Config)).
 
542
 
 
543
dir_403(Config) ->
 
544
        {ok, {{"HTTP/1.1", 403, "Forbidden"}, _Headers, _Body}} =
 
545
                httpc:request(build_url("/static/test_dir", Config)),
 
546
        {ok, {{"HTTP/1.1", 403, "Forbidden"}, _Headers, _Body}} =
 
547
                httpc:request(build_url("/static/test_dir/", Config)).
 
548
 
 
549
file_404(Config) ->
 
550
        {ok, {{"HTTP/1.1", 404, "Not Found"}, _Headers, _Body}} =
 
551
                httpc:request(build_url("/static/not_found", Config)).
 
552
 
 
553
file_400(Config) ->
 
554
        {ok, {{"HTTP/1.1", 400, "Bad Request"}, _Headers, _Body}} =
 
555
                httpc:request(build_url("/static/%2f", Config)),
 
556
        {ok, {{"HTTP/1.1", 400, "Bad Request"}, _Headers1, _Body1}} =
 
557
                httpc:request(build_url("/static/%2e", Config)),
 
558
        {ok, {{"HTTP/1.1", 400, "Bad Request"}, _Headers2, _Body2}} =
 
559
                httpc:request(build_url("/static/%2e%2e", Config)).
 
560
%% misc.
 
561
 
 
562
http_10_hostless(Config) ->
 
563
        Packet = "GET / HTTP/1.0\r\n\r\n",
 
564
        {Packet, 200} = raw_req(Packet, Config).
 
565
 
 
566
%% rest.
 
567
 
 
568
rest_simple(Config) ->
 
569
        Packet = "GET /simple HTTP/1.1\r\nHost: localhost\r\n\r\n",
 
570
        {Packet, 200} = raw_req(Packet, Config).
 
571
 
 
572
rest_keepalive(Config) ->
 
573
        {port, Port} = lists:keyfind(port, 1, Config),
 
574
        {ok, Socket} = gen_tcp:connect("localhost", Port,
 
575
                [binary, {active, false}, {packet, raw}]),
 
576
        ok = rest_keepalive_loop(Socket, 100),
 
577
        ok = gen_tcp:close(Socket).
 
578
 
 
579
rest_keepalive_loop(_Socket, 0) ->
 
580
        ok;
 
581
rest_keepalive_loop(Socket, N) ->
 
582
        ok = gen_tcp:send(Socket, "GET /simple HTTP/1.1\r\n"
 
583
                "Host: localhost\r\nConnection: keep-alive\r\n\r\n"),
 
584
        {ok, Data} = gen_tcp:recv(Socket, 0, 6000),
 
585
        {0, 12} = binary:match(Data, <<"HTTP/1.1 200">>),
 
586
        nomatch = binary:match(Data, <<"Connection: close">>),
 
587
        rest_keepalive_loop(Socket, N - 1).
 
588
 
 
589
rest_keepalive_post(Config) ->
 
590
        {port, Port} = lists:keyfind(port, 1, Config),
 
591
        {ok, Socket} = gen_tcp:connect("localhost", Port,
 
592
                [binary, {active, false}, {packet, raw}]),
 
593
        ok = rest_keepalive_post_loop(Socket, 10, forbidden_post),
 
594
        ok = gen_tcp:close(Socket).
 
595
 
 
596
rest_keepalive_post_loop(_Socket, 0, _) ->
 
597
        ok;
 
598
rest_keepalive_post_loop(Socket, N, simple_post) ->
 
599
        ok = gen_tcp:send(Socket, "POST /simple_post HTTP/1.1\r\n"
 
600
                "Host: localhost\r\nConnection: keep-alive\r\n"
 
601
                "Content-Length: 5\r\nContent-Type: text/plain\r\n\r\n12345"),
 
602
        {ok, Data} = gen_tcp:recv(Socket, 0, 6000),
 
603
        {0, 12} = binary:match(Data, <<"HTTP/1.1 303">>),
 
604
        nomatch = binary:match(Data, <<"Connection: close">>),
 
605
        rest_keepalive_post_loop(Socket, N - 1, forbidden_post);
 
606
rest_keepalive_post_loop(Socket, N, forbidden_post) ->
 
607
        ok = gen_tcp:send(Socket, "POST /forbidden_post HTTP/1.1\r\n"
 
608
                "Host: localhost\r\nConnection: keep-alive\r\n"
 
609
                "Content-Length: 5\r\nContent-Type: text/plain\r\n\r\n12345"),
 
610
        {ok, Data} = gen_tcp:recv(Socket, 0, 6000),
 
611
        {0, 12} = binary:match(Data, <<"HTTP/1.1 403">>),
 
612
        nomatch = binary:match(Data, <<"Connection: close">>),
 
613
        rest_keepalive_post_loop(Socket, N - 1, simple_post).