Change structure of EWF, to follow better categorization
This commit is contained in:
13
library/network/http_client/README.md
Normal file
13
library/network/http_client/README.md
Normal file
@@ -0,0 +1,13 @@
|
||||
# simple HTTP client
|
||||
|
||||
## Overview
|
||||
|
||||
## Requirements
|
||||
* Eiffel cURL library
|
||||
* cURL dynamic libraries in the PATH or the current directory (.dll or .so)
|
||||
|
||||
## Usage
|
||||
|
||||
## Examples
|
||||
|
||||
|
||||
27
library/network/http_client/http_client-safe.ecf
Normal file
27
library/network/http_client/http_client-safe.ecf
Normal file
@@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="ISO-8859-1"?>
|
||||
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-9-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-9-0 http://www.eiffel.com/developers/xml/configuration-1-9-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>
|
||||
<exclude>/.git$</exclude>
|
||||
<exclude>/EIFGENs$</exclude>
|
||||
<exclude>/.svn$</exclude>
|
||||
</file_rule>
|
||||
<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="net" location="$ISE_LIBRARY\library\net\net-safe.ecf"/>
|
||||
<library name="curl" location="$ISE_LIBRARY\library\cURL\cURL-safe.ecf">
|
||||
<condition>
|
||||
<version type="compiler" min="7.1.8.8674"/>
|
||||
</condition>
|
||||
</library>
|
||||
<library name="curl_local" location="..\..\..\contrib\ise_library\cURL-safe.ecf">
|
||||
<condition>
|
||||
<version type="compiler" max="7.1.8.8673"/>
|
||||
</condition>
|
||||
</library>
|
||||
<library name="encoder" location="..\..\text\encoder\encoder-safe.ecf"/>
|
||||
<cluster name="src" location=".\src\" recursive="true"/>
|
||||
</target>
|
||||
</system>
|
||||
27
library/network/http_client/http_client.ecf
Normal file
27
library/network/http_client/http_client.ecf
Normal file
@@ -0,0 +1,27 @@
|
||||
<?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">
|
||||
<target name="http_client">
|
||||
<root all_classes="true"/>
|
||||
<file_rule>
|
||||
<exclude>/.git$</exclude>
|
||||
<exclude>/EIFGENs$</exclude>
|
||||
<exclude>/.svn$</exclude>
|
||||
</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="net" location="$ISE_LIBRARY\library\net\net.ecf"/>
|
||||
<library name="curl" location="$ISE_LIBRARY\library\cURL\cURL.ecf">
|
||||
<condition>
|
||||
<version type="compiler" min="7.1.8.8674"/>
|
||||
</condition>
|
||||
</library>
|
||||
<library name="curl_local" location="..\..\..\contrib\ise_library\cURL.ecf">
|
||||
<condition>
|
||||
<version type="compiler" max="7.1.8.8673"/>
|
||||
</condition>
|
||||
</library>
|
||||
<library name="encoder" location="../../text/encoder/encoder.ecf"/>
|
||||
<cluster name="src" location=".\src\" recursive="true"/>
|
||||
</target>
|
||||
</system>
|
||||
10
library/network/http_client/license.lic
Normal file
10
library/network/http_client/license.lic
Normal file
@@ -0,0 +1,10 @@
|
||||
${NOTE_KEYWORD}
|
||||
copyright: "2011-${YEAR}, 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
|
||||
]"
|
||||
29
library/network/http_client/src/http_client.e
Normal file
29
library/network/http_client/src/http_client.e
Normal file
@@ -0,0 +1,29 @@
|
||||
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$"
|
||||
|
||||
deferred class
|
||||
HTTP_CLIENT
|
||||
|
||||
feature -- Status
|
||||
|
||||
new_session (a_base_url: READABLE_STRING_8): HTTP_CLIENT_SESSION
|
||||
-- Create a new session using `a_base_url'.
|
||||
deferred
|
||||
end
|
||||
|
||||
note
|
||||
copyright: "2011-2012, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
|
||||
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
|
||||
source: "[
|
||||
Eiffel Software
|
||||
5949 Hollister Ave., Goleta, CA 93117 USA
|
||||
Telephone 805-685-1006, Fax 805-685-6869
|
||||
Website http://www.eiffel.com
|
||||
Customer support http://support.eiffel.com
|
||||
]"
|
||||
end
|
||||
47
library/network/http_client/src/http_client_constants.e
Normal file
47
library/network/http_client/src/http_client_constants.e
Normal file
@@ -0,0 +1,47 @@
|
||||
note
|
||||
description: "Summary description for {HTTP_CLIENT_CONSTANTS}."
|
||||
author: ""
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
class
|
||||
HTTP_CLIENT_CONSTANTS
|
||||
|
||||
feature -- Auth type
|
||||
|
||||
auth_type_id (a_auth_type_string: READABLE_STRING_8): INTEGER
|
||||
local
|
||||
s: STRING_8
|
||||
do
|
||||
create s.make_from_string (a_auth_type_string)
|
||||
s.to_lower
|
||||
if s.same_string_general ("basic") then
|
||||
Result := Auth_type_basic
|
||||
elseif s.same_string_general ("digest") then
|
||||
Result := Auth_type_digest
|
||||
elseif s.same_string_general ("any") then
|
||||
Result := Auth_type_any
|
||||
elseif s.same_string_general ("anysafe") then
|
||||
Result := Auth_type_anysafe
|
||||
elseif s.same_string_general ("none") then
|
||||
Result := Auth_type_none
|
||||
end
|
||||
end
|
||||
|
||||
Auth_type_none: INTEGER = 0
|
||||
Auth_type_basic: INTEGER = 1
|
||||
Auth_type_digest: INTEGER = 2
|
||||
Auth_type_any: INTEGER = 3
|
||||
Auth_type_anysafe: INTEGER = 4
|
||||
|
||||
note
|
||||
copyright: "2011-2012, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
|
||||
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
|
||||
source: "[
|
||||
Eiffel Software
|
||||
5949 Hollister Ave., Goleta, CA 93117 USA
|
||||
Telephone 805-685-1006, Fax 805-685-6869
|
||||
Website http://www.eiffel.com
|
||||
Customer support http://support.eiffel.com
|
||||
]"
|
||||
end
|
||||
230
library/network/http_client/src/http_client_request.e
Normal file
230
library/network/http_client/src/http_client_request.e
Normal file
@@ -0,0 +1,230 @@
|
||||
note
|
||||
description: "[
|
||||
Object representing a http client request
|
||||
It is mainly used internally by the HTTP_CLIENT_SESSION
|
||||
]"
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
deferred class
|
||||
HTTP_CLIENT_REQUEST
|
||||
|
||||
inherit
|
||||
REFACTORING_HELPER
|
||||
|
||||
feature {NONE} -- Initialization
|
||||
|
||||
make (a_url: READABLE_STRING_8; a_session: like session; ctx: like context)
|
||||
-- Initialize `Current'.
|
||||
do
|
||||
session := a_session
|
||||
url := a_url
|
||||
headers := session.headers.twin
|
||||
if ctx /= Void then
|
||||
context := ctx
|
||||
import (ctx)
|
||||
end
|
||||
ensure
|
||||
context_set: context = ctx
|
||||
ctx_header_set: ctx /= Void implies across ctx.headers as ctx_h all attached headers.item (ctx_h.key) as v and then v.same_string (ctx_h.item) end
|
||||
end
|
||||
|
||||
session: HTTP_CLIENT_SESSION
|
||||
|
||||
context: detachable HTTP_CLIENT_REQUEST_CONTEXT
|
||||
|
||||
feature -- Status report
|
||||
|
||||
is_debug: BOOLEAN
|
||||
-- Debug mode enabled?
|
||||
do
|
||||
Result := session.is_debug
|
||||
end
|
||||
|
||||
feature -- Access
|
||||
|
||||
request_method: READABLE_STRING_8
|
||||
deferred
|
||||
end
|
||||
|
||||
url: READABLE_STRING_8
|
||||
|
||||
headers: HASH_TABLE [READABLE_STRING_8, READABLE_STRING_8]
|
||||
|
||||
feature {HTTP_CLIENT_SESSION} -- Execution
|
||||
|
||||
import (ctx: HTTP_CLIENT_REQUEST_CONTEXT)
|
||||
local
|
||||
l_headers: like headers
|
||||
do
|
||||
l_headers := headers
|
||||
across
|
||||
ctx.headers as ctx_headers
|
||||
loop
|
||||
--| fill header from `ctx'
|
||||
--| and use `force' to overwrite the "session" value if any
|
||||
l_headers.force (ctx_headers.item, ctx_headers.key)
|
||||
end
|
||||
end
|
||||
|
||||
execute: HTTP_CLIENT_RESPONSE
|
||||
deferred
|
||||
end
|
||||
|
||||
feature -- Authentication
|
||||
|
||||
auth_type: STRING
|
||||
-- Set the authentication type for the request.
|
||||
-- Types: "basic", "digest", "any"
|
||||
do
|
||||
Result := session.auth_type
|
||||
end
|
||||
|
||||
auth_type_id: INTEGER
|
||||
-- Set the authentication type for the request.
|
||||
-- Types: "basic", "digest", "any"
|
||||
do
|
||||
Result := session.auth_type_id
|
||||
end
|
||||
|
||||
username: detachable READABLE_STRING_32
|
||||
do
|
||||
Result := session.username
|
||||
end
|
||||
|
||||
password: detachable READABLE_STRING_32
|
||||
do
|
||||
Result := session.password
|
||||
end
|
||||
|
||||
credentials: detachable READABLE_STRING_32
|
||||
do
|
||||
Result := session.credentials
|
||||
end
|
||||
|
||||
proxy: detachable TUPLE [host: READABLE_STRING_8; port: INTEGER]
|
||||
do
|
||||
Result := session.proxy
|
||||
end
|
||||
|
||||
feature -- Settings
|
||||
|
||||
timeout: INTEGER
|
||||
-- HTTP transaction timeout in seconds.
|
||||
do
|
||||
Result := session.timeout
|
||||
end
|
||||
|
||||
connect_timeout: INTEGER
|
||||
-- HTTP connection timeout in seconds.
|
||||
do
|
||||
Result := session.connect_timeout
|
||||
end
|
||||
|
||||
max_redirects: INTEGER
|
||||
-- Maximum number of times to follow redirects.
|
||||
do
|
||||
Result := session.max_redirects
|
||||
end
|
||||
|
||||
ignore_content_length: BOOLEAN
|
||||
-- Does this session ignore Content-Size headers?
|
||||
do
|
||||
Result := session.ignore_content_length
|
||||
end
|
||||
|
||||
buffer_size: NATURAL
|
||||
-- Set the buffer size for request. This option will
|
||||
-- only be set if buffer_size is positive
|
||||
do
|
||||
Result := session.buffer_size
|
||||
end
|
||||
|
||||
default_response_charset: detachable READABLE_STRING_8
|
||||
-- Default encoding of responses. Used if no charset is provided by the host.
|
||||
do
|
||||
Result := session.default_response_charset
|
||||
end
|
||||
|
||||
is_insecure: BOOLEAN
|
||||
-- Allow connections to SSL sites without certs
|
||||
do
|
||||
Result := session.is_insecure
|
||||
end
|
||||
|
||||
feature {NONE} -- Utilities
|
||||
|
||||
append_parameters_to_url (a_parameters: HASH_TABLE [READABLE_STRING_32, READABLE_STRING_32]; a_url: STRING)
|
||||
-- Append parameters `a_parameters' to `a_url'
|
||||
require
|
||||
a_url_attached: a_url /= Void
|
||||
local
|
||||
l_first_param: BOOLEAN
|
||||
do
|
||||
if a_parameters.count > 0 then
|
||||
if a_url.index_of ('?', 1) > 0 then
|
||||
l_first_param := False
|
||||
elseif a_url.index_of ('&', 1) > 0 then
|
||||
l_first_param := False
|
||||
else
|
||||
l_first_param := True
|
||||
end
|
||||
|
||||
from
|
||||
a_parameters.start
|
||||
until
|
||||
a_parameters.after
|
||||
loop
|
||||
if l_first_param then
|
||||
a_url.append_character ('?')
|
||||
else
|
||||
a_url.append_character ('&')
|
||||
end
|
||||
a_url.append (urlencode (a_parameters.key_for_iteration))
|
||||
a_url.append_character ('=')
|
||||
a_url.append (urlencode (a_parameters.item_for_iteration))
|
||||
l_first_param := False
|
||||
a_parameters.forth
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
feature {NONE} -- Utilities: encoding
|
||||
|
||||
url_encoder: URL_ENCODER
|
||||
once
|
||||
create Result
|
||||
end
|
||||
|
||||
urlencode (s: READABLE_STRING_32): READABLE_STRING_8
|
||||
-- URL encode `s'
|
||||
do
|
||||
Result := url_encoder.encoded_string (s)
|
||||
end
|
||||
|
||||
urldecode (s: READABLE_STRING_8): READABLE_STRING_32
|
||||
-- URL decode `s'
|
||||
do
|
||||
Result := url_encoder.decoded_string (s)
|
||||
end
|
||||
|
||||
stripslashes (s: STRING): STRING
|
||||
do
|
||||
Result := s.string
|
||||
Result.replace_substring_all ("\%"", "%"")
|
||||
Result.replace_substring_all ("\'", "'")
|
||||
Result.replace_substring_all ("\/", "/")
|
||||
Result.replace_substring_all ("\\", "\")
|
||||
end
|
||||
|
||||
note
|
||||
copyright: "2011-2012, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
|
||||
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
|
||||
source: "[
|
||||
Eiffel Software
|
||||
5949 Hollister Ave., Goleta, CA 93117 USA
|
||||
Telephone 805-685-1006, Fax 805-685-6869
|
||||
Website http://www.eiffel.com
|
||||
Customer support http://support.eiffel.com
|
||||
]"
|
||||
end
|
||||
189
library/network/http_client/src/http_client_request_context.e
Normal file
189
library/network/http_client/src/http_client_request_context.e
Normal file
@@ -0,0 +1,189 @@
|
||||
note
|
||||
description: "[
|
||||
Context for HTTP client request
|
||||
This is used to hold
|
||||
- headers
|
||||
- query_parameters
|
||||
- form parameters
|
||||
- upload_data or upload_filename
|
||||
And in addition it has
|
||||
- credentials_required
|
||||
- proxy
|
||||
|
||||
Note that any value set in this context class overrides conflicting value eventually
|
||||
set in associated HTTP_CLIENT_SESSION.
|
||||
|
||||
Warning: for now [2012-May], you can have only one of the following data
|
||||
- form_parameters
|
||||
- or upload_data
|
||||
- or upload_filename
|
||||
If you set more than one, the priority is then upload_data, then upload_filename, then form_parameters
|
||||
]"
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
class
|
||||
HTTP_CLIENT_REQUEST_CONTEXT
|
||||
|
||||
create
|
||||
make,
|
||||
make_with_credentials_required
|
||||
|
||||
feature {NONE} -- Initialization
|
||||
|
||||
make
|
||||
do
|
||||
create headers.make (2)
|
||||
create query_parameters.make (5)
|
||||
create form_parameters.make (10)
|
||||
end
|
||||
|
||||
make_with_credentials_required
|
||||
do
|
||||
make
|
||||
set_credentials_required (True)
|
||||
end
|
||||
|
||||
feature -- Settings
|
||||
|
||||
credentials_required: BOOLEAN
|
||||
-- If True, the request will precise the HTTP_AUTHORIZATION.
|
||||
|
||||
proxy: detachable TUPLE [host: READABLE_STRING_8; port: INTEGER]
|
||||
-- Optional proxy, see {HTTP_CLIENT_SESSION}.proxy
|
||||
|
||||
feature -- Access
|
||||
|
||||
headers: HASH_TABLE [READABLE_STRING_8, READABLE_STRING_8]
|
||||
-- Specific headers to use in addition to the one set in the related HTTP_CLIENT_SESSION
|
||||
--| note: the value from Current context override the one from the session in case of conflict
|
||||
|
||||
query_parameters: HASH_TABLE [READABLE_STRING_32, READABLE_STRING_32]
|
||||
-- Query parameters to be appended to the url
|
||||
--| note: if the url already contains a query_string, the `query_parameters' will be appended to the url
|
||||
|
||||
form_parameters: HASH_TABLE [READABLE_STRING_32, READABLE_STRING_32]
|
||||
-- Form parameters
|
||||
|
||||
upload_data: detachable READABLE_STRING_8
|
||||
-- Upload data
|
||||
--| Note: make sure to precise the Content-Type header
|
||||
|
||||
upload_filename: detachable READABLE_STRING_8
|
||||
-- Upload data read from `upload_filename'
|
||||
--| Note: make sure to precise the Content-Type header
|
||||
|
||||
feature -- Status report
|
||||
|
||||
has_form_data: BOOLEAN
|
||||
do
|
||||
Result := not form_parameters.is_empty
|
||||
end
|
||||
|
||||
has_upload_data: BOOLEAN
|
||||
do
|
||||
Result := attached upload_data as d and then not d.is_empty
|
||||
end
|
||||
|
||||
has_upload_filename: BOOLEAN
|
||||
do
|
||||
Result := attached upload_filename as fn and then not fn.is_empty
|
||||
end
|
||||
|
||||
feature -- Element change
|
||||
|
||||
add_header (k: READABLE_STRING_8; v: READABLE_STRING_8)
|
||||
do
|
||||
headers.force (v, k)
|
||||
end
|
||||
|
||||
add_query_parameter (k: READABLE_STRING_32; v: READABLE_STRING_32)
|
||||
do
|
||||
query_parameters.force (v, k)
|
||||
end
|
||||
|
||||
add_form_parameter (k: READABLE_STRING_32; v: READABLE_STRING_32)
|
||||
do
|
||||
form_parameters.force (v, k)
|
||||
end
|
||||
|
||||
set_credentials_required (b: BOOLEAN)
|
||||
do
|
||||
credentials_required := b
|
||||
end
|
||||
|
||||
set_upload_data (a_data: like upload_data)
|
||||
require
|
||||
has_no_upload_data: not has_upload_data
|
||||
do
|
||||
upload_data := a_data
|
||||
end
|
||||
|
||||
set_upload_filename (a_fn: like upload_filename)
|
||||
require
|
||||
has_no_upload_filename: not has_upload_filename
|
||||
do
|
||||
upload_filename := a_fn
|
||||
end
|
||||
|
||||
feature -- Status setting
|
||||
|
||||
set_proxy (a_host: detachable READABLE_STRING_8; a_port: INTEGER)
|
||||
do
|
||||
if a_host = Void then
|
||||
proxy := Void
|
||||
else
|
||||
proxy := [a_host, a_port]
|
||||
end
|
||||
end
|
||||
|
||||
feature -- Conversion helpers
|
||||
|
||||
query_parameters_to_url_encoded_string: STRING_8
|
||||
-- `query_parameters' as url-encoded string.
|
||||
do
|
||||
Result := parameters_to_url_encoded_string (query_parameters)
|
||||
end
|
||||
|
||||
form_parameters_to_url_encoded_string: STRING_8
|
||||
-- `form_parameters' as url-encoded string.
|
||||
do
|
||||
Result := parameters_to_url_encoded_string (form_parameters)
|
||||
end
|
||||
|
||||
feature {NONE} -- Implementation
|
||||
|
||||
parameters_to_url_encoded_string (ht: HASH_TABLE [READABLE_STRING_32, READABLE_STRING_32]): STRING_8
|
||||
do
|
||||
create Result.make (64)
|
||||
from
|
||||
ht.start
|
||||
until
|
||||
ht.after
|
||||
loop
|
||||
if not Result.is_empty then
|
||||
Result.append_character ('&')
|
||||
end
|
||||
Result.append (url_encoder.encoded_string (ht.key_for_iteration))
|
||||
Result.append_character ('=')
|
||||
Result.append (url_encoder.encoded_string (ht.item_for_iteration))
|
||||
ht.forth
|
||||
end
|
||||
end
|
||||
|
||||
url_encoder: URL_ENCODER
|
||||
once
|
||||
create Result
|
||||
end
|
||||
|
||||
note
|
||||
copyright: "2011-2012, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
|
||||
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
|
||||
source: "[
|
||||
Eiffel Software
|
||||
5949 Hollister Ave., Goleta, CA 93117 USA
|
||||
Telephone 805-685-1006, Fax 805-685-6869
|
||||
Website http://www.eiffel.com
|
||||
Customer support http://support.eiffel.com
|
||||
]"
|
||||
end
|
||||
177
library/network/http_client/src/http_client_response.e
Normal file
177
library/network/http_client/src/http_client_response.e
Normal file
@@ -0,0 +1,177 @@
|
||||
note
|
||||
description : "[
|
||||
Response retrieved by the client
|
||||
]"
|
||||
date : "$Date$"
|
||||
revision : "$Revision$"
|
||||
|
||||
class
|
||||
HTTP_CLIENT_RESPONSE
|
||||
|
||||
create
|
||||
make
|
||||
|
||||
feature {NONE} -- Initialization
|
||||
|
||||
make
|
||||
-- Initialize `Current'.
|
||||
do
|
||||
--| Default values
|
||||
status := 200
|
||||
create {STRING_8} raw_header.make_empty
|
||||
end
|
||||
|
||||
feature -- Status
|
||||
|
||||
error_occurred: BOOLEAN
|
||||
-- Error occurred during request
|
||||
|
||||
feature {HTTP_CLIENT_REQUEST} -- Status setting
|
||||
|
||||
set_error_occurred (b: BOOLEAN)
|
||||
-- Set `error_occurred' to `b'
|
||||
do
|
||||
error_occurred := b
|
||||
end
|
||||
|
||||
feature -- Access
|
||||
|
||||
status: INTEGER assign set_status
|
||||
-- Status code of the response.
|
||||
|
||||
raw_header: READABLE_STRING_8
|
||||
-- Raw http header of the response.
|
||||
|
||||
header (a_name: READABLE_STRING_8): detachable READABLE_STRING_8
|
||||
-- Header entry value related to `a_name'
|
||||
-- if multiple entries, just concatenate them using comma character
|
||||
--| See: http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html
|
||||
--| Multiple message-header fields with the same field-name MAY be present in a message
|
||||
--| if and only if the entire field-value for that header field is defined as a comma-separated list [i.e., #(values)].
|
||||
--| It MUST be possible to combine the multiple header fields into one "field-name: field-value" pair,
|
||||
--| without changing the semantics of the message, by appending each subsequent field-value to the first, each separated by a comma.
|
||||
--| The order in which header fields with the same field-name are received is therefore significant
|
||||
--| to the interpretation of the combined field value, and thus a proxy MUST NOT change the order of
|
||||
--| these field values when a message is forwarded.
|
||||
local
|
||||
s: detachable STRING_8
|
||||
k,v: READABLE_STRING_8
|
||||
do
|
||||
across
|
||||
headers as hds
|
||||
loop
|
||||
k := hds.item.name
|
||||
if k.same_string (a_name) then
|
||||
v := hds.item.value
|
||||
if s = Void then
|
||||
create s.make_from_string (v)
|
||||
else
|
||||
s.append_character (',')
|
||||
s.append (v)
|
||||
end
|
||||
end
|
||||
end
|
||||
Result := s
|
||||
end
|
||||
|
||||
headers: LIST [TUPLE [name: READABLE_STRING_8; value: READABLE_STRING_8]]
|
||||
-- Computed table of http headers of the response.
|
||||
--| We use a LIST since one might have multiple message-header fields with the same field-name
|
||||
--| Then the user can handle those case using default or custom concatenation
|
||||
--| (note: `header' is concatenating using comma)
|
||||
local
|
||||
tb: like internal_headers
|
||||
pos, l_start, l_end, n, c: INTEGER
|
||||
h: like raw_header
|
||||
k: STRING_8
|
||||
do
|
||||
tb := internal_headers
|
||||
if tb = Void then
|
||||
create tb.make (3)
|
||||
h := raw_header
|
||||
from
|
||||
pos := 1
|
||||
n := h.count
|
||||
until
|
||||
pos = 0 or pos > n
|
||||
loop
|
||||
l_start := pos
|
||||
--| Left justify
|
||||
from until not h[l_start].is_space loop
|
||||
l_start := l_start + 1
|
||||
end
|
||||
pos := h.index_of ('%N', l_start)
|
||||
if pos > 0 then
|
||||
l_end := pos - 1
|
||||
elseif l_start < n then
|
||||
l_end := n + 1
|
||||
else
|
||||
-- Empty line
|
||||
l_end := 0
|
||||
end
|
||||
if l_end > 0 then
|
||||
--| Right justify
|
||||
from until not h[l_end].is_space loop
|
||||
l_end := l_end - 1
|
||||
end
|
||||
c := h.index_of (':', l_start)
|
||||
if c > 0 then
|
||||
k := h.substring (l_start, c - 1)
|
||||
k.right_adjust
|
||||
c := c + 1
|
||||
from until c <= n and not h[c].is_space loop
|
||||
c := c + 1
|
||||
end
|
||||
tb.force ([k, h.substring (c, l_end)])
|
||||
else
|
||||
check header_has_colon: c > 0 end
|
||||
end
|
||||
end
|
||||
pos := pos + 1
|
||||
end
|
||||
internal_headers := tb
|
||||
end
|
||||
Result := tb
|
||||
end
|
||||
|
||||
body: detachable READABLE_STRING_8 assign set_body
|
||||
-- Content of the response
|
||||
|
||||
feature -- Change
|
||||
|
||||
set_status (s: INTEGER)
|
||||
-- Set response `status' code to `s'
|
||||
do
|
||||
status := s
|
||||
end
|
||||
|
||||
set_raw_header (h: READABLE_STRING_8)
|
||||
-- Set http header `raw_header' to `h'
|
||||
do
|
||||
raw_header := h
|
||||
--| Reset internal headers
|
||||
internal_headers := Void
|
||||
end
|
||||
|
||||
set_body (s: like body)
|
||||
-- Set `body' message to `s'
|
||||
do
|
||||
body := s
|
||||
end
|
||||
|
||||
feature {NONE} -- Implementation
|
||||
|
||||
internal_headers: detachable ARRAYED_LIST [TUPLE [key: READABLE_STRING_8; value: READABLE_STRING_8]]
|
||||
-- Internal cached value for the headers
|
||||
|
||||
;note
|
||||
copyright: "2011-2012, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
|
||||
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
|
||||
source: "[
|
||||
Eiffel Software
|
||||
5949 Hollister Ave., Goleta, CA 93117 USA
|
||||
Telephone 805-685-1006, Fax 805-685-6869
|
||||
Website http://www.eiffel.com
|
||||
Customer support http://support.eiffel.com
|
||||
]"
|
||||
end
|
||||
283
library/network/http_client/src/http_client_session.e
Normal file
283
library/network/http_client/src/http_client_session.e
Normal file
@@ -0,0 +1,283 @@
|
||||
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$"
|
||||
|
||||
deferred class
|
||||
HTTP_CLIENT_SESSION
|
||||
|
||||
inherit
|
||||
ANY
|
||||
|
||||
HTTP_CLIENT_CONSTANTS
|
||||
rename
|
||||
auth_type_id as auth_type_id_from_string
|
||||
export
|
||||
{NONE} all
|
||||
end
|
||||
|
||||
feature {NONE} -- Initialization
|
||||
|
||||
make (a_base_url: READABLE_STRING_8)
|
||||
-- Initialize `Current'.
|
||||
do
|
||||
set_defaults
|
||||
create headers.make (3)
|
||||
|
||||
base_url := a_base_url
|
||||
initialize
|
||||
end
|
||||
|
||||
set_defaults
|
||||
do
|
||||
timeout := 5
|
||||
connect_timeout := 1
|
||||
max_redirects := 5
|
||||
set_basic_auth_type
|
||||
end
|
||||
|
||||
initialize
|
||||
deferred
|
||||
end
|
||||
|
||||
feature -- Basic operation
|
||||
|
||||
url (a_path: READABLE_STRING_8; ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT): STRING_8
|
||||
-- Url computed from Current and `ctx' data.
|
||||
local
|
||||
s: STRING_8
|
||||
url_encoder: URL_ENCODER
|
||||
do
|
||||
Result := base_url + a_path
|
||||
if ctx /= Void then
|
||||
create s.make_empty
|
||||
create url_encoder
|
||||
across
|
||||
ctx.query_parameters as q
|
||||
loop
|
||||
if not s.is_empty then
|
||||
s.append_character ('&')
|
||||
end
|
||||
s.append (url_encoder.encoded_string (q.key))
|
||||
s.append_character ('=')
|
||||
s.append (url_encoder.encoded_string (q.item))
|
||||
end
|
||||
if not s.is_empty then
|
||||
Result.append_character ('?')
|
||||
Result.append (s)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
get (a_path: READABLE_STRING_8; ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT): HTTP_CLIENT_RESPONSE
|
||||
-- Response for GET request based on Current, `a_path' and `ctx'.
|
||||
deferred
|
||||
end
|
||||
|
||||
head (a_path: READABLE_STRING_8; ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT): HTTP_CLIENT_RESPONSE
|
||||
-- Response for HEAD request based on Current, `a_path' and `ctx'.
|
||||
deferred
|
||||
end
|
||||
|
||||
post (a_path: READABLE_STRING_8; ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT; data: detachable READABLE_STRING_8): HTTP_CLIENT_RESPONSE
|
||||
-- Response for POST request based on Current, `a_path' and `ctx'
|
||||
-- with input `data'
|
||||
deferred
|
||||
end
|
||||
|
||||
post_file (a_path: READABLE_STRING_8; ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT; fn: detachable READABLE_STRING_8): HTTP_CLIENT_RESPONSE
|
||||
-- Response for POST request based on Current, `a_path' and `ctx'
|
||||
-- with uploaded data file `fn'
|
||||
deferred
|
||||
end
|
||||
|
||||
put (a_path: READABLE_STRING_8; ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT; data: detachable READABLE_STRING_8): HTTP_CLIENT_RESPONSE
|
||||
-- Response for PUT request based on Current, `a_path' and `ctx'
|
||||
-- with input `data'
|
||||
deferred
|
||||
end
|
||||
|
||||
put_file (a_path: READABLE_STRING_8; ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT; fn: detachable READABLE_STRING_8): HTTP_CLIENT_RESPONSE
|
||||
-- Response for PUT request based on Current, `a_path' and `ctx'
|
||||
-- with uploaded file `fn'
|
||||
deferred
|
||||
end
|
||||
|
||||
delete (a_path: READABLE_STRING_8; ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT): HTTP_CLIENT_RESPONSE
|
||||
-- Response for DELETE request based on Current, `a_path' and `ctx'
|
||||
deferred
|
||||
end
|
||||
|
||||
feature -- Status report
|
||||
|
||||
is_debug: BOOLEAN
|
||||
-- Produce debug output
|
||||
|
||||
feature -- Settings
|
||||
|
||||
timeout: INTEGER
|
||||
-- HTTP transaction timeout in seconds. Defaults to 5 seconds.
|
||||
|
||||
connect_timeout: INTEGER
|
||||
-- HTTP connection timeout in seconds. Defaults to 1 second.
|
||||
|
||||
max_redirects: INTEGER
|
||||
-- Maximum number of times to follow redirects.
|
||||
-- Set to 0 to disable and -1 to follow all redirects. Defaults to 5.
|
||||
|
||||
ignore_content_length: BOOLEAN
|
||||
-- Does this session ignore Content-Size headers?
|
||||
|
||||
buffer_size: NATURAL
|
||||
-- Set the buffer size for request. This option will
|
||||
-- only be set if buffer_size is positive
|
||||
|
||||
default_response_charset: detachable READABLE_STRING_8
|
||||
-- Default encoding of responses. Used if no charset is provided by the host.
|
||||
|
||||
is_insecure: BOOLEAN
|
||||
-- Allow connections to SSL sites without certs
|
||||
|
||||
proxy: detachable TUPLE [host: READABLE_STRING_8; port: INTEGER]
|
||||
-- Proxy information [`host' and `port']
|
||||
|
||||
feature -- Access
|
||||
|
||||
base_url: READABLE_STRING_8
|
||||
-- Base URL for any request created by Current session.
|
||||
|
||||
headers: HASH_TABLE [READABLE_STRING_8, READABLE_STRING_8]
|
||||
-- Headers common to any request created by Current session.
|
||||
|
||||
feature -- Authentication
|
||||
|
||||
auth_type: STRING
|
||||
-- Set the authentication type for the request.
|
||||
-- Types: "basic", "digest", "any"
|
||||
|
||||
auth_type_id: INTEGER
|
||||
-- See {HTTP_CLIENT_CONSTANTS}.Auth_type_*
|
||||
|
||||
username,
|
||||
password: detachable READABLE_STRING_32
|
||||
|
||||
credentials: detachable READABLE_STRING_32
|
||||
|
||||
|
||||
feature -- Status setting
|
||||
|
||||
set_is_debug (b: BOOLEAN)
|
||||
do
|
||||
is_debug := b
|
||||
end
|
||||
|
||||
feature -- Element change
|
||||
|
||||
set_base_url (u: like base_url)
|
||||
do
|
||||
base_url := u
|
||||
end
|
||||
|
||||
set_timeout (n_seconds: like timeout)
|
||||
do
|
||||
timeout := n_seconds
|
||||
end
|
||||
|
||||
set_connect_timeout (n: like connect_timeout)
|
||||
do
|
||||
connect_timeout := n
|
||||
end
|
||||
|
||||
set_user_agent (v: READABLE_STRING_8)
|
||||
do
|
||||
add_header ("User-Agent", v)
|
||||
end
|
||||
|
||||
set_is_insecure (b: BOOLEAN)
|
||||
do
|
||||
is_insecure := b
|
||||
end
|
||||
|
||||
add_header (k: READABLE_STRING_8; v: READABLE_STRING_8)
|
||||
do
|
||||
headers.force (v, k)
|
||||
end
|
||||
|
||||
remove_header (k: READABLE_STRING_8)
|
||||
do
|
||||
headers.prune (k)
|
||||
end
|
||||
|
||||
set_credentials (u: like username; p: like password)
|
||||
do
|
||||
username := u
|
||||
password := p
|
||||
if u /= Void and p /= Void then
|
||||
credentials := u + ":" + p
|
||||
else
|
||||
credentials := Void
|
||||
end
|
||||
end
|
||||
|
||||
set_auth_type (s: READABLE_STRING_8)
|
||||
do
|
||||
auth_type := s
|
||||
auth_type_id := auth_type_id_from_string (s)
|
||||
end
|
||||
|
||||
set_basic_auth_type
|
||||
do
|
||||
auth_type := "basic"
|
||||
auth_type_id := Auth_type_basic
|
||||
end
|
||||
|
||||
set_digest_auth_type
|
||||
do
|
||||
auth_type := "digest"
|
||||
auth_type_id := Auth_type_digest
|
||||
end
|
||||
|
||||
set_any_auth_type
|
||||
do
|
||||
auth_type := "any"
|
||||
auth_type_id := Auth_type_any
|
||||
end
|
||||
|
||||
set_anysafe_auth_type
|
||||
do
|
||||
auth_type := "anysafe"
|
||||
auth_type_id := Auth_type_anysafe
|
||||
end
|
||||
|
||||
set_max_redirects (n: like max_redirects)
|
||||
do
|
||||
max_redirects := n
|
||||
end
|
||||
|
||||
set_proxy (a_host: detachable READABLE_STRING_8; a_port: INTEGER)
|
||||
do
|
||||
if a_host = Void then
|
||||
proxy := Void
|
||||
else
|
||||
proxy := [a_host, a_port]
|
||||
end
|
||||
end
|
||||
|
||||
note
|
||||
copyright: "2011-2012, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
|
||||
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
|
||||
source: "[
|
||||
Eiffel Software
|
||||
5949 Hollister Ave., Goleta, CA 93117 USA
|
||||
Telephone 805-685-1006, Fax 805-685-6869
|
||||
Website http://www.eiffel.com
|
||||
Customer support http://support.eiffel.com
|
||||
]"
|
||||
end
|
||||
@@ -0,0 +1,94 @@
|
||||
note
|
||||
description: "[
|
||||
Default implementation of CURL_FUNCTION.
|
||||
Fixing eventual issue from CURL_DEFAULT_FUNCTION
|
||||
]"
|
||||
status: "See notice at end of class."
|
||||
legal: "See notice at end of class."
|
||||
date: "$Date: 2009-04-09 20:51:20 +0200 (Thu, 09 Apr 2009) $"
|
||||
revision: "$Revision: 78146 $"
|
||||
|
||||
class
|
||||
LIBCURL_DEFAULT_FUNCTION
|
||||
|
||||
inherit
|
||||
CURL_DEFAULT_FUNCTION
|
||||
redefine
|
||||
write_function,
|
||||
debug_function,
|
||||
dump
|
||||
end
|
||||
|
||||
create
|
||||
make
|
||||
|
||||
feature -- Command
|
||||
|
||||
write_function (a_data_pointer: POINTER; a_size, a_nmemb: INTEGER; a_object_id: POINTER): INTEGER
|
||||
-- Redefine
|
||||
local
|
||||
l_c_string: C_STRING
|
||||
l_identified: IDENTIFIED
|
||||
do
|
||||
Result := a_size * a_nmemb
|
||||
create l_c_string.make_shared_from_pointer_and_count (a_data_pointer, Result)
|
||||
|
||||
create l_identified
|
||||
if attached {CURL_STRING} l_identified.id_object (a_object_id.to_integer_32) as l_string then
|
||||
l_string.append (l_c_string.substring (1, Result))
|
||||
else
|
||||
check False end
|
||||
end
|
||||
end
|
||||
|
||||
debug_function (a_curl_handle: POINTER; a_curl_infotype: INTEGER; a_char_pointer: POINTER; a_size: INTEGER; a_object_id: POINTER): INTEGER
|
||||
-- Redefine
|
||||
local
|
||||
l_c_string: C_STRING
|
||||
do
|
||||
inspect
|
||||
a_curl_infotype
|
||||
when {CURL_INFO_TYPE}.curlinfo_data_in then
|
||||
dump ("<= Recv data", a_char_pointer, a_size)
|
||||
when {CURL_INFO_TYPE}.curlinfo_data_out then
|
||||
dump ("=> Send data", a_char_pointer, a_size)
|
||||
when {CURL_INFO_TYPE}.curlinfo_header_in then
|
||||
dump ("<= Recv header", a_char_pointer, a_size)
|
||||
when {CURL_INFO_TYPE}.curlinfo_header_out then
|
||||
dump ("=> Send header", a_char_pointer, a_size)
|
||||
when {CURL_INFO_TYPE}.curlinfo_ssl_data_in then
|
||||
dump ("<= Recv SSL data", a_char_pointer, a_size)
|
||||
when {CURL_INFO_TYPE}.curlinfo_ssl_data_out then
|
||||
dump ("=> Send SSL data", a_char_pointer, a_size)
|
||||
when {CURL_INFO_TYPE}.curlinfo_text then
|
||||
create l_c_string.make_by_pointer_and_count (a_char_pointer, a_size)
|
||||
print ("%N== Info: " + l_c_string.substring (1, a_size))
|
||||
else
|
||||
check type_unknow: False end
|
||||
end
|
||||
end
|
||||
|
||||
feature {NONE} -- Implementation
|
||||
|
||||
dump (a_text: STRING; a_char_pointer: POINTER; a_size: INTEGER)
|
||||
-- Dump debug information
|
||||
local
|
||||
l_c_string: C_STRING
|
||||
do
|
||||
create l_c_string.make_shared_from_pointer_and_count (a_char_pointer, a_size)
|
||||
print ("%N" + a_text + "%N" + l_c_string.substring (1, a_size))
|
||||
end
|
||||
|
||||
note
|
||||
library: "cURL: Library of reusable components for Eiffel."
|
||||
copyright: "2011-2012, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
|
||||
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
|
||||
source: "[
|
||||
Eiffel Software
|
||||
5949 Hollister Ave., Goleta, CA 93117 USA
|
||||
Telephone 805-685-1006, Fax 805-685-6869
|
||||
Website http://www.eiffel.com
|
||||
Customer support http://support.eiffel.com
|
||||
]"
|
||||
|
||||
end
|
||||
@@ -0,0 +1,42 @@
|
||||
note
|
||||
description : "[
|
||||
Specific implementation of HTTP_CLIENT based on Eiffel cURL library
|
||||
]"
|
||||
author : "$Author$"
|
||||
date : "$Date$"
|
||||
revision : "$Revision$"
|
||||
|
||||
class
|
||||
LIBCURL_HTTP_CLIENT
|
||||
|
||||
inherit
|
||||
HTTP_CLIENT
|
||||
|
||||
create
|
||||
make
|
||||
|
||||
feature {NONE} -- Initialization
|
||||
|
||||
make
|
||||
-- Initialize `Current'.
|
||||
do
|
||||
end
|
||||
|
||||
feature -- Status
|
||||
|
||||
new_session (a_base_url: READABLE_STRING_8): LIBCURL_HTTP_CLIENT_SESSION
|
||||
do
|
||||
create Result.make (a_base_url)
|
||||
end
|
||||
|
||||
note
|
||||
copyright: "2011-2012, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
|
||||
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
|
||||
source: "[
|
||||
Eiffel Software
|
||||
5949 Hollister Ave., Goleta, CA 93117 USA
|
||||
Telephone 805-685-1006, Fax 805-685-6869
|
||||
Website http://www.eiffel.com
|
||||
Customer support http://support.eiffel.com
|
||||
]"
|
||||
end
|
||||
@@ -0,0 +1,364 @@
|
||||
note
|
||||
description: "[
|
||||
Specific implementation of HTTP_CLIENT_REQUEST based on Eiffel cURL library
|
||||
]"
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
class
|
||||
LIBCURL_HTTP_CLIENT_REQUEST
|
||||
|
||||
inherit
|
||||
HTTP_CLIENT_REQUEST
|
||||
rename
|
||||
make as make_request
|
||||
redefine
|
||||
session
|
||||
end
|
||||
|
||||
create
|
||||
make
|
||||
|
||||
feature {NONE} -- Initialization
|
||||
|
||||
make (a_url: READABLE_STRING_8; a_request_method: like request_method; a_session: like session; ctx: like context)
|
||||
do
|
||||
make_request (a_url, a_session, ctx)
|
||||
request_method := a_request_method
|
||||
apply_workaround
|
||||
end
|
||||
|
||||
apply_workaround
|
||||
-- Due to issue with Eiffel cURL on Windows 32bits
|
||||
-- we need to do the following workaround
|
||||
once
|
||||
if attached (create {INET_ADDRESS_FACTORY}).create_localhost then
|
||||
end
|
||||
end
|
||||
|
||||
session: LIBCURL_HTTP_CLIENT_SESSION
|
||||
|
||||
feature -- Access
|
||||
|
||||
request_method: READABLE_STRING_8
|
||||
|
||||
feature -- Execution
|
||||
|
||||
execute: HTTP_CLIENT_RESPONSE
|
||||
local
|
||||
l_result: INTEGER
|
||||
l_curl_string: CURL_STRING
|
||||
l_url: READABLE_STRING_8
|
||||
l_form: detachable CURL_FORM
|
||||
l_last: CURL_FORM
|
||||
l_upload_file: detachable RAW_FILE
|
||||
l_uploade_file_read_function: detachable LIBCURL_UPLOAD_FILE_READ_FUNCTION
|
||||
curl: detachable CURL_EXTERNALS
|
||||
curl_easy: detachable CURL_EASY_EXTERNALS
|
||||
curl_handle: POINTER
|
||||
ctx: like context
|
||||
p_slist: POINTER
|
||||
retried: BOOLEAN
|
||||
l_form_data: detachable HASH_TABLE [READABLE_STRING_32, READABLE_STRING_32]
|
||||
l_upload_data: detachable READABLE_STRING_8
|
||||
l_upload_filename: detachable READABLE_STRING_8
|
||||
l_headers: like headers
|
||||
do
|
||||
if not retried then
|
||||
curl := session.curl
|
||||
curl_easy := session.curl_easy
|
||||
curl_handle := curl_easy.init
|
||||
curl.global_init
|
||||
|
||||
ctx := context
|
||||
|
||||
--| Configure cURL session
|
||||
initialize_curl_session (ctx, curl, curl_easy, curl_handle)
|
||||
|
||||
--| URL
|
||||
l_url := url
|
||||
if ctx /= Void then
|
||||
append_parameters_to_url (ctx.query_parameters, l_url)
|
||||
end
|
||||
|
||||
debug ("service")
|
||||
io.put_string ("SERVICE: " + l_url)
|
||||
io.put_new_line
|
||||
end
|
||||
curl_easy.setopt_string (curl_handle, {CURL_OPT_CONSTANTS}.curlopt_url, l_url)
|
||||
|
||||
l_headers := headers
|
||||
|
||||
-- Context
|
||||
if ctx /= Void then
|
||||
--| Credential
|
||||
if ctx.credentials_required then
|
||||
if attached credentials as l_credentials then
|
||||
inspect auth_type_id
|
||||
when {HTTP_CLIENT_CONSTANTS}.Auth_type_none then
|
||||
curl_easy.setopt_integer (curl_handle, {CURL_OPT_CONSTANTS}.curlopt_httpauth, {CURL_OPT_CONSTANTS}.curlauth_none)
|
||||
when {HTTP_CLIENT_CONSTANTS}.Auth_type_basic then
|
||||
curl_easy.setopt_integer (curl_handle, {CURL_OPT_CONSTANTS}.curlopt_httpauth, {CURL_OPT_CONSTANTS}.curlauth_basic)
|
||||
when {HTTP_CLIENT_CONSTANTS}.Auth_type_digest then
|
||||
curl_easy.setopt_integer (curl_handle, {CURL_OPT_CONSTANTS}.curlopt_httpauth, {CURL_OPT_CONSTANTS}.curlauth_digest)
|
||||
when {HTTP_CLIENT_CONSTANTS}.Auth_type_any then
|
||||
curl_easy.setopt_integer (curl_handle, {CURL_OPT_CONSTANTS}.curlopt_httpauth, {CURL_OPT_CONSTANTS}.curlauth_any)
|
||||
when {HTTP_CLIENT_CONSTANTS}.Auth_type_anysafe then
|
||||
curl_easy.setopt_integer (curl_handle, {CURL_OPT_CONSTANTS}.curlopt_httpauth, {CURL_OPT_CONSTANTS}.curlauth_anysafe)
|
||||
else
|
||||
end
|
||||
|
||||
curl_easy.setopt_string (curl_handle, {CURL_OPT_CONSTANTS}.curlopt_userpwd, l_credentials)
|
||||
else
|
||||
--| Credentials not provided ...
|
||||
end
|
||||
end
|
||||
|
||||
if ctx.has_upload_data then
|
||||
l_upload_data := ctx.upload_data
|
||||
end
|
||||
if ctx.has_upload_filename then
|
||||
l_upload_filename := ctx.upload_filename
|
||||
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
|
||||
if
|
||||
l_headers.has_key ("Content-Type") and then
|
||||
attached l_headers.found_item as l_ct
|
||||
then
|
||||
if l_ct.starts_with ("application/x-www-form-urlencoded") then
|
||||
-- Content-Type is already application/x-www-form-urlencoded
|
||||
l_upload_data := ctx.form_parameters_to_url_encoded_string
|
||||
else
|
||||
-- Existing Content-Type and not application/x-www-form-urlencoded
|
||||
end
|
||||
else
|
||||
l_upload_data := ctx.form_parameters_to_url_encoded_string
|
||||
end
|
||||
else
|
||||
create l_form.make
|
||||
create l_last.make
|
||||
from
|
||||
l_form_data.start
|
||||
until
|
||||
l_form_data.after
|
||||
loop
|
||||
curl.formadd_string_string (l_form, l_last,
|
||||
{CURL_FORM_CONSTANTS}.curlform_copyname, l_form_data.key_for_iteration,
|
||||
{CURL_FORM_CONSTANTS}.curlform_copycontents, l_form_data.item_for_iteration,
|
||||
{CURL_FORM_CONSTANTS}.curlform_end
|
||||
)
|
||||
l_form_data.forth
|
||||
end
|
||||
l_last.release_item
|
||||
curl_easy.setopt_form (curl_handle, {CURL_OPT_CONSTANTS}.curlopt_httppost, l_form)
|
||||
end
|
||||
end
|
||||
|
||||
if l_upload_data /= Void then
|
||||
check
|
||||
post_or_put_request_method: request_method.is_case_insensitive_equal ("POST")
|
||||
or request_method.is_case_insensitive_equal ("PUT")
|
||||
end
|
||||
|
||||
curl_easy.setopt_string (curl_handle, {CURL_OPT_CONSTANTS}.curlopt_postfields, l_upload_data)
|
||||
curl_easy.setopt_integer (curl_handle, {CURL_OPT_CONSTANTS}.curlopt_postfieldsize, l_upload_data.count)
|
||||
elseif l_upload_filename /= Void then
|
||||
check
|
||||
post_or_put_request_method: request_method.is_case_insensitive_equal ("POST")
|
||||
or request_method.is_case_insensitive_equal ("PUT")
|
||||
end
|
||||
|
||||
create l_upload_file.make (l_upload_filename)
|
||||
if l_upload_file.exists and then l_upload_file.is_readable then
|
||||
curl_easy.setopt_integer (curl_handle, {CURL_OPT_CONSTANTS}.curlopt_upload, 1)
|
||||
|
||||
curl_easy.setopt_integer (curl_handle, {CURL_OPT_CONSTANTS}.curlopt_infilesize, l_upload_file.count)
|
||||
-- specify callback read function for upload file
|
||||
create l_uploade_file_read_function.make_with_file (l_upload_file)
|
||||
l_upload_file.open_read
|
||||
curl_easy.set_curl_function (l_uploade_file_read_function)
|
||||
curl_easy.set_read_function (curl_handle)
|
||||
end
|
||||
else
|
||||
check no_upload_data: l_upload_data = Void and l_upload_filename = Void end
|
||||
end
|
||||
end -- ctx /= Void
|
||||
|
||||
--| Header
|
||||
across
|
||||
l_headers as curs
|
||||
loop
|
||||
p_slist := curl.slist_append (p_slist, curs.key + ": " + curs.item)
|
||||
end
|
||||
p_slist := curl.slist_append (p_slist, "Expect:")
|
||||
curl_easy.setopt_slist (curl_handle, {CURL_OPT_CONSTANTS}.curlopt_httpheader, p_slist)
|
||||
|
||||
--| Execution
|
||||
curl_easy.set_read_function (curl_handle)
|
||||
curl_easy.set_write_function (curl_handle)
|
||||
if is_debug then
|
||||
curl_easy.set_debug_function (curl_handle)
|
||||
curl_easy.setopt_integer (curl_handle, {CURL_OPT_CONSTANTS}.curlopt_verbose, 1)
|
||||
end
|
||||
create l_curl_string.make_empty
|
||||
curl_easy.setopt_curl_string (curl_handle, {CURL_OPT_CONSTANTS}.curlopt_writedata, l_curl_string)
|
||||
|
||||
create Result.make
|
||||
l_result := curl_easy.perform (curl_handle)
|
||||
|
||||
--| Result
|
||||
if l_result = {CURL_CODES}.curle_ok then
|
||||
Result.status := response_status_code (curl_easy, curl_handle)
|
||||
set_header_and_body_to (l_curl_string.string, Result)
|
||||
else
|
||||
Result.set_error_occurred (True)
|
||||
Result.status := response_status_code (curl_easy, curl_handle)
|
||||
end
|
||||
|
||||
--| Cleaning
|
||||
|
||||
curl.global_cleanup
|
||||
curl_easy.cleanup (curl_handle)
|
||||
else
|
||||
create Result.make
|
||||
Result.set_error_occurred (True)
|
||||
end
|
||||
|
||||
--| Remaining cleaning
|
||||
if l_form /= Void then
|
||||
l_form.dispose
|
||||
end
|
||||
if curl /= Void and then p_slist /= default_pointer then
|
||||
curl.slist_free_all (p_slist)
|
||||
end
|
||||
if l_upload_file /= Void and then not l_upload_file.is_closed then
|
||||
l_upload_file.close
|
||||
end
|
||||
rescue
|
||||
retried := True
|
||||
if curl /= Void then
|
||||
curl.global_cleanup
|
||||
curl := Void
|
||||
end
|
||||
if curl_easy /= Void and curl_handle /= default_pointer then
|
||||
curl_easy.cleanup (curl_handle)
|
||||
curl_easy := Void
|
||||
end
|
||||
retry
|
||||
end
|
||||
|
||||
initialize_curl_session (ctx: like context; curl: CURL_EXTERNALS; curl_easy: CURL_EASY_EXTERNALS; curl_handle: POINTER)
|
||||
local
|
||||
l_proxy: like proxy
|
||||
do
|
||||
--| RESPONSE HEADERS
|
||||
curl_easy.setopt_integer (curl_handle, {CURL_OPT_CONSTANTS}.curlopt_header, 1)
|
||||
|
||||
--| PROXY ...
|
||||
|
||||
if ctx /= Void then
|
||||
l_proxy := ctx.proxy
|
||||
end
|
||||
if l_proxy = Void then
|
||||
l_proxy := proxy
|
||||
end
|
||||
if l_proxy /= Void then
|
||||
curl_easy.setopt_integer (curl_handle, {CURL_OPT_CONSTANTS}.curlopt_proxyport, l_proxy.port)
|
||||
curl_easy.setopt_string (curl_handle, {CURL_OPT_CONSTANTS}.curlopt_proxy, l_proxy.host)
|
||||
end
|
||||
|
||||
--| Timeout
|
||||
if timeout > 0 then
|
||||
curl_easy.setopt_integer (curl_handle, {CURL_OPT_CONSTANTS}.curlopt_timeout, timeout)
|
||||
end
|
||||
--| Connect Timeout
|
||||
if connect_timeout > 0 then
|
||||
curl_easy.setopt_integer (curl_handle, {CURL_OPT_CONSTANTS}.curlopt_connecttimeout, timeout)
|
||||
end
|
||||
--| Redirection
|
||||
if max_redirects /= 0 then
|
||||
curl_easy.setopt_integer (curl_handle, {CURL_OPT_CONSTANTS}.curlopt_followlocation, 1)
|
||||
curl_easy.setopt_integer (curl_handle, {CURL_OPT_CONSTANTS}.curlopt_maxredirs, max_redirects)
|
||||
else
|
||||
curl_easy.setopt_integer (curl_handle, {CURL_OPT_CONSTANTS}.curlopt_followlocation, 0)
|
||||
end
|
||||
|
||||
--| SSL
|
||||
if is_insecure then
|
||||
curl_easy.setopt_integer (curl_handle, {CURL_OPT_CONSTANTS}.curlopt_ssl_verifyhost, 0)
|
||||
curl_easy.setopt_integer (curl_handle, {CURL_OPT_CONSTANTS}.curlopt_ssl_verifypeer, 0)
|
||||
end
|
||||
|
||||
--| Request method
|
||||
if request_method.is_case_insensitive_equal ("GET") then
|
||||
curl_easy.setopt_integer (curl_handle, {CURL_OPT_CONSTANTS}.curlopt_httpget, 1)
|
||||
elseif request_method.is_case_insensitive_equal ("POST") then
|
||||
curl_easy.setopt_integer (curl_handle, {CURL_OPT_CONSTANTS}.curlopt_post, 1)
|
||||
elseif request_method.is_case_insensitive_equal ("PUT") then
|
||||
curl_easy.setopt_integer (curl_handle, {CURL_OPT_CONSTANTS}.curlopt_put, 1)
|
||||
elseif request_method.is_case_insensitive_equal ("HEAD") then
|
||||
curl_easy.setopt_integer (curl_handle, {CURL_OPT_CONSTANTS}.curlopt_nobody, 1)
|
||||
elseif request_method.is_case_insensitive_equal ("DELETE") then
|
||||
curl_easy.setopt_string (curl_handle, {CURL_OPT_CONSTANTS}.curlopt_customrequest, "DELETE")
|
||||
else
|
||||
curl_easy.setopt_string (curl_handle, {CURL_OPT_CONSTANTS}.curlopt_customrequest, request_method)
|
||||
--| ignored
|
||||
end
|
||||
end
|
||||
|
||||
feature {NONE} -- Implementation
|
||||
|
||||
response_status_code (curl_easy: CURL_EASY_EXTERNALS; curl_handle: POINTER): INTEGER
|
||||
local
|
||||
l_result: INTEGER
|
||||
a_data: CELL [detachable ANY]
|
||||
do
|
||||
create a_data.put (Void)
|
||||
l_result := curl_easy.getinfo (curl_handle, {CURL_INFO_CONSTANTS}.curlinfo_response_code, a_data)
|
||||
if l_result = 0 and then attached {INTEGER} a_data.item as l_http_status then
|
||||
Result := l_http_status
|
||||
else
|
||||
Result := 0
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
set_header_and_body_to (a_source: READABLE_STRING_8; res: HTTP_CLIENT_RESPONSE)
|
||||
-- Parse `a_source' response
|
||||
-- and set `header' and `body' from HTTP_CLIENT_RESPONSE `res'
|
||||
local
|
||||
pos, l_start : INTEGER
|
||||
do
|
||||
l_start := a_source.substring_index ("%R%N", 1)
|
||||
if l_start > 0 then
|
||||
--| Skip first line which is the status line
|
||||
--| ex: HTTP/1.1 200 OK%R%N
|
||||
l_start := l_start + 2
|
||||
end
|
||||
if l_start < a_source.count and then a_source[l_start] = '%R' and a_source[l_start + 1] = '%N' then
|
||||
res.set_body (a_source)
|
||||
else
|
||||
pos := a_source.substring_index ("%R%N%R%N", l_start)
|
||||
if pos > 0 then
|
||||
res.set_raw_header (a_source.substring (l_start, pos + 1)) --| Keep the last %R%N
|
||||
res.set_body (a_source.substring (pos + 4, a_source.count))
|
||||
else
|
||||
res.set_body (a_source)
|
||||
end
|
||||
end
|
||||
end
|
||||
note
|
||||
copyright: "2011-2012, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
|
||||
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
|
||||
source: "[
|
||||
Eiffel Software
|
||||
5949 Hollister Ave., Goleta, CA 93117 USA
|
||||
Telephone 805-685-1006, Fax 805-685-6869
|
||||
Website http://www.eiffel.com
|
||||
Customer support http://support.eiffel.com
|
||||
]"
|
||||
end
|
||||
@@ -0,0 +1,160 @@
|
||||
note
|
||||
description: "[
|
||||
Specific implementation of HTTP_CLIENT_SESSION based on Eiffel cURL library
|
||||
]"
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
class
|
||||
LIBCURL_HTTP_CLIENT_SESSION
|
||||
|
||||
inherit
|
||||
HTTP_CLIENT_SESSION
|
||||
|
||||
create
|
||||
make
|
||||
|
||||
feature {NONE} -- Initialization
|
||||
|
||||
initialize
|
||||
do
|
||||
create curl -- cURL externals
|
||||
create curl_easy -- cURL easy externals
|
||||
curl_easy.set_curl_function (create {LIBCURL_DEFAULT_FUNCTION}.make)
|
||||
end
|
||||
|
||||
feature -- Basic operation
|
||||
|
||||
get (a_path: READABLE_STRING_8; ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT): HTTP_CLIENT_RESPONSE
|
||||
local
|
||||
req: HTTP_CLIENT_REQUEST
|
||||
do
|
||||
create {LIBCURL_HTTP_CLIENT_REQUEST} req.make (base_url + a_path, "GET", Current, ctx)
|
||||
Result := req.execute
|
||||
end
|
||||
|
||||
head (a_path: READABLE_STRING_8; ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT): HTTP_CLIENT_RESPONSE
|
||||
local
|
||||
req: HTTP_CLIENT_REQUEST
|
||||
do
|
||||
create {LIBCURL_HTTP_CLIENT_REQUEST} req.make (base_url + a_path, "HEAD", Current, ctx)
|
||||
Result := req.execute
|
||||
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_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_post (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
|
||||
local
|
||||
req: HTTP_CLIENT_REQUEST
|
||||
ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT
|
||||
f: detachable RAW_FILE
|
||||
l_data: detachable READABLE_STRING_8
|
||||
do
|
||||
--| Quick and dirty hack using real file, for PUT uploaded data
|
||||
--| FIXME [2012-05-23]: better use libcurl for that purpose
|
||||
ctx := a_ctx
|
||||
if data /= Void then
|
||||
if ctx = Void then
|
||||
create ctx.make
|
||||
end
|
||||
ctx.set_upload_data (data)
|
||||
end
|
||||
if ctx /= Void then
|
||||
l_data := ctx.upload_data
|
||||
end
|
||||
if l_data /= Void then
|
||||
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.name)
|
||||
end
|
||||
end
|
||||
create {LIBCURL_HTTP_CLIENT_REQUEST} req.make (base_url + a_path, "PUT", Current, ctx)
|
||||
Result := req.execute
|
||||
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
|
||||
|
||||
put_file (a_path: READABLE_STRING_8; a_ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT; fn: detachable READABLE_STRING_8): HTTP_CLIENT_RESPONSE
|
||||
local
|
||||
req: HTTP_CLIENT_REQUEST
|
||||
ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT
|
||||
do
|
||||
ctx := a_ctx
|
||||
if fn /= Void then
|
||||
if ctx = Void then
|
||||
create ctx.make
|
||||
end
|
||||
ctx.set_upload_filename (fn)
|
||||
end
|
||||
create {LIBCURL_HTTP_CLIENT_REQUEST} req.make (base_url + a_path, "PUT", Current, ctx)
|
||||
Result := req.execute
|
||||
end
|
||||
|
||||
delete (a_path: READABLE_STRING_8; ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT): HTTP_CLIENT_RESPONSE
|
||||
local
|
||||
req: HTTP_CLIENT_REQUEST
|
||||
do
|
||||
create {LIBCURL_HTTP_CLIENT_REQUEST} req.make (base_url + a_path, "DELETE", Current, ctx)
|
||||
Result := req.execute
|
||||
end
|
||||
|
||||
feature {NONE} -- Implementation
|
||||
|
||||
impl_post (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
|
||||
local
|
||||
req: HTTP_CLIENT_REQUEST
|
||||
ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT
|
||||
do
|
||||
ctx := a_ctx
|
||||
if data /= Void then
|
||||
if ctx = Void then
|
||||
create ctx.make
|
||||
end
|
||||
ctx.set_upload_data (data)
|
||||
end
|
||||
if fn /= Void then
|
||||
if ctx = Void then
|
||||
create ctx.make
|
||||
end
|
||||
ctx.set_upload_filename (fn)
|
||||
end
|
||||
create {LIBCURL_HTTP_CLIENT_REQUEST} req.make (base_url + a_path, "POST", Current, ctx)
|
||||
Result := req.execute
|
||||
end
|
||||
|
||||
feature {LIBCURL_HTTP_CLIENT_REQUEST} -- Curl implementation
|
||||
|
||||
curl: CURL_EXTERNALS
|
||||
-- cURL externals
|
||||
|
||||
curl_easy: CURL_EASY_EXTERNALS
|
||||
-- cURL easy externals
|
||||
|
||||
|
||||
;note
|
||||
copyright: "2011-2012, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
|
||||
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
|
||||
source: "[
|
||||
Eiffel Software
|
||||
5949 Hollister Ave., Goleta, CA 93117 USA
|
||||
Telephone 805-685-1006, Fax 805-685-6869
|
||||
Website http://www.eiffel.com
|
||||
Customer support http://support.eiffel.com
|
||||
]"
|
||||
end
|
||||
@@ -0,0 +1,78 @@
|
||||
note
|
||||
description: "[
|
||||
LIBCURL_UPLOAD_FILE_READ_FUNCTION is used to uploaded file as part of the client request
|
||||
]"
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
class
|
||||
LIBCURL_UPLOAD_FILE_READ_FUNCTION
|
||||
|
||||
inherit
|
||||
LIBCURL_DEFAULT_FUNCTION
|
||||
redefine
|
||||
read_function
|
||||
end
|
||||
|
||||
create
|
||||
make_with_file
|
||||
|
||||
feature {NONE} -- Initialization
|
||||
|
||||
make_with_file (f: FILE)
|
||||
require
|
||||
f_is_open: f.is_open_read
|
||||
do
|
||||
make
|
||||
file_to_read := f
|
||||
end
|
||||
|
||||
feature -- Access
|
||||
|
||||
file_to_read: detachable FILE
|
||||
-- File for sending data
|
||||
|
||||
feature -- Basic operation
|
||||
|
||||
read_function (a_data_pointer: POINTER; a_size, a_nmemb: INTEGER_32; a_object_id: POINTER): INTEGER_32
|
||||
-- <Precursor>
|
||||
local
|
||||
l_pointer: MANAGED_POINTER
|
||||
l_max_transfer, l_byte_transfered: INTEGER
|
||||
do
|
||||
if attached file_to_read as l_file and then not l_file.after then
|
||||
l_max_transfer := a_size * a_nmemb
|
||||
if l_max_transfer > l_file.count - l_file.position then
|
||||
l_max_transfer := l_file.count - l_file.position
|
||||
end
|
||||
create l_pointer.share_from_pointer (a_data_pointer, l_max_transfer)
|
||||
|
||||
from
|
||||
until
|
||||
l_file.after or l_byte_transfered >= l_max_transfer
|
||||
loop
|
||||
l_file.read_character
|
||||
l_pointer.put_character (l_file.last_character, l_byte_transfered)
|
||||
|
||||
l_byte_transfered := l_byte_transfered + 1
|
||||
end
|
||||
|
||||
Result := l_max_transfer
|
||||
else
|
||||
-- Result is 0 means stop file transfer
|
||||
Result := 0
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
note
|
||||
copyright: "2011-2012, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
|
||||
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
|
||||
source: "[
|
||||
Eiffel Software
|
||||
5949 Hollister Ave., Goleta, CA 93117 USA
|
||||
Telephone 805-685-1006, Fax 805-685-6869
|
||||
Website http://www.eiffel.com
|
||||
Customer support http://support.eiffel.com
|
||||
]"
|
||||
end
|
||||
18
library/network/http_client/tests/test-safe.ecf
Normal file
18
library/network/http_client/tests/test-safe.ecf
Normal file
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="ISO-8859-1"?>
|
||||
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-9-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-9-0 http://www.eiffel.com/developers/xml/configuration-1-9-0.xsd" name="test_http_client" uuid="920E5C50-41E1-4DAC-8D48-D9C860E49228">
|
||||
<target name="test_http_client">
|
||||
<root class="TEST" feature="make"/>
|
||||
<file_rule>
|
||||
<exclude>/.git$</exclude>
|
||||
<exclude>/EIFGENs$</exclude>
|
||||
<exclude>/.svn$</exclude>
|
||||
</file_rule>
|
||||
<option warning="true" full_class_checking="true" is_attached_by_default="true" void_safety="all">
|
||||
<assertions precondition="true"/>
|
||||
</option>
|
||||
<library name="base" location="$ISE_LIBRARY\library\base\base-safe.ecf"/>
|
||||
<library name="http_client" location="..\http_client-safe.ecf" readonly="false"/>
|
||||
<library name="testing" location="$ISE_LIBRARY\library\testing\testing-safe.ecf"/>
|
||||
<tests name="tests" location=".\"/>
|
||||
</target>
|
||||
</system>
|
||||
50
library/network/http_client/tests/test.e
Normal file
50
library/network/http_client/tests/test.e
Normal file
@@ -0,0 +1,50 @@
|
||||
class TEST
|
||||
|
||||
create
|
||||
make
|
||||
|
||||
feature -- Init
|
||||
|
||||
make
|
||||
do
|
||||
test_http_client
|
||||
end
|
||||
|
||||
test_http_client
|
||||
-- New test routine
|
||||
local
|
||||
sess: LIBCURL_HTTP_CLIENT_SESSION
|
||||
h: STRING_8
|
||||
do
|
||||
create sess.make ("http://www.google.com")
|
||||
if attached sess.get ("/search?q=eiffel", Void) as res then
|
||||
assert ("Get returned without error", not res.error_occurred)
|
||||
create h.make_empty
|
||||
if attached res.headers as hds then
|
||||
across
|
||||
hds as c
|
||||
loop
|
||||
h.append (c.item.name + ": " + c.item.value + "%R%N")
|
||||
end
|
||||
end
|
||||
if attached res.body as l_body then
|
||||
assert ("body not empty", not l_body.is_empty)
|
||||
end
|
||||
assert ("same headers", h.same_string (res.raw_header))
|
||||
else
|
||||
assert ("Not found", False)
|
||||
end
|
||||
end
|
||||
|
||||
assert (m: READABLE_STRING_8; b: BOOLEAN)
|
||||
local
|
||||
e: DEVELOPER_EXCEPTION
|
||||
do
|
||||
if not b then
|
||||
create e
|
||||
e.set_message (m)
|
||||
e.raise
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
92
library/network/http_client/tests/test_http_client.e
Normal file
92
library/network/http_client/tests/test_http_client.e
Normal file
@@ -0,0 +1,92 @@
|
||||
note
|
||||
description: "[
|
||||
Eiffel tests that can be executed by testing tool.
|
||||
]"
|
||||
author: "EiffelStudio test wizard"
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
testing: "type/manual"
|
||||
|
||||
class
|
||||
TEST_HTTP_CLIENT
|
||||
|
||||
inherit
|
||||
EQA_TEST_SET
|
||||
|
||||
feature -- Test routines
|
||||
|
||||
test_http_client
|
||||
-- New test routine
|
||||
local
|
||||
sess: LIBCURL_HTTP_CLIENT_SESSION
|
||||
h: STRING_8
|
||||
do
|
||||
create sess.make ("http://www.google.com")
|
||||
if attached sess.get ("/search?q=eiffel", Void) as res then
|
||||
assert ("Get returned without error", not res.error_occurred)
|
||||
create h.make_empty
|
||||
if attached res.headers as hds then
|
||||
across
|
||||
hds as c
|
||||
loop
|
||||
h.append (c.item.name + ": " + c.item.value + "%R%N")
|
||||
end
|
||||
end
|
||||
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))
|
||||
else
|
||||
assert ("Not found", False)
|
||||
end
|
||||
end
|
||||
|
||||
test_headers
|
||||
local
|
||||
res: HTTP_CLIENT_RESPONSE
|
||||
h: STRING
|
||||
do
|
||||
create res.make
|
||||
create h.make_empty
|
||||
h.append ("normal: NORMAL%R%N")
|
||||
h.append ("concat: ABC%R%N")
|
||||
h.append ("concat: DEF%R%N")
|
||||
h.append ("key1: KEY%R%N")
|
||||
h.append (" key2 : KEY%R%N")
|
||||
h.append (" %T key3 : KEY%R%N")
|
||||
h.append ("value1:VALUE%R%N")
|
||||
h.append ("value2: VALUE%R%N")
|
||||
h.append ("value3: VALUE%R%N")
|
||||
h.append ("value4: VALUE %R%N")
|
||||
h.append (" %Tfoo : BAR%T %R%N")
|
||||
res.set_raw_header (h)
|
||||
create h.make_empty
|
||||
if attached res.headers as hds then
|
||||
across
|
||||
hds as c
|
||||
loop
|
||||
h.append (c.item.name + ": " + c.item.value + "%N")
|
||||
end
|
||||
end
|
||||
assert ("Expected headers map", h.same_string (
|
||||
"[
|
||||
normal: NORMAL
|
||||
concat: ABC
|
||||
concat: DEF
|
||||
key1: KEY
|
||||
key2: KEY
|
||||
key3: KEY
|
||||
value1: VALUE
|
||||
value2: VALUE
|
||||
value3: VALUE
|
||||
value4: VALUE
|
||||
foo: BAR
|
||||
|
||||
]"))
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
Reference in New Issue
Block a user