Added simple HTTP client.

For now the implementation is using Eiffel cURL library.
It requires Eiffel cURL coming with next EiffelStudio 7.0 (or from eiffelstudio's repo from rev#87244 )
This commit is contained in:
Jocelyn Fiat
2011-09-20 16:55:44 +02:00
parent b3ef7c846b
commit c2f7c198e0
10 changed files with 835 additions and 0 deletions

View File

@@ -0,0 +1,21 @@
<?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="standard">
</option>
<library name="base" location="$ISE_LIBRARY/library/base/base.ecf"/>
<library name="curl" location="$ISE_LIBRARY/library/cUrl/cUrl.ecf"/>
<library name="encoder" location="../../text/encoder/encoder.ecf"/>
<cluster name="src" location=".\src\" recursive="true">
<file_rule>
<exclude>/request$</exclude>
</file_rule>
</cluster>
</target>
</system>

View File

@@ -0,0 +1,16 @@
note
description : "Objects that ..."
author : "$Author$"
date : "$Date$"
revision : "$Revision$"
deferred class
HTTP_CLIENT
feature -- Status
new_session (a_base_url: READABLE_STRING_8): HTTP_CLIENT_SESSION
deferred
end
end

View File

@@ -0,0 +1,37 @@
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
end

View File

@@ -0,0 +1,183 @@
note
description : "Objects that ..."
author : "$Author$"
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)
-- Initialize `Current'.
do
session := a_session
url := a_url
headers := session.headers.twin
end
session: HTTP_CLIENT_SESSION
feature -- Access
request_method: READABLE_STRING_8
deferred
end
url: READABLE_STRING_8
headers: HASH_TABLE [READABLE_STRING_8, READABLE_STRING_8]
feature -- Execution
import (ctx: HTTP_CLIENT_REQUEST_CONTEXT)
do
headers.fill (ctx.headers)
end
execute (ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT): 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_8
do
Result := session.username
end
password: detachable READABLE_STRING_8
do
Result := session.password
end
credentials: detachable READABLE_STRING_8
do
Result := session.credentials
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
feature {NONE} -- Utilities
append_parameters_to_url (a_url: STRING; a_parameters: detachable ARRAY [detachable TUPLE [name: READABLE_STRING_8; value: READABLE_STRING_8]])
-- Append parameters `a_parameters' to `a_url'
require
a_url_attached: a_url /= Void
local
i: INTEGER
l_first_param: BOOLEAN
do
if a_parameters /= Void and then 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
i := a_parameters.lower
until
i > a_parameters.upper
loop
if attached a_parameters[i] as a_param then
if l_first_param then
a_url.append_character ('?')
else
a_url.append_character ('&')
end
a_url.append_string (a_param.name)
a_url.append_character ('=')
a_url.append_string (a_param.value)
l_first_param := False
end
i := i + 1
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
end

View File

@@ -0,0 +1,48 @@
note
description: "Summary description for {HTTP_CLIENT_REQUEST_CONTEXT}."
author: ""
date: "$Date$"
revision: "$Revision$"
class
HTTP_CLIENT_REQUEST_CONTEXT
create
make
feature {NONE} -- Initialization
make
do
create headers.make (2)
create query_parameters.make (5)
create form_data_parameters.make (10)
end
feature -- Settings
credentials_required: BOOLEAN
feature -- Access
headers: HASH_TABLE [READABLE_STRING_8, READABLE_STRING_8]
query_parameters: HASH_TABLE [READABLE_STRING_32, READABLE_STRING_8]
form_data_parameters: HASH_TABLE [READABLE_STRING_32, READABLE_STRING_8]
feature -- Status report
has_form_data: BOOLEAN
do
Result := not form_data_parameters.is_empty
end
feature -- Element change
set_credentials_required (b: BOOLEAN)
do
credentials_required := b
end
end

View File

