aboutsummaryrefslogtreecommitdiffstats
path: root/lib/inets/src/http_client/httpc_response.erl
diff options
context:
space:
mode:
authorErlang/OTP <[email protected]>2009-11-20 14:54:40 +0000
committerErlang/OTP <[email protected]>2009-11-20 14:54:40 +0000
commit84adefa331c4159d432d22840663c38f155cd4c1 (patch)
treebff9a9c66adda4df2106dfd0e5c053ab182a12bd /lib/inets/src/http_client/httpc_response.erl
downloadotp-84adefa331c4159d432d22840663c38f155cd4c1.tar.gz
otp-84adefa331c4159d432d22840663c38f155cd4c1.tar.bz2
otp-84adefa331c4159d432d22840663c38f155cd4c1.zip
The R13B03 release.OTP_R13B03
Diffstat (limited to 'lib/inets/src/http_client/httpc_response.erl')
-rw-r--r--lib/inets/src/http_client/httpc_response.erl431
1 files changed, 431 insertions, 0 deletions
diff --git a/lib/inets/src/http_client/httpc_response.erl b/lib/inets/src/http_client/httpc_response.erl
new file mode 100644
index 0000000000..e2ba66f730
--- /dev/null
+++ b/lib/inets/src/http_client/httpc_response.erl
@@ -0,0 +1,431 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2004-2009. 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(httpc_response).
+
+-include("http_internal.hrl").
+-include("httpc_internal.hrl").
+
+%% API
+-export([parse/1, result/2, send/2, error/2, is_server_closing/1,
+ stream_start/3]).
+
+%% Callback API - used for example if the header/body is received a
+%% little at a time on a socket.
+-export([parse_version/1, parse_status_code/1, parse_reason_phrase/1,
+ parse_headers/1, whole_body/1, whole_body/2]).
+
+%%%=========================================================================
+%%% API
+%%%=========================================================================
+
+parse([Bin, MaxHeaderSize, Relaxed]) ->
+ parse_version(Bin, [], MaxHeaderSize, [], Relaxed).
+
+whole_body([Bin, Body, Length]) ->
+ whole_body(<<Body/binary, Bin/binary>>, Length).
+
+%% Functions that may be returned during the decoding process
+%% if the input data is incompleate.
+parse_version([Bin, Version, MaxHeaderSize, Result, Relaxed]) ->
+ parse_version(Bin, Version, MaxHeaderSize, Result, Relaxed).
+
+parse_status_code([Bin, Code, MaxHeaderSize, Result, Relaxed]) ->
+ parse_status_code(Bin, Code, MaxHeaderSize, Result, Relaxed).
+
+parse_reason_phrase([Bin, Rest, Phrase, MaxHeaderSize, Result, Relaxed]) ->
+ parse_reason_phrase(<<Rest/binary, Bin/binary>>, Phrase,
+ MaxHeaderSize, Result, Relaxed).
+
+parse_headers([Bin, Rest,Header, Headers, MaxHeaderSize, Result, Relaxed]) ->
+ parse_headers(<<Rest/binary, Bin/binary>>, Header, Headers,
+ MaxHeaderSize, Result, Relaxed).
+
+whole_body(Body, Length) ->
+ case size(Body) of
+ N when (N < Length) andalso (N > 0) ->
+ {?MODULE, whole_body, [Body, Length]};
+ %% OBS! The Server may close the connection to indicate that the
+ %% whole body is now sent instead of sending a lengh
+ %% indicator.In this case the lengh indicator will be
+ %% -1.
+ N when (N >= Length) andalso (Length >= 0) ->
+ %% Potential trailing garbage will be thrown away in
+ %% format_response/1 Some servers may send a 100-continue
+ %% response without the client requesting it through an
+ %% expect header in this case the trailing bytes may be
+ %% part of the real response message.
+ {ok, Body};
+ _ -> %% Length == -1
+ {?MODULE, whole_body, [Body, Length]}
+ end.
+
+%%-------------------------------------------------------------------------
+%% result(Response, Request) ->
+%% Response - {StatusLine, Headers, Body}
+%% Request - #request{}
+%% Session - #tcp_session{}
+%%
+%% Description: Checks the status code ...
+%%-------------------------------------------------------------------------
+result(Response = {{_, Code,_}, _, _},
+ Request = #request{stream = Stream})
+ when ((Code =:= 200) orelse (Code =:= 206)) andalso (Stream =/= none) ->
+ stream_end(Response, Request);
+
+result(Response = {{_,100,_}, _, _}, Request) ->
+ status_continue(Response, Request);
+
+%% In redirect loop
+result(Response = {{_, Code, _}, _, _}, Request =
+ #request{redircount = Redirects,
+ settings = #http_options{autoredirect = true}})
+ when ((Code div 100) =:= 3) andalso (Redirects > ?HTTP_MAX_REDIRECTS) ->
+ transparent(Response, Request);
+
+%% multiple choices
+result(Response = {{_, 300, _}, _, _},
+ Request = #request{settings =
+ #http_options{autoredirect =
+ true}}) ->
+ redirect(Response, Request);
+
+result(Response = {{_, Code, _}, _, _},
+ Request = #request{settings =
+ #http_options{autoredirect = true},
+ method = head}) when (Code =:= 301) orelse
+ (Code =:= 302) orelse
+ (Code =:= 303) orelse
+ (Code =:= 307) ->
+ redirect(Response, Request);
+result(Response = {{_, Code, _}, _, _},
+ Request = #request{settings =
+ #http_options{autoredirect = true},
+ method = get}) when (Code =:= 301) orelse
+ (Code =:= 302) orelse
+ (Code =:= 303) orelse
+ (Code =:= 307) ->
+ redirect(Response, Request);
+
+
+result(Response = {{_,503,_}, _, _}, Request) ->
+ status_service_unavailable(Response, Request);
+result(Response = {{_,Code,_}, _, _}, Request) when (Code div 100) =:= 5 ->
+ status_server_error_50x(Response, Request);
+
+result(Response, Request) ->
+ transparent(Response, Request).
+
+send(To, Msg) ->
+ To ! {http, Msg}.
+
+%%%========================================================================
+%%% Internal functions
+%%%========================================================================
+parse_version(<<>>, Version, MaxHeaderSize, Result, Relaxed) ->
+ {?MODULE, parse_version, [Version, MaxHeaderSize,Result, Relaxed]};
+parse_version(<<?SP, Rest/binary>>, Version,
+ MaxHeaderSize, Result, Relaxed) ->
+ case lists:reverse(Version) of
+ "HTTP/" ++ _ = Newversion ->
+ parse_status_code(Rest, [], MaxHeaderSize,
+ [Newversion | Result], Relaxed);
+ NewVersion ->
+ throw({error, {invalid_version, NewVersion}})
+ end;
+
+parse_version(<<Octet, Rest/binary>>, Version,
+ MaxHeaderSize, Result, Relaxed) ->
+ parse_version(Rest, [Octet | Version], MaxHeaderSize,Result, Relaxed).
+
+parse_status_code(<<>>, StatusCodeStr, MaxHeaderSize, Result, Relaxed) ->
+ {?MODULE, parse_status_code,
+ [StatusCodeStr, MaxHeaderSize, Result, Relaxed]};
+
+%% Some Apache servers has been known to leave out the reason phrase,
+%% in relaxed mode we will allow this.
+parse_status_code(<<?CR>> = Data, StatusCodeStr,
+ MaxHeaderSize, Result, true) ->
+ {?MODULE, parse_status_code,
+ [Data, StatusCodeStr, MaxHeaderSize, Result, true]};
+parse_status_code(<<?LF>>, StatusCodeStr,
+ MaxHeaderSize, Result, true) ->
+ %% If ?CR is is missing RFC2616 section-19.3
+ parse_status_code(<<?CR, ?LF>>, StatusCodeStr,
+ MaxHeaderSize, Result, true);
+
+parse_status_code(<<?CR, ?LF, Rest/binary>>, StatusCodeStr,
+ MaxHeaderSize, Result, true) ->
+ parse_headers(Rest, [], [], MaxHeaderSize,
+ [" ", list_to_integer(lists:reverse(
+ string:strip(StatusCodeStr)))
+ | Result], true);
+
+parse_status_code(<<?SP, Rest/binary>>, StatusCodeStr,
+ MaxHeaderSize, Result, Relaxed) ->
+ parse_reason_phrase(Rest, [], MaxHeaderSize,
+ [list_to_integer(lists:reverse(StatusCodeStr)) |
+ Result], Relaxed);
+
+parse_status_code(<<Octet, Rest/binary>>, StatusCodeStr,
+ MaxHeaderSize,Result, Relaxed) ->
+ parse_status_code(Rest, [Octet | StatusCodeStr], MaxHeaderSize, Result,
+ Relaxed).
+
+parse_reason_phrase(<<>>, Phrase, MaxHeaderSize, Result, Relaxed) ->
+ {?MODULE, parse_reason_phrase,
+ [<<>>, Phrase, MaxHeaderSize, Result, Relaxed]};
+
+parse_reason_phrase(<<?CR, ?LF, ?LF, Body/binary>>, Phrase,
+ MaxHeaderSize, Result, Relaxed) ->
+ %% If ?CR is is missing RFC2616 section-19.3
+ parse_reason_phrase(<<?CR, ?LF, ?CR, ?LF, Body/binary>>, Phrase,
+ MaxHeaderSize, Result, Relaxed);
+
+parse_reason_phrase(<<?CR, ?LF, ?CR, ?LF, Body/binary>>, Phrase,
+ _, Result, _) ->
+ ResponseHeaderRcord =
+ http_response:headers([], #http_response_h{}),
+ {ok, list_to_tuple(
+ lists:reverse([Body, ResponseHeaderRcord |
+ [lists:reverse(Phrase) | Result]]))};
+
+parse_reason_phrase(<<?CR, ?LF, ?CR>> = Data, Phrase, MaxHeaderSize, Result,
+ Relaxed) ->
+ {?MODULE, parse_reason_phrase, [Data, Phrase, MaxHeaderSize, Result],
+ Relaxed};
+
+parse_reason_phrase(<<?CR, ?LF>> = Data, Phrase, MaxHeaderSize, Result,
+ Relaxed) ->
+ {?MODULE, parse_reason_phrase, [Data, Phrase, MaxHeaderSize, Result,
+ Relaxed]};
+parse_reason_phrase(<<?LF, Rest/binary>>, Phrase,
+ MaxHeaderSize, Result, Relaxed) ->
+ %% If ?CR is is missing RFC2616 section-19.3
+ parse_reason_phrase(<<?CR, ?LF, Rest/binary>>, Phrase,
+ MaxHeaderSize, Result, Relaxed);
+parse_reason_phrase(<<?CR, ?LF, Rest/binary>>, Phrase,
+ MaxHeaderSize, Result, Relaxed) ->
+ parse_headers(Rest, [], [], MaxHeaderSize,
+ [lists:reverse(Phrase) | Result], Relaxed);
+parse_reason_phrase(<<?LF>>, Phrase, MaxHeaderSize, Result, Relaxed) ->
+ %% If ?CR is is missing RFC2616 section-19.3
+ parse_reason_phrase(<<?CR, ?LF>>, Phrase, MaxHeaderSize, Result,
+ Relaxed);
+parse_reason_phrase(<<?CR>> = Data, Phrase, MaxHeaderSize, Result, Relaxed) ->
+ {?MODULE, parse_reason_phrase,
+ [Data, Phrase, MaxHeaderSize, Result, Relaxed]};
+parse_reason_phrase(<<Octet, Rest/binary>>, Phrase, MaxHeaderSize, Result,
+ Relaxed) ->
+ parse_reason_phrase(Rest, [Octet | Phrase], MaxHeaderSize,
+ Result, Relaxed).
+
+parse_headers(<<>>, Header, Headers, MaxHeaderSize, Result, Relaxed) ->
+ {?MODULE, parse_headers, [<<>>, Header, Headers, MaxHeaderSize, Result,
+ Relaxed]};
+
+parse_headers(<<?CR,?LF,?LF,Body/binary>>, Header, Headers,
+ MaxHeaderSize, Result, Relaxed) ->
+ %% If ?CR is is missing RFC2616 section-19.3
+ parse_headers(<<?CR,?LF,?CR,?LF,Body/binary>>, Header, Headers,
+ MaxHeaderSize, Result, Relaxed);
+
+parse_headers(<<?LF,?LF,Body/binary>>, Header, Headers,
+ MaxHeaderSize, Result, Relaxed) ->
+ %% If ?CR is is missing RFC2616 section-19.3
+ parse_headers(<<?CR,?LF,?CR,?LF,Body/binary>>, Header, Headers,
+ MaxHeaderSize, Result, Relaxed);
+
+parse_headers(<<?CR,?LF,?CR,?LF,Body/binary>>, Header, Headers,
+ MaxHeaderSize, Result, _) ->
+ HTTPHeaders = [lists:reverse(Header) | Headers],
+ Length = lists:foldl(fun(H, Acc) -> length(H) + Acc end,
+ 0, HTTPHeaders),
+ case ((Length =< MaxHeaderSize) or (MaxHeaderSize == nolimit)) of
+ true ->
+ ResponseHeaderRcord =
+ http_response:headers(HTTPHeaders, #http_response_h{}),
+ {ok, list_to_tuple(
+ lists:reverse([Body, ResponseHeaderRcord | Result]))};
+ false ->
+ throw({error, {header_too_long, MaxHeaderSize,
+ MaxHeaderSize-Length}})
+ end;
+parse_headers(<<?CR,?LF,?CR>> = Data, Header, Headers,
+ MaxHeaderSize, Result, Relaxed) ->
+ {?MODULE, parse_headers, [Data, Header, Headers,
+ MaxHeaderSize, Result, Relaxed]};
+parse_headers(<<?CR,?LF>> = Data, Header, Headers,
+ MaxHeaderSize, Result, Relaxed) ->
+ {?MODULE, parse_headers, [Data, Header, Headers, MaxHeaderSize,
+ Result, Relaxed]};
+parse_headers(<<?CR,?LF, Octet, Rest/binary>>, Header, Headers,
+ MaxHeaderSize, Result, Relaxed) ->
+ parse_headers(Rest, [Octet],
+ [lists:reverse(Header) | Headers], MaxHeaderSize,
+ Result, Relaxed);
+parse_headers(<<?CR>> = Data, Header, Headers,
+ MaxHeaderSize, Result, Relaxed) ->
+ {?MODULE, parse_headers, [Data, Header, Headers, MaxHeaderSize,
+ Result, Relaxed]};
+
+parse_headers(<<?LF>>, Header, Headers,
+ MaxHeaderSize, Result, Relaxed) ->
+ %% If ?CR is is missing RFC2616 section-19.3
+ parse_headers(<<?CR, ?LF>>, Header, Headers,
+ MaxHeaderSize, Result, Relaxed);
+
+parse_headers(<<Octet, Rest/binary>>, Header, Headers,
+ MaxHeaderSize, Result, Relaxed) ->
+ parse_headers(Rest, [Octet | Header], Headers, MaxHeaderSize,
+ Result, Relaxed).
+
+
+%% RFC2616, Section 10.1.1
+%% Note:
+%% - Only act on the 100 status if the request included the
+%% "Expect:100-continue" header, otherwise just ignore this response.
+status_continue(_, #request{headers =
+ #http_request_h{expect = "100-continue"}}) ->
+ continue;
+
+status_continue({_,_, Data}, _) ->
+ %% The data in the body in this case is actually part of the real
+ %% response sent after the "fake" 100-continue.
+ {ignore, Data}.
+
+status_service_unavailable(Response = {_, Headers, _}, Request) ->
+ case Headers#http_response_h.'retry-after' of
+ undefined ->
+ status_server_error_50x(Response, Request);
+ Time when (length(Time) < 3) -> % Wait only 99 s or less
+ NewTime = list_to_integer(Time) * 100, % time in ms
+ {_, Data} = format_response(Response),
+ {retry, {NewTime, Request}, Data};
+ _ ->
+ status_server_error_50x(Response, Request)
+ end.
+
+status_server_error_50x(Response, Request) ->
+ {Msg, _} = format_response(Response),
+ {stop, {Request#request.id, Msg}}.
+
+
+redirect(Response = {StatusLine, Headers, Body}, Request) ->
+ {_, Data} = format_response(Response),
+ case Headers#http_response_h.location of
+ undefined ->
+ transparent(Response, Request);
+ RedirUrl ->
+ case http_uri:parse(RedirUrl) of
+ {error, no_scheme} when
+ (Request#request.settings)#http_options.relaxed ->
+ NewLocation = fix_relative_uri(Request, RedirUrl),
+ redirect({StatusLine, Headers#http_response_h{
+ location = NewLocation},
+ Body}, Request);
+ {error, Reason} ->
+ {ok, error(Request, Reason), Data};
+ %% Automatic redirection
+ {Scheme, _, Host, Port, Path, Query} ->
+ NewHeaders =
+ (Request#request.headers)#http_request_h{host =
+ Host},
+ NewRequest =
+ Request#request{redircount =
+ Request#request.redircount+1,
+ scheme = Scheme,
+ headers = NewHeaders,
+ address = {Host,Port},
+ path = Path,
+ pquery = Query,
+ abs_uri =
+ atom_to_list(Scheme) ++ "://" ++
+ Host ++ ":" ++
+ integer_to_list(Port) ++
+ Path ++ Query},
+ {redirect, NewRequest, Data}
+ end
+ end.
+
+maybe_to_list(Port) when is_integer(Port) ->
+ integer_to_list(Port);
+maybe_to_list(Port) when is_list(Port) ->
+ Port.
+
+%%% Guessing that we received a relative URI, fix it to become an absoluteURI
+fix_relative_uri(Request, RedirUrl) ->
+ {Server, Port0} = Request#request.address,
+ Port = maybe_to_list(Port0),
+ Path = Request#request.path,
+ atom_to_list(Request#request.scheme) ++ "://" ++ Server ++ ":" ++ Port
+ ++ Path ++ RedirUrl.
+
+error(#request{id = Id}, Reason) ->
+ {Id, {error, Reason}}.
+
+transparent(Response, Request) ->
+ {Msg, Data} = format_response(Response),
+ {ok, {Request#request.id, Msg}, Data}.
+
+stream_start(Headers, Request, ignore) ->
+ {Request#request.id, stream_start, http_response:header_list(Headers)};
+
+stream_start(Headers, Request, Pid) ->
+ {Request#request.id, stream_start,
+ http_response:header_list(Headers), Pid}.
+
+stream_end(Response, Request = #request{stream = Self})
+ when (Self =:= self) orelse (Self =:= {self, once}) ->
+ {{_, Headers, _}, Data} = format_response(Response),
+ {ok, {Request#request.id, stream_end, Headers}, Data};
+
+stream_end(Response, Request) ->
+ {_, Data} = format_response(Response),
+ {ok, {Request#request.id, saved_to_file}, Data}.
+
+is_server_closing(Headers) when is_record(Headers, http_response_h) ->
+ case Headers#http_response_h.connection of
+ "close" ->
+ true;
+ _ ->
+ false
+ end.
+
+format_response({{"HTTP/0.9", _, _} = StatusLine, _, Body}) ->
+ {{StatusLine, [], Body}, <<>>};
+format_response({StatusLine, Headers, Body = <<>>}) ->
+ {{StatusLine, http_response:header_list(Headers), Body}, <<>>};
+
+format_response({StatusLine, Headers, Body}) ->
+ Length = list_to_integer(Headers#http_response_h.'content-length'),
+ {NewBody, Data} =
+ case Length of
+ 0 ->
+ {Body, <<>>};
+ -1 -> % When no lenght indicator is provided
+ {Body, <<>>};
+ Length when (Length =< size(Body)) ->
+ <<BodyThisReq:Length/binary, Next/binary>> = Body,
+ {BodyThisReq, Next};
+ _ -> %% Connection prematurely ended.
+ {Body, <<>>}
+ end,
+ {{StatusLine, http_response:header_list(Headers), NewBody}, Data}.
+