1%% 2%% %CopyrightBegin% 3%% 4%% Copyright Ericsson AB 2004-2018. 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-module(httpc_request). 22 23-include_lib("inets/src/http_lib/http_internal.hrl"). 24-include("httpc_internal.hrl"). 25 26%%% Internal API 27-export([send/3, is_idempotent/1, is_client_closing/1]). 28 29 30%%%========================================================================= 31%%% Internal application API 32%%%========================================================================= 33%%------------------------------------------------------------------------- 34%% send(MaybeProxy, Request) -> 35%% MaybeProxy - {Host, Port} 36%% Host = string() 37%% Port = integer() 38%% Request - #request{} 39%% Socket - socket() 40%% CookieSupport - enabled | disabled | verify 41%% 42%% Description: Composes and sends a HTTP-request. 43%%------------------------------------------------------------------------- 44send(SendAddr, #session{socket = Socket, socket_type = SocketType}, 45 #request{socket_opts = SocketOpts} = Request) 46 when is_list(SocketOpts) -> 47 case http_transport:setopts(SocketType, Socket, SocketOpts) of 48 ok -> 49 send(SendAddr, Socket, SocketType, 50 Request#request{socket_opts = undefined}); 51 {error, Reason} -> 52 {error, {setopts_failed, Reason}} 53 end; 54send(SendAddr, #session{socket = Socket, socket_type = SocketType}, Request) -> 55 send(SendAddr, Socket, SocketType, Request). 56 57send(SendAddr, Socket, SocketType, 58 #request{method = Method, 59 path = Path, 60 pquery = Query, 61 headers = Headers, 62 content = Content, 63 address = Address, 64 abs_uri = AbsUri, 65 headers_as_is = HeadersAsIs, 66 settings = HttpOptions, 67 userinfo = UserInfo}) -> 68 69 ?hcrt("send", 70 [{send_addr, SendAddr}, 71 {socket, Socket}, 72 {method, Method}, 73 {path, Path}, 74 {pquery, Query}, 75 {headers, Headers}, 76 {content, Content}, 77 {address, Address}, 78 {abs_uri, AbsUri}, 79 {headers_as_is, HeadersAsIs}, 80 {settings, HttpOptions}, 81 {userinfo, UserInfo}]), 82 83 TmpHdrs = handle_user_info(UserInfo, Headers), 84 85 {TmpHdrs2, Body} = post_data(Method, TmpHdrs, Content, HeadersAsIs), 86 87 {NewHeaders, Uri} = 88 case Address of 89 SendAddr -> 90 {TmpHdrs2, Path ++ Query}; 91 _Proxy when SocketType == ip_comm -> 92 TmpHdrs3 = handle_proxy(HttpOptions, TmpHdrs2), 93 {TmpHdrs3, AbsUri}; 94 _ -> 95 {TmpHdrs2, Path ++ Query} 96 end, 97 98 FinalHeaders = 99 case NewHeaders of 100 HeaderList when is_list(HeaderList) -> 101 http_headers(HeaderList, []); 102 _ -> 103 http_request:http_headers(NewHeaders) 104 end, 105 Version = HttpOptions#http_options.version, 106 107 do_send_body(SocketType, Socket, Method, Uri, Version, FinalHeaders, Body). 108 109 110do_send_body(SocketType, Socket, Method, Uri, Version, Headers, 111 {ProcessBody, Acc}) when is_function(ProcessBody, 1) -> 112 ?hcrt("send", [{acc, Acc}]), 113 case do_send_body(SocketType, Socket, Method, Uri, Version, Headers, []) of 114 ok -> 115 do_send_body(SocketType, Socket, ProcessBody, Acc); 116 Error -> 117 Error 118 end; 119 120do_send_body(SocketType, Socket, Method, Uri, Version, Headers, Body) -> 121 ?hcrt("create message", [{body, Body}]), 122 Message = [method(Method), " ", Uri, " ", 123 version(Version), ?CRLF, 124 headers(Headers, Version), ?CRLF, Body], 125 ?hcrd("send", [{message, Message}]), 126 http_transport:send(SocketType, Socket, Message). 127 128 129do_send_body(SocketType, Socket, ProcessBody, Acc) -> 130 case ProcessBody(Acc) of 131 eof -> 132 ok; 133 {ok, Data, NewAcc} -> 134 case http_transport:send(SocketType, Socket, Data) of 135 ok -> 136 do_send_body(SocketType, Socket, ProcessBody, NewAcc); 137 Error -> 138 Error 139 end 140 end. 141 142 143%%------------------------------------------------------------------------- 144%% is_idempotent(Method) -> 145%% Method = atom() 146%% 147%% Description: Checks if Method is considered idempotent. 148%%------------------------------------------------------------------------- 149 150%% In particular, the convention has been established that the GET and 151%% HEAD methods SHOULD NOT have the significance of taking an action 152%% other than retrieval. These methods ought to be considered "safe". 153is_idempotent(head) -> 154 true; 155is_idempotent(get) -> 156 true; 157%% Methods can also have the property of "idempotence" in that (aside 158%% from error or expiration issues) the side-effects of N > 0 159%% identical requests is the same as for a single request. 160is_idempotent(put) -> 161 true; 162is_idempotent(delete) -> 163 true; 164%% Also, the methods OPTIONS and TRACE SHOULD NOT have side effects, 165%% and so are inherently idempotent. 166is_idempotent(trace) -> 167 true; 168is_idempotent(options) -> 169 true; 170is_idempotent(_) -> 171 false. 172 173%%------------------------------------------------------------------------- 174%% is_client_closing(Headers) -> 175%% Headers = #http_request_h{} 176%% 177%% Description: Checks if the client has supplied a "Connection: 178%% close" header. 179%%------------------------------------------------------------------------- 180is_client_closing(Headers) -> 181 case Headers#http_request_h.connection of 182 "close" -> 183 true; 184 _ -> 185 false 186 end. 187 188%%%======================================================================== 189%%% Internal functions 190%%%======================================================================== 191post_data(Method, Headers, {ContentType, Body}, HeadersAsIs) 192 when (Method =:= post) 193 orelse (Method =:= put) 194 orelse (Method =:= patch) 195 orelse (Method =:= delete) -> 196 NewBody = update_body(Headers, Body), 197 NewHeaders = update_headers(Headers, ContentType, Body, HeadersAsIs), 198 {NewHeaders, NewBody}; 199 200post_data(_, Headers, _, []) -> 201 {Headers, ""}; 202post_data(_, _, _, HeadersAsIs = [_|_]) -> 203 {HeadersAsIs, ""}. 204 205update_body(Headers, Body) -> 206 case Headers#http_request_h.expect of 207 "100-continue" -> 208 ""; 209 _ -> 210 Body 211 end. 212 213update_headers(Headers, ContentType, Body, []) -> 214 case Body of 215 [] -> 216 Headers1 = Headers#http_request_h{'content-length' = "0"}, 217 handle_content_type(Headers1, ContentType); 218 <<>> -> 219 Headers1 = Headers#http_request_h{'content-length' = "0"}, 220 handle_content_type(Headers1, ContentType); 221 {Fun, _Acc} when is_function(Fun, 1) -> 222 %% A client MUST NOT generate a 100-continue expectation in a request 223 %% that does not include a message body. This implies that either the 224 %% Content-Length or the Transfer-Encoding header MUST be present. 225 %% DO NOT send content-type when Body is empty. 226 Headers1 = Headers#http_request_h{'content-type' = ContentType}, 227 handle_transfer_encoding(Headers1); 228 _ -> 229 Headers#http_request_h{ 230 'content-length' = body_length(Body), 231 'content-type' = ContentType} 232 end; 233update_headers(_, _, _, HeadersAsIs) -> 234 HeadersAsIs. 235 236handle_transfer_encoding(Headers = #http_request_h{'transfer-encoding' = undefined}) -> 237 Headers; 238handle_transfer_encoding(Headers) -> 239 %% RFC7230 3.3.2 240 %% A sender MUST NOT send a 'Content-Length' header field in any message 241 %% that contains a 'Transfer-Encoding' header field. 242 Headers#http_request_h{'content-length' = undefined}. 243 244body_length(Body) when is_binary(Body) -> 245 integer_to_list(size(Body)); 246 247body_length(Body) when is_list(Body) -> 248 integer_to_list(length(Body)). 249 250%% Set 'Content-Type' when it is explicitly set. 251handle_content_type(Headers, "") -> 252 Headers; 253handle_content_type(Headers, ContentType) -> 254 Headers#http_request_h{'content-type' = ContentType}. 255 256method(Method) -> 257 http_util:to_upper(atom_to_list(Method)). 258 259version("HTTP/0.9") -> 260 ""; 261version(Version) -> 262 Version. 263 264headers(_, "HTTP/0.9") -> 265 ""; 266%% HTTP 1.1 headers not present in HTTP 1.0 should be 267%% consider as unknown extension headers that should be 268%% ignored. 269headers(Headers, _) -> 270 Headers. 271 272 273http_headers([], Headers) -> 274 lists:flatten(Headers); 275http_headers([{Key,Value} | Rest], Headers) -> 276 Header = Key ++ ": " ++ Value ++ ?CRLF, 277 http_headers(Rest, [Header | Headers]). 278 279handle_proxy(_, Headers) when is_list(Headers) -> 280 Headers; %% Headers as is option was specified 281handle_proxy(HttpOptions, Headers) -> 282 case HttpOptions#http_options.proxy_auth of 283 undefined -> 284 Headers; 285 {User, Password} -> 286 UserPasswd = base64:encode_to_string(User ++ ":" ++ Password), 287 Headers#http_request_h{'proxy-authorization' = 288 "Basic " ++ UserPasswd} 289 end. 290 291handle_user_info([], Headers) -> 292 Headers; 293handle_user_info(UserInfo, Headers) -> 294 case string:tokens(UserInfo, ":") of 295 [User, Passwd] -> 296 UserPasswd = base64:encode_to_string(User ++ ":" ++ Passwd), 297 Headers#http_request_h{authorization = "Basic " ++ UserPasswd}; 298 [User] -> 299 UserPasswd = base64:encode_to_string(User ++ ":"), 300 Headers#http_request_h{authorization = "Basic " ++ UserPasswd}; 301 _ -> 302 Headers 303 end. 304