Nginx fallback for file not found to different directory with same path - angularjs

Short Version: Is there any easy way to automatically redirect a path like /templates/my_child_theme/main/sidebar/user_nav.html to /templates/default/main/sidebar/user_nav.html whenever a 404 is encountered?
Question: Is there an easy way to use something like try_files within nginx to change a filepath when not found to a different folder with the same file path at the end? I'm currently using a client-side framework (AngularJS) and dealing with checking for the existence of files can get fairly expensive as I am literally performing an AJAX call for each file and looking for a 404 before performing the logic to swap out the path in Javascript. I've seen similar solutions for fallback images but haven't gotten a good solution yet. Here's the gist of what I'm looking to do:
Look for file at /templates/$1/$2.
On HTTP 404 instead return /templates/default/$2.
This only really needs to happen in the /templates/ location for now.
It's possible that these files could be nested several layers deep so I need something like /templates/my_child_theme/main/sidebar/user_nav.html to redirect to /templates/default/main/sidebar/user_nav.html
Backstory:
I've been building a site in AngularJS that has a fairly complex templating setup. I am utilizing the awesome ui-router module from Angular UI for deeply nested views and have written a few custom functions to implement child themes similar to Magento's fallback system. Here's what currently happens:
AngularJS requests a template path using a getTemplate() function I wrote which first looks for the file at /templates/child_theme_name_here/filepath by performing an XMLHttpRequest, checking for a status code of 404 (file not found), and then either returning that path or (in the case of a 404) returning /templates/default/filepath instead.
This way I can overload modify specific templates without needing to copy the entire theme each time, making development easier (we have 3 major corporate clients which will each have their own branded child theme) by not making me keep up with each change across multiple themes.
If there is a better way to do this within AngularJS I am open to that as well, it just seemed to me that Nginx would be the most logical place to perform such an action due to to it's low-level integration with the filesystem.

Solved.
Had to teach myself a bit on regular expressions, but finally got it working.
Here's what worked:
location ~* ^\/templates\/([^\/\\\]+)(.*)$ {
try_files /templates/$1$2 /templates/default$2 =404;
}
Regex Explanation
~* means case-insensitive matching (not really regex, just nginx syntax)
^ means start of a string
\/ means match a backslash
templates means literally match the word templates
\/ means match a backslash again
( means start capturing the following match as a group for later use
[^\\\/] means match anything that's not a backslash or forward slash means the previous set of characters can be matched multiple times (i.e. keep matching anything that isn't a slash.
) means stop capturing characters for this group. We have now defined the string that represents the first folder after /templates/
(.*) means match any other character as many times as needed (match everything that isn't a line feed in other words)
$ means match the end of the string
try_files then tries each URL in order
/templates/$1$2 means try /templates/(everything in capture group 1 above, which holds the folder we captured)(then add everything from capture group which holds the backslash and anything after it until the end of the url)
/templates/default$2 is very similar, except instead of using the text from capture group 1 ($1, the folder name we matched) we use the text "default" and then add everything from the second capture group to the end like before
=404 means that if neither of those worked return a 404 error
I'm seeing a significant speed improvement by moving this fallback mechanism into the server versus all of the extraneous calls I was forced to do before on the client.

Related

Encoded slash (%2F) in query parameter causes redirection to fail in Angular Application

In my angular application, I need to make GET call to a Tomcat server. This GET call requires query parameters which could contain special characters too like "+", "/", "/+"
GET call is being made from angular controller using $window.open with target as "_blank"
Currently the redirection is getting failed without any encoding.
So, I added encoding in .js file before the GET call is being made by using encodeURIComponent.
Then I added decoding logic using URLDecode.decode in backend java code to decode query parameters.
But still it doesn't work.
It works only if I encode query parameters twice within the .js file using encodeURIComponent twice.
I am trying to find the root cause for double encoding but no luck yet. I would greatly appreciate if anyone could share any inputs.
Made it work by adding a * in path parameter in app.js. Adding a star means that the request will include multiple path parameters separated by /, and so angular will not try to encode / in the request.
Double encoding could also work but then the server side logic has to be modified to decode the request parameters twice and replace %2B2F by %2F

Routing to a directory with a leading period?

Trying to enable letsencrypt with web2py. As part of that I may need to create a route for a url like www.example.com/.well-known/acme-challenge/<some long string>
As I test this I notice that the following route works:
('/\.well-known/acme-challenge/test.html',
'/some_app/static/well-known/acme-challenge/test.html'),
While this almost identical route doesn't:
('/\.well-known/acme-challenge/test.html',
'/some_app/static/.well-known/acme-challenge/test.html'),
The only difference between these two routes is that in the latter one, in the second element of the tuple, .well-known has a leading period while the former route has well-known without a period.
Note I did try escaping the period like \.well-known but it doesn't work either.
Why does the route with the leading period not work? And how can I fix it?
Even though it is a static URL, the path segment immediately after /static/ is interpreted as the controller function and is therefore expected to be a valid Python identifier (the regular expression used to match that part of the path is \w+).
Note, rather than having web2py serve the letsencrypt response, you might consider configuring your web server to return the response directly (e.g., it is easy to configure Nginx to do this).
I haven't come across a direct solution to the leading period issue (as mentioned by Anthony web2py expects a valid python identifier)
However, to solve the specific problem of working with Let's Encrypt I did follow Anthony's suggestion to handle it in the webserver. I am using Apache, in my httpd.conf I added:
LoadModule alias_module modules/mod_alias.so
Alias "/.well-known/" "/var/www/html/somedirectory/.well-known/"
Note Alias will automatically map whatever comes after the url path. Example with the above Alias:
example.com/.well-known/something/hello
will map to
/var/www/html/somedirectory/.well-known/something/hello

How to use $header in routes

I'm creating a route using the Java DSL in Camel.
I'd like to perform a text substitution without creating a new processor or bean.
I have this:
.setHeader(MY_THING,
constant(my_template.replace("{id1}", simple("${header.subs_val}").getText())))
If I don't add 'constant' I get type mismatch errors. If I don't put getText() on the simple() part, I get text mismatch answers. When I run my route, it replaces {id} with the literal ${header.subs_val} instead of fetching my value from the header. Yet if I take the quotes off, I get compile errors; Java doesn't know the ${...} syntax of course.
Deployment takes a few minutes, so experiments are expensive.
So, how can I just do a simple substitution. Nothing I am finding on the web actually seems to work.
EDIT - what is the template? Specifically, a string (it's a URL)
http://this/that/{id1}/another/thing
I've inherited some code, so I am unable to simply to(...) the URL and apply the special .tof() (??) formatting.
Interesting case!
If you place my_template in a header you could use a nested simple expression(Camel 2.9 onwards) like in the example below. I am also setting a value to subs_val for the example, but I suppose your header has already a value in the route.
.setHeader("my_template", constant("http://this/that/{id1}/another/thing"))
.setHeader("subs_val",constant("22"))
.setHeader("MY_THING",simple("${in.header.my_template.replaceAll(\"\\{id1.?\",${in.header.subs_val.toString()})}"))
After this step header MY_THING has the value http://this/that/22/another/thing.
1)In this example I could skip to_String() but I do not know what's the type of your header "subs_val" .
2) I tried first with replaceAll(\"\{id1\"}\") but it didn't work with } Probably this is a bug...Will look at it again. That's why in my regex I used .?
3) When you debug your application inside a processor, where the exchange is available you can use SimpleBuilder to evaluate a simple expression easily in your IDE, without having to restart your app
SimpleBuilder.simple("${in.header.url.replaceAll(\"\\{id1.?\",${in.header.subs_val.toString()})}").evaluate(exchange, String.class);
Hope it helped :)

