Home > OS >  Redirecting captured regex group using nginx
Redirecting captured regex group using nginx

Time:05-17

With the following nginx location directive

  location ~* (.*)(\/graphql)$ {
    proxy_pass http://my-backend:80/$2;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_set_header Host $host;
  }

I expect that URLs like https://example.com/anything/graphql is redirected to http://my-backend:80/$2 but that's not the case as nginx is giving me a 404 error whenever I try to visit an URL ending with /graphql. The error will be

[error] 31#31: *1 no resolver defined to resolve my-backend, client: 172.18.0.1, server: localhost, request: "GET /anything/graphql HTTP/2.0", host: "localhost"

CodePudding user response:

I usually define an upstream as I know the IP of my backend. This is a kind of config I have:


upstream graphql_backend {
    server 192.168.10.2:8080; # Your backend server IP and port.
}

server {
    listen 80;
    listen 443 ssl;
    server_name example.com www.example.com;
    root "/var/www/example.com";
    charset utf-8;

    location ~* (.*)(\/graphql)$ {
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host $host;
        proxy_set_header X-Proxy-Secret "asdkjwe623jqmkjwer"; # This should be checked by the backend.
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-Host $http_host;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_pass http://graphql_backend;
    }
    # Enable SSL
    ssl_certificate "/etc/ssl/certs/star.example.com.bundle.crt";
    ssl_certificate_key "/etc/ssl/private/star.example.com.private.key";
    ssl_session_timeout 5m;
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
    ssl_ciphers ALL:!ADH:!EXPORT56:RC4 RSA: HIGH: MEDIUM: LOW: SSLv3: EXP;
    ssl_prefer_server_ciphers on;
}

Or you could add a resolver in the config:

  location ~* (.*)(\/graphql)$ {
    # Use Google as DNS
    resolver 8.8.8.8
    proxy_pass http://my-backend:80/$2;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_set_header Host $host;
  }

More information here: http://www.nginx-discovery.com/2011/05/day-51-proxypass-and-resolver.html

CodePudding user response:

It is a known nginx limitation that you can't specify an URI for proxy_pass inside the regex location. If you'd try to do something like

proxy_pass http://my-backend/graphql;

you've got the following nginx error:

nginx: [emerg] "proxy_pass" cannot have URI part in location given by regular expression, or inside named location, or inside "if" statement, or inside "limit_except" block in ...

You have two options instead. The first one is to use variables to pass whatever you want to your upstream as an URI, either the way you have chosen or, if it is a constant string, specifying your endpoint explicitly like

set $endpoint graphql;
proxy_pass http://my_backend/$endpoint;

However this approach have a drawback you already faced - it is requires a resolver being defined if your upstream is specified via the hostname rather than IP address. That is, the

proxy_pass http://localhost/$endpoint;

configuration line would require a resolver while

proxy_pass http://127.0.0.1/$endpoint;

won't require it. It doesn't matter the $endpoint variable is used only for the URI part. If you wonder what is the need of that resolver, let me quote the most relevant part from the blog post already mentioned:

Linux, POSIX and the like offer only one way to get an IP from a name: gethostbyname. If you take time to read the man page (always a safe thing to do... ;)) you'll realise there is a lot to do to resolve a name: open files, ask NIS or YP what they think about it, ask a DNS server (may be in a few different ways). And all this is synchronous. Now that you are used to the nginx way, you know how bad this is and you don't want to go down the ugly synchronous way. So Igor, faithful to himself, reimplemented a DNS lookup (with an in-memory cache, mind you) just to avoid calling this ugly blocking gethostbyname... And that's why we have this extra resolver directive. Yes, coding the fastest web server comes at a price...

When there are no other way but to use variables with the proxy_pass, using the public DNS like 8.8.8.8 is an option, however setting up your own local one should be much more performant (or you'll have continuous traffic exchange with that 8.8.8.8 host). However there is another option. You can rewrite an URI inside your location block to required one and use the proxy_pass without any additional variables:

location ~ /graphql$ {
    rewrite ^ /graphql break;
    proxy_pass http://my-backend;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_set_header Host $host;
}

For the most of cases this is a more convenient way since using such a configuration you won't need any specified resolver at all.

  • Related