Node reverse proxy basic routes issue - node.js

I have node-reverse-proxy set up like this:
var options = {
pathnameOnly: true,
router: {
'/myapp': '127.0.0.1:9000',
}
}
httpProxy.createServer(options).listen(8000);
The webapp at the root of 9000 has an index.html file with a stylesheet link like this:
<link rel="stylesheet" href="styles/blue.css">
When I hit localhost:9000 directly the html is loaded and the css is found. Then I hit it through the reverse proxy at localhost:8000/myapp, however I get an Error 404 because localhost:9000/styles/blue.css is not found, because the file is served apparently at localhost:9000/myapp/styles/blue.css.
The html of my app of course doesn't know about the reverse proxy so I can't fix this in the index.html. So I guess I'm missing something basic about the setup of the proxy??

I don't know if you've found a workaround for this by now, but I had the exact same problem and think I've worked it out.
When you hit the target endpoint directly localhost:9000 the path is sent through as /, ie just the root. Then when it requests the css etc, it resolves those as expected, from the root.
When you call via the proxy localhost:8000/myapp it routes /myapp to the target and then passes the target service / as the path, at which point it behaves exactly as above.
The problem here is actually at the browser. Because the path is specified as /myapp, it assumes that "myapp" is a filename. Then when it requests the css etc, it peels that back to the last slash and uses that as the base for all relative hrefs, which is why it then asks for /styles/blue.css and not /myapp/styles/blue.css. If you try browsing to localhost:8000/myapp/ - note the trailing slash - then it works because the browser now treats /myapp/ as a component of the path.
Obviously, you can't dictate that users must add the trailing slash. And you can't easily do much about it in the target service, if only because it doesn't see any difference, and anyway you don't want to have to repeat the same solution in every end service. So the answer seems to be to intercept it in the proxy.
This example works, though there might be a more robust way of implementing it. It looks out for cases where the last part of the path is probably a directory component, in which case the request is redirected to force the browser to re-issue it with a trailing slash. It makes that decision based on it not looking like a filename.ext or already having a trailing slash:
httpProxy.createServer(function(req, res, proxy) {
var parsed = url.parse(req.url);
if(!parsed.pathname.match(/(\w+\.\w+|\/)$/)) {
parsed.protocol = 'http';
parsed.host = req.headers.host;
parsed.pathname += '/';
res.writeHead(301, {Location: url.format(parsed)});
res.end();
} else {
proxy.proxyRequest(req, res);
}
}, options);

Related

Remove additional slash from URL in Asp.Net Core

