How to migrate a modular Sinatra app to AWS Lambda - 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!

Related

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

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.

Strange issue in Sinatra

ok so this is very strange (well is to me), everything in my master branch works fine, I then created a new branch called twitter to conduct some twitter feed implementation. I have done this and was working yesterday on my linux machine.. I have pulled the branch today in a windows environment but when i load the app i now get the regular Sinatra 404 Sinatra doesn’t know this ditty.
This is my profile.rb file
require 'bundler/setup'
Bundler.require(:default)
require 'rubygems'
require 'sinatra'
require './config/config.rb' if File.exists?('./config/config.rb')
require 'sinatra/jsonp'
require 'twitter'
require 'sinatra/static_assets'
class Profile < Sinatra::Base
helpers Sinatra::Jsonp
enable :json_pretty
register Sinatra::StaticAssets
##twitter_client = Twitter::Client.new(
:consumer_key => ENV["CONSUMER_KEY"],
:consumer_secret => ENV["CONSUMER_SECRET"],
:oauth_token => ENV["OAUTH_TOKEN"],
:oauth_token_secret => ENV["OAUTH_SECRET"],
)
get '/' do
erb :index
end
get '/feed' do
jsonp ##twitter_client.user_timeline('richl14').map(&:attrs)
end
end
Config.ru
require './profile'
run Profile
Does anyone have any ideas of what i need to be looking at to solve this? Can anyone speak from experience with this?
Thanks
When you use the classic Sinatra style you use require 'sinatra' and then add routes to the top level. These routes get added to the Sinatra::Application. When you directly run this file, e.g. with ruby my_app.rb, Sinatra runs a built in web server, which will serve the Sinatra::Application app.
When you use the modular style, you use require 'sinatra/base', and then add routes to your Sinatra::Base subclass. In this case directly executing the file doesn’t start the built in server.
In your case you are using the modular style, but have used require 'sinatra'. You create your Profile app, but when you run the file directly Sinatra launches the built in server and serves the Sinatra::Application app. Since you haven’t added any routes to this (they’ve all been added to Profile) it runs but all requests return 404.
One way to get your app to launch you is to use rackup. This will launch the Profile app that you have explicitly set in your config.ru. (Explicitly starting your webserver will also work, e.g. using thin start).
Another possibility would be to add a line like this to the end of your Profile class:
run! if app_file == $0
This tells Sinatra to start the build in server running the Profile app if the file is the same as the Ruby file being executed, in a similar way to how the classic style app is launched. If you use this method you should change require 'sinatra' to require 'sinatra/base' otherwise you will get two servers launched, one after the other (in fact you should probably make that change anyway).
See the Sinatra docs for more info about the difference between classic and modular style.

Mount multiple Rack apps without adding a prefix to the url

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] )

Resources