Ngiêm cấm sử đụng PROXY và VPS vào mục đích trái pháp luật bạn sẽ phải chịu toàn bộ trách nhiệm trước pháp luật khi sử dụng dịch vụ của chúng tôi tks!
RFC 9457 Problem Details client: đọc lỗi API có cấu trúc

RFC 9457 Problem Details client: đọc lỗi API có cấu trúc

Chuyên viên Kỹ thuật ProxyVN 03-24-2026 Đang tính... phút đọc

RFC 9457 Problem Details client là phần mã đọc phản hồi lỗi dạng application/problem+json rồi biến nó thành một lỗi có cấu trúc mà pipeline xử lý được. Bài này viết cho client thu thập API, nơi lỗi nghiệp vụ, lỗi proxy và lỗi mạng hay bị gộp làm một. ProxyVN dựng một API lab trả 11 kiểu phản hồi, một proxy lab tự sinh lỗi 502 và 407, rồi chạy requests 2.34.2 và httpx 0.28.1 thật để ghi lại cách client nên phân loại.

RFC 9457 Problem Details client: mục tiêu và phạm vi không lặp

Phần này chốt bài toán, đầu ra và ranh giới với các bài đã có.

Bài toán cần giải

Một crawler gọi API giá sản phẩm, gặp lỗi thì ghi log kiểu "HTTP 404" hoặc "request failed". Khi cần tra lại, không ai biết lỗi đó do sản phẩm không còn, do token thiếu quyền hay do proxy hỏng giữa đường. Phía API thường đã trả đủ thông tin trong body, chỉ là client không đọc.

Đầu ra nghiệm thu

Đầu ra là một bảng fixture gồm type, status, instance và correlation ID của từng ca. Đi kèm là hàm phân loại đưa mỗi phản hồi vào đúng một nhóm: lỗi API có cấu trúc, lỗi HTTP không cấu trúc, lỗi proxy, lỗi transport. Bảng phải cho thấy trace_id đi theo lỗi tới tận log, kể cả khi request đi qua proxy.

Ranh giới với bài 422 và 409

Bài lỗi 422 Unprocessable Content đã nói cách đọc mảng errors theo từng trường khi dữ liệu gửi lên sai. Bài lỗi 409 Conflict khi gọi API nói về xung đột trạng thái tài nguyên. Bài này không giải thích từng mã trạng thái. Nó tập trung vào định dạng problem details như một hợp đồng chung, và cách client đọc định dạng đó cho mọi mã.

Giới hạn của lab

Lab không gọi API thương mại nào. API và proxy đều là máy chủ Python tự viết bằng http.server, chạy trên 127.0.0.1. Thứ thật trong lab là hai thư viện client và cách chúng phản ứng với từng phản hồi. Nội dung lỗi do ProxyVN tự đặt để phủ đủ các ca trong RFC.

Ai nên đọc

Người viết client cho API nội bộ hoặc API đối tác có trả problem+json. Người vận hành crawler đi qua proxy, cần biết lỗi nằm ở đâu trước khi đổi IP. Người viết SDK nhỏ cho đội dữ liệu, muốn có một lớp lỗi dùng chung thay vì mỗi script tự xử lý một kiểu.

Vì sao chuẩn này đáng dùng

RFC 9457 thay RFC 7807, giữ nguyên định dạng nhưng làm rõ thêm vài điểm như registry các problem type và cách dùng nhiều problem trong một phản hồi. Nhiều framework web đã có sẵn bộ sinh lỗi theo định dạng này. Khi phía server đã theo chuẩn, phía client chỉ cần viết một bộ đọc là dùng được cho nhiều dịch vụ.

Chuẩn hóa đọc application/problem+json trong RFC 9457 Problem Details client

Phần này tóm tắt những gì chuẩn yêu cầu, chỉ lấy ý cần cho client.

Problem details là gì

Theo RFC 9457, problem details là một cách mang chi tiết lỗi trong phản hồi HTTP để khỏi phải tự chế định dạng lỗi mới cho từng API. Bản JSON có media type application/problem+json. Đây là một object JSON với năm trường chuẩn và có thể có thêm trường mở rộng.

Năm trường chuẩn

