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



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

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)


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

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



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

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



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

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



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

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

#do_POST(req, res) ⇒ Object



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

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



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

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



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

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



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

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

#service(req, res) ⇒ Object

:stopdoc:



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

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