%%% BEGIN openflax/http/interface.erl %%% %%% %%% openflax - Open Source web server for Erlang/OTP %%% Copyright (c)2004 Cat's Eye Technologies. All rights reserved. %%% %%% Redistribution and use in source and binary forms, with or without %%% modification, are permitted provided that the following conditions %%% are met: %%% %%% Redistributions of source code must retain the above copyright %%% notice, this list of conditions and the following disclaimer. %%% %%% Redistributions in binary form must reproduce the above copyright %%% notice, this list of conditions and the following disclaimer in %%% the documentation and/or other materials provided with the %%% distribution. %%% %%% Neither the name of Cat's Eye Technologies nor the names of its %%% contributors may be used to endorse or promote products derived %%% from this software without specific prior written permission. %%% %%% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND %%% CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, %%% INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF %%% MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE %%% DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE %%% LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, %%% OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, %%% PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, %%% OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON %%% ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, %%% OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY %%% OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE %%% POSSIBILITY OF SUCH DAMAGE. %% @doc The HTTP service for Openflax. %% %%

This module implements a middleman process which takes ownership %% of the TCP/IP socket and does the low-level translation of the HTTP %% protocol, so that the other parts of OpenFlax need not deal with %% moving data to and from the socket itself. It uses the function %% library module openflax.http.parse to do the HTTP %% parsing, and is intended to be used only from the modules %% openflax.http.request and %% openflax.http.response.

