טיפול בהודעות שגיאה מותאמות אישית עבור REST API

REST למעלה

רק הכרזתי על החדש למד אביב קורס, המתמקד ביסודות האביב 5 ומגף האביב 2:

>> בדוק את הקורס

1. סקירה כללית

במדריך זה - נדון כיצד ליישם מטפל בשגיאות גלובלי עבור ממשק API של REST של אביב.

נשתמש בסמנטיקה של כל יוצא מן הכלל כדי לבנות הודעות שגיאה משמעותיות עבור הלקוח, במטרה ברורה לתת ללקוח את כל המידע בכדי לאבחן את הבעיה בקלות.

2. הודעת שגיאה מותאמת אישית

נתחיל ביישום מבנה פשוט לשליחת שגיאות דרך החוט - ה- ApiError:

מעמד ציבורי ApiError {סטטוס HttpStatus פרטי; הודעת מחרוזת פרטית; שגיאות רשימה פרטיות; ApiError ציבורי (סטטוס HttpStatus, הודעת מחרוזת, שגיאות רשימה) {super (); this.status = סטטוס; הודעה זו = הודעה; this.errors = שגיאות; } ApiError ציבורי (סטטוס HttpStatus, הודעת מחרוזת, שגיאת מחרוזת) {super (); this.status = סטטוס; הודעה זו = הודעה; שגיאות = Arrays.asList (שגיאה); }}

המידע כאן צריך להיות פשוט:

  • סטָטוּס: קוד מצב ה- HTTP
  • הוֹדָעָה: הודעת השגיאה המשויכת לחריג
  • שְׁגִיאָה: רשימת הודעות שגיאה שנבנו

וכמובן, עבור ההיגיון בפועל של חריגים באביב, נשתמש ב- @ControllerAdvice ביאור:

@ControllerAdvice מחלקה ציבורית CustomRestExceptionHandler מרחיב את ResponseEntityExceptionHandler {...}

3. טיפול בחריגות בקשות לא טובות

3.1. טיפול בחריגים

עכשיו, בואו נראה איך נוכל לטפל בשגיאות הלקוח הנפוצות ביותר - בעצם תרחישים של לקוח שלחו בקשה לא חוקית ל- API:

  • BindException: חריג זה נזרק כאשר מתרחשות טעויות קשירה קטלניות.
  • MethodArgumentNotValidException: חריג זה נזרק כאשר הוויכוח מתואר עם @תָקֵף אימות נכשל:

ResponseEntity handleMethodArgumentNotValid מוגן על ידי @Override (שיטת MethodArgumentNotValidException, כותרות HttpHeaders, מצב HttpStatus, בקשת WebRequest) {שגיאות רשימה = ArrayList חדש (); עבור (שגיאת FieldError: ex.getBindingResult (). getFieldErrors ()) {error.add (error.getField () + ":" + error.getDefaultMessage ()); } עבור (שגיאת ObjectError: ex.getBindingResult (). getGlobalErrors ()) {errors.add (error.getObjectName () + ":" + error.getDefaultMessage ()); } ApiError apiError = ApiError חדש (HttpStatus.BAD_REQUEST, ex.getLocalizedMessage (), שגיאות); return handleExceptionInternal (לשעבר, apiError, כותרות, apiError.getStatus (), בקשה); } 

כפי שאתה יכול לראות, אנו עוקפים שיטת בסיס מתוך ResponseEntityExceptionHandler ומספקים יישום מותאם אישית משלנו.

לא תמיד זה יהיה המקרה - לפעמים נצטרך לטפל בחריג מותאם אישית שאין בו יישום ברירת מחדל בכיתת הבסיס, כפי שנראה בהמשך כאן.

הַבָּא:

  • MissingServletRequestPartException: חריג זה נזרק כאשר כאשר החלק של בקשה מרובת חלקים לא נמצא

  • MissingServletRequestParameterException: חריג זה נזרק כאשר פרמטר חסר בבקשה:

ResponseEntity handle מוגן על ידי @OverrideMissingServletRequestParameter (MissingServletRequestParameterException ex, כותרות HttpHeaders, סטטוס HttpStatus, בקשת WebRequest) {שגיאת מחרוזת = ex.getParameterName () + "חסר"; ApiError apiError = ApiError חדש (HttpStatus.BAD_REQUEST, ex.getLocalizedMessage (), שגיאה); להחזיר ResponseEntity חדש (apiError, HttpHeaders חדשים (), apiError.getStatus ()); }
  • ConstrainViolationException: חריג זה מדווח על תוצאה של הפרות אילוצים:

@ExceptionHandler ({ConstraintViolationException.class}) תגובה ציבורית מטפל באינטנסיות ConstraintViolation (ConstraintViolationException לשעבר, בקשת WebRequest) {שגיאות רשימה = ArrayList חדש (); עבור (ConstraintViolation הפרעה: ex.getConstraintViolations ()) {errors.add (viol.getRootBeanClass (). getName () + "" + violation.getPropertyPath () + ":" + violage.getMessage ()); } ApiError apiError = ApiError חדש (HttpStatus.BAD_REQUEST, ex.getLocalizedMessage (), שגיאות); להחזיר ResponseEntity חדש (apiError, HttpHeaders חדשים (), apiError.getStatus ()); }
  • TypeMismatchException: חריג זה נזרק כשמנסים להגדיר נכס שעועית לסוג שגוי.

  • MethodArgumentTypeMismatchException: חריג זה נזרק כאשר טיעון השיטה אינו מהסוג הצפוי:

@ExceptionHandler ({MethodArgumentTypeMismatchException.class}) ציבורי ResponseEntity handleMethodArgumentTypeMismatch (MethodArgumentTypeMismatchException לשעבר, בקשת WebRequest) {שגיאת מחרוזת = ex.getName () + "צריכה להיות מסוג" + ex.getRequiredType (). ApiError apiError = ApiError חדש (HttpStatus.BAD_REQUEST, ex.getLocalizedMessage (), שגיאה); להחזיר ResponseEntity חדש (apiError, HttpHeaders חדשים (), apiError.getStatus ()); }

3.2. צורכת את ה- API מהלקוח

בואו נסתכל עכשיו על מבחן שנקלע ל MethodArgumentTypeMismatchException: נו שלח בקשה עם תְעוּדַת זֶהוּת כפי ש חוּט במקום ארוך:

@ מבחן ציבורי בטל כאשר MethodArgumentMismatch_thenBadRequest () {תגובה תגובה = givenAuth (). קבל (URL_PREFIX + "/ api / foos / ccc"); שגיאת ApiError = response.as (ApiError.class); assertEquals (HttpStatus.BAD_REQUEST, error.getStatus ()); assertEquals (1, error.getErrors (). size ()); assertTrue (error.getErrors (). get (0) .contains ("צריך להיות מסוג")); }

ולבסוף - בהתחשב באותה בקשה::

שיטת בקשה: GET נתיב בקשה: // localhost: 8080 / spring-security-rest / api / foos / ccc 

כך תיראה סוג זה של תגובת שגיאות JSON:

{"status": "BAD_REQUEST", "message": "נכשל המרת הערך מסוג [java.lang.String] לסוג הנדרש [java.lang.Long]; חריג מקונן הוא java.lang.NumberFormatException: עבור מחרוזת קלט : \ "ccc \" "," שגיאות ": [" המזהה צריך להיות מסוג java.lang.Long "]}

4. ידית NoHandlerFoundException

לאחר מכן, אנו יכולים להתאים אישית את סרוולט שלנו כך שישליך חריג זה במקום לשלוח תגובה 404 - כדלקמן:

 api org.springframework.web.servlet.DispatcherServlet throwExceptionIfNoHandlerFound נכון 

ואז, ברגע שזה קורה, אנחנו יכולים פשוט להתמודד עם זה כמו בכל חריג אחר:

טיפול ב- ResponseEntity מוגן על ידי @OverrideNoHandlerFoundException (NoHandlerFoundException ex, כותרות HttpHeaders, מצב HttpStatus, בקשת WebRequest) {String error = "לא נמצא מטפל עבור" + ex.getHttpMethod () + "" + ex.getRequestURL (); ApiError apiError = ApiError חדש (HttpStatus.NOT_FOUND, ex.getLocalizedMessage (), שגיאה); להחזיר ResponseEntity חדש (apiError, HttpHeaders חדשים (), apiError.getStatus ()); }

הנה מבחן פשוט:

@ מבחן ציבורי בטל כאשר NoHandlerForHttpRequest_thenNotFound () {תגובה תגובה = givenAuth (). מחק (URL_PREFIX + "/ api / xx"); שגיאת ApiError = response.as (ApiError.class); assertEquals (HttpStatus.NOT_FOUND, error.getStatus ()); assertEquals (1, error.getErrors (). size ()); assertTrue (error.getErrors (). get (0) .contains ("לא נמצא מטפל")); }

בואו נסתכל על הבקשה המלאה:

שיטת בקשה: מחק נתיב בקשה: // localhost: 8080 / spring-security-rest / api / xx

וה תגובת JSON לשגיאה:

{"status": "NOT_FOUND", "message": "לא נמצא מטפל עבור DELETE / spring-security-rest / api / xx", "שגיאות": ["לא נמצא מטפל עבור DELETE / spring-security-rest / api / xx "]}

5. ידית HttpRequestMethodNotSupportedException

לאחר מכן, בואו נסתכל על חריג מעניין אחר - ה- HttpRequestMethodNotSupportedException - המתרחש כאשר אתה שולח בקשה בשיטת HTTP שאינה נתמכת:

ידית ResponseEntity מוגנת @OverrideHttpRequestMethodNotSupported (HttpRequestMethodNotSupportedException ex, כותרות HttpHeaders, HttpStatus status, בקשת WebRequest) {בונה StringBuilder = StringBuilder חדש (); builder.append (ex.getMethod ()); builder.append ("השיטה אינה נתמכת לבקשה זו. השיטות הנתמכות הן"); ex.getSupportedHttpMethods (). forEach (t -> builder.append (t + "")); ApiError apiError = ApiError חדש (HttpStatus.METHOD_NOT_ALLOWED, ex.getLocalizedMessage (), builder.toString ()); להחזיר ResponseEntity חדש (apiError, HttpHeaders חדשים (), apiError.getStatus ()); }

