Vlastné spracovanie chybových správ pre REST API
Práve som oznámil nové Naučte sa jar kurz zameraný na základy jari 5 a Spring Boot 2:
>> SKONTROLUJTE KURZ1. 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