Files
EWF/library/network/http_client/src/http_client_response.e
2015-10-19 08:46:29 +02:00

418 lines
10 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: READABLE_STRING_8)
-- 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
http_version: detachable READABLE_STRING_8
-- http version associated with `status_line'.
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.
redirections_count: INTEGER
-- Number of redirections.
do
if attached redirections as lst then
Result := lst.count
end
end
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
multiple_header (a_name: READABLE_STRING_8): detachable LIST [READABLE_STRING_8]
-- Header multiple entries related to `a_name'
local
k: READABLE_STRING_8
do
across
headers as hds
loop
k := hds.item.name
if k.same_string (a_name) then
if Result = Void then
create {ARRAYED_LIST [READABLE_STRING_8]} Result.make (1)
end
Result.force (hds.item.value)
end
end
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 -- Status report
is_http_1_0: BOOLEAN
-- Is response using HTTP/1.0 protocole?
--| Note: it is relevant once the raw header are set.
do
Result := attached http_version as v and then v.same_string ("1.0")
end
is_http_1_1: BOOLEAN
-- Is response using HTTP/1.1 protocole?
--| Note: it is relevant once the raw header are set.
do
Result := attached http_version as v and then v.same_string ("1.1")
end
feature -- Change
set_http_version (v: like http_version)
-- Set `http_version' to `v'.
do
http_version := v
end
set_status (s: INTEGER)
-- Set response `status' code to `s'
do
status := s
end
set_status_line (a_line: detachable READABLE_STRING_8)
-- Set status line to `a_line',
-- and also `status' extracted from `a_line' if possible.
local
i,j: INTEGER
s: READABLE_STRING_8
do
status_line := a_line
http_version := Void
if a_line /= Void then
if a_line.starts_with ("HTTP/") then
i := a_line.index_of (' ', 1)
if i > 0 then
http_version := a_line.substring (1 + 5, i - 1) -- ("HTTP/").count = 5
i := i + 1
end
else
i := 1
end
-- Get status code token.
if i > 0 then
j := a_line.index_of (' ', i)
if j > i then
s := a_line.substring (i, j - 1)
if s.is_integer then
set_status (s.to_integer)
end
end
end
end
end
set_response_message (a_source: READABLE_STRING_8; ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT)
-- Parse `a_source' response message
-- and set `status_line', `status', `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
set_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'
local
i: INTEGER
s: STRING_8
do
raw_header := h
--| Reset internal headers
internal_headers := Void
--| Set status line, right away.
i := h.index_of ('%N', 1)
if i > 0 then
s := h.substring (1, i - 1)
if s.starts_with ("HTTP/") then
s.right_adjust
set_status_line (s)
end
end
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-2015, 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