%% %% @end -module(openflax.http.interface). -vsn('$Id: interface.erl 31 2004-04-23 07:00:11Z catseye $'). -author('catseye@catseye.mb.ca'). -copyright('Copyright (c)2004 Cat`s Eye Technologies. All rights reserved.'). -export([start/4]). -import(lists). -import(gen_tcp). %%% -------------------------- ENTRY POINT ------------------------ %%% %% @spec start(fun(), interface(), port(), maxconn()) -> never_returns %% @doc Creates a new HTTP service on the given port with the given %% maximum number of simultaneous connections. start(Fun, Interface, Port, MaxConn) -> IfaceOpts = case Interface of undefined -> []; String -> [{ip, openflax.tcp:get_iface_ip_addr(Interface)}] end, openflax.tcp:server(fun(Socket, RemoteAddress) -> Http = self(), Pid = spawn_link(fun() -> Fun(Http, RemoteAddress) end), server(Socket, Pid) end, Port, [list, {reuseaddr, true}, {nodelay, true}, {active, false}, {packet, raw}, {recbuf, 8192}, {sndbuf, 8192} ] ++ IfaceOpts, MaxConn). %% server(socket(), pid()) -> ok server(Socket, Pid) -> case catch get_request_line(Socket, Pid, "", "") of {'EXIT', Reason} -> deliver_error(Pid, bad_request, Reason); ok -> send_response(Socket, Pid) end. %%% -------------------------- ENTRY POINT ------------------------ %%% % In the following functions, % S means Socket % P means Parent (pid) % B means Buffer % H means Head (of buffer) % T means Tail (of buffer) % A means Accumulator % Z means Size (expected size of body) % Y means body type (plain, chunked, or multipart) % C means chunk-accumulator % U means boundary (for multipart) % E means ender (for multipart) get_request_line(S, P, "", A) -> get_request_line(S, P, replenish(S), A); get_request_line(S, P, [10 | T], A) -> deliver_request_line(P, A), get_header(S, P, T, "", undefined, undefined); get_request_line(S, P, [13, 10 | T], A) -> deliver_request_line(P, A), get_header(S, P, T, "", undefined, undefined); get_request_line(S, P, [H | T], A) -> get_request_line(S, P, T, [H | A]). get_header(S, P, "", A, Z, Y) -> get_header(S, P, replenish(S), A, Z, Y); get_header(S, P, [13, 10, WS | T], A, Z, Y) when WS == 9; WS == 32 -> get_header(S, P, T, [32 | A], Z, Y); get_header(S, P, [13, 10, 13, 10 | T], A, Z, Y) -> {Z0, Y0} = deliver_header(P, A, Z, Y), P ! {self(), request_header_end, []}, get_body(S, P, T, [], Z0, Y0); get_header(S, P, D=[13, 10], A, Z, Y) -> get_header(S, P, D ++ replenish(S), A, Z, Y); get_header(S, P, D=[13, 10, 13], A, Z, Y) -> get_header(S, P, D ++ replenish(S), A, Z, Y); get_header(S, P, [13, 10 | T], A, Z, Y) -> {Z0, Y0} = deliver_header(P, A, Z, Y), get_header(S, P, T, "", Z0, Y0); get_header(S, P, [H | T], A, Z, Y) -> get_header(S, P, T, [H | A], Z, Y). get_body(S, P, B, A, Z, undefined) -> % deliver_error(P, bad_request, unknown_body_encoding), ok; get_body(S, P, B, A, Z, plain); get_body(S, P, B, A, undefined, plain) -> % deliver_error(P, bad_request, missing_size), ok; get_body(S, P, B, A, 0, plain); get_body(S, P, B, A, Z, plain) -> get_plain_body(S, P, B, A, Z); get_body(S, P, B, A, _Z, chunked) -> get_chunked_body(S, P, B, A, []); get_body(S, P, B, A, Z, {multipart, Boundary}) -> get_multipart_body(S, P, B, A, Z, Boundary). get_plain_body(_S, P, _B, _A, undefined) -> deliver_plain_body(P, ""), ok; get_plain_body(_S, P, _B, A, 0) -> deliver_plain_body(P, A), ok; get_plain_body(S, P, "", A, Z) -> get_plain_body(S, P, replenish(S), A, Z); get_plain_body(S, P, [H | T], A, Z) -> get_plain_body(S, P, T, [H | A], Z-1). get_chunked_body(S, P, "", A, C) -> get_chunked_body(S, P, replenish(S), A, C); get_chunked_body(S, P, [10 | T], A, C) -> case get_chunk_size(A) of 0 -> deliver_plain_body(P, lists:flatten(lists:reverse(A))), ok; ChunkSize -> {ok, Chunk} = gen_tcp:recv(S, ChunkSize - length(T), 60000), get_chunked_body(S, P, "", A, [T ++ Chunk | C]) end; get_chunked_body(S, P, [H | T], A, C) -> get_chunked_body(S, P, T, [H | A], C). get_chunk_size(A) -> A0 = string:strip(openflax.string:chomp(lists:reverse(A))), [A1 | _] = string:tokens(A0, " "), openflax.string:decode_hex(A1). get_multipart_body(S, P, B, A, Z, U) -> % openflax.app:debug(getting_multipart, U), get_multipart_boundary(S, P, B, A, Z, "--" ++ U, "--" ++ U ++ "--"). get_multipart_boundary(S, P, "", A, Z, U, E) -> get_multipart_boundary(S, P, replenish(S), A, Z, U, E); get_multipart_boundary(S, P, [13, 10 | T], A, Z, U, E) -> % openflax.app:debug(skinny_puppy, {lists:reverse(A), U, E}), case lists:reverse(A) of U -> get_multipart_header(S, P, T, "", Z, U, E) end; get_multipart_boundary(S, P, [H | T], A, Z, U, E) -> get_multipart_boundary(S, P, T, [H | A], Z, U, E). get_multipart_header(S, P, "", A, Z, U, E) -> get_multipart_header(S, P, replenish(S), A, Z, U, E); get_multipart_header(S, P, [13, 10 | T], A, Z, U, E) -> case lists:reverse(A) of "" -> get_multipart_content(S, P, T, "", Z, U, E); Line -> P ! {self(), multipart_header, openflax.http.parse:header_field(Line)}, get_multipart_header(S, P, T, "", Z, U, E) end; get_multipart_header(S, P, [H | T], A, Z, U, E) -> get_multipart_header(S, P, T, [H | A], Z, U, E). get_multipart_content(S, P, "", A, Z, U, E) -> % get_multipart_content(S, P, replenish(S), A, Z, U, E); P ! {self(), multipart_chunk, lists:reverse(A)}, get_multipart_content(S, P, replenish(S), "", Z, U, E); get_multipart_content(S, P, [13], A, Z, U, E) -> get_multipart_content(S, P, [13] ++ replenish(S), A, Z, U, E); get_multipart_content(S, P, [13, 10 | T], A, Z, U, E) -> % openflax.app:debug(fat_albert, {lists:reverse(A), U, E}), case lists:reverse(A) of U -> get_multipart_header(S, P, T, "", Z, U, E); E -> % openflax.app:debug(multipart_ended, ok), P ! {self(), multipart_end, ok}, ok; Else -> P ! {self(), multipart_chunk, Else}, get_multipart_content(S, P, T, "", Z, U, E) end; get_multipart_content(S, P, [H | T], A, Z, U, E) -> get_multipart_content(S, P, T, [H | A], Z, U, E). %%% ------------------------ RESPONSE GENERATOR --------------------- %%% send_response(Socket, Pid) -> receive {Pid, send_response_line, Response} -> gen_tcp:send(Socket, ["HTTP/1.1 ", Response, "\r\n"]), % openflax.app:debug(responsing, {Response, Result}), send_response(Socket, Pid); {Pid, send_headers, List} -> % openflax.app:debug(responsing, List), lists:foreach(fun({Key, Value}) -> case catch atom_to_list(Key) of "res_" ++ Tail -> gen_tcp:send(Socket, [openflax.string:to_header(Tail), ": ", openflax.string:from_term(Value), "\r\n"]); _ -> ok end end, List), gen_tcp:send(Socket, "\r\n"), send_response(Socket, Pid); {Pid, send_data, Data} -> % openflax.app:debug(responsing, Data), gen_tcp:send(Socket, Data), send_response(Socket, Pid); {Pid, start_stream, {HandlerPid, _Size}} -> stream_loop(Socket, HandlerPid), Pid ! {self(), end_stream}, send_response(Socket, Pid); {Pid, close} -> % openflax.app:debug(closing, Socket), gen_tcp:close(Socket) end. %% @spec stream_loop(socket(), pid()) -> ok %% @doc Handles a response streamed through messages. stream_loop(Socket, Pid) -> receive {Pid, stream_data, Data} -> gen_tcp:send(Socket, Data), Pid ! {self(), stream_acknowledge, data}, % Pid ! {self(), stream_error, Error} stream_loop(Socket, Pid); {Pid, stream_close} -> Pid ! {self(), stream_acknowledge, close}, ok end. %%% --------------------------- UTILITIES ------------------------ %%% replenish(Socket) -> {ok, Buffer} = gen_tcp:recv(Socket, 0, 60000), Buffer. deliver_request_line(Pid, Line) -> % openflax.app:debug(delivering, {Pid, Line}), Pid ! {self(), request_line, openflax.http.parse:request_line(lists:reverse(Line))}. deliver_header(Pid, Header, Size, BodyType) -> R = (catch deliver_header0(Pid, Header, Size, BodyType)), % openflax.app:debug(delhdr, R), R. deliver_header0(Pid, Header, Size, BodyType) -> {Key, Value} = openflax.http.parse:header_field(lists:reverse(Header)), Size0 = case Key of req_content_length -> list_to_integer(Value); _ -> Size end, BodyType0 = case {Key, Value} of {req_transfer_encoding, ""} -> plain; {req_transfer_encoding, "chunked"} -> chunked; % TODO: ignore (delete!) content length {req_content_type, _} -> case get_multipart_boundary(Value) of {ok, Boundary} -> {multipart, Boundary}; _ -> BodyType end; _ -> BodyType end, % openflax.app:debug(delivering_rq_h, {Key, Value}), Pid ! {self(), request_header, {Key, Value}}, {Size0, BodyType0}. deliver_plain_body(Pid, Body) -> % openflax.app:debug(delivering, {Body}), Pid ! {self(), request_body, openflax.http.parse:body(lists:reverse(Body))}. deliver_error(Pid, Error, Reason) -> % openflax.app:debug(delivering, {Error, Reason}), Pid ! {self(), {error, {Error, Reason}}}. get_multipart_boundary(Value) when is_list(Value), is_tuple(hd(Value)) -> {String, _} = hd(Value), case openflax.string:to_caps(String) of "Multipart/Form-Data" ++ _ -> case lists:keysearch("boundary", 1, Value) of {value, {"boundary", Boundary}} -> {ok, Boundary}; _ -> error end; _ -> error end; get_multipart_boundary(_Value) -> error. %%% END of openflax/http/interface.erl %%%