Do not allow ".xml"/".html"/"index" in URI?

I'm going through Lift's basics in Section 3.2 SiteMap of Simply Lift and one thing struck me.
Using the default SiteMap code, you can ask for, say, info view in three ways:
GET /info,
GET /info.html,
GET /info.xml (why?).
What is more, you can request index view in four different ways:
GET /,
GET /index,
GET /index.html,
GET /index.xml.
How can I limit this behaviour to GET / for directories and GET /info for files?
P.S. All of these return 200 OK:
foursquare.com/,
foursquare.com/index,
foursquare.com/index.html,
foursquare.com/index.xml.
Shouldn't one resource have one URL only?
There are actually more than four ways that it can be parsed. The full list of known suffixes (any of which can be used to access the page) can be found here.
I think the reason for that is that lift can be used to serve any resource, so most are explicitly added by default.
I think you could disable Lift's processing of all extensions by adding this to Boot.scala:
LiftRules.explicitlyParsedSuffixes = Nil
However, I wouldn't recommend that as there may be some side-effects.
Using Req with RestHelper you can specify the suffix explicitly, but I don't know if there is such a construct to do so with Sitemap.
Actually, the code to determine whether Lift should handle the request or not is here. You can see the default extensions in the liftHandled method directly above, but they can all be overridden with LiftRules.liftRequest. Something like:
LiftRules.liftRequest append {
case r => Full(r.path.suffix.trim == "")
}
Should do the trick.
As far as why it works that way, Jason is right that Lift is designed to handle multiple types of dynamic resource.

Load Paths and Ruby C-Extensions

How do you allow a C-extension to use rb_f_require to require a file from outside the ext directory (e.g. requiring lib/foo/foo.rb from ext/foo.so).
Not really sure why this isn't converted into html like the rest of the ruby hacking guide that had been translated, but perhaps some portion of this would be helpful?
http://rhg.rubyforge.org/svn/en/chapter18.txt
Given that rb_f_require appears to do a normal load path search, it would seem it would search out into lib/foo if that is in the search path. However, if you are looking for another foo.rb I would imagine you would have name problems if foo.so appears first. Perhaps using a different name for foo.rb could solve the problem?

Resources