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



83
84
85
86
87
# File 'lib/webrick/httpproxy.rb', line 83

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)


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
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
# File 'lib/webrick/httpproxy.rb', line 132

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
    os.close
    @logger.debug("CONNECT #{host}:#{port}: closed")
  end

  raise HTTPStatus::EOFError
end

#do_GET(req, res) ⇒ Object



212
213
214
215
216
# File 'lib/webrick/httpproxy.rb', line 212

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



218
219
220
221
222
# File 'lib/webrick/httpproxy.rb', line 218

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



230
231
232
# File 'lib/webrick/httpproxy.rb', line 230

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

#do_POST(req, res) ⇒ Object



224
225
226
227
228
# File 'lib/webrick/httpproxy.rb', line 224

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



100
101
102
103
104
105
# File 'lib/webrick/httpproxy.rb', line 100

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



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

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



107
108
109
110
# File 'lib/webrick/httpproxy.rb', line 107

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

#service(req, res) ⇒ Object

:stopdoc:



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

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