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:
Jocelyn Fiat
2012-05-04 12:23:37 +02:00
parent d40cc5d669
commit 4508a76683
8 changed files with 244 additions and 120 deletions

View File

@@ -1,6 +1,8 @@
note note
description : "Objects that ..." description : "[
author : "$Author$" Instantiate one of the descendant of HTTP_CLIENT
then use `new_session' to create a session of http requests
]"
date : "$Date$" date : "$Date$"
revision : "$Revision$" revision : "$Revision$"
@@ -10,6 +12,7 @@ deferred class
feature -- Status feature -- Status
new_session (a_base_url: READABLE_STRING_8): HTTP_CLIENT_SESSION new_session (a_base_url: READABLE_STRING_8): HTTP_CLIENT_SESSION
-- Create a new session using `a_base_url'.
deferred deferred
end end

View File

@@ -1,8 +1,10 @@
note note
description : "Objects that ..." description: "[
author : "$Author$" Object representing a http client request
date : "$Date$" It is mainly used internally by the HTTP_CLIENT_SESSION
revision : "$Revision$" ]"
date: "$Date$"
revision: "$Revision$"
deferred class deferred class
HTTP_CLIENT_REQUEST HTTP_CLIENT_REQUEST
@@ -152,15 +154,14 @@ feature -- Settings
feature {NONE} -- Utilities 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' -- Append parameters `a_parameters' to `a_url'
require require
a_url_attached: a_url /= Void a_url_attached: a_url /= Void
local local
i: INTEGER
l_first_param: BOOLEAN l_first_param: BOOLEAN
do 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 if a_url.index_of ('?', 1) > 0 then
l_first_param := False l_first_param := False
elseif a_url.index_of ('&', 1) > 0 then elseif a_url.index_of ('&', 1) > 0 then
@@ -168,23 +169,22 @@ feature {NONE} -- Utilities
else else
l_first_param := True l_first_param := True
end end
from from
i := a_parameters.lower a_parameters.start
until until
i > a_parameters.upper a_parameters.after
loop loop
if attached a_parameters[i] as a_param then if l_first_param then
if l_first_param then a_url.append_character ('?')
a_url.append_character ('?') else
else a_url.append_character ('&')
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 end
i := i + 1 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 end
end end

View File

@@ -1,6 +1,24 @@
note note
description: "Summary description for {HTTP_CLIENT_REQUEST_CONTEXT}." description: "[
author: "" 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$" date: "$Date$"
revision: "$Revision$" revision: "$Revision$"
@@ -29,20 +47,31 @@ feature {NONE} -- Initialization
feature -- Settings feature -- Settings
credentials_required: BOOLEAN credentials_required: BOOLEAN
-- If True, the request will precise the HTTP_AUTHORIZATION.
feature -- Access feature -- Access
headers: HASH_TABLE [READABLE_STRING_8, READABLE_STRING_8] 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: detachable READABLE_STRING_8
-- Upload data
--| Note: make sure to precise the Content-Type header
upload_filename: detachable READABLE_STRING_8 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] proxy: detachable TUPLE [host: READABLE_STRING_8; port: INTEGER]
-- Optional proxy, see {HTTP_CLIENT_SESSION}.proxy
feature -- Status report feature -- Status report
@@ -63,12 +92,17 @@ feature -- Status report
feature -- Element change 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 do
query_parameters.force (v, k) query_parameters.force (v, k)
end end
add_form_parameter (k: READABLE_STRING_8; v: READABLE_STRING_32) add_form_parameter (k: READABLE_STRING_32; v: READABLE_STRING_32)
do do
form_parameters.force (v, k) form_parameters.force (v, k)
end end
@@ -101,6 +135,45 @@ feature -- Element change
end end
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 note
copyright: "2011-2012, Jocelyn Fiat, Javier Velilla, Eiffel Software and others" copyright: "2011-2012, Jocelyn Fiat, Javier Velilla, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)" license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"

View File

@@ -1,13 +1,12 @@
note note
description : "[ description : "[
HTTP_CLIENT_SESSION represent a session HTTP_CLIENT_SESSION represents a session
and is used to call get, post, .... request and is used to call get, post, .... request
with predefined settings such as with predefined settings such as
base_url base_url
specific common headers specific common headers
timeout and so on ... timeout and so on ...
]" ]"
author: "$Author$"
date: "$Date$" date: "$Date$"
revision: "$Revision$" revision: "$Revision$"

View File

@@ -1,5 +1,7 @@
note note
description : "Objects that ..." description : "[
Specific implementation of HTTP_CLIENT based on Eiffel cURL library
]"
author : "$Author$" author : "$Author$"
date : "$Date$" date : "$Date$"
revision : "$Revision$" revision : "$Revision$"

View File

@@ -1,8 +1,9 @@
note note
description : "Objects that ..." description: "[
author : "$Author$" Specific implementation of HTTP_CLIENT_REQUEST based on Eiffel cURL library
date : "$Date$" ]"
revision : "$Revision$" date: "$Date$"
revision: "$Revision$"
class class
LIBCURL_HTTP_CLIENT_REQUEST LIBCURL_HTTP_CLIENT_REQUEST
@@ -58,6 +59,10 @@ feature -- Execution
ctx: like context ctx: like context
p_slist: POINTER p_slist: POINTER
retried: BOOLEAN 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 do
if not retried then if not retried then
curl := session.curl curl := session.curl
@@ -73,16 +78,7 @@ feature -- Execution
--| URL --| URL
l_url := url l_url := url
if ctx /= Void then if ctx /= Void then
if attached ctx.query_parameters as l_query_params then append_parameters_to_url (ctx.query_parameters, l_url)
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 end
debug ("service") debug ("service")
@@ -91,78 +87,117 @@ feature -- Execution
end end
curl_easy.setopt_string (curl_handle, {CURL_OPT_CONSTANTS}.curlopt_url, l_url) curl_easy.setopt_string (curl_handle, {CURL_OPT_CONSTANTS}.curlopt_url, l_url)
--| Header l_headers := headers
if attached headers as l_headers then
across -- Context
l_headers as curs if ctx /= Void then
loop --| Credential
p_slist := curl.slist_append (p_slist, curs.key + ": " + curs.item) 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 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 end
p_slist := curl.slist_append (p_slist, "Expect:") p_slist := curl.slist_append (p_slist, "Expect:")
curl_easy.setopt_slist (curl_handle, {CURL_OPT_CONSTANTS}.curlopt_httpheader, p_slist) curl_easy.setopt_slist (curl_handle, {CURL_OPT_CONSTANTS}.curlopt_httpheader, p_slist)
--| Credential --| Execution
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_parameters as l_forms and then not l_forms.is_empty then
create l_form.make
create l_last.make
from
l_forms.start
until
l_forms.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
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
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)
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)
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
end
end
end
curl_easy.set_read_function (curl_handle) curl_easy.set_read_function (curl_handle)
curl_easy.set_write_function (curl_handle) curl_easy.set_write_function (curl_handle)
if is_debug then if is_debug then
@@ -175,6 +210,7 @@ feature -- Execution
create Result.make create Result.make
l_result := curl_easy.perform (curl_handle) l_result := curl_easy.perform (curl_handle)
--| Result
if l_result = {CURL_CODES}.curle_ok then if l_result = {CURL_CODES}.curle_ok then
Result.status := response_status_code (curl_easy, curl_handle) Result.status := response_status_code (curl_easy, curl_handle)
set_header_and_body_to (l_curl_string.string, Result) 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) Result.status := response_status_code (curl_easy, curl_handle)
end end
--| Cleaning
curl.global_cleanup curl.global_cleanup
curl_easy.cleanup (curl_handle) curl_easy.cleanup (curl_handle)
else else
create Result.make create Result.make
Result.set_error_occurred (True) Result.set_error_occurred (True)
end end
--| Remaining cleaning
if l_form /= Void then if l_form /= Void then
l_form.dispose l_form.dispose
end end

View File

@@ -1,8 +1,9 @@
note note
description : "Objects that ..." description: "[
author : "$Author$" Specific implementation of HTTP_CLIENT_SESSION based on Eiffel cURL library
date : "$Date$" ]"
revision : "$Revision$" date: "$Date$"
revision: "$Revision$"
class class
LIBCURL_HTTP_CLIENT_SESSION LIBCURL_HTTP_CLIENT_SESSION

View File

@@ -275,6 +275,7 @@ feature -- Test routines
fn: FILE_NAME fn: FILE_NAME
f: RAW_FILE f: RAW_FILE
s: STRING s: STRING
ctx: HTTP_CLIENT_REQUEST_CONTEXT
do do
get_http_session get_http_session
if attached http_session as sess then if attached http_session as sess then
@@ -284,6 +285,10 @@ feature -- Test routines
f.put_string (s) f.put_string (s)
f.close f.close
test_post_request_with_filename ("post/file/01", Void, fn.string, "post-file-01%N" + s) 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 else
assert ("not_implemented", False) assert ("not_implemented", False)
end end
@@ -298,10 +303,11 @@ feature -- Test routines
if attached http_session as sess then if attached http_session as sess then
create ctx.make create ctx.make
ctx.add_form_parameter ("id", "123") ctx.add_form_parameter ("id", "123")
test_post_request ("post/01", ctx, "post-01 : id=123") ctx.add_form_parameter ("Eiffel", "Web")
test_post_request ("post/01/?foo=bar", ctx, "post-01(foo=bar) : id=123") test_post_request ("post/01", ctx, "post-01 : " + ctx.form_parameters_to_url_encoded_string)
test_post_request ("post/01/?foo=bar&abc=def", ctx, "post-01(foo=bar&abc=def) : id=123") test_post_request ("post/01/?foo=bar", ctx, "post-01(foo=bar) : " + ctx.form_parameters_to_url_encoded_string)
test_post_request ("post/01/?lst=a&lst=b", ctx, "post-01(lst=[a,b]) : id=123") 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 else
assert ("not_implemented", False) assert ("not_implemented", False)
end end