1
%% ``The contents of this file are subject to the Erlang Public License,
2
%% Version 1.1, (the "License"); you may not use this file except in
3
%% compliance with the License. You should have received a copy of the
4
%% Erlang Public License along with this software. If not, it can be
5
%% retrieved via the world wide web at http://www.erlang.org/.
7
%% Software distributed under the License is distributed on an "AS IS"
8
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
9
%% the License for the specific language governing rights and limitations
12
%% The Initial Developer of the Original Code is Ericsson Utvecklings AB.
13
%% Portions created by Ericsson are Copyright 1999, Ericsson Utvecklings
14
%% AB. All Rights Reserved.''
18
%% Description: Implements a request handler process for the HTTP server.
21
-module(httpd_request_handler).
23
-behaviour(gen_server).
25
%% Application internal API
26
-export([start/2, start/3, socket_ownership_transfered/3]).
28
%% gen_server callbacks
29
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
30
terminate/2, code_change/3]).
32
-include("httpd.hrl").
33
-include("http_internal.hrl").
35
-record(state, {mod, %% #mod{}
37
status, %% accept | busy | blocked
38
mfa, %% {Module, Function, Args}
39
max_keep_alive_request = infinity, %% integer() | infinity
40
response_sent = false, %% true | false
41
timeout, %% infinity | integer() > 0
42
timer, %% ref() - Request timer
43
headers, %% #http_request_h{}
47
%%====================================================================
48
%% Application internal API
49
%%====================================================================
50
%%--------------------------------------------------------------------
51
%% Function: start() -> {ok, Pid} | ignore | {error,Error}
52
%% Description: Starts a httpd-request handler process. Intended to be
53
%% called by the httpd acceptor process.
54
%%--------------------------------------------------------------------
55
start(Manager, ConfigDB) ->
56
start(Manager, ConfigDB, 15000).
57
start(Manager, ConfigDB, AcceptTimeout) ->
58
proc_lib:start(?MODULE, init, [[Manager, ConfigDB,AcceptTimeout]]).
60
%%--------------------------------------------------------------------
61
%% socket_ownership_transfered(Pid, SocketType, Socket) -> void()
64
%% SocketType = ip_comm | ssl
67
%% Description: Send a message to the request handler process
68
%% confirming that the socket ownership has now sucssesfully been
69
%% transfered to it. Intended to be called by the httpd acceptor
71
%%--------------------------------------------------------------------
72
socket_ownership_transfered(Pid, SocketType, Socket) ->
73
Pid ! {socket_ownership_transfered, SocketType, Socket}.
75
%%--------------------------------------------------------------------
76
%% Function: init(Args) -> _
78
%% Description: Initiates the server. Obs special init that uses
79
%% gen_server:enter_loop/3. This is used instead of the normal
80
%% gen_server callback init, as a more complex init than the
81
%% gen_server provides is needed.
82
%%--------------------------------------------------------------------
83
init([Manager, ConfigDB,AcceptTimeout]) ->
84
%% Make sure this process terminates if the httpd manager process
87
%% At this point the function httpd_request_handler:start/2 will return.
88
proc_lib:init_ack({ok, self()}),
90
{SocketType, Socket} = await_socket_ownership_transfer(AcceptTimeout),
92
Resolve = http_transport:resolve(),
93
Peername = httpd_socket:peername(SocketType, Socket),
94
InitData = #init_data{peername = Peername, resolve = Resolve},
95
Mod = #mod{config_db = ConfigDB,
96
socket_type = SocketType,
98
init_data = InitData},
100
MaxHeaderSize = httpd_util:lookup(ConfigDB, max_header_size,
101
?HTTP_MAX_HEADER_SIZE),
102
TimeOut = httpd_util:lookup(ConfigDB, keep_alive_timeout, 150000),
103
NrOfRequest = httpd_util:lookup(ConfigDB,
104
max_keep_alive_request, infinity),
106
{_, Status} = httpd_manager:new_connection(Manager),
109
State = #state{mod = Mod, manager = Manager, status = Status,
110
timeout = TimeOut, max_keep_alive_request = NrOfRequest,
111
mfa = {httpd_request, parse, [MaxHeaderSize]}},
113
NewState = activate_request_timeout(State),
115
http_transport:setopts(SocketType, Socket, [binary,{packet, 0},
117
gen_server:enter_loop(?MODULE, [], NewState).
119
%%====================================================================
120
%% gen_server callbacks
121
%%====================================================================
123
%%--------------------------------------------------------------------
124
%% handle_call(Request, From, State) -> {reply, Reply, State} |
125
%% {reply, Reply, State, Timeout} |
126
%% {noreply, State} |
127
%% {noreply, State, Timeout} |
128
%% {stop, Reason, Reply, State} |
129
%% {stop, Reason, State}
130
%% Description: Handling call messages
131
%%--------------------------------------------------------------------
132
handle_call(Request, From, State) ->
133
{stop, {call_api_violation, Request, From}, State}.
135
%%--------------------------------------------------------------------
136
%% handle_cast(Msg, State) -> {noreply, State} |
137
%% {noreply, State, Timeout} |
138
%% {stop, Reason, State}
139
%% Description: Handling cast messages
140
%%--------------------------------------------------------------------
141
handle_cast(Msg, State) ->
142
{reply, {cast_api_violation, Msg}, State}.
144
%%--------------------------------------------------------------------
145
%% handle_info(Info, State) -> {noreply, State} |
146
%% {noreply, State, Timeout} |
147
%% {stop, Reason, State}
148
%% Description: Handling all non call/cast messages
149
%%--------------------------------------------------------------------
150
handle_info({Proto, Socket, Data}, State =
151
#state{mfa = {Module, Function, Args},
152
mod = #mod{socket_type = SockType,
153
socket = Socket} = ModData}
154
= State) when Proto == tcp; Proto == ssl; Proto == dummy ->
156
case Module:Function([Data | Args]) of
158
NewState = cancel_request_timeout(State),
159
handle_http_msg(Result, NewState);
160
{error, {header_too_long, MaxHeaderSize}, Version} ->
161
NewModData = ModData#mod{http_version = Version},
162
httpd_response:send_status(NewModData, 413, "Header too big"),
163
Reason = io_lib:format("Header too big, max size is ~p~n",
165
error_log(Reason, NewModData),
166
{stop, normal, State#state{response_sent = true,
169
http_transport:setopts(SockType, Socket, [{active, once}]),
170
{noreply, State#state{mfa = NewMFA}}
174
handle_info({tcp_closed, _}, State) ->
175
{stop, normal, State};
176
handle_info({ssl_closed, _}, State) ->
177
{stop, normal, State};
178
handle_info({tcp_error, _, _} = Reason, State) ->
179
{stop, Reason, State};
180
handle_info({ssl_error, _, _} = Reason, State) ->
181
{stop, Reason, State};
184
handle_info(timeout, #state{mod = ModData, mfa = {_, parse, _}} = State) ->
185
error_log("No request received on keep-alive connection"
186
"before server side timeout", ModData),
187
%% No response should be sent!
188
{stop, normal, State#state{response_sent = true}};
189
handle_info(timeout, #state{mod = ModData} = State) ->
190
httpd_response:send_status(ModData, 408, "Request timeout"),
191
error_log("The client did not send the whole request before the"
192
"server side timeout", ModData),
193
{stop, normal, State#state{response_sent = true}};
196
handle_info(Info, #state{mod = ModData} = State) ->
197
Error = lists:flatten(
198
io_lib:format("Unexpected message received: ~n~p~n", [Info])),
199
error_log(Error, ModData),
202
%%--------------------------------------------------------------------
203
%% terminate(Reason, State) -> void()
205
%% Description: This function is called by a gen_server when it is about to
206
%% terminate. It should be the opposite of Module:init/1 and do any necessary
207
%% cleaning up. When it returns, the gen_server terminates with Reason.
208
%% The return value is ignored.
209
%%--------------------------------------------------------------------
210
terminate(normal, State) ->
212
terminate(Reason, #state{response_sent = false, mod = ModData} = State) ->
213
httpd_response:send_status(ModData, 500, none),
214
error_log(httpd_util:reason_phrase(500), ModData),
215
terminate(Reason, State#state{response_sent = true, mod = ModData});
216
terminate(_, State) ->
219
do_terminate(#state{mod = ModData, manager = Manager} = State) ->
220
catch httpd_manager:done_connection(Manager),
221
cancel_request_timeout(State),
222
httpd_socket:close(ModData#mod.socket_type, ModData#mod.socket).
224
%%--------------------------------------------------------------------
225
%% code_change(OldVsn, State, Extra) -> {ok, NewState}
227
%% Description: Convert process state when code is changed
228
%%--------------------------------------------------------------------
229
code_change(_OldVsn, State, _Extra) ->
232
%%--------------------------------------------------------------------
233
%%% Internal functions
234
%%--------------------------------------------------------------------
235
await_socket_ownership_transfer(AcceptTimeout) ->
237
{socket_ownership_transfered, SocketType, Socket} ->
239
after AcceptTimeout ->
240
exit(accept_socket_timeout)
243
handle_http_msg({_, _, Version, {_, _}, _}, #state{status = busy,
244
mod = ModData} = State) ->
245
handle_manager_busy(State#state{mod =
246
ModData#mod{http_version = Version}}),
247
{stop, normal, State};
249
handle_http_msg({_, _, Version, {_, _}, _},
250
#state{status = blocked, mod = ModData} = State) ->
251
handle_manager_blocked(State#state{mod =
252
ModData#mod{http_version = Version}}),
253
{stop, normal, State};
255
handle_http_msg({Method, Uri, Version, {RecordHeaders, Headers}, Body},
256
#state{status = accept, mod = ModData} = State) ->
257
case httpd_request:validate(Method, Uri, Version) of
260
httpd_request:update_mod_data(ModData, Method, Uri,
263
case is_host_specified_if_required(NewModData#mod.absolute_uri,
264
RecordHeaders, Version) of
266
handle_body(State#state{headers = RecordHeaders,
270
httpd_response:send_status(ModData#mod{http_version =
273
{stop, normal, State#state{response_sent = true}}
275
{error, {not_supported, What}} ->
276
httpd_response:send_status(ModData#mod{http_version = Version},
277
501, {Method, Uri, Version}),
278
Reason = io_lib:format("Not supported: ~p~n", [What]),
279
error_log(Reason, ModData),
280
{stop, normal, State#state{response_sent = true}};
281
{error, {bad_request, {forbidden, URI}}} ->
282
httpd_response:send_status(ModData#mod{http_version = Version},
284
Reason = io_lib:format("Forbidden URI: ~p~n", [URI]),
285
error_log(Reason, ModData),
286
{stop, normal, State#state{response_sent = true}};
287
{error,{bad_request, {malformed_syntax, URI}}} ->
288
httpd_response:send_status(ModData#mod{http_version = Version},
290
Reason = io_lib:format("Malformed syntax in URI: ~p~n", [URI]),
291
error_log(Reason, ModData),
292
{stop, normal, State#state{response_sent = true}}
294
handle_http_msg({ChunkedHeaders, Body},
295
State = #state{headers = Headers}) ->
296
NewHeaders = http_chunk:handle_headers(Headers, ChunkedHeaders),
297
handle_response(State#state{headers = NewHeaders, body = Body});
298
handle_http_msg(Body, State) ->
299
handle_response(State#state{body = Body}).
301
handle_manager_busy(#state{mod = #mod{config_db = ConfigDB}} = State) ->
302
MaxClients = httpd_util:lookup(ConfigDB, max_clients, 150),
303
Reason = io_lib:format("heavy load (>~w processes)", [MaxClients]),
304
reject_connection(State, lists:flatten(Reason)).
306
handle_manager_blocked(State) ->
307
Reason = "Server maintenance performed, try again later",
308
reject_connection(State, Reason).
310
reject_connection(#state{mod = ModData} = State, Reason) ->
311
httpd_response:send_status(ModData, 503, Reason),
312
{stop, normal, State#state{response_sent = true}}.
314
is_host_specified_if_required(nohost, #http_request_h{host = undefined},
317
is_host_specified_if_required(_, _, _) ->
320
handle_body(#state{mod = #mod{config_db = ConfigDB}} = State) ->
323
httpd_util:lookup(ConfigDB, max_header_size, ?HTTP_MAX_HEADER_SIZE),
324
MaxBodySize = httpd_util:lookup(ConfigDB, max_body_size, nolimit),
326
case handle_expect(State, MaxBodySize) of
328
handle_body(State, MaxHeaderSize, MaxBodySize);
334
handle_body(#state{headers = Headers, body = Body, mod = ModData} = State,
335
MaxHeaderSize, MaxBodySize) ->
336
case Headers#http_request_h.'transfer-encoding' of
338
case http_chunk:decode(Body, MaxBodySize, MaxHeaderSize) of
339
{Module, Function, Args} ->
340
http_transport:setopts(ModData#mod.socket_type,
343
{noreply, State#state{mfa =
344
{Module, Function, Args}}};
345
{ok, {ChunkedHeaders, NewBody}} ->
347
http_chunk:handle_headers(Headers, ChunkedHeaders),
348
handle_response(State#state{headers = NewHeaders,
351
Encoding when list(Encoding) ->
352
httpd_response:send_status(ModData, 501,
353
"Unknown Transfer-Encoding"),
354
Reason = io_lib:format("Unknown Transfer-Encoding: ~p~n",
356
error_log(Reason, ModData),
357
{stop, normal, State#state{response_sent = true}};
360
list_to_integer(Headers#http_request_h.'content-length'),
361
case ((Length =< MaxBodySize) or (MaxBodySize == nolimit)) of
363
case httpd_request:whole_body(Body, Length) of
364
{Module, Function, Args} ->
365
http_transport:setopts(ModData#mod.socket_type,
368
{noreply, State#state{mfa =
369
{Module, Function, Args}}};
373
State#state{headers = Headers,
377
httpd_response:send_status(ModData, 413, "Body too big"),
378
error_log("Body too big", ModData),
379
{stop, normal, State#state{response_sent = true}}
383
handle_expect(#state{headers = Headers, mod =
384
#mod{config_db = ConfigDB} = ModData} = State,
386
Length = Headers#http_request_h.'content-length',
387
case expect(Headers, ModData#mod.http_version, ConfigDB) of
388
continue when MaxBodySize > Length; MaxBodySize == nolimit ->
389
httpd_response:send_status(ModData, 100, ""),
391
continue when MaxBodySize < Length ->
392
httpd_response:send_status(ModData, 413, "Body too big"),
393
error_log("Body too big", ModData),
394
{stop, normal, State#state{response_sent = true}};
396
httpd_response:send_status(ModData, 417,
397
"Unexpected expect value"),
398
Reason = io_lib:format("Unexpected expect value: ~p~n", [Value]),
399
error_log(Reason, ModData),
400
{stop, normal, State#state{response_sent = true}};
403
http_1_0_expect_header ->
404
httpd_response:send_status(ModData, 400,
405
"Only HTTP/1.1 Clients "
406
"may use the Expect Header"),
407
error_log("Client with lower version than 1.1 tried to send"
408
"an expect header", ModData),
409
{stop, normal, State#state{response_sent = true}}
412
expect(Headers, "HTTP/1.1", _) ->
413
case Headers#http_request_h.expect of
421
expect(Headers, _, ConfigDB) ->
422
case Headers#http_request_h.expect of
426
case httpd_util:lookup(ConfigDB, expect, continue) of
430
http_1_0_expect_header
434
handle_response(#state{body = Body, mod = ModData, headers = Headers,
435
max_keep_alive_request = Max} = State) when Max > 0 ->
436
{NewBody, Data} = httpd_request:body_data(Headers, Body),
437
ok = httpd_response:generate_and_send_response(
438
ModData#mod{entity_body = NewBody}),
439
handle_next_request(State#state{response_sent = true}, Data);
441
handle_response(#state{body = Body, headers = Headers,
442
mod = ModData} = State) ->
443
{NewBody, _} = httpd_request:body_data(Headers, Body),
444
ok = httpd_response:generate_and_send_response(
445
ModData#mod{entity_body = NewBody}),
446
{stop, normal, State#state{response_sent = true}}.
448
handle_next_request(#state{mod = #mod{connection = true} = ModData,
449
max_keep_alive_request = Max} = State, Data) ->
450
NewModData = #mod{socket_type = ModData#mod.socket_type,
451
socket = ModData#mod.socket,
452
config_db = ModData#mod.config_db,
453
init_data = ModData#mod.init_data},
455
httpd_util:lookup(ModData#mod.config_db,
456
max_header_size, ?HTTP_MAX_HEADER_SIZE),
458
TmpState = State#state{mod = NewModData,
459
mfa = {httpd_request, parse, [MaxHeaderSize]},
460
max_keep_alive_request = decrease(Max),
461
headers = undefined, body = undefined,
462
response_sent = false},
464
NewState = activate_request_timeout(TmpState),
468
http_transport:setopts(ModData#mod.socket_type,
469
ModData#mod.socket, [{active, once}]),
472
handle_info({dummy, ModData#mod.socket, Data}, NewState)
475
handle_next_request(State, _) ->
476
{stop, normal, State}.
478
activate_request_timeout(#state{timeout = Time} = State) ->
479
Ref = erlang:send_after(Time, self(), timeout),
480
State#state{timer = Ref}.
482
cancel_request_timeout(#state{timer = undefined} = State) ->
484
cancel_request_timeout(#state{timer = Timer} = State) ->
485
erlang:cancel_timer(Timer),
492
State#state{timer = undefined}.
494
decrease(N) when integer(N)->
499
error_log(ReasonString, #mod{socket = Socket, socket_type = SocketType,
500
config_db = ConfigDB,
501
init_data = #init_data{peername = Peername}}) ->
502
Error = lists:flatten(
503
io_lib:format("Error reading request: ~s",[ReasonString])),
504
error_log(mod_log, SocketType, Socket, ConfigDB, Peername, Error),
505
error_log(mod_disk_log, SocketType, Socket, ConfigDB, Peername, Error).
507
error_log(Mod, SocketType, Socket, ConfigDB, Peername, String) ->
508
Modules = httpd_util:lookup(ConfigDB, modules,
509
[mod_get, mod_head, mod_log]),
510
case lists:member(Mod, Modules) of
512
Mod:error_log(SocketType, Socket, ConfigDB, Peername, String);