diff --git a/lib/middleman/features.rb b/lib/middleman/features.rb index 23a3d42f..c62cd5b6 100644 --- a/lib/middleman/features.rb +++ b/lib/middleman/features.rb @@ -71,6 +71,9 @@ module Middleman::Features # to dynamic requests. autoload :Data, "middleman/features/data" + # Proxy web services requests in dev mode only + # autoload :Proxy, "middleman/features/proxy" + # Automatically resize images for mobile devises # autoload :TinySrc, "middleman/features/tiny_src" diff --git a/lib/middleman/features/proxy.rb b/lib/middleman/features/proxy.rb new file mode 100644 index 00000000..ff1ca1a2 --- /dev/null +++ b/lib/middleman/features/proxy.rb @@ -0,0 +1,194 @@ +# =========================================================================== +# Original Project: Abbot - SproutCore Build Tools +# Copyright: ©2009 Apple Inc. +# portions copyright @2006-2011 Strobe Inc. +# and contributors +# =========================================================================== + +begin + require 'net/https' + Middleman::HTTPS_ENABLED = true +rescue LoadError => e + require 'net/http' + Middleman::HTTPS_ENABLED = false +end + +module Middleman::Features::Proxy + class << self + def registered(app) + app.extend ClassMethods + app.use Middleman::Features::Proxy::Rack + end + alias :included :registered + end + + class Collection + def initialize(app) + @app = app + end + + def self.proxies + @@proxies ||= {} + end + + def self.add(path, options={}) + @@proxies ||= {} + @@proxies[path] = options[:to] + end + end + + module ClassMethods + # Proxies requests to the path + # + # proxy '/twitter', "http://twitter/web/service" + def proxy(path, options={}) + Middleman::Features::Proxy::Collection.add(path, options) + end + end + + # Rack application proxies requests as needed for the given project. + module Rack + + def initialize(app) + @app = app + end + + def call(env) + url = env['PATH_INFO'] + + @proxies = Middleman::Features::Proxy::Collection.proxies + @proxies.each do |proxy, value| + if url.match(/^#{Regexp.escape(proxy.to_s)}/) + return handle_proxy(value, proxy.to_s, env) + end + end + + return [404, {}, "not found"] + end + + def handle_proxy(proxy, proxy_url, env) + if proxy[:secure] && !Middleman::HTTPS_ENABLED + $stderr.puts "~ WARNING: HTTPS is not supported on your system, using HTTP instead.\n" + $stderr.puts" If you are using Ubuntu, you can run `apt-get install libopenssl-ruby`\n" + proxy[:secure] = false + end + + origin_host = env['SERVER_NAME'] # capture the origin host for cookies + http_method = env['REQUEST_METHOD'].to_s.downcase + url = env['PATH_INFO'] + params = env['QUERY_STRING'] + + # collect headers... + headers = {} + env.each do |key, value| + next unless key =~ /^HTTP_/ + key = key.gsub(/^HTTP_/,'').downcase.sub(/^\w/){|l| l.upcase}.gsub(/_(\w)/){|l| "-#{$1.upcase}"} # remove HTTP_, dasherize and titleize + if !key.eql? "Version" + headers[key] = value + end + end + + # Rack documentation says CONTENT_TYPE and CONTENT_LENGTH aren't prefixed by HTTP_ + headers['Content-Type'] = env['CONTENT_TYPE'] if env['CONTENT_TYPE'] + + length = env['CONTENT_LENGTH'] + headers['Content-Length'] = length if length + + http_host, http_port = proxy[:to].split(':') + http_port = proxy[:secure] ? '443' : '80' if http_port.nil? + + # added 4/23/09 per Charles Jolley, corrects problem + # when making requests to virtual hosts + headers['Host'] = "#{http_host}:#{http_port}" + + if proxy[:url] + url = url.sub(/^#{Regexp.escape proxy_url}/, proxy[:url]) + end + + http_path = [url] + http_path << params if params && params.size>0 + http_path = http_path.join('?') + + response = nil + no_body_method = %w(get copy head move options trace) + + done = false + tries = 0 + until done + http = ::Net::HTTP.new(http_host, http_port) + + if proxy[:secure] + http.use_ssl = true + http.verify_mode = OpenSSL::SSL::VERIFY_NONE + end + + http.start do |web| + if no_body_method.include?(http_method) + response = web.send(http_method, http_path, headers) + else + http_body = env['rack.input'] + http_body.rewind # May not be necessary but can't hurt + + req = Net::HTTPGenericRequest.new(http_method.upcase, + true, true, http_path, headers) + req.body_stream = http_body if length.to_i > 0 + response = web.request(req) + end + end + + status = response.code # http status code + protocol = proxy[:secure] ? 'https' : 'http' + + $stderr.puts "~ PROXY: #{http_method.upcase} #{status} #{url} -> #{protocol}://#{http_host}:#{http_port}#{http_path}\n" + + # display and construct specific response headers + response_headers = {} + ignore_headers = ['transfer-encoding', 'keep-alive', 'connection'] + response.each do |key, value| + next if ignore_headers.include?(key.downcase) + # If this is a cookie, strip out the domain. This technically may + # break certain scenarios where services try to set cross-domain + # cookies, but those services should not be doing that anyway... + value.gsub!(/domain=[^\;]+\;? ?/,'') if key.downcase == 'set-cookie' + # Location headers should rewrite the hostname if it is included. + value.gsub!(/^http:\/\/#{http_host}(:[0-9]+)?\//, "http://#{http_host}/") if key.downcase == 'location' + # content-length is returning char count not bytesize + if key.downcase == 'content-length' + if response.body.respond_to?(:bytesize) + value = response.body.bytesize.to_s + elsif response.body.respond_to?(:size) + value = response.body.size.to_s + else + value = '0' + end + end + + $stderr.puts << " #{key}: #{value}\n" + response_headers[key] = value + end + + if [301, 302, 303, 307].include?(status.to_i) && proxy[:redirect] != false + $stderr.puts '~ REDIRECTING: '+response_headers['location']+"\n" + + uri = URI.parse(response_headers['location']); + http_host = uri.host + http_port = uri.port + http_path = uri.path + http_path += '?'+uri.query if uri.query + + tries += 1 + if tries > 10 + raise "Too many redirects!" + end + else + done = true + end + end + + # Thin doesn't like null bodies + response_body = response.body || '' + + return [status, ::Rack::Utils::HeaderHash.new(response_headers), [response_body]] + end + end +end \ No newline at end of file diff --git a/lib/middleman/server.rb b/lib/middleman/server.rb index b2e4b57a..7df9e309 100644 --- a/lib/middleman/server.rb +++ b/lib/middleman/server.rb @@ -43,6 +43,9 @@ module Middleman # Activate Yaml Data package register Middleman::Features::Data + # Activate Webservices Proxy package + # register Middleman::Features::Proxy + # Activate Lorem helpers register Middleman::Features::Lorem