Phần trước đo Router. Bài này về thứ đi kèm mọi API thật: đọc thân request, giới hạn nó, và kiểm tra nó — ba việc mà Vert.x Web làm khác các khung quen thuộc ở một chỗ quan trọng.

Thân request không tự có

Vert.x đọc request theo luồng. Thân chỉ được gom lại nếu bạn bảo nó gom:

POST /khong-body  -> body = null      (không có BodyHandler)
POST /sau         -> body = null      (BodyHandler đăng ký SAU route)
POST /dung        -> body = {"ten":"An"}

Dòng giữa là cái bẫy thật. Đăng ký BodyHandler sau route cần nó thì không có lỗi, không có cảnh báo — chỉ là body luôn null. Và vì phần trước đo được rằng route chạy theo đúng thứ tự đăng ký, nên đây là hệ quả trực tiếp chứ không phải một quy tắc riêng cần nhớ.

// dat o dau, truoc moi route can doc than
r.route().handler(BodyHandler.create());

Đặt nó ngay dòng đầu tiên sau khi tạo Router, trừ khi bạn có lý do rõ ràng để không gom thân của một số route — ví dụ endpoint nhận tệp lớn mà bạn muốn xử lý theo luồng.

Giới hạn kích thước

BodyHandler.create().setBodyLimit(1024);
gửi   501 byte -> [HTTP 200] nhận 501 byte
gửi 5 001 byte -> [HTTP 413]

Vert.x trả 413 Payload Too Largecắt kết nối trước khi đọc hết — đó là điều bạn muốn, vì nó có nghĩa là một client gửi 10 GB không làm bộ nhớ máy chủ phình lên.

Một chi tiết đáng biết: trong failureHandler, c.failure() ở trường hợp này là null. Bạn chỉ có c.statusCode() bằng 413, không có ngoại lệ nào để đọc. Nên đừng viết c.failure().getMessage() mà không kiểm null — chính chỗ đó sẽ ném NullPointerException bên trong failureHandler.

Và nhớ đặt giới hạn này: mặc định là không giới hạn.

Chỗ lỗi của client bị báo thành lỗi của server

JsonObject j = c.body().asJsonObject();
if (j == null || !j.containsKey("ten")) { c.fail(400, ...); return; }
{"ten":"An"}        -> [HTTP 200] chao An
{"khong-co-ten":1}  -> [HTTP 400] thiếu trường 'ten'
khong-phai-json     -> [HTTP 500] DecodeException: Failed to decode: Unrecognized token 'khong'...
Dòng cuối là lỗi thiết kế nằm sẵn trong mã mà ai cũng viết. asJsonObject() ném khi thân không phải JSON hợp lệ, và ngoại lệ đó đi thẳng vào failureHandler với mã 500 — tức là bạn đang báo cho client rằng máy chủ hỏng, trong khi chính họ gửi sai.

Hậu quả không chỉ là mã HTTP xấu: 500 làm nổi cảnh báo của đội vận hành, làm bẩn tỉ lệ lỗi, và che mất lỗi 500 thật. Một script gõ sai có thể làm biểu đồ sức khoẻ dịch vụ đỏ rực.

Cách chữa gọn nhất là bọc lại và tự quyết định mã:

JsonObject j;
try {
    j = c.body().asJsonObject();
} catch (DecodeException e) {
    c.fail(400, e);                       // loi cua client, tra ve 400
    return;
}
if (j == null || !j.containsKey("ten")) { c.fail(400, new IllegalArgumentException("thieu 'ten'")); return; }

Hoặc dùng module vertx-web-validation để khai schema một lần thay vì kiểm tay từng trường — nhưng nguyên tắc không đổi: mọi thứ đến từ client đều phải giả định là sai, và sai của client là 4xx.

Ba việc phải làm cho mọi endpoint nhận dữ liệu

BodyHandler đặt ở đầu router, không phải sau route.

Luôn setBodyLimit. Mặc định không giới hạn nghĩa là một request đủ lớn có thể làm hỏng cả tiến trình.

Phân biệt 4xx với 5xx cho bằng được. JSON hỏng, thiếu trường, sai kiểu — tất cả là 400. Chỉ khi bạn hỏng mới là 500.

Bài sau: Vert.x Web Client — gọi HTTP ra ngoài, và cái bẫy quên đặt phép chờ.

Thử ba mươi giây

# endpoint cua ban tra ve gi khi nhan rac
curl -s -o /dev/null -w "%{http_code}\n" -X POST -H 'Content-Type: application/json' \
     -d 'khong-phai-json' http://localhost:8080/api/cua-ban

Ra 500 nghĩa là mọi client gõ sai đều đang được ghi nhận như một sự cố của bạn.