Tilpasset feilmeldingshåndtering for REST API
Jeg kunngjorde nettopp det nye Lær våren kurs, med fokus på det grunnleggende i vår 5 og vårstøvel 2:
>> KONTROLLER KURSET1. Oversikt
I denne veiledningen vil vi diskutere hvordan du implementerer en global feilbehandler for en Spring REST API.
Vi vil bruke semantikken til hvert unntak for å bygge ut meningsfylte feilmeldinger for klienten, med det klare målet å gi den klienten all informasjonen for å enkelt diagnostisere problemet.
2. En egendefinert feilmelding
La oss starte med å implementere en enkel struktur for å sende feil over ledningen - the ApiError:
offentlig klasse ApiError {privat HttpStatus-status; privat strengmelding; private List-feil; offentlig ApiError (HttpStatus-status, strengmelding, listefeil) {super (); this.status = status; denne meldingen = melding; this.errors = feil; } offentlig ApiError (HttpStatus-status, strengmelding, strengfeil) {super (); this.status = status; denne meldingen = melding; feil = Arrays.asList (feil); }}
Informasjonen her skal være grei:
- status: HTTP-statuskoden
- beskjed: feilmeldingen assosiert med unntak
- feil: Liste over konstruerte feilmeldinger
Og selvfølgelig, for den faktiske unntakshåndteringslogikken våren, bruker vi @ControllerAdvice kommentar:
@ControllerAdvice offentlig klasse CustomRestExceptionHandler utvider ResponseEntityExceptionHandler {...}
3. Håndter unntak fra dårlige forespørsler
3.1. Håndtering av unntakene
La oss nå se hvordan vi kan håndtere de vanligste klientfeilene - i utgangspunktet sendte en klient scenarioer en ugyldig forespørsel til API:
- BindException: Dette unntaket blir kastet når det oppstår fatale bindingsfeil.
MethodArgumentNotValidException: Dette unntaket kastes når argumentet kommenteres med @Gyldig mislykket validering:
@ Override-beskyttet ResponseEntity handleMethodArgumentNotValid (MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {List feil = ny ArrayList (); for (FieldError error: ex.getBindingResult (). getFieldErrors ()) {feil.add (error.getField () + ":" + error.getDefaultMessage ()); } for (ObjectError-feil: ex.getBindingResult (). getGlobalErrors ()) {feil.add (error.getObjectName () + ":" + error.getDefaultMessage ()); } ApiError apiError = ny ApiError (HttpStatus.BAD_REQUEST, ex.getLocalizedMessage (), feil); return handleExceptionInternal (ex, apiError, headers, apiError.getStatus (), forespørsel); }
Som du kan se, vi overstyrer en basemetode ut av ResponseEntityExceptionHandler og tilby vår egen tilpassede implementering.
Det vil ikke alltid være tilfelle - noen ganger trenger vi å håndtere et tilpasset unntak som ikke har en standardimplementering i basisklassen, som vi får se senere her.
Neste:
MissingServletRequestPartException: Dette unntaket blir kastet når den delen av en flerdelt forespørsel ikke ble funnet
MissingServletRequestParameterException: Dette unntaket kastes når forespørselen mangler parameter:
@ Override-beskyttet ResponseEntity handleMissingServletRequestParameter (MissingServletRequestParameterException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {Strengfeil = ex.getParameterName () + "parameter mangler"; ApiError apiError = ny ApiError (HttpStatus.BAD_REQUEST, ex.getLocalizedMessage (), feil); returner nye ResponseEntity (apiError, nye HttpHeaders (), apiError.getStatus ()); }
ConstrainViolationException: Dette unntaket rapporterer resultatet av tvangsbrudd:
@ExceptionHandler ({ConstraintViolationException.class}) public ResponseEntity handleConstraintViolation (ConstraintViolationException ex, WebRequest request) {List feil = ny ArrayList (); for (ConstraintViolation brudd: ex.getConstraintViolations ()) {feil.add (violation.getRootBeanClass (). getName () + "" + violation.getPropertyPath () + ":" + violation.getMessage ()); } ApiError apiError = ny ApiError (HttpStatus.BAD_REQUEST, ex.getLocalizedMessage (), feil); returner ny ResponseEntity (apiError, nye HttpHeaders (), apiError.getStatus ()); }
TypeMismatchException: Dette unntaket kastes når du prøver å angi bønneegenskap med feil type.
MethodArgumentTypeMismatchException: Dette unntaket kastes når metodeargument ikke er den forventede typen:
@ExceptionHandler ({MethodArgumentTypeMismatchException.class}) public ResponseEntity handleMethodArgumentTypeMismatch (MethodArgumentTypeMismatchException ex, WebRequest request) {Strengfeil = ex.getName () + "skal være av typen" + ex.getRequiredType (). ApiError apiError = ny ApiError (HttpStatus.BAD_REQUEST, ex.getLocalizedMessage (), feil); returner nye ResponseEntity (apiError, nye HttpHeaders (), apiError.getStatus ()); }
3.2. Forbruker API fra klienten
La oss nå ta en titt på en test som løper inn i en MethodArgumentTypeMismatchException: vi vil send en forespørsel med id som String i stedet for lang:
@Test offentlig ugyldig nårMethodArgumentMismatch_thenBadRequest () {Response response = givenAuth (). Get (URL_PREFIX + "/ api / foos / ccc"); ApiError feil = 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 av typen")); }
Og til slutt - vurderer denne samme forespørselen::
Forespørselsmetode: GET Forespørselssti: // localhost: 8080 / spring-security-rest / api / foos / ccc
Slik ser denne typen JSON-feilrespons ut:
{"status": "BAD_REQUEST", "message": "Kunne ikke konvertere verdien av typen [java.lang.String] til ønsket type [java.lang.Long]; nestet unntak er java.lang.NumberFormatException: For inngangsstreng : \ "ccc \" "," feil ": [" id skal være av typen java.lang.Long "]}
4. Håndtak NoHandlerFoundException
Deretter kan vi tilpasse servletten vår for å kaste dette unntaket i stedet for å sende 404-svar - som følger:
api org.springframework.web.servlet.DispatcherServlet throwExceptionIfNoHandlerFound true
Så når dette skjer, kan vi bare håndtere det akkurat som ethvert annet unntak:
@ Override-beskyttet ResponseEntity handleNoHandlerFoundException (NoHandlerFoundException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {String error = "No handler found for" + ex.getHttpMethod () + "" + ex.getRequestURL (); ApiError apiError = ny ApiError (HttpStatus.NOT_FOUND, ex.getLocalizedMessage (), feil); returner ny ResponseEntity (apiError, nye HttpHeaders (), apiError.getStatus ()); }
Her er en enkel test:
@Test offentlig ugyldig nårNoHandlerForHttpRequest_thenNotFound () {Response response = givenAuth (). Delete (URL_PREFIX + "/ api / xx"); ApiError feil = respons.as (ApiError.class); assertEquals (HttpStatus.NOT_FOUND, error.getStatus ()); assertEquals (1, error.getErrors (). størrelse ()); assertTrue (error.getErrors (). get (0) .contains ("Ingen behandler funnet")); }
La oss ta en titt på den fullstendige forespørselen:
Forespørselsmetode: SLETT Forespørselssti: // localhost: 8080 / spring-security-rest / api / xx
Og feil JSON-svar:
{"status": "NOT_FOUND", "message": "Ingen behandler funnet for DELETE / spring-security-rest / api / xx", "feil": ["Ingen handler funnet for DELETE / spring-security-rest / api / xx "]}
5. Håndtak HttpRequestMethodNotSupportedException
Deretter, la oss ta en titt på et annet interessant unntak - HttpRequestMethodNotSupportedException - som oppstår når du sender en forespurt med en HTTP-metode som ikke støttes:
@ Override-beskyttet ResponseEntity handleHttpRequestMethodNotSupported (HttpRequestMethodNotSupportedException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {StringBuilder builder = new StringBuilder (); builder.append (ex.getMethod ()); builder.append ("metoden støttes ikke for denne forespørselen. Stø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 enkel test som gjengir dette unntaket:
@Test offentlig ugyldig nårHttpRequestMethodNotSupported_thenMethodNotAllowed () {Response response = givenAuth (). Delete (URL_PREFIX + "/ api / foos / 1"); ApiError feil = respons.as (ApiError.class); assertEquals (HttpStatus.METHOD_NOT_ALLOWED, error.getStatus ()); assertEquals (1, error.getErrors (). størrelse ()); assertTrue (error.getErrors (). get (0) .contains ("Støttede metoder er")); }
Og her er den fulle forespørselen:
Forespørselsmetode: SLETT Forespørselssti: // localhost: 8080 / spring-security-rest / api / foos / 1
Og feil JSON-svar:
{"status": "METHOD_NOT_ALLOWED", "message": "Forespørselsmetode 'DELETE' støttes ikke", "feil": ["SLETT-metoden støttes ikke for denne forespørselen. Støttede metoder er GET"]}
6. Håndtak HttpMediaTypeNotSupportedException
Nå, la oss håndtere HttpMediaTypeNotSupportedException - som oppstår når klienten sender en forespørsel med medietype som ikke støttes - 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 støttes ikke. Stø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 enkel test som går inn i dette problemet:
@Test offentlig ugyldig nårSendInvalidHttpMediaType_thenUnsupportedMediaType () {Response response = givenAuth (). Body (""). Post (URL_PREFIX + "/ api / foos"); ApiError feil = respons.as (ApiError.class); assertEquals (HttpStatus.UNSUPPORTED_MEDIA_TYPE, error.getStatus ()); assertEquals (1, error.getErrors (). størrelse ()); assertTrue (error.getErrors (). get (0) .contains ("medietypen støttes ikke")); }
Til slutt - her er en prøveforespørsel:
Forespørselsmetode: POST Forespørselssti: // localhost: 8080 / spring-security- Overskrifter: Content-Type = text / plain; charset = ISO-8859-1
Og feil JSON-svar:
{"status": "UNSUPPORTED_MEDIA_TYPE", "message": "Innholdstype 'tekst / vanlig; charset = ISO-8859-1' støttes ikke", "feil": ["text / plain; charset = ISO-8859-1 medietype støttes ikke. Støttede medietyper er tekst / xml-applikasjon / x-www-form-urlenkodet applikasjon / * + xml-applikasjon / json; charset = UTF-8-program / * + json; charset = UTF-8 * / " ]}
7. Standard behandler
Til slutt, la oss implementere en tilbakeførselshåndterer - en alt-i-ett-type logikk som omhandler alle andre unntak som ikke har spesifikke håndtere:
@ExceptionHandler ({Exception.class}) public ResponseEntity handleAll (Exception ex, WebRequest request) {ApiError apiError = new ApiError (HttpStatus.INTERNAL_SERVER_ERROR, ex.getLocalizedMessage (), "feil oppstod"); returner nye ResponseEntity (apiError, nye HttpHeaders (), apiError.getStatus ()); }
8. Konklusjon
Å bygge en riktig, moden feilbehandler for en Spring REST API er vanskelig og definitivt en iterativ prosess. Forhåpentligvis vil denne opplæringen være et godt utgangspunkt for å gjøre det for API-en, og også et godt anker for hvordan du skal se på å hjelpe kundene til API-et raskt og enkelt å diagnostisere feil og gå forbi dem.
De full gjennomføring av denne veiledningen finner du i Github-prosjektet - dette er et formørkelsesbasert prosjekt, så det skal være enkelt å importere og kjøre som det er.
HVILLE bunnen