Bài 2

proxy_pass và dấu gạch chéo

proxy_pass quyết định đường dẫn mà backend nhận được. Bài này đo bốn cách viết trên cùng một request để xem mỗi cách cho ra đường dẫn nào. Phần khó nằm ở chỗ một dấu gạch chéo ở cuối làm đổi hẳn quy tắc ghép.

Quy tắc

nginx phân biệt hai trường hợp, dựa trên việc proxy_pass có phần đường dẫn hay không:

Phép thay ở trường hợp thứ hai là thay chuỗi thuần. nginx không chèn thêm dấu phân cách, cũng không chuẩn hoá lại đường dẫn sau khi ghép.

Bốn cách viết, đo trên nginx thật

Bộ đo gồm hai server trong cùng một container nginx:1.27-alpine. Server sau đóng vai backend và trả về đúng $request_uri mà nó nhận, nên không phải suy đoán.

server {
    listen 8080;
    location / { default_type text/plain; return 200 "backend nhan: $request_uri\n"; }
}

server {
    listen 80;
    location /a/ { proxy_pass http://127.0.0.1:8080; }
    location /b/ { proxy_pass http://127.0.0.1:8080/; }
    location /c/ { proxy_pass http://127.0.0.1:8080/goc/; }
    location /d/ { proxy_pass http://127.0.0.1:8080/goc; }
}
locationproxy_passGọiBackend nhận
/a/:8080/a/x/y/a/x/y
/b/:8080//b/x/y/x/y
/c/:8080/goc//c/x/y/goc/x/y
/d/:8080/goc/d/x/y/gocx/y
Dòng cuối là lỗi hay gặp nhất /goc không có dấu gạch chéo cuối, còn /d/ thì có. Phần còn lại của request là x/y, và nginx nối thẳng /goc với x/y thành /gocx/y. Backend nhận một đường dẫn không tồn tại, thường trả 404, và log của nginx không có gì bất thường để lần ra.
Cách ghi nhớ khớp với số đo Nếu location kết thúc bằng / thì phần đường dẫn trong proxy_pass cũng nên kết thúc bằng /. Ba dòng đầu trong bảng đều theo quy ước đó và đều cho kết quả đọc được; dòng thứ tư phá quy ước và cho ra đoạn nối liền.

Phòng thí nghiệm

Đổi cả ba ô. Thuật toán chạy trên trình duyệt là bản viết lại quy tắc ở trên, đã đối chiếu với nginx 1.27.5 trên đúng bốn dòng của bảng, khớp 4/4.

Ghép đường dẫn gửi tới backend

Đổi ba ô bất kỳ.

Bốn cách viết đã đo:

Khi location là regex

Quy tắc thay tiền tố chỉ áp dụng cho location dạng tiền tố. Với location ~ …, nginx không biết phần nào của URI là tiền tố để thay, nên proxy_pass trong khối regex không được phép có phần đường dẫn. Viết proxy_pass http://backend/api/ trong một khối regex thì nginx từ chối khởi động:

[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

Trong khối regex, cách làm thay thế là gán $uri bằng rewrite trước, rồi proxy_pass tới gốc không kèm đường dẫn.

Tự kiểm tra

location /api/ với proxy_pass http://be:3000/. Request /api/users/7 tới backend dưới dạng nào? /users/7. proxy_pass có phần đường dẫn là /, nên tiền tố /api/ bị thay bằng /, còn lại users/7.
Bỏ dấu gạch chéo cuối thành proxy_pass http://be:3000 thì đổi gì? Không còn phần đường dẫn, nên request URI được giữ nguyên: backend nhận /api/users/7. Đây là điều bạn muốn khi backend cũng phục vụ dưới tiền tố /api/.
Vì sao proxy_pass http://be:3000/v1 với location /api/ là một cấu hình đáng ngờ? Request /api/users sẽ tới backend thành /v1users. Hai chuỗi nối liền vì /v1 không kết thúc bằng dấu gạch chéo. Thử ngay trong phòng thí nghiệm phía trên.