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:
-
Không có phần đường dẫn (chỉ
scheme://host:port): request URI được giữ nguyên và gửi thẳng tới backend. -
Có phần đường dẫn, kể cả khi phần đó chỉ là một dấu
/: phần request URI khớp với tiền tố củalocationbị thay bằng phần đường dẫn đó.
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; }
}
| location | proxy_pass | Gọi | Backend 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 |
/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.
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.