Rack URL Mapping - ruby

I am trying to write two kind of Rack routes. Rack allow us to write such routes like so:
app = Rack::URLMap.new('/test' => SimpleAdapter.new,
'/files' => Rack::File.new('.'))
In my case, I would like to handle those routes:
"/" or "index"
"/*" in order to match any other routes
So I had trying this:
app = Rack::URLMap.new('/index' => SimpleAdapter.new,
'/' => Rack::File.new('./public'))
This works well, but... I don't know how to add '/' path (as alternative of '/index' path). The path '/*' is not interpreted as a wildcard, according to my tests. Do you know how I could do?
Thanks

You are correct that Rack::URLMap doesn't treat '*' in a path as a wildcard. The actual translation from path to regular expression looks like this:
Regexp.new("^#{Regexp.quote(location).gsub('/', '/+')}(.*)", nil, 'n')
That is, it treats any characters in the path as literals, but also matches a path with any suffix. I believe the only way for you to accomplish what you're attempting is to use a middleware instead of an endpoint. In your config.ru you might have something like this:
use SimpleAdapter
run Rack::File
And your lib/simple_adapter.rb might look something like this:
class SimpleAdapter
SLASH_OR_INDEX = %r{/(?:index)?}
def initialize(app)
#app = app
end
def call(env)
request = Rack::Request.new(env)
if request.path =~ SLASH_OR_INDEX
# return some Rack response triple...
else
# pass the request on down the stack:
#app.call(env)
end
end
end

Related

Is there a way to use different mime type in my sinatra app

I am creating my Api in Sinatra but i want for example use this routes:
/places
/places.meta
/places.list
/users/id/places.list
i have this working in rails but fails in sinatra
def index
case request.format.to_sym.to_s
when 'list'
result = Place.single_list(parameters)
when 'meta'
result = #parameters.to_meta
else
result = Place.get_all(parameters)
end
render json: result, status: 200
end
Sinatra doesn't have a built-in concept of a "request format", so you have to manually specify a format-aware route pattern which Rails provide you automatically.
Here I use route pattern specified as a Regexp with a named capture:
require 'sinatra'
get /\/places(\.(?<format>meta|list))?/ do # named capture 'format'
case params['format'] # params populated with named captures from the route pattern
when 'list'
result = Place.single_list(parameters)
when 'meta'
result = #parameters.to_meta
else
result = Place.get_all(parameters)
end
result.to_json # replace with your favourite way of building a Sinatra response
end

ruby sinatra how to redirect with regex

I am trying to move stuff at root to /en/ directory to make my little service multi-lingual.
So, I want to redirect this url
mysite.com/?year=2018
to
mysite.com/en/?year=2018
My code is like
get %r{^/(\?year\=\d{4})$} do |c|
redirect "/en/#{c}"
end
but it seems like I never get #{c} part from the url.
Why is that? or are there just better ways to do this?
Thanks!
You can use the request.path variable to get the information you're looking for.
For example,
get "/something" do
puts request.path # => "/something"
redirect "/en#{request.path}"
end
However if you are using query parameters (i.e. ?yeah=2000) you'll have to manually pass those off to the redirect route.
Kind of non-intuitively, there's a helper method for this in ActiveRecord.
require 'active_record'
get "/something" do
puts params.to_param
# if params[:year] is 2000, you'll get "year=2000"
redirect "/en#{request.path}?#{params.to_param}"
end
You could alternatively write your own helper method pretty easily:
def hash_to_param_string(hash)
hash.reduce("") do |string, (key, val)|
string << "#{key}=#{val}&"
end
end
puts hash_to_param_string({key1: "val1", key2: "val2"})
# => "key1=val1&key2=val2"

Sinatra URL Matching with question mark?

Is there a way to match urls with Sinatra using question mark?
get '/:id?inspect' do
# ...
end
get '/:id?last' do
# ...
end
get '/:id' do
# ...
end
I tried escaping the question mark \?, regex etc, but none of those worked.
I don't want to retrieve the value of inspect or last. I only want to know if they were supplied in the url.
Is that possible?
You can’t directly do what you want. When describing the route, Sinatra treats ? as defining an optional parameter, and doesn’t provide a way to escape it.
In a url a ? separates the path from the query string, and Sinatra only uses the path when matching routes. The contents of the query string are parsed and available in params, and the raw string is available as request.query_string.
Your url scheme seems rather unusual, but one possibility if you want to avoid checking the query_string in the actual route could be to create a custom condition to apply to the routes, and check in there:
set(:query) { |val| condition { request.query_string == val } }
get '/:id', :query => 'inspect' do
# ...
end
get '/:id', :query => 'last' do
# ...
end
get '/:id' do
# ...
end
A standard route is not defined by query parameters and should not be. Why don't you use a if construct on the params in the get /:id route?
I also suggest that when you want to set a query parameter in the request you set it like this: /:id?inspect=true (provide a dummy value)

output from a ruby script into web browser

I'm trying to implement http://blog.sosedoff.com/2011/04/09/serving-maintenance-page-with-rack-middleware/ with only one difference - my message is a whole *.html file which is read like so:
def default_prompt(t)
File.open("public/tmp/maintenance/maintenance.html", "r").read
end
and output is
if File.exists?(#file)
body = #block.nil? ? default_prompt(time_info) : #block.call(time_info)
res = Response.new
res.write(body)
res.finish
else
#app.call(env)
But i get text of the html file in the end because output is surrounded by <pre> tags.
How can i solve this problem?
It just appears that you have <pre> tags surrounding it. What's actually happening is that the result that you're returning isn't a properly formed response to Rack (you need some kind of content headers to indicate what you're sending back). You need to implement something more like this:
if File.exists(#file)
maintenance_html = File.open(#file, "r").read
[200, {"Content-Type" => "text/html"}, maintenance_html] # This is a proper Rack response.
else
#app.call(env)
inside of your middleware call function.

Using a Regex in the URI of a Mongrel Handler

I'm currently using Mongrel to develop a custom web application project.
I would like Mongrel to use a defined Http Handler based on a regular expression. For example, everytime someone calls a url like http://test/bla1.js or http://test/bla2.js the same Http handler is called to manage the request.
My code so far looks a like that:
http_server = Mongrel::Configurator.new :host => config.get("http_host") do
listener :port => config.get("http_port") do
uri Regexp.escape("/[a-z0-9]+.js"), :handler => BLAH::CustomHandler.new
uri '/ui/public', :handler => Mongrel::DirHandler.new("#{$d}/public/")
uri '/favicon', :handler => Mongrel::Error404Handler.new('')
trap("INT") { stop }
run
end
end
As you can see, I am trying to use a regex instead of a string here:
uri Regexp.escape("/[a-z0-9]+.js"), :handler => BLAH::CustomHandler.new
but that does not work. Any solution?
Thanks for that.
You should consider creating a Rack application instead. Rack is:
the standard for Ruby web applications
used internally by all popular Ruby web frameworks (Rails, Merb, Sinatra, Camping, Ramaze, ...)
much easier to extend
ready to be run on any application server (Mongrel, Webrick, Thin, Passenger, ...)
Rack has a URL mapping DSL, Rack::Builder, which allows you to map different Rack applications to particular URL prefixes. You typically save it as config.ru, and run it with rackup.
Unfortunately, it does not allow regular expressions either. But because of the simplicity of Rack, it is really easy to write an "application" (a lambda, actually) that will call the proper app if the URL matches a certain regex.
Based on your example, your config.ru may look something like this:
require "my_custom_rack_app" # Whatever provides your MyCustomRackApp.
js_handler = MyCustomRackApp.new
default_handlers = Rack::Builder.new do
map "/public" do
run Rack::Directory.new("my_dir/public")
end
# Uncomment this to replace Rack::Builder's 404 handler with your own:
# map "/" do
# run lambda { |env|
# [404, {"Content-Type" => "text/plain"}, ["My 404 response"]]
# }
# end
end
run lambda { |env|
if env["PATH_INFO"] =~ %r{/[a-z0-9]+\.js}
js_handler.call(env)
else
default_handlers.call(env)
end
}
Next, run your Rack app on the command line:
% rackup
If you have mongrel installed, it will be started on port 9292. Done!
You have to inject new code into part of Mongrel's URIClassifier, which is otherwise blissfully unaware of regular expression URIs.
Below is one way of doing just that:
#
# Must do the following BEFORE Mongrel::Configurator.new
# Augment some of the key methods in Mongrel::URIClassifier
# See lib/ruby/gems/XXX/gems/mongrel-1.1.5/lib/mongrel/uri_classifier.rb
#
Mongrel::URIClassifier.class_eval <<-EOS, __FILE__, __LINE__
# Save original methods
alias_method :register_without_regexp, :register
alias_method :unregister_without_regexp, :unregister
alias_method :resolve_without_regexp, :resolve
def register(uri, handler)
if uri.is_a?(Regexp)
unless (#regexp_handlers ||= []).any? { |(re,h)| re==uri ? h.concat(handler) : false }
#regexp_handlers << [ uri, handler ]
end
else
# Original behaviour
register_without_regexp(uri, handler)
end
end
def unregister(uri)
if uri.is_a?(Regexp)
raise Mongrel::URIClassifier::RegistrationError, "\#{uri.inspect} was not registered" unless (#regexp_handlers ||= []).reject! { |(re,h)| re==uri }
else
# Original behaviour
unregister_without_regexp(uri)
end
end
def resolve(request_uri)
# Try original behaviour FIRST
result = resolve_without_regexp(request_uri)
# If a match is not found with non-regexp URIs, try regexp
if result[0].blank?
(#regexp_handlers ||= []).any? { |(re,h)| (m = re.match(request_uri)) ? (result = [ m.pre_match + m.to_s, (m.to_s == Mongrel::Const::SLASH ? request_uri : m.post_match), h ]) : false }
end
result
end
EOS
http_server = Mongrel::Configurator.new :host => config.get("http_host") do
listener :port => config.get("http_port") do
# Can pass a regular expression as URI
# (URI must be of type Regexp, no escaping please!)
# Regular expression can match any part of an URL, start with "^/..." to
# anchor match at URI beginning.
# The way this is implemented, regexp matches are only evaluated AFTER
# all non-regexp matches have failed (mostly for performance reasons.)
# Also, for regexp URIs, the :in_front is ignored; adding multiple handlers
# to the same URI regexp behaves as if :in_front => false
uri /^[a-z0-9]+.js/, :handler => BLAH::CustomHandler.new
uri '/ui/public', :handler => Mongrel::DirHandler.new("#{$d}/public/")
uri '/favicon', :handler => Mongrel::Error404Handler.new('')
trap("INT") { stop }
run
end
end
Seems to work just fine with Mongrel 1.1.5.

Resources