Nginx locations configuration - nginx

Let's say I have two upstreams: upstream1 and upstream2.
Also I have API endpount /api/thing.
I want to configure two locations in nginx like this:
location /api/thing/? {
proxy_intercept_errors on;
proxy_pass http://upstream1;
}
location /api/thing/* {
proxy_intercept_errors on;
proxy_pass http://upstream2; }
In locations I used regular regexps (non-nginx configuration syntax):
Requests to /api/thing, /api/thing?param1=val1&param2=val2, etc should be proxied to upstream1
Requests to /api/thing/subthing1?a=b, /api/thing/subthing2/, etc should be proxied to upstream2
Is it possible in terms of nginx configuration?

I would write:
location = /api/thing {
# upstream1
}
location = /api/thing/ {
# upstream1
}
location /api/thing/ {
# upstream2
}

Related

Replace first part of nginx $uri before proxy_pass

I have following location ngix config:
location /bar {
proxy_pass http://bar-api/api;
}
I can test it with curl
$ curl foo.com/bar/test
{"test passed":true}
But IP address of internal server bar-api could change, so I can not specify it directly as above.
I understand, I need to use variables in the location block and specify resolver. Resolver is already specified in http block of nginx config resolver 192.168.0.11 valid=10s;
I'm not sure how to modify location block, so it will work the same way as before.
I tried this:
location /bar {
set $target http://bar-api/api;
proxy_pass $target;
}
But test fails:
$ curl foo.com/bar/test
<getting 404>
Also tried:
location /bar {
set $target bar-api;
proxy_pass http://$target/api;
}
Test still fails:
$ curl foo.com/bar/test
<getting 404>
Probably $uri should be modified with regex. This part /bar/test should be /api/test
So I could use it in location.
location /bar {
set $target bar-api;
<some magic here>
proxy_pass http://$target/$modified_uri;
}
But how to do it?
Use rewrite...break to make the URI change. For example:
location /bar {
set $target bar-api;
rewrite ^/bar(.*)$ /api$1 break;
proxy_pass http://$target;
}
See this document for details.

conditional routing with nginx based on referer

I need to route traffic based on the http request origin. I have two environments and we need to redirect every http request for "/us-en" to Environment1 and others to Environment2 using "$http_referer".
Redirection based on location works.
location ~ /us-en {
proxy_pass Environment1;
proxy_set_header Host Environment1;
}
With '$http_referer' the below option does not work. Request your suggestion on the same.
if ($http_referer ~ ^https?://dev.xyz.com/us-en){
rewrite ^/us-en(/*)$ HOME_PAGE$1 break;
proxy_pass Environment1;
}
Error: nginx: [emerg] "proxy_pass" directive is not allowed here in /opt/nginx/conf/nginx.conf.
Note: By default all the traffic goes to Environment2 as an upstream configuration is present.
# needed if your proxy destination specified with domain name instead of IP address
resolver 8.8.8.8;
location /home/ {
proxy_set_header Host HOST1;
# setup other proxied headers if needed
if ($http_referer ~ ^https?://dev.xyz.com/home) {
rewrite ^/home(/.*)$ HOME_PAGE$1 break;
proxy_pass https://HOST1:8080; # this can be specified by IP address
}
}
With such configuration requests to your_domain.com/home/path/file from dev.xyz.com/home/... (but not from dev.xyz.com/any/other/path!) will be proxied to https://HOST1:8080/HOME_PAGE/path/file. If you specify your proxy destination with domain name instead of IP address, you'll need to specify the additional parameter resolver in your server config. You can use your local name server if you have one, or use something external like Google public DNS (8.8.8.8) or DNS provided for you by your ISP. Anyway such configuration leads to additional DNS lookups, so if you can, specify your proxy destination with IP address.
Update
There is another way to do it with the valid_referers directive:
# needed if your proxy destination specified with domain name instead of IP address
resolver 8.8.8.8;
location /home/ {
proxy_set_header Host HOST1;
# setup other proxied headers if needed
valid_referers example.com/home;
if ($invalid_referer = "") {
rewrite ^/home(/.*)$ HOME_PAGE$1 break;
proxy_pass https://HOST1:8080; # this can be specified by IP address
}
}
Update # 2020.11.11
Besides this answer somehow achieved a score of 5, the given solution has an extremely bad design (it isn't a good approach to have different content handlers in the location and the nested if block; moreover, having an if block with any directive other than from the nginx rewrite module should be avoided if possible) and won't work at all on early nginx versions (I wanna cry when I look at some of my early answers). An original OP question was
The logic should be like below but has some syntax mistakes.
if ($http_origin ~ '^http?://(dev.xyz.com/home)') {
set $flag 'true';
}
if ($flag = 'true') {
location /home/ {
proxy_pass "https://HOST1:8080/HOME PAGE/";
}
}else{
Do Not proxy pass
}
It is unclear what do not proxy pass means. If it means returning some HTTP error (for example, HTTP 403 Forbidden), it can be done with the following configuration:
location /home/ {
if ($http_referer !~ ^https?://dev.xyz.com/home) {
return 403;
}
rewrite ^/home(/.*)$ HOME_PAGE$1 break;
proxy_set_header Host HOST1;
# setup other proxied headers if needed
proxy_pass https://HOST1:8080; # this can be specified by IP address
}
If do not proxy pass means to serve the request locally, the solution is more complex:
map $http_referer $loc {
~^https?://dev.xyz.com/home loc_proxy;
default loc_local;
}
server {
...
location /home/ {
try_files /dev/null #$loc;
}
location #loc_proxy {
rewrite ^/home(/.*)$ HOME_PAGE$1 break;
proxy_set_header Host HOST1;
# setup other proxied headers if needed
proxy_pass https://HOST1:8080;
}
location #loc_local {
rewrite ^/home(/.*)$ HOME_PAGE$1 break;
root /path/to/required/page;
...
}
The try_files /dev/null #the_named_location; trick is taken from this excellent answer.
However now the edited OP's question states for a different requirements, which also could be achieved with the map directive help:
map $http_referer $environment {
~^https?://dev.xyz.com/home Environment1;
default Environment2;
}
server {
...
location /home/ {
rewrite ^/home(/.*)$ HOME_PAGE$1 break;
proxy_set_header Host $environment;
# setup other proxied headers if needed
proxy_pass https://$environment;
}

nginx as rate limiter based on http body

I'm evaluating nginx to act as rate limiter for a multi tenancy REST API system. I need to limit API calls by tenant-id.
For example i want to allow 100 r/s for tenant1 and only 50 r/s for tenant2.
It can be easily achived when there are differant urls like: "me.com/tenant1/api" and "me.com/tenant2/api" (with the location directive).
But, in my case the urls are the same for all tenants "me.com/api" (I can't change this).
To find the tenant-id I need to extract a JSON attribute from the Body of the request, and then check the DB for the real tenant-id.
Is it possible to limit_req with my requirements?
Thank for the help!
I decided to build another service getTenant for parsing the body and extracting the Tenant from the DB. This service is called internally by Nginx.
I'm not sure if that is the best nginx (/openresty) solution, but this is what i came up with:
limit_req_zone t1Limit zone=t1Zone:10m rate=200r/s;
limit_req_zone t2Limit zone=t2Zone:10m rate=90r/s;
server {
location /api{
content_by_lua_block {
ngx.req.read_body();
local reqBody = ngx.req.get_body_data()
local res = ngx.location.capture("/getTenant", {method=ngx.HTTP_POST,body=reqBody});
local tenantId= res.body;
if tenantId== "none" then
ngx.log(ngx.ERR, "Tenant not found!");
ngx.say(tenantId);
else
ngx.req.set_header("x_myTenantId", tenantId)
local res2 = ngx.location.capture("/" .. tenantId .."/doApi", {method=ngx.HTTP_POST,body=reqBody});
if res2.status == ngx.HTTP_OK then
ngx.say(res2.body);
ngx.exit(res2.status);
else
ngx.status = res2.status
ngx.exit(res2.status)
end
end;
}
}
location /getTenant {
internal; #this is not accessible from outside.
proxy_pass http://UpStream1/getCustomer;
proxy_set_header X-Original-URI $request_uri;
}
location /tenant1/doApi {
internal; #this is not accessible from outside.
# Proxy all requests to the AReqUpStream server group
proxy_pass http://UpStream2/doApi;
limit_req zone=tenant1Zone burst=25;
limit_req_log_level notice;
}
location /tenant2/doApi {
internal; #this is not accessible from outside.
# Proxy all requests to the AReqUpStream server group
proxy_pass http://UpStream2/doApi;
limit_req zone=tenant2Zone burst=10 ;#nodelay;
limit_req_status 409;
limit_req_log_level notice;
}
}
Basically, when me.com/api is called, a new subrequest is issued to service /getTenant. The response of that call is used to build another subrequest call to the /tenant[X]/doApi service. That way i can define locations per tenant and provide different rate_limis to each.
Comments on that are more than welcome!

nginx config - pass request from subdomain to path with argument, regex in location

I have a server view.example.com, and I have to take a request like this: view.example.com/1234 - domain + 4 numbers/letters.
What i want to do is proxy_pass the request to my local service at: 192.168.33.10/view/1234
How can I write the nginx config to:
care only about requests which match the regex (4 numbers/letters)
pass the request along to my service.
So far I have:
server {
server_name view.example.com;
listen 80;
location ~ '^/(?<hash>[a-zA-Z0-9\-_]{4})/?$' {
# rewrite - which I should probably use but not sure how
proxy_pass http://192.168.33.10/view/;
}
}
Arek
just checked this one:
server {
server_name view.example.com;
listen 80;
location ~ '^/(?<hash>[a-zA-Z0-9\-_]{4})/?$' {
rewrite ^ /view/$hash break;
proxy_pass http://192.168.33.10;
}
}
and it seems to do the trick - any better solutions ?
Arek

How can I configure Nginx to forward www.myserver.com/XYZ to different servers depending on XYZ?

Suppose I have multiple different Tornado servers on my machine. I would like them to be called depending on the URL. How can I configure Nginx to do this? E.g., I have servers A on localhost:8000 and B on localhost:9000. I would like A to handle requests to www.myserver.com/A and B to handle requests to www.myserver.com/B.
Have you tried something like ...
server {
listen 80;
server_name example.com;
root /path/to/webroot;
location / {
# For requests to www.myserver.com/A
location ~ ^/A {
proxy_pass localhost:8000;
}
# For requests to www.myserver.com/B
location ~ ^/B {
proxy_pass localhost:9000;
}
# Some will skip the "A" or "B" flags ... so handle these
proxy_pass localhost:9000$request_uri;
}
This can be expanded / refined into something like ....
location / {
# For requests to www.myserver.com/A/some/request/string
location ~ ^/A(.+)$ {
proxy_pass localhost:8000$1;
}
# For requests to www.myserver.com/B/some/request/string
location ~ ^/B(.+)$ {
proxy_pass localhost:9000$1;
}
# Some will skip the "A" or "B" flags ... so handle these
proxy_pass localhost:9000$request_uri;
}
A better way perhaps is to catch requests for one server and default the rest to the other ....
location / {
# For requests to www.myserver.com/A/some/request/string
location ~ ^/A(.+)$ {
proxy_pass localhost:8000$1;
}
# Send all other requests to alternate location.
# Handle both well formed and not well formed ones.
location ~ ^/(.)/(.+)$ {
proxy_pass localhost:9000/$1;
}
proxy_pass localhost:9000$request_uri;
}
}
i dont believe its possible, u cant configure the dns requests to folders, if u create a folder /xyz u can create a frameset to open the localhost:9000
But if u really want reach the desired results i advice you to use subdomains.

Resources