Năm trường chuẩn

Năm trường chuẩn

Bảng dưới gom năm trường, kiểu dữ liệu và việc client nên làm với từng trường.

Bảng: Năm trường chuẩn

Trường

Kiểu

Client nên làm gì

type

chuỗi URI

Dùng làm khóa phân loại chính, mặc định about:blank

status

số

Chỉ tham khảo, ưu tiên mã HTTP thật

title

chuỗi

Hiển thị cho người, không dùng để rẽ nhánh

detail

chuỗi

Ghi log, không parse để lấy dữ liệu

instance

chuỗi URI

Lưu để tra lại đúng lần xảy ra lỗi

Trường type là khóa chính

RFC ghi rõ người dùng định dạng phải dựa vào type để hiểu loại lỗi. Khi type vắng mặt, giá trị coi như about:blank. Với about:blank, lỗi không mang nghĩa gì thêm ngoài mã HTTP, và title nên giống tên mã trạng thái. Client vì vậy luôn có một giá trị type để so, không cần nhánh riêng cho trường hợp thiếu.

Client không nên tự tải type

Type thường là URL tới trang tài liệu mô tả lỗi. RFC khuyên client không tự động tải URL này, trừ khi có lý do như cung cấp thông tin cho lập trình viên lúc debug. Trong crawler chạy hàng nghìn request, tải trang tài liệu mỗi lần gặp lỗi chỉ tạo thêm tải và thêm điểm hỏng.

Status chỉ để tham khảo

Trường status trong body được RFC gọi là chỉ mang tính tư vấn. Server phải dùng cùng mã đó cho phản hồi HTTP thật, để phần mềm không hiểu định dạng vẫn chạy đúng. Status trong body hữu ích khi mã HTTP bị một intermediary thay đổi, hoặc khi body được lưu lại mà mất thông tin HTTP.

Title và detail dành cho con người

Title là tóm tắt ngắn, không nên đổi giữa các lần xảy ra cùng loại lỗi, trừ khi bản địa hóa. Detail mô tả lần xảy ra cụ thể này. RFC nói người dùng không nên parse detail để lấy thông tin. Thông tin máy cần đọc phải nằm ở trường mở rộng, nơi có kiểu dữ liệu rõ ràng.

Instance trỏ tới lần xảy ra

Instance là URI định danh lần xảy ra lỗi. Có server dùng đường dẫn tài nguyên, có server dùng mã sự kiện riêng. Với crawler, instance là thứ nên đưa vào ticket khi cần bên API hỗ trợ, vì nó chỉ đúng một lần lỗi chứ không chỉ loại lỗi.

URI tương đối

Khi type hoặc instance là URI tương đối, nó được phân giải theo base URI của tài liệu, thường là URL của request. RFC cảnh báo URI tương đối dễ gây nhầm và nên dùng URI đầy đủ. Client vẫn phải phân giải đúng, nếu không hai API khác nhau cùng dùng type "problems/bad-cursor" sẽ bị coi là một.

Trường sai kiểu thì bỏ qua

Đây là điểm ít người để ý. RFC ghi rằng nếu giá trị của một trường không đúng kiểu quy định, trường đó phải bị bỏ qua, xử lý tiếp như thể trường không có mặt. Client không được báo lỗi parse cả phản hồi chỉ vì status là chuỗi "422" thay vì số 422.

Nhận content type và các trường chuẩn trong RFC 9457 Problem Details client

Phần này là lab, chạy thật trên máy Windows với Python 3.12.10.

Dựng API lab

API lab nghe ở cổng 9711 và trả 11 kiểu phản hồi. Mỗi phản hồi có header X-Request-ID để mô phỏng mã yêu cầu mà nhiều API gắn sẵn. Đoạn dưới là hàm trả lỗi dùng chung.

def prob(h, code, body, ctype="application/problem+json", extra=None):
    b = json.dumps(body).encode() if not isinstance(body, bytes) else body
    h.send_response(code)
    h.send_header("Content-Type", ctype)
    h.send_header("Content-Length", str(len(b)))
    h.send_header("X-Request-ID", "req-" + h.path.strip("/").replace("/", "-"))
    for k, v in (extra or {}).items():
        h.send_header(k, v)
    h.end_headers()
    h.wfile.write(b)

