Class WEBrick::HTTPProxyServer
In: lib/webrick/httpproxy.rb
Parent: HTTPServer

Methods

Constants

HopByHop = %w( connection keep-alive proxy-authenticate upgrade proxy-authorization te trailers transfer-encoding )   Some header fields should not be transferred.
ShouldNotTransfer = %w( set-cookie proxy-connection )

Public Class methods

[Source]

    # File lib/webrick/httpproxy.rb, line 27
27:     def initialize(config)
28:       super
29:       c = @config
30:       @via = "#{c[:HTTPVersion]} #{c[:ServerName]}:#{c[:Port]}"
31:     end

Public Instance methods

[Source]

    # File lib/webrick/httpproxy.rb, line 56
56:     def choose_header(src, dst)
57:       connections = split_field(src['connection'])
58:       src.each{|key, value|
59:         key = key.downcase
60:         if HopByHop.member?(key)          || # RFC2616: 13.5.1
61:            connections.member?(key)       || # RFC2616: 14.10
62:            ShouldNotTransfer.member?(key)    # pragmatics
63:           @logger.debug("choose_header: `#{key}: #{value}'")
64:           next
65:         end
66:         dst[key] = value
67:       }
68:     end

[Source]

     # File lib/webrick/httpproxy.rb, line 250
250:     def do_OPTIONS(req, res)
251:       res['allow'] = "GET,HEAD,POST,OPTIONS,CONNECT"
252:     end

[Source]

    # File lib/webrick/httpproxy.rb, line 43
43:     def proxy_auth(req, res)
44:       if proc = @config[:ProxyAuthProc]
45:         proc.call(req, res)
46:       end
47:       req.header.delete("proxy-authorization")
48:     end

[Source]

     # File lib/webrick/httpproxy.rb, line 169
