Class: WEBrick::HTTPProxyServer

Inherits:
HTTPServer show all
Defined in:
lib/webrick/httpproxy.rb

Overview

An HTTP Proxy server which proxies GET, HEAD and POST requests.

Instance Attribute Summary

Attributes inherited from GenericServer

#config, #listeners, #logger, #status, #tokens

Instance Method Summary collapse

Methods inherited from HTTPServer

#access_log, #lookup_server, #mount, #mount_proc, #run, #search_servlet, #unmount, #virtual_host

Methods inherited from GenericServer

#[], #listen, #run, #setup_ssl_context, #shutdown, #ssl_context, #start, #stop

Constructor Details

#initialize(config = {}, default = Config::HTTP) ⇒ HTTPProxyServer

Proxy server configurations. The proxy server handles the following configuration items in addition to those supported by HTTPServer:

:ProxyAuthProc

Called with a request and response to authorize a request

:ProxyVia

Appended to the via header

:ProxyURI

The proxy server’s URI

:ProxyContentHandler

Called with a request and resopnse and allows modification of the response

:ProxyTimeout

Sets the proxy timeouts to 30 seconds for open and 60 seconds for read operations



54
55
56
57
58
# File 'lib/webrick/httpproxy.rb', line 54

def initialize(config={}, default=Config::HTTP)
  super(config, default)
  c = @config
  @via = "#{c[:HTTPVersion]} #{c[:ServerName]}:#{c[:Port]}"
end

Instance Method Details

#do_CONNECT(req, res) ⇒ Object

Raises:

  • (HTTPStatus::InternalServerError)


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
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
# File 'lib/webrick/httpproxy.rb', line 102

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

  ua = Thread.current[:WEBrickSocket]  # User-Agent
  raise 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("m").delete("\n")
    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 a credentials")
        os << "Proxy-Authorization: " << credentials << CRLF
      end
      os << CRLF
      proxy_status_line = os.gets(LF)
      @logger.debug("CONNECT: read a Status-Line form 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 HTTPStatus::BadGateway
      end
    end
    @logger.debug("CONNECT #{host}:#{port}: succeeded")
    res.status = HTTPStatus::RC_OK
  rescue => ex
    @logger.debug("CONNECT #{host}:#{port}: failed `#{ex.message}'")
    res.set_error(ex)
    raise HTTPStatus::EOFError
  ensure
    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 sesponse twice.
    # see: HTTPServer#run
    req.parse(NullReader) rescue nil
  end

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

  raise HTTPStatus::EOFError
end

#do_GET(req, res) ⇒ Object



182
183
184
185
186
# File 'lib/webrick/httpproxy.rb', line 182

def do_GET(req, res)
  perform_proxy_request(req, res) do |http, path, header|
    http.get(path, header)
  end
end

#do_HEAD(req, res) ⇒ Object



188
189
190
191
192
# File 'lib/webrick/httpproxy.rb', line 188

def do_HEAD(req, res)
  perform_proxy_request(req, res) do |http, path, header|
    http.head(path, header)
  end
end

#do_OPTIONS(req, res) ⇒ Object



200
201
202
# File 'lib/webrick/httpproxy.rb', line 200

def do_OPTIONS(req, res)
  res['allow'] = "GET,HEAD,POST,OPTIONS,CONNECT"
end

#do_POST(req, res) ⇒ Object



194
195
196
197
198
# File 'lib/webrick/httpproxy.rb', line 194

def do_POST(req, res)
  perform_proxy_request(req, res) do |http, path, header|
    http.post(path, req.body || "", header)
  end
end

#proxy_auth(req, res) ⇒ Object



70
71
72
73
74
75
# File 'lib/webrick/httpproxy.rb', line 70

def proxy_auth(req, res)
  if proc = @config[:ProxyAuthProc]
    proc.call(req, res)
  end
  req.header.delete("proxy-authorization")
end

#proxy_service(req, res) ⇒ Object



82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
# File 'lib/webrick/httpproxy.rb', line 82

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

  begin
    self.send("do_#{req.request_method}", req, res)
  rescue NoMethodError
    raise HTTPStatus::MethodNotAllowed,
      "unsupported method `#{req.request_method}'."
  rescue => err
    logger.debug("#{err.class}: #{err.message}")
    raise HTTPStatus::ServiceUnavailable, err.message
  end

  # Process contents
  if handler = @config[:ProxyContentHandler]
    handler.call(req, res)
  end
end

#proxy_uri(req, res) ⇒ Object



77
78
79
80
# File 'lib/webrick/httpproxy.rb', line 77

def proxy_uri(req, res)
  # should return upstream proxy server's URI
  return @config[:ProxyURI]
end

#service(req, res) ⇒ Object



60
61
62
63
64
65
66
67
68
# File 'lib/webrick/httpproxy.rb', line 60

def service(req, res)
  if req.request_method == "CONNECT"
    do_CONNECT(req, res)
  elsif req.unparsed_uri =~ %r!^http://!
    proxy_service(req, res)
  else
    super(req, res)
  end
end