Rack apps mounted in different subdomains - ruby

I'm building a Grape API alongside Sinatra. So far I've been mounting them in separate routes like this:
run Rack::URLMap.new("/" => Frontend::Server.new,
"/api" => API::Server.new)
Where the "/api" is served by a Grape app and "/" by a Sinatra app. But I wanted to use subdomains to separate those concerns instead of the actual "sub-URL". Any clues on how to do this?
Thanks in advance for the help.

There is a rack-subdomain gem, but it only handles redirection to paths, not rack apps. You could fork it and make it redirect to rack apps instead.
You could also just roll your own :
class SubdomainDispatcher
def initialize
#frontend = Frontend::Server.new
#api = API::Server.new
end
def call(env)
if subdomain == 'api'
return #api.call(env)
else
return #frontend.call(env)
end
end
private
# If may be more robust to use a 3rd party plugin to extract the subdomain
# e.g ActionDispatch::Http::URL.extract_subdomain(#env['HTTP_HOST'])
def subdomain
#env['HTTP_HOST'].split('.').first
end
end
run SubdomainDispatcher.new

Related

Error handlers don't run in modular Sinatra app

I have a Sinatra application that uses the modular style. Everything works fine apart from my error handler blocks which don't get invoked. Here's the relevant code:
app.rb
require_relative './routes/base'
require_relative './routes/routing'
module Example
class App < Sinatra::Application
use Routes::Base
use Routes::Routing
end
end
base.rb
require 'sinatra/base'
module Example
module Routes
class Base < Sinatra::Application
configure do
# etc.
end
# Error pages.
error 404 do # <- Doesn't get invoked.
erb :not_found
end
error 500 do # <- Doesn't get invoked.
erb :internal_server_error
end
end
end
end
routing.rb
module Example
module Routes
class Routing < Base
get '/?' do
erb :home
end
end
end
end
Why don't my error handlers work?
Thanks in advance.
The use method is for adding middleware to an app, you can’t use it to compose an app like this.
In your example you actually have three different Sinatra applications, two of which are being run as middleware. When a Sinatra app is run as middleware then any request that matches one of its routes is handled by that app, otherwise the request is passed to the next component in the Rack stack. Error handlers will only apply if the request has been handled by the same app. The app that you have defined the error handlers in has no routes defined, so all requests will be passed on down the stack — the error handlers will never be used.
One way to organise a large app like this would be to simply use the same class and reopen it in the different files. This other question has an example that might be useful: Using Sinatra for larger projects via multiple files.

Setting Heroku to accept traffic only from CloudFlare

I have a website deployed on Heroku (e.g. www.example.com) and I have set up CloudFlare to work as my CDN, therefore all traffic to my website goes through CloudFlare.
But I still have the link to my app on Heroku sub-domain (example.heroku.com), and if somebody tries this address, it wouldn't pass through CloudFlare anymore.
How can I hide my Heroku app address (example.heroku.com) and make my website to accept traffic only from CloudFlare?
My answer is based on the assumption that you're using Heroku to host a Ruby Rack application, since I believe that is the profile of most of Heroku's users. Otherwise, please skip.
If you're hosting a Rack app on Heroku, you could potentially insert a small piece of Rack middleware to do redirects for you.
# lib/rack/domain_redirect.rb
# encoding utf-8
# Rack Middleware that was created to handle
# autoredirecting requests away from *.herokuapp.com to
# the equivalent *.example.com. That said, it does allow you to configure
# what domain to redirect from and what domain to redirect to as well
module Rack
class DomainRedirect
attr_accessor :redirect_from_domain, :redirect_to_domain
def initialize(app, redirect_from_domain = "herokuapp.com", redirect_to_domain = "example.com")
self.redirect_from_domain = redirect_from_domain
self.redirect_to_domain = redirect_to_domain
#app = app
end
def call(env)
request = Rack::Request.new(env)
if request.host.include?(redirect_from_domain)
[301, {"Location" => request.url.sub(redirect_from_domain, redirect_to_domain)}, []]
else
#app.call(env)
end
end
end
end
Then in your config.ru
# some other middlewares and requires
require File.expand_path("../lib/rack/domain_redirect.rb", __FILE__)
use Rack::DomainRedirect
# run your app
run MyApp

Use HTTP Digest Authentication for a Single Route

