From 98e92ee0fea304751d46f0cdad48b84729933342 Mon Sep 17 00:00:00 2001 From: Jocelyn Fiat Date: Wed, 17 Jun 2015 10:32:07 +0200 Subject: [PATCH] Basic initial Eiffel NET implementation. --- .../network/http_client/http_client-safe.ecf | 5 +- .../http_client/src/http_client_response.e | 8 +- .../src/spec/socket/net_http_client_request.e | 162 +++++++++++++++++- .../network/http_client/tests/test-safe.ecf | 2 +- .../http_client/tests/test_http_client.e | 49 +++++- 5 files changed, 215 insertions(+), 11 deletions(-) diff --git a/library/network/http_client/http_client-safe.ecf b/library/network/http_client/http_client-safe.ecf index 87aea4f2..5601cf27 100644 --- a/library/network/http_client/http_client-safe.ecf +++ b/library/network/http_client/http_client-safe.ecf @@ -1,5 +1,5 @@ - + @@ -11,8 +11,9 @@ - + + diff --git a/library/network/http_client/src/http_client_response.e b/library/network/http_client/src/http_client_response.e index aa6e547f..37f57aa4 100644 --- a/library/network/http_client/src/http_client_response.e +++ b/library/network/http_client/src/http_client_response.e @@ -13,7 +13,7 @@ create feature {NONE} -- Initialization - make (a_url: like url) + make (a_url: READABLE_STRING_8) -- Initialize `Current'. do --| Default values @@ -115,7 +115,7 @@ feature -- Access loop l_start := pos --| Left justify - from until not h[l_start].is_space loop + from until not h [l_start].is_space loop l_start := l_start + 1 end pos := h.index_of ('%N', l_start) @@ -129,7 +129,7 @@ feature -- Access end if l_end > 0 then --| Right justify - from until not h[l_end].is_space loop + from until not h [l_end].is_space loop l_end := l_end - 1 end c := h.index_of (':', l_start) @@ -308,7 +308,7 @@ feature {NONE} -- Implementation -- Internal cached value for the headers ;note - copyright: "2011-2012, Jocelyn Fiat, Javier Velilla, Eiffel Software and others" + 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 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 c9146cae..244a30b7 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 @@ -14,6 +14,11 @@ inherit session end + TRANSFER_COMMAND_CONSTANTS + undefine + is_equal + end + REFACTORING_HELPER create @@ -27,11 +32,162 @@ feature -- Access response: HTTP_CLIENT_RESPONSE -- + local + l_uri: URI + l_host: 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 do - to_implement ("implementation based on EiffelNET") - (create {EXCEPTIONS}).die (0) - end + create Result.make (url) + -- 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 + if attached l_uri.userinfo as l_userinfo then + -- TODO: Add support for HTTP Authorization + -- See {HTTP_AUTHORIZATION} from http_authorization EWF library. + 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 + + -- 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 (' ') + s.append (Http_version) + s.append (Http_end_of_header_line) + + s.append (Http_host_header) + s.append (": ") + s.append (l_host) + 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) + end + s.append (Http_end_of_header_line) + end + + socket.put_string (s) + + -- Get header message + from + l_content_length := -1 + create l_message.make_empty + socket.read_line + s := socket.last_string + until + s.same_string ("%R") or not socket.is_readable + loop + l_message.append (s) + l_message.append_character ('%N') + -- Search for Content-Length is not yet set. + if + l_content_length = -1 and then -- Content-Length is not yet found. + s.starts_with_general ("Content-Length:") + then + i := s.index_of (':', 1) + check has_colon: i > 0 end + s.remove_head (i) + s.right_adjust -- Remove trailing %R + if s.is_integer then + l_content_length := s.to_integer + end + elseif + l_location = Void and then + s.starts_with_general ("Location:") + then + i := s.index_of (':', 1) + check has_colon: i > 0 end + s.remove_head (i) + s.left_adjust -- Remove startung spaces + s.right_adjust -- Remove trailing %R + l_location := s + end + -- Next iteration + socket.read_line + s := socket.last_string + end + l_message.append (http_end_of_header_line) + + -- Get content if any. + if + l_content_length > 0 and + socket.is_readable + then + socket.read_stream_thread_aware (l_content_length) + if socket.bytes_read /= l_content_length then + check full_content_read: False end + end + l_message.append (socket.last_string) + end + 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 + else + i := 1 + 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) + end + end + end + end + if l_location /= Void then + -- TODO: add redirection support. + -- See if it could be similar to libcurl implementation + -- otherwise, maybe update the class HTTP_CLIENT_RESPONSE. + end + else + Result.set_error_message ("Could not connect") + end + 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/tests/test-safe.ecf b/library/network/http_client/tests/test-safe.ecf index f2bce7ee..8d9005e2 100644 --- a/library/network/http_client/tests/test-safe.ecf +++ b/library/network/http_client/tests/test-safe.ecf @@ -1,5 +1,5 @@ - + diff --git a/library/network/http_client/tests/test_http_client.e b/library/network/http_client/tests/test_http_client.e index 0000f8d8..cae5e7ba 100644 --- a/library/network/http_client/tests/test_http_client.e +++ b/library/network/http_client/tests/test_http_client.e @@ -15,7 +15,7 @@ inherit feature -- Test routines - test_http_client + test_libcurl_http_client -- New test routine local sess: LIBCURL_HTTP_CLIENT_SESSION @@ -42,6 +42,53 @@ feature -- Test routines assert ("Not found", False) end end + + test_socket_http_client + -- New test routine + local + sess: NET_HTTP_CLIENT_SESSION + h: STRING_8 + do + create sess.make ("http://www.google.com") + if attached sess.get ("/search?q=eiffel", Void) as res then + assert ("Get returned without error", not res.error_occurred) + create h.make_empty + if attached res.headers as hds then + across + hds as c + loop + h.append (c.item.name + ": " + c.item.value + "%R%N") + end + end + if attached res.body as l_body then + assert ("body not empty", not l_body.is_empty) + else + assert ("missing body", False) + end + assert ("same headers", h.same_string (res.raw_header)) + else + assert ("Not found", False) + end + end + + test_socket_http_client_requestbin + local + sess: NET_HTTP_CLIENT_SESSION + h: STRING_8 + do + --| Add your code here + create sess.make("http://requestb.in") + create h.make_empty + if attached sess.get ("/1a0q2h61", 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_headers local