2009/09/02

コンテンツフィルターをwebrickで頑張ってみる

ポスト @ 22:53:05 | , ,     このエントリーを含むはてなブックマーク

RAA - reverse_proxyがあったので、それを使ってやってみる。

#!/usr/bin/env ruby

require 'webrick'
require 'net/http'

module WEBrick
  class WEBrick::HTTPReverseProxyServer < WEBrick::HTTPServer
    def service(req, res)
      rule = first_matching_proxy_rule(req)
      if rule.nil?
        super(req, res)
      else
        service_proxy(req, res, rule)
      end
    end
  
    # find the *first* matching pattern in the proxy map
    def first_matching_proxy_rule(req)
      matching_rule = @config[:ProxyRules].detect { |rule|
        re = Regexp.new(rule.pattern)
        m = re.match(req.request_uri.to_s)
        not m.nil?
      }
      return matching_rule
    end
    
    def map_to_proxyURI(req, rule)
      path = (req.path).sub(%r!#{rule.pattern}!, rule.replacement)
      path += '?' + req.query_string if req.query_string
      return [rule.host, rule.port, path]
    end
  
    def service_proxy(req, res, rule)
      host, port, path  = map_to_proxyURI(req, rule)

      # convert WEBrick header (values wrapped in an array) into Net::HTTP header (simple values)
      header = {}
      req.header.keys { |key| header[key] = req.header[key][0] }
      header['x-forwarded-for'] = req.peeraddr[2] # the name of the requesting host
      header['host'] = host
      # send the new request to the private server (hacked from WEBrick::HTTPProxyServer)
      response = nil
      begin
        http = Net::HTTP.new(host, port)
        if port.to_i == 443
            http.use_ssl = true
            http.verify_mode = OpenSSL::SSL::VERIFY_PEER
        end

        http.start {
          case req.request_method
          when "GET"  then response = http.get(path, header)
          when "POST" then response = http.post(path, req.body || "", header)
          when "HEAD" then response = http.head(path, header)
          else
            raise HTTPStatus::MethodNotAllowed, "unsupported method `#{req.request_method}'."
          end
        }
      rescue => err
        logger.debug("#{err.class}: #{err.message}")
        raise HTTPStatus::ServiceUnavailable, err.message
      end
      res['connection'] = "close"

      # Convert Net::HTTP::HTTPResponse to WEBrick::HTTPResponse
      res.status = response.code.to_i
      response.each { |key, val| res[key] = val }
      res.body = response.body

      @config[:FilterRules].each{|rule|
        regex = Regexp.new(rule.pattern)
        unless res.header['location'].nil?
          res.header['location'].gsub!(regex, rule.replacement)
        end
        res.body.gsub!(regex, rule.replacement);
      }
      @config[:CookieFilterRules].each{|rule|
        regex = Regexp.new(rule.pattern)
        unless res['set-cookie'].nil?
          res['set-cookie'].gsub!(regex, rule.replacement)
        end
      }
      res.header['content-length'] = res.body.size
      puts res.header['content-length']

      # Process contents
      if handler = @config[:ProxyContentHandler]
        handler.call(req, res)
      end
    end
  end
  ProxyRule = Struct.new("ProxyRule", :pattern, :host, :port, :replacement)
  FilterRule = Struct.new("FilterRule", :pattern, :replacement)
end

server = WEBrick::HTTPReverseProxyServer.new(
    :ServerName => 'internal.domain',
    :BindAddress => '0.0.0.0',
    :Port => 10080,
    :Logger => WEBrick::Log.new($stderr, WEBrick::Log::DEBUG),
    :ProxyVia => false,
    :ProxyRules => [
       WEBrick::ProxyRule.new('http://internal.domain', 'external.domain', '80', '/'),
       WEBrick::ProxyRule.new('https://secure.internal.domain', 'secure.external.domain', '443', '/'),
    ],
    :FilterRules => [
        WEBrick::FilterRule.new('https://secure.external.domain', 'https://secure.internal.domain:10083'),
        WEBrick::FilterRule.new('http://img.external.domain', 'http://img.internal.domain:10080'),
        WEBrick::FilterRule.new('http://external.domain', 'http://internal.domain:10080'),
    ],
    :CookieFilterRules => [
        WEBrick::FilterRule.new('.external.domain', '.internal.domain'),
    ]
)

[:INT, :TERM].each {|signal|
    Signal.trap(signal) {
        server.shutdown
    }
}

server.start

Apache(httpd) の mod_proxy_html + mod_ext_filter + mod_proxy (ProxyPassReverse, ProxyPassReverseCookieDomain) とか使って、コンテンツの書き換えとかcookieまわりを頑張って書き換えてみたけど、webrick でも結構頑張るなぁ...(しかも不完全だし)
うーん。難しい。


Trackback

No Trackbacks

Track from Your Website

http://blog.xole.net/trackback/tb.php?id=730

Comment

No Comments

Post Your Comment


*は入力必須です。E-Mailは公開されません。

1 + 2 =