I have a web-site, hosted on Azure App Service. What I'd like to do is to redirect traffic, such that an address like:
https://mysitecom//test//test2
Or:
https://mysitecom/test//test2
Would redirect to the same location:
https://mysitecom/test/test2
So far, I've tried adding some middleware to deal with this; for example:
app.Use(next => context =>
{
if (context.Request.Path.Value.Contains("//"))
{
context.Response.Redirect(context.Request.Path.Value.Replace("//", "/"), true);
return;
}
});
I've also tried:
var options = new RewriteOptions()
.AddRedirect("redirect-rule/^(.*)//(.*)$", "redirected/$1/%2");
app.UseRewriter(options);
Both of these work locally to an extent; however, it appears that running under Kestrel the slashes are automatically reduced to a single one. When this is deployed to Azure App Service, the following happens:
The slashes remain (e.g. https://mysitecom//test//test2)
The correct page is located
The middleware sees only a single slash
Please can someone explain why the middleware (or the redirect) is only ever seeing a single slash when this is deployed to Azure?
(SO wouldn't allow me to use an actual URL in the post)
If your site runs on a Windows AppService, it runs in IIS. You are encountering an IIS feature (The "URL Rewrite Module", I think). If you run the same site on Linux, it will behave differently.
If you want to detect the "original" URL, you can try something like this:
// => /test/test2
var path1 = Request.Path
// => //test//test2
var path2 = HttpContext.Features.Get<IServerVariablesFeature>()["UNENCODED_URL"]
There are multiple sources for this behavior, though I am not sure how you could turn it off if you wanted to.
Remove multiple forward slashes
https://www.ctrl.blog/entry/relative-double-slashes-url.html
http://www.timacheson.com/Blog/2010/jun/prevent_multiple_slashes_in_url
It should work in .NET Core
var options = new RewriteOptions()
.AddRedirect("(.*)/$", "$1", 301 /* Moved Permanently */);
app.UseRewriter(options);

using relative URLs in nodejs express

I am using express routing like:
router.get('/test', function(req, res, next) {
if (user logged in)
res.render('test');
else res.redirect('login')
I'm rendering an EJS template file that has CSS links in it and URLs that are
relative to this.
So you might see stuff like this in the test.ejs template:
<link href = "../stylesheets/bootstrap.min.css" rel = "stylesheet">
blee
The problem I encounter is that this route handler accepts URLs that look like
/test and /test/ - This second URL causes my relative URLs to form incorrect absolute URLs.
So if the URLs in my app (when deployed to production using Apache to forward to myapp) are:
example.com/myapp/test
I expect that the CSS be found at
example.com/myapp/stylesheets/bootstrap.min.css
and the blee URL found at
example.com/myapp/blee
and the redirect should go to
example.com/myapp/login
This works as long as the URL coming into the handler is /test
If, however, it is /test/ , the handler accepts it BUT the relative URLs formed are all incorrect.
My dev environment is different from my deploy environment so I can't code full URLs to things because its a longer path before /test. Relative URLS just make better sense.
I've been told not to worry about the URL /test/ coming in as long as my app doesn't create it. But sometimes a user types something into the location box and it sucks that if they type this, it lands inside this handler and generates a broken page.

Expose routes on different domains

I am struggling with something that doesn't look that hard : let's say I have 2 urls to access my server :
http://localhost:80/
and an external url
http://domain.com/internal/
Is there a way to do add a basepath internal if the forwarded host is equal to the external url host?
Something like :
app.use(function(req, res, next) {
if (req.headers['x-forwarded-host'] === 'domain.com') {
app.use('/internal', routes);
} else {
next();
}
})
There wont be any direct method as in a shortcut to work your way around for personal use cases.
I suggest this simple method though. Let's take example of app.get('/xyz') route.
This can be accessed locally via http://locahost:80/xyz or yourdomain.com/xyz via any application not hosted locally (Unless you make a call using your domainname in your own application).
Add a header element with every request when the call is internal.
Now, whenever our/xyz route is called check for that header element using a if condition and if the request is made internally you'll have that header element there and then you can simply use either res.redirect or any other method that you find useful (Exporting function in current route or anything else you find easy and needful).

Express static serving wrong path

I am messing around with express.js and have built some basic functionality but am having issues with express static serving from the wrong place if the URL is longer than one directory from root. See the examples below.
I am using the normal documented approach to using static.
app.use(express.static(__dirname + '/public'));
And have set up a couple of routes. eg.
app.get('/signup', function(req, res) {
res.render('signup.ejs');
});
With a 404 catch at the end of the chain.
app.get('*', function(req, res){
res.status(404).render('404');
});
If I hit page such as localhost:3000 or localhost:3000/login which are defined routes, all is well. Even if I hit an undefined route of localhost:3000/foo, I get the 404 rendered correctly with all images present.
However if I go one further and do something like localhost:3000/login/foo all the images are missing and I will get an error in the browsers console with the following address.
http://localhost:3000/login/img/site-brand.png
This happens the same on routes defined with more than one directory too.
I interpreted the docs on the express website that regardless of what was calling for the static image it would be served from the public directory in root, which contains a js, img, and css directories.
My questions are, what have I misinterpreted? and how do I get express to always serve relative to root?
I wrote the whole question then realised that when I had set up the src="" tags in my .ejs files I had used relative paths, not absolute. Rather than delete the question I decided to answer it and post it for others.
So instead of using src="img/my-image.png" it should be src="/img/my-image.png" The leading slash indicates that the request is relative to root not the path that is making the request.
Basic web development stuff there. I should have seen it first time out but its late, and I am cramming my head full of new frameworks which is in turn squeezing the more trivial stuff out of my small brain.

serve a 404 when a locale file is missing in express

I'm using DaftMonk's generator-angular-fullstack, which has been great, except for one small problem I'm having...
I'm using i18next to handle localization, and the way the library works is it attempts to load a locale file from your specified path for the user's given localization setting, ie: /locales/en-US/app.json -- if that doesn't exist, it tries /locales/en/app.json (and in my case finds the file here).
The problem is the way the generator works is it has a route catchall which redirects all 404s to the index.html (the entry point for an angular app). This allows dynamic routing for the single page app.
When this happens, i18next chokes because it was expecting either a 404 or a json locale file, but instead got the index.html page.
I need a way to exclude the /locales path from being handled by this wildcard catch all and serve a 404 rather then the index.html page if a file is missing.
Here is what the express wildcard does:
//is there anyway to write an exclusion to this `/*` wildcard?
app.route('/*')
.get(middleware.setUserCookie, index.index);
/**
* Send our single page app
*/
exports.index = function (req, res) {
res.render('index', {
name: pkg.name,
version: pkg.version,
env: config.env
});
};
//in dev mode
app.use(express.static(path.join(config.root, '.tmp')));
app.use(express.static(path.join(config.root, 'app')));
//in production mode
app.use(express.static(path.join(config.root, 'public')));
How can I write a rule that will redirect /locales/* to serve a 404 if the file isn't found, rather than redirect to index.html?
What about
if (/\/locales\//.test(req.originalUrl) {
send404function()
}
Maybe it's not app.use(), but hopefully you wouldn't have to do this too much.

Resources