Added null http client for upcoming changes.
Refactored NET request implementation. - fixed potential issue with header conflict. - simplified, and extract parts of the code into routine. - Implemented read of chunked Transfer-Encoding - Fixed potential issue with socket handling. First steps to be able to exclude net or libcurl implementation when using http_client lib. Removed from NET implementation the hack related to PUT and upload data (it was used to workaround an issue with libcurl).
This commit is contained in:
@@ -10,12 +10,34 @@
|
||||
<option warning="true" full_class_checking="true" is_attached_by_default="true" void_safety="all" syntax="provisional">
|
||||
</option>
|
||||
<library name="base" location="$ISE_LIBRARY\library\base\base-safe.ecf"/>
|
||||
<library name="curl" location="$ISE_LIBRARY\library\cURL\cURL-safe.ecf"/>
|
||||
<library name="curl" location="$ISE_LIBRARY\library\cURL\cURL-safe.ecf">
|
||||
<condition>
|
||||
<custom name="libcurl_http_client_disabled" excluded_value="true"/>
|
||||
</condition>
|
||||
</library>
|
||||
|
||||
<library name="encoder" location="..\..\text\encoder\encoder-safe.ecf"/>
|
||||
<library name="http" location="..\protocol\http\http-safe.ecf"/>
|
||||
<library name="http_auth" location="..\..\server\authentication\http_authorization\http_authorization-safe.ecf"/>
|
||||
<library name="net" location="$ISE_LIBRARY\library\net\net-safe.ecf"/>
|
||||
<library name="net" location="$ISE_LIBRARY\library\net\net-safe.ecf">
|
||||
<condition>
|
||||
<custom name="net_http_client_disabled" excluded_value="true"/>
|
||||
</condition>
|
||||
</library>
|
||||
|
||||
<library name="uri" location="$ISE_LIBRARY\library\text\uri\uri-safe.ecf"/>
|
||||
<cluster name="src" location=".\src\" recursive="true"/>
|
||||
<cluster name="src" location=".\src\">
|
||||
<cluster name="spec_null" location="$|spec/null" recursive="true"/>
|
||||
<cluster name="spec_net" location="$|spec/socket" recursive="true">
|
||||
<condition>
|
||||
<custom name="net_http_client_disabled" excluded_value="true"/>
|
||||
</condition>
|
||||
</cluster>
|
||||
<cluster name="spec_libcurl" location="$|spec/libcurl" recursive="true">
|
||||
<condition>
|
||||
<custom name="libcurl_http_client_disabled" excluded_value="true"/>
|
||||
</condition>
|
||||
</cluster>
|
||||
</cluster>
|
||||
</target>
|
||||
</system>
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="ISO-8859-1"?>
|
||||
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-8-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-8-0 http://www.eiffel.com/developers/xml/configuration-1-8-0.xsd" name="http_client" uuid="628F5A96-021B-4191-926B-B3BF49272866" library_target="http_client">
|
||||
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-14-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-14-0 http://www.eiffel.com/developers/xml/configuration-1-14-0.xsd" name="http_client" uuid="628F5A96-021B-4191-926B-B3BF49272866" library_target="http_client">
|
||||
<target name="http_client">
|
||||
<root all_classes="true"/>
|
||||
<file_rule>
|
||||
@@ -9,13 +9,35 @@
|
||||
</file_rule>
|
||||
<option warning="true" full_class_checking="true" void_safety="none" syntax="provisional">
|
||||
</option>
|
||||
<library name="base" location="$ISE_LIBRARY/library/base/base.ecf"/>
|
||||
<library name="curl" location="$ISE_LIBRARY\library\cURL\cURL.ecf"/>
|
||||
<library name="base" location="$ISE_LIBRARY\library\base\base.ecf"/>
|
||||
<library name="curl" location="$ISE_LIBRARY\library\cURL\cURL.ecf">
|
||||
<condition>
|
||||
<custom name="libcurl_http_client_disabled" excluded_value="true"/>
|
||||
</condition>
|
||||
</library>
|
||||
|
||||
<library name="encoder" location="..\..\text\encoder\encoder.ecf"/>
|
||||
<library name="http" location="..\protocol\http\http.ecf"/>
|
||||
<library name="http_auth" location="..\..\server\authentication\http_authorization\http_authorization.ecf"/>
|
||||
<library name="net" location="$ISE_LIBRARY\library\net\net.ecf"/>
|
||||
<library name="net" location="$ISE_LIBRARY\library\net\net.ecf">
|
||||
<condition>
|
||||
<custom name="net_http_client_disabled" excluded_value="true"/>
|
||||
</condition>
|
||||
</library>
|
||||
|
||||
<library name="uri" location="$ISE_LIBRARY\library\text\uri\uri.ecf"/>
|
||||
<cluster name="src" location=".\src\" recursive="true"/>
|
||||
<cluster name="src" location=".\src\">
|
||||
<cluster name="spec_null" location="$|spec/null" recursive="true"/>
|
||||
<cluster name="spec_net" location="$|spec/socket" recursive="true">
|
||||
<condition>
|
||||
<custom name="net_http_client_disabled" excluded_value="true"/>
|
||||
</condition>
|
||||
</cluster>
|
||||
<cluster name="spec_libcurl" location="$|spec/libcurl" recursive="true">
|
||||
<condition>
|
||||
<custom name="libcurl_http_client_disabled" excluded_value="true"/>
|
||||
</condition>
|
||||
</cluster>
|
||||
</cluster>
|
||||
</target>
|
||||
</system>
|
||||
@@ -16,7 +16,6 @@ feature -- Status
|
||||
deferred
|
||||
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)"
|
||||
|
||||
@@ -54,6 +54,9 @@ feature -- Access
|
||||
|
||||
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.
|
||||
|
||||
@@ -190,15 +193,54 @@ feature -- Access
|
||||
|
||||
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 `header' and `body'.
|
||||
-- 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
|
||||
@@ -234,7 +276,7 @@ feature -- Change
|
||||
|
||||
j := i + 2
|
||||
pos := j
|
||||
status_line := l_status_line
|
||||
set_status_line (l_status_line)
|
||||
set_raw_header (h)
|
||||
|
||||
-- libcURL does not cache redirection content.
|
||||
|
||||
@@ -218,9 +218,9 @@ feature -- Element change
|
||||
cookie := Void
|
||||
end
|
||||
|
||||
set_cookie (c: READABLE_STRING_8)
|
||||
set_cookie (a_cookie: detachable READABLE_STRING_8)
|
||||
do
|
||||
cookie := c
|
||||
cookie := a_cookie
|
||||
end
|
||||
|
||||
set_timeout (n_seconds: like timeout)
|
||||
@@ -253,12 +253,26 @@ feature -- Element change
|
||||
headers.prune (k)
|
||||
end
|
||||
|
||||
set_credentials (u: like username; p: like password)
|
||||
set_credentials (u,p: detachable READABLE_STRING_GENERAL)
|
||||
local
|
||||
s: STRING_32
|
||||
do
|
||||
username := u
|
||||
password := p
|
||||
if u = Void then
|
||||
username := Void
|
||||
else
|
||||
create {STRING_32} username.make_from_string_general (u)
|
||||
end
|
||||
if p = Void then
|
||||
password := Void
|
||||
else
|
||||
create {STRING_32} password.make_from_string_general (p)
|
||||
end
|
||||
if u /= Void and p /= Void then
|
||||
credentials := u + ":" + p
|
||||
create s.make (u.count + 1 + p.count)
|
||||
s.append_string_general (u)
|
||||
s.append_character (':')
|
||||
s.append_string_general (p)
|
||||
credentials := s
|
||||
else
|
||||
credentials := Void
|
||||
end
|
||||
|
||||
33
library/network/http_client/src/spec/null/null_http_client.e
Normal file
33
library/network/http_client/src/spec/null/null_http_client.e
Normal file
@@ -0,0 +1,33 @@
|
||||
note
|
||||
description : "[
|
||||
Instantiate one of the descendant of HTTP_CLIENT
|
||||
then use `new_session' to create a session of http requests
|
||||
]"
|
||||
date : "$Date$"
|
||||
revision : "$Revision$"
|
||||
|
||||
class
|
||||
NULL_HTTP_CLIENT
|
||||
|
||||
inherit
|
||||
HTTP_CLIENT
|
||||
|
||||
feature -- Status
|
||||
|
||||
new_session (a_base_url: READABLE_STRING_8): NULL_HTTP_CLIENT_SESSION
|
||||
-- Create a new session using `a_base_url'.
|
||||
do
|
||||
create Result.make (a_base_url)
|
||||
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,114 @@
|
||||
note
|
||||
description : "[
|
||||
HTTP_CLIENT_SESSION represents a session
|
||||
and is used to call get, post, .... request
|
||||
with predefined settings such as
|
||||
base_url
|
||||
specific common headers
|
||||
timeout and so on ...
|
||||
]"
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
class
|
||||
NULL_HTTP_CLIENT_SESSION
|
||||
|
||||
inherit
|
||||
HTTP_CLIENT_SESSION
|
||||
|
||||
create
|
||||
make
|
||||
|
||||
feature {NONE} -- Initialization
|
||||
|
||||
initialize
|
||||
do
|
||||
end
|
||||
|
||||
feature -- Custom
|
||||
|
||||
custom (a_method: READABLE_STRING_8; a_path: READABLE_STRING_8; ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT): HTTP_CLIENT_RESPONSE
|
||||
do
|
||||
create Result.make (base_url + a_path)
|
||||
end
|
||||
|
||||
custom_with_upload_data (a_method: READABLE_STRING_8; a_path: READABLE_STRING_8; a_ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT; data: READABLE_STRING_8): HTTP_CLIENT_RESPONSE
|
||||
do
|
||||
Result := impl_custom (a_method, a_path, a_ctx, data, Void)
|
||||
end
|
||||
|
||||
custom_with_upload_file (a_method: READABLE_STRING_8; a_path: READABLE_STRING_8; a_ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT; fn: READABLE_STRING_8): HTTP_CLIENT_RESPONSE
|
||||
do
|
||||
Result := impl_custom (a_method, a_path, a_ctx, Void, fn)
|
||||
end
|
||||
|
||||
feature -- Helper
|
||||
|
||||
get (a_path: READABLE_STRING_8; ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT): HTTP_CLIENT_RESPONSE
|
||||
do
|
||||
Result := custom ("GET", a_path, ctx)
|
||||
end
|
||||
|
||||
head (a_path: READABLE_STRING_8; ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT): HTTP_CLIENT_RESPONSE
|
||||
do
|
||||
Result := custom ("HEAD", a_path, ctx)
|
||||
end
|
||||
|
||||
post (a_path: READABLE_STRING_8; a_ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT; data: detachable READABLE_STRING_8): HTTP_CLIENT_RESPONSE
|
||||
do
|
||||
Result := impl_custom ("POST", a_path, a_ctx, data, Void)
|
||||
end
|
||||
|
||||
post_file (a_path: READABLE_STRING_8; a_ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT; fn: detachable READABLE_STRING_8): HTTP_CLIENT_RESPONSE
|
||||
do
|
||||
Result := impl_custom ("POST", a_path, a_ctx, Void, fn)
|
||||
end
|
||||
|
||||
patch (a_path: READABLE_STRING_8; a_ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT; data: detachable READABLE_STRING_8): HTTP_CLIENT_RESPONSE
|
||||
do
|
||||
Result := impl_custom ("PATCH", a_path, a_ctx, data, Void)
|
||||
end
|
||||
|
||||
patch_file (a_path: READABLE_STRING_8; a_ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT; fn: detachable READABLE_STRING_8): HTTP_CLIENT_RESPONSE
|
||||
do
|
||||
Result := impl_custom ("PATCH", a_path, a_ctx, Void, fn)
|
||||
end
|
||||
|
||||
put (a_path: READABLE_STRING_8; a_ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT; data: detachable READABLE_STRING_8): HTTP_CLIENT_RESPONSE
|
||||
do
|
||||
Result := impl_custom ("PUT", a_path, a_ctx, data, Void)
|
||||
end
|
||||
|
||||
put_file (a_path: READABLE_STRING_8; a_ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT; fn: detachable READABLE_STRING_8): HTTP_CLIENT_RESPONSE
|
||||
do
|
||||
Result := impl_custom ("PUT", a_path, a_ctx, Void, fn)
|
||||
end
|
||||
|
||||
delete (a_path: READABLE_STRING_8; ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT): HTTP_CLIENT_RESPONSE
|
||||
do
|
||||
Result := custom ("DELETE", a_path, ctx)
|
||||
end
|
||||
|
||||
feature {NONE} -- Implementation
|
||||
|
||||
impl_custom (a_method: READABLE_STRING_8; a_path: READABLE_STRING_8; a_ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT; data: detachable READABLE_STRING_8; fn: detachable READABLE_STRING_8): HTTP_CLIENT_RESPONSE
|
||||
do
|
||||
Result := custom (a_method, a_path, a_ctx)
|
||||
end
|
||||
|
||||
feature -- Status report
|
||||
|
||||
is_available: BOOLEAN = False
|
||||
-- Is interface usable?
|
||||
|
||||
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
|
||||
@@ -18,6 +18,8 @@ inherit
|
||||
|
||||
REFACTORING_HELPER
|
||||
|
||||
SHARED_EXECUTION_ENVIRONMENT
|
||||
|
||||
create
|
||||
make
|
||||
|
||||
@@ -33,19 +35,19 @@ feature -- Access
|
||||
local
|
||||
redirection_response: detachable like response
|
||||
l_uri: URI
|
||||
l_header_key: READABLE_STRING_8
|
||||
l_host: READABLE_STRING_8
|
||||
l_cookie: detachable READABLE_STRING_8
|
||||
l_request_uri: STRING
|
||||
l_url: HTTP_URL
|
||||
socket: NETWORK_STREAM_SOCKET
|
||||
s: STRING
|
||||
l_message: STRING
|
||||
i,j: INTEGER
|
||||
l_content_length: INTEGER
|
||||
l_location: detachable READABLE_STRING_8
|
||||
l_port: INTEGER
|
||||
l_authorization: HTTP_AUTHORIZATION
|
||||
l_platform: STRING
|
||||
l_useragent: STRING
|
||||
l_upload_data: detachable READABLE_STRING_8
|
||||
l_form_data: detachable HASH_TABLE [READABLE_STRING_32, READABLE_STRING_32]
|
||||
ctx: like context
|
||||
@@ -58,37 +60,40 @@ feature -- Access
|
||||
l_prev_header: READABLE_STRING_8
|
||||
l_boundary: READABLE_STRING_8
|
||||
l_is_http_1_0: BOOLEAN
|
||||
retried: BOOLEAN
|
||||
do
|
||||
ctx := context
|
||||
if ctx /= Void then
|
||||
l_is_http_1_0 := attached ctx.http_version as l_http_version and then l_http_version.same_string ("HTTP/1.0")
|
||||
end
|
||||
create Result.make (url)
|
||||
|
||||
create l_form_string.make_empty
|
||||
|
||||
-- Get URL data
|
||||
create l_uri.make_from_string (url)
|
||||
l_port := l_uri.port
|
||||
if l_port = 0 then
|
||||
if url.starts_with_general ("https://") then
|
||||
l_port := 443
|
||||
else
|
||||
l_port := 80
|
||||
if not retried then
|
||||
ctx := context
|
||||
if ctx /= Void then
|
||||
l_is_http_1_0 := attached ctx.http_version as l_http_version and then l_http_version.same_string ("HTTP/1.0")
|
||||
end
|
||||
end
|
||||
create Result.make (url)
|
||||
|
||||
if attached l_uri.host as h then
|
||||
l_host := h
|
||||
else
|
||||
create l_url.make (url)
|
||||
l_host := l_url.host
|
||||
end
|
||||
create l_form_string.make_empty
|
||||
|
||||
-- add headers for authorization
|
||||
if attached l_uri.userinfo as l_userinfo then
|
||||
if attached l_uri.username as u_name then
|
||||
if attached l_uri.password as u_pass then
|
||||
-- Get URL data
|
||||
create l_uri.make_from_string (url)
|
||||
l_port := l_uri.port
|
||||
if l_port = 0 then
|
||||
if url.starts_with_general ("https://") then
|
||||
l_port := 443
|
||||
else
|
||||
l_port := 80
|
||||
end
|
||||
end
|
||||
if attached l_uri.host as h then
|
||||
l_host := h
|
||||
else
|
||||
create l_url.make (url)
|
||||
l_host := l_url.host
|
||||
end
|
||||
|
||||
-- add headers for authorization
|
||||
if not headers.has ("Authorization") then
|
||||
if
|
||||
attached username as u_name and
|
||||
attached password as u_pass
|
||||
then
|
||||
create l_authorization.make_basic_auth (u_name, u_pass)
|
||||
if attached l_authorization.http_authorization as auth then
|
||||
headers.extend (auth, "Authorization")
|
||||
@@ -96,286 +101,356 @@ feature -- Access
|
||||
check headers.has_key ("Authorization") end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
create l_request_uri.make_from_string (l_uri.path)
|
||||
if attached l_uri.query as l_query then
|
||||
l_request_uri.append_character ('?')
|
||||
l_request_uri.append (l_query)
|
||||
end
|
||||
|
||||
-- add headers for user agent
|
||||
if {PLATFORM}.is_unix then
|
||||
l_platform := "Unix"
|
||||
else
|
||||
if {PLATFORM}.is_mac then
|
||||
l_platform := "Mac"
|
||||
else
|
||||
l_platform := "Windows"
|
||||
end
|
||||
end
|
||||
if l_platform /= Void then
|
||||
l_useragent := "eiffelhttpclient/" + net_http_client_version + " (" + l_platform + ")"
|
||||
headers.extend (l_useragent, "User-Agent")
|
||||
end
|
||||
|
||||
|
||||
-- handle sending data
|
||||
if attached ctx then
|
||||
if ctx.has_upload_filename then
|
||||
l_upload_filename := ctx.upload_filename
|
||||
create l_request_uri.make_from_string (l_uri.path)
|
||||
if attached l_uri.query as l_query then
|
||||
l_request_uri.append_character ('?')
|
||||
l_request_uri.append (l_query)
|
||||
end
|
||||
|
||||
if ctx.has_upload_data then
|
||||
l_upload_data := ctx.upload_data
|
||||
end
|
||||
|
||||
if ctx.has_form_data then
|
||||
l_form_data := ctx.form_parameters
|
||||
check non_empty_form_data: not l_form_data.is_empty end
|
||||
if l_upload_data = Void and l_upload_filename = Void then
|
||||
-- Send as form-urlencoded
|
||||
headers.extend ("application/x-www-form-urlencoded", "Content-Type")
|
||||
l_upload_data := ctx.form_parameters_to_url_encoded_string
|
||||
headers.force (l_upload_data.count.out, "Content-Length")
|
||||
|
||||
-- add computed header User-Agent if not yet set.
|
||||
if not headers.has ("User-Agent") then
|
||||
if {PLATFORM}.is_unix then
|
||||
l_platform := "Unix"
|
||||
elseif {PLATFORM}.is_windows then
|
||||
l_platform := "Windows"
|
||||
elseif {PLATFORM}.is_mac then
|
||||
l_platform := "Mac"
|
||||
elseif {PLATFORM}.is_vms then
|
||||
l_platform := "VMS"
|
||||
elseif {PLATFORM}.is_vxworks then
|
||||
l_platform := "VxWorks"
|
||||
else
|
||||
-- create form
|
||||
l_boundary := new_mime_boundary
|
||||
headers.extend ("multipart/form-data; boundary=" + l_boundary, "Content-Type")
|
||||
if l_form_data /= Void then
|
||||
headers.extend ("*/*", "Accept")
|
||||
from
|
||||
l_form_data.start
|
||||
until
|
||||
l_form_data.after
|
||||
loop
|
||||
l_form_string.append (l_boundary)
|
||||
l_form_string.append (http_end_of_header_line)
|
||||
l_form_string.append ("Content-Disposition: form-data; name=")
|
||||
l_form_string.append ("%"" + l_form_data.key_for_iteration + "%"")
|
||||
l_form_string.append (http_end_of_header_line)
|
||||
l_form_string.append (http_end_of_header_line)
|
||||
l_form_string.append (l_form_data.item_for_iteration)
|
||||
l_form_string.append (http_end_of_header_line)
|
||||
l_form_data.forth
|
||||
end
|
||||
|
||||
if l_upload_filename /= Void then
|
||||
-- get file extension, otherwise set default
|
||||
l_mime_type := "application/octet-stream"
|
||||
create l_mime_type_mapping.make_default
|
||||
l_fn_extension := l_upload_filename.tail (l_upload_filename.count - l_upload_filename.last_index_of ('.', l_upload_filename.count))
|
||||
if attached l_mime_type_mapping.mime_type (l_fn_extension) as mime then
|
||||
l_mime_type := mime
|
||||
end
|
||||
|
||||
l_form_string.append (l_boundary)
|
||||
l_form_string.append (http_end_of_header_line)
|
||||
l_form_string.append ("Content-Disposition: form-data; name=%"" + l_upload_filename.as_string_32 + "%"")
|
||||
l_form_string.append ("; filename=%"" + l_upload_filename + "%"")
|
||||
l_form_string.append (http_end_of_header_line)
|
||||
l_form_string.append ("Content-Type: ")
|
||||
l_form_string.append (l_mime_type)
|
||||
l_form_string.append (http_end_of_header_line)
|
||||
l_form_string.append (http_end_of_header_line)
|
||||
|
||||
create l_upload_file.make_with_name (l_upload_filename)
|
||||
if l_upload_file.exists and then l_upload_file.is_readable then
|
||||
l_upload_file.open_read
|
||||
l_upload_file.read_stream (l_upload_file.count)
|
||||
l_form_string.append (l_upload_file.last_string)
|
||||
end
|
||||
l_form_string.append (http_end_of_header_line)
|
||||
end
|
||||
l_form_string.append (l_boundary + "--") --| end
|
||||
|
||||
l_upload_data := l_form_string
|
||||
headers.extend (l_upload_data.count.out, "Content-Length")
|
||||
end
|
||||
end
|
||||
else
|
||||
if request_method.is_case_insensitive_equal ("POST") then
|
||||
if ctx /= Void then
|
||||
if ctx.has_upload_data then
|
||||
l_upload_data := ctx.upload_data
|
||||
if l_upload_data /= Void then
|
||||
headers.extend ("application/x-www-form-urlencoded", "Content-Type")
|
||||
headers.extend (l_upload_data.count.out, "Content-Length")
|
||||
end
|
||||
elseif ctx.has_upload_filename then
|
||||
if l_upload_filename /= Void then
|
||||
create l_upload_file.make_with_name (l_upload_filename)
|
||||
if l_upload_file.exists and then l_upload_file.is_readable then
|
||||
headers.extend (l_upload_file.count.out, "Content-Length")
|
||||
l_upload_file.open_read
|
||||
end
|
||||
check l_upload_file /= Void end
|
||||
end
|
||||
end
|
||||
end
|
||||
l_platform := "Unknown"
|
||||
end
|
||||
headers.extend ("eiffelhttpclient/" + net_http_client_version + " (" + l_platform + ")", "User-Agent")
|
||||
end
|
||||
end
|
||||
|
||||
-- handle put requests
|
||||
if request_method.is_case_insensitive_equal ("PUT") then
|
||||
-- handle sending data
|
||||
if ctx /= Void then
|
||||
if ctx.has_upload_filename then
|
||||
if l_upload_filename /= Void then
|
||||
create l_upload_file.make_with_name (l_upload_filename)
|
||||
if l_upload_file.exists and then l_upload_file.is_readable then
|
||||
headers.extend (l_upload_file.count.out, "Content-Length")
|
||||
l_upload_file.open_read
|
||||
l_upload_filename := ctx.upload_filename
|
||||
end
|
||||
|
||||
if ctx.has_upload_data then
|
||||
l_upload_data := ctx.upload_data
|
||||
end
|
||||
|
||||
if ctx.has_form_data then
|
||||
l_form_data := ctx.form_parameters
|
||||
check non_empty_form_data: not l_form_data.is_empty end
|
||||
if l_upload_data = Void and l_upload_filename = Void then
|
||||
-- Send as form-urlencoded
|
||||
headers.extend ("application/x-www-form-urlencoded", "Content-Type")
|
||||
l_upload_data := ctx.form_parameters_to_url_encoded_string
|
||||
headers.force (l_upload_data.count.out, "Content-Length")
|
||||
|
||||
else
|
||||
-- create form
|
||||
l_boundary := new_mime_boundary
|
||||
headers.extend ("multipart/form-data; boundary=" + l_boundary, "Content-Type")
|
||||
if l_form_data /= Void then
|
||||
headers.extend ("*/*", "Accept")
|
||||
from
|
||||
l_form_data.start
|
||||
until
|
||||
l_form_data.after
|
||||
loop
|
||||
l_form_string.append (l_boundary)
|
||||
l_form_string.append (http_end_of_header_line)
|
||||
l_form_string.append ("Content-Disposition: form-data; name=")
|
||||
l_form_string.append ("%"" + l_form_data.key_for_iteration + "%"")
|
||||
l_form_string.append (http_end_of_header_line)
|
||||
l_form_string.append (http_end_of_header_line)
|
||||
l_form_string.append (l_form_data.item_for_iteration)
|
||||
l_form_string.append (http_end_of_header_line)
|
||||
l_form_data.forth
|
||||
end
|
||||
|
||||
if l_upload_filename /= Void then
|
||||
-- get file extension, otherwise set default
|
||||
l_mime_type := "application/octet-stream"
|
||||
create l_mime_type_mapping.make_default
|
||||
l_fn_extension := l_upload_filename.tail (l_upload_filename.count - l_upload_filename.last_index_of ('.', l_upload_filename.count))
|
||||
if attached l_mime_type_mapping.mime_type (l_fn_extension) as mime then
|
||||
l_mime_type := mime
|
||||
end
|
||||
|
||||
l_form_string.append (l_boundary)
|
||||
l_form_string.append (http_end_of_header_line)
|
||||
l_form_string.append ("Content-Disposition: form-data; name=%"" + l_upload_filename.as_string_32 + "%"")
|
||||
l_form_string.append ("; filename=%"" + l_upload_filename + "%"")
|
||||
l_form_string.append (http_end_of_header_line)
|
||||
l_form_string.append ("Content-Type: ")
|
||||
l_form_string.append (l_mime_type)
|
||||
l_form_string.append (http_end_of_header_line)
|
||||
l_form_string.append (http_end_of_header_line)
|
||||
|
||||
create l_upload_file.make_with_name (l_upload_filename)
|
||||
if l_upload_file.exists and then l_upload_file.is_access_readable then
|
||||
append_file_content_to (l_upload_file, l_upload_file.count, l_form_string)
|
||||
-- Reset l_upload_file to Void, since the related content is already processed.
|
||||
l_upload_file := Void
|
||||
end
|
||||
l_form_string.append (http_end_of_header_line)
|
||||
end
|
||||
l_form_string.append (l_boundary + "--") --| end
|
||||
|
||||
l_upload_data := l_form_string
|
||||
headers.extend (l_upload_data.count.out, "Content-Length")
|
||||
end
|
||||
check l_upload_filename /= Void end
|
||||
end
|
||||
elseif
|
||||
request_method.is_case_insensitive_equal ("POST")
|
||||
or else request_method.is_case_insensitive_equal ("PUT")
|
||||
then
|
||||
if l_upload_data /= Void then
|
||||
check ctx.has_upload_data end
|
||||
headers.extend ("application/x-www-form-urlencoded", "Content-Type")
|
||||
headers.extend (l_upload_data.count.out, "Content-Length")
|
||||
elseif l_upload_filename /= Void then
|
||||
check ctx.has_upload_filename end
|
||||
create l_upload_file.make_with_name (l_upload_filename)
|
||||
if l_upload_file.exists and then l_upload_file.readable then
|
||||
headers.extend (l_upload_file.count.out, "Content-Length")
|
||||
end
|
||||
check l_upload_file /= Void end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Connect
|
||||
create socket.make_client_by_port (l_port, l_host)
|
||||
socket.set_timeout (timeout)
|
||||
socket.set_connect_timeout (connect_timeout)
|
||||
socket.connect
|
||||
if socket.is_connected then
|
||||
create s.make_from_string (request_method.as_upper)
|
||||
s.append_character (' ')
|
||||
s.append (l_request_uri)
|
||||
s.append_character (' ')
|
||||
if l_is_http_1_0 then
|
||||
s.append ("HTTP/1.0")
|
||||
else
|
||||
s.append ("HTTP/1.1")
|
||||
end
|
||||
s.append (Http_end_of_header_line)
|
||||
|
||||
s.append (Http_host_header)
|
||||
s.append (": ")
|
||||
s.append (l_host)
|
||||
s.append (http_end_of_header_line)
|
||||
if attached session.cookie as cookie then
|
||||
s.append ("Cookie: " + cookie + http_end_of_header_line)
|
||||
end
|
||||
if headers.is_empty then
|
||||
s.append (Http_end_of_command)
|
||||
else
|
||||
across
|
||||
headers as ic
|
||||
loop
|
||||
s.append (ic.key)
|
||||
s.append (": ")
|
||||
s.append (ic.item)
|
||||
s.append (Http_end_of_header_line)
|
||||
-- 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.
|
||||
--| only one Cookie: is allowed, so merge multiple into one;
|
||||
--| if Host is in header, use that one.
|
||||
-- Compute Request line.
|
||||
create s.make_from_string (request_method.as_upper)
|
||||
s.append_character (' ')
|
||||
s.append (l_request_uri)
|
||||
s.append_character (' ')
|
||||
if l_is_http_1_0 then
|
||||
s.append ("HTTP/1.0")
|
||||
else
|
||||
s.append ("HTTP/1.1")
|
||||
end
|
||||
s.append (Http_end_of_header_line)
|
||||
end
|
||||
|
||||
if l_upload_data /= Void then
|
||||
s.append (l_upload_data)
|
||||
s.append (http_end_of_header_line)
|
||||
end
|
||||
if attached l_upload_file then
|
||||
if not l_upload_file.after then
|
||||
from
|
||||
until
|
||||
l_upload_file.after
|
||||
loop
|
||||
l_upload_file.read_line_thread_aware
|
||||
s.append (l_upload_file.last_string)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
socket.put_string (s)
|
||||
|
||||
--| Get response.
|
||||
--| Get header message
|
||||
create l_message.make_empty
|
||||
read_header_from_socket (socket, l_message)
|
||||
l_prev_header := Result.raw_header
|
||||
Result.set_raw_header (l_message.string)
|
||||
l_content_length := -1
|
||||
if attached Result.header ("Content-Length") as s_len and then s_len.is_integer then
|
||||
l_content_length := s_len.to_integer
|
||||
end
|
||||
l_location := Result.header ("Location")
|
||||
if attached Result.header ("Set-Cookies") as s_cookies then
|
||||
session.set_cookie (s_cookies)
|
||||
end
|
||||
l_message.append (http_end_of_header_line)
|
||||
|
||||
-- Get content if any.
|
||||
append_socket_content_to (Result, socket, l_content_length, l_message)
|
||||
-- Restore previous header
|
||||
Result.set_raw_header (l_prev_header)
|
||||
-- Set message
|
||||
Result.set_response_message (l_message, context)
|
||||
|
||||
-- Get status code.
|
||||
if attached Result.status_line as l_status_line then
|
||||
if l_status_line.starts_with ("HTTP/") then
|
||||
i := l_status_line.index_of (' ', 1)
|
||||
if i > 0 then
|
||||
i := i + 1
|
||||
end
|
||||
-- Compute Header Host:
|
||||
s.append (Http_host_header)
|
||||
s.append (": ")
|
||||
if attached headers [Http_host_header] as h_host then
|
||||
s.append (h_host)
|
||||
else
|
||||
i := 1
|
||||
s.append (l_host)
|
||||
end
|
||||
-- Get status code token.
|
||||
if i > 0 then
|
||||
j := l_status_line.index_of (' ', i)
|
||||
if j > i then
|
||||
s := l_status_line.substring (i, j - 1)
|
||||
if s.is_integer then
|
||||
Result.set_status (s.to_integer)
|
||||
s.append (http_end_of_header_line)
|
||||
|
||||
-- Append the given request headers
|
||||
l_cookie := Void
|
||||
if headers.is_empty then
|
||||
s.append (Http_end_of_command)
|
||||
else
|
||||
across
|
||||
headers as ic
|
||||
loop
|
||||
l_header_key := ic.key
|
||||
if l_header_key.same_string_general ("Host") then
|
||||
-- FIXME: already handled elsewhere!
|
||||
elseif l_header_key.same_string_general ("Cookie") then
|
||||
-- FIXME: need cookie merging.
|
||||
l_cookie := ic.item
|
||||
else
|
||||
s.append (ic.key)
|
||||
s.append (": ")
|
||||
s.append (ic.item)
|
||||
s.append (Http_end_of_header_line)
|
||||
end
|
||||
end
|
||||
s.append (Http_end_of_header_line)
|
||||
end
|
||||
end
|
||||
|
||||
-- follow redirect
|
||||
if l_location /= Void then
|
||||
if current_redirects < max_redirects then
|
||||
current_redirects := current_redirects + 1
|
||||
initialize (l_location, ctx)
|
||||
redirection_response := response
|
||||
redirection_response.add_redirection (Result.status_line, Result.raw_header, Result.body)
|
||||
Result := redirection_response
|
||||
-- Result.add_redirection (redirection_response.status_line, redirection_response.raw_header, redirection_response.body)
|
||||
-- Compute Header Cookie: if needed
|
||||
-- Get session cookie
|
||||
if l_cookie = Void then
|
||||
l_cookie := session.cookie
|
||||
else
|
||||
-- Overwrite potential session cookie, if specified by the user.
|
||||
end
|
||||
if l_cookie /= Void then
|
||||
s.append ("Cookie: ")
|
||||
s.append (l_cookie)
|
||||
s.append (http_end_of_header_line)
|
||||
end
|
||||
end
|
||||
|
||||
current_redirects := 0
|
||||
if l_upload_data /= Void then
|
||||
s.append (l_upload_data)
|
||||
s.append (http_end_of_header_line)
|
||||
end
|
||||
--| Note that any remaining file to upload will be done directly via the socket
|
||||
--| to optimize memory usage
|
||||
|
||||
--| Send request
|
||||
if socket.ready_for_writing then
|
||||
socket.put_string (s)
|
||||
--| Send remaining payload data, if needed.
|
||||
if l_upload_file /= Void then
|
||||
-- i.e: not yet processed
|
||||
append_file_content_to_socket (l_upload_file, l_upload_file.count, socket)
|
||||
end
|
||||
|
||||
--| Get response.
|
||||
--| Get header message
|
||||
if socket.ready_for_reading then
|
||||
create l_message.make_empty
|
||||
append_socket_header_content_to (socket, l_message)
|
||||
l_prev_header := Result.raw_header
|
||||
Result.set_raw_header (l_message.string)
|
||||
l_content_length := -1
|
||||
if attached Result.header ("Content-Length") as s_len and then s_len.is_integer then
|
||||
l_content_length := s_len.to_integer
|
||||
end
|
||||
l_location := Result.header ("Location")
|
||||
if attached Result.header ("Set-Cookies") as s_cookies then
|
||||
session.set_cookie (s_cookies)
|
||||
end
|
||||
l_message.append (http_end_of_header_line)
|
||||
|
||||
-- Get content if any.
|
||||
append_socket_content_to (Result, socket, l_content_length, l_message)
|
||||
-- Restore previous header
|
||||
Result.set_raw_header (l_prev_header)
|
||||
-- Set message
|
||||
Result.set_response_message (l_message, ctx)
|
||||
-- Check status code.
|
||||
check status_coherent: attached Result.status_line as l_status_line implies l_status_line.has_substring (Result.status.out) end
|
||||
|
||||
-- follow redirect
|
||||
if l_location /= Void then
|
||||
if current_redirects < max_redirects then
|
||||
current_redirects := current_redirects + 1
|
||||
initialize (l_location, ctx)
|
||||
redirection_response := response
|
||||
redirection_response.add_redirection (Result.status_line, Result.raw_header, Result.body)
|
||||
Result := redirection_response
|
||||
end
|
||||
end
|
||||
|
||||
current_redirects := current_redirects - 1
|
||||
else
|
||||
Result.set_error_message ("Read Timeout")
|
||||
end
|
||||
else
|
||||
Result.set_error_message ("Write Timeout")
|
||||
end
|
||||
else
|
||||
Result.set_error_message ("Could not connect")
|
||||
end
|
||||
else
|
||||
Result.set_error_message ("Could not connect")
|
||||
create Result.make (url)
|
||||
Result.set_error_message ("Error: internal error")
|
||||
end
|
||||
rescue
|
||||
retried := True
|
||||
retry
|
||||
end
|
||||
|
||||
feature {NONE} -- Helpers
|
||||
|
||||
read_header_from_socket (a_socket: NETWORK_STREAM_SOCKET; a_output: STRING)
|
||||
append_file_content_to_socket (a_file: FILE; a_len: INTEGER; a_output: NETWORK_STREAM_SOCKET)
|
||||
-- Append `a_file' content to `a_output'.
|
||||
-- If `a_len' >= 0 then read only `a_len' characters.
|
||||
require
|
||||
a_file_readable: a_file.exists and then a_file.is_access_readable
|
||||
local
|
||||
l_was_open: BOOLEAN
|
||||
l_count: INTEGER
|
||||
do
|
||||
if a_len >= 0 then
|
||||
l_count := a_len
|
||||
else
|
||||
l_count := a_file.count
|
||||
end
|
||||
if l_count > 0 then
|
||||
l_was_open := a_file.is_open_read
|
||||
if a_file.is_open_read then
|
||||
l_was_open := True
|
||||
else
|
||||
a_file.open_read
|
||||
end
|
||||
from
|
||||
until
|
||||
l_count = 0 or a_file.exhausted
|
||||
loop
|
||||
a_file.read_stream_thread_aware (l_count.min (2_048))
|
||||
a_output.put_string (a_file.last_string)
|
||||
l_count := l_count - a_file.bytes_read
|
||||
end
|
||||
if not l_was_open then
|
||||
a_file.close
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
append_file_content_to (a_file: FILE; a_len: INTEGER; a_output: STRING)
|
||||
-- Append `a_file' content to `a_output'.
|
||||
-- If `a_len' >= 0 then read only `a_len' characters.
|
||||
require
|
||||
a_file_readable: a_file.exists and then a_file.is_access_readable
|
||||
local
|
||||
l_was_open: BOOLEAN
|
||||
l_count: INTEGER
|
||||
do
|
||||
if a_len >= 0 then
|
||||
l_count := a_len
|
||||
else
|
||||
l_count := a_file.count
|
||||
end
|
||||
if l_count > 0 then
|
||||
l_was_open := a_file.is_open_read
|
||||
if a_file.is_open_read then
|
||||
l_was_open := True
|
||||
else
|
||||
a_file.open_read
|
||||
end
|
||||
from
|
||||
|
||||
until
|
||||
l_count = 0 or a_file.exhausted
|
||||
loop
|
||||
a_file.read_stream_thread_aware (l_count.min (2_048))
|
||||
a_output.append (a_file.last_string)
|
||||
l_count := l_count - a_file.bytes_read
|
||||
end
|
||||
if not l_was_open then
|
||||
a_file.close
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
append_socket_header_content_to (a_socket: NETWORK_STREAM_SOCKET; a_output: STRING)
|
||||
-- Get header from `a_socket' into `a_output'.
|
||||
local
|
||||
s: READABLE_STRING_8
|
||||
do
|
||||
if a_socket.is_readable then
|
||||
from
|
||||
s := ""
|
||||
until
|
||||
s.same_string ("%R") or not a_socket.is_readable
|
||||
loop
|
||||
a_socket.read_line_thread_aware
|
||||
s := a_socket.last_string
|
||||
if s.same_string ("%R") then
|
||||
-- Reach end of header
|
||||
-- a_output.append (http_end_of_header_line)
|
||||
else
|
||||
a_output.append (s)
|
||||
a_output.append_character ('%N')
|
||||
end
|
||||
from
|
||||
s := ""
|
||||
until
|
||||
s.same_string ("%R") or not a_socket.readable
|
||||
loop
|
||||
a_socket.read_line_thread_aware
|
||||
s := a_socket.last_string
|
||||
if s.same_string ("%R") then
|
||||
-- Reach end of header
|
||||
-- a_output.append (http_end_of_header_line)
|
||||
else
|
||||
a_output.append (s)
|
||||
a_output.append_character ('%N')
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -386,61 +461,94 @@ feature {NONE} -- Helpers
|
||||
-- this is probably HTTP/1.0 without any Content-Length.
|
||||
local
|
||||
s: STRING_8
|
||||
n,pos: INTEGER
|
||||
n,pos, l_chunk_size, l_count: INTEGER
|
||||
hexa2int: HEXADECIMAL_STRING_TO_INTEGER_CONVERTER
|
||||
do
|
||||
if a_socket.is_readable then
|
||||
if a_socket.readable then
|
||||
if a_len >= 0 then
|
||||
a_socket.read_stream_thread_aware (a_len)
|
||||
s := a_socket.last_string
|
||||
check full_content_read: a_socket.bytes_read = a_len end
|
||||
a_buffer.append (s)
|
||||
else
|
||||
if attached a_response.header ("Transfer-Encoding") as l_enc and then l_enc.is_case_insensitive_equal ("chunked") then
|
||||
from
|
||||
create hexa2int.make
|
||||
n := 1
|
||||
until
|
||||
n = 0 or not a_socket.is_readable
|
||||
loop
|
||||
a_socket.read_line_thread_aware -- Read chunk info
|
||||
s := a_socket.last_string
|
||||
s.right_adjust
|
||||
pos := s.index_of (';', 1)
|
||||
if pos > 0 then
|
||||
s.keep_head (pos - 1)
|
||||
end
|
||||
if s.is_empty then
|
||||
n := 0
|
||||
from
|
||||
n := a_len
|
||||
until
|
||||
n = 0 or else not a_socket.readable or else a_response.error_occurred
|
||||
loop
|
||||
if a_socket.ready_for_reading then
|
||||
a_socket.read_stream_thread_aware (n)
|
||||
l_count := l_count + a_socket.bytes_read
|
||||
n := n - a_socket.bytes_read
|
||||
a_buffer.append (a_socket.last_string)
|
||||
else
|
||||
a_response.set_error_message ("Could not read chunked data, timeout")
|
||||
end
|
||||
end
|
||||
check full_content_read: 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
|
||||
from
|
||||
create hexa2int.make
|
||||
n := 1
|
||||
until
|
||||
n = 0 or not a_socket.readable
|
||||
loop
|
||||
a_socket.read_line_thread_aware -- Read chunk info
|
||||
s := a_socket.last_string
|
||||
s.right_adjust
|
||||
pos := s.index_of (';', 1)
|
||||
if pos > 0 then
|
||||
s.keep_head (pos - 1)
|
||||
end
|
||||
if s.is_empty then
|
||||
n := 0
|
||||
else
|
||||
hexa2int.parse_string_with_type (s, hexa2int.type_integer)
|
||||
if hexa2int.parse_successful then
|
||||
n := hexa2int.parsed_integer
|
||||
else
|
||||
hexa2int.parse_string_with_type (s, hexa2int.type_integer)
|
||||
if hexa2int.parse_successful then
|
||||
n := hexa2int.parsed_integer
|
||||
n := 0
|
||||
end
|
||||
end
|
||||
if n > 0 then
|
||||
from
|
||||
until
|
||||
n = 0 or else not a_socket.readable or else a_response.error_occurred
|
||||
loop
|
||||
if a_socket.ready_for_reading then
|
||||
a_socket.read_stream_thread_aware (n)
|
||||
l_count := l_count + a_socket.bytes_read
|
||||
n := n - a_socket.bytes_read
|
||||
a_buffer.append (a_socket.last_string)
|
||||
else
|
||||
n := 0
|
||||
a_response.set_error_message ("Could not read chunked data, timeout")
|
||||
end
|
||||
end
|
||||
if n > 0 then
|
||||
a_socket.read_stream_thread_aware (n)
|
||||
check a_socket.bytes_read = n end
|
||||
a_buffer.append (a_socket.last_string)
|
||||
|
||||
if a_socket.ready_for_reading then
|
||||
a_socket.read_character
|
||||
check a_socket.last_character = '%R' end
|
||||
a_socket.read_character
|
||||
check a_socket.last_character = '%N' end
|
||||
else
|
||||
a_response.set_error_message ("Could not read chunked data, timeout")
|
||||
end
|
||||
end
|
||||
else
|
||||
-- HTTP/1.0
|
||||
from
|
||||
n := 1_024
|
||||
until
|
||||
n < 1_024 or not a_socket.is_readable
|
||||
loop
|
||||
a_socket.read_stream_thread_aware (1_024)
|
||||
end
|
||||
else
|
||||
-- No Content-Length and no chunked transfer encoding!
|
||||
-- maybe HTTP/1.0 ?
|
||||
-- FIXME: check solution!
|
||||
from
|
||||
l_count := 0
|
||||
l_chunk_size := 1_024
|
||||
n := l_chunk_size --| value to satisfy until condition on first loop.
|
||||
until
|
||||
n < l_chunk_size or not a_socket.readable
|
||||
loop
|
||||
if a_socket.ready_for_reading then
|
||||
a_socket.read_stream_thread_aware (l_chunk_size)
|
||||
s := a_socket.last_string
|
||||
n := a_socket.bytes_read
|
||||
l_count := l_count + n
|
||||
a_buffer.append (s)
|
||||
else
|
||||
a_response.set_error_message ("Could not read data, timeout")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -98,7 +98,6 @@ feature {NONE} -- Implementation
|
||||
local
|
||||
req: HTTP_CLIENT_REQUEST
|
||||
ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT
|
||||
f: detachable RAW_FILE
|
||||
l_data: detachable READABLE_STRING_8
|
||||
do
|
||||
ctx := a_ctx
|
||||
@@ -117,34 +116,12 @@ feature {NONE} -- Implementation
|
||||
if ctx /= Void then
|
||||
l_data := ctx.upload_data
|
||||
if l_data /= Void and a_method.is_case_insensitive_equal_general ("PUT") then
|
||||
--| Quick and dirty hack using real file, for PUT uploaded data
|
||||
--| FIXME [2012-05-23]: better use libcurl for that purpose
|
||||
|
||||
if ctx.has_upload_filename then
|
||||
check put_conflict_file_and_data: False end
|
||||
end
|
||||
create f.make_open_write (create {FILE_NAME}.make_temporary_name)
|
||||
f.put_string (l_data)
|
||||
f.close
|
||||
check ctx /= Void then
|
||||
ctx.set_upload_data (Void)
|
||||
ctx.set_upload_filename (f.path.name)
|
||||
end
|
||||
check put_conflict_file_and_data: not ctx.has_upload_filename end
|
||||
end
|
||||
end
|
||||
|
||||
create {NET_HTTP_CLIENT_REQUEST} req.make (base_url + a_path, a_method, Current, ctx)
|
||||
Result := req.response
|
||||
|
||||
-- FIXME file should be deleted, but uncommenting the following leads to a PERMISSION DENIED exception..
|
||||
--if f /= Void then
|
||||
-- f.delete
|
||||
--end
|
||||
|
||||
if l_data /= Void and a_ctx /= Void then
|
||||
a_ctx.set_upload_filename (Void)
|
||||
a_ctx.set_upload_data (l_data)
|
||||
end
|
||||
end
|
||||
|
||||
note
|
||||
|
||||
@@ -1,190 +0,0 @@
|
||||
note
|
||||
description : "httptest application root class"
|
||||
date : "$Date$"
|
||||
revision : "$Revision$"
|
||||
|
||||
class
|
||||
APPLICATION
|
||||
|
||||
inherit
|
||||
ARGUMENTS
|
||||
|
||||
create
|
||||
make
|
||||
|
||||
feature {NONE} -- Initialization
|
||||
|
||||
make
|
||||
-- Run application.
|
||||
do
|
||||
requestbin_path := "/15u47xi2"
|
||||
test_1
|
||||
test_2
|
||||
test_3
|
||||
test_4
|
||||
test_5
|
||||
test_6
|
||||
test_7
|
||||
end
|
||||
|
||||
requestbin_path: STRING
|
||||
|
||||
feature -- Tests
|
||||
|
||||
test_1
|
||||
local
|
||||
sess: NET_HTTP_CLIENT_SESSION
|
||||
h: STRING_8
|
||||
l_ctx: HTTP_CLIENT_REQUEST_CONTEXT
|
||||
do
|
||||
-- URL ENCODED POST REQUEST
|
||||
-- check requestbin to ensure the "Hello World" has been received in the raw body
|
||||
-- also check that User-Agent was sent
|
||||
create h.make_empty
|
||||
create sess.make("http://requestb.in")
|
||||
if attached sess.post (requestbin_path, Void, "Hello World").headers as hds then
|
||||
across
|
||||
hds as c
|
||||
loop
|
||||
h.append (c.item.name + ": " + c.item.value + "%R%N")
|
||||
end
|
||||
end
|
||||
print (h)
|
||||
end
|
||||
|
||||
test_2
|
||||
local
|
||||
sess: NET_HTTP_CLIENT_SESSION
|
||||
h: STRING_8
|
||||
l_ctx: HTTP_CLIENT_REQUEST_CONTEXT
|
||||
do
|
||||
-- POST REQUEST WITH FORM DATA
|
||||
-- check requestbin to ensure the form parameters are correctly received
|
||||
create sess.make("http://requestb.in")
|
||||
create l_ctx.make
|
||||
l_ctx.form_parameters.extend ("First Value", "First Key")
|
||||
l_ctx.form_parameters.extend ("Second Value", "Second Key")
|
||||
create sess.make("http://requestb.in")
|
||||
create h.make_empty
|
||||
if attached sess.post (requestbin_path, l_ctx, "").headers as hds then
|
||||
across
|
||||
hds as c
|
||||
loop
|
||||
h.append (c.item.name + ": " + c.item.value + "%R%N")
|
||||
end
|
||||
end
|
||||
print (h)
|
||||
end
|
||||
|
||||
test_3
|
||||
local
|
||||
sess: NET_HTTP_CLIENT_SESSION
|
||||
h: STRING_8
|
||||
l_ctx: HTTP_CLIENT_REQUEST_CONTEXT
|
||||
do
|
||||
-- POST REQUEST WITH A FILE
|
||||
-- check requestbin to ensure the form parameters are correctly received
|
||||
-- set filename to a local file
|
||||
create sess.make("http://requestb.in")
|
||||
create l_ctx.make
|
||||
l_ctx.set_upload_filename ("C:\temp\test.txt")
|
||||
create h.make_empty
|
||||
if attached sess.post (requestbin_path, l_ctx, "").headers as hds then
|
||||
across
|
||||
hds as c
|
||||
loop
|
||||
h.append (c.item.name + ": " + c.item.value + "%R%N")
|
||||
end
|
||||
end
|
||||
print (h)
|
||||
end
|
||||
|
||||
test_4
|
||||
local
|
||||
sess: NET_HTTP_CLIENT_SESSION
|
||||
h: STRING_8
|
||||
l_ctx: HTTP_CLIENT_REQUEST_CONTEXT
|
||||
do
|
||||
-- PUT REQUEST WITH A FILE
|
||||
-- check requestbin to ensure the file is correctly received
|
||||
-- set filename to a local file
|
||||
create sess.make("http://requestb.in")
|
||||
create l_ctx.make
|
||||
l_ctx.set_upload_filename ("C:\temp\test.txt")
|
||||
create h.make_empty
|
||||
if attached sess.put (requestbin_path, l_ctx, "").headers as hds then
|
||||
across
|
||||
hds as c
|
||||
loop
|
||||
h.append (c.item.name + ": " + c.item.value + "%R%N")
|
||||
end
|
||||
end
|
||||
print (h)
|
||||
end
|
||||
|
||||
test_5
|
||||
local
|
||||
sess: NET_HTTP_CLIENT_SESSION
|
||||
h: STRING_8
|
||||
l_ctx: HTTP_CLIENT_REQUEST_CONTEXT
|
||||
do
|
||||
-- POST REQUEST WITH A FILE AND FORM DATA
|
||||
-- check requestbin to ensure the file and form parameters are correctly received
|
||||
-- set filename to a local file
|
||||
create sess.make("http://requestb.in")
|
||||
create l_ctx.make
|
||||
l_ctx.set_upload_filename ("C:\temp\logo.png")
|
||||
l_ctx.form_parameters.extend ("First Value", "First Key")
|
||||
l_ctx.form_parameters.extend ("Second Value", "Second Key")
|
||||
create h.make_empty
|
||||
if attached sess.post (requestbin_path, l_ctx, "").headers as hds then
|
||||
across
|
||||
hds as c
|
||||
loop
|
||||
h.append (c.item.name + ": " + c.item.value + "%R%N")
|
||||
end
|
||||
end
|
||||
print (h)
|
||||
end
|
||||
|
||||
test_6
|
||||
local
|
||||
sess: NET_HTTP_CLIENT_SESSION
|
||||
h: STRING_8
|
||||
l_ctx: HTTP_CLIENT_REQUEST_CONTEXT
|
||||
do
|
||||
-- GET REQUEST, Forwarding (google's first answer is a forward)
|
||||
-- check headers received (printed in console)
|
||||
create sess.make("http://google.com")
|
||||
create h.make_empty
|
||||
if attached sess.get ("/", Void).headers as hds then
|
||||
across
|
||||
hds as c
|
||||
loop
|
||||
h.append (c.item.name + ": " + c.item.value + "%R%N")
|
||||
end
|
||||
end
|
||||
print (h)
|
||||
end
|
||||
|
||||
test_7
|
||||
local
|
||||
sess: NET_HTTP_CLIENT_SESSION
|
||||
h: STRING_8
|
||||
l_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 sess.make("http://test:test@browserspy.dk")
|
||||
create h.make_empty
|
||||
if attached sess.get ("/password-ok.php", Void).headers as hds then
|
||||
across
|
||||
hds as c
|
||||
loop
|
||||
h.append (c.item.name + ": " + c.item.value + "%R%N")
|
||||
end
|
||||
end
|
||||
print (h)
|
||||
end
|
||||
|
||||
end
|
||||
@@ -1,19 +0,0 @@
|
||||
<?xml version="1.0" encoding="ISO-8859-1"?>
|
||||
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-14-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-14-0 http://www.eiffel.com/developers/xml/configuration-1-14-0.xsd" name="httptest" uuid="C8DFC1BA-78CA-4518-B0F2-2241E42808FF">
|
||||
<target name="httptest">
|
||||
<root class="APPLICATION" feature="make"/>
|
||||
<option warning="true" void_safety="all">
|
||||
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
|
||||
</option>
|
||||
<setting name="console_application" value="true"/>
|
||||
<library name="base" location="$ISE_LIBRARY\library\base\base-safe.ecf"/>
|
||||
<library name="http_client" location="..\http_client-safe.ecf"/>
|
||||
<cluster name="httptest" location=".\" recursive="true">
|
||||
<file_rule>
|
||||
<exclude>/EIFGENs$</exclude>
|
||||
<exclude>/CVS$</exclude>
|
||||
<exclude>/.svn$</exclude>
|
||||
</file_rule>
|
||||
</cluster>
|
||||
</target>
|
||||
</system>
|
||||
BIN
library/network/http_client/tests/logo.jpg
Normal file
BIN
library/network/http_client/tests/logo.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 8.7 KiB |
@@ -6,7 +6,13 @@ create
|
||||
feature -- Init
|
||||
|
||||
make
|
||||
local
|
||||
null: NULL_HTTP_CLIENT
|
||||
do
|
||||
create null
|
||||
if attached null.new_session ("http://example.com/") as l_sess then
|
||||
check not l_sess.is_available end
|
||||
end
|
||||
test_http_client
|
||||
end
|
||||
|
||||
@@ -42,7 +48,7 @@ feature -- Init
|
||||
do
|
||||
if not b then
|
||||
create e
|
||||
e.set_message (m)
|
||||
e.set_description (m)
|
||||
e.raise
|
||||
end
|
||||
end
|
||||
|
||||
1
library/network/http_client/tests/test.txt
Normal file
1
library/network/http_client/tests/test.txt
Normal file
@@ -0,0 +1 @@
|
||||
This is a text sample for testing HTTP Client library.
|
||||
@@ -44,8 +44,6 @@ feature -- Test routines
|
||||
assert ("missing body", False)
|
||||
end
|
||||
assert ("same headers", h.same_string (res.raw_header))
|
||||
else
|
||||
assert ("Not found", False)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -54,7 +52,6 @@ feature -- Test routines
|
||||
sess: like new_session
|
||||
h: STRING_8
|
||||
do
|
||||
--| Add your code here
|
||||
sess := new_session ("http://requestb.in")
|
||||
create h.make_empty
|
||||
if attached sess.get ("/1a0q2h61", Void).headers as hds then
|
||||
@@ -64,7 +61,6 @@ feature -- Test routines
|
||||
h.append (c.item.name + ": " + c.item.value + "%R%N")
|
||||
end
|
||||
end
|
||||
|
||||
print (h)
|
||||
end
|
||||
|
||||
|
||||
67
library/network/http_client/tests/test_libcurl_with_web.e
Normal file
67
library/network/http_client/tests/test_libcurl_with_web.e
Normal file
@@ -0,0 +1,67 @@
|
||||
note
|
||||
description: "[
|
||||
Objects that ...
|
||||
]"
|
||||
author: "$Author$"
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
class
|
||||
TEST_LIBCURL_WITH_WEB
|
||||
|
||||
inherit
|
||||
TEST_WITH_WEB_I
|
||||
|
||||
feature -- Factory
|
||||
|
||||
new_session (a_url: READABLE_STRING_8): HTTP_CLIENT_SESSION
|
||||
local
|
||||
cl: LIBCURL_HTTP_CLIENT
|
||||
do
|
||||
create cl
|
||||
Result := cl.new_session (a_url)
|
||||
end
|
||||
|
||||
feature -- Tests
|
||||
|
||||
libcurl_test_post_url_encoded
|
||||
do
|
||||
test_post_url_encoded
|
||||
end
|
||||
|
||||
libcurl_test_post_with_form_data
|
||||
do
|
||||
test_post_with_form_data
|
||||
end
|
||||
|
||||
libcurl_test_post_with_file
|
||||
do
|
||||
test_post_with_file
|
||||
end
|
||||
|
||||
libcurl_test_put_with_file
|
||||
do
|
||||
test_put_with_file
|
||||
end
|
||||
|
||||
libcurl_test_put_with_data
|
||||
do
|
||||
test_put_with_data
|
||||
end
|
||||
|
||||
libcurl_test_post_with_file_and_form_data
|
||||
do
|
||||
test_post_with_file_and_form_data
|
||||
end
|
||||
|
||||
libcurl_test_get_with_redirection
|
||||
do
|
||||
test_get_with_redirection
|
||||
end
|
||||
|
||||
libcurl_test_get_with_authentication
|
||||
do
|
||||
test_get_with_authentication
|
||||
end
|
||||
|
||||
end
|
||||
67
library/network/http_client/tests/test_net_with_web.e
Normal file
67
library/network/http_client/tests/test_net_with_web.e
Normal file
@@ -0,0 +1,67 @@
|
||||
note
|
||||
description: "[
|
||||
Objects that ...
|
||||
]"
|
||||
author: "$Author$"
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
class
|
||||
TEST_NET_WITH_WEB
|
||||
|
||||
inherit
|
||||
TEST_WITH_WEB_I
|
||||
|
||||
feature -- Factory
|
||||
|
||||
new_session (a_url: READABLE_STRING_8): HTTP_CLIENT_SESSION
|
||||
local
|
||||
cl: NET_HTTP_CLIENT
|
||||
do
|
||||
create cl
|
||||
Result := cl.new_session (a_url)
|
||||
end
|
||||
|
||||
feature -- Tests
|
||||
|
||||
net_test_post_url_encoded
|
||||
do
|
||||
test_post_url_encoded
|
||||
end
|
||||
|
||||
net_test_post_with_form_data
|
||||
do
|
||||
test_post_with_form_data
|
||||
end
|
||||
|
||||
net_test_post_with_file
|
||||
do
|
||||
test_post_with_file
|
||||
end
|
||||
|
||||
net_test_put_with_file
|
||||
do
|
||||
test_put_with_file
|
||||
end
|
||||
|
||||
net_test_put_with_data
|
||||
do
|
||||
test_put_with_data
|
||||
end
|
||||
|
||||
net_test_post_with_file_and_form_data
|
||||
do
|
||||
test_post_with_file_and_form_data
|
||||
end
|
||||
|
||||
net_test_get_with_redirection
|
||||
do
|
||||
test_get_with_redirection
|
||||
end
|
||||
|
||||
net_test_get_with_authentication
|
||||
do
|
||||
test_get_with_authentication
|
||||
end
|
||||
|
||||
end
|
||||
302
library/network/http_client/tests/test_with_web_i.e
Normal file
302
library/network/http_client/tests/test_with_web_i.e
Normal file
@@ -0,0 +1,302 @@
|
||||
note
|
||||
description: "[
|
||||
Eiffel tests that can be executed by testing tool.
|
||||
]"
|
||||
author: "EiffelStudio test wizard"
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
testing: "type/manual"
|
||||
|
||||
deferred class
|
||||
TEST_WITH_WEB_I
|
||||
|
||||
inherit
|
||||
EQA_TEST_SET
|
||||
redefine
|
||||
on_prepare
|
||||
end
|
||||
|
||||
feature -- Initialization
|
||||
|
||||
on_prepare
|
||||
do
|
||||
Precursor
|
||||
global_requestbin_path := new_requestbin_path
|
||||
end
|
||||
|
||||
feature -- Factory
|
||||
|
||||
new_session (a_url: READABLE_STRING_8): HTTP_CLIENT_SESSION
|
||||
deferred
|
||||
end
|
||||
|
||||
feature -- Requestbin
|
||||
|
||||
global_requestbin_path: detachable READABLE_STRING_8
|
||||
|
||||
new_requestbin_path: detachable STRING
|
||||
local
|
||||
i,j: INTEGER
|
||||
do
|
||||
if
|
||||
attached new_session ("http://requestb.in") as sess and then
|
||||
attached sess.post ("/api/v1/bins", Void, Void) as resp
|
||||
then
|
||||
if resp.error_occurred then
|
||||
print ("Error occurred!%N")
|
||||
elseif attached resp.body as l_content then
|
||||
|
||||
i := l_content.substring_index ("%"name%":", 1)
|
||||
if i > 0 then
|
||||
j := l_content.index_of (',', i + 1)
|
||||
if j > 0 then
|
||||
Result := l_content.substring (i + 7, j - 1)
|
||||
Result.adjust
|
||||
if Result.starts_with ("%"") then
|
||||
Result.remove_head (1)
|
||||
end
|
||||
if Result.ends_with ("%"") then
|
||||
Result.remove_tail (1)
|
||||
end
|
||||
if not Result.starts_with ("/") then
|
||||
Result.prepend_character ('/')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
feature -- Factory
|
||||
|
||||
test_post_url_encoded
|
||||
local
|
||||
sess: HTTP_CLIENT_SESSION
|
||||
h: STRING_8
|
||||
do
|
||||
if attached global_requestbin_path as requestbin_path then
|
||||
-- URL ENCODED POST REQUEST
|
||||
-- check requestbin to ensure the "Hello World" has been received in the raw body
|
||||
-- also check that User-Agent was sent
|
||||
create h.make_empty
|
||||
sess := new_session ("http://requestb.in")
|
||||
if
|
||||
attached sess.post (requestbin_path, Void, "Hello World") as res and then
|
||||
attached res.headers as hds
|
||||
then
|
||||
across
|
||||
hds as c
|
||||
loop
|
||||
h.append (c.item.name + ": " + c.item.value + "%R%N")
|
||||
end
|
||||
end
|
||||
print (h)
|
||||
else
|
||||
assert ("Has requestbin path", False)
|
||||
end
|
||||
end
|
||||
|
||||
test_post_with_form_data
|
||||
local
|
||||
sess: HTTP_CLIENT_SESSION
|
||||
h: STRING_8
|
||||
l_ctx: HTTP_CLIENT_REQUEST_CONTEXT
|
||||
do
|
||||
if attached global_requestbin_path as requestbin_path then
|
||||
|
||||
-- POST REQUEST WITH FORM DATA
|
||||
-- check requestbin to ensure the form parameters are correctly received
|
||||
sess := new_session ("http://requestb.in")
|
||||
create l_ctx.make
|
||||
l_ctx.form_parameters.extend ("First Value", "First Key")
|
||||
l_ctx.form_parameters.extend ("Second Value", "Second Key")
|
||||
create h.make_empty
|
||||
if
|
||||
attached sess.post (requestbin_path, l_ctx, "") as res and then
|
||||
attached res.headers as hds
|
||||
then
|
||||
across
|
||||
hds as c
|
||||
loop
|
||||
h.append (c.item.name + ": " + c.item.value + "%R%N")
|
||||
end
|
||||
end
|
||||
print (h)
|
||||
else
|
||||
assert ("Has requestbin path", False)
|
||||
end
|
||||
end
|
||||
|
||||
test_post_with_file
|
||||
local
|
||||
sess: HTTP_CLIENT_SESSION
|
||||
h: STRING_8
|
||||
l_ctx: HTTP_CLIENT_REQUEST_CONTEXT
|
||||
do
|
||||
if attached global_requestbin_path as requestbin_path then
|
||||
|
||||
-- POST REQUEST WITH A FILE
|
||||
-- check requestbin to ensure the form parameters are correctly received
|
||||
-- set filename to a local file
|
||||
sess := new_session ("http://requestb.in")
|
||||
create l_ctx.make
|
||||
l_ctx.set_upload_filename ("test.txt")
|
||||
create h.make_empty
|
||||
if
|
||||
attached sess.post (requestbin_path, l_ctx, "") as res and then
|
||||
attached res.headers as hds
|
||||
then
|
||||
across
|
||||
hds as c
|
||||
loop
|
||||
h.append (c.item.name + ": " + c.item.value + "%R%N")
|
||||
end
|
||||
end
|
||||
print (h)
|
||||
else
|
||||
assert ("Has requestbin path", False)
|
||||
end
|
||||
end
|
||||
|
||||
test_put_with_file
|
||||
local
|
||||
sess: HTTP_CLIENT_SESSION
|
||||
h: STRING_8
|
||||
l_ctx: HTTP_CLIENT_REQUEST_CONTEXT
|
||||
do
|
||||
if attached global_requestbin_path as requestbin_path then
|
||||
|
||||
-- PUT REQUEST WITH A FILE
|
||||
-- check requestbin to ensure the file is correctly received
|
||||
-- set filename to a local file
|
||||
sess := new_session ("http://requestb.in")
|
||||
create l_ctx.make
|
||||
l_ctx.set_upload_filename ("test.txt")
|
||||
create h.make_empty
|
||||
if
|
||||
attached sess.put (requestbin_path, l_ctx, Void) as res and then
|
||||
attached res.headers as hds
|
||||
then
|
||||
across
|
||||
hds as c
|
||||
loop
|
||||
h.append (c.item.name + ": " + c.item.value + "%R%N")
|
||||
end
|
||||
end
|
||||
print (h)
|
||||
else
|
||||
assert ("Has requestbin path", False)
|
||||
end
|
||||
end
|
||||
|
||||
test_put_with_data
|
||||
local
|
||||
sess: HTTP_CLIENT_SESSION
|
||||
h: STRING_8
|
||||
l_ctx: HTTP_CLIENT_REQUEST_CONTEXT
|
||||
do
|
||||
if attached global_requestbin_path as requestbin_path then
|
||||
|
||||
-- PUT REQUEST WITH A FILE
|
||||
-- check requestbin to ensure the file is correctly received
|
||||
-- set filename to a local file
|
||||
sess := new_session ("http://requestb.in")
|
||||
create l_ctx.make
|
||||
l_ctx.set_upload_data ("This is a test for http client.%N")
|
||||
create h.make_empty
|
||||
if
|
||||
attached sess.put (requestbin_path, l_ctx, Void) as res and then
|
||||
attached res.headers as hds
|
||||
then
|
||||
across
|
||||
hds as c
|
||||
loop
|
||||
h.append (c.item.name + ": " + c.item.value + "%R%N")
|
||||
end
|
||||
end
|
||||
print (h)
|
||||
else
|
||||
assert ("Has requestbin path", False)
|
||||
end
|
||||
end
|
||||
|
||||
test_post_with_file_and_form_data
|
||||
local
|
||||
sess: HTTP_CLIENT_SESSION
|
||||
h: STRING_8
|
||||
l_ctx: HTTP_CLIENT_REQUEST_CONTEXT
|
||||
do
|
||||
if attached global_requestbin_path as requestbin_path then
|
||||
|
||||
-- POST REQUEST WITH A FILE AND FORM DATA
|
||||
-- check requestbin to ensure the file and form parameters are correctly received
|
||||
-- set filename to a local file
|
||||
sess := new_session ("http://requestb.in")
|
||||
create l_ctx.make
|
||||
l_ctx.set_upload_filename ("logo.jpg")
|
||||
l_ctx.form_parameters.extend ("First Value", "First Key")
|
||||
l_ctx.form_parameters.extend ("Second Value", "Second Key")
|
||||
create h.make_empty
|
||||
if
|
||||
attached sess.post (requestbin_path, l_ctx, Void) as res and then
|
||||
attached res.headers as hds
|
||||
then
|
||||
across
|
||||
hds as c
|
||||
loop
|
||||
h.append (c.item.name + ": " + c.item.value + "%R%N")
|
||||
end
|
||||
end
|
||||
print (h)
|
||||
else
|
||||
assert ("Has requestbin path", False)
|
||||
end
|
||||
end
|
||||
|
||||
test_get_with_redirection
|
||||
local
|
||||
sess: HTTP_CLIENT_SESSION
|
||||
h: STRING_8
|
||||
do
|
||||
if attached global_requestbin_path as requestbin_path then
|
||||
|
||||
-- GET REQUEST, Forwarding (google's first answer is a forward)
|
||||
-- check headers received (printed in console)
|
||||
sess := new_session ("http://google.com")
|
||||
create h.make_empty
|
||||
if attached sess.get ("/", Void) as res and then attached res.headers as hds then
|
||||
across
|
||||
hds as c
|
||||
loop
|
||||
h.append (c.item.name + ": " + c.item.value + "%R%N")
|
||||
end
|
||||
end
|
||||
print (h)
|
||||
else
|
||||
assert ("Has requestbin path", False)
|
||||
end
|
||||
end
|
||||
|
||||
test_get_with_authentication
|
||||
local
|
||||
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)
|
||||
sess := 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
|
||||
|
||||
|
||||
end
|
||||
|
||||
|
||||
Reference in New Issue
Block a user