Class | WEBrick::HTTPProxyServer |
In: |
lib/webrick/httpproxy.rb
|
Parent: | HTTPServer |
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 ) |
# 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
# 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
# File lib/webrick/httpproxy.rb, line 250 250: def do_OPTIONS(req, res) 251: res['allow'] = "GET,HEAD,POST,OPTIONS,CONNECT" 252: end
# 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
# 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
# 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
# File lib/webrick/httpproxy.rb, line 98 98: def proxy_uri(req, res) 99: @config[:ProxyURI] 100: end
# 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:
# 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
# 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