How do I get all requests in puma rack app? - ruby

I'm writing a dead-simple puma server,
and I keep getting 404 for every request.
How do I configure the '/' path without Sinatra?
I simply want to catch all requests.
#config.ru
module Moon
class HelloWorldApp
def call(env)
[200, {}, 'Hello World']
end
def each(env=nil)
env
end
end
end
run Rack::Cascade.new Moon::HelloWorldApp.new
I run it like this:
$ puma config.ru -p 9595
Use this curl:
$ curl http://0.0.0.0:9595/test
And get 404:
127.0.0.1 - - [07/Apr/2015:22:49:25 +0300] "GET /test HTTP/1.1" 404 - 0.0002

Rack::Cascade expects an explicit array of apps to be passed to the constructor (or at least something like an enumerable that responds to each and yields the apps). It then calls each on this array to get each app (it looks like you’ve hit the no method error and tried to work round it by adding an each method to your app).
Fix it by changing the run line to:
run Rack::Cascade.new [Moon::HelloWorldApp.new]
You will also need to change the body part of your returned array, it needs to be something that responds to each and yields Strings, the simplest way to fix that is to return an array:
[200, {}, ['Hello World']]

Related

Altering params in Sinatra before blocks

I am trying to alter the request params in a before block in Sinatra.
It seems that when using this syntax, it works:
before do
#params['global-before'] = 'yes'
end
But when using this syntax, it does not work:
before '/:id/test' do
#params['route-before'] = 'yes'
end
Here is a full example:
# test.rb
require 'sinatra'
require 'sinatra/reloader'
set :bind, '0.0.0.0'
set :port, 3000
before do
#params['global-before'] = 'yes'
end
before '/:id/test' do
#params['route-before'] = 'yes'
end
get '/' do
params.to_json
end
get '/:id/test' do
params.to_json
end
Then running:
$ ruby test.rb
$ curl localhost:3000
{"global-before":"yes"}
$ curl localhost:3000/123/test
{"global-before":"yes","id":"123"}
I was expecting to see the params['route-before'] populated as well.
I have tried using request.params instead of #params but that did not work at all.
Can anyone shed some light on this?
Update:
Opened an issue in Sinatra's issue tracker
The route filter goes first, and it has route parameters: {"id"=>"123"}, so this happens:
original, #params = #params, #params.merge(params) if params.any?
where original ends up as {} and #params as {"id"=>"123"}. When the global filter runs, there are no route parameters, so original remains unassigned (nil) and #params is the {} that was originally there.
After the filter processes, in the ensure clause, there is this:
#params = original if original
So global filter skips it, because original is nil, because there were no route parameters. The route filter resets the #params to whatever it was before the filter ran, because original is preserved, because there were route parameters.
I can't say whether this is a bug or an intended behaviour, but it's a "how" at least, if not a "why". It may make sense asking the Sinatra team (and reporting back here with the verdict).
tl;dr: #params are reset to pre-filter state if there are parameters in the filter's path pattern.
Note: you can hack around it by making your own instance variable:
before '/:id/test' do
#route_before = 'yes'
end
get '/:id/test' do
"Did we run route before? #{#route_before}"
end

Getting Rack mounted path in Sinatra application

Suppose I have the following config.ru file
require './status.rb'
map "/status" do
run Sinatra::Application
end
and the status.rb is a simple
require 'sinatra'
get '/' do
'Some status here...'
end
I'd like to know where the Sinatra application is mounted inside status.rb (for example to provide proper paths to resources). Is there a way of retrieving that information from Rack?
To get where the app is mounted you can use request.script_name.
get '/' do
p request.script_name # will print "/status"
'Some status here...'
end
If you’re generating urls for resources, you might want to look at the url method instead. That will take into account things like proxies as well as where the app is mounted:
get '/' do
p url('foo') # will print "http://localhost:9292/status/foo"
'Some status here...'
end

Sinatra/Rack session.fetch producing unexpected results

I was writing a quick helper in Sinatra for redirect_to_next, where I redirect to the path provided by session[:next] if it exists, or a default.
In Sinatra, session really provided by Rack, and by spec, it is said to provide a hash like interface for fetch. I wrote the following error helper to explain my problem.
error 401 do
session[:next] = request.path
puts "get #{session[:next]}"
puts "fetch #{session.fetch(:next, '/')}"
redirect "/login"
end
When I attempt to access /settings when not logged in, I halt 401 which runs the above code. This is what it prints to my terminal:
get /settings
fetch /
:next exists as a key, so why is it giving me the default as if it does not?
Update
This minimal example shows the same behavior.
require 'sinatra'
set :sessions, true
get '/' do
session[:testing] = "hello"
puts "get #{session[:testing]}"
puts "fetch #{session.fetch(:testing, 'goodbye')}"
end
Logs
[2012-04-29 14:11:51] INFO WEBrick::HTTPServer#start: pid=1954 port=9292
get hello
fetch goodbye
10.0.2.2 - - [29/Apr/2012 14:11:54] "GET / HTTP/1.1" 200 - 0.0485
Software
ruby (1.9.3p194)
rack (1.4.1)
sinatra (1.3.2)
The session hash isn’t a normal Ruby Hash, it’s a Rack::Session::Abstract::SessionHash. SessionHash actually inherits from Hash, but it overrides the []= and [] methods, calling to_s on any keys before storing and retrieving them.
Extending your update example:
require 'sinatra'
set :sessions, true
get '/' do
session[:testing] = "hello"
puts "get #{session[:testing]}"
puts "fetch #{session.fetch(:testing, 'goodbye')}"
puts "fetch with string #{session.fetch(:testing.to_s, 'goodbye')}"
end
gives this output:
get hello
fetch goodbye
fetch with string hello
When you use Hash#fetch, passing a symbol, the method gets dispatched directly to the parent hash, without being converted to a string, and so the matching key isn’t found.
So, always use Strings as keys in your sessions and everything should work.

