Reputation: 9456
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.
Upvotes: 25
Views: 29708
Reputation: 9456
Figured it out.
require 'rack-proxy'
class Proxy < Rack::Proxy
def initialize(app)
@app = app
end
def rewrite_env(env)
# do magic in here
end
end
config.middleware.use "Proxy"
Upvotes: 3
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
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
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
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