Hàm này chỉ dùng trong lab. API thật có thể đặt mã yêu cầu ở header khác hoặc trong body.

Danh sách ca trong fixture

Mỗi ca nhắm vào một điểm trong RFC hoặc một tình huống hay gặp ở API thật.

  • products/1: 200 bình thường, application/json.
  • products/404: problem đủ năm trường cộng trace_id.
  • quota: 429 có trường mở rộng retry_after_s và header Retry-After.
  • blank: 403 không có type.
  • relative: type và instance là URI tương đối.
  • badtypes: type là số, title là mảng, status là chuỗi, detail là null.
  • mismatch: HTTP 200 nhưng body là problem với status 502.
  • plainjson: body giống problem nhưng content type là application/json.
  • broken: content type đúng, JSON bị cắt giữa chừng.
  • business: 409 hết hàng, có trường mở rộng sku.
  • nope: 404 trả HTML.

Kiểm content type trước khi parse

Bộ đọc chỉ coi là problem khi media type đúng application/problem+json, bỏ tham số như charset. Nhiều API trả lỗi JSON tùy biến với application/json. Đoán theo hình dạng body dễ đưa nhầm dữ liệu thường vào nhánh lỗi, nên lab chọn cách chặt.

ct = headers.get("content-type", "").split(";")[0].strip().lower()
if ct != "application/problem+json":
    return None, corr, f"content-type={ct}"

Ca plainjson cho thấy hệ quả: body có type, title, status nhưng bị xếp vào UnstructuredHttpError. Nếu một API anh em đang dùng trả kiểu này, viết bộ chuyển riêng cho API đó thay vì nới quy tắc chung.

Kiểm kiểu từng trường

Bộ đọc đi qua năm trường chuẩn, giữ trường đúng kiểu và ghi cảnh báo cho trường sai kiểu. Lưu ý trong Python bool là con của int, nên phải loại riêng, nếu không status true sẽ lọt.

STD = {"type": str, "title": str, "status": int, "detail": str, "instance": str}
warn, std = [], {}
for k, t in STD.items():
    if k in d:
        if isinstance(d[k], t) and not (t is int and isinstance(d[k], bool)):
            std[k] = d[k]
        else:
            warn.append(f"bo {k}={d[k]!r}")

Ở ca badtypes, kết quả là type về about:blank, title None, bốn cảnh báo, còn instance /x vẫn được giữ. Phản hồi không bị coi là hỏng.

Kết quả đọc thô của requests

Kết quả đọc 11 ca

Kết quả đọc 11 ca

Đoạn output dưới là kết quả thật của requests 2.34.2 gọi thẳng API lab, đã rút gọn.

404      HTTP 404 ApiProblem type=https://api.shop.test/problems/product-not-found instance=http://127.0.0.1:9711/v1/products/404 corr=4bf92f3577b34da6a3ce929d0e0e4736
quota    HTTP 429 ApiProblem type=https://api.shop.test/problems/quota corr=aa11 ext={'retry_after_s': 3600}
blank    HTTP 403 ApiProblem type=about:blank title='Forbidden' corr=req-v1-blank
relative HTTP 400 ApiProblem type=http://127.0.0.1:9711/v1/problems/bad-cursor instance=http://127.0.0.1:9711/v1/errors/77
badtypes HTTP 422 ApiProblem type=about:blank title=None warn=['bo type=42', "bo title=['sai']", "bo status='422'", 'bo detail=None']
mismatch HTTP 200 ApiProblem type=https://api.shop.test/problems/upstream warn=['status body 502 != HTTP 200']
plainjson HTTP 400 UnstructuredHttpError: 400 content-type=application/json
broken   HTTP 500 UnstructuredHttpError: 500 problem+json hong (json loi: JSONDecodeError)
nope     HTTP 404 UnstructuredHttpError: 404 content-type=text/html

Mười một ca đều được xếp vào đúng một nhóm. Không có ca nào làm hàm đọc ném ngoại lệ ra ngoài.

Bộ đọc lỗi không được phép tự gây lỗi. Phản hồi lạ đến đâu, nó cũng phải trả về một nhóm và một mã correlation.

