Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Routing should be agnostic to leading and trailing slashes #848

Closed
jashenson opened this issue Jan 7, 2012 · 33 comments
Closed

Routing should be agnostic to leading and trailing slashes #848

jashenson opened this issue Jan 7, 2012 · 33 comments

Comments

@jashenson
Copy link

Currently, defining routes as "/search" is not equal to "search", which is also not equal to "search/" or "/search/". Backbone should consider all of these routes as equal, so that all routing is agnostic to the leading and trailing slashes.

@bradwright
Copy link

In my opinion, /url/ and /url are not the same thing at all, even though modern frameworks like Django make the mistake of allowing people to redirect the latter to the former.

url and url/ are relative paths and should be calculated from the current path - a path must start with a trailing slash to be absolute.

@jscheel
Copy link

jscheel commented Jan 10, 2012

This becomes a bigger problem when different frameworks and different servers choose to strip trailing slashes

@jashenson
Copy link
Author

@jscheel Can you suggest an example of how this might play out?

@bradleywright I definitely see your point for the leading slashes vis-a-vis absolute and relative paths. But what about the trailing slash? How do you see the two cases /url/ and /url as differing?

@bradwright
Copy link

@jayred:

@bradleywright I definitely see your point for the leading slashes
vis-a-vis absolute and relative paths. But what about the trailing
slash? How do you see the two cases /url/ and /url as
differing?

Web servers treat them differently unless the URL routing used in front (by Rails or Django, for example) forces an implicit 302 or 301 to "normalise" the URL.

A "/url/" says to a web server "look for index file inside the /url/ directory" (index.html or similar), whereas "/url" says "look for a file named url". They're definitely different things, as they map directly to the file system.

Google themselves note this.

@jashenson
Copy link
Author

@bradleywright From a server stand-point, you're absolutely right. But what about the user's perspective? If the user visits your site by typing in abc.com/search/ they should expect to get the same page as abc.com/search (the preferred format). Right now, you have to create two routes in Backbone to accomplish this.

@bradwright
Copy link

Having the same resource served at two different URLs (/search and /search/) is a bad practice - the "wrong" one should be redirecting to the canonical one (whichever is your preference).

So your point about the user is fine, but that's an implementation detail in whatever you're using to route requests on the application server - IMO Backbone shouldn't make assumptions about things like that, so I feel that the route you put in Backbone should be the canonical one, and not a mix of the two.

@jashkenas
Copy link
Owner

Yep. I'm afraid that in terms of backbone routes, which can (but don't have to be) client-side only ... a trailing slash should indeed be significant.

If your route looks like this:

pages/help.html

Then we certainly shouldn't be making it the same as this:

pages/help.html/

... by default.

For the record, you should never be using leading slashes with Backbone routes -- any trailing characters are your own business.

@beezee
Copy link

beezee commented Jan 31, 2012

I know this issue is closed, but I really have to ask, what is the problem with leading slashes? Gmail and Twitter both do this, and I personally think index.html/#/login is MUCH more attractive than index.html#/login.

I realize this may just be a personal preference, but what's the reason for preventing this behavior, as I notice is being done in 0.9.0?

@jscheel
Copy link

jscheel commented Jan 31, 2012

I think it's actually forcing it to

`````` index.html/#login... note the absence of a leading slash on the js route, the trailing slash on the url is still there. However, I too am really confused as to why leading slashes are a no-no. I get not using the hash-bang, since it suggests additional work on the backend that google can crawl, and has seriously burnt people in the past, but I prefer the leading slash as well. It provides a nice, clear delineation. However, I don't think gmail uses it anymore. Looking at my gmail, I see: https://mail.google.com/mail/u/0/#inbox/[code]```
There must be some reason for this. @jashkenas, any insight?

@justinperkins
Copy link

So, the way I see it, if we want to allow trailing slashes in our backbone app we have to add duplicate routes all over the place? If this issue is going to be closed as "won't fix" I think there should at least legit comment explaining how to solve this problem for those of us philistines who just want to allow trailing slashes in our awfully coded, bad practice ridden app.

@ptnplanet
Copy link

I've just stumbled across this issue and would like to provide a simple solution for cases where your api framework treats urls with and without trailing slashes as the same. This may be confusing for your user, because entering sales/ will result in a valid request to your webserver, but the backbone.js router wont route.

With apache modify your .htaccess to redirect the user to non-trailing-slash equivalent of the url:

# Rewrite trailing slashes to non-trailing-slash equivalents
RewriteCond %{REQUEST_URI} /$ [NC]
RewriteRule ^(.*)/$ $1 [NC,L,R=301]

@vincentbriglia
Copy link

In my router's initialization method I manually added a catchall route that removes trailing slashes if this is helpful to anyone.

