before it was storing some header in the body.
now we added redirections: .. which is a list of redirection informations:
- status line
- header
- and eventual redirection body (but at least by default, libcurl does not cache body)
Enhanced the http_client library to be able to write directly the downloaded data into a file (or as convenient thanks to agent).
321 lines
8.2 KiB
Plaintext
321 lines
8.2 KiB
Plaintext
note
|
|
description : "[
|
|
Response retrieved by the client
|
|
]"
|
|
date : "$Date$"
|
|
revision : "$Revision$"
|
|
|
|
class
|
|
HTTP_CLIENT_RESPONSE
|
|
|
|
create
|
|
make
|
|
|
|
feature {NONE} -- Initialization
|
|
|
|
make (a_url: like url)
|
|
-- Initialize `Current'.
|
|
do
|
|
--| Default values
|
|
status := 200
|
|
url := a_url
|
|
create {STRING_8} raw_header.make_empty
|
|
end
|
|
|
|
feature -- Status
|
|
|
|
error_occurred: BOOLEAN
|
|
-- Error occurred during request
|
|
|
|
error_message: detachable READABLE_STRING_8
|
|
|
|
feature {HTTP_CLIENT_REQUEST} -- Status setting
|
|
|
|
set_error_occurred (b: BOOLEAN)
|
|
-- Set `error_occurred' to `b'
|
|
do
|
|
error_occurred := b
|
|
end
|
|
|
|
set_error_message (m: READABLE_STRING_8)
|
|
-- Set `error_message' to `m'
|
|
do
|
|
set_error_occurred (True)
|
|
error_message := m
|
|
end
|
|
|
|
feature -- Access
|
|
|
|
url: STRING_8
|
|
-- URL associated with Current response
|
|
|
|
status: INTEGER assign set_status
|
|
-- Status code of the response.
|
|
|
|
status_line: detachable READABLE_STRING_8
|
|
|
|
raw_header: READABLE_STRING_8
|
|
-- Raw http header of the response.
|
|
|
|
redirections: detachable ARRAYED_LIST [TUPLE [status_line: detachable READABLE_STRING_8; raw_header: READABLE_STRING_8; body: detachable READABLE_STRING_8]]
|
|
-- Header of previous redirection if any.
|
|
|
|
header (a_name: READABLE_STRING_8): detachable READABLE_STRING_8
|
|
-- Header entry value related to `a_name'
|
|
-- if multiple entries, just concatenate them using comma character
|
|
--| See: http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html
|
|
--| Multiple message-header fields with the same field-name MAY be present in a message
|
|
--| if and only if the entire field-value for that header field is defined as a comma-separated list [i.e., #(values)].
|
|
--| It MUST be possible to combine the multiple header fields into one "field-name: field-value" pair,
|
|
--| without changing the semantics of the message, by appending each subsequent field-value to the first, each separated by a comma.
|
|
--| The order in which header fields with the same field-name are received is therefore significant
|
|
--| to the interpretation of the combined field value, and thus a proxy MUST NOT change the order of
|
|
--| these field values when a message is forwarded.
|
|
local
|
|
s: detachable STRING_8
|
|
k,v: READABLE_STRING_8
|
|
do
|
|
across
|
|
headers as hds
|
|
loop
|
|
k := hds.item.name
|
|
if k.same_string (a_name) then
|
|
v := hds.item.value
|
|
if s = Void then
|
|
create s.make_from_string (v)
|
|
else
|
|
s.append_character (',')
|
|
s.append (v)
|
|
end
|
|
end
|
|
end
|
|
Result := s
|
|
end
|
|
|
|
headers: LIST [TUPLE [name: READABLE_STRING_8; value: READABLE_STRING_8]]
|
|
-- Computed table of http headers of the response.
|
|
--| We use a LIST since one might have multiple message-header fields with the same field-name
|
|
--| Then the user can handle those case using default or custom concatenation
|
|
--| (note: `header' is concatenating using comma)
|
|
local
|
|
tb: like internal_headers
|
|
pos, l_start, l_end, n, c: INTEGER
|
|
h: like raw_header
|
|
k: STRING_8
|
|
do
|
|
tb := internal_headers
|
|
if tb = Void then
|
|
create tb.make (3)
|
|
h := raw_header
|
|
from
|
|
pos := 1
|
|
n := h.count
|
|
until
|
|
pos = 0 or pos > n
|
|
loop
|
|
l_start := pos
|
|
--| Left justify
|
|
from until not h[l_start].is_space loop
|
|
l_start := l_start + 1
|
|
end
|
|
pos := h.index_of ('%N', l_start)
|
|
if pos > 0 then
|
|
l_end := pos - 1
|
|
elseif l_start < n then
|
|
l_end := n + 1
|
|
else
|
|
-- Empty line
|
|
l_end := 0
|
|
end
|
|
if l_end > 0 then
|
|
--| Right justify
|
|
from until not h[l_end].is_space loop
|
|
l_end := l_end - 1
|
|
end
|
|
c := h.index_of (':', l_start)
|
|
if c > 0 then
|
|
k := h.substring (l_start, c - 1)
|
|
k.right_adjust
|
|
c := c + 1
|
|
from until c <= n and not h[c].is_space loop
|
|
c := c + 1
|
|
end
|
|
tb.force ([k, h.substring (c, l_end)])
|
|
else
|
|
check header_has_colon: c > 0 end
|
|
end
|
|
end
|
|
pos := pos + 1
|
|
end
|
|
internal_headers := tb
|
|
end
|
|
Result := tb
|
|
end
|
|
|
|
body: detachable READABLE_STRING_8 assign set_body
|
|
-- Content of the response
|
|
|
|
response_message_source (a_include_redirection: BOOLEAN): STRING_8
|
|
-- Full message source including redirection if any
|
|
do
|
|
create Result.make (1_024)
|
|
if
|
|
a_include_redirection and then
|
|
attached redirections as lst
|
|
then
|
|
across
|
|
lst as c
|
|
loop
|
|
if attached c.item.status_line as s then
|
|
Result.append (s)
|
|
Result.append ("%R%N")
|
|
end
|
|
Result.append (c.item.raw_header)
|
|
Result.append ("%R%N")
|
|
if attached c.item.body as l_body then
|
|
Result.append (l_body)
|
|
end
|
|
end
|
|
end
|
|
if attached status_line as s then
|
|
Result.append (s)
|
|
Result.append ("%R%N")
|
|
end
|
|
Result.append (raw_header)
|
|
Result.append ("%R%N")
|
|
if attached body as l_body then
|
|
Result.append (l_body)
|
|
end
|
|
end
|
|
|
|
feature -- Change
|
|
|
|
set_status (s: INTEGER)
|
|
-- Set response `status' code to `s'
|
|
do
|
|
status := s
|
|
end
|
|
|
|
set_response_message (a_source: READABLE_STRING_8; ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT)
|
|
-- Parse `a_source' response message
|
|
-- and set `header' and `body'.
|
|
--| ctx is the context associated with the request
|
|
--| it might be useful to deal with redirection customization...
|
|
local
|
|
i, j, pos: INTEGER
|
|
l_has_location: BOOLEAN
|
|
l_content_length: INTEGER
|
|
s: READABLE_STRING_8
|
|
l_status_line,h: detachable STRING_8
|
|
do
|
|
from
|
|
i := 1
|
|
j := 1
|
|
pos := 1
|
|
|
|
i := a_source.substring_index ("%R%N", i)
|
|
until
|
|
i = 0 or i > a_source.count
|
|
loop
|
|
s := a_source.substring (j, i - 1)
|
|
if s.starts_with ("HTTP/") then
|
|
--| Skip first line which is the status line
|
|
--| ex: HTTP/1.1 200 OK%R%N
|
|
j := i + 2
|
|
l_status_line := s
|
|
pos := j
|
|
elseif s.is_empty then
|
|
-- End of header %R%N%R%N
|
|
if attached raw_header as l_raw_header and then not l_raw_header.is_empty then
|
|
add_redirection (status_line, l_raw_header, body)
|
|
end
|
|
|
|
h := a_source.substring (pos, i - 1)
|
|
|
|
j := i + 2
|
|
pos := j
|
|
status_line := l_status_line
|
|
set_raw_header (h)
|
|
|
|
-- libcURL does not cache redirection content.
|
|
-- FIXME: check if this is customizable
|
|
-- if l_has_location then
|
|
-- if l_content_length > 0 then
|
|
-- j := pos + l_content_length - 1
|
|
-- l_body := a_source.substring (pos, j)
|
|
-- pos := j
|
|
-- else
|
|
-- l_body := Void
|
|
-- end
|
|
-- set_body (l_body)
|
|
-- end
|
|
if not l_has_location then
|
|
i := 0 -- exit loop
|
|
end
|
|
l_content_length := 0
|
|
l_status_line := Void
|
|
l_has_location := False
|
|
else
|
|
if s.starts_with ("Location:") then
|
|
l_has_location := True
|
|
elseif s.starts_with ("Content-Length:") then
|
|
s := s.substring (16, s.count)
|
|
if s.is_integer then
|
|
l_content_length := s.to_integer
|
|
end
|
|
end
|
|
j := i + 2
|
|
end
|
|
if i > 0 then
|
|
i := a_source.substring_index ("%R%N", j)
|
|
end
|
|
end
|
|
set_body (a_source.substring (pos, a_source.count))
|
|
ensure
|
|
parsed: response_message_source (True).count = a_source.count
|
|
end
|
|
|
|
set_raw_header (h: READABLE_STRING_8)
|
|
-- Set http header `raw_header' to `h'
|
|
do
|
|
raw_header := h
|
|
--| Reset internal headers
|
|
internal_headers := Void
|
|
end
|
|
|
|
add_redirection (s: detachable READABLE_STRING_8; h: READABLE_STRING_8; a_body: detachable READABLE_STRING_8)
|
|
-- Add redirection with status line `s' and raw header `h' and body `a_body' if any
|
|
local
|
|
lst: like redirections
|
|
do
|
|
lst := redirections
|
|
if lst = Void then
|
|
create lst.make (1)
|
|
redirections := lst
|
|
end
|
|
lst.force ([s,h, a_body])
|
|
end
|
|
|
|
set_body (s: like body)
|
|
-- Set `body' message to `s'
|
|
do
|
|
body := s
|
|
end
|
|
|
|
feature {NONE} -- Implementation
|
|
|
|
internal_headers: detachable ARRAYED_LIST [TUPLE [key: READABLE_STRING_8; value: READABLE_STRING_8]]
|
|
-- Internal cached value for the headers
|
|
|
|
;note
|
|
copyright: "2011-2012, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
|
|
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
|
|
source: "[
|
|
Eiffel Software
|
|
5949 Hollister Ave., Goleta, CA 93117 USA
|
|
Telephone 805-685-1006, Fax 805-685-6869
|
|
Website http://www.eiffel.com
|
|
Customer support http://support.eiffel.com
|
|
]"
|
|
end
|