4
%% Copyright Ericsson AB 2001-2010. All Rights Reserved.
6
%% The contents of this file are subject to the Erlang Public License,
7
%% Version 1.1, (the "License"); you may not use this file except in
8
%% compliance with the License. You should have received a copy of the
9
%% Erlang Public License along with this software. If not, it can be
10
%% retrieved online at http://www.erlang.org/.
12
%% Software distributed under the License is distributed on an "AS IS"
13
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
14
%% the License for the specific language governing rights and limitations
20
-module(httpd_test_lib).
22
-include("inets_test_lib.hrl").
25
-export([verify_request/6, verify_request/7, is_expect/1]).
27
-record(state, {request, % string()
29
status_line, % {Version, StatusCode, ReasonPharse}
30
headers, % #http_response_h{}
32
mfa = {httpc_response, parse, [nolimit, false]},
33
canceled = [], % [RequestId]
34
max_header_size = nolimit, % nolimit | integer()
35
max_body_size = nolimit, % nolimit | integer()
39
%%% Part of http.hrl - Temporary solution %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
41
-record(http_response_h,{
42
%%% --- Standard "General" headers
52
%%% --- Standard "Response" headers
62
%%% --- Standard "Entity" headers
66
'content-length' = "0",
73
other=[] % list() - Key/Value list with other headers
77
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
79
%%--------------------------------------------------------------------
81
%%------------------------------------------------------------------
82
verify_request(SocketType, Host, Port, Node, RequestStr, Options) ->
83
verify_request(SocketType, Host, Port, Node, RequestStr, Options, 30000).
84
verify_request(SocketType, Host, Port, Node, RequestStr, Options, TimeOut) ->
85
{ok, Socket} = inets_test_lib:connect_bin(SocketType, Host, Port),
87
_SendRes = inets_test_lib:send(SocketType, Socket, RequestStr),
89
State = case inets_regexp:match(RequestStr, "printenv") of
96
case request(State#state{request = RequestStr,
97
socket = Socket}, TimeOut) of
99
tsp("request failed: "
100
"~n Reason: ~p", [Reason]),
103
tsp("validate reply: "
104
"~n NewState: ~p", [NewState]),
105
ValidateResult = validate(RequestStr, NewState, Options,
107
tsp("validation result: "
108
"~n ~p", [ValidateResult]),
109
inets_test_lib:close(SocketType, Socket),
113
request(#state{mfa = {Module, Function, Args},
114
request = RequestStr, socket = Socket} = State, TimeOut) ->
116
HeadRequest = lists:sublist(RequestStr, 1, 4),
118
{tcp, Socket, Data} ->
119
print(tcp, Data, State),
120
case Module:Function([Data | Args]) of
122
handle_http_msg(Parsed, State);
123
{_, whole_body, _} when HeadRequest =:= "HEAD" ->
124
State#state{body = <<>>};
126
request(State#state{mfa = NewMFA}, TimeOut)
128
{tcp_closed, Socket} when Function =:= whole_body ->
129
print(tcp, "closed", State),
130
State#state{body = hd(Args)};
131
{tcp_closed, Socket} ->
132
test_server:fail(connection_closed);
133
{tcp_error, Socket, Reason} ->
134
test_server:fail({tcp_error, Reason});
135
{ssl, Socket, Data} ->
136
print(ssl, Data, State),
137
case Module:Function([Data | Args]) of
139
handle_http_msg(Parsed, State);
140
{_, whole_body, _} when HeadRequest =:= "HEAD" ->
141
State#state{body = <<>>};
143
request(State#state{mfa = NewMFA}, TimeOut)
145
{ssl_closed, Socket} when Function =:= whole_body ->
146
print(ssl, "closed", State),
147
State#state{body = hd(Args)};
148
{ssl_closed, Socket} ->
149
test_server:fail(connection_closed);
150
{ssl_error, Socket, Reason} ->
151
test_server:fail({ssl_error, Reason})
153
test_server:fail(connection_timed_out)
156
handle_http_msg({Version, StatusCode, ReasonPharse, Headers, Body},
157
State = #state{request = RequestStr}) ->
158
case is_expect(RequestStr) of
160
State#state{status_line = {Version,
165
handle_http_body(Body,
166
State#state{status_line = {Version,
172
handle_http_msg({ChunkedHeaders, Body},
173
State = #state{headers = Headers}) ->
174
NewHeaders = http_chunk:handle_headers(Headers, ChunkedHeaders),
175
State#state{headers = NewHeaders, body = Body};
177
handle_http_msg(Body, State) ->
178
State#state{body = Body}.
180
handle_http_body(<<>>, State = #state{request = "HEAD" ++ _}) ->
181
State#state{body = <<>>};
183
handle_http_body(Body, State = #state{headers = Headers,
184
max_body_size = MaxBodySize}) ->
185
case Headers#http_response_h.'transfer-encoding' of
187
case http_chunk:decode(Body, State#state.max_body_size,
188
State#state.max_header_size) of
189
{Module, Function, Args} ->
190
request(State#state{mfa = {Module, Function, Args}},
192
{ok, {ChunkedHeaders, NewBody}} ->
193
NewHeaders = http_chunk:handle_headers(Headers,
195
State#state{headers = NewHeaders, body = NewBody}
199
list_to_integer(Headers#http_response_h.'content-length'),
200
case ((Length =< MaxBodySize) or (MaxBodySize == nolimit)) of
202
case httpc_response:whole_body(Body, Length) of
204
State#state{body = NewBody};
206
request(State#state{mfa = MFA}, 5000)
209
test_server:fail(body_too_big)
213
validate(RequestStr, #state{status_line = {Version, StatusCode, _},
215
body = Body}, Options, N, P) ->
217
%io:format("Status~p: H:~p B:~p~n", [StatusCode, Headers, Body]),
218
check_version(Version, Options),
219
case lists:keysearch(statuscode, 1, Options) of
221
check_status_code(StatusCode, Options, Options);
225
do_validate(http_response:header_list(Headers), Options, N, P),
226
check_body(RequestStr, StatusCode,
227
Headers#http_response_h.'content-type',
228
list_to_integer(Headers#http_response_h.'content-length'),
231
%%--------------------------------------------------------------------
232
%% Internal functions
233
%%------------------------------------------------------------------
234
check_version(Version, Options) ->
235
case lists:keysearch(version, 1, Options) of
236
{value, {version, Version}} ->
238
{value, {version, Ver}} ->
239
test_server:fail({wrong_version, [{got, Version},
246
test_server:fail({wrong_version, [{got, Version},
247
{expected, "HTTP/1.1"}]})
251
check_status_code(StatusCode, [], Options) ->
252
test_server:fail({wrong_status_code, [{got, StatusCode},
253
{expected, Options}]});
254
check_status_code(StatusCode, Current = [_ | Rest], Options) ->
255
case lists:keysearch(statuscode, 1, Current) of
256
{value, {statuscode, StatusCode}} ->
258
{value, {statuscode, _OtherStatus}} ->
259
check_status_code(StatusCode, Rest, Options);
261
test_server:fail({wrong_status_code, [{got, StatusCode},
262
{expected, Options}]})
265
do_validate(_, [], _, _) ->
267
do_validate(Header, [{statuscode, _Code} | Rest], N, P) ->
268
do_validate(Header, Rest, N, P);
269
do_validate(Header, [{header, HeaderField}|Rest], N, P) ->
270
LowerHeaderField = http_util:to_lower(HeaderField),
271
case lists:keysearch(LowerHeaderField, 1, Header) of
272
{value, {LowerHeaderField, _Value}} ->
275
test_server:fail({missing_header_field, LowerHeaderField, Header});
277
test_server:fail({missing_header_field, LowerHeaderField, Header})
279
do_validate(Header, Rest, N, P);
280
do_validate(Header, [{header, HeaderField, Value}|Rest],N,P) ->
281
LowerHeaderField = http_util:to_lower(HeaderField),
282
case lists:keysearch(LowerHeaderField, 1, Header) of
283
{value, {LowerHeaderField, Value}} ->
286
test_server:fail({wrong_header_field_value, LowerHeaderField,
289
test_server:fail({wrong_header_field_value, LowerHeaderField,
292
do_validate(Header, Rest, N, P);
293
do_validate(Header,[{no_last_modified,HeaderField}|Rest],N,P) ->
294
% io:format("Header: ~p~nHeaderField: ~p~n",[Header,HeaderField]),
295
case lists:keysearch(HeaderField,1,Header) of
297
test_server:fail({wrong_header_field_value, HeaderField,
302
do_validate(Header, Rest, N, P);
303
do_validate(Header, [_Unknown | Rest], N, P) ->
304
do_validate(Header, Rest, N, P).
306
is_expect(RequestStr) ->
308
case inets_regexp:match(RequestStr, "xpect:100-continue") of
315
%% OTP-5775, content-length
316
check_body("GET /cgi-bin/erl/httpd_example:get_bin HTTP/1.0\r\n\r\n", 200, "text/html", Length, _Body) when Length /= 274->
317
test_server:fail(content_length_error);
318
check_body("GET /cgi-bin/cgi_echo HTTP/1.0\r\n\r\n", 200, "text/plain",
324
test_server:fail(content_length_error)
327
check_body(RequestStr, 200, "text/html", _, Body) ->
328
HeadRequest = lists:sublist(RequestStr, 1, 3),
331
inets_test_lib:check_body(binary_to_list(Body));
336
check_body(_, _, _, _,_) ->
339
print(Proto, Data, #state{print = true}) ->
340
test_server:format("Received ~p: ~p~n", [Proto, Data]);
341
print(_, _, #state{print = false}) ->
348
test_server:format("~p ~p:" ++ F ++ "~n", [self(), ?MODULE | A]).