diff --git a/examples/proxy/application_execution.e b/examples/proxy/application_execution.e index ab54495e..5e3d88d4 100644 --- a/examples/proxy/application_execution.e +++ b/examples/proxy/application_execution.e @@ -9,41 +9,98 @@ class inherit WSF_EXECUTION - WSF_URI_REWRITER - rename - uri as proxy_uri - end - create make feature -- Basic operations execute + local + l_forwarded: BOOLEAN do - -- NOTE: please enter the target server uri here - -- replace "http://localhost:8080/foobar" - send_proxy_response ("http://localhost:8080/foobar", Current) + -- NOTE: please edit the proxy.conf file + across + proxy_map as ic + until + l_forwarded + loop + if request.path_info.starts_with_general (ic.key) then + l_forwarded := True + send_proxy_response (ic.key, ic.item, agent proxy_uri (ic.key, ?)) + end + end + if not l_forwarded then + response.send (create {WSF_PAGE_RESPONSE}.make_with_body ("EiffelWeb proxy: not forwarded!")) + end end - send_proxy_response (a_remote: READABLE_STRING_8; a_rewriter: detachable WSF_URI_REWRITER) + proxy_map: HASH_TABLE [STRING, STRING] + -- location => target + local + f: PLAIN_TEXT_FILE + l_line: STRING + p: INTEGER + once ("thread") + create Result.make (1) + -- Load proxy.conf + create f.make_with_name ("proxy.conf") + if f.exists and then f.is_access_readable then + f.open_read + from + until + f.end_of_file or f.exhausted + loop + f.read_line + l_line := f.last_string + if l_line.starts_with ("#") then + -- ignore + else + -- Format: + -- path%Tserver + p := l_line.index_of ('%T', 1) + if p > 0 then + Result.force (l_line.substring (p + 1, l_line.count), l_line.head (p - 1)) + end + end + end + f.close + end + end + + send_proxy_response (a_location, a_remote: READABLE_STRING_8; a_rewriter: detachable FUNCTION [WSF_REQUEST, STRING]) 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 + if a_rewriter /= Void then + h.set_uri_rewriter (create {WSF_AGENT_URI_REWRITER}.make (a_rewriter)) + end + h.set_timeout_ns (10_000_000_000) -- 10 seconds h.set_connect_timeout (5_000) -- milliseconds = 5 seconds + + -- Uncomment following, if you want to provide proxy information +-- h.set_header_via (True) +-- h.set_header_forwarded (True) +-- h.set_header_x_forwarded (True) + -- Uncomment following line to keep the original Host value. +-- h.keep_proxy_host (True) + h.execute (request, response) end feature -- Helpers - proxy_uri (a_request: WSF_REQUEST): STRING + proxy_uri (a_location: READABLE_STRING_8; a_request: WSF_REQUEST): STRING -- Request uri rewriten as url. do Result := a_request.request_uri + -- If related proxy setting is + -- a_location=/foo -> http://foo.com + -- and if request was http://example.com/foo/bar, it will use http://foo.com/bar + -- so the Result here, is "/bar" + if Result.starts_with (a_location) then + Result.remove_head (a_location.count) + end end end diff --git a/examples/proxy/proxy.conf b/examples/proxy/proxy.conf new file mode 100644 index 00000000..5ba995d7 --- /dev/null +++ b/examples/proxy/proxy.conf @@ -0,0 +1,2 @@ +/google/ http://www.google.com/search?q=eiffel +/ http://localhost:8080/testproxy 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 index a7697d02..48444127 100644 --- 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 @@ -6,6 +6,14 @@ note class WSF_SIMPLE_REVERSE_PROXY_HANDLER +inherit + WSF_TIMEOUT_UTILITIES + export + {NONE} all + end + + ANY + create make @@ -14,9 +22,11 @@ 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 + timeout_ns := 30_000_000_000 -- seconds. See {NETWORK_SOCKET}.default_timeout connect_timeout := 5_000 -- 5 seconds. - is_via_header_supported := True + recv_timeout_ns := 5_000_000_000 -- 5 seconds + send_timeout_ns := 5_000_000_000 -- 5 seconds + is_forwarded_header_supported := True end feature -- Access @@ -33,13 +43,36 @@ feature -- Settings connect_timeout: INTEGER assign set_connect_timeout -- In milliseconds. - timeout: INTEGER assign set_timeout - -- In seconds. + timeout_ns, + recv_timeout_ns, + send_timeout_ns: NATURAL_64 - is_via_header_supported: BOOLEAN - -- Via: header supported. + is_via_header_supported: BOOLEAN assign set_header_via + -- "Via:" header supported. + -- See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Via -- Default: True. + is_forwarded_header_supported: BOOLEAN assign set_header_forwarded + -- "Forwarded:" header supported (standard) + -- Forwarded: by=;for=;host=;proto= + -- See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Forwarded + -- and https://tools.ietf.org/html/rfc7239#section-4 + -- Default: False + + is_x_forwarded_header_supported: BOOLEAN assign set_header_x_forwarded + -- "X-Forwarded-For:" header supported (XFF), and related ... + -- See: de-facto standard https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-For + -- https://en.wikipedia.org/wiki/X-Forwarded-For + -- Default: False + + is_using_proxy_host: BOOLEAN assign keep_proxy_host + -- Do not change the HTTP_HOST. + -- Default: False + +feature -- Forwarded header settings + + header_forwarded_by: detachable READABLE_STRING_8 assign set_header_forwarded_by + feature -- Change set_uri_rewriter (a_rewriter: like uri_rewriter) @@ -47,10 +80,30 @@ feature -- Change uri_rewriter := a_rewriter end +feature -- Timeout Change + set_timeout (a_timeout_in_seconds: INTEGER) -- in seconds. do - timeout := a_timeout_in_seconds + set_timeout_ns (seconds_to_nanoseconds (a_timeout_in_seconds)) + end + + set_timeout_ns (a_timeout_ns: NATURAL_64) + -- in nanoseconds. + do + timeout_ns := a_timeout_ns + end + + set_recv_timeout_ns (ns: NATURAL_64) + -- in nanoseconds. + do + recv_timeout_ns := ns + end + + set_send_timeout_ns (ns: NATURAL_64) + -- in nanoseconds. + do + send_timeout_ns := ns end set_connect_timeout (a_timeout_in_milliseconds: INTEGER) @@ -59,12 +112,37 @@ feature -- Change connect_timeout := a_timeout_in_milliseconds end - set_is_via_header_supported (b: BOOLEAN) +feature -- Header Change + + set_header_forwarded (b: BOOLEAN) + -- Set `is_forwarded_header_supported` to `b`. + do + is_forwarded_header_supported := b + end + + set_header_forwarded_by (a_id: like header_forwarded_by) + do + header_forwarded_by := a_id + end + + set_is_via_header_supported, + set_header_via (b: BOOLEAN) -- Set `is_via_header_supported' to `b'. do is_via_header_supported := b end + set_header_x_forwarded (b: BOOLEAN) + -- Set `is_x_forwarded_header_supported` to `b`. + do + is_x_forwarded_header_supported := b + end + + keep_proxy_host (b: BOOLEAN) + do + is_using_proxy_host := b + end + feature -- Execution proxy_uri (request: WSF_REQUEST): STRING @@ -90,20 +168,31 @@ feature -- Execution l_completed: BOOLEAN l_remote_uri: like remote_uri l_socket_factory: WSF_PROXY_SOCKET_FACTORY + l_forwarded: STRING + s: READABLE_STRING_8 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 + elseif attached {NETWORK_STREAM_SOCKET} 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.set_timeout_ns (timeout_ns) + l_socket.set_recv_timeout_ns (recv_timeout_ns) + l_socket.set_send_timeout_ns (send_timeout_ns) l_socket.connect - if l_socket.is_connected then + if + l_socket.is_connected and then + attached l_socket.peer_address as l_socket_peer_address + then create l_http_query.make_from_string (request.request_method) l_http_query.append_character (' ') l_http_query.append (l_remote_uri.path) + if attached l_remote_uri.query as q then + l_http_query.append_character ('?') + l_http_query.append (q) + end l_http_query.append (proxy_uri (request)) l_http_query.append_character (' ') l_http_query.append (request.server_protocol) @@ -115,15 +204,22 @@ feature -- Execution else create h.make_from_raw_header_data (l_raw_header.to_string_8) end - if attached l_remote_uri.host as l_remote_host then + s := Void + if is_using_proxy_host then + if attached request.http_host as l_request_host then + s := l_request_host + end + elseif attached l_remote_uri.host as l_remote_host then + s := l_remote_host 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) + s := s + ":" + l_remote_uri.port.out end end + if s /= Void then + h.put_header_key_value ("Host", s) + end - -- Via header + -- Proxy related headers if is_via_header_supported then if attached h.item ("Via") as v then l_via := v @@ -131,9 +227,51 @@ feature -- Execution else create l_via.make_empty end - l_via.append (request.server_protocol + " " + request.server_name + " (PROXY-" + request.server_software + ")") + l_via.append (request.server_protocol) + l_via.append_character (' ') + l_via.append (request.server_name) + l_via.append (" (PROXY-") + l_via.append (request.server_software) + l_via.append_character (')') h.put_header_key_value ("Via", l_via) end + if is_forwarded_header_supported then + -- Forwarded: for=;host=;proto= + create l_forwarded.make (50) + l_forwarded.append ("for=") + l_forwarded.append (request.remote_addr) + if attached request.http_host as l_host then + l_forwarded.append (";host=") + l_forwarded.append (l_host) + end + l_forwarded.append (";proto=") + if request.is_https then + l_forwarded.append ("https") + else + l_forwarded.append ("http") + end + if attached header_forwarded_by as l_id then + l_forwarded.append (";by=") + l_forwarded.append (l_id) + end + if attached request.meta_string_variable ("HTTP_FORWARDED") as l_req_forwarded then + l_forwarded := l_req_forwarded + ", " + l_forwarded + end + h.put_header_key_value ("Forwarded", l_forwarded) + end + if is_x_forwarded_header_supported then + s := request.remote_addr + if attached request.meta_string_variable ("HTTP_X_FORWARDED_FOR") as l_xff then + s := l_xff + ", " + s + end + h.put_header_key_value ("X-Forwarded-For", s) + h.put_header_key_value ("X-Forwarded-Port", request.server_port.out) + if request.is_https then + h.put_header_key_value ("X-Forwarded-Proto", "https") + else + h.put_header_key_value ("X-Forwarded-Proto", "http") + end + end -- Max-Forwards header handling if attached h.item ("Max-Forwards") as h_max_forward then