Vlastné spracovanie chybových správ pre REST API

ODPOČINOK Najlepšie

Práve som oznámil nové Naučte sa jar kurz zameraný na základy jari 5 a Spring Boot 2:

>> SKONTROLUJTE KURZ

1. Prehľad

V tomto tutoriáli - si ukážeme, ako implementovať globálny obslužný program chýb pre Spring REST API.

Sémantiku každej výnimky použijeme na vytvorenie zmysluplných chybových správ pre klienta s jasným cieľom poskytnúť mu všetky informácie, aby mohol ľahko diagnostikovať problém.

2. Vlastná chybová správa

Začnime implementáciou jednoduchej štruktúry na odosielanie chýb po drôte - ApiError:

verejná trieda ApiError {súkromný stav HttpStatus; súkromná reťazcová správa; chyby súkromného zoznamu; public ApiError (HttpStatus status, String message, List errors) {super (); this.status = stav; this.message = správa; this.errors = chyby; } public ApiError (HttpStatus status, String message, String error) {super (); this.status = stav; this.message = správa; errors = Arrays.asList (chyba); }}

Informácie tu by mali byť priame:

  • postavenie: stavový kód HTTP
  • správa: chybové hlásenie spojené s výnimkou
  • chyba: Zoznam vytvorených chybových správ

A samozrejme, pre skutočnú logiku spracovania výnimiek na jar použijeme @ControllerAdvice anotácia:

@ControllerAdvice verejná trieda CustomRestExceptionHandler rozširuje ResponseEntityExceptionHandler {...}

3. Vybavte výnimky zlých požiadaviek

3.1. Riešenie výnimiek

Teraz sa pozrime, ako zvládneme najbežnejšie chyby klientov - v podstate scenáre, keď klient poslal neplatnú požiadavku na API:

  • BindException: Táto výnimka je vyvolaná, keď sa vyskytnú závažné chyby vo väzbe.
  • MethodArgumentNotValidException: Táto výnimka je vyvolaná, keď argument anotovaný s @ Platné zlyhala validácia:

@Override chránený ResponseEntity handleMethodArgumentNotValid (MethodArgumentNotValidException ex, hlavičky HttpHeaders, stav HttpStatus, požiadavka WebRequest) {List errors = new ArrayList (); pre (chyba FieldError: ex.getBindingResult (). getFieldErrors ()) {errors.add (error.getField () + ":" + error.getDefaultMessage ()); } for (ObjectError error: ex.getBindingResult (). getGlobalErrors ()) {errors.add (error.getObjectName () + ":" + error.getDefaultMessage ()); } ApiError apiError = nový ApiError (HttpStatus.BAD_REQUEST, ex.getLocalizedMessage (), chyby); return handleExceptionInternal (ex, apiError, headers, apiError.getStatus (), request); } 

Ako môžeš vidieť, prepíšeme základnú metódu z ResponseEntityExceptionHandler a poskytnutie vlastnej implementácie na mieru.

Nie vždy to tak bude - niekedy budeme musieť zvládnuť vlastnú výnimku, ktorá nemá predvolenú implementáciu v základnej triede, ako si ukážeme neskôr.

Ďalšie:

  • MissingServletRequestPartException: Táto výnimka sa vyvolá, keď sa nenájde časť požiadavky na viac častí

  • MissingServletRequestParameterException: Táto výnimka sa vyvolá, keď požiadavke chýba parameter:

@Override chránený ResponseEntity handleMissingServletRequestParameter (MissingServletRequestParameterException ex, hlavičky HttpHeaders, stav HttpStatus, požiadavka WebRequest) {Chyba reťazca = ex.getParameterName () + "parameter chýba"; ApiError apiError = nový ApiError (HttpStatus.BAD_REQUEST, ex.getLocalizedMessage (), chyba); návrat new ResponseEntity (apiError, new HttpHeaders (), apiError.getStatus ()); }
  • ConstrainViolationException: Táto výnimka hlási výsledok porušenia obmedzenia:

@ExceptionHandler ({ConstraintViolationException.class}) public ResponseEntity handleConstraintViolation (ConstraintViolationException ex, požiadavka WebRequest) {List errors = new ArrayList (); pre (porušenie ConstraintViolation: ex.getConstraintViolations ()) {errors.add (violing.getRootBeanClass (). getName () + "" + priestupok.getPropertyPath () + ":" + priestupok.getMessage ()); } ApiError apiError = nový ApiError (HttpStatus.BAD_REQUEST, ex.getLocalizedMessage (), chyby); návrat new ResponseEntity (apiError, new HttpHeaders (), apiError.getStatus ()); }
  • TypeMismatchException: Táto výnimka je vyvolaná pri pokuse o nastavenie vlastnosti fazule s nesprávnym typom.

  • MethodArgumentTypeMismatchException: Táto výnimka je vyvolaná, keď argument metódy nie je očakávaným typom:

