1%% ``Licensed under the Apache License, Version 2.0 (the "License");
2%% you may not use this file except in compliance with the License.
3%% You may obtain a copy of the License at
4%%
5%%     http://www.apache.org/licenses/LICENSE-2.0
6%%
7%% Unless required by applicable law or agreed to in writing, software
8%% distributed under the License is distributed on an "AS IS" BASIS,
9%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10%% See the License for the specific language governing permissions and
11%% limitations under the License.
12%%
13%% The Initial Developer of the Original Code is Mobile Arts AB
14%% Portions created by Mobile Arts are Copyright 2002, Mobile Arts AB
15%% All Rights Reserved.''
16%%
17
18%%% This version of the HTTP/1.1 client implements:
19%%%      - RFC 2616 HTTP 1.1 client part
20%%%      - RFC 2817 Upgrading to TLS Within HTTP/1.1 (not yet!)
21%%%      - RFC 2818 HTTP Over TLS
22%%%      - RFC 3229 Delta encoding in HTTP (not yet!)
23%%%      - RFC 3230 Instance Digests in HTTP (not yet!)
24%%%      - RFC 3310 Authentication and Key Agreement (AKA) (not yet!)
25%%%      - HTTP/1.1 Specification Errata found at
26%%%        http://world.std.com/~lawrence/http_errata.html
27%%%    Additionally follows the following recommendations:
28%%%      - RFC 3143 Known HTTP Proxy/Caching Problems (not yet!)
29%%%      - draft-nottingham-hdrreg-http-00.txt (not yet!)
30%%%
31%%% Depends on
32%%%      - uri.erl for all URL parsing (except what is handled by the C driver)
33%%%      - http_lib.erl for all parsing of body and headers
34%%%
35%%% Supported Settings are:
36%%% http_timeout      % (int) Milliseconds before a request times out
37%%% http_useproxy     % (bool) True if a proxy should be used
38%%% http_proxy        % (string) Proxy
39%%% http_noproxylist  % (list) List with hosts not requiring proxy
40%%% http_autoredirect % (bool) True if automatic redirection on 30X responses
41%%% http_ssl          % (list) SSL settings. A non-empty list enables SSL/TLS
42%%%                      support in the HTTP client
43%%% http_pipelinesize % (int) Length of pipeline. 1 means no pipeline.
44%%%                      Only has effect when initiating a new session.
45%%% http_sessions     % (int) Max number of open sessions for {Addr,Port}
46%%%
47%%% TODO: (Known bugs!)
48%% - Cache handling
49%% - Doesn't handle a bunch of entity headers properly
50%% - Better handling of status codes different from 200,30X and 50X
51%% - Many of the settings above are not implemented!
52%% - close_session/2 and cancel_request/1 doesn't work
53%% - Variable pipe size.
54%% - Due to the fact that inet_drv only has a single timer, the timeouts given
55%%   for pipelined requests are not ok (too long)
56%%
57%% Note:
58%% - Some servers (e.g. Microsoft-IIS/5.0) may sometimes not return a proper
59%%   'Location' header on a redirect.
60%%   The client will fail with {error,no_scheme} in these cases.
61
62-module(http).
63-author("johan.blom@mobilearts.se").
64
65-export([start/0,
66	 request/3,request/4,cancel_request/1,
67	 request_sync/2,request_sync/3]).
68
69-include("http.hrl").
70-include("jnets_httpd.hrl").
71
72-define(START_OPTIONS,[]).
73
74%%% HTTP Client manager. Used to store open connections.
75%%% Will be started automatically unless started explicitly.
76start() ->
77    application:start(ssl),
78    httpc_manager:start().
79
80%%% Asynchronous HTTP request that spawns a handler.
81%%% Method                          HTTPReq
82%%% options,get,head,delete,trace = {Url,Headers}
83%%% post,put                      = {Url,Headers,ContentType,Body}
84%%%  where Url is a {Scheme,Host,Port,PathQuery} tuple, as returned by uri.erl
85%%%
86%%% Returns: {ok,ReqId} |
87%%%          {error,Reason}
88%%% If {ok,Pid} was returned, the handler will return with
89%%%    gen_server:cast(From,{Ref,ReqId,{error,Reason}}) |
90%%%    gen_server:cast(From,{Ref,ReqId,{Status,Headers,Body}})
91%%%  where Reason is an atom and Headers a #res_headers{} record
92%%% http:format_error(Reason) gives a more informative description.
93%%%
94%%% Note:
95%%% - Always try to find an open connection to a given host and port, and use
96%%%   the associated socket.
97%%% - Unless a 'Connection: close' header is provided don't close the socket
98%%%   after a response is given
99%%% - A given Pid, found in the database, might be terminated before the
100%%%   message is sent to the Pid. This will happen e.g., if the connection is
101%%%   closed by the other party and there are no pending requests.
102%%% - The HTTP connection process is spawned, if necessary, in
103%%%   httpc_manager:add_connection/4
104request(Ref,Method,HTTPReqCont) ->
105    request(Ref,Method,HTTPReqCont,[],self()).
106
107request(Ref,Method,HTTPReqCont,Settings) ->
108    request(Ref,Method,HTTPReqCont,Settings,self()).
109
110request(Ref,Method,{{Scheme,Host,Port,PathQuery},
111		    Headers,ContentType,Body},Settings,From) ->
112    case create_settings(Settings,#client_settings{}) of
113	{error,Reason} ->
114	    {error,Reason};
115	CS ->
116	    case create_headers(Headers,#req_headers{}) of
117		{error,Reason} ->
118		    {error,Reason};
119		H ->
120		    Req=#request{ref=Ref,from=From,
121				 scheme=Scheme,address={Host,Port},
122				 pathquery=PathQuery,method=Method,
123				 headers=H,content={ContentType,Body},
124				 settings=CS},
125		    httpc_manager:request(Req)
126	    end
127    end;
128request(Ref,Method,{Url,Headers},Settings,From) ->
129    request(Ref,Method,{Url,Headers,[],[]},Settings,From).
130
131%%% Cancels requests identified with ReqId.
132%%% FIXME! Doesn't work...
133cancel_request(ReqId) ->
134    httpc_manager:cancel_request(ReqId).
135
136%%% Close all sessions currently open to Host:Port
137%%% FIXME! Doesn't work...
138close_session(Host,Port) ->
139    httpc_manager:close_session(Host,Port).
140
141
142%%% Synchronous HTTP request that waits until a response is created
143%%% (e.g. successfull reply or timeout)
144%%% Method                          HTTPReq
145%%% options,get,head,delete,trace = {Url,Headers}
146%%% post,put                      = {Url,Headers,ContentType,Body}
147%%%  where Url is a string() or a {Scheme,Host,Port,PathQuery} tuple
148%%%
149%%% Returns: {Status,Headers,Body} |
150%%%          {error,Reason}
151%%% where Reason is an atom.
152%%% http:format_error(Reason) gives a more informative description.
153request_sync(Method,HTTPReqCont) ->
154    request_sync(Method,HTTPReqCont,[]).
155
156request_sync(Method,{Url,Headers},Settings)
157  when Method==options;Method==get;Method==head;Method==delete;Method==trace ->
158    case uri:parse(Url) of
159	{error,Reason} ->
160	    {error,Reason};
161	ParsedUrl ->
162	    request_sync(Method,{ParsedUrl,Headers,[],[]},Settings,0)
163    end;
164request_sync(Method,{Url,Headers,ContentType,Body},Settings)
165  when Method==post;Method==put ->
166    case uri:parse(Url) of
167	{error,Reason} ->
168	    {error,Reason};
169	ParsedUrl ->
170	    request_sync(Method,{ParsedUrl,Headers,ContentType,Body},Settings,0)
171    end;
172request_sync(Method,Request,Settings) ->
173    {error,bad_request}.
174
175request_sync(Method,HTTPCont,Settings,_Redirects) ->
176    case request(request_sync,Method,HTTPCont,Settings,self()) of
177	{ok,_ReqId} ->
178	    receive
179		{'$gen_cast',{request_sync,_ReqId2,{Status,Headers,Body}}} ->
180		    {Status,pp_headers(Headers),binary_to_list(Body)};
181		{'$gen_cast',{request_sync,_ReqId2,{error,Reason}}} ->
182		    {error,Reason};
183		Error ->
184		    Error
185	    end;
186	Error ->
187	    Error
188    end.
189
190
191create_settings([],Out) ->
192    Out;
193create_settings([{http_timeout,Val}|Settings],Out) ->
194    create_settings(Settings,Out#client_settings{timeout=Val});
195create_settings([{http_useproxy,Val}|Settings],Out) ->
196    create_settings(Settings,Out#client_settings{useproxy=Val});
197create_settings([{http_proxy,Val}|Settings],Out) ->
198    create_settings(Settings,Out#client_settings{proxy=Val});
199create_settings([{http_noproxylist,Val}|Settings],Out) ->
200    create_settings(Settings,Out#client_settings{noproxylist=Val});
201create_settings([{http_autoredirect,Val}|Settings],Out) ->
202    create_settings(Settings,Out#client_settings{autoredirect=Val});
203create_settings([{http_ssl,Val}|Settings],Out) ->
204    create_settings(Settings,Out#client_settings{ssl=Val});
205create_settings([{http_pipelinesize,Val}|Settings],Out)
206  when integer(Val),Val>0 ->
207    create_settings(Settings,Out#client_settings{max_quelength=Val});
208create_settings([{http_sessions,Val}|Settings],Out)
209  when integer(Val),Val>0 ->
210    create_settings(Settings,Out#client_settings{max_sessions=Val});
211create_settings([{Key,_Val}|_Settings],_Out) ->
212    io:format("ERROR bad settings, got ~p~n",[Key]),
213    {error,bad_settings}.
214
215
216create_headers([],Req) ->
217    Req;
218create_headers([{Key,Val}|Rest],Req) ->
219    case httpd_util:to_lower(Key) of
220	"expect" ->
221	    create_headers(Rest,Req#req_headers{expect=Val});
222	OtherKey ->
223	    create_headers(Rest,
224			   Req#req_headers{other=[{OtherKey,Val}|
225						  Req#req_headers.other]})
226    end.
227
228
229pp_headers(#res_headers{connection=Connection,
230			transfer_encoding=Transfer_encoding,
231			retry_after=Retry_after,
232			content_length=Content_length,
233			content_type=Content_type,
234			location=Location,
235			other=Other}) ->
236    H1=case Connection of
237	   undefined -> [];
238	   _ ->	 [{'Connection',Connection}]
239       end,
240    H2=case Transfer_encoding of
241	   undefined -> [];
242	   _ ->	 [{'Transfer-Encoding',Transfer_encoding}]
243       end,
244    H3=case Retry_after of
245	   undefined -> [];
246	   _ ->	 [{'Retry-After',Retry_after}]
247       end,
248    H4=case Location of
249	   undefined -> [];
250	   _ ->	 [{'Location',Location}]
251       end,
252    HCL=case Content_length of
253	   "0" -> [];
254	   _ ->	 [{'Content-Length',Content_length}]
255       end,
256    HCT=case Content_type of
257	   undefined -> [];
258	   _ ->	 [{'Content-Type',Content_type}]
259       end,
260    H1++H2++H3++H4++HCL++HCT++Other.
261