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.

Độ cụ thể = độ dài đường dẫn sau khi bỏ dấu * ở 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 adaptCho 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
Dòng cuối là chỗ dễ đoán sai nhất Viết 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.

Đổi chỗ hai dòng là đổi kết quả Hai Caddyfile dưới đây chỉ khác nhau ở thứ tự hai khối, và cho hai câu trả lời khác nhau cho cùng một request /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.5Caddy v2.11.4
Cơ chếnăm bước cố định trên danh sách gốcsắ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ôngchỉ giữa các khối regex với nhaucó, 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ẫutừ chối khởi động, duplicate locationgộp im lặng, khối viết trước chạy
Xem được thứ tự thật khôngkhông có lệnh nào in racaddy adapt in đúng danh sách sẽ chạy
Tiền tố chặn regexcó, 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.