@ExceptionHandler ({MethodArgumentTypeMismatchException.class}) public ResponseEntity handleMethodArgumentTypeMismatch (MethodArgumentTypeMismatchException ex, požiadavka WebRequest) {Chyba reťazca = ex.getName () + "by mala byť typu" + ex.getRequiredType (). ApiError apiError = nový ApiError (HttpStatus.BAD_REQUEST, ex.getLocalizedMessage (), chyba); návrat new ResponseEntity (apiError, new HttpHeaders (), apiError.getStatus ()); }

3.2. Spotrebovanie API od klienta

Poďme sa teraz pozrieť na test, ktorý narazí na a MethodArgumentTypeMismatchException: budeme pošlite žiadosť s id ako String namiesto dlho:

@Test public void whenMethodArgumentMismatch_thenBadRequest () {Response response = givenAuth (). Get (URL_PREFIX + "/ api / foos / ccc"); Chyba ApiError = response.as (ApiError.class); assertEquals (HttpStatus.BAD_REQUEST, error.getStatus ()); assertEquals (1, error.getErrors (). size ()); assertTrue (error.getErrors (). get (0) .contains ("malo by to byť typu")); }

A nakoniec - vzhľadom na tú istú žiadosť:

Metóda žiadosti: GET Cesta žiadosti: // localhost: 8080 / spring-security-rest / api / foos / ccc 

Takto bude vyzerať tento druh chybovej reakcie JSON:

{"status": "BAD_REQUEST", "message": "Nepodarilo sa previesť hodnotu typu [java.lang.String] na požadovaný typ [java.lang.Long]; vnorenou výnimkou je java.lang.NumberFormatException: pre vstupný reťazec : \ "ccc \" "," errors ": [" ID by malo byť typu java.lang.Long "]}}

4. Rukoväť NoHandlerFoundException

Ďalej môžeme prispôsobiť náš servlet tak, aby namiesto odoslania odpovede 404 vyvolala túto výnimku, a to nasledovne:

 api org.springframework.web.servlet.DispatcherServlet throwExceptionIfNoHandlerFound pravda 

Potom, keď sa to stane, môžeme to jednoducho vyriešiť ako každú inú výnimku:

@Override chránená ResponseEntity handleNoHandlerFoundException (NoHandlerFoundException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {String error = "Nenašiel sa žiadny obslužný program pre" + ex.getHttpMethod () + "" + ex.getRequestURL (); ApiError apiError = nový ApiError (HttpStatus.NOT_FOUND, ex.getLocalizedMessage (), chyba); návrat new ResponseEntity (apiError, new HttpHeaders (), apiError.getStatus ()); }

Tu je jednoduchý test:

@Test public void whenNoHandlerForHttpRequest_thenNotFound () {Response response = givenAuth (). Delete (URL_PREFIX + "/ api / xx"); Chyba ApiError = response.as (ApiError.class); assertEquals (HttpStatus.NOT_FOUND, error.getStatus ()); assertEquals (1, error.getErrors (). size ()); assertTrue (error.getErrors (). get (0) .contains ("Nenašiel sa žiadny obslužný program")); }

Pozrime sa na celú žiadosť:

Metóda požiadavky: DELETE Cesta požiadavky: // localhost: 8080 / spring-security-rest / api / xx

A chyba odpovede JSON:

{"status": "NOT_FOUND", "message": "Nenašiel sa žiadny obslužný program pre príkaz DELETE / spring-security-rest / api / xx", "errors": ["Nenašiel sa žiadny obslužný program pre program DELETE / spring-security-rest / api / xx "]}

5. Rukoväť HttpRequestMethodNotSupportedException

Ďalej sa pozrime na ďalšiu zaujímavú výnimku - HttpRequestMethodNotSupportedException - ku ktorej dôjde, keď pošlete požiadavku s nepodporovanou metódou HTTP:

@Override chránený ResponseEntity handleHttpRequestMethodNotSupported (HttpRequestMethodNotSupportedException ex, hlavičky HttpHeaders, stav HttpStatus, požiadavka WebRequest) {StringBuilder builder = nový StringBuilder (); builder.append (ex.getMethod ()); builder.append ("metóda nie je pre túto požiadavku podporovaná. Podporované metódy sú"); ex.getSupportedHttpMethods (). forEach (t -> builder.append (t + "")); ApiError apiError = nový ApiError (HttpStatus.METHOD_NOT_ALLOWED, ex.getLocalizedMessage (), builder.toString ()); návrat new ResponseEntity (apiError, new HttpHeaders (), apiError.getStatus ()); }

Tu je jednoduchý test reprodukujúci túto výnimku:

@Test public void whenHttpRequestMethodNotSupported_thenMethodNotAllowed () {Response response = givenAuth (). Delete (URL_PREFIX + "/ api / foos / 1"); Chyba ApiError = response.as (ApiError.class); assertEquals (HttpStatus.METHOD_NOT_ALLOWED, error.getStatus ()); assertEquals (1, error.getErrors (). size ()); assertTrue (error.getErrors (). get (0) .contains ("Podporované metódy sú")); }

A tu je úplná žiadosť:

Metóda požiadavky: DELETE Cesta požiadavky: // localhost: 8080 / spring-security-rest / api / foos / 1

A chybová odpoveď JSON:

{"status": "METHOD_NOT_ALLOWED", "message": "Metóda požiadavky 'DELETE' nie je podporovaná", "chyby": ["Metóda DELETE nie je pre túto požiadavku podporovaná. Podporované metódy sú GET"]}

6. Rukoväť HttpMediaTypeNotSupportedException

Poďme na to HttpMediaTypeNotSupportedException - ku ktorej dôjde, keď klient pošle žiadosť s nepodporovaným typom média - nasledovne:

@Override chránený ResponseEntity handleHttpMediaTypeNotSupported (HttpMediaTypeNotSupportedException ex, hlavičky HttpHeaders, stav HttpStatus, požiadavka WebRequest) {StringBuilder builder = nový StringBuilder (); builder.append (ex.getContentType ()); builder.append ("typ média nie je podporovaný. Podporované typy médií sú"); ex.getSupportedMediaTypes (). forEach (t -> builder.append (t + ",")); ApiError apiError = nový ApiError (HttpStatus.UNSUPPORTED_MEDIA_TYPE, ex.getLocalizedMessage (), builder.substring (0, builder.length () - 2)); návrat new ResponseEntity (apiError, new HttpHeaders (), apiError.getStatus ()); }

Tu je jednoduchý test, ktorý sa zaoberá týmto problémom:

@Test public void whenSendInvalidHttpMediaType_thenUnsupportedMediaType () {Response response = givenAuth (). Body (""). Post (URL_PREFIX + "/ api / foos"); Chyba ApiError = response.as (ApiError.class); assertEquals (HttpStatus.UNSUPPORTED_MEDIA_TYPE, error.getStatus ()); assertEquals (1, error.getErrors (). size ()); assertTrue (error.getErrors (). get (0) .contains ("typ média nie je podporovaný")); }

Na záver - tu je vzorová požiadavka:

Metóda žiadosti: POST Cesta žiadosti: // localhost: 8080 / spring-security- Hlavičky: Content-Type = text / plain; znaková sada = ISO-8859-1

A chybová odpoveď JSON:

{"status": "UNSUPPORTED_MEDIA_TYPE", "message": "Typ obsahu 'text / plain; charset = ISO-8859-1' nie je podporovaný", "errors": ["text / plain; charset = ISO-8859-1" typ média nie je podporovaný. Podporované typy médií sú text / aplikácia xml / aplikácia x-www-form-urlen / * + aplikácia xml / json; charset = aplikácia UTF-8 / * + json; charset = UTF-8 * / " ]}

7. Predvolený obslužný program

Na záver poďme implementovať záložnú obslužnú rutinu - univerzálny typ logiky, ktorý sa zaoberá všetkými ostatnými výnimkami, ktoré nemajú konkrétne obslužné programy:

@ExceptionHandler ({Exception.class}) public ResponseEntity handleAll (Exception ex, WebRequest request) {ApiError apiError = new ApiError (HttpStatus.INTERNAL_SERVER_ERROR, ex.getLocalizedMessage (), "vyskytla sa chyba"); návrat new ResponseEntity (apiError, new HttpHeaders (), apiError.getStatus ()); }

8. Záver

Vytvorenie správneho a vyspelého obslužného programu chýb pre Spring REST API je náročný a určite iteračný proces. Dúfajme, že tento výukový program bude dobrým východiskovým bodom k tomu, aby ste to mohli urobiť pre svoje API, a tiež dobrým ukotvením toho, ako by ste sa mali pozerať na to, ako pomáhať klientom vášho API rýchlo a ľahko diagnostikovať chyby a prechádzať okolo nich.

The úplná implementácia tohto tutoriálu nájdete v projekte Github - jedná sa o projekt založený na Eclipse, takže by malo byť ľahké ho importovať a spustiť tak, ako je.

REST spodok

Práve som oznámil nové Naučte sa jar kurz zameraný na základy jari 5 a Spring Boot 2:

>> SKONTROLUJTE KURZ

$config[zx-auto] not found$config[zx-overlay] not found