Mount multiple Rack apps without adding a prefix to the url - ruby

How do I mount/run multiple rack apps without using map or Rack::UrlMap? Using these will dispatch the apps fine, but will also prefix the route used for dispatch to the beginning of the matcher, so:
class API < Sinatra::Base
get "/api" do
# blah
end
end
map( "/api" ) { run API }
The route above is accessed at "/api/api" which is not what I want, just "/api" is what I want. I don't want to dig into the request object with a filter and remove prefixes if there's a better way.
I've tried:
use API.app # the app is wrapped in a `def self.app` for convenience.
run Web.app
but use causes problems if the app itself has used use within it too. Doing this:
run API.app
run Web.app
will only serve routes from the last app given to run.
I'm about to try Rack::Cascade but I've never used that before and don't know if it's a good answer to this problem.

The answer is indeed Rack::Cascade:
run Rack::Cascade.new( [API, Web] )

Related

How to migrate a modular Sinatra app to AWS Lambda

I have a modular Sinatra app that runs fine under rackup, that is with a config.ru that has three 'use' statements and one run statement. I am trying to get my head around how to port the application to AWS lambda where API_gateway will provide web server services and just call my app.
I am using the recommended lambda.rb script from https://github.com/aws-samples/serverless-sinatra-sample/blob/master/lambda.rb as my entrypoint. What I don't get is how, in the AWS Lambda micro-clime, do I assemble the modules/layers of my application without rackup and config.ru?
I assume that my noob brain is just missing something really basic in spite of the fact that I have read every blog post, bit of Sinatra and Rack documentation I know of, and the great "Sinatra Up and Running" book. What am I missing?
Upon re-reading the book, "Sinatra: Up and Running" again, it seems that there is a fairly elegant solution. If I set up my lambda.handler entrypoint to point to a simple Sinatra router, then it will be the router that composes the individual controllers into a 'system' which will handle all of my end points. the example from the book:
Example 4-32. Using Sinatra as router
require 'sinatra/base'
class Foo < Sinatra::Base
get('/foo') { 'foo' }
end
class Bar < Sinatra::Base
get('/bar') { 'bar' }
end
class Routes < Sinatra::Base
get('/foo') { Foo.call(env) }
get('/bar') { Bar.call(env) }
end
run Routes
Of course, in my case, I won't need the 'run Routes' line. I will just have my lambda.handler call Routes, as in Routes.call. This seems very doable!

Combine Rack::Builder and Rack::Cascade

Sorry if this question is a duplication of another question, but i haven't found it yet.
I have some Grape APIs (which are Rack apps) and one of them (the user API) uses a middleware for authentication.
In my config.ru file i combined all APIs to one app via Rack::Cascade. Here's the code:
user_management = Rack::Builder.new {
use Middleware
run UserAPI.new
}
app = Rack::Cascade.new [
user_management,
ExampleAPI1,
ExampleAPI2,
ExampleAPI3
]
The problem is that the middleware is called every time when any of the APIs gets a request.
Does anybody have any advice on how to use the middleware only when the user API gets a request?
The answer to this question is that I had to remove the resources (e.g. resource :user) from the APIs and then use Rack::Builder as follows:
app = Rack::Builder.new {
map '/user' do
use Middleware
run ExampleAPI1
end
map '/items' do
run ExampleAPI2
end
map '/locations' do
run ExampleAPI3
end
map '/reports' do
run ExampleAPI4
end
}
The problem with Rack::Cascade was that it tries every app from top to bottom until it finds the a suitable endpoint

Modular Sinatra app returns 404's under Passenger

I have a modular Sinatra app which runs fine when executed with rackup. The config.ru file is defined as follows:
map '/' do
run My::Controllers::Default
end
map '/api' do
run My::Controllers::Api
end
When I run the app under nginx/passenger I get nothing but 404's, even for the '/' route. Suspecting that something was wrong with routing, I modified config.ru as follows:
run My::Controllers::Default
After restarting nginx, I was served the default page of the app. However, the default page of the app reaches into the api route to get some documentation to display, and that part returns a 404. Given that config.ru is able to run the Default controller, I'm sure that the issue has nothing to do with being able to load all of the relevant ruby files--which seems to be the problem in other related questions I've found on SO.
With that in mind I modified config.ru as follows:
map '/api' do
run My::Controllers::Api
end
run My::Controllers::Default
At this point I'm back to getting nothing but 404's, even for the '/' route. It seems that the map statement is confusing the webserver and making it unable to find the correct routes.
If I just run the app using rackup everything behaves as expected, so I'm really at a loss to explain what I'm seeing.
I remember this being the answer. Let me know if it works for you. If it does I'll "Accept" the answer so that others will find it.
Middleware
A bug in passenger prevents it from understanding the map statement in config.ru https://groups.google.com/forum/#!msg/phusion-passenger/PoEEp9YcWbY/1y0QL_i3tHYJ
class PassengerFix
def initialize(app)
#app = app
end
def call(env)
env["SERVER_NAME"] = env["HTTP_HOST"]
return #app.call(env)
end
end
config.ru
configure do
use PassengerFix
end

Simplest method of enforcing HTTPS for Heroku Ruby Sinatra app

I have an app I created on Heroku which is written in Ruby (not rails) and Sinatra.
It is hosted on the default herokuapp domain so I can address the app with both HTTP and HTTPS.
The app requests user credentials which I forward on to an HTTPS call so the forwarding part is secure.
I want to ensure my users always connect securely to my app so the credentials aren't passed in clear text.
Despite lots of research, I've not found a solution to this simple requirement.
Is there a simple solution without changing my app to Ruby rails or otherwise?
Thanks,
Alan
I use a helper that looks like this:
def https_required!
if settings.production? && request.scheme == 'http'
headers['Location'] = request.url.sub('http', 'https')
halt 301, "https required\n"
end
end
I can then add it to any single route I want to force to https, or use it in the before filter to force on a set of urls:
before "/admin/*" do
https_required!
end
Redirect in a Before Filter
This is untested, but it should work. If not, or if it needs additional refinement, it should at least give you a reasonable starting point.
before do
redirect request.url.sub('http', 'https') unless request.secure?
end
See Also
Filters
Request Object
RackSsl::Enforcer

Jekyll site via Sinatra and Heroku - can't route to new posts

I created a 'Hello, World' app using Sinatra and then pushed to Heroku and all worked.
I've since created a basic Jekyll blog, and am trying to access it via Heroku using the following routes:
get '/?' do
file.read("_site/index.html")
end
get '/.*.*' do
file.read("_site/#{params[:splat]}")
end
not_found do
file.read("_site/error/index.html")
end
The route to the index works fine link to my site
but as soon as I click to the first post it always fails.
I have tried so many variations of different routes for the :splat and get, just can't seem to get it to work? Any ideas?
In the route that's failing, before the file.read statement, add warn "splat = #{params[:splat]}" and that will output the result to the terminal, and you can see what it's actually getting, e.g.
get '/.*.*' do
warn "splat = #{params[:splat]}"
file.read("_site/#{params[:splat]}")
end
You could also try using an absolute path to the files, though if you're getting the index page then it suggests it's not needed:
config do
set :statics, File.expand_path(File.join(settings.root, "_site"))
end
get '/.*.*' do
file.read( File.join settings.statics, params[:splat] )
end
Unless there's something else you were planning to use Sinatra's routes for, you could probably remove the Sinatra routes entirely and just make the "_site" folder the public_folder, and then Sinatra will do the serving of the static files for you:
config do
set :public_folder, File.expand_path(File.join(settings.root, "_site"))
end
# no more to do...

Resources