ruby middleware add stack adding middlware - ruby

I am trying to add/append middleware to the stack (in config.ru) on certain requests (where request.path == "/hi")
I was trying to do that with Rack::Builder
But i seem to fail at it.
Rack::Builder.new do
use added_middleware1
use added_middleware2
end.call(#env) if #request.path == "/something"
I am not sure if that makes my problem clear.

The #request variable should only be available when an actual request is happening, but the code you posted is probably getting run at configuration time when the server starts up. There is example code on the front page of the Rack::Builder documentation showing how to do something like you would want:
app = Rack::Builder.new {
use Rack::CommonLogger
use Rack::ShowExceptions
map "/lobster" do
use Rack::Lint
run Rack::Lobster.new
end
}

The stack should look more like this:
use CommonMiddleware
map '/something' do
use SpecialMiddleware
run TheApp.new
end
map '/' do
run TheApp.new
end
Alternatively, you could write a middleware that looks at the request path and runs the other special middleware as needed - sort of like a wrapper for it. The machinery would look something like this:
def initialize app, special_middleware
#app, #special_middleware = app, special_middleware
end
def call env
if route_matches?(env)
#special_middleware.new(#app).call(env) # assumes special_middleware initializer takes no extra parameters
else
#app.call(env)
end
end
def route_matches? env
# examine the rack environment hash, return true or false
end
And the config for using it would look like this:
use RouteMatchingMiddleware, SpecialMiddleware
run TheApp.new

Related

How can I run a method at the end of a file from a gem?

I have a gem (e.g. mygem) and as is normal, I add mygem to a file by putting require "mygem" at the top. What if I have a method in mygem called finish_jobs and I want it to run in the following location:
require "mygem"
# code, code code
finish_jobs
How would I do that without forcing the user to add the method every time they use the gem?
Specifically, what I am trying to do is write a server app (with rack) and I need the methods in the body of the file to be processed before the server is started.
This is certainly possible.
Why not just add the code directly into the Gem (since it sounds like it is under your control and is not an external dependency)?
module MyGem
def printSomething
p 2 + 2
end
module_function :printSomething
printSomething()
# => 4
end
If this isn't what you had in mind, let me know and I can update the solution.
Also, see Kernel#at_exit
A more explanatory guide on Kernel#at_exit
I don't know of a way to do what you're describing.
One workaround would be to provide an API which accepts a block. This approach allows you to run code after the user's setup without exposing implementation details to them.
A user could call your library method, providing a block to set up their server:
require "mygem"
MyGem.code_code_code {
# user's code goes here
}
Then, your library code would:
Accept the block
Call some library code
Here's an example implementation:
module MyGem
# Run some user-provided code by `yield`-ing the block
# Then run the gem's finalizer
def self.code_code_code
# Execute the block:
yield
# Finalize:
finish_jobs
end
end
This way, you can accept code from the user but still control setup and finalization.
I hope it helps!

How do I route based on a url parameter in sinatra?

I am using Sinatra and I want to use something like a referrer code in my urls that will somewhat control access and identify the provenance of a given URL.
/secret-code/rest/of/path
should be rejected if "secret-code" is not in a predetermined list.
I want to use route conditions
set(:valid_tag) { |tag| condition { tag === 'abcd' } }
get '/:tag', :valid_tag => params[:tag] do
'Hello world!'
end
but params is not in scope. Do I need to dispatch in the block? What is the best way to handle multiple routes without having to duplicate the tag checking logic in each one?
/secret/route1/
/secret/route1/blah
/secret/route2/
Is there a way to chain handlers? Can I do
get /:tag/*
# check :tag
redirect_to_handler(params[:splat])
By the sounds of things it looks like you're trying to make use of Sinatra's named parameters. Params is only in scope within the block:
get '/:secret_code/*' do
redirect_to_handler unless secret_codes.include? params[:secret_code]
end
The code above assumes you have a collection of 'secret_codes' that you're going to check with the secret_code from the URL.
(Answering my own question)
Sinatra matches the lexically first rule and you can pass onto the next matching rule using 'pass'. So something like this works as long as it is the first rule that would match.
get '/:tag/*' do
halt_if_bad_tag params[:tag]
pass
end
get '/:tag/route1' do
'hello world'
end

Rails 3: How to intercept any http request

Lets say I have an image at app/assets/images/privateimages/myrestrictedimage1.jpg
If I try to go directly to the image via url say with something like
http://localhost:5555/assets/privateimages/myrestrictedimage1.jpg
I am able to view the image.
I would like to have a way to inspect any http request to decide if the user is allowed access to it.
I know I can use before_filter in controllers to do some preprocessing before continuing onto any of the controller actions but I dont think this will help me because I need to be attempting to do a controller action for this to take effect.
I have heard I might be able to do it with a rake task but after much searching I haven't found anything like what I am trying to do. Perhaps I have to create a ruby gem to do this but I have no clue how to do this.
Can anyone point me in the right direction? Thanks.
I used Rack Middleware
The middleware class looks like this:
class MyChecker
def initialize(app)
#app = app
end
def call(env)
if (docheck)
#do stuff here such as check the path.
#For example #path = env['PATH_INFO'] and compare against your okay paths
#if youre good and are able to continue then
#app.call(env)
else
#redirect such as
[301, {"Location" => /somewhere, "Content-Type" => "text/html"}, []]
end
end
end
make sure to make your middleware visible by adding the following to application.rb
class Application < Rails::Application
...
config.autoload_paths += %W(#{config.root}/lib) #if MyChecker is located in lib othewise substitute lib with wherever you have your middleware class
config.middleware.use "MyChecker"
end
You want to look at Rack (not rake).

How can I refactor my Sinatra app?

I've just started writing a reasonably straightforward site using sinatra. My problem is that I wanted to refactor the main app.rb file but am getting errors trying to access the url params.
In my get '/' action, Sinatra's looking at which params are set and then needs to do a few different things depending on what's in the url. Something like this.
class App < Sinatra::Application
...
get '/' do
if params['code1']
#network = 'code1'
mode code here
elsif params['called'] && params['mac']
#network = 'code2'
mode code here
elsif params['code3']
#network = 'code3'
mode code here
end
end
The problem is that I need to require a file that also uses the params.
I've put the following in the above code:
require File.dirname(__FILE__) + '/lib/networks/code1.rb'
Where code1.rb includes:
class App < Sinatra::Application
if params['login'] # == 'login'
pass = 'uampass'
elsif
...
But that gives me the following error:
undefined local variable or method `params' for main:Object
How can I refactor this without causing an error
As far as i know you can't use two (or more) Sinatra applications in, well one application. Since both files define a Sinatra::Application descendant this isn't possible.
Also if you want to use values from the params-hash you should define helper methods Helper Documentation, which you call when processing the route, or you just create Class which has class or instance methods which take params-values as parameters. Actually calling params from another file/class doesn't seem like good practice.
To put this in context: Sinatra applications are organised as handlers. The Sinatra::Application descendant is something like the main handler which uses support methods(helpers and instance methods of the Sinatra::Application descendant) or support Classes, which are usually defined in other files, but do not descend from Sinatra::Application.
To make this a little bit more clearly:
Your main Sinatra file:
require_relative 'another_file.rb'
class App < Sinatra::Application
# ...
#a_handler = MyHandler.new
get '/' do
if params['something'] == 'wanted_value'
#a_handler.handle_it(params)
end
end
Another file ('another_file.rb'):
class MyHandler
def initialize
#an_instance_variable = 'foobar'
end
def handle_it(params_hash)
if params_hash['login'] # == 'login'
pass = 'uampass'
elsif
# ...
end
# ...
# do some stuff
# ....
return pass
end
end
Actual code would of course depend on the real problem you're trying to solve, so if you would elaborate i could be more precise...
The error message contains everything you need to know, and it's nothing to do with Sinatra.
You are requiring code1.rb, which contains this (slightly edited so it will run):
require 'sinatra'
class App < Sinatra::Application
if params['login'] # == 'login'
pass = 'uampass'
end
end
Ruby evaluates code as it encounters it. In this case, you've required 'code1.rb', so it evaluates the code in that file. It encounters 'params' and asks "is there a local variable or method with that name?". There isn't, so it fails as you've seen. Open an irb session and check it out.
Class definitions in ruby are just an expression with a scope.
In relation to Sinatra: params is available in the block that a route declaration takes.
I'd recommend reading Sinatra: Up and Running, which explains some of the 'magic' that is going on (a good companion to the Sinatra Book).

Writing a Sinatra Extension using options in routes

Lets say I'm writing a sinatra extension which mounts a second public directory at a given mount point.
require 'sinatra'
require 'sinatra/moar-public'
set :moar_local, './downloads/'
set :moar_remote, 'dls'
I now expect a user going to http://myapp.com/downloads/thing.bin to be given the file at [sinatra_root]/dls/thing.bin.
Writing this extension (obviously, it's a simplified example) I have something like this:
require 'sinatra/base'
module Sinatra
module MoarPublic
def self.registered(app)
app.set :moar_local, './downloads/'
app.set :moar_remote, 'downloads'
app.get "/#{app.options.moar_remote}/:filename" do
# Logic
end
end
end
register MoarPublic
end
But app.get has already been called with the default value for moar_remote so the download files are available at /downloads/thing.bin, not at /dls/thing.bin as I'd like. Any ideas?
You're asking for dynamic routes, but Sinatra compiles the route information so it won't work the way you're looking for.
As a work around, you might consider defining a catch-all route, and checking the route information inside the catch-all, e.g.
get %r{^/(*)/bar$} do |capture|
if settings.url_prefix == capture # or perhaps check against request.path_info
# get file
else
status 404
end
end
Obviously, there are still many things to be done there, but you get the drift.
I had no problem registering an extension explicitily in a modular configuration. Illustration below.
class Service < Sinatra::Base
set :url_prefix, 'foo'
register Common
end
module Common
def self.registered(app)
app.get "/#{app.options.url_prefix}/bar" do
"hello world"
end
end
end

Resources