1%% 2%% %CopyrightBegin% 3%% 4%% Copyright Ericsson AB 1997-2017. All Rights Reserved. 5%% 6%% Licensed under the Apache License, Version 2.0 (the "License"); 7%% you may not use this file except in compliance with the License. 8%% You may obtain a copy of the License at 9%% 10%% http://www.apache.org/licenses/LICENSE-2.0 11%% 12%% Unless required by applicable law or agreed to in writing, software 13%% distributed under the License is distributed on an "AS IS" BASIS, 14%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15%% See the License for the specific language governing permissions and 16%% limitations under the License. 17%% 18%% %CopyrightEnd% 19%% 20%% 21%% Description: Implements a request handler process for the HTTP server. 22%% 23 24-module(httpd_request_handler). 25 26-behaviour(gen_server). 27 28%% Application internal API 29-export([start_link/2, start_link/3, socket_ownership_transfered/3]). 30 31%% gen_server callbacks 32-export([init/1, handle_call/3, handle_cast/2, handle_info/2, 33 terminate/2, code_change/3, format_status/2]). 34 35-include("httpd.hrl"). 36-include("http_internal.hrl"). 37-include_lib("kernel/include/logger.hrl"). 38 39-define(HANDSHAKE_TIMEOUT, 5000). 40 41-record(state, {mod, %% #mod{} 42 manager, %% pid() 43 status, %% accept | busy | blocked 44 mfa, %% {Module, Function, Args} 45 max_keep_alive_request = infinity, %% integer() | infinity 46 response_sent = false :: boolean(), 47 timeout, %% infinity | integer() > 0 48 timer :: 'undefined' | reference(), % Request timer 49 headers, %% #http_request_h{} 50 body, %% binary() 51 data, %% The total data received in bits, checked after 10s 52 byte_limit, %% Bit limit per second before kick out 53 chunk 54 }). 55 56%%==================================================================== 57%% Application internal API 58%%==================================================================== 59%%-------------------------------------------------------------------- 60%% Function: start() -> {ok, Pid} | ignore | {error,Error} 61%% Description: Starts a httpd-request handler process. Intended to be 62%% called by the httpd acceptor process. 63%%-------------------------------------------------------------------- 64start_link(Manager, ConfigDB) -> 65 start_link(Manager, ConfigDB, 15000). 66start_link(Manager, ConfigDB, AcceptTimeout) -> 67 proc_lib:start_link(?MODULE, init, [[Manager, ConfigDB,AcceptTimeout]]). 68 69 70%%-------------------------------------------------------------------- 71%% socket_ownership_transfered(Pid, SocketType, Socket) -> void() 72%% 73%% Pid = pid() 74%% SocketType = ip_comm | ssl 75%% Socket = socket() 76%% 77%% Description: Send a message to the request handler process 78%% confirming that the socket ownership has now sucssesfully been 79%% transfered to it. Intended to be called by the httpd acceptor 80%% process. 81%%-------------------------------------------------------------------- 82socket_ownership_transfered(Pid, SocketType, Socket) -> 83 Pid ! {socket_ownership_transfered, SocketType, Socket}. 84 85%%-------------------------------------------------------------------- 86%% Function: init(Args) -> _ 87%% 88%% Description: Initiates the server. Obs special init that uses 89%% gen_server:enter_loop/3. This is used instead of the normal 90%% gen_server callback init, as a more complex init than the 91%% gen_server provides is needed. 92%%-------------------------------------------------------------------- 93init([Manager, ConfigDB, AcceptTimeout]) -> 94 process_flag(trap_exit, true), 95 %% Make sure this process terminates if the httpd manager process 96 %% should die! 97 %%link(Manager), 98 %% At this point the function httpd_request_handler:start/2 will return. 99 proc_lib:init_ack({ok, self()}), 100 101 {SocketType, Socket} = await_socket_ownership_transfer(AcceptTimeout), 102 103 Peername = http_transport:peername(SocketType, Socket), 104 Sockname = http_transport:sockname(SocketType, Socket), 105 106 %%Timeout value is in seconds we want it in milliseconds 107 KeepAliveTimeOut = 1000 * httpd_util:lookup(ConfigDB, keep_alive_timeout, 150), 108 109 case http_transport:negotiate(SocketType, Socket, ?HANDSHAKE_TIMEOUT) of 110 {error, {tls_alert, {_, AlertDesc}} = Error} -> 111 ModData = #mod{config_db = ConfigDB, init_data = #init_data{peername = Peername, 112 sockname = Sockname}}, 113 httpd_util:error_log(ConfigDB, httpd_logger:error_report('TLS', AlertDesc, 114 ModData, ?LOCATION)), 115 exit({shutdown, Error}); 116 {error, _Reason} = Error -> 117 %% This happens if the peer closes the connection 118 %% or the handshake is timed out. This is not 119 %% an error condition of the server and client will 120 %% retry in the timeout situation. 121 exit({shutdown, Error}); 122 {ok, TLSSocket} -> 123 continue_init(Manager, ConfigDB, SocketType, TLSSocket, 124 Peername, Sockname, KeepAliveTimeOut); 125 ok -> 126 continue_init(Manager, ConfigDB, SocketType, Socket, 127 Peername, Sockname, KeepAliveTimeOut) 128 end. 129 130continue_init(Manager, ConfigDB, SocketType, Socket, Peername, Sockname, 131 TimeOut) -> 132 Resolve = http_transport:resolve(), 133 InitData = #init_data{peername = Peername, 134 sockname = Sockname, 135 resolve = Resolve}, 136 Mod = #mod{config_db = ConfigDB, 137 socket_type = SocketType, 138 socket = Socket, 139 init_data = InitData}, 140 141 MaxHeaderSize = max_header_size(ConfigDB), 142 MaxURISize = max_uri_size(ConfigDB), 143 NrOfRequest = max_keep_alive_request(ConfigDB), 144 MaxContentLen = max_content_length(ConfigDB), 145 Customize = customize(ConfigDB), 146 MaxChunk = max_client_body_chunk(ConfigDB), 147 148 {_, Status} = httpd_manager:new_connection(Manager), 149 150 MFA = {httpd_request, parse, [[{max_uri, MaxURISize}, {max_header, MaxHeaderSize}, 151 {max_version, ?HTTP_MAX_VERSION_STRING}, 152 {max_method, ?HTTP_MAX_METHOD_STRING}, 153 {max_content_length, MaxContentLen}, 154 {customize, Customize} 155 ]]}, 156 157 State = #state{mod = Mod, 158 manager = Manager, 159 status = Status, 160 timeout = TimeOut, 161 max_keep_alive_request = NrOfRequest, 162 mfa = MFA, 163 chunk = chunk_start(MaxChunk)}, 164 165 http_transport:setopts(SocketType, Socket, 166 [binary, {packet, 0}, {active, once}]), 167 NewState = data_receive_counter(activate_request_timeout(State), httpd_util:lookup(ConfigDB, minimum_bytes_per_second, false)), 168 gen_server:enter_loop(?MODULE, [], NewState). 169 170 171%%==================================================================== 172%% gen_server callbacks 173%%==================================================================== 174 175%%-------------------------------------------------------------------- 176%% handle_call(Request, From, State) -> {reply, Reply, State} | 177%% {reply, Reply, State, Timeout} | 178%% {noreply, State} | 179%% {noreply, State, Timeout} | 180%% {stop, Reason, Reply, State} | 181%% {stop, Reason, State} 182%% Description: Handling call messages 183%%-------------------------------------------------------------------- 184handle_call(Request, From, #state{mod = #mod{config_db = Db} = ModData} = State) -> 185 httpd_util:error_log(Db, 186 httpd_logger:error_report(internal, 187 [{unexpected_call, Request}, {to, self()}, {from, From}], ModData, 188 ?LOCATION)), 189 {stop, {call_api_violation, Request, From}, State}. 190 191%%-------------------------------------------------------------------- 192%% handle_cast(Msg, State) -> {noreply, State} | 193%% {noreply, State, Timeout} | 194%% {stop, Reason, State} 195%% Description: Handling cast messages 196%%-------------------------------------------------------------------- 197handle_cast(Msg, #state{mod = #mod{config_db = Db} = ModData} = State) -> 198 httpd_util:error_log(Db, 199 httpd_logger:error_report(internal, [{unexpected_cast, Msg}, {to, self()}], ModData, 200 ?LOCATION)), 201 {noreply, State}. 202 203%%-------------------------------------------------------------------- 204%% handle_info(Info, State) -> {noreply, State} | 205%% {noreply, State, Timeout} | 206%% {stop, Reason, State} 207%% Description: Handling all non call/cast messages 208%%-------------------------------------------------------------------- 209handle_info({Proto, Socket, Data}, 210 #state{mfa = {Module, Function, Args}, 211 chunk = {ChunkState, _}, 212 mod = #mod{socket_type = SockType, 213 socket = Socket} = ModData} = State) 214 when (((Proto =:= tcp) orelse 215 (Proto =:= ssl) orelse 216 (Proto =:= dummy)) andalso is_binary(Data)) -> 217 218 PROCESSED = (catch Module:Function([Data | Args])), 219 NewDataSize = case State#state.byte_limit of 220 undefined -> 221 undefined; 222 _ -> 223 State#state.data + byte_size(Data) 224 end, 225 226 case PROCESSED of 227 {ok, Result} -> 228 NewState = case NewDataSize of 229 undefined -> 230 cancel_request_timeout(State); 231 _ -> 232 set_new_data_size(cancel_request_timeout(State), NewDataSize) 233 end, 234 handle_msg(Result, NewState); 235 {error, {size_error, MaxSize, ErrCode, ErrStr}, Version} -> 236 NewModData = ModData#mod{http_version = Version}, 237 httpd_response:send_status(NewModData, ErrCode, ErrStr, {max_size, MaxSize}), 238 {stop, normal, State#state{response_sent = true, 239 mod = NewModData}}; 240 241 {http_chunk = Module, Function, Args} when ChunkState =/= undefined -> 242 NewState = handle_chunk(Module, Function, Args, State), 243 {noreply, NewState}; 244 NewMFA -> 245 http_transport:setopts(SockType, Socket, [{active, once}]), 246 case NewDataSize of 247 undefined -> 248 {noreply, State#state{mfa = NewMFA}}; 249 _ -> 250 {noreply, State#state{mfa = NewMFA, data = NewDataSize}} 251 end 252 end; 253 254%% Error cases 255handle_info({tcp_closed, _}, State) -> 256 {stop, normal, State}; 257handle_info({ssl_closed, _}, State) -> 258 {stop, normal, State}; 259handle_info({tcp_error, _, _} = Reason, State) -> 260 {stop, {shutdown, Reason}, State}; 261handle_info({ssl_error, _, _} = Reason, State) -> 262 {stop, {shutdown, Reason}, State}; 263 264%% Timeouts 265handle_info(timeout, #state{mfa = {_, parse, _}} = State) -> 266 %% No request received on keep-alive connection 267 %% before server side timeout. No response should be sent! 268 {stop, normal, State#state{response_sent = true}}; 269handle_info(timeout, #state{mod = ModData} = State) -> 270 httpd_response:send_status(ModData, 408, "Request timeout", "The client did not send the whole request before the " 271 "server side timeout"), 272 {stop, normal, State#state{response_sent = true}}; 273handle_info(check_data_first, #state{data = Data, byte_limit = Byte_Limit} = State) -> 274 case Data >= (Byte_Limit*3) of 275 true -> 276 erlang:send_after(1000, self(), check_data), 277 {noreply, State#state{data = 0}}; 278 _ -> 279 {stop, normal, State#state{response_sent = true}} 280 end; 281handle_info(check_data, #state{data = Data, byte_limit = Byte_Limit} = State) -> 282 case Data >= Byte_Limit of 283 true -> 284 erlang:send_after(1000, self(), check_data), 285 {noreply, State#state{data = 0}}; 286 _ -> 287 {stop, normal, State#state{response_sent = true}} 288 end; 289 290handle_info({'EXIT', _, Reason}, State) -> 291 {stop, Reason, State}; 292 293%% Default case 294handle_info(Info, #state{mod = #mod{config_db = Db} =ModData} = State) -> 295 httpd_util:error_log(Db, 296 httpd_logger:error_report(internal, 297 [{unexpected_info, Info}, {to, self()}], ModData, 298 ?LOCATION)), 299 {noreply, State}. 300 301 302%%-------------------------------------------------------------------- 303%% terminate(Reason, State) -> void() 304%% 305%% Description: This function is called by a gen_server when it is about to 306%% terminate. It should be the opposite of Module:init/1 and do any necessary 307%% cleaning up. When it returns, the gen_server terminates with Reason. 308%% The return value is ignored. 309%%-------------------------------------------------------------------- 310terminate(Reason, State) when Reason == normal; 311 Reason == shutdown -> 312 do_terminate(State); 313terminate({shutdown,_}, State) -> 314 do_terminate(State); 315terminate(Reason, #state{response_sent = false, mod = ModData} = State) -> 316 httpd_response:send_status(ModData, 500, none), 317 terminate(Reason, State#state{response_sent = true, mod = ModData}); 318terminate(_Reason, State) -> 319 do_terminate(State). 320 321do_terminate(#state{mod = ModData} = State) -> 322 cancel_request_timeout(State), 323 httpd_socket:close(ModData#mod.socket_type, ModData#mod.socket). 324 325format_status(normal, [_, State]) -> 326 [{data, [{"StateData", State}]}]; 327format_status(terminate, [_, State]) -> 328 Mod = (State#state.mod), 329 case Mod#mod.socket_type of 330 ip_comm -> 331 [{data, [{"StateData", State}]}]; 332 {essl, _} -> 333 %% Do not print ssl options in superviosr reports 334 [{data, [{"StateData", 335 State#state{mod = Mod#mod{socket_type = 'TLS'}}}]}] 336 end. 337 338%%-------------------------------------------------------------------- 339%% code_change(OldVsn, State, Extra) -> {ok, NewState} 340%% 341%% Description: Convert process state when code is changed 342%%-------------------------------------------------------------------- 343code_change(_OldVsn, State, _Extra) -> 344 {ok, State}. 345 346 347%%-------------------------------------------------------------------- 348%%% Internal functions 349%%-------------------------------------------------------------------- 350set_new_data_size(State, NewData) -> 351 State#state{data = NewData}. 352await_socket_ownership_transfer(AcceptTimeout) -> 353 receive 354 {socket_ownership_transfered, SocketType, Socket} -> 355 {SocketType, Socket} 356 after AcceptTimeout -> 357 exit(accept_socket_timeout) 358 end. 359 360 361%%% Internal chunking of client body 362handle_msg({{continue, Chunk}, Module, Function, Args}, #state{chunk = {_, CbState}} = State) -> 363 handle_internal_chunk(State#state{chunk = {continue, CbState}, 364 body = Chunk}, Module, Function, Args); 365handle_msg({continue, Module, Function, Args}, #state{mod = ModData} = State) -> 366 http_transport:setopts(ModData#mod.socket_type, 367 ModData#mod.socket, 368 [{active, once}]), 369 {noreply, State#state{mfa = {Module, Function, Args}}}; 370handle_msg({last, Body}, #state{headers = Headers, chunk = {_, CbState}} = State) -> 371 NewHeaders = Headers#http_request_h{'content-length' = integer_to_list(size(Body))}, 372 handle_response(State#state{chunk = {last, CbState}, 373 headers = NewHeaders, 374 body = Body}); 375%%% Last data chunked by client 376handle_msg({ChunkedHeaders, Body}, #state{headers = Headers , chunk = {ChunkState, CbState}} = State) when ChunkState =/= undefined -> 377 NewHeaders = http_chunk:handle_headers(Headers, ChunkedHeaders), 378 handle_response(State#state{chunk = {last, CbState}, 379 headers = NewHeaders, 380 body = Body}); 381handle_msg({ChunkedHeaders, Body}, #state{headers = Headers , chunk = {undefined, _}} = State) -> 382 NewHeaders = http_chunk:handle_headers(Headers, ChunkedHeaders), 383 handle_response(State#state{headers = NewHeaders, 384 body = Body}); 385handle_msg(Result, State) -> 386 handle_http_msg(Result, State). 387 388handle_http_msg({_, _, Version, {_, _}, _}, 389 #state{status = busy, mod = ModData} = State) -> 390 handle_manager_busy(State#state{mod = 391 ModData#mod{http_version = Version}}), 392 {stop, normal, State}; 393 394handle_http_msg({_, _, Version, {_, _}, _}, 395 #state{status = blocked, mod = ModData} = State) -> 396 handle_manager_blocked(State#state{mod = 397 ModData#mod{http_version = Version}}), 398 {stop, normal, State}; 399 400handle_http_msg({Method, Uri, Version, {RecordHeaders, Headers}, Body}, 401 #state{status = accept, mod = ModData} = State) -> 402 case httpd_request:validate(Method, Uri, Version) of 403 ok -> 404 {ok, NewModData} = 405 httpd_request:update_mod_data(ModData, Method, Uri, 406 Version, Headers), 407 408 case is_host_specified_if_required(NewModData#mod.absolute_uri, 409 RecordHeaders, Version) of 410 true -> 411 handle_body(State#state{headers = RecordHeaders, 412 body = Body, 413 mod = NewModData}); 414 false -> 415 httpd_response:send_status(ModData#mod{http_version = 416 Version}, 417 400, none), 418 {stop, normal, State#state{response_sent = true}} 419 end; 420 {error, {not_supported, What}} -> 421 httpd_response:send_status(ModData#mod{http_version = Version}, 422 501, {Method, Uri, Version}, {not_sup, What}), 423 {stop, normal, State#state{response_sent = true}}; 424 {error, {bad_request, {forbidden, URI}}} -> 425 httpd_response:send_status(ModData#mod{http_version = Version}, 426 403, URI), 427 {stop, normal, State#state{response_sent = true}}; 428 {error, {bad_request, {malformed_syntax, URI}}} -> 429 httpd_response:send_status(ModData#mod{http_version = Version}, 430 400, URI, {malformed_syntax, URI}), 431 {stop, normal, State#state{response_sent = true}}; 432 {error, {bad_version, Ver}} -> 433 httpd_response:send_status(ModData#mod{http_version = "HTTP/0.9"}, 400, Ver, {malformed_syntax, Ver}), 434 {stop, normal, State#state{response_sent = true}} 435 end; 436handle_http_msg(Body, State) -> 437 handle_response(State#state{body = Body}). 438 439handle_manager_busy(#state{mod = #mod{config_db = ConfigDB}} = State) -> 440 MaxClients = httpd_util:lookup(ConfigDB, max_clients, 150), 441 Reason = io_lib:format("heavy load (>~w processes)", [MaxClients]), 442 reject_connection(State, lists:flatten(Reason)). 443 444handle_manager_blocked(State) -> 445 Reason = "Server maintenance performed, try again later", 446 reject_connection(State, Reason). 447 448reject_connection(#state{mod = ModData} = State, Reason) -> 449 httpd_response:send_status(ModData, 503, Reason), 450 {stop, normal, State#state{response_sent = true}}. 451 452is_host_specified_if_required(nohost, #http_request_h{host = undefined}, 453 "HTTP/1.1") -> 454 false; 455is_host_specified_if_required(_, _, _) -> 456 true. 457 458handle_body(#state{mod = #mod{config_db = ConfigDB}} = State) -> 459 MaxHeaderSize = max_header_size(ConfigDB), 460 MaxBodySize = max_body_size(ConfigDB), 461 462 case handle_expect(State, MaxBodySize) of 463 ok -> 464 handle_body(State, MaxHeaderSize, MaxBodySize); 465 Other -> 466 Other 467 468 end. 469 470handle_body(#state{headers = Headers, body = Body, 471 chunk = {ChunkState, CbState}, mod = #mod{config_db = ConfigDB} = ModData} = State, 472 MaxHeaderSize, MaxBodySize) -> 473 MaxChunk = max_client_body_chunk(ConfigDB), 474 case Headers#http_request_h.'transfer-encoding' of 475 "chunked" -> 476 try http_chunk:decode(Body, MaxBodySize, MaxHeaderSize) of 477 {Module, Function, Args} -> 478 http_transport:setopts(ModData#mod.socket_type, 479 ModData#mod.socket, 480 [{active, once}]), 481 {noreply, State#state{mfa = 482 {Module, Function, Args}, 483 chunk = chunk_start(MaxChunk)}}; 484 {ok, {ChunkedHeaders, NewBody}} -> 485 NewHeaders = http_chunk:handle_headers(Headers, ChunkedHeaders), 486 handle_response(State#state{headers = NewHeaders, 487 body = NewBody, 488 chunk = chunk_finish(ChunkState, CbState, MaxChunk)}) 489 catch 490 throw:Error -> 491 httpd_response:send_status(ModData, 400, 492 "Bad input", {chunk_decoding, bad_input, Error}), 493 {stop, normal, State#state{response_sent = true}} 494 end; 495 Encoding when is_list(Encoding) -> 496 httpd_response:send_status(ModData, 501, 497 "Unknown Transfer-Encoding", 498 {unknown_transfer_encoding, Encoding}), 499 {stop, normal, State#state{response_sent = true}}; 500 _ -> 501 Length = list_to_integer(Headers#http_request_h.'content-length'), 502 MaxChunk = max_client_body_chunk(ConfigDB), 503 case ((Length =< MaxBodySize) or (MaxBodySize == nolimit)) of 504 true -> 505 case httpd_request:body_chunk_first(Body, Length, MaxChunk) of 506 %% This is the case that the we need more data to complete 507 %% the body but chunking to the mod_esi user is not enabled. 508 {Module, add_chunk = Function, Args} -> 509 http_transport:setopts(ModData#mod.socket_type, 510 ModData#mod.socket, 511 [{active, once}]), 512 {noreply, State#state{mfa = 513 {Module, Function, Args}}}; 514 %% Chunking to mod_esi user is enabled 515 {ok, {continue, Module, Function, Args}} -> 516 http_transport:setopts(ModData#mod.socket_type, 517 ModData#mod.socket, 518 [{active, once}]), 519 {noreply, State#state{mfa = 520 {Module, Function, Args}}}; 521 {ok, {{continue, Chunk}, Module, Function, Args}} -> 522 handle_internal_chunk(State#state{chunk = chunk_start(MaxChunk), 523 body = Chunk}, Module, Function, Args); 524 %% Whole body delivered, if chunking mechanism is enabled the whole 525 %% body fits in one chunk. 526 {ok, NewBody} -> 527 handle_response(State#state{chunk = chunk_finish(ChunkState, 528 CbState, MaxChunk), 529 headers = Headers, 530 body = NewBody}) 531 end; 532 false -> 533 httpd_response:send_status(ModData, 413, "Body too long"), 534 {stop, normal, State#state{response_sent = true}} 535 end 536 end. 537 538handle_expect(#state{headers = Headers, mod = 539 #mod{config_db = ConfigDB} = ModData} = State, 540 MaxBodySize) -> 541 Length = list_to_integer(Headers#http_request_h.'content-length'), 542 case expect(Headers, ModData#mod.http_version, ConfigDB) of 543 continue when (MaxBodySize > Length) orelse (MaxBodySize =:= nolimit) -> 544 httpd_response:send_status(ModData, 100, ""), 545 ok; 546 continue when MaxBodySize < Length -> 547 httpd_response:send_status(ModData, 413, "Body too long"), 548 {stop, normal, State#state{response_sent = true}}; 549 {break, Value} -> 550 httpd_response:send_status(ModData, 417, 551 "Unexpected expect value", 552 {unexpected, Value} 553 ), 554 {stop, normal, State#state{response_sent = true}}; 555 no_expect_header -> 556 ok; 557 http_1_0_expect_header -> 558 httpd_response:send_status(ModData, 400, 559 "Only HTTP/1.1 Clients " 560 "may use the Expect Header", 561 "Client with lower version than 1.1 tried to send" 562 "an expect header"), 563 {stop, normal, State#state{response_sent = true}} 564 end. 565 566expect(Headers, "HTTP/1.1", _) -> 567 case Headers#http_request_h.expect of 568 "100-continue" -> 569 continue; 570 undefined -> 571 no_expect_header; 572 Other -> 573 {break, Other} 574 end; 575expect(Headers, _, ConfigDB) -> 576 case Headers#http_request_h.expect of 577 undefined -> 578 no_expect_header; 579 _ -> 580 case httpd_util:lookup(ConfigDB, expect, continue) of 581 continue-> 582 no_expect_header; 583 _ -> 584 http_1_0_expect_header 585 end 586 end. 587 588handle_chunk(http_chunk = Module, decode_data = Function, 589 [ChunkSize, TotalChunk, {MaxBodySize, BodySoFar, _AccLength, MaxHeaderSize}], 590 #state{chunk = {_, CbState}, 591 mod = #mod{socket_type = SockType, 592 socket = Socket} = ModData} = State) -> 593 {continue, NewCbState} = httpd_response:handle_continuation(ModData#mod{entity_body = 594 {continue, BodySoFar, CbState}}), 595 http_transport:setopts(SockType, Socket, [{active, once}]), 596 State#state{chunk = {continue, NewCbState}, mfa = {Module, Function, [ChunkSize, TotalChunk, {MaxBodySize, <<>>, 0, MaxHeaderSize}]}}; 597 598handle_chunk(http_chunk = Module, decode_size = Function, 599 [Data, HexList, _AccSize, {MaxBodySize, BodySoFar, _AccLength, MaxHeaderSize}], 600 #state{chunk = {_, CbState}, 601 mod = #mod{socket_type = SockType, 602 socket = Socket} = ModData} = State) -> 603 {continue, NewCbState} = httpd_response:handle_continuation(ModData#mod{entity_body = {continue, BodySoFar, CbState}}), 604 http_transport:setopts(SockType, Socket, [{active, once}]), 605 State#state{chunk = {continue, NewCbState}, mfa = {Module, Function, [Data, HexList, 0, {MaxBodySize, <<>>, 0, MaxHeaderSize}]}}; 606handle_chunk(Module, Function, Args, #state{mod = #mod{socket_type = SockType, 607 socket = Socket}} = State) -> 608 http_transport:setopts(SockType, Socket, [{active, once}]), 609 State#state{mfa = {Module, Function, Args}}. 610 611handle_internal_chunk(#state{chunk = {ChunkState, CbState}, body = Chunk, 612 mod = #mod{socket_type = SockType, 613 socket = Socket} = ModData} = State, Module, Function, Args)-> 614 Bodychunk = body_chunk(ChunkState, CbState, Chunk), 615 {continue, NewCbState} = httpd_response:handle_continuation(ModData#mod{entity_body = Bodychunk}), 616 case Args of 617 [<<>> | _] -> 618 http_transport:setopts(SockType, Socket, [{active, once}]), 619 {noreply, State#state{chunk = {continue, NewCbState}, mfa = {Module, Function, Args}}}; 620 _ -> 621 handle_info({dummy, Socket, <<>>}, State#state{chunk = {continue, NewCbState}, 622 mfa = {Module, Function, Args}}) 623 end. 624 625handle_response(#state{body = Body, 626 headers = Headers, 627 mod = ModData, 628 chunk = {last, CbState}, 629 max_keep_alive_request = Max} = State) when Max > 0 -> 630 {NewBody, Data} = httpd_request:body_data(Headers, Body), 631 ok = httpd_response:generate_and_send_response( 632 ModData#mod{entity_body = {last, NewBody, CbState}}), 633 handle_next_request(State#state{response_sent = true}, Data); 634handle_response(#state{body = Body, 635 mod = ModData, 636 headers = Headers, 637 max_keep_alive_request = Max} = State) when Max > 0 -> 638 {NewBody, Data} = httpd_request:body_data(Headers, Body), 639 %% Backwards compatible, may cause memory explosion 640 ok = httpd_response:generate_and_send_response( 641 ModData#mod{entity_body = binary_to_list(NewBody)}), 642 handle_next_request(State#state{response_sent = true}, Data); 643handle_response(#state{body = Body, 644 headers = Headers, 645 mod = ModData} = State) -> 646 {NewBody, _} = httpd_request:body_data(Headers, Body), 647 ok = httpd_response:generate_and_send_response( 648 ModData#mod{entity_body = NewBody}), 649 {stop, normal, State#state{response_sent = true}}. 650 651handle_next_request(#state{mod = #mod{connection = true} = ModData, 652 max_keep_alive_request = Max} = State, Data) -> 653 654 NewModData = #mod{socket_type = ModData#mod.socket_type, 655 socket = ModData#mod.socket, 656 config_db = ModData#mod.config_db, 657 init_data = ModData#mod.init_data}, 658 MaxHeaderSize = max_header_size(ModData#mod.config_db), 659 MaxURISize = max_uri_size(ModData#mod.config_db), 660 MaxContentLen = max_content_length(ModData#mod.config_db), 661 Customize = customize(ModData#mod.config_db), 662 MaxChunk = max_client_body_chunk(ModData#mod.config_db), 663 664 MFA = {httpd_request, parse, [[{max_uri, MaxURISize}, {max_header, MaxHeaderSize}, 665 {max_version, ?HTTP_MAX_VERSION_STRING}, 666 {max_method, ?HTTP_MAX_METHOD_STRING}, 667 {max_content_length, MaxContentLen}, 668 {customize, Customize} 669 ]]}, 670 TmpState = State#state{mod = NewModData, 671 mfa = MFA, 672 max_keep_alive_request = decrease(Max), 673 headers = undefined, 674 body = undefined, 675 chunk = chunk_start(MaxChunk), 676 response_sent = false}, 677 678 NewState = activate_request_timeout(TmpState), 679 680 case Data of 681 <<>> -> 682 http_transport:setopts(ModData#mod.socket_type, 683 ModData#mod.socket, [{active, once}]), 684 {noreply, NewState}; 685 _ -> 686 handle_info({dummy, ModData#mod.socket, Data}, NewState) 687 end; 688 689handle_next_request(State, _) -> 690 {stop, normal, State}. 691 692activate_request_timeout(#state{timeout = Time} = State) -> 693 Ref = erlang:send_after(Time, self(), timeout), 694 State#state{timer = Ref}. 695data_receive_counter(State, Byte_limit) -> 696 case Byte_limit of 697 false -> 698 State#state{data = 0}; 699 Nr -> 700 erlang:send_after(3000, self(), check_data_first), 701 State#state{data = 0, byte_limit = Nr} 702 end. 703cancel_request_timeout(#state{timer = undefined} = State) -> 704 State; 705cancel_request_timeout(#state{timer = Timer} = State) -> 706 erlang:cancel_timer(Timer), 707 receive 708 timeout -> 709 ok 710 after 0 -> 711 ok 712 end, 713 State#state{timer = undefined}. 714 715decrease(N) when is_integer(N) -> 716 N-1; 717decrease(N) -> 718 N. 719 720%-------------------------------------------------------------------- 721%% Config access wrapper functions 722%%-------------------------------------------------------------------- 723 724max_header_size(ConfigDB) -> 725 httpd_util:lookup(ConfigDB, max_header_size, ?HTTP_MAX_HEADER_SIZE). 726 727max_client_body_chunk(ConfigDB) -> 728 httpd_util:lookup(ConfigDB, max_client_body_chunk, nolimit). 729 730max_uri_size(ConfigDB) -> 731 httpd_util:lookup(ConfigDB, max_uri_size, ?HTTP_MAX_URI_SIZE). 732 733max_body_size(ConfigDB) -> 734 httpd_util:lookup(ConfigDB, max_body_size, nolimit). 735 736max_keep_alive_request(ConfigDB) -> 737 httpd_util:lookup(ConfigDB, max_keep_alive_request, infinity). 738 739max_content_length(ConfigDB) -> 740 httpd_util:lookup(ConfigDB, max_content_length, ?HTTP_MAX_CONTENT_LENGTH). 741 742customize(ConfigDB) -> 743 httpd_util:lookup(ConfigDB, customize, httpd_custom). 744 745chunk_start(nolimit) -> 746 {undefined, undefined}; 747chunk_start(_) -> 748 {first, undefined}. 749chunk_finish(_, _, nolimit) -> 750 {undefined, undefined}; 751chunk_finish(_, CbState, _) -> 752 {last, CbState}. 753 754body_chunk(first, _, Chunk) -> 755 {first, Chunk}; 756body_chunk(ChunkState, CbState, Chunk) -> 757 {ChunkState, Chunk, CbState}. 758