Class: Ferrum::Proxy::HTTPProxyServer

Inherits:
WEBrick::HTTPProxyServer
  • Object
show all
Defined in:
lib/ferrum/proxy.rb

Overview

Fix hanging proxy at exit

Instance Method Summary collapse

Instance Method Details

#do_CONNECT(req, res) ⇒ Object

rubocop:disable all

Raises:

  • (WEBrick::HTTPStatus::InternalServerError)


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
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
# File 'lib/ferrum/proxy.rb', line 62

def do_CONNECT(req, res)
  # Proxy Authentication
  proxy_auth(req, res)

  ua = Thread.current[:WEBrickSocket]  # User-Agent
  raise WEBrick::HTTPStatus::InternalServerError,
        "[BUG] cannot get socket" unless ua

  host, port = req.unparsed_uri.split(":", 2)
  # Proxy authentication for upstream proxy server
  if proxy = proxy_uri(req, res)
    proxy_request_line = "CONNECT #{host}:#{port} HTTP/1.0"
    if proxy.userinfo
      credentials = "Basic " + [proxy.userinfo].pack("m0")
    end
    host, port = proxy.host, proxy.port
  end

  begin
    @logger.debug("CONNECT: upstream proxy is `#{host}:#{port}'.")
    os = TCPSocket.new(host, port)     # origin server

    if proxy
      @logger.debug("CONNECT: sending a Request-Line")
      os << proxy_request_line << CRLF
      @logger.debug("CONNECT: > #{proxy_request_line}")
      if credentials
        @logger.debug("CONNECT: sending credentials")
        os << "Proxy-Authorization: " << credentials << CRLF
      end
      os << CRLF
      proxy_status_line = os.gets(LF)
      @logger.debug("CONNECT: read Status-Line from the upstream server")
      @logger.debug("CONNECT: < #{proxy_status_line}")
      if %r{^HTTP/\d+\.\d+\s+200\s*} =~ proxy_status_line
        while line = os.gets(LF)
          break if /\A(#{CRLF}|#{LF})\z/om =~ line
        end
      else
        raise WEBrick::HTTPStatus::BadGateway
      end
    end
    @logger.debug("CONNECT #{host}:#{port}: succeeded")
    res.status = WEBrick::HTTPStatus::RC_OK
  rescue => ex
    @logger.debug("CONNECT #{host}:#{port}: failed `#{ex.message}'")
    res.set_error(ex)
    raise WEBrick::HTTPStatus::EOFError
  ensure
    # At exit os variable sometimes can be nil which results in hanging forever
    raise WEBrick::HTTPStatus::EOFError unless os

    if handler = @config[:ProxyContentHandler]
      handler.call(req, res)
    end
    res.send_response(ua)
    access_log(@config, req, res)

    # Should clear request-line not to send the response twice.
    # see: HTTPServer#run
    req.parse(NullReader) rescue nil
  end

  begin
    while fds = IO::select([ua, os])
      if fds[0].member?(ua)
        buf = ua.readpartial(1024);
        @logger.debug("CONNECT: #{buf.bytesize} byte from User-Agent")
        os.write(buf)
      elsif fds[0].member?(os)
        buf = os.readpartial(1024);
        @logger.debug("CONNECT: #{buf.bytesize} byte from #{host}:#{port}")
        ua.write(buf)
      end
    end
  rescue
    os.close
    @logger.debug("CONNECT #{host}:#{port}: closed")
  end

  raise WEBrick::HTTPStatus::EOFError
end