I'd like to use HTTP Digest authentication for a specific route in my modular Sinatra App.
The examples listed within the sinatra recipes website simply describe how to enable digest auth for an entire app. The solution presented for getting this to work for specific routes is to create two separate apps, (one with digest auth and another without) placing the protected and unprotected routes in their respective applications.
That would require something like the following:
require 'sinatra/base'
class Protected < Sinatra::Base
use Rack::Auth::Basic, "Protected Area" do |username, password|
username == 'foo' && password == 'bar'
end
get '/' do
"secret"
end
end
class Public < Sinatra::Base
get '/' do
"public"
end
end
This seems like overkill to me.
Is there a way to protect a single route in a modular sinatra app without having to create an entirely new app?
There is an example in the FAQ which references creating an instance of Rack::Auth::Basic::Request and passing in the environment but doing something like this with digest auth would differ greatly and be much more of a manual authentication procedure.
Here is the basic authentication example:
def authorized?
#auth ||= Rack::Auth::Basic::Request.new(request.env)
#auth.provided? &&
#auth.basic? &&
#auth.credentials &&
#auth.credentials == ['admin', 'admin']
end
Does anyone have thoughts on how this could be done?
Using a before Filters. Do you need different behaviour for the same route?

Multiple Sinatra apps using rack-mount

I have a question regarding using rack-mount with Sinatra. I've got two classic-style Sinatra apps. Let's call one App defined in app.rb and the other API defined in api.rb.
I would like it so that api.rb handles all routes beginning with '/api' and app.rb handles all other requests including the root ('/').
How would I set this up with rack-mount? Or is there a better solution than that?
I think you'll prefer Rack::URLMap - it will probably look something like this:
run Rack::URLMap.new("/" => App.new,
"/api" => Api.new)
That should go in your config.ru file.
I had a similar issue and I am not very familiar with Rack. I could not figure out what to do based on the answers above. My final solution was to have the following in config.ru.
This works perfectly for me.
# Main Ramaze site
map "/" do
Encoding.default_external = Encoding::UTF_8
Encoding.default_internal = Encoding::UTF_8
require ::File.expand_path('../app', __FILE__)
Ramaze.start(:root => __DIR__, :started => true)
run Ramaze
end
# Sinatra & Grape API
map "/api" do
use Rack::Static, :urls => ["/stylesheets", "/images", "/javascripts"], :root => "public"
use Rack::Session::Cookie
run Rack::Cascade.new([
MySinatraApp::Application,
MySinatraApp::API])
end
In config.ru you could also take advantage of Sinatra's middleware feature. If you have several Sinatra apps, each with its own routes, and want to run them simultaneously, you can arrange them in the order you want them found, e.g.
# config.ru
...
use MyAppA
use MyAppB
use MyAppC
run MyAppD
I had the same problem once and so I came up with this template: sinatra-rspec-bundler-template which is layed out for multiple apps.
It may have more features than you need but it should help you when you need something "a bit more" complex.

Sinatra: How do I provide access to a login form while preventing access to the rest of my Sinatra app?

I recently created a Sinatra app with a login form (no basic auth). To prevent access to the app unless the user logged in I put a before block in place
before do
unless request.path_info == '/login'
authenticated?
end
end
I quickly realized that this prevented me from accessing resources in the public directory like my style sheet and logo unless authenticated first as well. To get around that I changed my filter to the following:
before do
unless request.path_info == '/login' || request.path_info == "/stylesheets/master.css" || request.path_info == "/images/logo.png"
authenticated?
end
end
If there were lots of resources I needed to provide exceptions to this way of making them would quickly become overwhelming. What is a better way to code this so I can make exceptions for the public directory or even its specific sub-directories and files like /stylesheets, /images, /images/bg.png but not /secret or /secret/eyes-only.pdf?
Or ... Is there a completely different best-practice to handle this situation of locking down everything except the stuff related to logging in (handlers, views, resources)?
You could extract the login logic into it's own Rack middleware (which can be a Sinatra app).
The authentication middleware will serve the public files.
require 'sinatra'
class Authentication < Sinatra::Base
def logged_in?
# your login logic goes here
end
get '/login' do
# login formular and logic here
end
get(//) do
pass if logged_in?
redirect '/login'
end
end
configure { |c| c.use Authenitcation }
get('/') { ... }
Instead of putting the authorization information into your Sinatra application directly, why don't you extract it into Rack using Rack::Auth:
# my_app.ru
app = Rack::Builder.new do
use Rack::Static, :urls => /^(stylesheets|javascripts|images|fonts)\//
map '/login' do
run MyApplication
end
map '/' do
use Rack::Auth::Basic do |username, password|
# check the username and password sent via HTTP Basic auth
end
run MyApplication
end
end

Resources