@@ -0,0 +1,60 @@
note
description : "Objects that ..."
author : "$Author$"
date : "$Date$"
revision : "$Revision$"
class
HTTP_CLIENT_RESPONSE
create
make
feature {NONE} -- Initialization
make
-- Initialize `Current'.
do
status := 200
raw_headers := ""
end
feature -- Status
feature -- Access
status: INTEGER assign set_status
raw_headers: READABLE_STRING_8
headers: HASH_TABLE [READABLE_STRING_8, READABLE_STRING_8]
local
tb: like internal_headers
do
tb := internal_headers
if tb = Void then
create tb.make (3)
internal_headers := tb
end
Result := tb
end
body: detachable READABLE_STRING_8 assign set_body
feature -- Change
set_status (s: INTEGER)
do
status := s
end
set_body (s: like body)
do
body := s
end
feature {NONE} -- Implementation
internal_headers: detachable like headers
end

View File

@@ -0,0 +1,163 @@
note
description : "Objects that ..."
author : "$Author$"
date : "$Date$"
revision : "$Revision$"
deferred class
HTTP_CLIENT_SESSION
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
get (a_path: READABLE_STRING_8; ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT): HTTP_CLIENT_RESPONSE
deferred
end
head (a_path: READABLE_STRING_8; ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT): HTTP_CLIENT_RESPONSE
deferred
end
post (a_path: READABLE_STRING_8; ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT): HTTP_CLIENT_RESPONSE
deferred
end
put (a_path: READABLE_STRING_8; ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT): HTTP_CLIENT_RESPONSE
deferred
end
delete (a_path: READABLE_STRING_8; ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT): HTTP_CLIENT_RESPONSE
deferred
end
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.
feature -- Access
base_url: READABLE_STRING_8
headers: HASH_TABLE [READABLE_STRING_8, READABLE_STRING_8]
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_8
credentials: detachable READABLE_STRING_8
feature -- Change
set_timeout (n: like timeout)
do
timeout := n
end
set_user_agent (v: READABLE_STRING_8)
do
add_header ("User-Agent", v)
end
add_header (k: READABLE_STRING_8; v: READABLE_STRING_8)
do
headers.force (v, 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 := http_client_constants.auth_type_id (s)
end
set_basic_auth_type
do
auth_type := "basic"
auth_type_id := {HTTP_CLIENT_CONSTANTS}.auth_type_basic
end
set_digest_auth_type
do
auth_type := "digest"
auth_type_id := {HTTP_CLIENT_CONSTANTS}.auth_type_digest
end
set_any_auth_type
do
auth_type := "any"
auth_type_id := {HTTP_CLIENT_CONSTANTS}.auth_type_any
end
set_anysafe_auth_type
do
auth_type := "anysafe"
auth_type_id := {HTTP_CLIENT_CONSTANTS}.auth_type_anysafe
end
feature {NONE} -- Implementation
http_client_constants: HTTP_CLIENT_CONSTANTS
once
create Result
end
end

View File

@@ -0,0 +1,30 @@
note
description : "Objects that ..."
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
end

View File

@@ -0,0 +1,192 @@
note
description : "Objects that ..."
author : "$Author$"
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)
do
make_request (a_url, a_session)
request_method := a_request_method
end
session: LIBCURL_HTTP_CLIENT_SESSION
feature -- Access
request_method: READABLE_STRING_8
feature -- Execution
execute (ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT): HTTP_CLIENT_RESPONSE
local
l_result: INTEGER
l_curl_string: CURL_STRING
l_url: READABLE_STRING_8
p: POINTER
a_data: CELL [detachable ANY]
l_form, l_last: CURL_FORM
curl: CURL_EXTERNALS
curl_easy: CURL_EASY_EXTERNALS
curl_handle: POINTER
do
curl := session.curl
curl_easy := session.curl_easy
l_url := url
if ctx /= Void then
if attached ctx.query_parameters as l_query_params then
from
l_query_params.start
until
l_query_params.after
loop
append_parameters_to_url (l_url, <<[l_query_params.key_for_iteration, urlencode (l_query_params.item_for_iteration)]>>)
l_query_params.forth
end
end
end
--| Configure cURL session
curl_handle := curl_easy.init
--| URL
curl_easy.setopt_string (curl_handle, {CURL_OPT_CONSTANTS}.curlopt_url, l_url)
--| 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
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
--| Credential
if ctx /= Void and then 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 prov ided ...
end
end
if ctx /= Void and then ctx.has_form_data then
if attached ctx.form_data_parameters as l_posts and then not l_posts.is_empty then
-- curl_easy.set_debug_function (curl_handle)
-- curl_easy.setopt_integer (curl_handle, {CURL_OPT_CONSTANTS}.curlopt_verbose, 1)
create l_form.make
create l_last.make
from
l_posts.start
until
l_posts.after
loop
curl.formadd_string_string (l_form, l_last, {CURL_FORM_CONSTANTS}.CURLFORM_COPYNAME, l_posts.key_for_iteration, {CURL_FORM_CONSTANTS}.CURLFORM_COPYCONTENTS, l_posts.item_for_iteration, {CURL_FORM_CONSTANTS}.CURLFORM_END)
l_posts.forth
end
l_last.release_item
curl_easy.setopt_form (curl_handle, {CURL_OPT_CONSTANTS}.curlopt_httppost, l_form)
end
end
curl.global_init
if attached headers as l_headers then
across
l_headers as curs
loop
p := curl.slist_append (p, curs.key + ": " + curs.item)
-- curl_easy.setopt_slist (curl_handle, {CURL_OPT_CONSTANTS}.curlopt_httpheader, p)
end
end
p := curl.slist_append (p, "Expect:")
curl_easy.setopt_slist (curl_handle, {CURL_OPT_CONSTANTS}.curlopt_httpheader, p)
curl.global_cleanup
curl_easy.set_read_function (curl_handle)
curl_easy.set_write_function (curl_handle)
create l_curl_string.make_empty
curl_easy.setopt_curl_string (curl_handle, {CURL_OPT_CONSTANTS}.curlopt_writedata, l_curl_string)
debug ("service")
io.put_string ("SERVICE: " + l_url)
io.put_new_line
end
create Result.make
l_result := curl_easy.perform (curl_handle)
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.status := l_http_status
else
Result.status := 0
end
-- last_api_call := l_url
curl_easy.cleanup (curl_handle)
Result.body := l_curl_string.string
end
end

View File

@@ -0,0 +1,85 @@
note
description : "Objects that ..."
author : "$Author$"
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
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)
Result := execute_request (req, ctx)
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)
Result := execute_request (req, ctx)
end
post (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, "POST", Current)
Result := execute_request (req, ctx)
end
put (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, "PUT", Current)
Result := execute_request (req, ctx)
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)
Result := execute_request (req, ctx)
end
feature {NONE} -- Implementation
execute_request (req: HTTP_CLIENT_REQUEST; ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT): HTTP_CLIENT_RESPONSE
do
if ctx /= Void then
req.import (ctx)
end
Result := req.execute (ctx)
end
feature {LIBCURL_HTTP_CLIENT_REQUEST} -- Curl implementation
curl: CURL_EXTERNALS
-- cURL externals
curl_easy: CURL_EASY_EXTERNALS
-- cURL easy externals
end