Class: Yahns::ProxyPass

Inherits:
Object
  • Object
show all
Defined in:
lib/yahns/proxy_pass.rb

Overview

:nodoc:

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(dest, opts = {}) ⇒ ProxyPass

Returns a new instance of ProxyPass.



21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# File 'lib/yahns/proxy_pass.rb', line 21

def initialize(dest, opts = {})
  case dest
  when %r{\Aunix:([^:]+)(?::(/.*))?\z}
    path = $2
    @sockaddr = Socket.sockaddr_un($1)
  when %r{\Ahttp://([^/]+)(/.*)?\z}
    path = $2
    host, port = $1.split(':')
    @sockaddr = Socket.sockaddr_in(port || 80, host)
  else
    raise ArgumentError, "destination must be an HTTP URL or unix: path"
  end
  @response_headers = opts[:response_headers] || {}
  @proxy_buffering = opts[:proxy_buffering]
  @proxy_buffering = true if @proxy_buffering.nil? # allow false

  # It's wrong to send the backend Server tag through.  Let users say
  # { "Server => "yahns" } if they want to advertise for us, but don't
  # advertise by default (for security)
  @response_headers['Server'] ||= :ignore
  init_path_vars(path)
end

Instance Attribute Details

#proxy_bufferingObject (readonly)

Returns the value of attribute proxy_buffering.



19
20
21
# File 'lib/yahns/proxy_pass.rb', line 19

def proxy_buffering
  @proxy_buffering
end

#response_headersObject (readonly)

Returns the value of attribute response_headers.



19
20
21
# File 'lib/yahns/proxy_pass.rb', line 19

def response_headers
  @response_headers
end

Instance Method Details

#call(env) ⇒ Object



57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
# File 'lib/yahns/proxy_pass.rb', line 57

def call(env)
  # 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

  req = Rack::Request.new(env)
  req = @path.gsub(/\$(\w+)/) { req.__send__($1) }

  # start the connection asynchronously and early so TCP can do a
  case ver = env['HTTP_VERSION']
  when 'HTTP/1.1' # leave alone, response may be chunked
  else # no chunking for HTTP/1.0 and HTTP/0.9
    ver = 'HTTP/1.0'.freeze
  end

  addr = env['REMOTE_ADDR']
  xff = env['HTTP_X_FORWARDED_FOR']
  xff = xff =~ /\S/ ? "#{xff}, #{addr}" : addr
  req = "#{env['REQUEST_METHOD']} #{req} #{ver}\r\n" \
        "X-Forwarded-Proto: #{env['rack.url_scheme']}\r\n" \
        "X-Forwarded-For: #{xff}\r\n".dup

  # pass most HTTP_* headers through as-is
  chunked = false
  env.each do |key, val|
    %r{\AHTTP_(\w+)\z} =~ key or next
    key = $1
    # trailers are folded into the header, so do not send the Trailer:
    # header in the request
    next if /\A(?:VERSION|CONNECTION|KEEP_ALIVE|X_FORWARDED_FOR|TRAILER)/ =~
       key
    'TRANSFER_ENCODING'.freeze == key && val =~ /\bchunked\b/i and
      chunked = true
    key.tr!('_'.freeze, '-'.freeze)
    req << "#{key}: #{val}\r\n"
  end

  # special cases which Rack does not prefix:
  ctype = env["CONTENT_TYPE"] and req << "Content-Type: #{ctype}\r\n"
  clen = env["CONTENT_LENGTH"] and req << "Content-Length: #{clen}\r\n"
  input = chunked || (clen && clen.to_i > 0) ? env['rack.input'] : nil

  # finally, prepare to emit the headers
  rr.req_start(c, req << "\r\n".freeze, input, chunked, self)

  # this probably breaks fewer middlewares than returning whatever else...
  [ 500, [], [] ]
rescue => e
  Yahns::Log.exception(env['rack.logger'], 'proxy_pass', e)
  [ 502, { 'Content-Length' => '0', 'Content-Type' => 'text/plain' }, [] ]
end

#init_path_vars(path) ⇒ Object



44
45
46
47
48
49
50
51
52
53
54
55
# File 'lib/yahns/proxy_pass.rb', line 44

def init_path_vars(path)
  path ||= '$fullpath'
  # methods from Rack::Request we want:
  allow = %w(fullpath host_with_port host port url path)
  want = path.scan(/\$(\w+)/).flatten! || []
  diff = want - allow
  diff.empty? or
           raise ArgumentError, "vars not allowed: #{diff.uniq.join(' ')}"

  # kill leading slash just in case...
  @path = path.gsub(%r{\A/(\$(?:fullpath|path))}, '\1')
end