Nginx - Forwarding a request to the Server-App if 'alias' fails - nginx

For a project I implemented automatic serverside thumbnail-generation. If you request an Image with '_thumb' in its filename the server checks if there already is a thumbnail for the original image, if not it generates it and sends it back. Works fine.
Now I'd like to bring Nginx into the mix to serve the images directly from the harddrive without involving the server-app (the url itself would work fine and return the image if requested from the server-app itself).
The following Block works fine, if the thumbnail already exists. If there is no thumbnail already generated it just responds 404 - unsurprisingly.
How would I modify the following Nginx-Block to make another request to the server in case the thumbnail cant be found to create it?
Request
http://www.proj.com/files/images/234782348234/bunny_thumb.jpg
Nginx-Block
location ~* ^/files/images/(\w+)/.+_thumb\.(jpg|png|gif)$ {
alias /srv/proj/data/uploads/images/temp/$1_thumb.$2;
// No image found? Request it from the server directly on "files/images/234782348234/bunny_thumb.jpg"
}
Actual File-Path
/srv/proj/data/uploads/images/temp/234782348234_thumb.jpg

Sometimes it helps to read the excellent blog of the nginx company.
What you search for is called named location with try_files.
https://www.nginx.com/resources/admin-guide/nginx-web-server/
https://www.nginx.com/resources/admin-guide/serving-static-content/
location / {
try_files $uri $uri/ #backend;
}
location #backend {
proxy_pass http://backend.example.com;
}
For your case I would try the following
location ~* ^/files/images/(\w+)/.+_thumb\.(jpg|png|gif)$ {
try_files /srv/proj/data/uploads/images/temp/$1_thumb.$2 #callapp;
// No image found? Request it from the server directly
// on "files/images/234782348234/bunny_thumb.jpg"
}
location #callapp {
...
}

So, finally had to tackle it again and found a pretty easy solution. RegexMatches stillt won't work in try_files. So I just used "alias" to direct to the actual file-path and defined a 'error_page 404' pointing to the expressapp:
location ~* ^/files/images/(\w+)/.+_thumb\.(jpg|png|gif)$ {
error_page 404 #makethumb
alias /srv/proj/data/uploads/images/temp/$1_thumb.$2;
}
location #makethumb {
proxy_pass http://localhost:3000$uri;
}

Related

Nginx pass_proxy with variables

I'm having trouble making nginx proxy an url with variable to a service within kubernetes.
Url looks like this:
http://localhost/user?username=Dave
I expect this url to take me to a subpage /user, which will read ?username=Dave and then fetch data from a database. However this takes me to the home page of the application(/ instead of /user) and does not read the variable even though url includes /user?username=Dave.
My current nginx config file looks like this:
server {
listen 0.0.0.0:80;
server_name localhost;
location / {
proxy_pass http://${FLASK_APP}:8080/;
}
location /user {
proxy_pass http://${GO_APP}:8000/;
}
}
I have read that location /user will match the url I'm passing. What is wrong with it? Or do I need to add something to proxy_pass http://${GO_APP}:8000/; or location /user?
As noted in the comments, the issue arises because you are using a variable in the proxy_pass target. As also noted in the comments, this question is related. As the answer referencing the docs states:
A special case is using variables in the proxy_pass statement: The
requested URL is not used and you are fully responsible to construct
the target URL yourself.
This means that you either need to use a static proxy_pass target, such as
// note that I added the forward slash
location /user/ {
proxy_pass http://destination:8000/;
}
Or as an alternative, I believe you can do it this way also
location /user/ {
proxy_pass http://${GO_APP}:8000/user$is_args$args;
}

NGINX try_files works for all but one url

