Afhandeling van aangepaste foutmeldingen voor REST API

REST Top

Ik heb zojuist het nieuwe aangekondigd Leer de lente natuurlijk, gericht op de basisprincipes van Spring 5 en Spring Boot 2:

>> BEKIJK DE CURSUS

1. Overzicht

In deze zelfstudie bespreken we hoe u een algemene foutafhandelaar implementeert voor een Spring REST API.

We zullen de semantiek van elke uitzondering gebruiken om betekenisvolle foutmeldingen voor de klant op te bouwen, met het duidelijke doel om die klant alle informatie te geven om het probleem gemakkelijk te diagnosticeren.

2. Een aangepast foutbericht

Laten we beginnen met het implementeren van een eenvoudige structuur voor het verzenden van fouten over de draad - de ApiError:

openbare klasse ApiError {privé HttpStatus-status; privé String-bericht; privélijstfouten; openbare ApiError (HttpStatus-status, String-bericht, lijstfouten) {super (); this.status = status; this.message = bericht; this.errors = fouten; } openbare ApiError (HttpStatus-status, String-bericht, String-fout) {super (); this.status = status; this.message = bericht; errors = Arrays.asList (fout); }}

De informatie hier zou duidelijk moeten zijn:

  • toestand: de HTTP-statuscode
  • bericht: het foutbericht dat is gekoppeld aan een uitzondering
  • fout: Lijst met geconstrueerde foutmeldingen

En natuurlijk gebruiken we voor de feitelijke logica voor het afhandelen van uitzonderingen in Spring de @ControllerAdvice annotatie:

@ControllerAdvice openbare klasse CustomRestExceptionHandler breidt ResponseEntityExceptionHandler {...}

3. Behandel uitzonderingen voor slechte verzoeken

3.1. Omgaan met de uitzonderingen

Laten we nu eens kijken hoe we de meest voorkomende clientfouten kunnen aanpakken - in feite scenario's waarin een client een ongeldig verzoek naar de API heeft gestuurd:

  • BindException: Deze uitzondering wordt gegenereerd wanneer er fatale bindingsfouten optreden.
  • MethodArgumentNotValidException: Deze uitzondering wordt gegenereerd wanneer het argument wordt geannoteerd met @Geldig mislukte validatie:

@Override beschermde ResponseEntity handleMethodArgumentNotValid (MethodArgumentNotValidException ex, HttpHeaders-headers, HttpStatus-status, WebRequest-verzoek) {Lijstfouten = nieuwe ArrayList (); voor (FieldError-fout: ex.getBindingResult (). getFieldErrors ()) {errors.add (error.getField () + ":" + error.getDefaultMessage ()); } voor (ObjectError-fout: ex.getBindingResult (). getGlobalErrors ()) {errors.add (error.getObjectName () + ":" + error.getDefaultMessage ()); } ApiError apiError = nieuwe ApiError (HttpStatus.BAD_REQUEST, ex.getLocalizedMessage (), fouten); return handleExceptionInternal (ex, apiError, headers, apiError.getStatus (), request); } 

Zoals je kunt zien, we overschrijven een basismethode uit de ResponseEntityExceptionHandler en het verzorgen van onze eigen maatwerk implementatie.

Dat zal niet altijd het geval zijn - soms moeten we een aangepaste uitzondering afhandelen die geen standaardimplementatie heeft in de basisklasse, zoals we later hier zullen zien.

De volgende:

  • MissingServletRequestPartException: Deze uitzondering wordt gegenereerd wanneer het deel van een uit meerdere delen bestaande aanvraag niet wordt gevonden

  • MissingServletRequestParameterException: Deze uitzondering wordt gegenereerd wanneer een ontbrekende parameter wordt aangevraagd:

@Override beschermde ResponseEntity handleMissingServletRequestParameter (MissingServletRequestParameterException ex, HttpHeaders headers, HttpStatus status, WebRequest-verzoek) {String error = ex.getParameterName () + "parameter ontbreekt"; ApiError apiError = nieuwe ApiError (HttpStatus.BAD_REQUEST, ex.getLocalizedMessage (), fout); retourneer nieuwe ResponseEntity (apiError, nieuwe HttpHeaders (), apiError.getStatus ()); }
  • ConstrainViolationException: Deze uitzondering rapporteert het resultaat van schendingen van beperkingen:

@ExceptionHandler ({ConstraintViolationException.class}) openbare ResponseEntity handleConstraintViolation (ConstraintViolationException ex, WebRequest-verzoek) {Lijstfouten = nieuwe ArrayList (); voor (ConstraintViolation-schending: ex.getConstraintViolations ()) {errors.add (schending.getRootBeanClass (). getName () + "" + schending.getPropertyPath () + ":" + schending.getMessage ()); } ApiError apiError = nieuwe ApiError (HttpStatus.BAD_REQUEST, ex.getLocalizedMessage (), fouten); retourneer nieuwe ResponseEntity (apiError, nieuwe HttpHeaders (), apiError.getStatus ()); }
  • TypeMismatchException: Deze uitzondering wordt gegenereerd wanneer wordt geprobeerd de bean-eigenschap in te stellen met het verkeerde type.

  • MethodArgumentTypeMismatchException: Deze uitzondering wordt gegenereerd als het methode-argument niet het verwachte type is:

