Rack level reverse proxy. Route requests to services with pure Ruby if that's your boat. The boat won't be pretty fast, but you can deploy it on your Heroku's, and what-not-fancy-cloud-services, without a bit of complications.
The proxy can sit after the request authentication, or before it, depending on
the services you have to route requests to. You can rewrite requests URLs, and
the whole Net::HTTPRequest
to be sent out.
Put this in the application Gemfile
:
gem 'rack-delegate'
rack-delegate
requires Ruby 2.0 and above.
To use rack-delegate
, you first need to configure a delegator proxy. You can
do that with Rack::Delegate.configure
. Note that Rack::Delegate.configure
,
actually creates a middleware, which we can insert in an arbitrary stack later
on.
Macro::ApiGateway = Rack::Delegate.configure do
# Strips the leading /api out of the outgoing requests.
rewrite { path.gsub!(%r{\A/api}, '') }
# Don't proxy requests without them matching on the condition in the block.
constraints { |request| Version.new('v1').matches?(request) }
# With the rewrite on, requests you /api/users will go to
# http://users-service.intern/users.
from %r{\A/api/users}, to: 'http://users-service.intern'
# Requests go to http://payments-service.intern/payments.
from %r{\A/api/payments}, to: 'http://payments-service.intern'
end
module Macro
class Appplication
middleware.use ApiGateway
end
end
Wait, what happened? Rack::Delegate.configure
created a class, we can use as
an middleware. The configuration is based on a DSL, you can check it out
here.
In the example above, we said:
-
Rewrite the incoming requests URL and strip the leading
/api
out of them, before sending them off to the service that will handle them. The block ofrewrite
is aninstance_eval
of anURI
object. You can call all the methods onURI
. -
Don't proxy requests which don't match a constraint. A constraint is any object that responds to
matches?
(you can reuse your Rails constraints, for example),call
, or===
. The method is called with a plainRack::Request
as an input. If you pass it a block, that block becomes a constraint. -
Proxy requests matching a path of
/api/users
tohttp://users-service.intern
. Because we have setup a rewrite rule, we will hithttp://users-service.intern/users
and nothttp://users-service.intern/api/users
Yes, you can insert multiple Rack::Delegate
middleware instances in your
stack. Say, one for requests that don't require authentication and one for
requests that do.
(Given that the request authentication is a middleware itself.)
Macro::UnauthenticatedGateway = Rack::Delegate.configure do
from %r{\A/registration}, to: 'http://registration-service.intern'
end
Macro::AuthenticatedGateway = Rack::Delegate.configure do
from %r{\A/api/users}, to: 'http://users-service.intern'
from %r{\A/api/payments}, to: 'http://payments-service.intern'
end
module Macro
class Appplication
middleware.insert_before "Auth", UnauthenticatedGateway
middleware.insert_after "Auth", AuthenticatedGateway
end
end
If you wanna go the (micro) services route, you can do it quickly with Ruby and prototype. If you need the speed, you can check out OpenResty.