var re = new RegExp("(\/)+$", "g");
/*jshint regexp: false*/
this.route(/(.*)\/+$/, "trailFix", function (id) 
    // remove all trailing slashes if more than one
    id = id.replace(re, '');
    this.navigate(id, true);
});

@armaanahluwalia
Copy link

+1

@squaretone
Copy link

@vincentbriglia thanks for that snippet for removing the trailing slashes! I added a missing { right after the function on line 3. I've added it below.

var re = new RegExp("(\/)+$", "g");
/*jshint regexp: false*/
this.route(/(.*)\/+$/, "trailFix", function (id) {
    // remove all trailing slashes if more than one
    id = id.replace(re, '');
    this.navigate(id, true);
});

@joshpangell
Copy link

I do know this thread is fairly old and closed, but I have a solution and thought it may help anyone looking to accomplish this. If you re-write the _routeToRegExp method when you create the router, you can force the trailing slash option by adding \/? to the namedParam regex.

Additionally, if you would like the route to also be case insensitive, this is also the place to do it by adding the 'i' attribute to the return value.

_routeToRegExp: function (route) {
      var namedParam    = /:\w+/g;
      var splatParam    = /\*\w+/g;
      var escapeRegExp  = /[-[\]{}()+?.,\\^$|#\s]/g;    

      route = route.replace(escapeRegExp, '\\$&')
                   .replace(namedParam, '([^\/]+\/?)')
                   .replace(splatParam, '(.*?)');

      return new RegExp('^' + route + '$');
      /*
       * Note: If you would like case insensitivity, 
       *       add the "i" attribute to the return
       * return new RegExp('^' + route + '$', 'i');
       */
}

Note: This method is an exact copy for the one in the default backbone library, but I moved the variables into the function.

@braddunbar
Copy link
Collaborator

@joshpangell Support for optional groups was added in #1509. You can now do the following:

var Router = Backbone.Router.extend({
  routes: {
    'route(/)': 'route'
  },
  route: function() {...}
});

@mindscratch
Copy link
Contributor

@braddunbar any idea when the next version of backbone will be released? it would be great to have this feature (as well as other features and fixes that have been done since 0.9.2 came out around 9 months ago).

@philfreo
Copy link
Contributor

@mindscratch soon - see here - #1885

@braddunbar
Copy link
Collaborator

Yep, very soon. :)

@andrewtennison
Copy link

for what its worth...

I appreciate SEO is usually ignored / not an issue with single page apps based on backbone, but if you allow urls both with and without trailing / it is detrimental to your sites SEO as each page is viewed as duplicated. As previously mentioned you are better off setting apache or similar to manage the invalid url and redirect to the valid one.

@jamesmehorter
Copy link

Thanks @ptnplanet and @andrewtennison - I opted to change our framework so that no trailing slashes would ever be used. We're using WordPress, so I simply created a custom permalink in 'Settings > Permalinks' of /%postname% without a trailing slash, works great.

@tomasdev
Copy link

@braddunbar i love you. (/) totally worked. I just put it in my _routeToRegExp thingy.

@braddunbar
Copy link
Collaborator

Glad I could help! 😄

@aholmes
Copy link

aholmes commented Apr 10, 2014

I am frustrated that backbone defined its own URI scheme that is not inline with the HTTP URI scheme.

RFC 3986 (Updated, but not obsoleted by RFC 6874) Section 6.2.3 states, and gives an example, that an empty segment path is the same as a segment ending with "/".

Specifically, this is addressed for the root of the URI. It is important to understand Section 6 to build context around applying this to URI segments. Section 5.4.1 can also be referenced for some examples.

In fact, RFC 2616 (the HTTP RFC, updated by a few RFcs, but not obsoleted) specifically states how to compare URLs in Section 3.2.3.

An empty abs_path is equivalent to an abs_path of "/".

Section 5.1.2 and Section 3.2.2 state the structure of a request URI and define what abs_path is.

Request-URI = "*" | absoluteURI | abs_path | authority

http_URL = "http:" "//" host [ ":" port ] [ abs_path [ "?" query ]]

It is erroneous for backbone to treat trailing slashes and non-present trailing slashes as nonequivalent. This issue should be reopened and reconsidered.

@jashkenas
Copy link
Owner

Eh? I don't mean to be rude here — but that's an extremely strong appeal to authority without actually 1) looking at what servers do with trailing slashes on the web currently, and 2) carefully reading what those RFCs are saying.

When they mean "an empty segment path", and "an empty abs_path", they're talking about a truly empty path ... i.e., the root of the server, which is all well and fine.

Trailing slashes as part of a non-empty path are (perhaps wrongly) entirely semantic. Some servers will redirect you to the equivalent automatically, but many will not. Backbone needs to support use cases that prefer not to have:

