Merge branch 'reverse_proxy'

This commit is contained in:
2016-09-19 22:19:21 +02:00
12 changed files with 636 additions and 0 deletions

View File

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

View File

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

28
examples/proxy/proxy.ecf Normal file
View File

@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-15-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-15-0 http://www.eiffel.com/developers/xml/configuration-1-15-0.xsd" name="proxy" uuid="B55F0D95-3793-4C90-BBAC-BF5F2DECD5E6" library_target="proxy">
<target name="common" abstract="true">
<file_rule>
<exclude>/.svn$</exclude>
<exclude>/CVS$</exclude>
<exclude>/EIFGENs$</exclude>
</file_rule>
<option warning="true" full_class_checking="false" is_attached_by_default="true" void_safety="transitional" syntax="transitional">
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
</option>
<setting name="console_application" value="true"/>
<variable name="ssl_supported" value="false"/>
<library name="base" location="$ISE_LIBRARY\library\base\base-safe.ecf"/>
<library name="http" location="..\..\library\network\protocol\http\http-safe.ecf"/>
<library name="wsf" location="..\..\library\server\wsf\wsf-safe.ecf"/>
<library name="wsf_proxy" location="..\..\library\server\wsf_proxy\wsf_proxy-safe.ecf" readonly="false"/>
</target>
<target name="proxy" extends="common">
<root class="APPLICATION" feature="make_and_launch"/>
<option warning="true" is_attached_by_default="true" void_safety="all" syntax="transitional">
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
</option>
<setting name="concurrency" value="scoop"/>
<library name="default_standalone" location="..\..\library\server\wsf\default\standalone-safe.ecf"/>
<cluster name="proxy" location=".\" recursive="true"/>
</target>
</system>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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
-- <Precursor>.
do
Result := rewriter_function (a_request)
end
end

View File

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

View File

@@ -0,0 +1,34 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-15-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-15-0 http://www.eiffel.com/developers/xml/configuration-1-15-0.xsd" name="wsf_proxy" uuid="A39CCC27-BF63-4959-B881-7D0713F4C84A" library_target="wsf_proxy">
<target name="wsf_proxy">
<root all_classes="true"/>
<library name="base" location="$ISE_LIBRARY\library\base\base-safe.ecf"/>
<library name="http" location="..\..\..\library\network\protocol\http\http-safe.ecf"/>
<library name="net" location="$ISE_LIBRARY\library\net\net-safe.ecf"/>
<library name="net_ssl" location="$ISE_LIBRARY\unstable\library\network\socket\netssl\net_ssl-safe.ecf">
<condition>
<custom name="ssl_supported" value="true"/>
</condition>
</library>
<library name="uri" location="$ISE_LIBRARY\library\text\uri\uri-safe.ecf"/>
<library name="wsf" location="..\wsf\wsf-safe.ecf"/>
<cluster name="network" location=".\network\" recursive="true">
<file_rule>
<exclude>no_ssl</exclude>
<exclude>ssl</exclude>
</file_rule>
<cluster name="network_ssl" location="$|ssl\">
<condition>
<custom name="ssl_supported" value="true"/>
</condition>
</cluster>
<cluster name="network_no_ssl" location="$|no_ssl\">
<condition>
<custom name="ssl_supported" excluded_value="true"/>
</condition>
</cluster>
</cluster>
<cluster name="reverse_proxy" location=".\reverse_proxy\" recursive="true"/>
<cluster name="rewriter" location=".\rewriter\" recursive="true"/>
</target>
</system>

View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<system xmlns="http://www.eiffel.com/developers/xml/configuration-1-15-0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eiffel.com/developers/xml/configuration-1-15-0 http://www.eiffel.com/developers/xml/configuration-1-15-0.xsd" name="wsf_proxy" uuid="A39CCC27-BF63-4959-B881-7D0713F4C84A" library_target="wsf_proxy">
<target name="wsf_proxy">
<root all_classes="true"/>
<option void_safety="none">
</option>
<library name="base" location="$ISE_LIBRARY\library\base\base.ecf"/>
<library name="http" location="..\..\..\library\network\protocol\http\http.ecf"/>
<library name="net" location="$ISE_LIBRARY\library\net\net.ecf"/>
<library name="wsf" location="..\wsf\wsf.ecf"/>
<cluster name="reverse_proxy" location=".\reverse_proxy\" recursive="true"/>
<cluster name="rewriter" location=".\rewriter\" recursive="true"/>
</target>
</system>