John
John

Reputation: 9456

How do I use Rack::Proxy within Rails to proxy requests to a specific path to another app

I found this great blog post on how to use Rack::Proxy as a separate proxy app. The article explains how he uses Rack::Proxy to proxy requests to http://localhost:3000 to an app on port 3001 and requests to http://localhost:3000/api to an app on port 3002. I want to do the same thing, but I do not want to create a separate proxy app. Instead, I want my main Rails app to proxy requests to /blog to a different app.

Blog Post: http://livsey.org/blog/2012/02/23/using-rack-proxy-to-serve-multiple-rails-apps-from-the-same-domain-and-port/

Upvotes: 25

Views: 29708

Answers (5)

John
John

Reputation: 9456

Figured it out.

lib/proxy.rb

require 'rack-proxy'
class Proxy < Rack::Proxy
    def initialize(app)
        @app = app
    end

    def rewrite_env(env)
        # do magic in here
    end
end

config/application.rb

config.middleware.use "Proxy"

Upvotes: 3

oklas
oklas

Reputation: 8220

I found gem rails-reverse-proxy more simpler and obvious to use (for react app):

Add a simple proxy controller:

class ProxyController < ApplicationController
  include ReverseProxy::Controller

  def index
    reverse_proxy "http://localhost:3000" do |config|
      # We got a 404!
      config.on_missing do |code, response|
        redirect_to root_url and return
      end
    end
  end
end

And add a route:

match 'static/*path' => 'proxy#index', via: [:get, :post, :put, :patch, :delete]

Upvotes: 1

mrlindsey
mrlindsey

Reputation: 310

Below is even simpler code to proxy an api when you want http://localhost:3000/api/users/1 (for example) to go to the api namespace defined in routes.rb without using a proxy server program.

On production it would be something like http://api.sample.com/users/1.

lib/proxy.rb

require 'rack-proxy'

class Proxy < Rack::Proxy
   def perform_request(env)
     request = Rack::Request.new(env)
     if request.path =~ %r{^/api}
       #do nothing
     else
       @app.call(env)
     end
   end
end

config/application.rb

config.middleware.use "Proxy"

config/routes.rb

namespace :api, defaults: { format: :json },
        constraints: { subdomain: 'api' }, path: '/'  do
  scope module: :v1, constraints: ApiConstraints.new(version: 1, default: true) do
  resources :users, :only => [:show, :create, :update, :destroy]
end

lib/api_constraints.rb

class ApiConstraints
  def initialize(options)
    @version = options[:version]
    @default = options[:default]
  end

  def matches?(req)
    @default || req.headers['Accept'].include?("application/vnd.sample.v#{@version}")
  end
end

Upvotes: 1

wbyoung
wbyoung

Reputation: 22493

This is a slight change to steve's solution that uses a little less internal understanding of Rack::Proxy:

require 'rack/proxy'

class MyProxy < Rack::Proxy
  def initialize(app)
    @app = app
  end

  def call(env)
    # call super if we want to proxy, otherwise just handle regularly via call
    (proxy?(env) && super) || @app.call(env)
  end

  def proxy?(env)
    # do not alter env here, but return true if you want to proxy for this request.
    return true
  end

  def rewrite_env(env)
    # change the env here
    env["HTTP_HOST"] = "some.other.host"
    env
  end
end

Upvotes: 17

steve
steve

Reputation: 3356

FWIW, I also just tackled this problem. Some may find the full code helpful, as I needed more than you posted:

# lib/proxy_to_other.rb
class ProxyToOther < Rack::Proxy
  def initialize(app)
    @app = app
  end

  def call(env)
    original_host = env["HTTP_HOST"]
    rewrite_env(env)
    if env["HTTP_HOST"] != original_host
      perform_request(env)
    else
      # just regular
      @app.call(env)
    end
  end

  def rewrite_env(env)
    request = Rack::Request.new(env)
    if request.path =~ %r{^/prefix|^/other_prefix}
      # do nothing
    else
      env["HTTP_HOST"] = "localhost:3000"
    end
    env
  end
end

Also:

# config/application.rb
# ...snip ...
module MyApplication
  class Application < Rails::Application
    # Custom Rack middlewares
    config.middleware.use "ProxyToOther" if ["development", "test"].include? Rails.env
#...snip....

This assumes your app you want to proxy some requests to is running on port 3001. I daresay the app you're hitting can be run on any port. This also assumes you only want to do the proxying in development and test environments, because you'll have a 'real' solution in production & staging (eg, nginx or a loadbalancer doing the proper thing).

Upvotes: 32

Related Questions