Expose multiple api uri on the same nginx server block

情到浓时终转凉″ 提交于 2021-02-13 17:33:42

问题


Goal

My goal is to setup multiple backend api container exposed on the same nginx server :

  • http://localhost:80/api/account -> call http://account-service:9000/
  • http://localhost:80/api/cart -> call http://cart-service:9000/
  • http://localhost:80/api/order -> call http://order-service:9000/
  • http://localhost:80/api/product -> call http://product-service:9000/
  • ...

My backend container are based on php:7.2-fpm (symfony hosted on every apache container) and they don't have any route called api/${NAME_SERVICE} (i don't want to create some useless parent route in my backends).

So, my request is simple, i want that when i call for example :

  • http://localhost:80/api/account/profile

That my backend account container serve this uri :

  • http://account-service:9000/profile

What i tried so far

  • rewrite (doesn't help with fastcgi params)
  • setting up an upstream server with proxy_pass
  • tweaking fastcgi_param REQUEST_URI (without any success)
  • alias (forbidden access)

Conf

Here is my nginx.conf :

...
server {
        server_name ~.*;
        client_max_body_size 50m;

        location / {
            try_files $uri /index.php$is_args$args;
        }
        # work if i want to serve account-service on http://localhost:80/
        # location ~ ^/index\.php(/|$) {
        #     fastcgi_pass account-service:9000;
        #     fastcgi_buffers 16 16k;
        #     fastcgi_buffer_size 32k;
        #     fastcgi_param SCRIPT_FILENAME /usr/src/app/public/index.php;
        #     include fastcgi_params;
        # }

        # my last attempt with alias
        location ~* ^/api/account {
            alias /;
            index index.php;
            
            location ~ index\.php(/|$) {
                fastcgi_pass account-service:9000;
                fastcgi_buffers 16 16k;
                fastcgi_buffer_size 32k;
                fastcgi_param SCRIPT_FILENAME /usr/src/app/public/index.php;
                fastcgi_intercept_errors on;
                include fastcgi_params;
            }
        }
}
...

docker-compose.yml :

  nginx:
    image: nginx:1.15.3-alpine
    restart: on-failure
    volumes:
      - "./build/nginx/default.conf:/etc/nginx/nginx.conf:ro"
      - "./logs/nginx:/var/log/nginx"
    ports:
      - "80:80"
    depends_on:
      - account-service
      - account-db
      - cart-service
      - cart-db
      - order-service
      - order-db
      - product-service
      - product-db

  account-service:
    env_file:
      - config/account.env
    build: apps/account-service
    restart: on-failure
    expose:
      - "9000"
    volumes:
      - "./apps/account-service:/usr/src/app"
    depends_on:
      - account-db

  cart-service:
     ...

P.S: I known that you can split nginx conf into multiple server blocks that listen on different port/hostname, but that's not what i want to achieve here.


回答1:


What do you mean by tweaking fastcgi_param REQUEST_URI? If you try to set some custom value to REQUEST_URI before you include the fastcgi_params file, value set by fastcgi_params would overwrite any of your tweakings:

fastcgi_pass service:9000;
fastcgi_param REQUEST_URI /some/path;
include fastcgi_params;
# REQUEST_URI passed as the real request URI

However this one would work as expected:

fastcgi_pass service:9000;
include fastcgi_params;
fastcgi_param REQUEST_URI /some/path;
# REQUEST_URI passed as "/some/path"

Trying to change this with rewrite won't work because the REQUEST_URI fastcgi parameter is set to $request_uri internal nginx variable value inside the fastcgi_params file, and that variable doesn't changed by rewrite directive rules, it is an $uri one that does.

Here is the most simple solution that should work:

server {
    ...
    location ~ ^/api(/(?:account|cart|order|product)/.*) {
        # strip "/api" part from the URI and search for the new location block
        rewrite ^ $1 last;
    }

    location /account {
        # strip "/account" part from the URI and continue processing within the current location block
        rewrite ^/account(.*) $1 break;
        # include default fastcgi parameters first
        include fastcgi_params;
        # all our tweakings goes after it
        fastcgi_buffers 16 16k;
        fastcgi_buffer_size 32k;
        # use the rewrited $uri variable instead of the default $request_uri
        # $uri variable does not include query arguments, so add them manually if they exists
        fastcgi_param REQUEST_URI $uri$is_args$args;
        fastcgi_param SCRIPT_FILENAME /usr/src/app/public/index.php;
        fastcgi_intercept_errors on;
        fastcgi_pass account-service:9000;
    }
    location /cart {
        rewrite ^/cart(.*) $1 break;
        ...
        fastcgi_pass cart-service:9000;
    }
    location /order {
        rewrite ^/order(.*) $1 break;
        ...
        fastcgi_pass order-service:9000;
    }
    location /product {
        rewrite ^/product(.*) $1 break;
        ...
        fastcgi_pass product-service:9000;
    }
}

This solution could be greatly optimized using advanced nginx techniques:

server {
    ...
    # This is a very important one!
    # Since we are using variables for backend name, we need a resolver to resolve it at the runtime
    # Docker default internal resolver is 127.0.0.11
    resolver 127.0.0.11;

    location ~ ^/api/(?<api>account|cart|order|product)(?<path>/.*) {
        include fastcgi_params;
        fastcgi_buffers 16 16k;
        fastcgi_buffer_size 32k;
        # note we are using the $path variable here instead of the $uri one
        fastcgi_param REQUEST_URI $path$is_args$args;
        # assuming this path is the same within all the backend services
        fastcgi_param SCRIPT_FILENAME /usr/src/app/public/index.php;
        fastcgi_intercept_errors on;
        # using $api variable as part of backend container name
        fastcgi_pass $api-service:9000;
    }
}

Note that we need a new resolver directive since we are using a variable to specify the backend name. You can read additional details here, and the resolver address for docker taken from this answer.

If your script path vary upon the different API backend containers, you can use an additional map block to get the script path from the $api variable value:

map $api $script {
    account    /usr/src/app/public/index.php;
    cart       /some/other/path;
    ...
}

server {
    ...
    location ~ ^/api/(?<api>account|cart|order|product)(?<path>/.*) {
        ...
        fastcgi_param SCRIPT_FILENAME $script;
        ...
    }
}


来源:https://stackoverflow.com/questions/62968482/expose-multiple-api-uri-on-the-same-nginx-server-block

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!