How to remove '/' from end of Sinatra routes - ruby

I'm using Sinatra and the shotgun server.
When I type in http://localhost:9393/tickets, my page loads as expected. But, with an extra "/" on the end, Sinatra suggests that I add
get '/tickets/' do
How do I get the server to accept the extra "/" without creating the extra route?
The information in Sinatra's "How do I make the trailing slash optional?" section looks useful, but this means I would need to add this code to every single route.
Is there an easier or more standard way to do that?
My route is set up as
get '/tickets' do

It looks like the FAQ doesn't mention an option that was added in 2017 (https://github.com/sinatra/sinatra/pull/1273/commits/2445a4994468aabe627f106341af79bfff24451e)
Put this in the same scope where you are defining your routes:
set :strict_paths, false
With this, Sinatra will treat /tickets/ as if it were /tickets so you don't need to add /? to all your paths

This question is actually bigger than it appears at first glance. Following the advice in "How do I make the trailing slash optional?" does solve the problem, but:
it requires you to modify all existing routes, and
it creates a "duplicate content" problem, where identical content is served from multiple URLs.
Both of these issues are solvable but I believe a cleaner solution is to create a redirect for all non-root URLs that end with a /. This can easily be done by adding Sinatra's before filter into the existing application controller:
before '/*/' do
redirect request.path_info.chomp('/')
end
get '/tickets' do
…
end
After that, your existing /tickets route will work as it did before, but now all requests to /tickets/ will be redirected to /tickets before being processed as normal.
Thus, the application will respond on both /ticket and /tickets/ endpoints without you having to change any of the existing routes.
PS: Redirecting the root URL (eg: http://localhost:9393/ → http://localhost:9393) will create an infinite loop, so you definitely don't want to do that.

Related

Add a prefix to generated links, but not to incoming routes

Our Rails 4 application needs to be accessible over an archaic portal. This portal works by adding (from the perspective of the browser) a prefix to each URL; this prefix is removed by the portal before forwarding the request to my application.
So the browser calls https://portal.company.com/portal/prefix/xyzzy/myapp/mymodel/new; the portal does its thing and requests https://myserver.company.com/myapp/mymodel/new (passing along the stripped prefix in some irrelevant way). The prefix is dynamic and can change between requests.
The problem is that the portal is not able to rewrite the HTML pages served by my application. That is, it does not put in the prefix. It expects applications to either only emit relative URLs, or to add the portal prefix themselves.
So:
A regular URL /myapp/mymodel/new, for example, must stay as is for when the application is accessed directly (for certain users which do not use the portal).
When accessed over the portal, our application must still understand /myapp/mymodel/new as usual, but when using mymodel_new_path or link_to #mymodel or form_for #my_model or whatever other magic URL generators there are, it has to add the portal prefix. So, any URL emitted by the application must look like /portal/prefix/xyzzy/myapp/mymodel/new where the per-request string /portal/prefix/xyzzy is given by some method defined by us (and the part xyzzy can change between requests).
How can I achieve that? My routes.rb looks like this today:
MyApp::application.routes.draw do
scope ' /myapp' do
get ...
This probably has to stay as is, because URLs in incoming requests do not change when coming from the portal. But how do I influence the outgoing URLs?
This suggestion will allow you to easily prefix the urls produced by the Rails path helpers as your require. Do note, however, it will also make these extended paths valid requests for your application - they shoud just route where expected but you'll get get some extra values in the params hash that you can ignore, so I suspect this is possibly acceptable.
First, add all the prefix bits as optional parameters to your routes' base scope:
scope '(:portal/)(:prefixA/)(:prefixB)/myapp' do
# routes
end
Note that the those optional params cannot include the / char without it being escaped by the path helpers, so if you have a few levels in the prefix (which it appears you do in the question) you'll need a few different params, all but the last followed by a slash, as above.
With that done, you should define default_url_options in your ApplicationController, it should return a hash of the values you need in your routes:
def default_url_options(_options={})
{
portal: 'portal',
prefixA: 'whatevertheprefixis',
prefixB: 'nextbitoftheprefix'
}
end
And that should do it, path helpers (along with link_to #object etc) should all now include those values every time you use them.
Note that since the portal bit at the start is also an optional parameter, you can simply add additional logic to default_url_options and have it return an empty hash whenever you do not want this prefixing behaviour.

Ruby on Sinatra: Imitate a request based on a parameter

I am currently developing a Ruby API based on Sinatra. This API mostly receives GET requests from an existing social platform which supports external API integration.
The social platform fires off GET requests in the following format (only relevant parameters shown):
GET /{command}
Parameters: command and text
Where text is a string that the user has entered.
In my case, params[:text] is in fact a series of commands, delimited by a space. What I want to achieve is, for example: If params[:text]="corporate finance"
Then I want my API to interpret the request as a GET request to
/{command}/corporate/finance
instead of requesting /{command} with a string as a parameter containing the rest of the request.
Can this be achieved on my side? Nothing can be changed in terms of the initial request from the social platform.
EDIT: I think a better way of explaining what I am trying to achieve is the following:
GET /list?text=corporate finance
Should hit the same endpoint/route as
GET /list/corporate/finance
This must not affect the initial GET request from the social platform as it expects a response containing text to display to the user. Is there a neat, best practice way of doing this?
get "/" do {
text = params[:text].split.join "/"
redirect "#{params[:command]}/#{text}"
end
might do the trick. Didn't check though.
EDIT: ok, the before filter was stupid. Basically you could also route to "/" and then redirect. Or, even better:
get "/:command" do {
text = params[:text].split.join "/"
redirect "#{params[:command]}/#{text}"
}
There a many possible ways of achieving this. You should check the routes section of the sinatra docs (https://github.com/sinatra/sinatra)
The answer by three should do the trick, and to get around the fact that the filter will be invoked with every request, a conditional like this should do:
before do
if params[:text]
sub_commands = params[:text].split.join "/"
redirect "#{params[:command]}/#{sub_commands}"
end
end
I have tested it in a demo application and it seems to work fine.
The solution was to use the call! method.
I used a regular expression to intercept calls which match /something with no further parameters (i.e. /something/something else). I think this step can be done more elegantly.
From there, I split up my commands:
get %r{^\/\w+$} do
params[:text] ? sub_commands="/"+params[:text].split.join("/") : sub_commands=""
status, headers, body = call! env.merge("PATH_INFO" => "/#{params[:command]}#{sub_commands}")
[status, headers, body]
end
This achieves exactly what I needed, as it activates the correct endpoint, as if the URL was typed it the usual format i.e. /command/subcommand1/subcommand2 etc.
Sorry, I completely misunderstood your question, so I replace my answer with this:
require 'sinatra'
get '/list/?*' do
"yep"
end
like this, the following routes all lead to the same
You need to add a routine for each command or replace the command with a * and depend your output based on a case when.
The params entered by the user can be referred by the params hash.
http://localhost:4567/list
http://localhost:4567/list/corporate/finance
http://localhost:4567/list?text=corporate/finance

How can I shorten routes in Codeigniter for certain requests?

I have a page that has this category URL website.com/category/view/honda-red-car and I just want it to say http://website.com/honda-red-car no html or php and get rid of the category view in the URL.. this website has been done using the CodeIgniter framework..
also this product view URL website.com/product/details/13/honda-accord-red-car
and I want it to be website.com/honda-accord-red-car PLEASE HELP!!!
I cannot find correct instructions on what I am doing wrong??
In Routes.php you need to create one like so
$route['mycar'] = "controller_name/function_name";
So for your example it would be:
$route['honda-red-car] = "category/view/honda-red-car";
Take a look into the URI Routing part of the user guide.
If you have concrete set of urls that you want to route then by adding rules to the application/config/routes.php you should be able to achieve what you want.
If you want some general solution (any uri segment can be a product/details page) then you might need to add every other url explicitly to the routes.php config file and set up a catch-all rule to route everything else to the right controller/method. Remember to handle 404 urls too!
Examples:
Lets say the /honda-red-car is something special and you want only this one to be redirected internally you write:
$routes['honda-red-car'] = 'product/details/13/honda-accord-red-car';
If you want to generalize everything that starts with the honda- string you do:
$routes['(honda-.*)'] = 'product/details_by_slug/$1'; // imaginary endpoint
These rules are used inside a preg_replace() call passing in the key as the pattern, and the value as the replace string, so the () are for capture groups, $1 for placing the capture part.
Be careful with the patterns, if they are too general they might catch every request coming in, so:
$routes['(.*)'] = 'product/details_by_slug/$1';
While it would certainly work for any car name like suzuki-swift-car too it would catch the ordinary root url, or the product/details/42 request too.
These rules are evaulated top to bottom, so start with specific rules at the top and leave general rules at the end of the file.

Sinatra Url '/' interpretations

I am a ruby newbie and have been trying Sinatra for quite some time now, one thing that Iam not able to figure out is why does a '/' in the url make such a big difference.
I mean isnt:
get 'some_url' do
end
and
get 'some_url/' do
end
Supposed to point to the same route? why is that Sinatra considers it as different routes? I spent a good one hour trying to figure that out.
According to RFC 2616 and RFC 2396 (RFCs defining resource identity) those URLs do not define the same resource. Therefore Sinatra treats them differently. This is esp. important if you imagine the route returning a page with relative links. This link
click me
Would point to /bar if you're coming from /foo, to /foo/bar if you're coming from /foo/.
You can use the following syntax to define a route matching both:
get '/foo/?' do
# ...
end
Or the Regexp version mentioned in the comments above.
They are different routes. The second is a URL with a directory extension ('/'); the first is a URL with no extension. A lot of frameworks (like Rails) will interpret both as the same route, or append the `/' (e.g., Django, and Apache can be configured to do that as well), but technically they are different URLs.

ignoring last uri segment via mod_rewrite or CodeIgniter

I was just wondering if it is possible to ignore the last URI segment of my application via either mod_rewrite or CodeIgniter. I don't want a redirect away or a remove the URI segment. I just want my app to not know it exists. So in the browser the client will see:
http://example.com/keep/keep/ignore/
but the app is only aware of:
http://example.com/keep/keep/
The idea is, if JavaScript detects /ignore/ in the URI, it will trigger an action.
/ignore/ may appear as 1st, 2nd, 3rd or 4th segment, but will only ever appear as the final one and may sometimes not appear at all.
I found some info online about ignoring sub-directories with mod-rewrite, but none of them really work like this.
**
Incase any CodeIgniters suggest passing it as an unused extra parameter to my method - The app has far too many controllers and far too many wildcard routes for this to work site wide.
I think a mod_rewrite solution would be best if possible. If not, perhaps it can be done with a CodeIgniter pre-controller hook or something, but I'm not sure how that would work.
EDIT: How I got it to work
For anyone else who would ever like to know the same thing - in the end I overwrote _explode_segments() in MY_URI to not include this segment.
With the URI class you can check and detect what URI's are and what they have.
$this->uri->segment(n)
Check out the user guide: http://codeigniter.com/user_guide/libraries/uri.html

Resources