Lỗi HTML từ chính API

Ca nope trả 404 với body HTML. Đây là kiểu hay gặp khi gọi sai đường dẫn: framework trả trang lỗi mặc định, không qua lớp sinh problem. Lab xếp vào UnstructuredHttpError vẫn kèm X-Request-ID. Gặp nhiều ca này, kiểm lại URL endpoint trong cấu hình trước khi nghi API đổi định dạng.

Thư viện không làm việc này giúp anh em

requests có raise_for_status, nhưng thông báo chỉ là "404 Client Error: Not Found for url". httpx cũng tương tự với "Client error '409 Conflict'". Cả hai không đọc body. Gọi r.json() trên ca mismatch trả về một dict bình thường, không có gì báo đây là lỗi. Lớp đọc problem phải do anh em tự viết.

Phản hồi 200 mang problem

Ca mismatch là tình huống lạ nhưng có thật ở vài gateway: mã 200, body báo lỗi upstream 502. RFC yêu cầu server dùng cùng mã, nên đây là lỗi phía server. Lab vẫn xếp nó vào ApiProblem kèm cảnh báo lệch status, vì nếu coi là dữ liệu thì pipeline sẽ lưu một bản ghi rác.

Map type, status, detail và instance sang lỗi của RFC 9457 Problem Details client

Phần này biến kết quả đọc thành lớp lỗi mà mã nghiệp vụ dùng được.

Một lớp lỗi cho mọi API

Lớp ApiProblem giữ mã HTTP thật, type đã phân giải, title, detail, instance, các trường mở rộng, mã correlation và danh sách cảnh báo. Mã nghiệp vụ chỉ cần bắt một loại ngoại lệ, rồi rẽ nhánh theo type.

class ApiProblem(Exception):
    def __init__(self, http_status, ptype, title, detail, instance, ext, corr, warn):
        super().__init__(f"{http_status} {ptype}")
        self.http_status, self.ptype = http_status, ptype
        self.title, self.detail, self.instance = title, detail, instance
        self.ext, self.corr, self.warn = ext, corr, warn

Lớp này là mã lab. Trong dự án thật, anh em có thể dùng dataclass và thêm trường thời điểm, URL request.

Phân giải type và instance

Hàm urljoin của thư viện chuẩn làm đúng việc phân giải URI tương đối theo URL request. Với ca relative, type problems/bad-cursor thành http://127.0.0.1:9711/v1/problems/bad-cursor, instance errors/77 thành http://127.0.0.1:9711/v1/errors/77.

ptype = urljoin(url, std.get("type", "about:blank"))
if "instance" in std:
    std["instance"] = urljoin(url, std["instance"])

urljoin giữ nguyên URI đầy đủ và giữ about:blank, nên một dòng xử lý được cả ba trường hợp.

Bảng quyết định theo type

Mã nghiệp vụ không nên rẽ nhánh theo title hay detail. Lab dùng một bảng ánh xạ từ type sang hành động, có mặc định theo mã HTTP cho type lạ.

Bảng: Bảng quyết định theo type

Type

HTTP

Hành động của crawler

.../product-not-found

404

Đánh dấu sản phẩm đã gỡ, không thử lại

.../quota

429

Dừng theo retry_after_s, báo người vận hành

.../out-of-stock

409

Ghi trạng thái hết hàng, đây là dữ liệu hợp lệ

about:blank

403

Kiểm token và scope, không đổi IP

type lạ

5xx

Thử lại có giới hạn, giữ trace_id

Lỗi 409 hết hàng là dữ liệu

Ca business đáng chú ý. Với một crawler theo dõi tồn kho, 409 out-of-stock kèm sku A12 là thông tin cần lưu, không phải sự cố. Nếu client gộp mọi 4xx thành "request lỗi" rồi bỏ qua, bảng tồn kho sẽ thiếu đúng những dòng quan trọng nhất.

Không suy luận từ detail

Ca 404 có detail "Khong co san pham 404". Rất dễ viết regex lấy mã sản phẩm từ chuỗi này. Đừng. Detail có thể đổi câu chữ, đổi ngôn ngữ bất cứ lúc nào. Nếu cần mã sản phẩm, đề nghị bên API đưa vào trường mở rộng như sku ở ca business.