How do I config.ru properly in modular Sinatra application.?

I'm trying to use subclassing style in Sinatra application. So, I have a main app like this.
class MyApp < Sinatra::Base
get '/'
end
...
end
class AnotherRoute < MyApp
get '/another'
end
post '/another'
end
end
run Rack::URLMap.new \
"/" => MyApp.new,
"/another" => AnotherRoute.new
In config.ru I understand that it's only for "GET" how about other resources (e.g. "PUT", "POST")? I'm not sure if I'm missing someting obvious. And also if I have ten path (/path1, /path2, ...) do I have to config them all in config.ru even though they are in the same class?
app.rb
class MyApp < Sinatra::Base
get '/'
end
end
app2.rb If you want two separate files. Note this inherits from Sinatra::Base not MyApp.
class AnotherRoute < Sinatra::Base
get '/'
end
post '/'
end
end
The config.ru
require 'bundler/setup'
Bundler.require(:default)
require File.dirname(__FILE__) + "/lib/app.rb"
require File.dirname(__FILE__) + "/lib/app2.rb"
map "/" do
run MyApp
end
map "/another" do
run AnotherRoute
end
You could write this as
class MyApp < Sinatra::Base
get '/'
end
get '/another'
end
post '/another'
end
end
in config.ru
require './my_app'
run MyApp
Run:
rackup -p 1234
Refer to documentation at http://www.sinatrarb.com/intro#Serving%20a%20Modular%20Application
With URLMap you specify a base url where the app should be mounted. The path specified in the map is not used when determining which route to use within the app itself. In other words the app acts as if it's root is after the path used in URLMap.
For example, your code will respond to the following paths:
/: will be routed to the / route in MyApp
/another: will go to the / route in AnotherRoute. Since AnotherRoute extends MyApp this will be the same as / in MyApp (but in a different instance).
URLMap sees /another and uses it to map to AnotherRoute, stripping this part of the request from the path. AnotherRoute then only sees /.
/another/another: will be routed to the two /another routes in AnotherRoute. Again, the first another is used by the URLMap to route the request to AnotherRoute. AnotherRoute then only sees the second another as the path.
Note that this path will respond to both GET and POST requests, each being handled by the appropriate block.
It's not clear what you're trying to do, but I think you can achieve what you want by running an instance of AnotherRoute, with a config.ru that is just:
run AnotherRoute.new
Since AnotherRoute extends MyApp, the / route will be defined for it.
If you're looking for a way to add routes to an existing Sinatra application, you could create a module with an included method that adds the routes, rather than use inheritance.

Metaprogramming sinatra get

I have a list of words in a list, and I want to handle get requests to any of them (and respond the same way).
#words = ["foo","bar"....etc]
One of the ways I thought I could do this is to loop through the list and have a get directive generated for each word when sinatra is launched.
#words.each do |word|
get word do
# what to do
end
end
that doesn't work, but something in that fashion, maybe.
Another way of doing it might be responding to get %r{/(.+)} and then doing some processing inside there to see if it matches anything in the list and respond accordingly, but I'm interested nonetheless to see if there's a way I can do it as described above.
What you wrote does work, with a very minor change. For example:
require 'sinatra'
%w[foo bar].each do |word|
get "/#{word}" do
"You said #{word}!"
end
end
$ curl http://localhost:4567/bar
You said bar!
Instead of a catch-all regex route, you can craft a custom regexp dynamically to match only the words you want to match:
require 'sinatra'
words = %w[foo bar]
route_re = %r{^/(#{words.join '|'})$}i # case-insensitive
p route_re
#=> /^\/(foo|bar)$/i
get route_re do |name|
"You said #{name}!"
end
$ curl http://localhost:4567/FoO
You said FoO!
$ curl -I http://localhost:4567/jim
HTTP/1.1 404 Not Found
X-Cascade: pass
Content-Type: text/html;charset=utf-8
Content-Length: 413
Connection: keep-alive
Server: thin 1.2.7 codename No Hup
Depending on what you need, this might be enough:
get '/:word' do
# check if params[:word] is in your list
# and respond accordingly
if #words.include?(params[:word])
"yeeah"
else
pass
end
end
But keep in mind that this route matches everything.

Resources