Håndtering af brugerdefineret fejlmeddelelse til REST API

REST Top

Jeg har lige annonceret det nye Lær foråret kursus med fokus på det grundlæggende i Spring 5 og Spring Boot 2:

>> KONTROLLER KURSEN

1. Oversigt

I denne vejledning diskuterer vi, hvordan du implementerer en global fejlhåndterer til en Spring REST API.

Vi bruger semantikken i hver undtagelse til at opbygge meningsfulde fejlmeddelelser til klienten med det klare mål at give denne klient al den information, der let kan diagnosticere problemet.

2. En brugerdefineret fejlmeddelelse

Lad os starte med at implementere en simpel struktur til at sende fejl over ledningen - the ApiError:

offentlig klasse ApiError {privat HttpStatus-status; privat streng besked; private List-fejl; offentlig ApiError (HttpStatus-status, strengmeddelelse, listefejl) {super (); this.status = status; denne besked = besked; this.errors = fejl; } offentlig ApiError (HttpStatus-status, strengmeddelelse, strengfejl) {super (); this.status = status; denne besked = besked; fejl = Arrays.asList (fejl); }}

Oplysningerne her skal være ligetil:

  • status: HTTP-statuskoden
  • besked: fejlmeddelelsen forbundet med undtagelse
  • fejl: Liste over konstruerede fejlmeddelelser

Og selvfølgelig bruger vi den for den faktiske undtagelseshåndteringslogik i foråret @ControllerAdvice kommentar:

@ControllerAdvice offentlig klasse CustomRestExceptionHandler udvider ResponseEntityExceptionHandler {...}

3. Håndter undtagelser fra dårlige anmodninger

3.1. Håndtering af undtagelserne

Lad os nu se, hvordan vi kan håndtere de mest almindelige klientfejl - grundlæggende scenarier for en klient sendte en ugyldig anmodning til API:

  • BindException: Denne undtagelse kastes, når der opstår fatale bindingsfejl.
  • MethodArgumentNotValidException: Denne undtagelse kastes, når argumentet kommenteres med @Gyldig mislykket validering:

@ Override-beskyttet ResponseEntity handleMethodArgumentNotValid (MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {Listefejl = ny ArrayList (); for (FieldError-fejl: ex.getBindingResult (). getFieldErrors ()) {fejl.add (error.getField () + ":" + error.getDefaultMessage ()); } for (ObjectError-fejl: ex.getBindingResult (). getGlobalErrors ()) {fejl.add (error.getObjectName () + ":" + error.getDefaultMessage ()); } ApiError apiError = ny ApiError (HttpStatus.BAD_REQUEST, ex.getLocalizedMessage (), fejl); return handleExceptionInternal (ex, apiError, headers, apiError.getStatus (), anmodning); } 

Som du kan se, vi tilsidesætter en basemetode ud af ResponseEntityExceptionHandler og leverer vores egen brugerdefinerede implementering.

Det vil ikke altid være tilfældet - nogle gange bliver vi nødt til at håndtere en brugerdefineret undtagelse, der ikke har en standardimplementering i basisklassen, som vi får se senere her.

Næste:

  • MissingServletRequestPartException: Denne undtagelse kastes, når den del af en anmodning om flere dele ikke blev fundet

  • MissingServletRequestParameterException: Denne undtagelse kastes, når anmodningen mangler parameter:

@ Override beskyttet ResponseEntity handleMissingServletRequestParameter (MissingServletRequestParameterException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {String error = ex.getParameterName () + "parameter mangler"; ApiError apiError = ny ApiError (HttpStatus.BAD_REQUEST, ex.getLocalizedMessage (), fejl); returner ny ResponseEntity (apiError, nye HttpHeaders (), apiError.getStatus ()); }
  • ConstrainViolationException: Denne undtagelse rapporterer resultatet af overtrædelser af begrænsninger:

@ExceptionHandler ({ConstraintViolationException.class}) public ResponseEntity handleConstraintViolation (ConstraintViolationException ex, WebRequest anmodning) {Liste fejl = ny ArrayList (); for (ConstraintViolation overtrædelse: ex.getConstraintViolations ()) {fejl.add (violation.getRootBeanClass (). getName () + "" + violation.getPropertyPath () + ":" + violation.getMessage ()); } ApiError apiError = ny ApiError (HttpStatus.BAD_REQUEST, ex.getLocalizedMessage (), fejl); returner ny ResponseEntity (apiError, nye HttpHeaders (), apiError.getStatus ()); }
  • TypeMismatchException: Denne undtagelse kastes, når du prøver at indstille bønneegenskaber med forkert type.

  • MethodArgumentTypeMismatchException: Denne undtagelse kastes, når metodeargument ikke er den forventede type:

@ExceptionHandler ({MethodArgumentTypeMismatchException.class}) public ResponseEntity handleMethodArgumentTypeMismatch (MethodArgumentTypeMismatchException ex, WebRequest request) {String error = ex.getName () + "should be of type" + ex.getRequiredType (); ApiError apiError = ny ApiError (HttpStatus.BAD_REQUEST, ex.getLocalizedMessage (), fejl); returner ny ResponseEntity (apiError, nye HttpHeaders (), apiError.getStatus ()); }

3.2. Forbruger API fra klienten

Lad os nu se på en test, der løber ind i en MethodArgumentTypeMismatchException: godt send en anmodning med id som Snor i stedet for lang:

@Test offentlig ugyldig nårMethodArgumentMismatch_thenBadRequest () {Response response = givenAuth (). Get (URL_PREFIX + "/ api / foos / ccc"); ApiError-fejl = respons.as (ApiError.class); assertEquals (HttpStatus.BAD_REQUEST, error.getStatus ()); assertEquals (1, error.getErrors (). størrelse ()); assertTrue (error.getErrors (). get (0) .contains ("skal være af typen")); }

Og endelig - i betragtning af den samme anmodning::

Anmodningsmetode: GET anmodningssti: // localhost: 8080 / spring-security-rest / api / foos / ccc 

Sådan ser denne type JSON-fejlrespons ud:

{"status": "BAD_REQUEST", "message": "Kunne ikke konvertere værdien af ​​typen [java.lang.String] til den krævede type [java.lang.Long]; indlejret undtagelse er java.lang.NumberFormatException: For inputstreng : \ "ccc \" "," fejl ": [" id skal være af typen java.lang.Long "]}

4. Håndtag NoHandlerFoundException

Dernæst kan vi tilpasse vores servlet til at kaste denne undtagelse i stedet for at sende 404-svar - som følger:

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

Så når dette sker, kan vi simpelthen håndtere det som enhver anden undtagelse:

@ Override beskyttet ResponseEntity handleNoHandlerFoundException (NoHandlerFoundException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {String error = "Ingen handler fundet for" + ex.getHttpMethod () + "" + ex.getRequestURL (); ApiError apiError = ny ApiError (HttpStatus.NOT_FOUND, ex.getLocalizedMessage (), fejl); returner ny ResponseEntity (apiError, nye HttpHeaders (), apiError.getStatus ()); }

Her er en simpel test:

@Test offentlig ugyldig nårNoHandlerForHttpRequest_thenNotFound () {Response response = givenAuth (). Delete (URL_PREFIX + "/ api / xx"); ApiError-fejl = respons.as (ApiError.class); assertEquals (HttpStatus.NOT_FOUND, error.getStatus ()); assertEquals (1, error.getErrors (). størrelse ()); assertTrue (error.getErrors (). get (0) .contains ("Ingen handler fundet")); }

Lad os se på den fulde anmodning:

Anmodningsmetode: SLET Anmodningssti: // localhost: 8080 / spring-security-rest / api / xx

Og fejl JSON-svar:

{"status": "NOT_FOUND", "message": "Ingen handler fundet for SLET / fjeder-sikkerhed-hvile / api / xx", "fejl": ["Ingen behandler fundet for SLET / fjeder-sikkerhed-hvile / api / xx "]}

5. Håndtag HttpRequestMethodNotSupportedException

Lad os derefter se på en anden interessant undtagelse - HttpRequestMethodNotSupportedException - som opstår, når du sender en anmodning med en ikke-understøttet HTTP-metode:

@ Override beskyttet ResponseEntity handleHttpRequestMethodNotSupported (HttpRequestMethodNotSupportedException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {StringBuilder builder = new StringBuilder (); builder.append (ex.getMethod ()); builder.append ("metoden understøttes ikke til denne anmodning. Understøttede metoder er"); ex.getSupportedHttpMethods (). forEach (t -> builder.append (t + "")); ApiError apiError = ny ApiError (HttpStatus.METHOD_NOT_ALLOWED, ex.getLocalizedMessage (), builder.toString ()); returner ny ResponseEntity (apiError, nye HttpHeaders (), apiError.getStatus ()); }

Her er en simpel test, der gengiver denne undtagelse:

@Test offentlig ugyldig nårHttpRequestMethodNotSupported_thenMethodNotAllowed () {Response response = givenAuth (). Delete (URL_PREFIX + "/ api / foos / 1"); ApiError-fejl = respons.as (ApiError.class); assertEquals (HttpStatus.METHOD_NOT_ALLOWED, error.getStatus ()); assertEquals (1, error.getErrors (). størrelse ()); assertTrue (error.getErrors (). get (0) .contains ("Understøttede metoder er")); }

Og her er den fulde anmodning:

Anmodningsmetode: SLET Anmodningssti: // localhost: 8080 / spring-security-rest / api / foos / 1

Og fejlen JSON-svar:

{"status": "METHOD_NOT_ALLOWED", "message": "Anmodningsmetode 'DELETE' understøttes ikke", "fejl": ["DELETE-metoden understøttes ikke til denne anmodning. Understøttede metoder er GET"]}

6. Håndtag HttpMediaTypeNotSupportedException

Lad os nu håndtere HttpMediaTypeNotSupportedException - der opstår, når klienten sender en anmodning med ikke-understøttet medietype - som følger:

@ Override beskyttet ResponseEntity handleHttpMediaTypeNotSupported (HttpMediaTypeNotSupportedException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {StringBuilder builder = new StringBuilder (); builder.append (ex.getContentType ()); builder.append ("medietype understøttes ikke. Understøttede medietyper er"); ex.getSupportedMediaTypes (). forEach (t -> builder.append (t + ",")); ApiError apiError = ny ApiError (HttpStatus.UNSUPPORTED_MEDIA_TYPE, ex.getLocalizedMessage (), builder.substring (0, builder.length () - 2)); returner ny ResponseEntity (apiError, nye HttpHeaders (), apiError.getStatus ()); }

Her er en simpel test, der løber ind i dette problem:

@Test offentligt ugyldigt nårSendInvalidHttpMediaType_thenUnsupportedMediaType () {Response response = givenAuth (). Body (""). Post (URL_PREFIX + "/ api / foos"); ApiError-fejl = respons.as (ApiError.class); assertEquals (HttpStatus.UNSUPPORTED_MEDIA_TYPE, error.getStatus ()); assertEquals (1, error.getErrors (). størrelse ()); assertTrue (error.getErrors (). get (0) .contains ("medietype understøttes ikke")); }

Endelig - her er en prøveanmodning:

Anmodningsmetode: POST Anmodningssti: // localhost: 8080 / spring-security- Headers: Content-Type = text / plain; tegnsæt = ISO-8859-1

Og fejlen JSON-svar:

{"status": "UNSUPPORTED_MEDIA_TYPE", "message": "Indholdstype 'tekst / almindelig; charset = ISO-8859-1' understøttes ikke", "fejl": ["tekst / almindelig; charset = ISO-8859-1 medietype understøttes ikke. Understøttede medietyper er tekst / xml-applikation / x-www-form-urlenkodet applikation / * + xml-applikation / json; charset = UTF-8-applikation / * + json; charset = UTF-8 * / " ]}

7. Standardhandler

Endelig lad os implementere en tilbageføringshåndterer - en logisk type, der tager fat på alle andre undtagelser, der ikke har specifikke håndtere:

@ExceptionHandler ({Exception.class}) public ResponseEntity handleAll (Exception ex, WebRequest request) {ApiError apiError = new ApiError (HttpStatus.INTERNAL_SERVER_ERROR, ex.getLocalizedMessage (), "fejl opstod"); returner ny ResponseEntity (apiError, nye HttpHeaders (), apiError.getStatus ()); }

8. Konklusion

Opbygning af en ordentlig, moden fejlhåndterer til en Spring REST API er hård og bestemt en iterativ proces. Forhåbentlig vil denne tutorial være et godt udgangspunkt for at gøre det for din API og også et godt anker for, hvordan du skal se på at hjælpe klienterne i din API hurtigt og nemt diagnosticere fejl og gå forbi dem.

Det fuld implementering af denne vejledning kan findes i Github-projektet - dette er et Eclipse-baseret projekt, så det skal være let at importere og køre som det er.

REST bunden

Jeg har lige annonceret det nye Lær foråret kursus med fokus på det grundlæggende i Spring 5 og Spring Boot 2:

>> KONTROLLER KURSEN

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