Type lạ vẫn phải có đường đi

Client sẽ gặp type chưa có trong bảng. Khi đó rơi về hành vi theo mã HTTP: 4xx không thử lại, 5xx thử lại có giới hạn. Ghi type lạ vào một bộ đếm riêng, để biết khi nào bên API thêm loại lỗi mới mà mình chưa xử lý.

Bản địa hóa title

RFC cho phép title thay đổi theo bản địa hóa, ví dụ qua Accept-Language. Nếu crawler chạy nhiều vùng, cùng một lỗi có thể có title tiếng Anh ở lần này, tiếng Việt ở lần sau. Đây thêm một lý do để dùng type làm khóa thống kê thay vì title.

Nhiều problem trong một phản hồi

RFC 9457 có phần nói về trường hợp có nhiều lỗi cùng loại, ví dụ nhiều trường đầu vào sai, được gom vào một problem với trường mở rộng dạng mảng. Lab không dựng ca này. Bài 422 đã có phần đọc mảng errors theo trường, anh em có thể ghép bộ đọc đó vào trường ext của ApiProblem.

Kiểm trường extension cùng correlation ID trong RFC 9457 Problem Details client

Phần này nói về thứ giúp tra lỗi xuyên hệ thống: mã correlation.

Trường mở rộng là gì

Mọi khóa ngoài năm trường chuẩn là trường mở rộng. RFC nói client phải bỏ qua trường mở rộng mà nó không hiểu. Nghĩa là thêm trường mới phía server không được làm vỡ client cũ. Bộ đọc của lab giữ toàn bộ trường mở rộng trong một dict, không lọc theo danh sách cố định.

Correlation ID nằm ở đâu

Không có trường chuẩn nào tên trace_id hay request_id. Mỗi API tự chọn. Có API đặt trong body như trace_id, có API đặt ở header như X-Request-ID, có API dùng traceparent theo W3C Trace Context. Client cần thứ tự ưu tiên rõ ràng.

Thứ tự ưu tiên của lab

Lab lấy trace_id trong body nếu có, nếu không thì dùng X-Request-ID ở header. Lý do: trace_id trong body do chính lớp ứng dụng sinh ra, gắn với lần xảy ra lỗi. Header có thể do gateway gắn.

ext = {k: v for k, v in d.items() if k not in STD}
corr = ext.get("trace_id") or corr

Ở ca 404, corr là 4bf92f3577b34da6a3ce929d0e0e4736 từ body. Ở ca blank, corr là req-v1-blank từ header.

Correlation ID qua proxy

Lab cho httpx đi qua proxy lab ở cổng 9712. Proxy chuyển tiếp header X-Request-ID và Content-Type, thêm Via: 1.1 proxy-lab. Kết quả đọc lỗi qua proxy giống hệt khi gọi thẳng: ca 404 vẫn giữ trace_id, ca business vẫn giữ sku A12.

headers: {'content-type': 'application/problem+json', 'x-request-id': 'req-v1-products-404', 'via': '1.1 proxy-lab'}
404      HTTP 404 ApiProblem type=https://api.shop.test/problems/product-not-found corr=4bf92f3577b34da6a3ce929d0e0e4736
business HTTP 409 ApiProblem type=https://api.shop.test/problems/out-of-stock corr=cc33 ext={'sku': 'A12'}

Proxy chuyển tiếp HTTP thông thường không đụng vào body, nên problem details đi qua nguyên vẹn. Vai trò của header Via được giải thích ở bài Via header.

Ghi correlation vào log

Mỗi dòng log lỗi nên có ít nhất: thời điểm, URL đã che token, mã HTTP thật, type, instance, corr. Với log dạng JSON, các trường này lọc được ngay. Khi cần nối với trace phía server, bài OpenTelemetry truy vết request qua proxy nói cách gắn trace từ phía client.

Trường mở rộng có thể lộ dữ liệu

