diff --git a/examples/proxy/application.e b/examples/proxy/application.e new file mode 100644 index 00000000..a90f4897 --- /dev/null +++ b/examples/proxy/application.e @@ -0,0 +1,29 @@ +note + description: "Launcher for reverse proxy web application." + date: "$Date$" + revision: "$Revision$" + +class + APPLICATION + +inherit + WSF_DEFAULT_SERVICE [APPLICATION_EXECUTION] + redefine + initialize + end + +create + make_and_launch + +feature {NONE} -- Initialization + + initialize + -- Initialize current service. + do + -- Specific to `standalone' connector (the EiffelWeb server). + -- See `{WSF_STANDALONE_SERVICE_LAUNCHER}.initialize' + set_service_option ("port", 9090) + import_service_options (create {WSF_SERVICE_LAUNCHER_OPTIONS_FROM_INI}.make_from_file ("server.ini")) + end + +end diff --git a/examples/proxy/application_execution.e b/examples/proxy/application_execution.e new file mode 100644 index 00000000..ab54495e --- /dev/null +++ b/examples/proxy/application_execution.e @@ -0,0 +1,49 @@ +note + description: "Reverse proxy example." + date: "$Date$" + revision: "$Revision$" + +class + APPLICATION_EXECUTION + +inherit + WSF_EXECUTION + + WSF_URI_REWRITER + rename + uri as proxy_uri + end + +create + make + +feature -- Basic operations + + execute + do + -- NOTE: please enter the target server uri here + -- replace "http://localhost:8080/foobar" + send_proxy_response ("http://localhost:8080/foobar", Current) + end + + send_proxy_response (a_remote: READABLE_STRING_8; a_rewriter: detachable WSF_URI_REWRITER) + local + h: WSF_SIMPLE_REVERSE_PROXY_HANDLER + do + create h.make (a_remote) + h.set_uri_rewriter (a_rewriter) + h.set_uri_rewriter (create {WSF_AGENT_URI_REWRITER}.make (agent proxy_uri)) + h.set_timeout (30) -- 30 seconds + h.set_connect_timeout (5_000) -- milliseconds = 5 seconds + h.execute (request, response) + end + +feature -- Helpers + + proxy_uri (a_request: WSF_REQUEST): STRING + -- Request uri rewriten as url. + do + Result := a_request.request_uri + end + +end diff --git a/examples/proxy/proxy.ecf b/examples/proxy/proxy.ecf new file mode 100644 index 00000000..9bf59a52 --- /dev/null +++ b/examples/proxy/proxy.ecf @@ -0,0 +1,28 @@ + + + + + /.svn$ + /CVS$ + /EIFGENs$ + + + + + + + + + + + + + + + + + diff --git a/examples/proxy/server.ini b/examples/proxy/server.ini new file mode 100644 index 00000000..ae90215c --- /dev/null +++ b/examples/proxy/server.ini @@ -0,0 +1,8 @@ +verbose=true +verbose_level=ALERT +port=9090 +#max_concurrent_connections=100 +#keep_alive_timeout=15 +#max_tcp_clients=100 +#socket_timeout=300 +#max_keep_alive_requests=300 diff --git a/library/server/wsf_proxy/network/no_ssl/wsf_proxy_socket_factory.e b/library/server/wsf_proxy/network/no_ssl/wsf_proxy_socket_factory.e new file mode 100644 index 00000000..21c2e7ea --- /dev/null +++ b/library/server/wsf_proxy/network/no_ssl/wsf_proxy_socket_factory.e @@ -0,0 +1,20 @@ +note + description: "Summary description for {WSF_PROXY_SOCKET_FACTORY}." + author: "" + date: "$Date$" + revision: "$Revision$" + +class + WSF_PROXY_SOCKET_FACTORY + +inherit + WSF_PROXY_SOCKET_FACTORY_I + +feature {NONE} -- Implementation + + ssl_socket (a_host: READABLE_STRING_8; a_port: INTEGER): detachable NETWORK_STREAM_SOCKET + do + check supported: False end + end + +end diff --git a/library/server/wsf_proxy/network/ssl/wsf_proxy_socket_factory.e b/library/server/wsf_proxy/network/ssl/wsf_proxy_socket_factory.e new file mode 100644 index 00000000..ad264908 --- /dev/null +++ b/library/server/wsf_proxy/network/ssl/wsf_proxy_socket_factory.e @@ -0,0 +1,30 @@ +note + description: "Summary description for {WSF_PROXY_SOCKET_FACTORY}." + author: "" + date: "$Date$" + revision: "$Revision$" + +class + WSF_PROXY_SOCKET_FACTORY + +inherit + WSF_PROXY_SOCKET_FACTORY_I + redefine + is_ssl_supported + end + +feature {NONE} -- Implementation + + ssl_socket (a_host: READABLE_STRING_8; a_port: INTEGER): detachable SSL_NETWORK_STREAM_SOCKET + do + if attached create_from_name (a_host) as l_peer_address then + create Result.make_client_by_address_and_port (l_peer_address, a_port) + end + end + +feature -- Status + + is_ssl_supported: BOOLEAN = True + -- Is https:// supported? + +end diff --git a/library/server/wsf_proxy/network/wsf_proxy_socket_factory_i.e b/library/server/wsf_proxy/network/wsf_proxy_socket_factory_i.e new file mode 100644 index 00000000..900db008 --- /dev/null +++ b/library/server/wsf_proxy/network/wsf_proxy_socket_factory_i.e @@ -0,0 +1,67 @@ +note + description: "Summary description for {WSF_PROXY_SOCKET_FACTORY_I}." + date: "$Date$" + revision: "$Revision$" + +deferred class + WSF_PROXY_SOCKET_FACTORY_I + +inherit + INET_ADDRESS_FACTORY + +feature -- Access + + socket_from_uri (a_uri: URI): like socket + local + l_port: INTEGER + do + if a_uri.is_valid and then attached a_uri.host as l_host then + l_port := a_uri.port + if a_uri.scheme.is_case_insensitive_equal_general ("https") then + if is_ssl_supported then + if l_port <= 0 then + l_port := 443 + end + Result := ssl_socket (l_host, l_port) + end + elseif a_uri.scheme.is_case_insensitive_equal_general ("http") then + if l_port <= 0 then + l_port := 80 + end + Result := socket (l_host, l_port) + end + end + end + +feature -- Status + + is_uri_supported (a_uri: URI): BOOLEAN + do + Result := a_uri.scheme.is_case_insensitive_equal_general ("http") + or else ( + a_uri.scheme.is_case_insensitive_equal_general ("https") + and is_ssl_supported + ) + end + + is_ssl_supported: BOOLEAN + -- Is https:// supported? + do + end + +feature {NONE} -- Implementation + + socket (a_host: READABLE_STRING_8; a_port: INTEGER): detachable NETWORK_STREAM_SOCKET + do + if attached create_from_name (a_host) as l_peer_address then + create Result.make_client_by_address_and_port (l_peer_address, a_port) + end + end + + ssl_socket (a_host: READABLE_STRING_8; a_port: INTEGER): detachable NETWORK_STREAM_SOCKET + require + is_ssl_supported: is_ssl_supported + deferred + end + +end diff --git a/library/server/wsf_proxy/reverse_proxy/wsf_simple_reverse_proxy_handler.e b/library/server/wsf_proxy/reverse_proxy/wsf_simple_reverse_proxy_handler.e new file mode 100644 index 00000000..a0ab3c6b --- /dev/null +++ b/library/server/wsf_proxy/reverse_proxy/wsf_simple_reverse_proxy_handler.e @@ -0,0 +1,303 @@ +note + description: "Summary description for {WSF_SIMPLE_REVERSE_PROXY_HANDLER}." + date: "$Date$" + revision: "$Revision$" + +class + WSF_SIMPLE_REVERSE_PROXY_HANDLER + +create + make + +feature {NONE} -- Initialization + + make (a_remote_uri: READABLE_STRING_8) + do + create remote_uri.make_from_string (a_remote_uri) + timeout := 30 -- seconds. See {NETWORK_SOCKET}.default_timeout + connect_timeout := 5_000 -- 5 seconds. + is_via_header_supported := True + end + +feature -- Access + + remote_uri: URI + -- Url for the targetted service. + + uri_rewriter: detachable WSF_URI_REWRITER assign set_uri_rewriter + -- URI rewriter component, to compute the URI on targetted service + -- based on current request. + +feature -- Settings + + connect_timeout: INTEGER assign set_connect_timeout + -- In milliseconds. + + timeout: INTEGER assign set_timeout + -- In seconds. + + is_via_header_supported: BOOLEAN + -- Via: header supported. + -- Default: True. + +feature -- Change + + set_uri_rewriter (a_rewriter: like uri_rewriter) + do + uri_rewriter := a_rewriter + end + + set_timeout (a_timeout_in_seconds: INTEGER) + -- in seconds. + do + timeout := a_timeout_in_seconds + end + + set_connect_timeout (a_timeout_in_milliseconds: INTEGER) + -- in milliseconds. + do + connect_timeout := a_timeout_in_milliseconds + end + + set_is_via_header_supported (b: BOOLEAN) + -- Set `is_via_header_supported' to `b'. + do + is_via_header_supported := b + end + +feature -- Execution + + proxy_uri (request: WSF_REQUEST): STRING + -- URI to query on proxyfied host. + do + if attached uri_rewriter as r then + Result := r.uri (request) + else + Result := request.request_uri + end + end + + execute (request: WSF_REQUEST; response: WSF_RESPONSE) + -- Execute reverse proxy request. + local + h: HTTP_HEADER + l_http_query: STRING + l_status_line: STRING + l_max_forward: INTEGER + l_via: detachable STRING + l_protocol: STRING + i: INTEGER + l_completed: BOOLEAN + l_remote_uri: like remote_uri + l_socket_factory: WSF_PROXY_SOCKET_FACTORY + do + l_remote_uri := remote_uri + create l_socket_factory + if not l_socket_factory.is_uri_supported (l_remote_uri) then + send_error (request, response, {HTTP_STATUS_CODE}.bad_gateway, l_remote_uri.scheme + " is not supported! [for remote " + l_remote_uri.string + "]") + elseif attached l_socket_factory.socket_from_uri (l_remote_uri) as l_socket then + l_socket.set_connect_timeout (connect_timeout) -- milliseconds + l_socket.set_timeout (timeout) -- seconds + + l_socket.connect + if l_socket.is_connected then + create l_http_query.make_from_string (request.request_method) + l_http_query.append_character (' ') + l_http_query.append (l_remote_uri.path) + l_http_query.append (proxy_uri (request)) + l_http_query.append_character (' ') + l_http_query.append (request.server_protocol) + if attached request.raw_header_data as l_raw_header then + i := l_raw_header.substring_index ("%R%N", 1) + if i > 0 then + -- Skip the first status line. + create h.make_from_raw_header_data (l_raw_header.substring (i + 2, l_raw_header.count)) + else + create h.make_from_raw_header_data (l_raw_header) + end + if attached l_remote_uri.host as l_remote_host then + if l_remote_uri.port > 0 then + h.put_header_key_value ("Host", l_remote_host + ":" + l_remote_uri.port.out) + else + h.put_header_key_value ("Host", l_remote_host) + end + end + + -- Via header + if is_via_header_supported then + if attached h.item ("Via") as v then + l_via := v + l_via.append (", ") + else + create l_via.make_empty + end + l_via.append (request.server_protocol + " " + request.server_name + " (PROXY-" + request.server_software + ")") + h.put_header_key_value ("Via", l_via) + end + + -- Max-Forwards header handling + if attached h.item ("Max-Forwards") as h_max_forward then + -- Max-Forwards: 0 stop, otherwise decrement by one. + -- see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.31 + if h_max_forward.is_integer then + l_max_forward := h_max_forward.to_integer - 1 + if l_max_forward >= 0 then + h.put_header_key_value ("Max-Forwards", l_max_forward.out) + end + end + end + if l_max_forward < 0 then + -- i.e previous Max-Forwards was '0' + send_error (request, response, {HTTP_STATUS_CODE}.bad_gateway, "Reached maximum number of Forwards, not forwarded to " + l_remote_uri.string) + else + l_socket.put_string (l_http_query) + l_socket.put_string ("%R%N") + l_socket.put_string (h.string) + l_socket.put_string ("%R%N") + if request.content_length_value > 0 then + request.read_input_data_into_file (l_socket) + end + + -- Get HTTP status + l_socket.read_line_thread_aware + create l_status_line.make_from_string (l_socket.last_string) + -- Get HTTP header block + if attached next_http_header_block (l_socket) as l_resp_header then + create h.make_from_raw_header_data (l_resp_header) + if attached status_line_info (l_status_line) as l_status_info then + l_protocol := l_status_info.protocol + if attached l_status_info.reason_phrase as l_phrase then + response.set_status_code_with_reason_phrase (l_status_info.status_code, l_phrase) + else + response.set_status_code (l_status_info.status_code) + end + else + check has_status_line: False end + l_protocol := "1.0" -- Default? + response.set_status_code (80) + end + + if is_via_header_supported then + if attached h.item ("Via") as v then + l_via := v + l_via.append (", ") + else + create l_via.make_empty + end + l_via.append (l_protocol + " " + request.server_name + " (PROXY-" + request.server_software + ")") + h.put_header_key_value ("Via", l_via) + end + + response.add_header_lines (h) + from + l_socket.read_stream (2_048) + until + l_socket.was_error + or not l_socket.is_connected + or l_socket.bytes_read <= 0 + or l_completed + loop + response.put_string (l_socket.last_string) + if l_socket.bytes_read = 2_048 then + l_socket.read_stream (2_048) + else + l_completed := True + end + end + else + send_error (request, response, {HTTP_STATUS_CODE}.internal_server_error, "Invalid response header!") + end + end + else + send_error (request, response, {HTTP_STATUS_CODE}.internal_server_error, "Can not access request header!") + end + else + send_error (request, response, {HTTP_STATUS_CODE}.gateway_timeout, "Unable to connect " + l_remote_uri.string) + end + else + send_error (request, response, {HTTP_STATUS_CODE}.bad_gateway, "Unable to connect " + l_remote_uri.string) + end + end + +feature {NONE} -- Implementation + + status_line_info (a_line: READABLE_STRING_8): detachable TUPLE [protocol: READABLE_STRING_8; status_code: INTEGER; reason_phrase: detachable READABLE_STRING_8] + -- Info from status line + --| Such as "HTTP/1.1 200 OK" -> ["1.1", 200, "OK"] + local + i,j: INTEGER + p,s: detachable READABLE_STRING_8 + c: INTEGER + do + i := a_line.index_of (' ', 1) + if i > 0 then + p := a_line.substring (1, i - 1) + if p.starts_with_general ("HTTP/") then + p := p.substring (6, p.count) -- We could also keep HTTP/ + end + j := i + 1 + i := a_line.index_of (' ', j) + if i > 0 then + s := a_line.substring (j, i - 1) + if s.is_integer then + c := s.to_integer + s := a_line.substring (i + 1, a_line.count) + if s.is_whitespace then + s := Void + elseif s[s.count].is_space then + s := s.substring (1, s.count - 1) + end + Result := [p, c, s] + end + end + end + end + + next_http_header_block (a_socket: NETWORK_STREAM_SOCKET): detachable STRING + local + h: STRING + do + create h.make_empty + from + a_socket.read_line_thread_aware + until + Result /= Void + or a_socket.was_error + or (a_socket.bytes_read = 0 or a_socket.bytes_read = -1) + or not a_socket.is_connected + loop + if a_socket.last_string.same_string ("%R") then + -- End of header + Result := h + else + h.append (a_socket.last_string) + h.append ("%N") + a_socket.read_line_thread_aware + end + end + end + + send_error (request: WSF_REQUEST; response: WSF_RESPONSE; a_status_code: INTEGER; a_message: READABLE_STRING_8) + local + s: STRING + do + -- To send a response we need to setup, the status code and + -- the response headers. + create s.make_from_string (a_message) + debug + s.append ("%N(UTC time is " + (create {HTTP_DATE}.make_now_utc).rfc850_string + ").%N") + end + response.put_header ({HTTP_STATUS_CODE}.ok, <<["Content-Type", "plain/text"], ["Content-Length", s.count.out]>>) + response.set_status_code (a_status_code) + response.header.put_content_type_text_html + response.header.put_content_length (s.count) + if + attached request.http_connection as l_connection and then + l_connection.is_case_insensitive_equal_general ("keep-alive") + then + response.header.put_header_key_value ("Connection", "keep-alive") + end + response.put_string (s) + end + +end diff --git a/library/server/wsf_proxy/rewriter/wsf_agent_uri_rewriter.e b/library/server/wsf_proxy/rewriter/wsf_agent_uri_rewriter.e new file mode 100644 index 00000000..fc054b36 --- /dev/null +++ b/library/server/wsf_proxy/rewriter/wsf_agent_uri_rewriter.e @@ -0,0 +1,38 @@ +note + description: "Summary description for {WSF_AGENT_URI_REWRITER}." + author: "" + date: "$Date$" + revision: "$Revision$" + +class + WSF_AGENT_URI_REWRITER + +inherit + WSF_URI_REWRITER + +create + make + +--convert +-- make ({FUNCTION [TUPLE [WSF_REQUEST], STRING]}) + +feature {NONE} -- Initialization + + make (a_rewriter_function: like rewriter_function) + do + rewriter_function := a_rewriter_function + end + +feature -- Access + + rewriter_function: FUNCTION [TUPLE [WSF_REQUEST], STRING] + +feature -- Conversion + + uri (a_request: WSF_REQUEST): STRING + -- . + do + Result := rewriter_function (a_request) + end + +end diff --git a/library/server/wsf_proxy/rewriter/wsf_uri_rewriter.e b/library/server/wsf_proxy/rewriter/wsf_uri_rewriter.e new file mode 100644 index 00000000..6cbc1cad --- /dev/null +++ b/library/server/wsf_proxy/rewriter/wsf_uri_rewriter.e @@ -0,0 +1,16 @@ +note + description: "Summary description for {WSF_URI_REWRITER}." + date: "$Date$" + revision: "$Revision$" + +deferred class + WSF_URI_REWRITER + +feature -- Conversion + + uri (a_request: WSF_REQUEST): STRING + -- Rewritten request uri based on `a_request'. + deferred + end + +end diff --git a/library/server/wsf_proxy/wsf_proxy-safe.ecf b/library/server/wsf_proxy/wsf_proxy-safe.ecf new file mode 100644 index 00000000..9dd777ce --- /dev/null +++ b/library/server/wsf_proxy/wsf_proxy-safe.ecf @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + no_ssl + ssl + + + + + + + + + + + + + + + + diff --git a/library/server/wsf_proxy/wsf_proxy.ecf b/library/server/wsf_proxy/wsf_proxy.ecf new file mode 100644 index 00000000..01cf4581 --- /dev/null +++ b/library/server/wsf_proxy/wsf_proxy.ecf @@ -0,0 +1,14 @@ + + + + + + + + + + + + +