RFC 7807
Що таке RFC 7807?
RFC 7807 — це стандарт, що описує формат передачі помилок у HTTP API. Він пропонує використовувати уніфікований формат JSON-об'єкта, який називається "Problem Details". Ідея в тому, щоб зробити повідомлення про помилки структурованими, інформативними та зрозумілими як для розробників, так і для клієнтів.
Чому це важливо
Як часто вам доводилося бачити неструктуровані відповіді на помилки типу:
{
"error": "INVALID_REQUEST",
"message": "User ID is missing"
}
Начебто зрозуміло, але що робити з таким респонсом далі?
RFC 7807 дозволяє не лише стандартизувати ці повідомлення, а й надати більше контексту про те, що пішло не так і як це виправити. Завдяки цьому, помилки стають значно простішими для обробки.
Формат Problem Details
Основна структура відповіді:
{
"type": "https://api.example.com/problems/invalid-user",
"title": "Invalid User ID",
"status": 400,
"detail": "The user ID provided is not valid.",
"instance": "/users/123"
}
type — URL, який описує тип проблеми (може бути реальним посиланням на документацію або просто строкою).
title — короткий опис проблеми.
status — HTTP статус-код.
detail — деталі помилки.
instance — URI запиту, що викликав проблему (необов'язково).
Можна також додавати кастомні поля для специфічних кейсів:
{
"type": "https://api.example.com/problems/invalid-input",
"title": "Invalid Input",
"status": 422,
"detail": "The input data is invalid.",
"invalidField": "email"
}
Як виглядає респонс без RFC 7807 і з ним
Без RFC 7807:
{
"error": "USER_NOT_FOUND",
"message": "User with ID 123 not found"
}
З RFC 7807:
{
"type": "https://api.example.com/problems/user-not-found",
"title": "User Not Found",
"status": 404,
"detail": "User with ID 123 was not found in the database.",
"instance": "/users/123"
}
Погодьтесь, другий варіант виглядає більш зрозуміло і гнучко для подальшої автоматизації.
Best Practices
Використовуйте URL у полі
type
.
Це може бути статична сторінка документації вашого API або умовний посилання на тип помилки. Наприклад:https://api.example.com/problems/invalid-input
.Не додавайте зайвого.
Поля, що не мають практичної користі, лише ускладнюють відповідь.Кастомізуйте для своєї доменної області.
Ви можете додавати власні поля, але не змінюйте обов'язкові, описані у RFC.Обов'язково логуйте.
Навіть якщо користувач отримує зрозумілий респонс, логування помилки на стороні сервера є критично важливим для дебагу.
Реалізація в Java Spring Boot
Налаштування Controller Advice
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(AuthenticationException.class)
@ResponseStatus(HttpStatus.UNAUTHORIZED)
public ProblemDetail handleAuthenticationError(AuthenticationException ex, HttpServletRequest request) {
ProblemDetail problemDetail = ProblemDetail.forStatusAndDetail(
HttpStatus.UNAUTHORIZED,
ex.getMessage()
);
problemDetail.setType(URI.create("https://api.example.com/problems/authentication-failed"));
problemDetail.setTitle("Authentication Failed");
problemDetail.setInstance(URI.create(request.getRequestURI()));
return problemDetail;
}
}
Кастомна помилка
public class AuthenticationException extends RuntimeException {
public AuthenticationException(String message) {
super(message);
}
}
Ендпоінт для автентифікації, наприклад, з використанням FIDO2
@RestController
@RequestMapping("/auth")
public class AuthenticationController {
@PostMapping("/fido2")
public ResponseEntity<String> authenticateWithFido2(@RequestBody Fido2Request request) {
if (!fido2Service.validate(request)) {
throw new AuthenticationException("Invalid FIDO2 credentials provided");
}
return ResponseEntity.ok("Authentication successful");
}
}
Приклад респонсу при помилці автентифікації
{
"type": "https://api.example.com/problems/authentication-failed",
"title": "Authentication Failed",
"status": 401,
"detail": "Invalid FIDO2 credentials provided.",
"instance": "/auth/fido2"
}
Інтеграція та взаємодія з іншими компонентами
Стандартизована взаємодія
Інші сервіси або фронтенд можуть очікувати однакову структуру помилок, що спрощує їх обробку.
Наприклад, фронтенд може мати єдиний обробник для всіх помилок із зрозумілими полями
type
,title
,detail
таstatus
.
На фронтенді це може виглядати так:
fetch("/api/auth/fido2", { method: "POST", body: JSON.stringify(data) })
.then(response => {
if (!response.ok) {
return response.json().then(error => {
console.error(`Error Type: ${error.type}`);
alert(error.title + ": " + error.detail);
});
}
});
Машинна обробка помилок
Поле
type
дозволяє програмно класифікувати помилки. Наприклад, різніtype
можуть запускати різні сценарії в клієнтській програмі.У інтеграціях між сервісами це дозволяє швидко реагувати на певні типи помилок без аналізу тексту.
Приклад коду в іншому сервісі який інтегрується з нашим:
if (problemDetail.getType().equals(URI.create("https://api.example.com/problems/authentication-failed"))) {
retryAuthentication();
} else if (problemDetail.getType().equals(URI.create("https://api.example.com/problems/invalid-input"))) {
logInvalidInput();
}
Зрозумілий для користувача інтерфейс
Завдяки структурі, помилки легко локалізувати. Наприклад, title
може бути коротким і локалізованим для кінцевого користувача, тоді як detail
містить технічну інформацію для розробника.
Приклад локалізації:
{
"type": "https://api.example.com/problems/authentication-failed",
"title": "Аутентифікація не вдалася",
"status": 401,
"detail": "Облікові дані FIDO2 недійсні.",
"instance": "/auth/fido2"
}
Спрощений дебагінг і логування
Сервер надає достатньо контексту для розробників, включаючи URI запиту (через поле instance
), статус, та інші деталі. Це значно полегшує діагностику проблем.
Розширення без порушення сумісності
Ви можете додавати кастомні поля, не порушуючи стандарту. Наприклад, для інтеграції з платіжним сервісом можна передавати інформацію про специфічні поля:
{
"type": "https://api.example.com/problems/invalid-payment",
"title": "Payment Error",
"status": 400,
"detail": "The payment method is not supported.",
"instance": "/payments/456",
"paymentMethod": "crypto",
"retryAfter": 3600
}
На фронтенді:
if (error.paymentMethod === "crypto") {
alert("Please try another payment method.");
} else {
console.log(`Retry after: ${error.retryAfter} seconds`);
}
Готовність до автоматизації
Завдяки структурованим даним API-інтеграції можуть автоматично реагувати на помилки, наприклад, повторювати запит або повідомляти відповідний відділ підтримки.
if (problemDetail.getStatus() == 429) { // Too Many Requests
waitAndRetry(problemDetail.getRetryAfter());
}
Висновки
RFC 7807 робить роботу з помилками у вашому API прозорою та зручною.
Замість простих рядків ви отримуєте структуру, яка легко інтегрується в клієнтські додатки.
Реалізація у Spring Boot є простою та дозволяє швидко адаптуватися до стандарту.