%% %% %CopyrightBegin% %% %% Copyright Ericsson AB 2004-2010. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in %% compliance with the License. You should have received a copy of the %% Erlang Public License along with this software. If not, it can be %% retrieved online at http://www.erlang.org/. %% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and limitations %% under the License. %% %% %CopyrightEnd% %% % -module(http_transport). % Internal application API -export([start/1, connect/3, connect/4, listen/2, listen/3, accept/2, accept/3, close/2, send/3, controlling_process/3, setopts/3, peername/2, resolve/0]). -export([negotiate/3]). %%%========================================================================= %%% Internal application API %%%========================================================================= %%------------------------------------------------------------------------- %% start(SocketType) -> ok | {error, Reason} %% SocketType = ip_comm | {ssl, _} %% %% Description: Makes sure inet_db or ssl is started. %%------------------------------------------------------------------------- start(ip_comm) -> case inet_db:start() of {ok, _} -> ok; {error, {already_started, _}} -> ok; Error -> Error end; start({ssl, _}) -> case ssl:start() of ok -> ok; {error, {already_started,_}} -> ok; Error -> Error end. %%------------------------------------------------------------------------- %% connect(SocketType, Address, Options, Timeout) -> %% {ok, Socket} | {error, Reason} %% SocketType = ip_comm | {ssl, SslConfig} %% Address = {Host, Port} %% Options = [option()] %% Socket = socket() %% option() = ipfamily() | {ip, ip_address()} | {port, integer()} %% ipfamily() = inet | inet6 %% %% Description: Connects to the Host and Port specified in HTTPRequest. %%------------------------------------------------------------------------- connect(SocketType, Address, Opts) -> connect(SocketType, Address, Opts, infinity). connect(ip_comm = _SocketType, {Host, Port}, Opts0, Timeout) when is_list(Opts0) -> Opts = [binary, {packet, 0}, {active, false}, {reuseaddr, true} | Opts0], gen_tcp:connect(Host, Port, Opts, Timeout); connect({ssl, SslConfig}, {Host, Port}, _, Timeout) -> Opts = [binary, {active, false}] ++ SslConfig, ssl:connect(Host, Port, Opts, Timeout); connect({erl_ssl, SslConfig}, {Host, Port}, _, Timeout) -> Opts = [binary, {active, false}, {ssl_imp, new}] ++ SslConfig, ssl:connect(Host, Port, Opts, Timeout). %%------------------------------------------------------------------------- %% listen(SocketType, Port) -> {ok, Socket} | {error, Reason} %% SocketType = ip_comm | {ssl, SSLConfig} %% Port = integer() %% Socket = socket() %% %% Description: Sets up socket to listen on the port Port on the local %% host using either gen_tcp or ssl. In the gen_tcp case the port %% might allready have been initiated by a wrapper-program and is %% given as an Fd that can be retrieved by init:get_argument. The %% reason for this to enable a HTTP-server not running as root to use %% port 80. %%------------------------------------------------------------------------- listen(SocketType, Port) -> listen(SocketType, undefined, Port). listen(ip_comm, Addr, Port) -> case (catch listen_ip_comm(Addr, Port)) of {'EXIT', Reason} -> {error, {exit, Reason}}; Else -> Else end; listen({ssl, SSLConfig} = Ssl, Addr, Port) -> Opt = sock_opt(Ssl, Addr, SSLConfig), ssl:listen(Port, Opt); listen({erl_ssl, SSLConfig} = Ssl, Addr, Port) -> Opt = sock_opt(Ssl, Addr, SSLConfig), ssl:listen(Port, [{ssl_imp, new} | Opt]). listen_ip_comm(Addr, Port) -> {NewPort, Opts, IpFamily} = get_socket_info(Addr, Port), case IpFamily of inet6fb4 -> Opts2 = [inet6 | Opts], case (catch gen_tcp:listen(NewPort, Opts2)) of {error, Reason} when ((Reason =:= nxdomain) orelse (Reason =:= eafnosupport)) -> Opts3 = [inet | Opts], gen_tcp:listen(NewPort, Opts3); %% This is when a given hostname has resolved to a %% IPv4-address. The inet6-option together with a %% {ip, IPv4} option results in badarg {'EXIT', _} -> Opts3 = [inet | Opts], gen_tcp:listen(NewPort, Opts3); Other -> Other end; _ -> Opts2 = [IpFamily | Opts], gen_tcp:listen(NewPort, Opts2) end. ipfamily_default(Addr, Port) -> httpd_conf:lookup(Addr, Port, ipfamily, inet6fb4). get_socket_info(Addr, Port) -> Key = list_to_atom("httpd_" ++ integer_to_list(Port)), BaseOpts = [{backlog, 128}, {reuseaddr, true}], IpFamilyDefault = ipfamily_default(Addr, Port), case init:get_argument(Key) of {ok, [[Value]]} -> {Fd, IpFamily} = case string:tokens(Value, [$|]) of [FdStr, IpFamilyStr] -> Fd0 = fd_of(FdStr), IpFamily0 = ip_family_of(IpFamilyStr), {Fd0, IpFamily0}; [FdStr] -> {fd_of(FdStr), IpFamilyDefault}; _ -> throw({error, {bad_descriptor, Value}}) end, {0, sock_opt(ip_comm, Addr, [{fd, Fd} | BaseOpts]), IpFamily}; error -> {Port, sock_opt(ip_comm, Addr, BaseOpts), IpFamilyDefault} end. fd_of(FdStr) -> case (catch list_to_integer(FdStr)) of Fd when is_integer(Fd) -> Fd; _ -> throw({error, {bad_descriptor, FdStr}}) end. ip_family_of(IpFamilyStr) -> IpFamily = list_to_atom(IpFamilyStr), case lists:member(IpFamily, [inet, inet6, inet6fb4]) of true -> IpFamily; false -> throw({error, {bad_ipfamily, IpFamilyStr}}) end. %%------------------------------------------------------------------------- %% accept(SocketType, ListenSocket) -> {ok, Socket} | {error, Reason} %% accept(SocketType, ListenSocket, Timeout) -> ok | {error, Reason} %% SocketType = ip_comm | {ssl, SSLConfig} %% ListenSocket = socket() %% Timeout = infinity | integer() >= 0 %% Socket = socket() %% %% Description: Accepts an incoming connection request on a listen socket, %% using either gen_tcp or ssl. %%------------------------------------------------------------------------- accept(SocketType, ListenSocket) -> accept(SocketType, ListenSocket, infinity). accept(ip_comm, ListenSocket, Timeout) -> gen_tcp:accept(ListenSocket, Timeout); accept({ssl,_SSLConfig}, ListenSocket, Timeout) -> ssl:transport_accept(ListenSocket, Timeout). %%------------------------------------------------------------------------- %% controlling_process(SocketType, Socket, NewOwner) -> ok | {error, Reason} %% SocketType = ip_comm | {ssl, _} %% Socket = socket() %% NewOwner = pid() %% %% Description: Assigns a new controlling process to Socket. %%------------------------------------------------------------------------- controlling_process(ip_comm, Socket, NewOwner) -> gen_tcp:controlling_process(Socket, NewOwner); controlling_process({ssl, _}, Socket, NewOwner) -> ssl:controlling_process(Socket, NewOwner). %%------------------------------------------------------------------------- %% setopts(SocketType, Socket, Options) -> ok | {error, Reason} %% SocketType = ip_comm | {ssl, _} %% Socket = socket() %% Options = list() %% Description: Sets one or more options for a socket, using either %% gen_tcp or ssl. %%------------------------------------------------------------------------- setopts(ip_comm, Socket, Options) -> inet:setopts(Socket,Options); setopts({ssl, _}, Socket, Options) -> ssl:setopts(Socket, Options). %%------------------------------------------------------------------------- %% send(RequestOrSocketType, Socket, Message) -> ok | {error, Reason} %% SocketType = ip_comm | {ssl, _} %% Socket = socket() %% Message = list() | binary() %% Description: Sends a packet on a socket, using either gen_tcp or ssl. %%------------------------------------------------------------------------- send(ip_comm, Socket, Message) -> gen_tcp:send(Socket, Message); send({ssl, _}, Socket, Message) -> ssl:send(Socket, Message). %%------------------------------------------------------------------------- %% close(SocketType, Socket) -> ok | {error, Reason} %% SocketType = ip_comm | {ssl, _} %% Socket = socket() %% %% Description: Closes a socket, using either gen_tcp or ssl. %%------------------------------------------------------------------------- close(ip_comm, Socket) -> gen_tcp:close(Socket); close({ssl, _}, Socket) -> ssl:close(Socket). %%------------------------------------------------------------------------- %% peername(SocketType, Socket) -> ok | {error, Reason} %% SocketType = ip_comm | {ssl, _} %% Socket = socket() %% %% Description: Returns the address and port for the other end of a %% connection, usning either gen_tcp or ssl. %%------------------------------------------------------------------------- peername(ip_comm, Socket) -> case inet:peername(Socket) of {ok,{{A, B, C, D}, Port}} -> PeerName = integer_to_list(A)++"."++integer_to_list(B)++"."++ integer_to_list(C)++"."++integer_to_list(D), {Port, PeerName}; {ok,{{A, B, C, D, E, F, G, H}, Port}} -> PeerName = http_util:integer_to_hexlist(A) ++ ":"++ http_util:integer_to_hexlist(B) ++ ":" ++ http_util:integer_to_hexlist(C) ++ ":" ++ http_util:integer_to_hexlist(D) ++ ":" ++ http_util:integer_to_hexlist(E) ++ ":" ++ http_util:integer_to_hexlist(F) ++ ":" ++ http_util:integer_to_hexlist(G) ++":"++ http_util:integer_to_hexlist(H), {Port, PeerName}; {error, _} -> {-1, "unknown"} end; peername({ssl, _}, Socket) -> case ssl:peername(Socket) of {ok,{{A, B, C, D}, Port}} -> PeerName = integer_to_list(A)++"."++integer_to_list(B)++"."++ integer_to_list(C)++"."++integer_to_list(D), {Port, PeerName}; {error, _} -> {-1, "unknown"} end. %%------------------------------------------------------------------------- %% resolve() -> HostName %% HostName = string() %% %% Description: Returns the local hostname. %%------------------------------------------------------------------------- resolve() -> {ok, Name} = inet:gethostname(), Name. %%%======================================================================== %%% Internal functions %%%======================================================================== %% Address any comes from directive: BindAddress "*" sock_opt(ip_comm, any = Addr, Opts) -> sock_opt2([{ip, Addr} | Opts]); sock_opt(ip_comm, undefined, Opts) -> sock_opt2(Opts); sock_opt(_, any = _Addr, Opts) -> sock_opt2(Opts); sock_opt(_, undefined = _Addr, Opts) -> sock_opt2(Opts); sock_opt(_, {_,_,_,_} = Addr, Opts) -> sock_opt2([{ip, Addr} | Opts]); sock_opt(ip_comm, Addr, Opts) -> sock_opt2([{ip, Addr} | Opts]); sock_opt(_, Addr, Opts) -> sock_opt2([{ip, Addr} | Opts]). sock_opt2(Opts) -> [{packet, 0}, {active, false} | Opts]. negotiate(ip_comm,_,_) -> ok; negotiate({ssl,_},Socket,Timeout) -> negotiate(Socket, Timeout); negotiate({erl_ssl, _}, Socket, Timeout) -> negotiate(Socket, Timeout). negotiate(Socket, Timeout) -> case ssl:ssl_accept(Socket, Timeout) of ok -> ok; {error, Error} -> case lists:member(Error, [timeout,econnreset,esslaccept,esslerrssl]) of true -> {error,normal}; false -> {error, Error} end end.