169:     def proxy_connect(req, res)
170:       # Proxy Authentication
171:       proxy_auth(req, res)
172: 
173:       ua = Thread.current[:WEBrickSocket]  # User-Agent
174:       raise HTTPStatus::InternalServerError,
175:         "[BUG] cannot get socket" unless ua
176: 
177:       host, port = req.unparsed_uri.split(":", 2)
178:       # Proxy authentication for upstream proxy server
179:       if proxy = proxy_uri(req, res)
180:         proxy_request_line = "CONNECT #{host}:#{port} HTTP/1.0"
181:         if proxy.userinfo
182:           credentials = "Basic " + [proxy.userinfo].pack("m*")
183:           credentials.chomp!
184:         end
185:         host, port = proxy.host, proxy.port
186:       end
187: 
188:       begin
189:         @logger.debug("CONNECT: upstream proxy is `#{host}:#{port}'.")
190:         os = TCPSocket.new(host, port)     # origin server
191: 
192:         if proxy
193:           @logger.debug("CONNECT: sending a Request-Line")
194:           os << proxy_request_line << CRLF
195:           @logger.debug("CONNECT: > #{proxy_request_line}")
196:           if credentials
197:             @logger.debug("CONNECT: sending a credentials")
198:             os << "Proxy-Authorization: " << credentials << CRLF
199:           end
200:           os << CRLF
201:           proxy_status_line = os.gets(LF)
202:           @logger.debug("CONNECT: read a Status-Line form the upstream server")
203:           @logger.debug("CONNECT: < #{proxy_status_line}")
204:           if %r{^HTTP/\d+\.\d+\s+200\s*} =~ proxy_status_line
205:             while line = os.gets(LF)
206:               break if /\A(#{CRLF}|#{LF})\z/om =~ line
207:             end
208:           else
209:             raise HTTPStatus::BadGateway
210:           end
211:         end
212:         @logger.debug("CONNECT #{host}:#{port}: succeeded")
213:         res.status = HTTPStatus::RC_OK
214:       rescue => ex
215:         @logger.debug("CONNECT #{host}:#{port}: failed `#{ex.message}'")
216:         res.set_error(ex)
217:         raise HTTPStatus::EOFError
218:       ensure
219:         if handler = @config[:ProxyContentHandler]
220:           handler.call(req, res)
221:         end
222:         res.send_response(ua)
223:         access_log(@config, req, res)
224: 
225:         # Should clear request-line not to send the sesponse twice.
226:         # see: HTTPServer#run
227:         req.parse(NullReader) rescue nil
228:       end
229: 
230:       begin
231:         while fds = IO::select([ua, os])
232:           if fds[0].member?(ua)
233:             buf = ua.sysread(1024);
234:             @logger.debug("CONNECT: #{buf.size} byte from User-Agent")
235:             os.syswrite(buf)
236:           elsif fds[0].member?(os)
237:             buf = os.sysread(1024);
238:             @logger.debug("CONNECT: #{buf.size} byte from #{host}:#{port}")
239:             ua.syswrite(buf)
240:           end
241:         end
242:       rescue => ex
243:         os.close
244:         @logger.debug("CONNECT #{host}:#{port}: closed")
245:       end
246: 
247:       raise HTTPStatus::EOFError
248:     end

[Source]

     # File lib/webrick/httpproxy.rb, line 102
102:     def proxy_service(req, res)
103:       # Proxy Authentication
104:       proxy_auth(req, res)      
105: 
106:       # Create Request-URI to send to the origin server
107:       uri  = req.request_uri
108:       path = uri.path.dup
109:       path << "?" << uri.query if uri.query
110: 
111:       # Choose header fields to transfer
112:       header = Hash.new
113:       choose_header(req, header)
114:       set_via(header)
115: 
116:       # select upstream proxy server
117:       if proxy = proxy_uri(req, res)
118:         proxy_host = proxy.host
119:         proxy_port = proxy.port
120:         if proxy.userinfo
121:           credentials = "Basic " + [proxy.userinfo].pack("m*")
122:           credentials.chomp!
123:           header['proxy-authorization'] = credentials
124:         end
125:       end
126: 
127:       response = nil
128:       begin
129:         http = Net::HTTP.new(uri.host, uri.port, proxy_host, proxy_port)
130:         http.start{
131:           if @config[:ProxyTimeout]
132:             ##################################   these issues are 
133:             http.open_timeout = 30   # secs  #   necessary (maybe bacause
134:             http.read_timeout = 60   # secs  #   Ruby's bug, but why?)
135:             ##################################
136:           end
137:           case req.request_method
138:           when "GET"  then response = http.get(path, header)
139:           when "POST" then response = http.post(path, req.body || "", header)
140:           when "HEAD" then response = http.head(path, header)
141:           else
142:             raise HTTPStatus::MethodNotAllowed,
143:               "unsupported method `#{req.request_method}'."
144:           end
145:         }
146:       rescue => err
147:         logger.debug("#{err.class}: #{err.message}")
148:         raise HTTPStatus::ServiceUnavailable, err.message
149:       end
150:   
151:       # Persistent connction requirements are mysterious for me.
152:       # So I will close the connection in every response.
153:       res['proxy-connection'] = "close"
154:       res['connection'] = "close"
155: 
156:       # Convert Net::HTTP::HTTPResponse to WEBrick::HTTPProxy
157:       res.status = response.code.to_i
158:       choose_header(response, res)
159:       set_cookie(response, res)
160:       set_via(res)
161:       res.body = response.body
162: 
163:       # Process contents
164:       if handler = @config[:ProxyContentHandler]
165:         handler.call(req, res)
166:       end
167:     end

[Source]

     # File lib/webrick/httpproxy.rb, line 98
 98:     def proxy_uri(req, res)
 99:       @config[:ProxyURI]
100:     end

[Source]

    # File lib/webrick/httpproxy.rb, line 33
33:     def service(req, res)
34:       if req.request_method == "CONNECT"
35:         proxy_connect(req, res)
36:       elsif req.unparsed_uri =~ %r!^http://!
37:         proxy_service(req, res)
38:       else
39:         super(req, res)
40:       end
41:     end

Net::HTTP is stupid about the multiple header fields. Here is workaround:

[Source]

    # File lib/webrick/httpproxy.rb, line 72
72:     def set_cookie(src, dst)
73:       if str = src['set-cookie']
74:         cookies = []
75:         str.split(/,\s*/).each{|token|
76:           if /^[^=]+;/o =~ token
77:             cookies[-1] << ", " << token
78:           elsif /=/o =~ token
79:             cookies << token
80:           else
81:             cookies[-1] << ", " << token
82:           end
83:         }
84:         dst.cookies.replace(cookies)
85:       end
86:     end

[Source]

    # File lib/webrick/httpproxy.rb, line 88
88:     def set_via(h)
89:       if @config[:ProxyVia]
90:         if  h['via']
91:           h['via'] << ", " << @via
92:         else
93:           h['via'] = @via
94:         end
95:       end
96:     end

[Source]

    # File lib/webrick/httpproxy.rb, line 54
54:     def split_field(f) f ? f.split(/,\s+/).collect{|i| i.downcase } : [] end

[Validate]