I have a simple dashboard for my site. Here is the directive:
location /dashboard {
try_files $uri /dashboard/index.php;
}
It works for all items after /dashboard. For example, /users or /pages - all CRUD operations work as expected.
The index.php file at /dashboard is my "controller". It parses the url and includes and runs scripts from there.
For example: /dashboard/group/edit/123456 works as expected and I get the edit page for group number 123456.
But when I post from that page to /dashboard/group/update, it serves /dashboard/group/index.php
So, in the first example, The edit page is loaded and the url at the top of the screen does not change.
In the second example, NGINX is CHANGING the url so my script cannot get the url parts to do the job.
I thought it may have something to do with POST, but I have other forms that use POST without issue.
In addition, or possibly a clue, try_files is returning /dashboard/group/index.php while the directive should return /dashboard/index.php.
Is there another NGINX file that could have so old code in it that is overwriting this domain's config?
I've been at this a few hours and have run out of ideas. Any thoughts?
* One More Clue *
When I BROWSE to /dashboard/group/update, NGINX shows the page as expected. It is only when I POST to that page that NGNIX sends me to /dashboard/group/index.php.
Again, at the very least, it should be sending me to /dashboard/index.php and NOT /dashboard/group/index.php.
You not send all after /dashboard try this:
location /dashboard {
try_files $uri /dashboard/index.php?$uri&$args;
}
OR
location /dashboard {
try_files $uri /dashboard/index.php?$query_string;
}
Nginx docs: https://nginx.org/en/docs/http/ngx_http_core_module.html#try_files
Instead of
location /dashboard {
try_files $uri /dashboard/index.php;
}
Try
location /dashboard {
index index.php; #adding this may work alone
try_files $uri /dashboard/index.php?$uri;
}
I have concluded that I have a cache problem. The location directive works on all items that I have not yet accessed.
So, my configuration - as described - works as it should.
I just have to figure out how to clear my cache ( which in NOT set up in NGINX that I can see!)
Thank you all who helped!

How to handle 404 error request in vuejs SPA with nginx server

I have set up my vue-cli version 3 SPA so that any requests not found in my routes.js file will default to my 404 view as shown in the official documentation:
Inserted near bottom of routes.js file:
{ // catches 404 errors
path: '*',
name: '404',
component: () => import(/* webpackChunkName: "NotFoundComponent" */ './views/NotFoundComponent.vue'),
},
Inserted into nginx configuration file:
location / {
try_files $uri $uri/ /index.html;
}
This successfully alerts the user that the page they requested doesn't exist.
My Question:
I would like for the error 404 component to return a 404 response header (it current returns the 200 status code) and also log this error to the nginx error.log file. I imagine this is only possible through using nginx configuration. Has anyone achieved this goal?
I noticed that this issue is addressed in the following page in the vue-cli official docs, but it only is concerned with node express servers and not nginx:
https://router.vuejs.org/guide/essentials/history-mode.html#caveat
I think it is similar to Node solution - you should repeat all your routes in nginx config to return 404 status code correctly, the main idea is that you should use "equals" modifier in locations and define error_page to return same index.html file but with 404 status code, example:
server {
listen 80;
server_name localhost;
root /my/dir/with/app
error_page 404 /index.html;
location = / {
try_files $uri $uri/ /index.html;
}
location = /books {
try_files $uri $uri/ /index.html;
}
# example nested page
location = /books/authors {
try_files $uri $uri/ /index.html;
}
# example dynamic route to access book by id
location ~ /books/\d+$ {
try_files $uri $uri/ /index.html;
}
}
Probably this config can be simplified or improved because I am not very good at nginx configuration but it works.
I solved this the easy way by breaking out of the Vue ecosystem else it wont work or would take a lot of effort.
Make the following route in your Vue router:
{
path: '*',
name: 'PageNotFound',
component: PageNotFound
}
The PageNotFound component should have the following code:
<script>
export default {
name: 'PageNotFound',
created() {
window.location.href = "/404/"
}
}
</script>
The nginx config should return a 404 on getting /404/ route:
server {
...
location ~ ^/404/$ {
return 404;
}
...
}
I don't think it would work in a server side rendering environment. In such a case you might need to put the statement containing window.location.href in the mounted method.
For whoever lands into this issue and to save you hours of headaches.
Caveats with above answers
Simply reloading the page with a new URL (e.g. /notfound) does not solve it since that means that a potential spider already received 200.
Simply copying the routes, is a half-solution. That works with URLs that are never going to change and by checking the validity of the URL structure. So for example it can check if the book id in books/123 has the right format, but it can't check if books/123 actually exists in the backend.
Here's two approaches that can tackle the above issues
Have Nginx to make a mirrored subrequest to the backend to check if the resource actually exists. Then always return index.html but with the status from the subrequest's response. This is very tricky with Nginx, since by design it makes it very hard to combine answers.
Have the backend API to return index.html for Accept: text/html. Then Nginx simply needs to forward responses.
The first solution is a pain in the ass for someone not familiar with Nginx. It requires to get Lua with OpenResty and then again you will run into all kinds of quirks with how Nginx works. You end up with a lot of hard-to-read code and that makes it extra hard if you further-more want to introduce caching.
The second solution is easier. The only possible negative is that it means that you can't view the API from the browser if that's something currently you have in place.
nginx.config (when API responds with index.html on Accept: text/html)
location / {
try_files $uri $uri/ #fallback;
}
location #fallback {
rewrite ^(.*) /api$1 break;
proxy_set_header "Accept" "text/html";
proxy_pass http://localhost:8000;
}
In this case, Nginx will try first to serve the file and in case it doesn't find it locally, it will go through the fallback.
In the fallback we rewrite the URI to match what the backend server expects. In this example I prepend api/ to each request. Then I add the header Accept: text/html so that the backend API will respond with the index.html instead of JSON. And lastly we directly give back the response to the client.
This has the below benefits:
It does not rely on Nginx so it can work with any reverse proxy. Most importantly it does not rely on the proxy server to have certain features.
Works even during development without having Nginx running.
Easy to write tests for. You simply have to test your backend API for spitting out index.html when given Accept: text/html for any endpoint.
Does not require you to manually update Nginx configuration with each new endpoint.
Furthermore you can change the config to make Nginx follow redirects internally, and possibly not even having to look at the backend API for URLs that never change.

Nginx URL rewrite

I have a website that has laravel setup to run under http://www.example.com/lara/
So, most Laravel pages have URLs of type http://www.example.com/lara/page/23 OR http://www.example.com/lara/category/23 etc.
Nginx is the underlying server and it has the following configuration to handle these requests:
location /lara/ {
try_files $uri $uri/ /lara/index.php?$query_string;
}
Everything works ok.
Now, I need to setup a special page with the URL http://www.example.com/mystuff/ which actually is handled by
http://www.example.com/lara/category/29
To get this working I added the following rewrite right below location /lara/, that is:
rewrite ^/mystuff/(.*)$ /lara/category/29/$1 last;
Unfortunately, I get a page not found error. Any insights?
Further investigation & research:
1)
location /mystuff/ {
return 301 /lara/index.php/category/29;
}
worked although that's not (browser address bar changes to) what I actually want.
2) Looks like Laravel is not seeing the updated REQUEST_URI.
Try this
server {
...
rewrite ^/lara/(.*)\..*$ $1/mystuff last;
return 403;
...
}

nginx rewrite virtual directory to file

This should be really easy to do but I'm hitting my head on the wall. If I get a request for www.mysite.com/mypath I want to serve the content of www.mysite.com/myotherpath/thisfile.html. How can I do this with an nginx config.
location = /mypath {
try_files /myotherpath/thisfile.html =404;
}
http://nginx.org/r/try_files
http://nginx.org/r/location
Use rewrite directive within proper location block. So for example you have basic location which will handle all requests
location / {
/*your rules here*/
}
You will need to add another block, which will do for you handling of specific path
location /mypath {
rewrite ^/mypath$ /real/path/to/file/thisfile.html;
}
Also for your server to think in that block that thisfile.html is default you can use try thisfile.html directive
It is all well explained on official page Official Nginx RewriteModule page

Resources