Rewrite query string to path params - nginx

I have the following configuration of nginx that hosts my image service:
upstream thumbor {
server localhost:8888;
}
server {
listen 80;
server_name my.imageserver.com;
client_max_body_size 10M;
rewrite_log on;
location ~ /images {
if ($arg_width="10"){
rewrite ^/images(/.*)$ /unsafe/$1 last;
}
rewrite ^/images(/.*)$ /unsafe/$1 last;
}
location ~ /unsafe {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header HOST $http_host;
proxy_set_header X-NginX-Proxy true;
proxy_pass http://thumbor;
proxy_redirect off;
}
location = /favicon.ico {
return 204;
access_log off;
log_not_found off;
}
}
I am trying to rewrite the following urls:
from
http://my.imageserver.com/images/Jenesis/EmbeddedImage/image/jpeg/jpeg/9f5d124d-068d-43a4-92c0-1c044584c54a.jpeg
to
http://my.imageserver.com/unsafe/Jenesis/EmbeddedImage/image/jpeg/jpeg/9f5d124d-068d-43a4-92c0-1c044584c54a.jpeg
which is quite easy, the problem begins when I want to allow query string that should go to the path of the url like so:
from
http://my.imageserver.com/images/Jenesis/EmbeddedImage/image/jpeg/jpeg/9f5d124d-068d-43a4-92c0-1c044584c54a.jpeg?width=150&height=200&mode=smart
to
http://my.imageserver.com/unsafe/150x200/smart/Jenesis/EmbeddedImage/image/jpeg/jpeg/9f5d124d-068d-43a4-92c0-1c044584c54a.jpeg
Also it will be better if the order of the query strings won't matter.
I tried using:
$arg_width but it didn't seem to work.
Using nginx 1.6.1 on ubuntu.
Help would be much much appreciated.

