diff --git a/contrib/ise_library/cURL b/contrib/ise_library/cURL index ad2a498f..c4e25a30 160000 --- a/contrib/ise_library/cURL +++ b/contrib/ise_library/cURL @@ -1 +1 @@ -Subproject commit ad2a498fc067926713ffdc0a9db99358f142fada +Subproject commit c4e25a309132a7adced96f5ac1e278eb44582945 diff --git a/library/client/http_client/http_client-safe.ecf b/library/client/http_client/http_client-safe.ecf index de757e10..34db8bfd 100644 --- a/library/client/http_client/http_client-safe.ecf +++ b/library/client/http_client/http_client-safe.ecf @@ -13,12 +13,12 @@ - + - + diff --git a/library/client/http_client/http_client.ecf b/library/client/http_client/http_client.ecf index a4ec1415..ffce8e4e 100644 --- a/library/client/http_client/http_client.ecf +++ b/library/client/http_client/http_client.ecf @@ -13,12 +13,12 @@ - + - + diff --git a/library/client/http_client/src/http_client_request.e b/library/client/http_client/src/http_client_request.e index fbdc8b92..11a485ab 100644 --- a/library/client/http_client/src/http_client_request.e +++ b/library/client/http_client/src/http_client_request.e @@ -31,6 +31,14 @@ feature {NONE} -- Initialization 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 diff --git a/library/client/http_client/src/http_client_session.e b/library/client/http_client/src/http_client_session.e index af9cb3e7..e1b8bdf2 100644 --- a/library/client/http_client/src/http_client_session.e +++ b/library/client/http_client/src/http_client_session.e @@ -122,12 +122,16 @@ feature -- Basic operation 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. @@ -173,7 +177,15 @@ feature -- Authentication credentials: detachable READABLE_STRING_32 -feature -- Change + +feature -- Status setting + + set_is_debug (b: BOOLEAN) + do + is_debug := b + end + +feature -- Element change set_base_url (u: like base_url) do diff --git a/library/client/http_client/src/spec/libcurl/libcurl_http_client_request.e b/library/client/http_client/src/spec/libcurl/libcurl_http_client_request.e index e346b3b3..ce81683e 100644 --- a/library/client/http_client/src/spec/libcurl/libcurl_http_client_request.e +++ b/library/client/http_client/src/spec/libcurl/libcurl_http_client_request.e @@ -48,38 +48,173 @@ feature -- Execution l_result: INTEGER l_curl_string: CURL_STRING l_url: READABLE_STRING_8 - p: POINTER - l_form, l_last: CURL_FORM - curl: CURL_EXTERNALS - curl_easy: CURL_EASY_EXTERNALS + 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 - l_proxy: like proxy + p_slist: POINTER + retried: BOOLEAN do - ctx := context - curl := session.curl - curl_easy := session.curl_easy + if not retried then + curl := session.curl + curl_easy := session.curl_easy + curl_handle := curl_easy.init + curl.global_init - 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 + ctx := context + + --| Configure cURL session + initialize_curl_session (ctx, curl, curl_easy, curl_handle) + + --| 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 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) + + --| 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) + + --| 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 + + 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) + + 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 + + curl.global_cleanup + curl_easy.cleanup (curl_handle) + else + create Result.make + Result.set_error_occurred (True) end + 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 - --| Configure cURL session - curl_handle := curl_easy.init - - --| URL - curl_easy.setopt_string (curl_handle, {CURL_OPT_CONSTANTS}.curlopt_url, l_url) - + 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) @@ -118,6 +253,7 @@ feature -- Execution 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 @@ -132,98 +268,6 @@ feature -- Execution 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_parameters as l_forms and then not l_forms.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_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 --- 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) ---| Not Yet Implemented - end - 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) - 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) - - 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 - - curl_easy.cleanup (curl_handle) end feature {NONE} -- Implementation diff --git a/library/client/http_client/src/spec/libcurl/libcurl_upload_file_read_function.e b/library/client/http_client/src/spec/libcurl/libcurl_upload_file_read_function.e new file mode 100644 index 00000000..317be974 --- /dev/null +++ b/library/client/http_client/src/spec/libcurl/libcurl_upload_file_read_function.e @@ -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 + -- + 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 diff --git a/library/server/wsf/tests/src/test_wsf_request.e b/library/server/wsf/tests/src/test_wsf_request.e index 9017133d..fe73d718 100644 --- a/library/server/wsf/tests/src/test_wsf_request.e +++ b/library/server/wsf/tests/src/test_wsf_request.e @@ -77,6 +77,7 @@ feature {NONE} -- Events execute (req: WSF_REQUEST; res: WSF_RESPONSE) local q: detachable STRING_32 + n: NATURAL_64 page: WSF_PAGE_RESPONSE do debug @@ -145,6 +146,15 @@ feature {NONE} -- Events if not q.is_empty then page.put_string (" : " + q ) end + elseif l_uri.starts_with (test_url ("post/file/01")) then + page.put_header (200, <<["Content-Type", "text/plain"]>>) + page.put_string ("post-file-01") + n := req.content_length_value + if n > 0 then + req.input.read_string (n.to_integer_32) + q := req.input.last_string + page.put_string ("%N" + q) + end else page.put_header (200, <<["Content-Type", "text/plain"]>>) page.put_string ("Hello") @@ -191,6 +201,7 @@ feature {NONE} -- Events if attached {HTTP_CLIENT_SESSION} h.new_session ("localhost:" + port_number.out + "/" + b) as sess then http_session := sess sess.set_timeout (-1) + sess.set_is_debug (True) sess.set_connect_timeout (-1) -- sess.set_proxy ("127.0.0.1", 8888) --| inspect traffic with http://www.fiddler2.com/ end @@ -220,6 +231,18 @@ feature {NONE} -- Events end end + test_post_request_with_filename (a_url: READABLE_STRING_8; ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT; a_fn: STRING; a_expected_body: READABLE_STRING_8) + do + get_http_session + if attached http_session as sess then + if attached sess.post_file (a_url, adapted_context (ctx), a_fn) as res and then not res.error_occurred and then attached res.body as l_body then + assert ("Good answer got=%""+l_body+"%" expected=%""+a_expected_body+"%"", l_body.same_string (a_expected_body)) + else + assert ("Request %""+a_url+"%" failed", False) + end + end + end + adapted_context (ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT): HTTP_CLIENT_REQUEST_CONTEXT do if ctx /= Void then @@ -246,6 +269,26 @@ feature -- Test routines end end + test_post_request_uploaded_file + -- New test routine + local + fn: FILE_NAME + f: RAW_FILE + s: STRING + do + get_http_session + if attached http_session as sess then + create fn.make_temporary_name + create f.make_create_read_write (fn.string) + s := "This is an uploaded file%NTesting purpose%N" + f.put_string (s) + f.close + test_post_request_with_filename ("post/file/01", Void, fn.string, "post-file-01%N" + s) + else + assert ("not_implemented", False) + end + end + test_post_request_01 -- New test routine local diff --git a/library/server/wsf/tests/tests-safe.ecf b/library/server/wsf/tests/tests-safe.ecf index 2f52e285..6d3bfd74 100644 --- a/library/server/wsf/tests/tests-safe.ecf +++ b/library/server/wsf/tests/tests-safe.ecf @@ -7,7 +7,7 @@ /EIFGENs$ /.svn$ - @@ -15,10 +15,10 @@ + -