diff --git a/library/network/http_client/http_client-safe.ecf b/library/network/http_client/http_client-safe.ecf index 4bcaca2f..4768dc03 100644 --- a/library/network/http_client/http_client-safe.ecf +++ b/library/network/http_client/http_client-safe.ecf @@ -10,12 +10,34 @@ - + + + + + + - + + + + + + - + + + + + + + + + + + + + - \ No newline at end of file + diff --git a/library/network/http_client/http_client.ecf b/library/network/http_client/http_client.ecf index e084ae0a..2e08d08f 100644 --- a/library/network/http_client/http_client.ecf +++ b/library/network/http_client/http_client.ecf @@ -1,5 +1,5 @@ - + @@ -9,13 +9,35 @@ - - + + + + + + + - + + + + + + - + + + + + + + + + + + + + - \ No newline at end of file + diff --git a/library/network/http_client/src/http_client.e b/library/network/http_client/src/http_client.e index a99953ca..bd6c2d89 100644 --- a/library/network/http_client/src/http_client.e +++ b/library/network/http_client/src/http_client.e @@ -16,7 +16,6 @@ feature -- Status deferred end - note copyright: "2011-2015, Jocelyn Fiat, Javier Velilla, Eiffel Software and others" license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)" diff --git a/library/network/http_client/src/http_client_response.e b/library/network/http_client/src/http_client_response.e index 37f57aa4..1db2e774 100644 --- a/library/network/http_client/src/http_client_response.e +++ b/library/network/http_client/src/http_client_response.e @@ -54,6 +54,9 @@ feature -- Access status_line: detachable READABLE_STRING_8 + http_version: detachable READABLE_STRING_8 + -- http version associated with `status_line'. + raw_header: READABLE_STRING_8 -- Raw http header of the response. @@ -190,15 +193,54 @@ feature -- Access feature -- Change + set_http_version (v: like http_version) + -- Set `http_version' to `v'. + do + http_version := v + end + set_status (s: INTEGER) -- Set response `status' code to `s' do status := s end + set_status_line (a_line: detachable READABLE_STRING_8) + -- Set status line to `a_line', + -- and also `status' extracted from `a_line' if possible. + local + i,j: INTEGER + s: READABLE_STRING_8 + do + status_line := a_line + http_version := Void + + if a_line /= Void then + if a_line.starts_with ("HTTP/") then + i := a_line.index_of (' ', 1) + if i > 0 then + http_version := a_line.substring (1 + 5, i - 1) -- ("HTTP/").count = 5 + i := i + 1 + end + else + i := 1 + end + -- Get status code token. + if i > 0 then + j := a_line.index_of (' ', i) + if j > i then + s := a_line.substring (i, j - 1) + if s.is_integer then + set_status (s.to_integer) + end + end + end + end + end + set_response_message (a_source: READABLE_STRING_8; ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT) -- Parse `a_source' response message - -- and set `header' and `body'. + -- and set `status_line', `status', `header' and `body'. --| ctx is the context associated with the request --| it might be useful to deal with redirection customization... local @@ -234,7 +276,7 @@ feature -- Change j := i + 2 pos := j - status_line := l_status_line + set_status_line (l_status_line) set_raw_header (h) -- libcURL does not cache redirection content. diff --git a/library/network/http_client/src/http_client_session.e b/library/network/http_client/src/http_client_session.e index 8b46f503..9f9d5ded 100644 --- a/library/network/http_client/src/http_client_session.e +++ b/library/network/http_client/src/http_client_session.e @@ -218,9 +218,9 @@ feature -- Element change cookie := Void end - set_cookie (c: READABLE_STRING_8) + set_cookie (a_cookie: detachable READABLE_STRING_8) do - cookie := c + cookie := a_cookie end set_timeout (n_seconds: like timeout) @@ -253,12 +253,26 @@ feature -- Element change headers.prune (k) end - set_credentials (u: like username; p: like password) + set_credentials (u,p: detachable READABLE_STRING_GENERAL) + local + s: STRING_32 do - username := u - password := p + if u = Void then + username := Void + else + create {STRING_32} username.make_from_string_general (u) + end + if p = Void then + password := Void + else + create {STRING_32} password.make_from_string_general (p) + end if u /= Void and p /= Void then - credentials := u + ":" + p + create s.make (u.count + 1 + p.count) + s.append_string_general (u) + s.append_character (':') + s.append_string_general (p) + credentials := s else credentials := Void end diff --git a/library/network/http_client/src/spec/null/null_http_client.e b/library/network/http_client/src/spec/null/null_http_client.e new file mode 100644 index 00000000..c2eec1ac --- /dev/null +++ b/library/network/http_client/src/spec/null/null_http_client.e @@ -0,0 +1,33 @@ +note + description : "[ + Instantiate one of the descendant of HTTP_CLIENT + then use `new_session' to create a session of http requests + ]" + date : "$Date$" + revision : "$Revision$" + +class + NULL_HTTP_CLIENT + +inherit + HTTP_CLIENT + +feature -- Status + + new_session (a_base_url: READABLE_STRING_8): NULL_HTTP_CLIENT_SESSION + -- Create a new session using `a_base_url'. + do + create Result.make (a_base_url) + end + +note + copyright: "2011-2015, 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/network/http_client/src/spec/null/null_http_client_session.e b/library/network/http_client/src/spec/null/null_http_client_session.e new file mode 100644 index 00000000..9a766e57 --- /dev/null +++ b/library/network/http_client/src/spec/null/null_http_client_session.e @@ -0,0 +1,114 @@ +note + description : "[ + 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 ... + ]" + date: "$Date$" + revision: "$Revision$" + +class + NULL_HTTP_CLIENT_SESSION + +inherit + HTTP_CLIENT_SESSION + +create + make + +feature {NONE} -- Initialization + + initialize + do + end + +feature -- Custom + + custom (a_method: READABLE_STRING_8; a_path: READABLE_STRING_8; ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT): HTTP_CLIENT_RESPONSE + do + create Result.make (base_url + a_path) + end + + custom_with_upload_data (a_method: READABLE_STRING_8; a_path: READABLE_STRING_8; a_ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT; data: READABLE_STRING_8): HTTP_CLIENT_RESPONSE + do + Result := impl_custom (a_method, a_path, a_ctx, data, Void) + end + + custom_with_upload_file (a_method: READABLE_STRING_8; a_path: READABLE_STRING_8; a_ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT; fn: READABLE_STRING_8): HTTP_CLIENT_RESPONSE + do + Result := impl_custom (a_method, a_path, a_ctx, Void, fn) + end + +feature -- Helper + + get (a_path: READABLE_STRING_8; ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT): HTTP_CLIENT_RESPONSE + do + Result := custom ("GET", a_path, ctx) + end + + head (a_path: READABLE_STRING_8; ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT): HTTP_CLIENT_RESPONSE + do + Result := custom ("HEAD", a_path, ctx) + end + + post (a_path: READABLE_STRING_8; a_ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT; data: detachable READABLE_STRING_8): HTTP_CLIENT_RESPONSE + do + Result := impl_custom ("POST", a_path, a_ctx, data, Void) + end + + post_file (a_path: READABLE_STRING_8; a_ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT; fn: detachable READABLE_STRING_8): HTTP_CLIENT_RESPONSE + do + Result := impl_custom ("POST", a_path, a_ctx, Void, fn) + end + + patch (a_path: READABLE_STRING_8; a_ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT; data: detachable READABLE_STRING_8): HTTP_CLIENT_RESPONSE + do + Result := impl_custom ("PATCH", a_path, a_ctx, data, Void) + end + + patch_file (a_path: READABLE_STRING_8; a_ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT; fn: detachable READABLE_STRING_8): HTTP_CLIENT_RESPONSE + do + Result := impl_custom ("PATCH", a_path, a_ctx, Void, fn) + end + + put (a_path: READABLE_STRING_8; a_ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT; data: detachable READABLE_STRING_8): HTTP_CLIENT_RESPONSE + do + Result := impl_custom ("PUT", a_path, a_ctx, data, Void) + end + + put_file (a_path: READABLE_STRING_8; a_ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT; fn: detachable READABLE_STRING_8): HTTP_CLIENT_RESPONSE + do + Result := impl_custom ("PUT", a_path, a_ctx, Void, fn) + end + + delete (a_path: READABLE_STRING_8; ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT): HTTP_CLIENT_RESPONSE + do + Result := custom ("DELETE", a_path, ctx) + end + +feature {NONE} -- Implementation + + impl_custom (a_method: READABLE_STRING_8; a_path: READABLE_STRING_8; a_ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT; data: detachable READABLE_STRING_8; fn: detachable READABLE_STRING_8): HTTP_CLIENT_RESPONSE + do + Result := custom (a_method, a_path, a_ctx) + end + +feature -- Status report + + is_available: BOOLEAN = False + -- Is interface usable? + +note + copyright: "2011-2015, 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/network/http_client/src/spec/socket/net_http_client_request.e b/library/network/http_client/src/spec/socket/net_http_client_request.e index 010c2477..4b29180e 100644 --- a/library/network/http_client/src/spec/socket/net_http_client_request.e +++ b/library/network/http_client/src/spec/socket/net_http_client_request.e @@ -18,6 +18,8 @@ inherit REFACTORING_HELPER + SHARED_EXECUTION_ENVIRONMENT + create make @@ -33,19 +35,19 @@ feature -- Access local redirection_response: detachable like response l_uri: URI + l_header_key: READABLE_STRING_8 l_host: READABLE_STRING_8 + l_cookie: detachable READABLE_STRING_8 l_request_uri: STRING l_url: HTTP_URL socket: NETWORK_STREAM_SOCKET s: STRING l_message: STRING - i,j: INTEGER l_content_length: INTEGER l_location: detachable READABLE_STRING_8 l_port: INTEGER l_authorization: HTTP_AUTHORIZATION l_platform: STRING - l_useragent: STRING l_upload_data: detachable READABLE_STRING_8 l_form_data: detachable HASH_TABLE [READABLE_STRING_32, READABLE_STRING_32] ctx: like context @@ -58,37 +60,40 @@ feature -- Access l_prev_header: READABLE_STRING_8 l_boundary: READABLE_STRING_8 l_is_http_1_0: BOOLEAN + retried: BOOLEAN do - ctx := context - if ctx /= Void then - l_is_http_1_0 := attached ctx.http_version as l_http_version and then l_http_version.same_string ("HTTP/1.0") - end - create Result.make (url) - - create l_form_string.make_empty - - -- Get URL data - create l_uri.make_from_string (url) - l_port := l_uri.port - if l_port = 0 then - if url.starts_with_general ("https://") then - l_port := 443 - else - l_port := 80 + if not retried then + ctx := context + if ctx /= Void then + l_is_http_1_0 := attached ctx.http_version as l_http_version and then l_http_version.same_string ("HTTP/1.0") end - end + create Result.make (url) - if attached l_uri.host as h then - l_host := h - else - create l_url.make (url) - l_host := l_url.host - end + create l_form_string.make_empty - -- add headers for authorization - if attached l_uri.userinfo as l_userinfo then - if attached l_uri.username as u_name then - if attached l_uri.password as u_pass then + -- Get URL data + create l_uri.make_from_string (url) + l_port := l_uri.port + if l_port = 0 then + if url.starts_with_general ("https://") then + l_port := 443 + else + l_port := 80 + end + end + if attached l_uri.host as h then + l_host := h + else + create l_url.make (url) + l_host := l_url.host + end + + -- add headers for authorization + if not headers.has ("Authorization") then + if + attached username as u_name and + attached password as u_pass + then create l_authorization.make_basic_auth (u_name, u_pass) if attached l_authorization.http_authorization as auth then headers.extend (auth, "Authorization") @@ -96,286 +101,356 @@ feature -- Access check headers.has_key ("Authorization") end end end - end - create l_request_uri.make_from_string (l_uri.path) - if attached l_uri.query as l_query then - l_request_uri.append_character ('?') - l_request_uri.append (l_query) - end - - -- add headers for user agent - if {PLATFORM}.is_unix then - l_platform := "Unix" - else - if {PLATFORM}.is_mac then - l_platform := "Mac" - else - l_platform := "Windows" - end - end - if l_platform /= Void then - l_useragent := "eiffelhttpclient/" + net_http_client_version + " (" + l_platform + ")" - headers.extend (l_useragent, "User-Agent") - end - - - -- handle sending data - if attached ctx then - if ctx.has_upload_filename then - l_upload_filename := ctx.upload_filename + create l_request_uri.make_from_string (l_uri.path) + if attached l_uri.query as l_query then + l_request_uri.append_character ('?') + l_request_uri.append (l_query) end - if ctx.has_upload_data then - l_upload_data := ctx.upload_data - 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 - headers.extend ("application/x-www-form-urlencoded", "Content-Type") - l_upload_data := ctx.form_parameters_to_url_encoded_string - headers.force (l_upload_data.count.out, "Content-Length") - + -- add computed header User-Agent if not yet set. + if not headers.has ("User-Agent") then + if {PLATFORM}.is_unix then + l_platform := "Unix" + elseif {PLATFORM}.is_windows then + l_platform := "Windows" + elseif {PLATFORM}.is_mac then + l_platform := "Mac" + elseif {PLATFORM}.is_vms then + l_platform := "VMS" + elseif {PLATFORM}.is_vxworks then + l_platform := "VxWorks" else - -- create form - l_boundary := new_mime_boundary - headers.extend ("multipart/form-data; boundary=" + l_boundary, "Content-Type") - if l_form_data /= Void then - headers.extend ("*/*", "Accept") - from - l_form_data.start - until - l_form_data.after - loop - l_form_string.append (l_boundary) - l_form_string.append (http_end_of_header_line) - l_form_string.append ("Content-Disposition: form-data; name=") - l_form_string.append ("%"" + l_form_data.key_for_iteration + "%"") - l_form_string.append (http_end_of_header_line) - l_form_string.append (http_end_of_header_line) - l_form_string.append (l_form_data.item_for_iteration) - l_form_string.append (http_end_of_header_line) - l_form_data.forth - end - - if l_upload_filename /= Void then - -- get file extension, otherwise set default - l_mime_type := "application/octet-stream" - create l_mime_type_mapping.make_default - l_fn_extension := l_upload_filename.tail (l_upload_filename.count - l_upload_filename.last_index_of ('.', l_upload_filename.count)) - if attached l_mime_type_mapping.mime_type (l_fn_extension) as mime then - l_mime_type := mime - end - - l_form_string.append (l_boundary) - l_form_string.append (http_end_of_header_line) - l_form_string.append ("Content-Disposition: form-data; name=%"" + l_upload_filename.as_string_32 + "%"") - l_form_string.append ("; filename=%"" + l_upload_filename + "%"") - l_form_string.append (http_end_of_header_line) - l_form_string.append ("Content-Type: ") - l_form_string.append (l_mime_type) - l_form_string.append (http_end_of_header_line) - l_form_string.append (http_end_of_header_line) - - create l_upload_file.make_with_name (l_upload_filename) - if l_upload_file.exists and then l_upload_file.is_readable then - l_upload_file.open_read - l_upload_file.read_stream (l_upload_file.count) - l_form_string.append (l_upload_file.last_string) - end - l_form_string.append (http_end_of_header_line) - end - l_form_string.append (l_boundary + "--") --| end - - l_upload_data := l_form_string - headers.extend (l_upload_data.count.out, "Content-Length") - end - end - else - if request_method.is_case_insensitive_equal ("POST") then - if ctx /= Void then - if ctx.has_upload_data then - l_upload_data := ctx.upload_data - if l_upload_data /= Void then - headers.extend ("application/x-www-form-urlencoded", "Content-Type") - headers.extend (l_upload_data.count.out, "Content-Length") - end - elseif ctx.has_upload_filename then - if l_upload_filename /= Void then - create l_upload_file.make_with_name (l_upload_filename) - if l_upload_file.exists and then l_upload_file.is_readable then - headers.extend (l_upload_file.count.out, "Content-Length") - l_upload_file.open_read - end - check l_upload_file /= Void end - end - end - end + l_platform := "Unknown" end + headers.extend ("eiffelhttpclient/" + net_http_client_version + " (" + l_platform + ")", "User-Agent") end - end - -- handle put requests - if request_method.is_case_insensitive_equal ("PUT") then + -- handle sending data if ctx /= Void then if ctx.has_upload_filename then - if l_upload_filename /= Void then - create l_upload_file.make_with_name (l_upload_filename) - if l_upload_file.exists and then l_upload_file.is_readable then - headers.extend (l_upload_file.count.out, "Content-Length") - l_upload_file.open_read + l_upload_filename := ctx.upload_filename + end + + if ctx.has_upload_data then + l_upload_data := ctx.upload_data + 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 + headers.extend ("application/x-www-form-urlencoded", "Content-Type") + l_upload_data := ctx.form_parameters_to_url_encoded_string + headers.force (l_upload_data.count.out, "Content-Length") + + else + -- create form + l_boundary := new_mime_boundary + headers.extend ("multipart/form-data; boundary=" + l_boundary, "Content-Type") + if l_form_data /= Void then + headers.extend ("*/*", "Accept") + from + l_form_data.start + until + l_form_data.after + loop + l_form_string.append (l_boundary) + l_form_string.append (http_end_of_header_line) + l_form_string.append ("Content-Disposition: form-data; name=") + l_form_string.append ("%"" + l_form_data.key_for_iteration + "%"") + l_form_string.append (http_end_of_header_line) + l_form_string.append (http_end_of_header_line) + l_form_string.append (l_form_data.item_for_iteration) + l_form_string.append (http_end_of_header_line) + l_form_data.forth + end + + if l_upload_filename /= Void then + -- get file extension, otherwise set default + l_mime_type := "application/octet-stream" + create l_mime_type_mapping.make_default + l_fn_extension := l_upload_filename.tail (l_upload_filename.count - l_upload_filename.last_index_of ('.', l_upload_filename.count)) + if attached l_mime_type_mapping.mime_type (l_fn_extension) as mime then + l_mime_type := mime + end + + l_form_string.append (l_boundary) + l_form_string.append (http_end_of_header_line) + l_form_string.append ("Content-Disposition: form-data; name=%"" + l_upload_filename.as_string_32 + "%"") + l_form_string.append ("; filename=%"" + l_upload_filename + "%"") + l_form_string.append (http_end_of_header_line) + l_form_string.append ("Content-Type: ") + l_form_string.append (l_mime_type) + l_form_string.append (http_end_of_header_line) + l_form_string.append (http_end_of_header_line) + + create l_upload_file.make_with_name (l_upload_filename) + if l_upload_file.exists and then l_upload_file.is_access_readable then + append_file_content_to (l_upload_file, l_upload_file.count, l_form_string) + -- Reset l_upload_file to Void, since the related content is already processed. + l_upload_file := Void + end + l_form_string.append (http_end_of_header_line) + end + l_form_string.append (l_boundary + "--") --| end + + l_upload_data := l_form_string + headers.extend (l_upload_data.count.out, "Content-Length") end - check l_upload_filename /= Void end + end + elseif + request_method.is_case_insensitive_equal ("POST") + or else request_method.is_case_insensitive_equal ("PUT") + then + if l_upload_data /= Void then + check ctx.has_upload_data end + headers.extend ("application/x-www-form-urlencoded", "Content-Type") + headers.extend (l_upload_data.count.out, "Content-Length") + elseif l_upload_filename /= Void then + check ctx.has_upload_filename end + create l_upload_file.make_with_name (l_upload_filename) + if l_upload_file.exists and then l_upload_file.readable then + headers.extend (l_upload_file.count.out, "Content-Length") + end + check l_upload_file /= Void end end end end - end - -- Connect - create socket.make_client_by_port (l_port, l_host) - socket.set_timeout (timeout) - socket.set_connect_timeout (connect_timeout) - socket.connect - if socket.is_connected then - create s.make_from_string (request_method.as_upper) - s.append_character (' ') - s.append (l_request_uri) - s.append_character (' ') - if l_is_http_1_0 then - s.append ("HTTP/1.0") - else - s.append ("HTTP/1.1") - end - s.append (Http_end_of_header_line) - - s.append (Http_host_header) - s.append (": ") - s.append (l_host) - s.append (http_end_of_header_line) - if attached session.cookie as cookie then - s.append ("Cookie: " + cookie + http_end_of_header_line) - end - if headers.is_empty then - s.append (Http_end_of_command) - else - across - headers as ic - loop - s.append (ic.key) - s.append (": ") - s.append (ic.item) - s.append (Http_end_of_header_line) + -- Connect + create socket.make_client_by_port (l_port, l_host) + socket.set_connect_timeout (connect_timeout) + socket.set_timeout (timeout) + socket.connect + if socket.is_connected then + -- FIXME: check usage of headers and specific header variable. + --| only one Cookie: is allowed, so merge multiple into one; + --| if Host is in header, use that one. + -- Compute Request line. + create s.make_from_string (request_method.as_upper) + s.append_character (' ') + s.append (l_request_uri) + s.append_character (' ') + if l_is_http_1_0 then + s.append ("HTTP/1.0") + else + s.append ("HTTP/1.1") end s.append (Http_end_of_header_line) - end - if l_upload_data /= Void then - s.append (l_upload_data) - s.append (http_end_of_header_line) - end - if attached l_upload_file then - if not l_upload_file.after then - from - until - l_upload_file.after - loop - l_upload_file.read_line_thread_aware - s.append (l_upload_file.last_string) - end - end - end - - socket.put_string (s) - - --| Get response. - --| Get header message - create l_message.make_empty - read_header_from_socket (socket, l_message) - l_prev_header := Result.raw_header - Result.set_raw_header (l_message.string) - l_content_length := -1 - if attached Result.header ("Content-Length") as s_len and then s_len.is_integer then - l_content_length := s_len.to_integer - end - l_location := Result.header ("Location") - if attached Result.header ("Set-Cookies") as s_cookies then - session.set_cookie (s_cookies) - end - l_message.append (http_end_of_header_line) - - -- Get content if any. - append_socket_content_to (Result, socket, l_content_length, l_message) - -- Restore previous header - Result.set_raw_header (l_prev_header) - -- Set message - Result.set_response_message (l_message, context) - - -- Get status code. - if attached Result.status_line as l_status_line then - if l_status_line.starts_with ("HTTP/") then - i := l_status_line.index_of (' ', 1) - if i > 0 then - i := i + 1 - end + -- Compute Header Host: + s.append (Http_host_header) + s.append (": ") + if attached headers [Http_host_header] as h_host then + s.append (h_host) else - i := 1 + s.append (l_host) end - -- Get status code token. - if i > 0 then - j := l_status_line.index_of (' ', i) - if j > i then - s := l_status_line.substring (i, j - 1) - if s.is_integer then - Result.set_status (s.to_integer) + s.append (http_end_of_header_line) + + -- Append the given request headers + l_cookie := Void + if headers.is_empty then + s.append (Http_end_of_command) + else + across + headers as ic + loop + l_header_key := ic.key + if l_header_key.same_string_general ("Host") then + -- FIXME: already handled elsewhere! + elseif l_header_key.same_string_general ("Cookie") then + -- FIXME: need cookie merging. + l_cookie := ic.item + else + s.append (ic.key) + s.append (": ") + s.append (ic.item) + s.append (Http_end_of_header_line) end end + s.append (Http_end_of_header_line) end - end - -- follow redirect - if l_location /= Void then - if current_redirects < max_redirects then - current_redirects := current_redirects + 1 - initialize (l_location, ctx) - redirection_response := response - redirection_response.add_redirection (Result.status_line, Result.raw_header, Result.body) - Result := redirection_response --- Result.add_redirection (redirection_response.status_line, redirection_response.raw_header, redirection_response.body) + -- Compute Header Cookie: if needed + -- Get session cookie + if l_cookie = Void then + l_cookie := session.cookie + else + -- Overwrite potential session cookie, if specified by the user. + end + if l_cookie /= Void then + s.append ("Cookie: ") + s.append (l_cookie) + s.append (http_end_of_header_line) end - end - current_redirects := 0 + if l_upload_data /= Void then + s.append (l_upload_data) + s.append (http_end_of_header_line) + end + --| Note that any remaining file to upload will be done directly via the socket + --| to optimize memory usage + + --| Send request + if socket.ready_for_writing then + socket.put_string (s) + --| Send remaining payload data, if needed. + if l_upload_file /= Void then + -- i.e: not yet processed + append_file_content_to_socket (l_upload_file, l_upload_file.count, socket) + end + + --| Get response. + --| Get header message + if socket.ready_for_reading then + create l_message.make_empty + append_socket_header_content_to (socket, l_message) + l_prev_header := Result.raw_header + Result.set_raw_header (l_message.string) + l_content_length := -1 + if attached Result.header ("Content-Length") as s_len and then s_len.is_integer then + l_content_length := s_len.to_integer + end + l_location := Result.header ("Location") + if attached Result.header ("Set-Cookies") as s_cookies then + session.set_cookie (s_cookies) + end + l_message.append (http_end_of_header_line) + + -- Get content if any. + append_socket_content_to (Result, socket, l_content_length, l_message) + -- Restore previous header + Result.set_raw_header (l_prev_header) + -- Set message + Result.set_response_message (l_message, ctx) + -- Check status code. + check status_coherent: attached Result.status_line as l_status_line implies l_status_line.has_substring (Result.status.out) end + + -- follow redirect + if l_location /= Void then + if current_redirects < max_redirects then + current_redirects := current_redirects + 1 + initialize (l_location, ctx) + redirection_response := response + redirection_response.add_redirection (Result.status_line, Result.raw_header, Result.body) + Result := redirection_response + end + end + + current_redirects := current_redirects - 1 + else + Result.set_error_message ("Read Timeout") + end + else + Result.set_error_message ("Write Timeout") + end + else + Result.set_error_message ("Could not connect") + end else - Result.set_error_message ("Could not connect") + create Result.make (url) + Result.set_error_message ("Error: internal error") + end + rescue + retried := True + retry + end + +feature {NONE} -- Helpers + + append_file_content_to_socket (a_file: FILE; a_len: INTEGER; a_output: NETWORK_STREAM_SOCKET) + -- Append `a_file' content to `a_output'. + -- If `a_len' >= 0 then read only `a_len' characters. + require + a_file_readable: a_file.exists and then a_file.is_access_readable + local + l_was_open: BOOLEAN + l_count: INTEGER + do + if a_len >= 0 then + l_count := a_len + else + l_count := a_file.count + end + if l_count > 0 then + l_was_open := a_file.is_open_read + if a_file.is_open_read then + l_was_open := True + else + a_file.open_read + end + from + until + l_count = 0 or a_file.exhausted + loop + a_file.read_stream_thread_aware (l_count.min (2_048)) + a_output.put_string (a_file.last_string) + l_count := l_count - a_file.bytes_read + end + if not l_was_open then + a_file.close + end end end -feature {NONE} -- Helpers + append_file_content_to (a_file: FILE; a_len: INTEGER; a_output: STRING) + -- Append `a_file' content to `a_output'. + -- If `a_len' >= 0 then read only `a_len' characters. + require + a_file_readable: a_file.exists and then a_file.is_access_readable + local + l_was_open: BOOLEAN + l_count: INTEGER + do + if a_len >= 0 then + l_count := a_len + else + l_count := a_file.count + end + if l_count > 0 then + l_was_open := a_file.is_open_read + if a_file.is_open_read then + l_was_open := True + else + a_file.open_read + end + from - read_header_from_socket (a_socket: NETWORK_STREAM_SOCKET; a_output: STRING) + until + l_count = 0 or a_file.exhausted + loop + a_file.read_stream_thread_aware (l_count.min (2_048)) + a_output.append (a_file.last_string) + l_count := l_count - a_file.bytes_read + end + if not l_was_open then + a_file.close + end + end + end + + append_socket_header_content_to (a_socket: NETWORK_STREAM_SOCKET; a_output: STRING) -- Get header from `a_socket' into `a_output'. local s: READABLE_STRING_8 do - if a_socket.is_readable then - from - s := "" - until - s.same_string ("%R") or not a_socket.is_readable - loop - a_socket.read_line_thread_aware - s := a_socket.last_string - if s.same_string ("%R") then - -- Reach end of header --- a_output.append (http_end_of_header_line) - else - a_output.append (s) - a_output.append_character ('%N') - end + from + s := "" + until + s.same_string ("%R") or not a_socket.readable + loop + a_socket.read_line_thread_aware + s := a_socket.last_string + if s.same_string ("%R") then + -- Reach end of header +-- a_output.append (http_end_of_header_line) + else + a_output.append (s) + a_output.append_character ('%N') end end end @@ -386,61 +461,94 @@ feature {NONE} -- Helpers -- this is probably HTTP/1.0 without any Content-Length. local s: STRING_8 - n,pos: INTEGER + n,pos, l_chunk_size, l_count: INTEGER hexa2int: HEXADECIMAL_STRING_TO_INTEGER_CONVERTER do - if a_socket.is_readable then + if a_socket.readable then if a_len >= 0 then - a_socket.read_stream_thread_aware (a_len) - s := a_socket.last_string - check full_content_read: a_socket.bytes_read = a_len end - a_buffer.append (s) - else - if attached a_response.header ("Transfer-Encoding") as l_enc and then l_enc.is_case_insensitive_equal ("chunked") then - from - create hexa2int.make - n := 1 - until - n = 0 or not a_socket.is_readable - loop - a_socket.read_line_thread_aware -- Read chunk info - s := a_socket.last_string - s.right_adjust - pos := s.index_of (';', 1) - if pos > 0 then - s.keep_head (pos - 1) - end - if s.is_empty then - n := 0 + from + n := a_len + until + n = 0 or else not a_socket.readable or else a_response.error_occurred + loop + if a_socket.ready_for_reading then + a_socket.read_stream_thread_aware (n) + l_count := l_count + a_socket.bytes_read + n := n - a_socket.bytes_read + a_buffer.append (a_socket.last_string) + else + a_response.set_error_message ("Could not read chunked data, timeout") + end + end + check full_content_read: l_count = a_len end + elseif attached a_response.header ("Transfer-Encoding") as l_enc and then l_enc.is_case_insensitive_equal ("chunked") then + from + create hexa2int.make + n := 1 + until + n = 0 or not a_socket.readable + loop + a_socket.read_line_thread_aware -- Read chunk info + s := a_socket.last_string + s.right_adjust + pos := s.index_of (';', 1) + if pos > 0 then + s.keep_head (pos - 1) + end + if s.is_empty then + n := 0 + else + hexa2int.parse_string_with_type (s, hexa2int.type_integer) + if hexa2int.parse_successful then + n := hexa2int.parsed_integer else - hexa2int.parse_string_with_type (s, hexa2int.type_integer) - if hexa2int.parse_successful then - n := hexa2int.parsed_integer + n := 0 + end + end + if n > 0 then + from + until + n = 0 or else not a_socket.readable or else a_response.error_occurred + loop + if a_socket.ready_for_reading then + a_socket.read_stream_thread_aware (n) + l_count := l_count + a_socket.bytes_read + n := n - a_socket.bytes_read + a_buffer.append (a_socket.last_string) else - n := 0 + a_response.set_error_message ("Could not read chunked data, timeout") end end - if n > 0 then - a_socket.read_stream_thread_aware (n) - check a_socket.bytes_read = n end - a_buffer.append (a_socket.last_string) + + if a_socket.ready_for_reading then a_socket.read_character check a_socket.last_character = '%R' end a_socket.read_character check a_socket.last_character = '%N' end + else + a_response.set_error_message ("Could not read chunked data, timeout") end end - else - -- HTTP/1.0 - from - n := 1_024 - until - n < 1_024 or not a_socket.is_readable - loop - a_socket.read_stream_thread_aware (1_024) + end + else + -- No Content-Length and no chunked transfer encoding! + -- maybe HTTP/1.0 ? + -- FIXME: check solution! + from + l_count := 0 + l_chunk_size := 1_024 + n := l_chunk_size --| value to satisfy until condition on first loop. + until + n < l_chunk_size or not a_socket.readable + loop + if a_socket.ready_for_reading then + a_socket.read_stream_thread_aware (l_chunk_size) s := a_socket.last_string n := a_socket.bytes_read + l_count := l_count + n a_buffer.append (s) + else + a_response.set_error_message ("Could not read data, timeout") end end end diff --git a/library/network/http_client/src/spec/socket/net_http_client_session.e b/library/network/http_client/src/spec/socket/net_http_client_session.e index 0e1fd2cb..2bec47b1 100644 --- a/library/network/http_client/src/spec/socket/net_http_client_session.e +++ b/library/network/http_client/src/spec/socket/net_http_client_session.e @@ -98,7 +98,6 @@ feature {NONE} -- Implementation local req: HTTP_CLIENT_REQUEST ctx: detachable HTTP_CLIENT_REQUEST_CONTEXT - f: detachable RAW_FILE l_data: detachable READABLE_STRING_8 do ctx := a_ctx @@ -117,34 +116,12 @@ feature {NONE} -- Implementation if ctx /= Void then l_data := ctx.upload_data if l_data /= Void and a_method.is_case_insensitive_equal_general ("PUT") then - --| Quick and dirty hack using real file, for PUT uploaded data - --| FIXME [2012-05-23]: better use libcurl for that purpose - - if ctx.has_upload_filename then - check put_conflict_file_and_data: False end - end - create f.make_open_write (create {FILE_NAME}.make_temporary_name) - f.put_string (l_data) - f.close - check ctx /= Void then - ctx.set_upload_data (Void) - ctx.set_upload_filename (f.path.name) - end + check put_conflict_file_and_data: not ctx.has_upload_filename end end end create {NET_HTTP_CLIENT_REQUEST} req.make (base_url + a_path, a_method, Current, ctx) Result := req.response - - -- FIXME file should be deleted, but uncommenting the following leads to a PERMISSION DENIED exception.. - --if f /= Void then - -- f.delete - --end - - if l_data /= Void and a_ctx /= Void then - a_ctx.set_upload_filename (Void) - a_ctx.set_upload_data (l_data) - end end note diff --git a/library/network/http_client/tests/application.e b/library/network/http_client/tests/application.e deleted file mode 100644 index dfb7e63b..00000000 --- a/library/network/http_client/tests/application.e +++ /dev/null @@ -1,190 +0,0 @@ -note - description : "httptest application root class" - date : "$Date$" - revision : "$Revision$" - -class - APPLICATION - -inherit - ARGUMENTS - -create - make - -feature {NONE} -- Initialization - - make - -- Run application. - do - requestbin_path := "/15u47xi2" - test_1 - test_2 - test_3 - test_4 - test_5 - test_6 - test_7 - end - - requestbin_path: STRING - -feature -- Tests - - test_1 - local - sess: NET_HTTP_CLIENT_SESSION - h: STRING_8 - l_ctx: HTTP_CLIENT_REQUEST_CONTEXT - do - -- URL ENCODED POST REQUEST - -- check requestbin to ensure the "Hello World" has been received in the raw body - -- also check that User-Agent was sent - create h.make_empty - create sess.make("http://requestb.in") - if attached sess.post (requestbin_path, Void, "Hello World").headers as hds then - across - hds as c - loop - h.append (c.item.name + ": " + c.item.value + "%R%N") - end - end - print (h) - end - - test_2 - local - sess: NET_HTTP_CLIENT_SESSION - h: STRING_8 - l_ctx: HTTP_CLIENT_REQUEST_CONTEXT - do - -- POST REQUEST WITH FORM DATA - -- check requestbin to ensure the form parameters are correctly received - create sess.make("http://requestb.in") - create l_ctx.make - l_ctx.form_parameters.extend ("First Value", "First Key") - l_ctx.form_parameters.extend ("Second Value", "Second Key") - create sess.make("http://requestb.in") - create h.make_empty - if attached sess.post (requestbin_path, l_ctx, "").headers as hds then - across - hds as c - loop - h.append (c.item.name + ": " + c.item.value + "%R%N") - end - end - print (h) - end - - test_3 - local - sess: NET_HTTP_CLIENT_SESSION - h: STRING_8 - l_ctx: HTTP_CLIENT_REQUEST_CONTEXT - do - -- POST REQUEST WITH A FILE - -- check requestbin to ensure the form parameters are correctly received - -- set filename to a local file - create sess.make("http://requestb.in") - create l_ctx.make - l_ctx.set_upload_filename ("C:\temp\test.txt") - create h.make_empty - if attached sess.post (requestbin_path, l_ctx, "").headers as hds then - across - hds as c - loop - h.append (c.item.name + ": " + c.item.value + "%R%N") - end - end - print (h) - end - - test_4 - local - sess: NET_HTTP_CLIENT_SESSION - h: STRING_8 - l_ctx: HTTP_CLIENT_REQUEST_CONTEXT - do - -- PUT REQUEST WITH A FILE - -- check requestbin to ensure the file is correctly received - -- set filename to a local file - create sess.make("http://requestb.in") - create l_ctx.make - l_ctx.set_upload_filename ("C:\temp\test.txt") - create h.make_empty - if attached sess.put (requestbin_path, l_ctx, "").headers as hds then - across - hds as c - loop - h.append (c.item.name + ": " + c.item.value + "%R%N") - end - end - print (h) - end - - test_5 - local - sess: NET_HTTP_CLIENT_SESSION - h: STRING_8 - l_ctx: HTTP_CLIENT_REQUEST_CONTEXT - do - -- POST REQUEST WITH A FILE AND FORM DATA - -- check requestbin to ensure the file and form parameters are correctly received - -- set filename to a local file - create sess.make("http://requestb.in") - create l_ctx.make - l_ctx.set_upload_filename ("C:\temp\logo.png") - l_ctx.form_parameters.extend ("First Value", "First Key") - l_ctx.form_parameters.extend ("Second Value", "Second Key") - create h.make_empty - if attached sess.post (requestbin_path, l_ctx, "").headers as hds then - across - hds as c - loop - h.append (c.item.name + ": " + c.item.value + "%R%N") - end - end - print (h) - end - - test_6 - local - sess: NET_HTTP_CLIENT_SESSION - h: STRING_8 - l_ctx: HTTP_CLIENT_REQUEST_CONTEXT - do - -- GET REQUEST, Forwarding (google's first answer is a forward) - -- check headers received (printed in console) - create sess.make("http://google.com") - create h.make_empty - if attached sess.get ("/", Void).headers as hds then - across - hds as c - loop - h.append (c.item.name + ": " + c.item.value + "%R%N") - end - end - print (h) - end - - test_7 - local - sess: NET_HTTP_CLIENT_SESSION - h: STRING_8 - l_ctx: HTTP_CLIENT_REQUEST_CONTEXT - do - -- GET REQUEST WITH AUTHENTICATION, see http://browserspy.dk/password.php - -- check header WWW-Authenticate is received (authentication successful) - create sess.make("http://test:test@browserspy.dk") - create h.make_empty - if attached sess.get ("/password-ok.php", Void).headers as hds then - across - hds as c - loop - h.append (c.item.name + ": " + c.item.value + "%R%N") - end - end - print (h) - end - -end diff --git a/library/network/http_client/tests/httptest.ecf b/library/network/http_client/tests/httptest.ecf deleted file mode 100644 index 884c053d..00000000 --- a/library/network/http_client/tests/httptest.ecf +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - /EIFGENs$ - /CVS$ - /.svn$ - - - - \ No newline at end of file diff --git a/library/network/http_client/tests/logo.jpg b/library/network/http_client/tests/logo.jpg new file mode 100644 index 00000000..cb234734 Binary files /dev/null and b/library/network/http_client/tests/logo.jpg differ diff --git a/library/network/http_client/tests/test.e b/library/network/http_client/tests/test.e index 0073561a..6829c245 100644 --- a/library/network/http_client/tests/test.e +++ b/library/network/http_client/tests/test.e @@ -6,7 +6,13 @@ create feature -- Init make + local + null: NULL_HTTP_CLIENT do + create null + if attached null.new_session ("http://example.com/") as l_sess then + check not l_sess.is_available end + end test_http_client end @@ -42,7 +48,7 @@ feature -- Init do if not b then create e - e.set_message (m) + e.set_description (m) e.raise end end diff --git a/library/network/http_client/tests/test.txt b/library/network/http_client/tests/test.txt new file mode 100644 index 00000000..08d13bd7 --- /dev/null +++ b/library/network/http_client/tests/test.txt @@ -0,0 +1 @@ +This is a text sample for testing HTTP Client library. diff --git a/library/network/http_client/tests/test_http_client_i.e b/library/network/http_client/tests/test_http_client_i.e index b42af04b..e49f6823 100644 --- a/library/network/http_client/tests/test_http_client_i.e +++ b/library/network/http_client/tests/test_http_client_i.e @@ -44,8 +44,6 @@ feature -- Test routines assert ("missing body", False) end assert ("same headers", h.same_string (res.raw_header)) - else - assert ("Not found", False) end end @@ -54,7 +52,6 @@ feature -- Test routines sess: like new_session h: STRING_8 do - --| Add your code here sess := new_session ("http://requestb.in") create h.make_empty if attached sess.get ("/1a0q2h61", Void).headers as hds then @@ -64,7 +61,6 @@ feature -- Test routines h.append (c.item.name + ": " + c.item.value + "%R%N") end end - print (h) end diff --git a/library/network/http_client/tests/test_libcurl_with_web.e b/library/network/http_client/tests/test_libcurl_with_web.e new file mode 100644 index 00000000..52c6fc23 --- /dev/null +++ b/library/network/http_client/tests/test_libcurl_with_web.e @@ -0,0 +1,67 @@ +note + description: "[ + Objects that ... + ]" + author: "$Author$" + date: "$Date$" + revision: "$Revision$" + +class + TEST_LIBCURL_WITH_WEB + +inherit + TEST_WITH_WEB_I + +feature -- Factory + + new_session (a_url: READABLE_STRING_8): HTTP_CLIENT_SESSION + local + cl: LIBCURL_HTTP_CLIENT + do + create cl + Result := cl.new_session (a_url) + end + +feature -- Tests + + libcurl_test_post_url_encoded + do + test_post_url_encoded + end + + libcurl_test_post_with_form_data + do + test_post_with_form_data + end + + libcurl_test_post_with_file + do + test_post_with_file + end + + libcurl_test_put_with_file + do + test_put_with_file + end + + libcurl_test_put_with_data + do + test_put_with_data + end + + libcurl_test_post_with_file_and_form_data + do + test_post_with_file_and_form_data + end + + libcurl_test_get_with_redirection + do + test_get_with_redirection + end + + libcurl_test_get_with_authentication + do + test_get_with_authentication + end + +end diff --git a/library/network/http_client/tests/test_net_with_web.e b/library/network/http_client/tests/test_net_with_web.e new file mode 100644 index 00000000..1c551aac --- /dev/null +++ b/library/network/http_client/tests/test_net_with_web.e @@ -0,0 +1,67 @@ +note + description: "[ + Objects that ... + ]" + author: "$Author$" + date: "$Date$" + revision: "$Revision$" + +class + TEST_NET_WITH_WEB + +inherit + TEST_WITH_WEB_I + +feature -- Factory + + new_session (a_url: READABLE_STRING_8): HTTP_CLIENT_SESSION + local + cl: NET_HTTP_CLIENT + do + create cl + Result := cl.new_session (a_url) + end + +feature -- Tests + + net_test_post_url_encoded + do + test_post_url_encoded + end + + net_test_post_with_form_data + do + test_post_with_form_data + end + + net_test_post_with_file + do + test_post_with_file + end + + net_test_put_with_file + do + test_put_with_file + end + + net_test_put_with_data + do + test_put_with_data + end + + net_test_post_with_file_and_form_data + do + test_post_with_file_and_form_data + end + + net_test_get_with_redirection + do + test_get_with_redirection + end + + net_test_get_with_authentication + do + test_get_with_authentication + end + +end diff --git a/library/network/http_client/tests/test_with_web_i.e b/library/network/http_client/tests/test_with_web_i.e new file mode 100644 index 00000000..f386d5cb --- /dev/null +++ b/library/network/http_client/tests/test_with_web_i.e @@ -0,0 +1,302 @@ +note + description: "[ + Eiffel tests that can be executed by testing tool. + ]" + author: "EiffelStudio test wizard" + date: "$Date$" + revision: "$Revision$" + testing: "type/manual" + +deferred class + TEST_WITH_WEB_I + +inherit + EQA_TEST_SET + redefine + on_prepare + end + +feature -- Initialization + + on_prepare + do + Precursor + global_requestbin_path := new_requestbin_path + end + +feature -- Factory + + new_session (a_url: READABLE_STRING_8): HTTP_CLIENT_SESSION + deferred + end + +feature -- Requestbin + + global_requestbin_path: detachable READABLE_STRING_8 + + new_requestbin_path: detachable STRING + local + i,j: INTEGER + do + if + attached new_session ("http://requestb.in") as sess and then + attached sess.post ("/api/v1/bins", Void, Void) as resp + then + if resp.error_occurred then + print ("Error occurred!%N") + elseif attached resp.body as l_content then + + i := l_content.substring_index ("%"name%":", 1) + if i > 0 then + j := l_content.index_of (',', i + 1) + if j > 0 then + Result := l_content.substring (i + 7, j - 1) + Result.adjust + if Result.starts_with ("%"") then + Result.remove_head (1) + end + if Result.ends_with ("%"") then + Result.remove_tail (1) + end + if not Result.starts_with ("/") then + Result.prepend_character ('/') + end + end + end + end + end + end + +feature -- Factory + + test_post_url_encoded + local + sess: HTTP_CLIENT_SESSION + h: STRING_8 + do + if attached global_requestbin_path as requestbin_path then + -- URL ENCODED POST REQUEST + -- check requestbin to ensure the "Hello World" has been received in the raw body + -- also check that User-Agent was sent + create h.make_empty + sess := new_session ("http://requestb.in") + if + attached sess.post (requestbin_path, Void, "Hello World") as res and then + attached res.headers as hds + then + across + hds as c + loop + h.append (c.item.name + ": " + c.item.value + "%R%N") + end + end + print (h) + else + assert ("Has requestbin path", False) + end + end + + test_post_with_form_data + local + sess: HTTP_CLIENT_SESSION + h: STRING_8 + l_ctx: HTTP_CLIENT_REQUEST_CONTEXT + do + if attached global_requestbin_path as requestbin_path then + + -- POST REQUEST WITH FORM DATA + -- check requestbin to ensure the form parameters are correctly received + sess := new_session ("http://requestb.in") + create l_ctx.make + l_ctx.form_parameters.extend ("First Value", "First Key") + l_ctx.form_parameters.extend ("Second Value", "Second Key") + create h.make_empty + if + attached sess.post (requestbin_path, l_ctx, "") as res and then + attached res.headers as hds + then + across + hds as c + loop + h.append (c.item.name + ": " + c.item.value + "%R%N") + end + end + print (h) + else + assert ("Has requestbin path", False) + end + end + + test_post_with_file + local + sess: HTTP_CLIENT_SESSION + h: STRING_8 + l_ctx: HTTP_CLIENT_REQUEST_CONTEXT + do + if attached global_requestbin_path as requestbin_path then + + -- POST REQUEST WITH A FILE + -- check requestbin to ensure the form parameters are correctly received + -- set filename to a local file + sess := new_session ("http://requestb.in") + create l_ctx.make + l_ctx.set_upload_filename ("test.txt") + create h.make_empty + if + attached sess.post (requestbin_path, l_ctx, "") as res and then + attached res.headers as hds + then + across + hds as c + loop + h.append (c.item.name + ": " + c.item.value + "%R%N") + end + end + print (h) + else + assert ("Has requestbin path", False) + end + end + + test_put_with_file + local + sess: HTTP_CLIENT_SESSION + h: STRING_8 + l_ctx: HTTP_CLIENT_REQUEST_CONTEXT + do + if attached global_requestbin_path as requestbin_path then + + -- PUT REQUEST WITH A FILE + -- check requestbin to ensure the file is correctly received + -- set filename to a local file + sess := new_session ("http://requestb.in") + create l_ctx.make + l_ctx.set_upload_filename ("test.txt") + create h.make_empty + if + attached sess.put (requestbin_path, l_ctx, Void) as res and then + attached res.headers as hds + then + across + hds as c + loop + h.append (c.item.name + ": " + c.item.value + "%R%N") + end + end + print (h) + else + assert ("Has requestbin path", False) + end + end + + test_put_with_data + local + sess: HTTP_CLIENT_SESSION + h: STRING_8 + l_ctx: HTTP_CLIENT_REQUEST_CONTEXT + do + if attached global_requestbin_path as requestbin_path then + + -- PUT REQUEST WITH A FILE + -- check requestbin to ensure the file is correctly received + -- set filename to a local file + sess := new_session ("http://requestb.in") + create l_ctx.make + l_ctx.set_upload_data ("This is a test for http client.%N") + create h.make_empty + if + attached sess.put (requestbin_path, l_ctx, Void) as res and then + attached res.headers as hds + then + across + hds as c + loop + h.append (c.item.name + ": " + c.item.value + "%R%N") + end + end + print (h) + else + assert ("Has requestbin path", False) + end + end + + test_post_with_file_and_form_data + local + sess: HTTP_CLIENT_SESSION + h: STRING_8 + l_ctx: HTTP_CLIENT_REQUEST_CONTEXT + do + if attached global_requestbin_path as requestbin_path then + + -- POST REQUEST WITH A FILE AND FORM DATA + -- check requestbin to ensure the file and form parameters are correctly received + -- set filename to a local file + sess := new_session ("http://requestb.in") + create l_ctx.make + l_ctx.set_upload_filename ("logo.jpg") + l_ctx.form_parameters.extend ("First Value", "First Key") + l_ctx.form_parameters.extend ("Second Value", "Second Key") + create h.make_empty + if + attached sess.post (requestbin_path, l_ctx, Void) as res and then + attached res.headers as hds + then + across + hds as c + loop + h.append (c.item.name + ": " + c.item.value + "%R%N") + end + end + print (h) + else + assert ("Has requestbin path", False) + end + end + + test_get_with_redirection + local + sess: HTTP_CLIENT_SESSION + h: STRING_8 + do + if attached global_requestbin_path as requestbin_path then + + -- GET REQUEST, Forwarding (google's first answer is a forward) + -- check headers received (printed in console) + sess := new_session ("http://google.com") + create h.make_empty + if attached sess.get ("/", Void) as res and then attached res.headers as hds then + across + hds as c + loop + h.append (c.item.name + ": " + c.item.value + "%R%N") + end + end + print (h) + else + assert ("Has requestbin path", False) + end + end + + test_get_with_authentication + local + sess: HTTP_CLIENT_SESSION + ctx: HTTP_CLIENT_REQUEST_CONTEXT + do + -- GET REQUEST WITH AUTHENTICATION, see http://browserspy.dk/password.php + -- check header WWW-Authenticate is received (authentication successful) + sess := new_session ("http://browserspy.dk") + sess.set_credentials ("test", "test") + create ctx.make_with_credentials_required + if attached sess.get ("/password-ok.php", ctx) as res then + if attached {READABLE_STRING_8} res.body as l_body then + assert ("Fetch all body, including closing html tag", l_body.has_substring ("")) + else + assert ("has body", False) + end + end + end + + +end + +