Working with query arguments is always hard (this is also true with Apache).
But in your example when you do:
location ~ /images {
if ($arg_width="10"){
rewrite ^/images(/.*)$ /unsafe/$1 last;
}
rewrite ^/images(/.*)$ /unsafe/$1 last;
}
I do not see any difference between the 2 rewrites... so that's maybe why it is not working.
Anyway you could maybe try something like that (based on this thread):
location ^~ /images {
# get there only if we have a query string
if ($is_args) {
set $width "";
set $height "";
if ($args ~* "(?:^|&)width=([^&]+)") {
set $width $1;
}
if ($args ~* "(?:^|&)height=([^&]+)") {
set $height $1;
}
# string concatenation using sort of bash syntax
set $dim "${width}x${height}";
# maybe we should add a control here on $dim !='x'...
# the ? here prevent query string from being appended
rewrite ^/images(/.*)$ /unsafe/$dim/$1? last;
}
rewrite ^/images(/.*)$ /unsafe/$1 last;
}
location ~ /unsafe {
(...)

You can use the arg_name parameter, and you can get rid of location block:
rewrite ^/images/(.*)$ /unsafe/$1;
# WARNING: Here we suppose that when we have a "mode" parameter, we have
# width and height paremeters too
if ($arg_mode) {
rewrite ^/unsafe/(.*)$ /images/unsafe/smart/$arg_mode/${arg_width}x${arg_height}/$1 last;
}

You need as scripting option that can evaluate the input and construct the rewrite URL. I use the third party Nginx Lua Module for such logic.
You will need to compile the module into Nginx after installing Lua or preferably, LuaJit on your machine.
Alternatively, you can install Openresty which is Nginx bundled with a few modules that completely transforms the Nginx into a full web application server. Openresty will take care of Lua/LuaJit dependencies during installation.
If you have this in place, this should do the job for you:
upstream thumbor {
server localhost:8888;
}
server {
[...]
location ~ ^/images {
rewrite_by_lua '
-- Use local variables to limit their scope to this location block
local i, j, key, val, args, dimension, modetype, tempheight, replacement
-- We only go ahead if there are request arguments to start with
if ngx.var.is_args then
-- Get the request arguments
-- These are loaded into a Lua table of the form, { a = 1, b = 2, c = 3 }
args = ngx.req.get_uri_args()
-- Loop through the args table and build our replacement string
for key, val in pairs(args) do
if key == "width" then
if tempheight then
dimension = val .. "x" .. tempheight
else
dimension = val
end
elseif key == "height" then
if dimension then
dimension = dimension .. "x" .. val
else
tempheight = val
end
elseif key == "mode" then
modetype = val
end
end
-- Construct the replacement string.
replacement = "/unsafe/" .. dimension .. "/" .. modetype .. "/"
-- Replace "/images/" in the request url with the replacement string.
local newuri, n, err = ngx.re.sub(ngx.unescape_uri(ngx.var.request_uri), "/images/", replacement, "io")
-- If there is a new string, then redirect to the new URL
if newuri then
return ngx.redirect(newuri, 301)
end
end
';
}
location ~ /unsafe {
[...]
proxy_pass http://thumbor;
}
[...]
}

Related

NGINX rewrite args without an IF?

I would like to rewrite legacy links using a query parameter type of URL to a new style of URL.
Ex.
example.com/page?id=1 -> example.com/page/1
example.com/otherpage?id=1 -> example.com/otherpage/1
Currently I have the following configuration using the evil if.
if ($args ~* "id=(.*)") {
set $w1 $1;
rewrite .* $scheme://$host/page/$w1? permanent;
}
Note: I am using CloudFront, and relying on the host header above.
If the above is in a server block, with no other location block - would this qualify as a non-evil use of if in NGINX config? Also, the above only supported /page/. Any better ideas for making that portion work for otherpage and other pages?
I have seen a few other ideas discussing using a map, but I'm not quite sure how to bring it all together? I was thinking something along the lines of:
map $args_id ?? {
default ?
??
}
...
server {
...
???
}
UPDATE:
Based on the Answer from #Ivan, this was my final solution:
server {
listen 80;
root /usr/share/nginx/html;
index index.html index.htm;
# Handle legacy requests
if ($args ~* "id=(.*)") {
set $w1 $1;
rewrite ^ $scheme://$host$uri/$w1? permanent;
}
}
Your if construction isn't evil. You can use something like
rewrite ^ $scheme://$host$uri/$w1? permanent;
for any page. More complex example if you want to process both example.com/page?id=1 and example.com/page/?id=1:
map $uri $maybe_slash {
~/$ "";
default "/";
}
...
server {
...
rewrite ^ $scheme://$host$uri$maybe_slash$w1? permanent;
...
}

replace part of request_uri before passing it to proxy_pass

i'm trying to replace specific part or request_uri using rewrite, but it won't work for some reason
example url: http://example.com:3000/?soft=55191&src1=changethis&src2=HOME&type=0&id=7700458
server {
server_name example.com;
listen 3000;
location / {
resolver 8.8.8.8;
rewrite ^(?<=&src1=)(.*)(?=&src2)$ changewiththis$1 break;
proxy_pass http://example2.com;
}
}
so the Goal here is to replace the exact string between 'src1=' and '&src2' so it can be passed to proxy_pass with the changed string
The location and rewrite directives use a normalised URI which does not include the query string (anything from the ? onwards).
To manipulate the query string, you will need to look at the $request_uri or $args variables, or the individual parameters using the $arg_ family of variables (e.g. $arg_src1).
The simplest solution may be to use a map directive to manipulate $request_uri before passing the new value upstream.
For example:
map $request_uri $changethis {
default $request_uri;
~(?<prefix>.*[?](|.*&)src1)=[^&]*(?<suffix>.*)$ $prefix=newvalue$suffix;
}
server {
...
location / {
resolver ...;
proxy_pass http://example.com$changethis;
}
}
See this document for details.

How to write nginx rules/regexes to match empty non empty path

I want to have an nginx rule that will proxy requests with empty path / to a back end server, and another rule that match non empty paths, ex. http://mysite/x/y/z
The following two rules do not do this, the second one is catching all:
# empty path
location ^/?$ {
proxy_pass http://127.0.0.1:8000;
}
location / {
expires -1;
alias /var/static-site/;
}
I have tried /.*/ for the second rule, without success...
Use the "=" modifier to process an exact match on "/":
location = / {
proxy_pass http://127.0.0.1:8000;
}
location / {
expires -1;
alias /var/static-site/;
}

Nginx proxy pass and url rewriting

How to trig this rule only when I have GET parameters(query string) in url,
otherwise I will match on an alias.
location ~^/static/photos/.* {
rewrite ^/static/photos/(.*)$ /DynamicPhotoQualitySwitch/photos/$1 break;
expires 7d;
proxy_pass http://foofoofoo.com;
include /etc/nginx/proxy.conf;
}
The 1st way that I know of is using a regex against the $args parameter like so:
if ($args ~ "^(\w+)=") {
Or the 2nd way is to use the convenient $is_args like so:
if ($is_args != "") {
Remember that in both styles you need to put a space between the if and the opening parenthesis; "if (" not "if(" as well as a space after the closing parenthesis and the opening brace; ") {" rather than "){".
Full example using the 1st style above, nginx.conf:
location ~^/static/photos/.* {
include /etc/nginx/proxy.conf;
if ($args ~ "^(\w+)=") {
rewrite ^/static/photos/(.*)$ /DynamicPhotoQualitySwitch/photos/$1 break;
expires 7d;
proxy_pass http://foofoofoo.com;
}
}
Full example using the 2nd style above, nginx.conf:
location ~^/static/photos/.* {
include /etc/nginx/proxy.conf;
if ($is_args != "") {
rewrite ^/static/photos/(.*)$ /DynamicPhotoQualitySwitch/photos/$1 break;
expires 7d;
proxy_pass http://foofoofoo.com;
}
}
Note that the proxy.conf include goes outside of the if statement.
Version:
[nginx#hip1 ~]$ nginx -v
nginx version: nginx/1.2.6
And some info on the $args and $is_args variables:
http://nginx.org/en/docs/http/ngx_http_core_module.html
Reading the docs is always useful, I just discovered that $query_string is the same as $args, so where I have $args above, you could also use $query_string according to the docs.
IMPORTANT
It is important to note however, that If can be Evil!
And therefore either test thoroughly or use the recommendation provided in the link above to change the URL inside location statement in a way similar to the example provided there, something like:
location ~^/static/photos/.* {
error_page 418 = #dynamicphotos;
recursive_error_pages on;
if ($is_args != "") {
return 418;
}
# Your default, if no query parameters exist:
...
}
location #dynamicphotos {
# If query parameters are present:
rewrite ^/static/photos/(.*)$ /DynamicPhotoQualitySwitch/photos/$1 break;
expires 7d;
include /etc/nginx/proxy.conf;
proxy_pass http://foofoofoo.com;
}

Finding the length of a strength in an nginx.conf file

I am having a problem with my $memcached_keys being too long in my .conf file for nginx. I am using the memcached module but some of my urls are too long. I am in the process of trying to user MD5 hashes of the urls instead but in the meantime I was just wondering if there was a way I could check on the length of a string stored in a variable.
so:
set $memcached_key "byp-$uri";
if ($args) {
set $memcached_key "byp-$uri?$args";
}
if (len($memcache_key) < 250) {
memcached_pass 127.0.0.1:11211;
error_page 404 = #cache_miss;
error_page 502 = #cache_miss;
}
else {
pass to #cache_miss;
}
Old question but ....
To do this sort of thing, you need a scripting setup such as the Lua Module:
location / {
set_by_lua $memcached_key '
if not ngx.var.args then
return "byp-" .. ngx.var.uri
else
return "byp-" .. ngx.var.uri .. "?" .. ngx.var.args
end
';
content_by_lua '
local string = string;
if string.len($memcached_key) < 250 then
ngx.exec("/memcached");
else
ngx.exec("/cache_miss");
end
';
}
location /memcached {
internal;
memcached_pass 127.0.0.1:11211;
error_page 404 = /cache_miss;
error_page 502 = /cache_miss;
}
location /cache_miss {
internal;
...
}
Suggest "internal" locations instead of named locations due to a few quirks with the latter but named locations can be used as well.
I'm not positive, but I don't think it can be done within the nginx config language. I suspect it would be documented here if it existed, and I don't see anything.

Resources