about summary refs log tree commit homepage
path: root/lib/yahns/proxy_pass.rb
diff options
context:
space:
mode:
Diffstat (limited to 'lib/yahns/proxy_pass.rb')
-rw-r--r--lib/yahns/proxy_pass.rb82
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