@ExceptionHandler ({MethodArgumentTypeMismatchException.class}) openbare ResponseEntity handleMethodArgumentTypeMismatch (MethodArgumentTypeMismatchException ex, WebRequest-verzoek) {String error = ex.getName () + "moet van het type" + ex.getRequiredType (). GetName () zijn; ApiError apiError = nieuwe ApiError (HttpStatus.BAD_REQUEST, ex.getLocalizedMessage (), fout); retourneer nieuwe ResponseEntity (apiError, nieuwe HttpHeaders (), apiError.getStatus ()); }

3.2. De API van de klant gebruiken

Laten we nu eens kijken naar een test die tegen een MethodArgumentTypeMismatchException: goed stuur een verzoek met ID kaart net zo Draad in plaats van lang:

@Test public void whenMethodArgumentMismatch_thenBadRequest () {Response response = givenAuth (). Get (URL_PREFIX + "/ api / foos / ccc"); ApiError-fout = response.as (ApiError.class); assertEquals (HttpStatus.BAD_REQUEST, error.getStatus ()); assertEquals (1, error.getErrors (). size ()); assertTrue (error.getErrors (). get (0) .contains ("zou van het type moeten zijn")); }

En tot slot - gezien hetzelfde verzoek:

Verzoekmethode: GET Verzoekpad: // localhost: 8080 / spring-security-rest / api / foos / ccc 

Hier is hoe dit soort JSON-foutreactie eruit zal zien:

{"status": "BAD_REQUEST", "message": "Kan waarde van type [java.lang.String] niet converteren naar vereist type [java.lang.Long]; geneste uitzondering is java.lang.NumberFormatException: voor invoertekenreeks : \ "ccc \" "," errors ": [" id moet van het type java.lang.Long zijn "]}

4. Handvat NoHandlerFoundException

Vervolgens kunnen we onze servlet aanpassen om deze uitzondering te genereren in plaats van een 404-reactie te verzenden - als volgt:

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

Als dit eenmaal gebeurt, kunnen we het gewoon afhandelen, net als elke andere uitzondering:

@Override beschermde ResponseEntity handleNoHandlerFoundException (NoHandlerFoundException ex, HttpHeaders headers, HttpStatus status, WebRequest-verzoek) {String error = "Geen handler gevonden voor" + ex.getHttpMethod () + "" + ex.getRequestURL (); ApiError apiError = nieuwe ApiError (HttpStatus.NOT_FOUND, ex.getLocalizedMessage (), fout); retourneer nieuwe ResponseEntity (apiError, nieuwe HttpHeaders (), apiError.getStatus ()); }

Hier is een eenvoudige test:

@Test openbare ongeldigheid whenNoHandlerForHttpRequest_thenNotFound () {Reactie respons = givenAuth (). Delete (URL_PREFIX + "/ api / xx"); ApiError-fout = response.as (ApiError.class); assertEquals (HttpStatus.NOT_FOUND, error.getStatus ()); assertEquals (1, error.getErrors (). size ()); assertTrue (error.getErrors (). get (0) .contains ("Geen handler gevonden")); }

Laten we eens kijken naar het volledige verzoek:

Verzoekmethode: DELETE Verzoekpad: // localhost: 8080 / spring-security-rest / api / xx

En de fout JSON-reactie:

{"status": "NOT_FOUND", "message": "Geen handler gevonden voor DELETE / spring-security-rest / api / xx", "errors": ["Geen handler gevonden voor DELETE / spring-security-rest / api / xx "]}

5. Handvat HttpRequestMethodNotSupportedException

Laten we vervolgens eens kijken naar een andere interessante uitzondering: de HttpRequestMethodNotSupportedException - wat gebeurt wanneer u een verzoek verzendt met een niet-ondersteunde HTTP-methode:

@Override beschermde ResponseEntity handleHttpRequestMethodNotSupported (HttpRequestMethodNotSupportedException ex, HttpHeaders headers, HttpStatus status, WebRequest-verzoek) {StringBuilder builder = nieuwe StringBuilder (); builder.append (ex.getMethod ()); builder.append ("methode wordt niet ondersteund voor dit verzoek. Ondersteunde methoden zijn"); ex.getSupportedHttpMethods (). forEach (t -> builder.append (t + "")); ApiError apiError = nieuwe ApiError (HttpStatus.METHOD_NOT_ALLOWED, ex.getLocalizedMessage (), builder.toString ()); retourneer nieuwe ResponseEntity (apiError, nieuwe HttpHeaders (), apiError.getStatus ()); }

