Added https support with Net implementation.
Added notion of default HTTP_CLIENT, to be able to build portable code among http client implementation.
This commit is contained in:
@@ -24,20 +24,60 @@
|
|||||||
<custom name="net_http_client_disabled" excluded_value="true"/>
|
<custom name="net_http_client_disabled" excluded_value="true"/>
|
||||||
</condition>
|
</condition>
|
||||||
</library>
|
</library>
|
||||||
|
<library name="net_ssl" location="$ISE_LIBRARY\unstable\library\network\socket\netssl\net_ssl-safe.ecf">
|
||||||
|
<condition>
|
||||||
|
<custom name="net_http_client_disabled" excluded_value="true"/>
|
||||||
|
<custom name="netssl_http_client_enabled" value="true"/>
|
||||||
|
</condition>
|
||||||
|
</library>
|
||||||
|
|
||||||
<library name="uri" location="$ISE_LIBRARY\library\text\uri\uri-safe.ecf"/>
|
<library name="uri" location="$ISE_LIBRARY\library\text\uri\uri-safe.ecf"/>
|
||||||
<cluster name="src" location=".\src\">
|
<cluster name="src" location=".\src\">
|
||||||
<cluster name="spec_null" location="$|spec/null" recursive="true"/>
|
<cluster name="spec_null" location="$|spec/null" recursive="true"/>
|
||||||
<cluster name="spec_net" location="$|spec/socket" recursive="true">
|
<cluster name="spec_net" location="$|spec/net">
|
||||||
<condition>
|
<condition>
|
||||||
<custom name="net_http_client_disabled" excluded_value="true"/>
|
<custom name="net_http_client_disabled" excluded_value="true"/>
|
||||||
</condition>
|
</condition>
|
||||||
|
<cluster name="net_ssl_disabled" location="$|no_ssl">
|
||||||
|
<condition>
|
||||||
|
<custom name="netssl_http_client_enabled" excluded_value="true"/>
|
||||||
|
</condition>
|
||||||
|
</cluster>
|
||||||
|
<cluster name="net_ssl_enabled" location="$|ssl">
|
||||||
|
<condition>
|
||||||
|
<custom name="netssl_http_client_enabled" value="true"/>
|
||||||
|
</condition>
|
||||||
|
</cluster>
|
||||||
</cluster>
|
</cluster>
|
||||||
<cluster name="spec_libcurl" location="$|spec/libcurl" recursive="true">
|
<cluster name="spec_libcurl" location="$|spec/libcurl" recursive="true">
|
||||||
<condition>
|
<condition>
|
||||||
<custom name="libcurl_http_client_disabled" excluded_value="true"/>
|
<custom name="libcurl_http_client_disabled" excluded_value="true"/>
|
||||||
</condition>
|
</condition>
|
||||||
</cluster>
|
</cluster>
|
||||||
|
<cluster name="default_null" location="$|default/null">
|
||||||
|
<condition>
|
||||||
|
<custom name="net_http_client_disabled" value="true"/>
|
||||||
|
<custom name="libcurl_http_client_disabled" value="true"/>
|
||||||
|
</condition>
|
||||||
|
</cluster>
|
||||||
|
<cluster name="default_net" location="$|default/net">
|
||||||
|
<condition>
|
||||||
|
<custom name="net_http_client_disabled" excluded_value="true"/>
|
||||||
|
<custom name="libcurl_http_client_disabled" value="true"/>
|
||||||
|
</condition>
|
||||||
|
</cluster>
|
||||||
|
<cluster name="default_libcurl" location="$|default/libcurl">
|
||||||
|
<condition>
|
||||||
|
<custom name="net_http_client_disabled" value="true"/>
|
||||||
|
<custom name="libcurl_http_client_disabled" excluded_value="true"/>
|
||||||
|
</condition>
|
||||||
|
</cluster>
|
||||||
|
<cluster name="default_libcurl_or_net" location="$|default/libcurl_or_net">
|
||||||
|
<condition>
|
||||||
|
<custom name="net_http_client_disabled" excluded_value="true"/>
|
||||||
|
<custom name="libcurl_http_client_disabled" excluded_value="true"/>
|
||||||
|
</condition>
|
||||||
|
</cluster>
|
||||||
</cluster>
|
</cluster>
|
||||||
</target>
|
</target>
|
||||||
</system>
|
</system>
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
note
|
||||||
|
description: "[
|
||||||
|
Default HTTP_CLIENT based on LIBCURL_HTTP_CLIENT.
|
||||||
|
]"
|
||||||
|
author: "$Author$"
|
||||||
|
date: "$Date$"
|
||||||
|
revision: "$Revision$"
|
||||||
|
|
||||||
|
class
|
||||||
|
DEFAULT_HTTP_CLIENT
|
||||||
|
|
||||||
|
inherit
|
||||||
|
LIBCURL_HTTP_CLIENT
|
||||||
|
|
||||||
|
end
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
note
|
||||||
|
description: "[
|
||||||
|
Default HTTP_CLIENT based on LIBCURL_HTTP_CLIENT.
|
||||||
|
]"
|
||||||
|
author: "$Author$"
|
||||||
|
date: "$Date$"
|
||||||
|
revision: "$Revision$"
|
||||||
|
|
||||||
|
class
|
||||||
|
DEFAULT_HTTP_CLIENT
|
||||||
|
|
||||||
|
inherit
|
||||||
|
HTTP_CLIENT
|
||||||
|
|
||||||
|
feature -- Access
|
||||||
|
|
||||||
|
new_session (a_base_url: READABLE_STRING_8): HTTP_CLIENT_SESSION
|
||||||
|
-- Create a new session using `a_base_url'.
|
||||||
|
local
|
||||||
|
libcurl: LIBCURL_HTTP_CLIENT
|
||||||
|
net: NET_HTTP_CLIENT
|
||||||
|
do
|
||||||
|
--| For now, try libcurl first, and then net
|
||||||
|
--| the reason is the net implementation is still in progress.
|
||||||
|
create libcurl
|
||||||
|
Result := libcurl.new_session (a_base_url)
|
||||||
|
if not Result.is_available then
|
||||||
|
create net
|
||||||
|
Result := net.new_session (a_base_url)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
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
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
note
|
||||||
|
description: "[
|
||||||
|
Default HTTP_CLIENT based on NET_HTTP_CLIENT.
|
||||||
|
]"
|
||||||
|
author: "$Author$"
|
||||||
|
date: "$Date$"
|
||||||
|
revision: "$Revision$"
|
||||||
|
|
||||||
|
class
|
||||||
|
DEFAULT_HTTP_CLIENT
|
||||||
|
|
||||||
|
inherit
|
||||||
|
NET_HTTP_CLIENT
|
||||||
|
|
||||||
|
end
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
note
|
||||||
|
description: "[
|
||||||
|
Default HTTP_CLIENT based on NULL_HTTP_CLIENT.
|
||||||
|
]"
|
||||||
|
author: "$Author$"
|
||||||
|
date: "$Date$"
|
||||||
|
revision: "$Revision$"
|
||||||
|
|
||||||
|
class
|
||||||
|
DEFAULT_HTTP_CLIENT
|
||||||
|
|
||||||
|
inherit
|
||||||
|
NULL_HTTP_CLIENT
|
||||||
|
|
||||||
|
end
|
||||||
@@ -9,7 +9,7 @@ note
|
|||||||
deferred class
|
deferred class
|
||||||
HTTP_CLIENT
|
HTTP_CLIENT
|
||||||
|
|
||||||
feature -- Status
|
feature -- Access
|
||||||
|
|
||||||
new_session (a_base_url: READABLE_STRING_8): HTTP_CLIENT_SESSION
|
new_session (a_base_url: READABLE_STRING_8): HTTP_CLIENT_SESSION
|
||||||
-- Create a new session using `a_base_url'.
|
-- Create a new session using `a_base_url'.
|
||||||
|
|||||||
@@ -95,6 +95,24 @@ feature -- Access
|
|||||||
Result := s
|
Result := s
|
||||||
end
|
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]]
|
headers: LIST [TUPLE [name: READABLE_STRING_8; value: READABLE_STRING_8]]
|
||||||
-- Computed table of http headers of the response.
|
-- 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
|
--| We use a LIST since one might have multiple message-header fields with the same field-name
|
||||||
|
|||||||
@@ -14,18 +14,6 @@ class
|
|||||||
inherit
|
inherit
|
||||||
HTTP_CLIENT
|
HTTP_CLIENT
|
||||||
|
|
||||||
create
|
|
||||||
default_create,
|
|
||||||
make
|
|
||||||
|
|
||||||
feature {NONE} -- Initialization
|
|
||||||
|
|
||||||
make
|
|
||||||
-- Initialize `Current'.
|
|
||||||
do
|
|
||||||
default_create
|
|
||||||
end
|
|
||||||
|
|
||||||
feature -- Status
|
feature -- Status
|
||||||
|
|
||||||
new_session (a_base_url: READABLE_STRING_8): NET_HTTP_CLIENT_SESSION
|
new_session (a_base_url: READABLE_STRING_8): NET_HTTP_CLIENT_SESSION
|
||||||
@@ -28,6 +28,16 @@ feature {NONE} -- Internal
|
|||||||
session: NET_HTTP_CLIENT_SESSION
|
session: NET_HTTP_CLIENT_SESSION
|
||||||
net_http_client_version: STRING = "0.1"
|
net_http_client_version: STRING = "0.1"
|
||||||
|
|
||||||
|
|
||||||
|
new_socket (a_host: READABLE_STRING_8; a_port: INTEGER; a_is_https: BOOLEAN; ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT): NETWORK_STREAM_SOCKET
|
||||||
|
do
|
||||||
|
if a_is_https then
|
||||||
|
create {SSL_NETWORK_STREAM_SOCKET} Result.make_client_by_port (a_port, a_host)
|
||||||
|
else
|
||||||
|
create Result.make_client_by_port (a_port, a_host)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
feature -- Access
|
feature -- Access
|
||||||
|
|
||||||
response: HTTP_CLIENT_RESPONSE
|
response: HTTP_CLIENT_RESPONSE
|
||||||
@@ -40,12 +50,13 @@ feature -- Access
|
|||||||
l_cookie: detachable READABLE_STRING_8
|
l_cookie: detachable READABLE_STRING_8
|
||||||
l_request_uri: STRING
|
l_request_uri: STRING
|
||||||
l_url: HTTP_URL
|
l_url: HTTP_URL
|
||||||
socket: NETWORK_STREAM_SOCKET
|
l_socket: NETWORK_STREAM_SOCKET
|
||||||
s: STRING
|
s: STRING
|
||||||
l_message: STRING
|
l_message: STRING
|
||||||
l_content_length: INTEGER
|
l_content_length: INTEGER
|
||||||
l_location: detachable READABLE_STRING_8
|
l_location: detachable READABLE_STRING_8
|
||||||
l_port: INTEGER
|
l_port: INTEGER
|
||||||
|
l_is_https: BOOLEAN
|
||||||
l_authorization: HTTP_AUTHORIZATION
|
l_authorization: HTTP_AUTHORIZATION
|
||||||
l_platform: STRING
|
l_platform: STRING
|
||||||
l_upload_data: detachable READABLE_STRING_8
|
l_upload_data: detachable READABLE_STRING_8
|
||||||
@@ -69,13 +80,13 @@ feature -- Access
|
|||||||
end
|
end
|
||||||
create Result.make (url)
|
create Result.make (url)
|
||||||
|
|
||||||
create l_form_string.make_empty
|
|
||||||
|
|
||||||
-- Get URL data
|
-- Get URL data
|
||||||
|
l_is_https := url.starts_with_general ("https://")
|
||||||
create l_uri.make_from_string (url)
|
create l_uri.make_from_string (url)
|
||||||
l_port := l_uri.port
|
l_port := l_uri.port
|
||||||
if l_port = 0 then
|
if l_port = 0 then
|
||||||
if url.starts_with_general ("https://") then
|
if l_is_https then
|
||||||
l_port := 443
|
l_port := 443
|
||||||
else
|
else
|
||||||
l_port := 80
|
l_port := 80
|
||||||
@@ -88,6 +99,15 @@ feature -- Access
|
|||||||
l_host := l_url.host
|
l_host := l_url.host
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Connect
|
||||||
|
l_socket := new_socket (l_host, l_port, l_is_https, ctx)
|
||||||
|
l_socket.set_connect_timeout (connect_timeout)
|
||||||
|
l_socket.set_timeout (timeout)
|
||||||
|
l_socket.connect
|
||||||
|
if l_socket.is_connected then
|
||||||
|
|
||||||
|
create l_form_string.make_empty
|
||||||
|
|
||||||
-- add headers for authorization
|
-- add headers for authorization
|
||||||
if not headers.has ("Authorization") then
|
if not headers.has ("Authorization") then
|
||||||
if
|
if
|
||||||
@@ -220,12 +240,6 @@ feature -- Access
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Connect
|
|
||||||
create socket.make_client_by_port (l_port, l_host)
|
|
||||||
socket.set_connect_timeout (connect_timeout)
|
|
||||||
socket.set_timeout (timeout)
|
|
||||||
socket.connect
|
|
||||||
if socket.is_connected then
|
|
||||||
-- FIXME: check usage of headers and specific header variable.
|
-- FIXME: check usage of headers and specific header variable.
|
||||||
--| only one Cookie: is allowed, so merge multiple into one;
|
--| only one Cookie: is allowed, so merge multiple into one;
|
||||||
--| if Host is in header, use that one.
|
--| if Host is in header, use that one.
|
||||||
@@ -295,20 +309,32 @@ feature -- Access
|
|||||||
--| Note that any remaining file to upload will be done directly via the socket
|
--| Note that any remaining file to upload will be done directly via the socket
|
||||||
--| to optimize memory usage
|
--| to optimize memory usage
|
||||||
|
|
||||||
--| Send request
|
|
||||||
if socket.ready_for_writing then
|
--|-----------------------------|--
|
||||||
socket.put_string (s)
|
--| Request preparation is done |--
|
||||||
|
--|-----------------------------|--
|
||||||
|
|
||||||
|
if l_socket.ready_for_writing then
|
||||||
|
--| Socket is ready for writing, so let's send the request.
|
||||||
|
|
||||||
|
--|-------------------------|--
|
||||||
|
--| Send request |--
|
||||||
|
--|-------------------------|--
|
||||||
|
|
||||||
|
l_socket.put_string (s)
|
||||||
--| Send remaining payload data, if needed.
|
--| Send remaining payload data, if needed.
|
||||||
if l_upload_file /= Void then
|
if l_upload_file /= Void then
|
||||||
-- i.e: not yet processed
|
-- i.e: not yet processed
|
||||||
append_file_content_to_socket (l_upload_file, l_upload_file.count, socket)
|
append_file_content_to_socket (l_upload_file, l_upload_file.count, l_socket)
|
||||||
end
|
end
|
||||||
|
|
||||||
--| Get response.
|
--|-------------------------|--
|
||||||
--| Get header message
|
--| Get response. |--
|
||||||
if socket.ready_for_reading then
|
--| Get header message |--
|
||||||
|
--|-------------------------|--
|
||||||
|
if l_socket.ready_for_reading then
|
||||||
create l_message.make_empty
|
create l_message.make_empty
|
||||||
append_socket_header_content_to (socket, l_message)
|
append_socket_header_content_to (l_socket, l_message)
|
||||||
l_prev_header := Result.raw_header
|
l_prev_header := Result.raw_header
|
||||||
Result.set_raw_header (l_message.string)
|
Result.set_raw_header (l_message.string)
|
||||||
l_content_length := -1
|
l_content_length := -1
|
||||||
@@ -316,13 +342,13 @@ feature -- Access
|
|||||||
l_content_length := s_len.to_integer
|
l_content_length := s_len.to_integer
|
||||||
end
|
end
|
||||||
l_location := Result.header ("Location")
|
l_location := Result.header ("Location")
|
||||||
if attached Result.header ("Set-Cookies") as s_cookies then
|
if attached Result.header ("Set-Cookie") as s_cookies then
|
||||||
session.set_cookie (s_cookies)
|
session.set_cookie (s_cookies)
|
||||||
end
|
end
|
||||||
l_message.append (http_end_of_header_line)
|
l_message.append (http_end_of_header_line)
|
||||||
|
|
||||||
-- Get content if any.
|
-- Get content if any.
|
||||||
append_socket_content_to (Result, socket, l_content_length, l_message)
|
append_socket_content_to (Result, l_socket, l_content_length, l_message)
|
||||||
-- Restore previous header
|
-- Restore previous header
|
||||||
Result.set_raw_header (l_prev_header)
|
Result.set_raw_header (l_prev_header)
|
||||||
-- Set message
|
-- Set message
|
||||||
@@ -455,33 +481,47 @@ feature {NONE} -- Helpers
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
append_socket_content_to (a_response: HTTP_CLIENT_RESPONSE; a_socket: NETWORK_STREAM_SOCKET; a_len: INTEGER; a_buffer: STRING)
|
append_socket_content_to (a_response: HTTP_CLIENT_RESPONSE; a_socket: NETWORK_STREAM_SOCKET; a_len: INTEGER; a_output: STRING)
|
||||||
-- Get content from `a_socket' and append it to `a_buffer'.
|
-- Get content from `a_socket' and append it to `a_output'.
|
||||||
-- If `a_len' is negative, try to get as much as possible,
|
-- If `a_len' is negative, try to get as much as possible,
|
||||||
-- this is probably HTTP/1.0 without any Content-Length.
|
-- this is probably HTTP/1.0 without any Content-Length.
|
||||||
local
|
local
|
||||||
s: STRING_8
|
s: STRING_8
|
||||||
|
r: INTEGER -- remaining count
|
||||||
n,pos, l_chunk_size, l_count: INTEGER
|
n,pos, l_chunk_size, l_count: INTEGER
|
||||||
hexa2int: HEXADECIMAL_STRING_TO_INTEGER_CONVERTER
|
hexa2int: HEXADECIMAL_STRING_TO_INTEGER_CONVERTER
|
||||||
do
|
do
|
||||||
if a_socket.readable then
|
if a_socket.readable then
|
||||||
if a_len >= 0 then
|
if a_len >= 0 then
|
||||||
|
debug ("socket_content")
|
||||||
|
io.error.put_string ("Content-Length="+ a_len.out +"%N")
|
||||||
|
end
|
||||||
from
|
from
|
||||||
n := a_len
|
r := a_len
|
||||||
until
|
until
|
||||||
n = 0 or else not a_socket.readable or else a_response.error_occurred
|
r = 0 or else not a_socket.readable or else a_response.error_occurred
|
||||||
loop
|
loop
|
||||||
if a_socket.ready_for_reading then
|
if a_socket.ready_for_reading then
|
||||||
a_socket.read_stream_thread_aware (n)
|
a_socket.read_stream_thread_aware (r)
|
||||||
l_count := l_count + a_socket.bytes_read
|
l_count := l_count + a_socket.bytes_read
|
||||||
n := n - a_socket.bytes_read
|
debug ("socket_content")
|
||||||
a_buffer.append (a_socket.last_string)
|
io.error.put_string (" - byte read=" + a_socket.bytes_read.out + "%N")
|
||||||
|
io.error.put_string (" - current count=" + l_count.out + "%N")
|
||||||
|
end
|
||||||
|
r := r - a_socket.bytes_read
|
||||||
|
a_output.append (a_socket.last_string)
|
||||||
else
|
else
|
||||||
|
debug ("socket_content")
|
||||||
|
io.error.put_string (" -! TIMEOUT%N")
|
||||||
|
end
|
||||||
a_response.set_error_message ("Could not read chunked data, timeout")
|
a_response.set_error_message ("Could not read chunked data, timeout")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
check full_content_read: l_count = a_len end
|
check full_content_read: not a_response.error_occurred implies l_count = a_len end
|
||||||
elseif attached a_response.header ("Transfer-Encoding") as l_enc and then l_enc.is_case_insensitive_equal ("chunked") then
|
elseif attached a_response.header ("Transfer-Encoding") as l_enc and then l_enc.is_case_insensitive_equal ("chunked") then
|
||||||
|
debug ("socket_content")
|
||||||
|
io.error.put_string ("Chunked encoding%N")
|
||||||
|
end
|
||||||
from
|
from
|
||||||
create hexa2int.make
|
create hexa2int.make
|
||||||
n := 1
|
n := 1
|
||||||
@@ -491,6 +531,9 @@ feature {NONE} -- Helpers
|
|||||||
a_socket.read_line_thread_aware -- Read chunk info
|
a_socket.read_line_thread_aware -- Read chunk info
|
||||||
s := a_socket.last_string
|
s := a_socket.last_string
|
||||||
s.right_adjust
|
s.right_adjust
|
||||||
|
debug ("socket_content")
|
||||||
|
io.error.put_string (" - chunk info='" + s + "'%N")
|
||||||
|
end
|
||||||
pos := s.index_of (';', 1)
|
pos := s.index_of (';', 1)
|
||||||
if pos > 0 then
|
if pos > 0 then
|
||||||
s.keep_head (pos - 1)
|
s.keep_head (pos - 1)
|
||||||
@@ -505,17 +548,28 @@ feature {NONE} -- Helpers
|
|||||||
n := 0
|
n := 0
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
debug ("socket_content")
|
||||||
|
io.error.put_string (" - chunk size=" + n.out + "%N")
|
||||||
|
end
|
||||||
if n > 0 then
|
if n > 0 then
|
||||||
from
|
from
|
||||||
|
r := n
|
||||||
until
|
until
|
||||||
n = 0 or else not a_socket.readable or else a_response.error_occurred
|
r = 0 or else not a_socket.readable or else a_response.error_occurred
|
||||||
loop
|
loop
|
||||||
if a_socket.ready_for_reading then
|
if a_socket.ready_for_reading then
|
||||||
a_socket.read_stream_thread_aware (n)
|
a_socket.read_stream_thread_aware (r)
|
||||||
l_count := l_count + a_socket.bytes_read
|
l_count := l_count + a_socket.bytes_read
|
||||||
n := n - a_socket.bytes_read
|
debug ("socket_content")
|
||||||
a_buffer.append (a_socket.last_string)
|
io.error.put_string (" - byte read=" + a_socket.bytes_read.out + "%N")
|
||||||
|
io.error.put_string (" - current count=" + l_count.out + "%N")
|
||||||
|
end
|
||||||
|
r := r - a_socket.bytes_read
|
||||||
|
a_output.append (a_socket.last_string)
|
||||||
else
|
else
|
||||||
|
debug ("socket_content")
|
||||||
|
io.error.put_string (" -! TIMEOUT%N")
|
||||||
|
end
|
||||||
a_response.set_error_message ("Could not read chunked data, timeout")
|
a_response.set_error_message ("Could not read chunked data, timeout")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -525,7 +579,13 @@ feature {NONE} -- Helpers
|
|||||||
check a_socket.last_character = '%R' end
|
check a_socket.last_character = '%R' end
|
||||||
a_socket.read_character
|
a_socket.read_character
|
||||||
check a_socket.last_character = '%N' end
|
check a_socket.last_character = '%N' end
|
||||||
|
debug ("socket_content")
|
||||||
|
io.error.put_string (" - Found CRNL %N")
|
||||||
|
end
|
||||||
else
|
else
|
||||||
|
debug ("socket_content")
|
||||||
|
io.error.put_string (" -! TIMEOUT%N")
|
||||||
|
end
|
||||||
a_response.set_error_message ("Could not read chunked data, timeout")
|
a_response.set_error_message ("Could not read chunked data, timeout")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -546,7 +606,7 @@ feature {NONE} -- Helpers
|
|||||||
s := a_socket.last_string
|
s := a_socket.last_string
|
||||||
n := a_socket.bytes_read
|
n := a_socket.bytes_read
|
||||||
l_count := l_count + n
|
l_count := l_count + n
|
||||||
a_buffer.append (s)
|
a_output.append (s)
|
||||||
else
|
else
|
||||||
a_response.set_error_message ("Could not read data, timeout")
|
a_response.set_error_message ("Could not read data, timeout")
|
||||||
end
|
end
|
||||||
@@ -11,6 +11,8 @@ class
|
|||||||
inherit
|
inherit
|
||||||
HTTP_CLIENT_SESSION
|
HTTP_CLIENT_SESSION
|
||||||
|
|
||||||
|
NET_HTTP_CLIENT_INFO
|
||||||
|
|
||||||
create
|
create
|
||||||
make
|
make
|
||||||
|
|
||||||
@@ -22,8 +24,14 @@ feature {NONE} -- Initialization
|
|||||||
|
|
||||||
feature -- Status report
|
feature -- Status report
|
||||||
|
|
||||||
is_available: BOOLEAN = True
|
is_available: BOOLEAN
|
||||||
-- Is interface usable?
|
-- Is interface usable?
|
||||||
|
do
|
||||||
|
Result := True
|
||||||
|
if base_url.starts_with_general ("https://") then
|
||||||
|
Result := has_https_support
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
feature -- Custom
|
feature -- Custom
|
||||||
|
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
note
|
||||||
|
description: "Additional information related to NET HTTP Client.."
|
||||||
|
date: "$Date$"
|
||||||
|
revision: "$Revision$"
|
||||||
|
|
||||||
|
class
|
||||||
|
NET_HTTP_CLIENT_INFO
|
||||||
|
|
||||||
|
feature -- Access
|
||||||
|
|
||||||
|
has_https_support: BOOLEAN = False
|
||||||
|
-- Is HTTPS supported?
|
||||||
|
|
||||||
|
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
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
note
|
||||||
|
description: "[
|
||||||
|
A fake SSL network stream socket... when SSL is disabled at compilation time.
|
||||||
|
Its behavior is similar to NETWORK_STREAM_SOCKET.
|
||||||
|
]"
|
||||||
|
date: "$Date$"
|
||||||
|
revision: "$Revision$"
|
||||||
|
|
||||||
|
class
|
||||||
|
SSL_NETWORK_STREAM_SOCKET
|
||||||
|
|
||||||
|
inherit
|
||||||
|
NETWORK_STREAM_SOCKET
|
||||||
|
|
||||||
|
create
|
||||||
|
make, make_empty, make_client_by_port, make_client_by_address_and_port, make_server_by_port, make_loopback_server_by_port
|
||||||
|
|
||||||
|
create {SSL_NETWORK_STREAM_SOCKET}
|
||||||
|
make_from_descriptor_and_address, create_from_descriptor
|
||||||
|
|
||||||
|
|
||||||
|
end
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
note
|
||||||
|
description: "Additional information related to NET HTTP Client.."
|
||||||
|
date: "$Date$"
|
||||||
|
revision: "$Revision$"
|
||||||
|
|
||||||
|
class
|
||||||
|
NET_HTTP_CLIENT_INFO
|
||||||
|
|
||||||
|
feature -- Access
|
||||||
|
|
||||||
|
has_https_support: BOOLEAN = True
|
||||||
|
-- Is HTTPS supported?
|
||||||
|
|
||||||
|
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
|
||||||
@@ -10,6 +10,8 @@
|
|||||||
<option warning="true" full_class_checking="true" is_attached_by_default="true" void_safety="all">
|
<option warning="true" full_class_checking="true" is_attached_by_default="true" void_safety="all">
|
||||||
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
|
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
|
||||||
</option>
|
</option>
|
||||||
|
<variable name="netssl_http_client_enabled" value="false"/>
|
||||||
|
<variable name="libcurl_http_client_disabled" value="false"/>
|
||||||
<library name="base" location="$ISE_LIBRARY\library\base\base-safe.ecf"/>
|
<library name="base" location="$ISE_LIBRARY\library\base\base-safe.ecf"/>
|
||||||
<library name="http_client" location="..\http_client-safe.ecf" readonly="false" use_application_options="true">
|
<library name="http_client" location="..\http_client-safe.ecf" readonly="false" use_application_options="true">
|
||||||
<option>
|
<option>
|
||||||
@@ -17,6 +19,19 @@
|
|||||||
</option>
|
</option>
|
||||||
</library>
|
</library>
|
||||||
<library name="testing" location="$ISE_LIBRARY\library\testing\testing-safe.ecf"/>
|
<library name="testing" location="$ISE_LIBRARY\library\testing\testing-safe.ecf"/>
|
||||||
<tests name="tests" location=".\"/>
|
<tests name="tests" location=".\">
|
||||||
|
<file_rule>
|
||||||
|
<exclude>.*libcurl_.*.e$</exclude>
|
||||||
|
<condition>
|
||||||
|
<custom name="libcurl_http_client_disabled" value="true"/>
|
||||||
|
</condition>
|
||||||
|
</file_rule>
|
||||||
|
<file_rule>
|
||||||
|
<exclude>.*net_.*.e$</exclude>
|
||||||
|
<condition>
|
||||||
|
<custom name="net_http_client_disabled" value="true"/>
|
||||||
|
</condition>
|
||||||
|
</file_rule>
|
||||||
|
</tests>
|
||||||
</target>
|
</target>
|
||||||
</system>
|
</system>
|
||||||
|
|||||||
@@ -13,16 +13,40 @@ feature -- Init
|
|||||||
if attached null.new_session ("http://example.com/") as l_sess then
|
if attached null.new_session ("http://example.com/") as l_sess then
|
||||||
check not l_sess.is_available end
|
check not l_sess.is_available end
|
||||||
end
|
end
|
||||||
|
test_get_with_authentication
|
||||||
test_http_client
|
test_http_client
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test_get_with_authentication
|
||||||
|
local
|
||||||
|
cl: DEFAULT_HTTP_CLIENT
|
||||||
|
sess: HTTP_CLIENT_SESSION
|
||||||
|
ctx: HTTP_CLIENT_REQUEST_CONTEXT
|
||||||
|
do
|
||||||
|
-- GET REQUEST WITH AUTHENTICATION, see http://browserspy.dk/password.php
|
||||||
|
-- check header WWW-Authenticate is received (authentication successful)
|
||||||
|
create cl
|
||||||
|
sess := cl.new_session ("http://browserspy.dk")
|
||||||
|
sess.set_credentials ("test", "test")
|
||||||
|
create ctx.make_with_credentials_required
|
||||||
|
if attached sess.get ("/password-ok.php", ctx) as res then
|
||||||
|
if attached {READABLE_STRING_8} res.body as l_body then
|
||||||
|
assert ("Fetch all body, including closing html tag", l_body.has_substring ("</html>"))
|
||||||
|
else
|
||||||
|
assert ("has body", False)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
test_http_client
|
test_http_client
|
||||||
-- New test routine
|
-- New test routine
|
||||||
local
|
local
|
||||||
sess: LIBCURL_HTTP_CLIENT_SESSION
|
cl: DEFAULT_HTTP_CLIENT
|
||||||
|
sess: HTTP_CLIENT_SESSION
|
||||||
h: STRING_8
|
h: STRING_8
|
||||||
do
|
do
|
||||||
create sess.make ("http://www.google.com")
|
create cl
|
||||||
|
sess := cl.new_session ("http://www.google.com")
|
||||||
if attached sess.get ("/search?q=eiffel", Void) as res then
|
if attached sess.get ("/search?q=eiffel", Void) as res then
|
||||||
assert ("Get returned without error", not res.error_occurred)
|
assert ("Get returned without error", not res.error_occurred)
|
||||||
create h.make_empty
|
create h.make_empty
|
||||||
|
|||||||
@@ -47,21 +47,30 @@ feature -- Test routines
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
test_http_client_requestbin
|
test_http_client_ssl
|
||||||
|
-- New test routine
|
||||||
local
|
local
|
||||||
sess: like new_session
|
sess: like new_session
|
||||||
h: STRING_8
|
h: STRING_8
|
||||||
do
|
do
|
||||||
sess := new_session ("http://requestb.in")
|
sess := new_session ("https://www.eiffel.org")
|
||||||
|
if attached sess.get ("/welcome", Void) as res then
|
||||||
|
assert ("Get returned without error", not res.error_occurred)
|
||||||
create h.make_empty
|
create h.make_empty
|
||||||
if attached sess.get ("/1a0q2h61", Void).headers as hds then
|
if attached res.headers as hds then
|
||||||
across
|
across
|
||||||
hds as c
|
hds as c
|
||||||
loop
|
loop
|
||||||
h.append (c.item.name + ": " + c.item.value + "%R%N")
|
h.append (c.item.name + ": " + c.item.value + "%R%N")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
print (h)
|
if attached res.body as l_body then
|
||||||
|
assert ("body not empty", not l_body.is_empty)
|
||||||
|
else
|
||||||
|
assert ("missing body", False)
|
||||||
|
end
|
||||||
|
assert ("same headers", h.same_string (res.raw_header))
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
test_headers
|
test_headers
|
||||||
|
|||||||
@@ -27,9 +27,9 @@ feature -- Tests
|
|||||||
test_http_client
|
test_http_client
|
||||||
end
|
end
|
||||||
|
|
||||||
test_libcurl_http_client_requestbin
|
test_libcurl_http_client_ssl
|
||||||
do
|
do
|
||||||
test_http_client_requestbin
|
test_http_client_ssl
|
||||||
end
|
end
|
||||||
|
|
||||||
test_libcurl_headers
|
test_libcurl_headers
|
||||||
|
|||||||
@@ -27,9 +27,9 @@ feature -- Tests
|
|||||||
test_http_client
|
test_http_client
|
||||||
end
|
end
|
||||||
|
|
||||||
test_net_http_client_requestbin
|
test_net_http_client_ssl
|
||||||
do
|
do
|
||||||
test_http_client_requestbin
|
test_http_client_ssl
|
||||||
end
|
end
|
||||||
|
|
||||||
test_net_headers
|
test_net_headers
|
||||||
|
|||||||
Reference in New Issue
Block a user