Now the http_client will send the form parameters urlencoded if this is possible (instead of multipart form data)
Note for now, the library does not support sending file and form parameters at the same time.
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
note
|
||||
description : "Objects that ..."
|
||||
author : "$Author$"
|
||||
description : "[
|
||||
Instantiate one of the descendant of HTTP_CLIENT
|
||||
then use `new_session' to create a session of http requests
|
||||
]"
|
||||
date : "$Date$"
|
||||
revision : "$Revision$"
|
||||
|
||||
@@ -10,6 +12,7 @@ deferred class
|
||||
feature -- Status
|
||||
|
||||
new_session (a_base_url: READABLE_STRING_8): HTTP_CLIENT_SESSION
|
||||
-- Create a new session using `a_base_url'.
|
||||
deferred
|
||||
end
|
||||
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
note
|
||||
description : "Objects that ..."
|
||||
author : "$Author$"
|
||||
date : "$Date$"
|
||||
revision : "$Revision$"
|
||||
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
|
||||
@@ -152,15 +154,14 @@ feature -- Settings
|
||||
|
||||
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_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
|
||||
i: INTEGER
|
||||
l_first_param: BOOLEAN
|
||||
do
|
||||
if a_parameters /= Void and then a_parameters.count > 0 then
|
||||
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
|
||||
@@ -168,23 +169,22 @@ feature {NONE} -- Utilities
|
||||
else
|
||||
l_first_param := True
|
||||
end
|
||||
|
||||
from
|
||||
i := a_parameters.lower
|
||||
a_parameters.start
|
||||
until
|
||||
i > a_parameters.upper
|
||||
a_parameters.after
|
||||
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 (urlencode (a_parameters.key_for_iteration))
|
||||
a_url.append_character ('=')
|
||||
a_url.append_string (a_param.value)
|
||||
a_url.append (urlencode (a_parameters.item_for_iteration))
|
||||
l_first_param := False
|
||||
end
|
||||
i := i + 1
|
||||
a_parameters.forth
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,6 +1,24 @@
|
||||
note
|
||||
description: "Summary description for {HTTP_CLIENT_REQUEST_CONTEXT}."
|
||||
author: ""
|
||||
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$"
|
||||
|
||||
@@ -29,20 +47,31 @@ feature {NONE} -- Initialization
|
||||
feature -- Settings
|
||||
|
||||
credentials_required: BOOLEAN
|
||||
-- If True, the request will precise the HTTP_AUTHORIZATION.
|
||||
|
||||
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_8]
|
||||
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_8]
|
||||
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
|
||||
|
||||
proxy: detachable TUPLE [host: READABLE_STRING_8; port: INTEGER]
|
||||
-- Optional proxy, see {HTTP_CLIENT_SESSION}.proxy
|
||||
|
||||
feature -- Status report
|
||||
|
||||
@@ -63,12 +92,17 @@ feature -- Status report
|
||||
|
||||
feature -- Element change
|
||||
|
||||
add_query_parameter (k: READABLE_STRING_8; v: READABLE_STRING_32)
|
||||
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_8; v: READABLE_STRING_32)
|
||||
add_form_parameter (k: READABLE_STRING_32; v: READABLE_STRING_32)
|
||||
do
|
||||
form_parameters.force (v, k)
|
||||
end
|
||||
@@ -101,6 +135,45 @@ feature -- Element change
|
||||
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)"
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
note
|
||||
description : "[
|
||||
HTTP_CLIENT_SESSION represent a session
|
||||
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 ...
|
||||
]"
|
||||
author: "$Author$"
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
note
|
||||
description : "Objects that ..."
|
||||
description : "[
|
||||
Specific implementation of HTTP_CLIENT based on Eiffel cURL library
|
||||
]"
|
||||
author : "$Author$"
|
||||
date : "$Date$"
|
||||
revision : "$Revision$"
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
note
|
||||
description : "Objects that ..."
|
||||
author : "$Author$"
|
||||
date : "$Date$"
|
||||
revision : "$Revision$"
|
||||
description: "[
|
||||
Specific implementation of HTTP_CLIENT_REQUEST based on Eiffel cURL library
|
||||
]"
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
class
|
||||
LIBCURL_HTTP_CLIENT_REQUEST
|
||||
@@ -58,6 +59,10 @@ feature -- Execution
|
||||
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
|
||||
@@ -73,16 +78,7 @@ feature -- Execution
|
||||
--| URL
|
||||
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
|
||||
append_parameters_to_url (ctx.query_parameters, l_url)
|
||||
end
|
||||
|
||||
debug ("service")
|
||||
@@ -91,19 +87,12 @@ feature -- Execution
|
||||
end
|
||||
curl_easy.setopt_string (curl_handle, {CURL_OPT_CONSTANTS}.curlopt_url, l_url)
|
||||
|
||||
--| Header
|
||||
if attached headers as l_headers then
|
||||
across
|
||||
l_headers as curs
|
||||
loop
|
||||
p_slist := curl.slist_append (p_slist, curs.key + ": " + curs.item)
|
||||
end
|
||||
end
|
||||
p_slist := curl.slist_append (p_slist, "Expect:")
|
||||
curl_easy.setopt_slist (curl_handle, {CURL_OPT_CONSTANTS}.curlopt_httpheader, p_slist)
|
||||
l_headers := headers
|
||||
|
||||
-- Context
|
||||
if ctx /= Void then
|
||||
--| Credential
|
||||
if ctx /= Void and then ctx.credentials_required then
|
||||
if ctx.credentials_required then
|
||||
if attached credentials as l_credentials then
|
||||
inspect auth_type_id
|
||||
when {HTTP_CLIENT_CONSTANTS}.Auth_type_none then
|
||||
@@ -121,33 +110,68 @@ feature -- Execution
|
||||
|
||||
curl_easy.setopt_string (curl_handle, {CURL_OPT_CONSTANTS}.curlopt_userpwd, l_credentials)
|
||||
else
|
||||
--| Credentials not prov ided ...
|
||||
--| Credentials not provided ...
|
||||
end
|
||||
end
|
||||
|
||||
if ctx /= Void and then ctx.has_form_data then
|
||||
if attached ctx.form_parameters as l_forms and then not l_forms.is_empty then
|
||||
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_forms.start
|
||||
l_form_data.start
|
||||
until
|
||||
l_forms.after
|
||||
l_form_data.after
|
||||
loop
|
||||
curl.formadd_string_string (l_form, l_last, {CURL_FORM_CONSTANTS}.curlform_copyname, l_forms.key_for_iteration, {CURL_FORM_CONSTANTS}.CURLFORM_COPYCONTENTS, l_forms.item_for_iteration, {CURL_FORM_CONSTANTS}.CURLFORM_END)
|
||||
l_forms.forth
|
||||
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 ctx /= Void then
|
||||
if request_method.is_case_insensitive_equal ("POST") or request_method.is_case_insensitive_equal ("PUT") then
|
||||
if ctx.has_upload_data and then attached ctx.upload_data as l_upload_data then
|
||||
|
||||
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
|
||||
if ctx.has_upload_filename and then attached ctx.upload_filename as l_upload_filename then
|
||||
|
||||
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)
|
||||
@@ -159,10 +183,21 @@ feature -- Execution
|
||||
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
|
||||
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
|
||||
@@ -175,6 +210,7 @@ feature -- Execution
|
||||
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)
|
||||
@@ -183,12 +219,16 @@ feature -- Execution
|
||||
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
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
note
|
||||
description : "Objects that ..."
|
||||
author : "$Author$"
|
||||
date : "$Date$"
|
||||
revision : "$Revision$"
|
||||
description: "[
|
||||
Specific implementation of HTTP_CLIENT_SESSION based on Eiffel cURL library
|
||||
]"
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
class
|
||||
LIBCURL_HTTP_CLIENT_SESSION
|
||||
|
||||
@@ -275,6 +275,7 @@ feature -- Test routines
|
||||
fn: FILE_NAME
|
||||
f: RAW_FILE
|
||||
s: STRING
|
||||
ctx: HTTP_CLIENT_REQUEST_CONTEXT
|
||||
do
|
||||
get_http_session
|
||||
if attached http_session as sess then
|
||||
@@ -284,6 +285,10 @@ feature -- Test routines
|
||||
f.put_string (s)
|
||||
f.close
|
||||
test_post_request_with_filename ("post/file/01", Void, fn.string, "post-file-01%N" + s)
|
||||
|
||||
create ctx.make
|
||||
ctx.add_form_parameter ("foo", "bar")
|
||||
test_post_request_with_filename ("post/file/01", ctx, fn.string, "post-file-01%N" + s)
|
||||
else
|
||||
assert ("not_implemented", False)
|
||||
end
|
||||
@@ -298,10 +303,11 @@ feature -- Test routines
|
||||
if attached http_session as sess then
|
||||
create ctx.make
|
||||
ctx.add_form_parameter ("id", "123")
|
||||
test_post_request ("post/01", ctx, "post-01 : id=123")
|
||||
test_post_request ("post/01/?foo=bar", ctx, "post-01(foo=bar) : id=123")
|
||||
test_post_request ("post/01/?foo=bar&abc=def", ctx, "post-01(foo=bar&abc=def) : id=123")
|
||||
test_post_request ("post/01/?lst=a&lst=b", ctx, "post-01(lst=[a,b]) : id=123")
|
||||
ctx.add_form_parameter ("Eiffel", "Web")
|
||||
test_post_request ("post/01", ctx, "post-01 : " + ctx.form_parameters_to_url_encoded_string)
|
||||
test_post_request ("post/01/?foo=bar", ctx, "post-01(foo=bar) : " + ctx.form_parameters_to_url_encoded_string)
|
||||
test_post_request ("post/01/?foo=bar&abc=def", ctx, "post-01(foo=bar&abc=def) : " + ctx.form_parameters_to_url_encoded_string)
|
||||
test_post_request ("post/01/?lst=a&lst=b", ctx, "post-01(lst=[a,b]) : " + ctx.form_parameters_to_url_encoded_string)
|
||||
else
|
||||
assert ("not_implemented", False)
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user