PageRenderTime 127ms CodeModel.GetById 68ms app.highlight 55ms RepoModel.GetById 1ms app.codeStats 0ms

/tools/Ruby/lib/ruby/1.8/webrick/httpproxy.rb

http://github.com/agross/netopenspace
Ruby | 254 lines | 199 code | 28 blank | 27 comment | 27 complexity | e49ef18a3ba4c11878620ee77ae7963c MD5 | raw file
  1#
  2# httpproxy.rb -- HTTPProxy Class
  3#
  4# Author: IPR -- Internet Programming with Ruby -- writers
  5# Copyright (c) 2002 GOTO Kentaro
  6# Copyright (c) 2002 Internet Programming with Ruby writers. All rights
  7# reserved.
  8#
  9# $IPR: httpproxy.rb,v 1.18 2003/03/08 18:58:10 gotoyuzo Exp $
 10# $kNotwork: straw.rb,v 1.3 2002/02/12 15:13:07 gotoken Exp $
 11
 12require "webrick/httpserver"
 13require "net/http"
 14
 15Net::HTTP::version_1_2 if RUBY_VERSION < "1.7"
 16
 17module WEBrick
 18  NullReader = Object.new
 19  class << NullReader
 20    def read(*args)
 21      nil
 22    end
 23    alias gets read
 24  end
 25
 26  class HTTPProxyServer < HTTPServer
 27    def initialize(config)
 28      super
 29      c = @config
 30      @via = "#{c[:HTTPVersion]} #{c[:ServerName]}:#{c[:Port]}"
 31    end
 32
 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
 42
 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
 49
 50    # Some header fields should not be transferred.
 51    HopByHop = %w( connection keep-alive proxy-authenticate upgrade
 52                   proxy-authorization te trailers transfer-encoding )
 53    ShouldNotTransfer = %w( set-cookie proxy-connection )
 54    def split_field(f) f ? f.split(/,\s+/).collect{|i| i.downcase } : [] end
 55
 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
 69
 70    # Net::HTTP is stupid about the multiple header fields.
 71    # Here is workaround:
 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
 87
 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
 97
 98    def proxy_uri(req, res)
 99      @config[:ProxyURI]
100    end
101
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
168
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
249
250    def do_OPTIONS(req, res)
251      res['allow'] = "GET,HEAD,POST,OPTIONS,CONNECT"
252    end
253  end
254end