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 Mobile Arts AB
13
%% Portions created by Mobile Arts are Copyright 2002, Mobile Arts AB
14
%% All Rights Reserved.''
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
%%% Additionaly follows the following recommendations:
28
%%% - RFC 3143 Known HTTP Proxy/Caching Problems (not yet!)
29
%%% - draft-nottingham-hdrreg-http-00.txt (not yet!)
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
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}
47
%%% TODO: (Known bugs!)
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)
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.
63
-author("johan.blom@mobilearts.se").
66
request/3,request/4,cancel_request/1,
67
request_sync/2,request_sync/3]).
70
-include("jnets_httpd.hrl").
72
-define(START_OPTIONS,[]).
74
%%% HTTP Client manager. Used to store open connections.
75
%%% Will be started automatically unless started explicitly.
77
application:start(ssl),
78
httpc_manager:start().
80
%%% Asynchronous HTTP request that spawns a handler.
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
86
%%% Returns: {ok,ReqId} |
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.
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
104
request(Ref,Method,HTTPReqCont) ->
105
request(Ref,Method,HTTPReqCont,[],self()).
107
request(Ref,Method,HTTPReqCont,Settings) ->
108
request(Ref,Method,HTTPReqCont,Settings,self()).
110
request(Ref,Method,{{Scheme,Host,Port,PathQuery},
111
Headers,ContentType,Body},Settings,From) ->
112
case create_settings(Settings,#client_settings{}) of
116
case create_headers(Headers,#req_headers{}) of
120
Req=#request{ref=Ref,from=From,
121
scheme=Scheme,address={Host,Port},
122
pathquery=PathQuery,method=Method,
123
headers=H,content={ContentType,Body},
125
httpc_manager:request(Req)
128
request(Ref,Method,{Url,Headers},Settings,From) ->
129
request(Ref,Method,{Url,Headers,[],[]},Settings,From).
131
%%% Cancels requests identified with ReqId.
132
%%% FIXME! Doesn't work...
133
cancel_request(ReqId) ->
134
httpc_manager:cancel_request(ReqId).
136
%%% Close all sessions currently open to Host:Port
137
%%% FIXME! Doesn't work...
138
close_session(Host,Port) ->
139
httpc_manager:close_session(Host,Port).
142
%%% Synchronous HTTP request that waits until a response is created
143
%%% (e.g. successfull reply or timeout)
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
149
%%% Returns: {Status,Headers,Body} |
151
%%% where Reason is an atom.
152
%%% http:format_error(Reason) gives a more informative description.
153
request_sync(Method,HTTPReqCont) ->
154
request_sync(Method,HTTPReqCont,[]).
156
request_sync(Method,{Url,Headers},Settings)
157
when Method==options;Method==get;Method==head;Method==delete;Method==trace ->
158
case uri:parse(Url) of
162
request_sync(Method,{ParsedUrl,Headers,[],[]},Settings,0)
164
request_sync(Method,{Url,Headers,ContentType,Body},Settings)
165
when Method==post;Method==put ->
166
case uri:parse(Url) of
170
request_sync(Method,{ParsedUrl,Headers,ContentType,Body},Settings,0)
172
request_sync(Method,Request,Settings) ->
175
request_sync(Method,HTTPCont,Settings,_Redirects) ->
176
case request(request_sync,Method,HTTPCont,Settings,self()) of
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}}} ->
191
create_settings([],Out) ->
193
create_settings([{http_timeout,Val}|Settings],Out) ->
194
create_settings(Settings,Out#client_settings{timeout=Val});
195
create_settings([{http_useproxy,Val}|Settings],Out) ->
196
create_settings(Settings,Out#client_settings{useproxy=Val});
197
create_settings([{http_proxy,Val}|Settings],Out) ->
198
create_settings(Settings,Out#client_settings{proxy=Val});
199
create_settings([{http_noproxylist,Val}|Settings],Out) ->
200
create_settings(Settings,Out#client_settings{noproxylist=Val});
201
create_settings([{http_autoredirect,Val}|Settings],Out) ->
202
create_settings(Settings,Out#client_settings{autoredirect=Val});
203
create_settings([{http_ssl,Val}|Settings],Out) ->
204
create_settings(Settings,Out#client_settings{ssl=Val});
205
create_settings([{http_pipelinesize,Val}|Settings],Out)
206
when integer(Val),Val>0 ->
207
create_settings(Settings,Out#client_settings{max_quelength=Val});
208
create_settings([{http_sessions,Val}|Settings],Out)
209
when integer(Val),Val>0 ->
210
create_settings(Settings,Out#client_settings{max_sessions=Val});
211
create_settings([{Key,_Val}|_Settings],_Out) ->
212
io:format("ERROR bad settings, got ~p~n",[Key]),
213
{error,bad_settings}.
216
create_headers([],Req) ->
218
create_headers([{Key,Val}|Rest],Req) ->
219
case httpd_util:to_lower(Key) of
221
create_headers(Rest,Req#req_headers{expect=Val});
224
Req#req_headers{other=[{OtherKey,Val}|
225
Req#req_headers.other]})
229
pp_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,
236
H1=case Connection of
238
_ -> [{'Connection',Connection}]
240
H2=case Transfer_encoding of
242
_ -> [{'Transfer-Encoding',Transfer_encoding}]
244
H3=case Retry_after of
246
_ -> [{'Retry-After',Retry_after}]
250
_ -> [{'Location',Location}]
252
HCL=case Content_length of
254
_ -> [{'Content-Length',Content_length}]
256
HCT=case Content_type of
258
_ -> [{'Content-Type',Content_type}]
260
H1++H2++H3++H4++HCL++HCT++Other.