RFC có phần bảo mật nhắc server tránh đưa chi tiết triển khai như stack trace vào problem. Phía client cũng nên cẩn thận: trường mở rộng có thể chứa email, mã đơn hàng, thậm chí một phần token. Trước khi đẩy ext vào hệ thống log dùng chung, lọc các khóa nhạy cảm.

Retry-After ở hai nơi

Ca quota có retry_after_s trong body và Retry-After ở header. Hai giá trị trùng nhau trong lab. Ở API thật, nếu chúng khác nhau, nên tin header vì đó là ngữ nghĩa HTTP chuẩn, còn trường body chỉ là quy ước riêng của API đó. Ghi cảnh báo khi hai giá trị lệch.

RFC 9457 Problem Details client giữ status HTTP thật, tách lỗi nghiệp vụ khỏi lỗi transport

Phần này là ranh giới quan trọng nhất của bài: lỗi nào thuộc API, lỗi nào thuộc đường đi.

Bốn nhóm lỗi

Bốn nhóm lỗi

Bốn nhóm lỗi

Client của lab xếp mọi kết quả vào một trong năm trạng thái: ok, ApiProblem, UnstructuredHttpError, ProxyError, lỗi transport. Bốn nhóm cuối đều là lỗi, nhưng cách xử lý khác hẳn nhau.

Bảng: Bốn nhóm lỗi

Nhóm

Dấu hiệu

Ai sửa

ApiProblem

Content type problem+json, parse được

Đọc type, xử lý theo nghiệp vụ

UnstructuredHttpError

Lỗi HTTP không có problem hợp lệ

Xem body thô, liên hệ bên API

ProxyError

407, hoặc 502/503/504 do proxy sinh

Kiểm cấu hình proxy, upstream

Transport

Không có phản hồi HTTP

Kiểm mạng, DNS, cổng

Lỗi do proxy tự sinh

Proxy lab trả 502 với body HTML "502 Bad Gateway proxy-lab" khi upstream chết, và trả 407 khi cần xác thực. Cả hai không phải problem+json. Hàm phân loại nhận ra qua mã 407, hoặc qua Via chứa proxy-lab cộng mã 502, 503, 504 không có problem.

if status == 407 or ("proxy-lab" in headers.get("via", "")
                     and status in (502, 503, 504)
                     and "problem+json" not in ct):
    return ProxyError(f"{status} via {headers.get('via')}")

Quy tắc nhận diện qua tên trong Via là quy tắc của lab. Với proxy thật, anh em cần biết proxy của mình ghi gì vào Via hoặc Server, hoặc body lỗi có dấu hiệu gì riêng.

Vì sao không dựa vào mỗi mã 502

502 có thể do proxy sinh khi không nối được upstream, cũng có thể do chính API trả khi dịch vụ phía sau nó hỏng. API theo chuẩn sẽ trả 502 kèm problem+json. Nếu chỉ nhìn mã, crawler sẽ đổi IP vô ích cho một lỗi nằm trong hạ tầng của bên API.

Lỗi 407 không phải lỗi API

Ca need-auth trả 407 không có header Via vì proxy lab trả ngay, không chuyển tiếp. Đây là lỗi cấu hình phía anh em: thiếu hoặc sai thông tin đăng nhập proxy. Xử lý 407 có bài riêng ở sửa lỗi 407 Proxy Authentication Required.

Lỗi transport không có mã HTTP

Lab gọi tới cổng 9719 không có gì lắng nghe. Trên Windows, requests báo ConnectTimeout sau 2,0 giây, httpx báo ConnectTimeout sau 2,28 giây với timeout đặt 2 giây. ConnectTimeout của requests là lớp con của ConnectionError, của httpx là lớp con của TransportError. Không có status, không có body, không có gì để parse.

transport <- requests ConnectTimeout is ConnectionError 2.0 s
transport <- httpx ConnectTimeout is TransportError 2.28 s

Trên Linux, cổng đóng thường trả lỗi từ chối kết nối ngay. Con số thời gian ở đây chỉ đúng với máy lab.

Giữ mã HTTP thật

ApiProblem lưu http_status là mã của phản hồi, không lấy status trong body. Ca mismatch có HTTP 200 nhưng body nói 502. Nếu lấy status body, thống kê sẽ ghi một lỗi 502 mà log truy cập của gateway không hề có. Lưu cả hai, dùng mã HTTP cho thống kê, dùng cảnh báo lệch để báo bên API.

