Автор оригинала: Guest Contributor.
Вступление
Все инженеры-программисты, которые полагаются на внешние/сторонние сервисы или инструменты по протоколу HTTP, хотели бы знать, были ли приняты их запросы, и если нет, то что происходит.
Ваша роль как разработчика API состоит в том, чтобы обеспечить хороший опыт для ваших пользователей и, среди прочего, удовлетворить этот спрос. Чтобы другим разработчикам было легко определить, возвращает ли ваш API ошибку или нет, вы пройдете долгий путь, а в первом случае – сообщите другим разработчикам почему , чтобы вы продвинулись еще дальше.
Является ли ошибка вызвана внутренней службой API? Прислали ли они непоправимую ценность? Произошел ли полный сбой сервера, обрабатывающего эти запросы?
Сужение возможностей сбоя позволяет разработчикам, использующим ваш сервис, выполнять свою работу более эффективно. Именно здесь в игру вступают коды состояния HTTP с коротким сообщением в теле ответа, описывающим, что происходит.
В этом руководстве мы рассмотрим , как возвращать различные коды состояния HTTP в Spring Boot при разработке REST API.
Что такое Коды Состояния HTTP?
Проще говоря, код состояния HTTP относится к 3-значному коду, который является частью HTTP-ответа сервера. Первая цифра кода описывает категорию, в которую попадает ответ. Это уже дает подсказку для определения того, был ли запрос успешным или нет. Управление по присвоению номеров Интернету (IANA) ведет официальный реестр кодов состояния HTTP. Ниже приведены различные категории:
- Информация (1xx) : Указывает, что запрос был получен и процесс продолжается. Он предупреждает отправителя о необходимости дождаться окончательного ответа.
- Успешно (2xx) : Указывает, что запрос был успешно получен, понят и принят.
- Перенаправление (3xx) : Указывает, что для завершения запроса необходимо предпринять дальнейшие действия.
- Ошибки клиента (4xx) : Указывает, что во время обработки запроса произошла ошибка, и именно клиент вызвал ошибку.
- Ошибки сервера (5xx) : Указывает, что во время обработки запроса произошла ошибка, но она была вызвана сервером.
Хотя список вряд ли является исчерпывающим, вот некоторые из наиболее распространенных HTTP-кодов, с которыми вы столкнетесь:
Код | Статус | Описание |
200 | ОК | Запрос был успешно выполнен. |
201 | Созданный | Новый ресурс был успешно создан. |
400 | Плохой Запрос | Запрос был недействителен. |
401 | Неавторизованный | Запрос не включал маркер аутентификации или срок действия маркера аутентификации истек. |
403 | Запрещенный | У клиента не было разрешения на доступ к запрошенному ресурсу. |
404 | не найдено | Запрошенный ресурс не был найден. |
405 | Метод Не Допускается | Метод HTTP в запросе не поддерживался ресурсом. Например, метод УДАЛЕНИЯ нельзя использовать с API агента. |
500 | внутренняя ошибка сервера | Запрос не был выполнен из-за внутренней ошибки на стороне сервера. |
503 | Услуга недоступна | Сервер был недоступен. |
Возвращайте коды состояния HTTP при весенней загрузке
Spring Boot делает разработку приложений на основе Spring намного проще, чем когда-либо прежде, и автоматически возвращает соответствующие коды состояния. Если запрос прошел нормально, возвращается 200 OK
, в то время как 404 Не найден
возвращается, если ресурс не найден на сервере.
Тем не менее, есть много ситуаций, в которых мы хотели бы сами принять решение о коде состояния HTTP, который будет возвращен в ответе, и Spring Boot предоставляет нам несколько способов для этого.
Давайте запустим скелетный проект с помощью Spring Initializr:
Или с помощью пружинного зажима:
$ spring init -d=web
У нас будет простой контроллер, Тестовый контроллер
:
@Controller public class TestController {}
Здесь мы создадим несколько обработчиков запросов, которые возвращают различные коды состояния с помощью нескольких различных подходов.
Возврат кодов состояния ответа с помощью @ResponseStatus
Эта аннотация принимает в качестве аргумента код состояния HTTP, который должен быть возвращен в ответе. Spring упрощает нашу работу, предоставляя перечисление, содержащее все коды состояния HTTP. Это очень универсальная аннотация, и ее можно использовать в контроллерах на уровне класса или метода, в пользовательских классах исключений и в классах, аннотированных @ControllerAdvice
(на уровне класса или метода).
Он работает одинаково как в классах с аннотациями @ControllerAdvice
, так и в классах с аннотациями @Controller
. В обоих случаях он обычно сопровождается аннотацией @ResponseBody
. При использовании на уровне класса все методы класса приведут к ответу с указанным кодом состояния HTTP. Все аннотации уровня метода @ResponseStatus
переопределяют код уровня класса, и если @ResponseStatus
не связан с методом, который не создает исключение- a 200
возвращается по умолчанию:
@Controller @ResponseBody @ResponseStatus(HttpStatus.SERVICE_UNAVAILABLE) public class TestController { @GetMapping("/classlevel") public String serviceUnavailable() { return "The HTTP Status will be SERVICE_UNAVAILABLE (CODE 503)\n"; } @GetMapping("/methodlevel") @ResponseStatus(code = HttpStatus.OK, reason = "OK") public String ok() { return "Class Level HTTP Status Overriden. The HTTP Status will be OK (CODE 200)\n"; } }
Уровень класса @ResponseStatus
становится кодом по умолчанию, возвращаемым для всех методов, если только метод не переопределяет его. Обработчик /уровня класса
запроса не связан со статусом на уровне метода, поэтому включается статус на уровне класса, возвращающий 503 Недоступную службу
, если кто-то попадает в конечную точку. С другой стороны, /уровень метода
конечная точка возвращает 200 OK
:
$ curl -i 'http://localhost:8080/classlevel' HTTP/1.1 503 Content-Type: text/plain;charset=UTF-8 Content-Length: 55 Date: Thu, 17 Jun 2021 06:37:37 GMT Connection: close The HTTP Status will be SERVICE_UNAVAILABLE (CODE 503)
$ curl -i 'http://localhost:8080/methodlevel' HTTP/1.1 200 Content-Type: text/plain;charset=UTF-8 Content-Length: 73 Date: Thu, 17 Jun 2021 06:41:08 GMT Class Level HTTP Status Overriden. The HTTP Status will be OK (CODE 200)
@ResponseStatus
работает по-другому при использовании в пользовательских классах исключений. Здесь указанный код состояния HTTP будет возвращен в ответе, когда исключение такого типа генерируется, но не перехватывается. Мы подробнее рассмотрим все это в коде в более позднем разделе.
Кроме того, вы можете указать причину
, которая автоматически запускает метод HttpServletResponse.sendError ()
, что означает, что все, что вы вернете, не будет выполнено:
Git Essentials
Ознакомьтесь с этим практическим руководством по изучению Git, содержащим лучшие практики и принятые в отрасли стандарты. Прекратите гуглить команды Git и на самом деле изучите это!
@GetMapping("/methodlevel") @ResponseStatus(code = HttpStatus.NOT_FOUND, reason = "Resource was not found on the server") public String notFound() { return ""; }
Хотя, чтобы на самом деле получить причину
для отправки с помощью метода sendError ()
, вам нужно будет установить свойство include-message
в application.properties
:
server.error.include-message=always
Теперь, если мы отправим запрос на /уровень метода
:
$ curl -i http://localhost:8080/methodlevel HTTP/1.1 404 Content-Type: application/json Transfer-Encoding: chunked Date: Tue, 29 Jun 2021 16:52:28 GMT {"timestamp":"2021-06-29T16:52:28.894+00:00","status":404,"error":"Not Found","message":"Resource was not found on the server","path":"/methodlevel"}
Это, вероятно, самый простой способ вернуть статус HTTP, но также и жесткий . Мы действительно не можем изменить коды состояния вручную, с помощью кода здесь. Именно здесь вступает в действие класс ResponseEntity
.
Возврат кодов состояния ответа с помощью ResponseEntity
Класс ResponseEntity
используется, когда мы хотим программно указать все аспекты HTTP-ответа. Это включает в себя заголовки, текст и, конечно же, код состояния. Этот способ является наиболее подробным способом возврата HTTP-ответа при весенней загрузке, но также и наиболее настраиваемым. Многие предпочитают использовать аннотацию @ResponseBody
в сочетании с @ResponseStatus
, поскольку они проще. Объект ResponseEntity
может быть создан с помощью одного из нескольких конструкторов или с помощью метода статического конструктора:
@Controller @ResponseBody public class TestController { @GetMapping("/response_entity") public ResponseEntitywithResponseEntity() { return ResponseEntity.status(HttpStatus.CREATED).body("HTTP Status will be CREATED (CODE 201)\n"); } }
Основное преимущество использования ResponseEntity
заключается в том, что вы можете связать его с другой логикой, такой как:
@Controller @ResponseBody public class TestController { @GetMapping("/response_entity") public ResponseEntitywithResponseEntity() { int randomInt = new Random().ints(1, 1, 11).findFirst().getAsInt(); if (randomInt < 9) { return ResponseEntity.status(HttpStatus.EXPECTATION_FAILED).body("Expectation Failed from Client (CODE 417)\n"); } else { return ResponseEntity.status(HttpStatus.I_AM_A_TEAPOT).body("April Fool's Status Code (CODE 418)\n"); } } }
Здесь мы сгенерировали случайное целое число в диапазоне от 1 до 10 и вернули код состояния в зависимости от случайного целого числа. Проверив, является ли случайное значение Int
больше, чем 9
, мы дали клиенту 10% вероятность увидеть “Я чайник” Код статуса первоапрельского дурака, добавленный в RFC2324 .
Отправка нескольких запросов на эту конечную точку в конечном итоге вернет:
$ curl -i 'http://localhost:8080/response_entity' HTTP/1.1 418 Content-Type: text/plain;charset=UTF-8 Content-Length: 36 Date: Tue, 29 Jun 2021 16:36:21 GMT April Fool's Status Code (CODE 418)
Возврат кодов состояния ответа с исключением ResponseStatusException
Класс, используемый для возврата кодов состояния в исключительных случаях, – это класс ResponseStatusException
. Он используется для возврата определенного сообщения и кода состояния HTTP, который будет возвращен при возникновении ошибки. Это альтернатива использованию @ExceptionHandler
и @ControllerAdvice
. Обработка исключений с использованием ResponseStatusException
считается более детальной. Это позволяет избежать создания ненужных дополнительных классов исключений и уменьшает тесную связь между кодами состояния и самими классами исключений:
@Controller @ResponseBody public class TestController { @GetMapping("/rse") public String withResponseStatusException() { try { throw new RuntimeException("Error Occurred"); } catch (RuntimeException e) { throw new ResponseStatusException(HttpStatus.NOT_FOUND, "HTTP Status will be NOT FOUND (CODE 404)\n"); } } }
Он ведет себя так же, как когда мы задаем причину
через @ResponseStatus
, поскольку основной механизм тот же – метод sendError()
:
$ curl -i http://localhost:8080/rse HTTP/1.1 404 Content-Type: application/json Transfer-Encoding: chunked Date: Tue, 29 Jun 2021 17:00:23 GMT {"timestamp":"2021-06-29T17:01:17.874+00:00","status":404,"error":"Not Found","message":"HTTP Status will be NOT FOUND (CODE 404)\n","path":"/rse"}
Пользовательские классы исключений и Возвращающие коды состояния HTTP
Наконец, еще один способ обработки исключений-через @ResponseStatus
и @ControllerAdvice
аннотации и пользовательские классы исключений. Хотя ResponseStatusException
предпочтительнее, если по какой-либо причине его нет на картинке, вы всегда можете использовать их.
Давайте добавим два обработчика запросов, которые создают новые пользовательские исключения:
@Controller @ResponseBody @ResponseStatus(HttpStatus.SERVICE_UNAVAILABLE) public class TestController { @GetMapping("/caught") public String caughtException() { throw new CaughtCustomException("Caught Exception Thrown\n"); } @GetMapping("/uncaught") public String unCaughtException() { throw new UnCaughtException("The HTTP Status will be BAD REQUEST (CODE 400)\n"); } }
Теперь давайте определим эти исключения и их собственные коды по умолчанию @ResponseStatus
(которые переопределяют статус на уровне класса):
@ResponseStatus(HttpStatus.BAD_REQUEST) public class CaughtCustomException extends RuntimeException{ public CaughtCustomException(String message) { super(message); } } @ResponseStatus(HttpStatus.BAD_REQUEST) public class UnCaughtException extends RuntimeException { public UnCaughtException(String message) { super(message); } }
Наконец, мы создадим @ControllerAdvice
контроллер, который используется для настройки того, как Spring Boot управляет исключениями:
@ControllerAdvice @ResponseBody public class TestControllerAdvice { @ExceptionHandler(CaughtCustomException.class) @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) public String handleException(CaughtCustomException exception) { return String.format("The HTTP Status will be Internal Server Error (CODE 500)\n %s\n",exception.getMessage()) ; } }
Наконец, когда мы запускаем несколько HTTP-запросов, конечная точка , возвращающая Пойманное пользовательское исключение
, будет отформатирована в соответствии с @ControllerAdvice
, в то время как исключение UnCaughtCustomException
не будет:
$ curl -i http://localhost:8080/caught HTTP/1.1 500 Content-Type: text/plain;charset=UTF-8 Content-Length: 83 Date: Tue, 29 Jun 2021 17:10:01 GMT Connection: close The HTTP Status will be Internal Server Error (CODE 500) Caught Exception Thrown $ curl -i http://localhost:8080/uncaught HTTP/1.1 400 Content-Type: application/json Transfer-Encoding: chunked Date: Tue, 29 Jun 2021 17:10:06 GMT Connection: close {"timestamp":"2021-06-29T17:10:06.264+00:00","status":400,"error":"Bad Request","message":"The HTTP Status will be BAD REQUEST (CODE 400)\n","path":"/uncaught"}
Вывод
В этом руководстве мы рассмотрели, как возвращать коды состояния HTTP при весенней загрузке с помощью @ResponseStatus
, ResponseEntity
и ResponseStatusException
, а также как определять пользовательские исключения и обрабатывать их как через @ControllerAdvice
, так и без него.