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.

To create a simple proxy server:

require 'webrick'
require 'webrick/httpproxy'

proxy = WEBrick::HTTPProxyServer.new Port: 8000

trap 'INT'  do proxy.shutdown end
trap 'TERM' do proxy.shutdown end

proxy.start

See ::new for proxy-specific configuration items.

Modifying proxied responses

To modify content the proxy server returns use the :ProxyContentHandler option:

handler = proc do |req, res|
  if res['content-type'] == 'text/plain' then
    res.body << "\nThis content was proxied!\n"
  end
end

proxy =
  WEBrick::HTTPProxyServer.new Port: 8000, ProxyContentHandler: handler

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 response and allows modification of the response

:ProxyTimeout

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


85
86
87
88
89
# File 'lib/webrick/httpproxy.rb', line 85

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)

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
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
# File 'lib/webrick/httpproxy.rb', line 134

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 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.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


214
215
216
217
218
# File 'lib/webrick/httpproxy.rb', line 214

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


220
221
222
223
224
# File 'lib/webrick/httpproxy.rb', line 220

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


232
233
234
# File 'lib/webrick/httpproxy.rb', line 232

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

#do_POST(req, res) ⇒ Object


226
227
228
229
230
# File 'lib/webrick/httpproxy.rb', line 226

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


102
103
104
105
106
107
# File 'lib/webrick/httpproxy.rb', line 102

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


114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
# File 'lib/webrick/httpproxy.rb', line 114

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


109
110
111
112
# File 'lib/webrick/httpproxy.rb', line 109

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

#service(req, res) ⇒ Object

:stopdoc:


92
93
94
95
96
97
98
99
100
# File 'lib/webrick/httpproxy.rb', line 92

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