Khi nào status trong body có ích

RFC nêu status body giúp biết mã gốc khi intermediary hoặc cache thay đổi mã. Ví dụ một proxy chặn nội dung trả 403 nhưng giữ body problem của API với status 404. Trường hợp này hiếm. Client chỉ nên dùng status body để chẩn đoán, không dùng để quyết định thử lại.

Đổi IP chỉ đúng với một nhóm

Đổi proxy chỉ có ích khi lỗi thuộc đường đi: proxy hỏng, IP bị chặn ở tầng mạng, kết nối chập chờn. Với ApiProblem, đổi IP không làm sản phẩm xuất hiện lại, không thêm scope cho token, không tăng quota tài khoản. Bảng phân loại giúp crawler không đốt proxy vào những lỗi mà proxy không sửa được.

Gom cảnh báo thành chỉ số

Danh sách warn của từng ApiProblem nên được đếm theo API và theo loại cảnh báo. Một API bỗng có nhiều cảnh báo "bo status" sau một đợt cập nhật là dấu hiệu bên đó đổi bộ sinh lỗi. Biết sớm giúp anh em báo lại trước khi dữ liệu lỗi tích tụ trong kho. Chỉ số này rẻ, chỉ cần một bộ đếm theo cặp tên API và tên trường.

Thử lại theo nhóm, không theo mã

Chính sách thử lại nên gắn với nhóm. ProxyError và lỗi transport được thử lại sau khi đổi đường đi hoặc chờ. ApiProblem chỉ thử lại khi type nằm trong danh sách lỗi tạm thời như quota hay upstream. UnstructuredHttpError 5xx thử lại ít lần rồi dừng, kèm body thô để người xem.

Đầu ra nghiệm thu của RFC 9457 Problem Details client

Phần này gom fixture, tiêu chí đạt và phần chưa thử.

Bảng fixture

Fixture nghiệm thu

Fixture nghiệm thu

Bảng dưới là đầu ra chính, lấy từ output thật của lab với requests gọi thẳng và httpx qua proxy.

Bảng: Bảng fixture

Ca

HTTP

Nhóm

Type sau phân giải

Corr

products/404

404

ApiProblem

.../product-not-found

4bf92f35... (body)

quota

429

ApiProblem

.../quota

aa11 (body)

blank

403

ApiProblem

about:blank

req-v1-blank (header)

relative

400

ApiProblem

.../v1/problems/bad-cursor

req-v1-relative

badtypes

422

ApiProblem, 4 cảnh báo

about:blank

bb22

mismatch

200

ApiProblem, lệch status

.../upstream

req-v1-mismatch

plainjson

400

Unstructured

không đọc

req-v1-plainjson

broken

500

Unstructured

không đọc

req-v1-broken

business

409

ApiProblem

.../out-of-stock

cc33

upstream-down qua proxy

502

ProxyError

không áp dụng

không có

need-auth qua proxy

407

ProxyError

không áp dụng

không có

cổng đóng

không có

Transport

không áp dụng

không có

Tiêu chí đạt

Client đạt khi mọi điều dưới đây đúng trên fixture của anh em.

  • Chỉ parse problem khi content type là application/problem+json.
  • Trường sai kiểu bị bỏ qua, không làm hỏng cả phản hồi.
  • Type vắng mặt thành about:blank, URI tương đối được phân giải theo URL request.
  • Mã HTTP thật được giữ, lệch với status body thì có cảnh báo.
  • Trường mở rộng được giữ, kể cả trường client chưa biết.
  • Mỗi lỗi có một mã correlation, theo thứ tự ưu tiên đã chốt.
  • Lỗi proxy và lỗi transport không bao giờ bị xếp thành ApiProblem.

Kiểm lại với API của anh em

Thay base URL trong script bằng API thật, chọn vài endpoint có thể gây lỗi an toàn như tra một mã không tồn tại. So kết quả với bảng trên. Chỗ nào lệch, ghi lại type và header thật của API, rồi cập nhật bảng quyết định.

Những gì lab chưa chạy