הנה מבחן פשוט המשחזר חריג זה:

@ מבחן ציבורי בטל כאשר HttpRequestMethodNotSupported_thenMethodNotAllowed () {תגובה תגובה = givenAuth (). מחק (URL_PREFIX + "/ api / foos / 1"); שגיאת ApiError = response.as (ApiError.class); assertEquals (HttpStatus.METHOD_NOT_ALLOWED, error.getStatus ()); assertEquals (1, error.getErrors (). size ()); assertTrue (error.getErrors (). get (0) .contains ("שיטות נתמכות הן")); }

והנה הבקשה המלאה:

שיטת בקשה: מחק נתיב בקשה: // localhost: 8080 / spring-security-rest / api / foos / 1

וגם תגובת JSON השגיאה:

{"status": "METHOD_NOT_ALLOWED", "message": "שיטת הבקשה 'DELETE' אינה נתמכת", "שגיאות": ["שיטת DELETE אינה נתמכת לבקשה זו. השיטות הנתמכות הן GET"]}

6. ידית HttpMediaTypeNotSupportedException

עכשיו, בואו נתמודד HttpMediaTypeNotSupportedException - המתרחש כאשר הלקוח שולח בקשה עם סוג מדיה שאינו נתמך - כדלקמן:

ידית ResponseEntity מוגנת @OverrideHttpMediaTypeNotSupported (HttpMediaTypeNotSupportedException ex, כותרות HttpHeaders, HttpStatus סטטוס, בקשת WebRequest) {StringBuilder בונה = StringBuilder חדש (); builder.append (למשל.getContentType ()); builder.append ("סוג המדיה אינו נתמך. סוגי המדיה הנתמכים הם"); ex.getSupportedMediaTypes (). forEach (t -> builder.append (t + ",")); ApiError apiError = ApiError חדש (HttpStatus.UNSUPPORTED_MEDIA_TYPE, ex.getLocalizedMessage (), builder.substring (0, builder.length () - 2)); להחזיר ResponseEntity חדש (apiError, HttpHeaders חדשים (), apiError.getStatus ()); }

להלן מבחן פשוט העוסק בסוגיה זו:

@ מבחן ציבורי בטל כאשר SendInvalidHttpMediaType_thenUnsupportedMediaType () {Response response = givenAuth (). Body (""). Post (URL_PREFIX + "/ api / foos"); שגיאת ApiError = response.as (ApiError.class); assertEquals (HttpStatus.UNSUPPORTED_MEDIA_TYPE, error.getStatus ()); assertEquals (1, error.getErrors (). size ()); assertTrue (error.getErrors (). get (0) .contains ("סוג המדיה אינו נתמך")); }

לסיום - הנה בקשה לדוגמא:

שיטת בקשה: POST נתיב בקשה: // localhost: 8080 / spring-security- כותרות: Content-Type = text / plain; ערכת = ISO-8859-1

וגם תגובת JSON השגיאה:

{"status": "UNSUPPORTED_MEDIA_TYPE", "message": "סוג תוכן 'text / plain; charset = ISO-8859-1' לא נתמך", "שגיאות": ["text / plain; charset = ISO-8859-1 סוג המדיה אינו נתמך. סוגי המדיה הנתמכים הם יישום טקסט / xml / יישום x-www-form-urlencoded / * + יישום xml / json; charset = יישום UTF-8 / * + json; charset = UTF-8 * / " ]}

7. מטפל ברירת מחדל

לסיום, בואו נפעיל מטפל נפילה - סוג של לוגיקה הכוללת את כל החריגים האחרים שאין להם מטפלים ספציפיים:

@ExceptionHandler ({Exception.class}) תגובה ResponseEntity ציבורית (Exception ex, בקשת WebRequest) {ApiError apiError = ApiError new (HttpStatus.INTERNAL_SERVER_ERROR, ex.getLocalizedMessage (), "אירעה שגיאה"); להחזיר ResponseEntity חדש (apiError, HttpHeaders חדשים (), apiError.getStatus ()); }

8. מסקנה

בניית מטפל בשגיאות תקין ובוגר עבור ממשק API של REST הוא קשה ובהחלט תהליך איטרטיבי. אני מקווה, מדריך זה יהווה נקודת התחלה טובה לקראת ביצוע זאת עבור ה- API שלך, וגם עוגן טוב לאופן שבו אתה צריך להסתייע בלקוחות ה- API שלך לאבחן במהירות ובקלות שגיאות ולעבור על פניהם.

ה יישום מלא של מדריך זה ניתן למצוא בפרויקט Github - זהו פרויקט מבוסס Eclipse, כך שיהיה קל לייבא ולהפעיל אותו כפי שהוא.

REST תחתון

רק הכרזתי על החדש למד אביב קורס, המתמקד ביסודות האביב 5 ומגף האביב 2:

>> בדוק את הקורס

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