| 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