Chưa thử API thương mại có problem+json. Chưa thử problem+xml. Chưa thử phản hồi nén, HTTP/2, proxy có kiểm tra nội dung. Chưa thử trường hợp nhiều problem trong một phản hồi. Quy tắc nhận diện lỗi proxy dựa trên chuỗi proxy-lab chỉ đúng với lab. Bảng quyết định theo type là ví dụ triển khai.

Đặt proxy ở đâu

Proxy nằm ở tầng tải, giữa client và API. Bộ đọc problem nằm sau tầng tải, chạy được offline trên body đã lưu. Khi crawler cần IP tại Việt Nam để gọi API phục vụ người dùng trong nước, anh em có thể mua proxy tại trang chủ ProxyVN. Proxy không đổi nội dung lỗi của API. Vai trò của proxy trong crawler có ở bài Web Scraping proxy là gì, cách ghi chuỗi cấu hình có ở bài định dạng proxy.

Lưu body lỗi thô

Ngoài bản đã phân loại, nên lưu body lỗi thô với giới hạn kích thước, ví dụ vài KB đầu. Khi bên API đổi định dạng hoặc lớp đọc có lỗi, body thô là bằng chứng duy nhất để làm lại. Nhớ che dữ liệu nhạy cảm trước khi lưu lâu dài.

Viết test cho bộ đọc

Fixture trong bài nên được giữ như bộ test đơn vị. Mỗi ca là một cặp: phản hồi thô gồm mã, header, body, và kết quả mong đợi gồm nhóm, type, corr, số cảnh báo. Test không cần mạng, chạy trong vài mili giây. Khi ai đó sửa hàm đọc, test bắt ngay nếu ca badtypes bỗng ném ngoại lệ hoặc ca plainjson bỗng thành ApiProblem.

Phiên bản cần khóa

Lab chạy requests 2.34.2, httpx 0.28.1, Python 3.12.10. Hành vi đọc header của hai thư viện ổn định qua nhiều phiên bản, nhưng thông báo lỗi và tên lớp ngoại lệ có thể đổi. Ghi phiên bản vào tệp yêu cầu của dự án, chạy lại test sau mỗi lần nâng cấp.

RFC 9457 Problem Details client: câu hỏi thường gặp

Các câu dưới đây gom những điểm hay bị hỏi khi triển khai.

Có cần thư viện riêng để đọc problem details không?

Không bắt buộc. Lab chỉ dùng json và urljoin của thư viện chuẩn. Phần khó là quy tắc phân loại, không phải parse JSON.

API trả application/json có cấu trúc giống problem thì sao?

Lab xếp vào lỗi không cấu trúc. Nếu biết chắc API đó dùng cấu trúc tương tự, viết bộ chuyển riêng cho API đó, đừng nới quy tắc chung.

Status trong body khác mã HTTP thì tin cái nào?

Tin mã HTTP cho thống kê và hành vi thử lại. Ghi status body vào cảnh báo để báo bên API.

Trường status là chuỗi có làm hỏng phản hồi không?

Không. RFC yêu cầu bỏ qua trường sai kiểu. Lab ghi cảnh báo và vẫn trả ApiProblem.

Đổi proxy có sửa được lỗi 403 about:blank không?

Thường là không. 403 có problem từ API thường do token thiếu quyền. Kiểm scope trước khi nghĩ tới IP.

Correlation ID nên lấy từ body hay header?

Tùy API. Lab ưu tiên trace_id trong body, rơi về X-Request-ID. Chốt một thứ tự và ghi rõ trong tài liệu nội bộ.

RFC 9457 Problem Details client và những điều cần nhớ

RFC 9457 Problem Details client cần kiểm đúng content type, bỏ qua trường sai kiểu, dùng type làm khóa, giữ mã HTTP thật và luôn mang theo correlation ID. Lỗi proxy và lỗi mạng phải tách khỏi lỗi API trước khi nghĩ tới chuyện đổi IP. Proxy.vn - Nhà cung cấp dịch vụ proxy chất lượng hàng đầu Việt Nam khuyên anh em chạy lại fixture này với API thật trước khi tin vào RFC 9457 Problem Details client.

TIN TỨC LIÊN QUAN