Рубрики
Без рубрики

Как вернуть коды состояния HTTP в приложении Spring Boot

В этом руководстве мы рассмотрим, как возвращать коды состояния HTTP в приложениях Spring Boot с помощью @ResponseStatus, ResponseEntity и ResponseStatusException.

Автор оригинала: Guest Contributor.

Вступление

Все инженеры-программисты, которые полагаются на внешние/сторонние сервисы или инструменты по протоколу HTTP, хотели бы знать, были ли приняты их запросы, и если нет, то что происходит.

Ваша роль как разработчика API состоит в том, чтобы обеспечить хороший опыт для ваших пользователей и, среди прочего, удовлетворить этот спрос. Чтобы другим разработчикам было легко определить, возвращает ли ваш API ошибку или нет, вы пройдете долгий путь, а в первом случае – сообщите другим разработчикам почему , чтобы вы продвинулись еще дальше.

Является ли ошибка вызвана внутренней службой API? Прислали ли они непоправимую ценность? Произошел ли полный сбой сервера, обрабатывающего эти запросы?

Сужение возможностей сбоя позволяет разработчикам, использующим ваш сервис, выполнять свою работу более эффективно. Именно здесь в игру вступают коды состояния HTTP с коротким сообщением в теле ответа, описывающим, что происходит.

В этом руководстве мы рассмотрим , как возвращать различные коды состояния HTTP в Spring Boot при разработке REST API.

Что такое Коды Состояния HTTP?

Проще говоря, код состояния HTTP относится к 3-значному коду, который является частью HTTP-ответа сервера. Первая цифра кода описывает категорию, в которую попадает ответ. Это уже дает подсказку для определения того, был ли запрос успешным или нет. Управление по присвоению номеров Интернету (IANA) ведет официальный реестр кодов состояния HTTP. Ниже приведены различные категории:

  1. Информация (1xx) : Указывает, что запрос был получен и процесс продолжается. Он предупреждает отправителя о необходимости дождаться окончательного ответа.
  2. Успешно (2xx) : Указывает, что запрос был успешно получен, понят и принят.
  3. Перенаправление (3xx) : Указывает, что для завершения запроса необходимо предпринять дальнейшие действия.
  4. Ошибки клиента (4xx) : Указывает, что во время обработки запроса произошла ошибка, и именно клиент вызвал ошибку.
  5. Ошибки сервера (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 ResponseEntity withResponseEntity() {
        return ResponseEntity.status(HttpStatus.CREATED).body("HTTP Status will be CREATED (CODE 201)\n");
    }   
}

Основное преимущество использования ResponseEntity заключается в том, что вы можете связать его с другой логикой, такой как:

@Controller
@ResponseBody
public class TestController {
    
    @GetMapping("/response_entity")
    public ResponseEntity withResponseEntity() {
        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 , так и без него.