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
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

View File

@@ -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_character ('=')
a_url.append_string (a_param.value)
l_first_param := False
if l_first_param then
a_url.append_character ('?')
else
a_url.append_character ('&')
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

View File

@@ -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)"

View File

@@ -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$"

View File

@@ -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$"

View File

@@ -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,78 +87,117 @@ 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)
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)
--| 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_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
--| 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

View File

@@ -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

View File

@@ -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