diff options
Diffstat (limited to 'lib/yahns')
-rw-r--r-- | lib/yahns/proxy_pass.rb | 82 |
1 files changed, 67 insertions, 15 deletions
diff --git a/lib/yahns/proxy_pass.rb b/lib/yahns/proxy_pass.rb index 2a37773..bc902f8 100644 --- a/lib/yahns/proxy_pass.rb +++ b/lib/yahns/proxy_pass.rb @@ -1,24 +1,76 @@ # -*- encoding: binary -*- -# Copyright (C) 2013-2016 all contributors <yahns-public@yhbt.net> -# License: GPL-3.0+ (https://www.gnu.org/licenses/gpl-3.0.txt) +# Copyright (C) 2013-2019 all contributors <yahns-public@yhbt.net> +# License: GPL-3.0+ <https://www.gnu.org/licenses/gpl-3.0.txt> # frozen_string_literal: true require 'socket' require 'rack/request' -require 'timeout' - -# XXX consider this file and the proxy-related stuff in yahns -# unstable and experimental! It has never been documented and -# incompatible changes may still happen. -# -# However, it seems to be proxying for our mail archives well enough: -# https://yhbt.net/yahns-public/ +require 'timeout' # only for Timeout::Error require_relative 'proxy_http_response' require_relative 'req_res' -class Yahns::ProxyPass # :nodoc: - attr_reader :proxy_buffering, :response_headers +# Yahns::ProxyPass is a Rack (hijack) app which allows yahns to +# act as a fully-buffering reverse proxy to protect backends +# from slow HTTP clients. +# +# Yahns::ProxyPass relies on the default behavior of yahns to do +# full input and output buffering. Output buffering is lazy, +# meaning it allows streaming output in the best case and +# will only buffer if the client cannot keep up with the server. +# +# The goal of this reverse proxy is to act as a sponge on the same LAN +# or host to any backend HTTP server not optimized for slow clients. +# Yahns::ProxyPass accomplishes this by handling all the slow clients +# internally within yahns itself to minimize time spent in the backend +# HTTP server waiting on slow clients. +# +# It does not do load balancing (we rely on Varnish for that). +# Here is the exact config we use with Varnish, which uses +# the +:response_headers+ option to hide some Varnish headers +# from clients: +# +# run Yahns::ProxyPass.new('http://127.0.0.1:6081', +# response_headers: { +# 'Age' => :ignore, +# 'X-Varnish' => :ignore, +# 'Via' => :ignore +# }) +# +# This is NOT a generic Rack app and must be run with yahns. +# It uses +rack.hijack+, so compatibility with logging +# middlewares (e.g. Rack::CommonLogger) is not great and +# timing information gets lost. +# +# This provides HTTPS termination for our mail archives: +# https://yhbt.net/yahns-public/ +# +# See https://yhbt.net/yahns.git/tree/examples/https_proxy_pass.conf.rb +# and https://yhbt.net/yahns.git/tree/examples/proxy_pass.ru for examples +class Yahns::ProxyPass + attr_reader :proxy_buffering, :response_headers # :nodoc: - def initialize(dest, opts = {}) + # +dest+ must be an HTTP URL with optional variables prefixed with '$'. + # +dest+ may refer to the path to a Unix domain socket in the form: + # + # unix:/absolute/path/to/socket + # + # Variables which may be used in the +dest+ parameter include: + # + # - $url - the entire URL used to make the request + # - $path - the unescaped PATH_INFO of the HTTP request + # - $fullpath - $path with QUERY_STRING + # - $host - the hostname in the Host: header + # + # For Unix domain sockets, variables may be separated from the + # socket path via: ":/". For example: + # + # unix:/absolute/path/to/socket:/$host/$fullpath + # + # Currently :response_headers is the only +opts+ supported. + # :response_headers is a Hash containing a "from => to" mapping + # of response headers. The special value of +:ignore+ indicates + # the header from the backend HTTP server will be ignored instead + # of being blindly passed on to the client. + def initialize(dest, opts = { response_headers: { 'Server' => :ignore } }) case dest when %r{\Aunix:([^:]+)(?::(/.*))?\z} path = $2 @@ -41,7 +93,7 @@ class Yahns::ProxyPass # :nodoc: init_path_vars(path) end - def init_path_vars(path) + def init_path_vars(path) # :nodoc: path ||= '$fullpath' # methods from Rack::Request we want: allow = %w(fullpath host_with_port host port url path) @@ -54,7 +106,7 @@ class Yahns::ProxyPass # :nodoc: @path = path.gsub(%r{\A/(\$(?:fullpath|path))}, '\1') end - def call(env) + def call(env) # :nodoc: # 3-way handshake for TCP backends while we generate the request header rr = Yahns::ReqRes.start(@sockaddr) c = env['rack.hijack'].call # Yahns::HttpClient#call |