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