Hier is een eenvoudige test die deze uitzondering reproduceert:

@Test public void whenHttpRequestMethodNotSupported_thenMethodNotAllowed () {Response response = givenAuth (). Delete (URL_PREFIX + "/ api / foos / 1"); ApiError-fout = response.as (ApiError.class); assertEquals (HttpStatus.METHOD_NOT_ALLOWED, error.getStatus ()); assertEquals (1, error.getErrors (). size ()); assertTrue (error.getErrors (). get (0) .contains ("Ondersteunde methoden zijn")); }

En hier is het volledige verzoek:

Verzoekmethode: VERWIJDEREN Verzoekpad: // localhost: 8080 / spring-security-rest / api / foos / 1

En de fout JSON-reactie:

{"status": "METHOD_NOT_ALLOWED", "message": "Verzoekmethode 'DELETE' niet ondersteund", "errors": ["DELETE-methode wordt niet ondersteund voor dit verzoek. Ondersteunde methoden zijn GET"]}

6. Handvat HttpMediaTypeNotSupportedException

Laten we het nu afhandelen HttpMediaTypeNotSupportedException - wat gebeurt wanneer de client een verzoek verzendt met een niet-ondersteund mediatype - als volgt:

@Override beschermde ResponseEntity handleHttpMediaTypeNotSupported (HttpMediaTypeNotSupportedException ex, HttpHeaders headers, HttpStatus status, WebRequest-verzoek) {StringBuilder builder = nieuwe StringBuilder (); builder.append (ex.getContentType ()); builder.append ("mediatype wordt niet ondersteund. Ondersteunde mediatypen zijn"); ex.getSupportedMediaTypes (). forEach (t -> builder.append (t + ",")); ApiError apiError = nieuwe ApiError (HttpStatus.UNSUPPORTED_MEDIA_TYPE, ex.getLocalizedMessage (), builder.substring (0, builder.length () - 2)); retourneer nieuwe ResponseEntity (apiError, nieuwe HttpHeaders (), apiError.getStatus ()); }

Hier is een eenvoudige test die dit probleem tegenkomt:

@Test openbaar ongeldig whenSendInvalidHttpMediaType_thenUnsupportedMediaType () {Response response = givenAuth (). Body (""). Post (URL_PREFIX + "/ api / foos"); ApiError-fout = response.as (ApiError.class); assertEquals (HttpStatus.UNSUPPORTED_MEDIA_TYPE, error.getStatus ()); assertEquals (1, error.getErrors (). size ()); assertTrue (error.getErrors (). get (0) .contains ("mediatype wordt niet ondersteund")); }

Eindelijk - hier is een voorbeeldverzoek:

Verzoekmethode: POST Verzoekpad: // localhost: 8080 / spring-security- Headers: Content-Type = text / plain; karakterset = ISO-8859-1

En de fout JSON-reactie:

{"status": "UNSUPPORTED_MEDIA_TYPE", "message": "Inhoudstype 'text / plain; charset = ISO-8859-1' niet ondersteund", "errors": ["text / plain; charset = ISO-8859-1 mediatype wordt niet ondersteund. Ondersteunde mediatypen zijn text / xml application / x-www-form-urlencoded application / * + xml application / json; charset = UTF-8 application / * + json; charset = UTF-8 * / " ]}

7. Standaardhandler

Laten we tot slot een fall-back handler implementeren - een catch-all type logica dat alle andere uitzonderingen behandelt die geen specifieke handlers hebben:

@ExceptionHandler ({Exception.class}) openbare ResponseEntity handleAll (uitzondering ex, WebRequest-verzoek) {ApiError apiError = nieuwe ApiError (HttpStatus.INTERNAL_SERVER_ERROR, ex.getLocalizedMessage (), "fout opgetreden"); retourneer nieuwe ResponseEntity (apiError, nieuwe HttpHeaders (), apiError.getStatus ()); }

8. Conclusie

Het bouwen van een goede, volwassen foutafhandelaar voor een Spring REST API is moeilijk en zeker een iteratief proces. Hopelijk is deze tutorial een goed startpunt om dat voor je API te doen en ook een goed anker voor hoe je moet kijken naar het helpen van de clients van je API om snel en gemakkelijk fouten te diagnosticeren en voorbij te gaan.

De volledige implementatie van deze tutorial is te vinden in het Github-project - dit is een op Eclipse gebaseerd project, dus het moet gemakkelijk te importeren en uit te voeren zijn zoals het is.

REST onder

Ik heb zojuist het nieuwe aangekondigd Leer de lente natuurlijk, gericht op de basisprincipes van Spring 5 en Spring Boot 2:

>> BEKIJK DE CURSUS

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