Tám bài đầu dùng requestHandler trần. Từ đây trở đi là Vert.x Web — lớp trên cùng lo định tuyến, thân request, phiên, xác thực. Bài này đo cái giá của lớp đó, và câu trả lời làm tôi bất ngờ.

Router không tốn gì đo được

Câu hỏi tự nhiên khi thêm một lớp trừu tượng: nó ăn bao nhiêu? Đo ở 500 kết nối đồng thời:

Thông lượng p50
requestHandler trần, không Router 89 548 req/s 4,9 ms
Router, 1 handler 98 683 req/s 4,8 ms
Router, 5 handler rỗng + 1 99 108 req/s 4,9 ms
Router, 20 handler rỗng + 1 100 088 req/s 4,8 ms
Router, 50 handler rỗng + 1 99 042 req/s 4,6 ms

Năm mươi handler trong chuỗi cho cùng thông lượng với một handler. Router thậm chí đo ra nhỉnh hơn requestHandler trần — chênh lệch nằm trong nhiễu, nên cách đọc đúng là: Router miễn phí.

Kết quả này đi ngược trực giác "càng nhiều middleware càng chậm". Lý do: mỗi handler chỉ là một lời gọi hàm cộng một lần duyệt danh sách — vài chục nano giây, trong khi bản thân request tốn hàng chục micro giây. Đừng gộp handler lại vì sợ chậm; hãy tách chúng cho dễ đọc.

Quên next(): request biến mất

r.get("/quen").handler(c -> {
    kiemTraQuyen(c);          // xong việc rồi... quên gọi next() và cũng không end()
});
curl hết giờ sau 6 giây, nhận 0 byte

Không phải 500, không phải timeout của server, không có dòng log nào. Request treo cho tới khi client bỏ cuộc, và kết nối vẫn mở phía server.

Đây là lỗi phổ biến nhất với Vert.x Web, và nó đặc biệt hay xảy ra trong nhánh else hoặc trong khối catch — chỗ người ta viết vội. Luật: mọi nhánh của mọi handler phải kết thúc bằng đúng một trong hai việc: next() hoặc gửi response.

Thứ tự route: đăng ký trước thắng

r.get("/thu-tu/*").handler(c -> c.response().end("route-rong"));
r.get("/thu-tu/cu-the").handler(c -> c.response().end("route-cu-the"));
GET /thu-tu/cu-the -> route-rong

Route cụ thể không bao giờ chạy. Vert.x Web duyệt theo thứ tự đăng ký và dừng ở cái khớp đầu tiên gửi response. Khác với một số khung tự sắp xếp theo độ cụ thể, ở đây thứ tự trong mã thứ tự ưu tiên.

Hệ quả: đặt route bắt-tất (/*, /api/*) ở cuối, hoặc dùng next() trong đó nếu nó chỉ làm việc phụ như ghi log.

Ném ngoại lệ thì có failureHandler

r.route().failureHandler(c -> c.response()
        .setStatusCode(c.statusCode() > 0 ? c.statusCode() : 500)
        .end("lỗi: " + c.failure().getMessage()));
GET /nem -> [HTTP 500] lỗi: nem trong handler

Ngoại lệ ném đồng bộ trong handler được Vert.x Web bắt lại và đẩy vào failureHandler. Đây là khác biệt đáng kể so với Futurephần 6, nơi ngoại lệ ném trong onSuccess bị nuốt mất hoàn toàn.

Nhưng nhớ: nó chỉ bắt được ngoại lệ đồng bộ. Lỗi xảy ra trong một callback bất đồng bộ vẫn phải tự gọi c.fail(t).

next() sau khi đã end()

client nhận: [HTTP 200] ok
server ghi log: IllegalStateException: Response has already been written

Client thấy mọi thứ bình thường, server ghi một ngoại lệ. Đây là kiểu lỗi chỉ lộ ra khi ai đó đọc log — và nó thường là dấu hiệu hai handler cùng nghĩ mình chịu trách nhiệm trả lời.

Ba luật

Mỗi nhánh kết thúc bằng next() hoặc response. Không có lựa chọn thứ ba.

Route bắt-tất đặt cuối. Hoặc cho nó next().

Luôn có failureHandler ở cấp router. Không có nó, ngoại lệ đồng bộ thành response rỗng.

Bài sau: BodyHandler — đọc thân request, giới hạn kích thước, và vì sao JSON hỏng lại trả về 500 thay vì 400.

Thử ba mươi giây

# tim handler khong co next() lan khong co response
grep -rn -A3 "\.handler(" src/ | grep -B2 "}" | grep -v "next()\|\.end(\|\.fail(" | head

Không hoàn hảo, nhưng nó khoanh vùng nhanh những handler đáng nghi. Mỗi cái là một chỗ request có thể treo mà không để lại dấu vết nào.