www.mywebsite.com/index.html

... also be served when the user navigates to a broken URL like:

www.mywebsite.com/index.html/

Now, let's look in the wild. Say at the W3C, for an example. This page (http://www.w3.org/standards/webdesign/htmlcss) is served without a trailing slash. Try visiting it with one, and see where you get: http://www.w3.org/standards/webdesign/htmlcss/

"own URI scheme that is not inline with the HTTP URI scheme" my arse.

@aholmes
Copy link

aholmes commented Apr 10, 2014

Would you care to enlighten me on the parts I'm not carefully reading? Further references:

When they mean "an empty segment path", and "an empty abs_path", they're talking about a truly empty path ... i.e., the root of the server, which is all well and fine.

And herein lies the issue. Neither RFC states whether a URI with a trailing slash is equivalent to one without. After a fairly thorough reading, I have interpreted both to mean they are equivalent; other's will disagree, as you have.

Can you also clarify why that URL is broken?

when the user navigates to a broken URL like

.

URI that are hierarchical in nature use the slash "/" character for separating hierarchical components. For some file systems, a "/" character (used to denote the hierarchical structure of a URI) is the delimiter used to construct a file name hierarchy, and thus the URI path will look similar to a file pathname. This does NOT imply that the resource is a file or that the URI maps to an actual filesystem pathname.

I refute that I am making an appeal to authority. An appeal to authority roughly follows this idea:

  • A is an expert in B.
  • A does B X way.
  • X is right.

When RFCs are accepted, they become the de facto specification for the concepts they describe. Even if you are claiming the RFC is the authority I am appealing to, appeals to authority are not necessarily fallacious, as you seem to be implying. It's the misuse of appeals to authority that are problematic. Now, referencing what the W3C does with that specification is a misuse of an appeal to authority, and is a fallacy.

  • W3C is an expert is web technologies.
  • W3C treats the trailing and non-trailing-slash URIs as nonequivalent.
  • Trailing and non-trailing-slash URIs are nonequivalent.

Now, while we're on the subject of logical fallacies, consider also the "fallacy fallacy." If a fallacy is committed, it does not necessarily invalidate the argument.

Your example, for example, props up your interpretation. While this is a fallacious appeal to authority, it is important for developers to consider what happens out there. To that end, I understand why backbone treats the URLs differently, whether or not I agree with it.

Here are some examples that support my interpretation of the RFCs. These are all fallacious appeals to authority, but must be considered for practicalities sake.

"own URI scheme that is not inline with the HTTP URI scheme" my arse.

There's no need for snark. This is an issue tracking forum, not a roast.

With all this out of the way, understanding your point about needing to support both cases, perhaps some form of configuration option is a more workable solution than treating "/" as an optional part of the route definition?

@jashkenas
Copy link
Owner

With all this out of the way, understanding your point about needing to support both cases, perhaps some form of configuration option is a more workable solution

Sure, that sounds like a fine idea. Want to cook up a pull request?

aholmes pushed a commit to aholmes/backbone that referenced this issue Apr 24, 2014
…ter so application authors can determine whether their applications treats URIs with a trailing slash as a different resource than URIs without a trailing slash. Defaults to true. This option causes Router to set the 'routeTrailingSlashPattern' variable, which is added to the overall URIs matching regex. jashkenas#848
@tad-lispy
Copy link

Just out of curiosity: what's wrong with leading slashes?

I've spent some time googling and found several statements against it e.g. here @jashkenas and in the official docs:

Note that you'll want to avoid using a leading slash in your route definitions: [...]

but not a word - why.

@braddunbar
Copy link
Collaborator

Well, I think leading slashes are stripped from routes before any handling takes place. If nothing else, they're superfluous in Backbone routes.

@jashkenas
Copy link
Owner

There's no technical reason if you're using hashchange -- they're just ugly, but were trendy for a brief period.

Leading slashes make no sense if you're using pushstate.

@tad-lispy
Copy link

Got it. Thanks.

@raymondji-zz
Copy link

(Apologies for the revival)

#1885 says the update included:

  • Normalizing trailing and leading slash behavior in routes

But on Backbone 1.3.3 I'm continuing to see the issue of '.../foo' and '.../foo/' treated differently.
Am I misinterpreting the intended behaviour?

Thanks.

@emileber
Copy link
Contributor

@raymondji use .../foo(/) in your router's routes to treat both as the same routes. Backbone gives you the tools to choose how to interpret the routes.

From the doc on routes:

Trailing slashes are treated as part of the URL, and (correctly) treated as a unique route when accessed. docs and docs/ will fire different callbacks. If you can't avoid generating both types of URLs, you can define a "docs(/)" matcher to capture both cases.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests