I'm trying to use lua to serve 301 redirects directly from nginx instead of having to go through php or other stuffs.
I'm inspired by this article here:
http://www.agileweboperations.com/supporting-millions-of-pretty-url-rewrites-in-nginx-with-lua-and-redis
The idea is that I can save a list of redirects directly to redis, then match and serve them with lua directly within nginx to increase performance.
Since the backend project is Symfony, I have to find a way to tweak the code a bit to suit my need, below is what I have:
Here I try to match the usual mysite.com/this/that requests. I include the lua script to handle the redirection first, if nothing match I let nginx try_files
location / {
include /etc/nginx/include.d/lua_st_redis_rewrites.lua;
try_files $uri #rewriteapp;
}
location #rewriteapp {
rewrite ^(.*)$ /app.php/$1 last;
}
Since I want to support the dev environment, I have to handle url like this as well: mysite.com/app_dev.php/this/that
These urls will not match the location / block so I have to call include lua here again. The problem is that now the links mysite.com/this/that will actually call the lua script twice.
My idea is that I can init a true/false flag in the first call and then use it in the second call to check if the script is already included? At this stage I'm quite confused with the scope of variable however:
# pass the PHP scripts to FastCGI server from upstream phpfcgi
location ~ ^/(app|app_dev)\.php(/|$) {
# # Setup var defaults
# set $no_cache "";
include /etc/nginx/include.d/lua_st_redis_rewrites.lua;
# some more usual code for symfony here
}
Should I use global variables in this case to share data among 2 blocks of lua code? I see the use of global variables is strongly discouraged?
If I include the lua script twice, can I safely assume that variables declared in the script will always be re-declared every time it is called?
Thank you, I'm completely new to this so please forgive my obvious question.
As far as I know you can't include Lua files directly like that. Assuming you are using Openresty you need to use the relevant *_by_lua phase to process the request, in this case rewrite_by_lua.
Different blocks can't access each others globals but you can use the ngx.ctx table which sticks around for the duration of the request.
There's a handy diagram of the openresty phases here.
Related
new on nginx.
We have migrated a site and need to redirect about 800 urls to new site.
It would be faster for nginx to do it through maps, through return or some other method ?
It's not exactly true that map is very fast. It is known that (quoting docs):
Since variables are evaluated only when they are used, the mere declaration even of a large number of “map” variables does not add any extra costs to request processing.
However, in your case, you need a single variable and check it against a lot of strings. Sure it may be still fast, but it won't scale well with the more strings you have to compare. Moreover, although quite negligible, those strings have to be stored in memory.
So instead of potentially slowing down every single request, you might better off coding the redirects in your app's language.
server {
# ...
rewrite ^/article/(\d)/.* /old-format.php?article_id=$1;
location = /old-format.php {
internal;
fastcgi_pass /your/php/socket;
# all the fastcgi directives as usual
}
# ...
}
Inside the /old-format.php, check the $_REQUEST['article_id'] and emit the appropriate redirect.
Why this is better:
Pages conforming to the new URL format, won't suffer evaluation of request URI against 800 URLs; only a regular expression mismatch, which is much faster than the given map, especially so if you use pcre_jit on; directive atop your nginx.conf file
You can (likely) write the logic of redirects in your PHP file in few lines of code (fetching relation between article and category ids from database). If speed of access of old URLs is of any concern, you can apply FastCGI cache to old-format.php location with things like fastcgi_cache_valid 301 24h; (+ define FastCGI cache zone in the http context)
In this case you will need to use map - and if you get an error about map_hash_bucket_size being too small you should increase it (inside the http block) like this - map_hash_bucket_size 256
First create a file /etc/nginx/header-maps.conf like this:
map $request_uri $redirect_uri {
/path-1 /path-1-new;
/path-2 /path-2-new;
/path-3 /path-3-new;
}
Then in nginx.conf or in your domain-specific config file
server {
# other things here...
# if there is a $redirect_uri set for the current URI:
if ($redirect_uri) {
return 301 $redirect_uri;
}
# other things here...
}
map is very fast - so it should not affect performance too much.
What I'm trying to accomplish is to be able to pass in a route such as this:
mysite.com/abc123/file.mp3
In Nginx I want it to read the abc123 and then call a piece of code (don't care what language: php, python, golang, fortran...) and then return the actual key that is needed to load the file.
In my config I have this:
server{
#lots of basic stuff here
location / {
mp4;
mp4_buffer_size 1m;
mp4_max_buffer_size 5m;
root /my_path/;
}
}
This works when I pass in my abc123/file.mp3. It will find the file and play it if that file exists in /my_path/abc123/file.mp3.
What I want is to translate (from a database) abc123 to myKey123 which would live at /my_path/myKey123/file.mp3
So, first, is this even possible?
If so, I'm not sure how to approach this. I know this question could have multiple solutions, but any direction will be appreciated.
The PHP script can issue a redirection using the X-Accel-Redirect header. This is treated by nginx as an internal redirect, so the rewritten URI is not exposed to the user. The documentation is a bit thin on details, hopefully you can find an example using PHP.
If you can ring-fence the rewritten URIs with a unique prefix, e.g. /download/myKey123/file.mp3, you can protect the files from direct access by using the internal directive. See this document for details. In which case, you will not need to obfuscate myKey123.
location /download/ {
internal;
...
}
I'm trying to set it up so that certain URLs will rewrite (not change URL in address bar) to a particular page with identifying parameters.
I would like
site.com/reviews/froot_loops_review.php AND
site.com/reviews/froot_loops_review
to actually show
site.com/review?title=froot_loops
Additionally, I would like to be able to append any other parameters so:
site.com/reviews/froot_loops_review.php?var=1
would show what's at:
site.com/review?title=froot_loops&var=1
I'm having a hard time with this because it's my first time editing nginx conf files this is as far as I've got but it doesn't work (I think because it doesn't make the first match):
location /reviews/ {
rewrite ^/reviews/(.*)_review.php(.*) /review?title=$1 break;
return 403;
}
What am I doing wrong here? What should my next step be? What is a good resource for learning Nginx rewrites?
I am not sure if this is strictly an answer. But there are multiple issues with your current solution.
This is a WordPress site, so there is already a location ~ \.php$ block which is handling any URI that ends with .php. The URI /reviews/froot_loops_review.php will never make it to the location /reviews/ block in your question.
You can use the ^~ modifier on the location directive to force nginx to ignore any matching regular expression location block. See this document for details.
The rewritten URI cannot be processed within the same location block, so break is the wrong suffix to use. You will have more success using last. See this document for details.
But the main problem is WordPress itself. WordPress normally uses the REQUEST_URI (which is the URI as originally presented) to identify the requested page. Which is why we can rewrite everything to /index.php and still get the right page.
Trying to make WordPress use your rewritten URI without an external redirect (which you say you do not want to do) is tricky. You may be better off looking for a WordPress plugin to achieve it - or try to make the pretty permalinks work the way you want.
I am using the following configuration for NGinx currently to test my app :
location / {
# see if the 'id' cookie is set, if yes, pass to that server.
if ($cookie_id){
proxy_pass http://${cookie_id}/$request_uri;
break;
}
# if the cookie isn't set, then send him to somewhere else
proxy_pass http://localhost:99/index.php/setUserCookie;
}
But they say "IFisEvil". Can anyone show me a way how to do the same job without using "if"?
And also, is my usage of "if" is buggy?
There are two reasons why 'if is evil' as far as nginx is concerned. One is that many howtos found on the internet will directly translate htaccess rewrite rules into a series of ifs, when separate servers or locations would be a better choice. Secondly, nginx's if statement doesn't behave the way most people expect it to. It acts more like a nested location, and some settings don't inherit as you would expect. Its behavior is explained here.
That said, checking things like cookies must be done with ifs. Just be sure you read and understand how ifs work (especially regarding directive inheritance) and you should be ok.
You may want to rethink blindly proxying to whatever host is set in the cookie. Perhaps combine the cookie with a map to limit the backends.
EDIT: If you use names instead of ip addresses in the id cookie, you'll also need a resolver defined so nginx can look up the address of the backend. Also, your default proxy_pass will append the request onto the end of the setUserCookie. If you want to proxy to exactly that url, you replace that default proxy_pass with:
rewrite ^ /index.php/setUserCookie break;
proxy_pass http://localhost:99;
We just moved to a new site, and want to redirect old links where necessary - however, some still work. For instance,
/holidays/sku.html
still works, while
/holidays/christmas/
no longer works. I'd like to be able to allow the site to attempt to serve a page, and when a 404 is reached, THEN try to pass it through a series of regex redirects, that may look like:
location ~* /holidays/(.*)+$ { set $args ""; rewrite ^ /holidays.html?r=1 redirect; }
I'm using a ~* location directive instead of doing a direct rewrite because we're moving from a Windows-based ASPX site to Magento with php-fpm behind nginx, so we suddenly have to worry about case sensitivity.
Without using nested location directives (which are actively discouraged by nginx documentation) with an #handler of some sort, what's the best way to allow nginx to attempt to serve the page first, THEN pass it across redirects if it fails?
Thanks!
http://wiki.nginx.org/NginxHttpCoreModule#try_files