Bài 1
Caddy sắp lại route trước khi chạy
Caddyfile không phải là thứ Caddy chạy. Bộ chuyển đổi đọc nó rồi sinh ra một cấu hình JSON, và
trong lúc đó nó sắp lại thứ tự các khối handle. Lúc có request,
Caddy duyệt danh sách đã sắp và lấy route đầu tiên khớp. Biết thứ tự đó là biết kết quả.
Cấu hình bạn viết không phải cấu hình Caddy chạy
caddy adapt in ra cấu hình JSON đã sinh, nên không phải đoán. Lấy một Caddyfile
gồm năm khối viết theo thứ tự tuỳ ý:
handle /a* { ... }
handle /abcdef* { ... }
handle /abc* { ... }
handle /x/y/z* { ... }
handle /x* { ... }
handle { ... }
Thứ tự route trong JSON mà caddy adapt sinh ra:
0 path /abcdef*
1 path /x/y/z*
2 path /abc*
3 path /a*
4 path /x*
5 (không matcher)
Thứ tự đã đảo hẳn so với file. /abcdef* viết thứ hai nhưng được xét đầu tiên;
/a* viết đầu tiên nhưng tụt xuống thứ tư.
Quy tắc sắp xếp, suy ra từ 24 lần đo
Quy tắc dưới đây không chép từ tài liệu mà suy ra từ 24 Caddyfile chạy qua caddy adapt.
Phần lớn viết thành từng cặp, hai thứ tự ngược nhau, để tách phần do sắp xếp khỏi phần do thứ tự viết.
* ở cuối
Route có độ cụ thể lớn hơn được xét trước. Bằng nhau thì bản không có *
đứng trước. Vẫn bằng nhau thì so chuỗi tăng dần — không phải giữ thứ tự đã viết.
Bốn cặp đo tách bạch từng vế của quy tắc. Mỗi cặp chạy hai lần, đảo thứ tự viết giữa hai lần:
| Hai khối | Độ cụ thể | Thứ tự sau adapt | Cho thấy |
|---|---|---|---|
/abc và /abcd* |
4 và 5 | /abcd* trước |
dài hơn thì thắng, dù bên kia là khớp chính xác |
/abc và /xy* |
4 và 3 | /abc trước |
dấu * không được tính vào độ dài |
/abcd và /abcd* |
5 và 5 | /abcd trước |
hoà thì bản không có * thắng |
/a* và /x* |
2 và 2 | /a* trước |
hoà hoàn toàn thì so chuỗi, không phải thứ tự file |
handle /x* ở dòng trên và handle /a* ở dòng dưới, rồi chạy
caddy adapt: /a* vẫn lên trước. Đo thêm /z* với
/b* và /bb với /az đều cho cùng kết luận —
hoà độ cụ thể thì đường dẫn nào đứng trước theo thứ tự chuỗi sẽ được xét trước. Thứ tự viết
trong file hoàn toàn không tham gia ở mức này.
Route không mang matcher path chặn việc sắp lại
Đây là phần khó đoán thứ hai. Một route mang matcher khác — ví dụ path_regexp —
đứng yên tại chỗ, và việc sắp lại không vượt qua được nó. Cấu hình sau chứng minh:
handle /a* { ... }
@re path_regexp \.gif$
handle @re { ... }
handle /abcdef* { ... }
Nếu sắp xếp là toàn cục thì /abcdef* (độ cụ thể 7) phải nhảy lên trước
/a* (độ cụ thể 2). Nhưng caddy adapt in ra:
0 path /a*
1 path_regexp \.gif$
2 path /abcdef*
3 (không matcher)
Không đổi gì. Cách mô tả khớp với mọi số đo: chia danh sách thành các đoạn liền nhau của
route chỉ có matcher path, sắp xếp trong từng đoạn, còn route khác giữ nguyên vị trí
và cắt đoạn tại đó. Khối handle không có mẫu nào cũng vậy — nó bắt mọi request và
được xét đúng ở chỗ bạn viết, nên đặt nó giữa file là tự chặn hết phần sau.
/img/a.gif:
handle /img/* @gif path_regexp \.gif$
@gif path_regexp \.gif$ handle /img/*
handle @gif handle @gif → không còn, đã viết ở trên
curl /img/a.gif curl /img/a.gif
→ "prefix /img/*" → "regexp .gif$"
Hai khối cùng mẫu bị gộp, không báo lỗi
Viết hai khối handle /ab* trong cùng một site block. caddy adapt
không sinh ra hai route mà sinh ra một, với hai handler nối tiếp nhau bên trong:
"routes": [ { "group": "group2",
"match": [ { "path": ["/ab*"] } ],
"handle": [ { "handler": "subroute", … "body": "P" },
{ "handler": "subroute", … "body": "Q" } ] } ]
Gọi curl /ab/x trả về P — khối viết trước thắng, khối sau thành mã chết.
Caddy khởi động bình thường, không có cảnh báo nào. Việc gộp không cần hai khối đứng cạnh nhau:
thử với thứ tự /ab*, /zz*, /ab* cho kết quả y hệt.
nginx xử lý cùng tình huống theo hướng ngược lại. Với location /ab/ viết hai lần,
nginx -t trả về:
nginx: [emerg] duplicate location "/ab/" in /etc/nginx/conf.d/default.conf:4
nginx: configuration file /etc/nginx/nginx.conf test failed
Đối chiếu với nginx
Chạy phép thử "đổi chỗ hai dòng" trên nginx 1.27.5 với location /img/ và
location ~ \.gif$, viết theo cả hai thứ tự: cả hai lần
/img/a.gif đều trả về khối regex. nginx không quan tâm thứ tự viết giữa một khối
tiền tố và một khối regex, vì nó chạy năm bước cố định và bước regex luôn đứng trước bước quay
lại tiền tố.
| nginx 1.27.5 | Caddy v2.11.4 | |
|---|---|---|
| Cơ chế | năm bước cố định trên danh sách gốc | sắp lại danh sách lúc đọc cấu hình, rồi lấy route khớp đầu tiên |
| Thứ tự viết có tác dụng không | chỉ giữa các khối regex với nhau | có, mỗi khi một route bị một route không phải matcher path chặn lại |
| Hai khối trùng mẫu | từ chối khởi động, duplicate location | gộp im lặng, khối viết trước chạy |
| Xem được thứ tự thật không | không có lệnh nào in ra | caddy adapt in đúng danh sách sẽ chạy |
| Tiền tố chặn regex | có, viết ^~ | không có khái niệm đó; vị trí trong file là công cụ duy nhất |
Phòng thí nghiệm
Sửa Caddyfile và đường dẫn tuỳ ý. 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 24 kết quả caddy adapt và
14 kết quả curl qua Caddy thật — khớp toàn bộ.
Cột trái là số dòng trong file, nên nhìn được route nào đã bị dời đi đâu và khối nào bị gộp.
Các khối handle
Đường dẫn cần thử
Thứ tự sau khi Caddy sắp lại
Tự kiểm tra
Viết handle /api/* ở dòng cuối và handle /api/v1/* ở dòng đầu. Request /api/v1/x vào khối nào?
Khối /api/v1/*. Độ cụ thể của nó là 8, của /api/* là 5, nên nó được xét trước bất kể viết ở đâu — miễn là giữa hai dòng đó không có route mang matcher khác.
Chèn handle (không mẫu) vào giữa file thì sao?
Mọi route viết sau nó trở thành mã chết: khối không mẫu khớp mọi request và được xét đúng ở vị trí đã viết, nên request không bao giờ đi tiếp. Thử trong phòng thí nghiệm — danh sách sẽ cho thấy các route phía dưới không còn được xét tới.
Muốn một path_regexp luôn thắng một tiền tố dài, phải làm gì?
Viết khối regexp lên trên khối tiền tố. Không có công cụ nào như ^~ của nginx; với Caddy, vị trí trong file chính là công cụ đó, vì route regexp không tham gia việc sắp lại.
Hai khối handle /ab* viết hai lần thì Caddy báo gì?
Không báo gì. Hai khối bị gộp thành một route, khối viết trước chạy, khối sau không bao giờ chạy. Đây là chỗ khác hẳn nginx: nginx từ chối khởi động với duplicate location. Phòng thí nghiệm ghi rõ dòng nào bị gộp vào dòng nào.