טיפול בשגיאות עבור REST עם אביב

REST למעלה

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

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

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

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

לפני אביב 3.2, שתי הגישות העיקריות לטיפול בחריגים ביישום MVC באביב היו HandlerExceptionResolver או ה @ExceptionHandler ביאור. לשניהם חסרונות ברורים.

מאז 3.2, יש לנו את @ControllerAdvice ביאור כדי לטפל במגבלות של שני הפתרונות הקודמים ולקדם טיפול חריג מאוחד לאורך יישום שלם.

עַכשָׁיו אביב 5 מציג את ResponseStatusException מעמד - דרך מהירה לטיפול בשגיאות בסיסיות בממשקי ה- API של REST שלנו.

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

לבסוף נראה מה מגף האביב מביא לשולחן וכיצד נוכל להגדיר אותו כך שיתאים לצרכים שלנו.

2. פתרון 1: רמת הבקר @ExceptionHandler

הפתרון הראשון עובד ב- @בקר רָמָה. נגדיר שיטה להתמודד עם חריגים ולהערה עליה @ExceptionHandler:

מחלקה ציבורית FooController {// ... @ExceptionHandler ({CustomException1.class, CustomException2.class}) handle publicException () {//}}

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

אנו יכולים לעקוף את המגבלה הזו על ידי כך כל הבקרים מרחיבים כיתת בקר בסיס.

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

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

3. פיתרון 2: ה HandlerExceptionResolver

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

לפני שנלך לפתרון מותאם אישית, בוא נעבור על היישומים הקיימים.

3.1. ExceptionHandlerExceptionResolver

רזולבר זה הוצג באביב 3.1 והוא מופעל כברירת מחדל ב- DispatcherServlet. זהו למעשה מרכיב הליבה של האופן שבו ה- @ExceptionHandler מנגנון שהוצג עבודות קודמות.

3.2. DefaultHandlerExceptionResolver

רזולבר זה הוצג באביב 3.0 והוא מופעל כברירת מחדל ב DispatcherServlet.

הוא משמש לפתרון חריגי אביב סטנדרטיים לקודי סטטוס ה- HTTP המקבילים שלהם, כלומר שגיאת לקוח 4xx ושגיאת שרת 5xx קודי סטטוס. הנה ה רשימה מלאה של חריגי האביב שהוא מטפל בהם ואיך הם ממפים לקודי סטטוס.

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

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

3.3. ResponseStatusExceptionResolver

פותר זה הוצג גם באביב 3.0 והוא מופעל כברירת מחדל ב- DispatcherServlet.

האחריות העיקרית שלה היא להשתמש ב @ תגובה סטטוס ביאור זמין בחריגים מותאמים אישית וכדי למפות חריגים אלה לקודי סטטוס HTTP.

חריג מותאם אישית כזה עשוי להיראות כמו:

@ResponseStatus (value = HttpStatus.NOT_FOUND) מחלקה ציבורית MyResourceNotFoundException מרחיב RuntimeException {public MyResourceNotFoundException () {super (); } MyResourceNotFoundException ציבורי (הודעת מחרוזת, סיבה הניתנת לזריקה) {super (הודעה, סיבה); } ציבורי MyResourceNotFoundException (הודעת מחרוזת) {super (הודעה); } MyResourceNotFoundException ציבורי (סיבה הניתנת לזריקה) {סופר (סיבה); }}

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

3.4. SimpleMappingExceptionResolver ו ביאור שיטה מטפל חריג פותר

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

ה ביאור שיטת מתמודד יוצא מהכלל הוצג באביב 3.0 כדי לטפל בחריגים דרך @ExceptionHandler ביאור אך הוצא משימוש על ידי ExceptionHandlerExceptionResolver החל באביב 3.2.

3.5. המותאם אישית HandlerExceptionResolver

השילוב של DefaultHandlerExceptionResolver ו ResponseStatusExceptionResolver עושה דרך ארוכה לקראת מתן מנגנון טוב לטפל בשגיאות עבור שירות RESTful Spring. החיסרון הוא, כאמור, אין שליטה על גוף התגובה.

באופן אידיאלי, אנו רוצים להיות מסוגלים להפיק JSON או XML, תלוי באיזה פורמט הלקוח ביקש (דרך לְקַבֵּל כּוֹתֶרֶת).

זה לבדו מצדיק יצירה פותר חריג מותאם אישית חדש:

המחלקה הציבורית @Component RestResponseStatusExceptionResolver מרחיב את AbstractHandlerExceptionResolver {@Override מוגן ModelAndView doResolveException (HttpServletRequest בקשה, HttpServletResponse תגובה, מטפל אובייקט, exeption חריג) {נסה {אם (לדוגמא של IllegalArgal) } ...} לתפוס (Exception handlerException) {logger.warn ("טיפול ב- [" + ex.getClass (). getName () + "] הביא ל- Exception", handlerException); } להחזיר אפס; } ModelAndView הפרטי handleIllegalArgument (IllegalArgumentException ex, HttpServletResponse response) זורק IOException {response.sendError (HttpServletResponse.SC_CONFLICT); מחרוזת קבל = request.getHeader (HttpHeaders.ACCEPT); ... להחזיר ModelAndView חדש (); }}

פרט אחד שיש לשים לב כאן הוא שיש לנו גישה ל בַּקָשָׁה עצמה, כך שנוכל לשקול את הערך של ה- לְקַבֵּל כותרת שנשלחה על ידי הלקוח.

לדוגמא, אם הלקוח מבקש יישום / json, במקרה של מצב שגיאה, נרצה לוודא שאנחנו מחזירים גוף תגובה מקודד יישום / json.

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

גישה זו היא מנגנון עקבי וניתן להגדרה בקלות לטיפול בשגיאות בשירות Spring REST.

עם זאת, יש לכך מגבלות: זה מתקשר עם הרמה הנמוכה HtttpServletResponse ומשתלב במודל ה- MVC הישן שמשתמש בו ModelAndViewאז יש עדיין מקום לשיפור.

4. פתרון 3: @ControllerAdvice

אביב 3.2 מביא תמיכה ב גלובלי @ExceptionHandler עם ה @ControllerAdvice ביאור.

זה מאפשר מנגנון שמתנתק מדגם ה- MVC הישן ומשתמש בו תגובה תגובה יחד עם סוג הבטיחות והגמישות של @ExceptionHandler:

@ControllerAdvice מחלקה ציבורית RestResponseEntityExceptionHandler מרחיב את ResponseEntityExceptionHandler {@ExceptionHandler (value = {IllegalArgumentException.class, IllegalStateException.class}) מוגן ResponseEntity handleConflict (RuntimeException בקשה = "בקשה זו"). return handleExceptionInternal (לשעבר, bodyOfResponse, HttpHeaders חדשים (), HttpStatus.CONFLICT, בקשה); }}

ה@ControllerAdvice ביאור מאפשר לנו לאחד את המרובים שלנו, מפוזרים @ExceptionHandlerמלפנים לרכיב גלובלי יחיד לטיפול בשגיאות.

המנגנון בפועל פשוט ביותר אך גם גמיש מאוד:

  • זה נותן לנו שליטה מלאה על גוף התגובה כמו גם על קוד המצב.
  • הוא מספק מיפוי של כמה יוצאים מן הכלל לאותה השיטה, לטיפול יחד.
  • זה עושה שימוש טוב ב- RESTful החדשה יותר ResposeEntity תְגוּבָה.

דבר אחד שיש לזכור כאן הוא להתאים את החריגים שהוכרזו עם @ExceptionHandler לחריג המשמש כטיעון השיטה.

אם אלה לא תואמים, המהדר לא יתלונן - אין סיבה שהוא צריך - וגם אביב לא יתלונן.

עם זאת, כאשר החריג מושלך בפועל בזמן ריצה, מנגנון פתרון החריגים ייכשל עם:

java.lang.IllegalStateException: אין פותר מתאים לוויכוח [0] [type = ...] פרטי שיטה: ...

5. פתרון 4: ResponseStatusException (אביב 5 ומעלה)

אביב 5 הציג את תגובה סטטוס חריג מעמד.

אנו יכולים ליצור מופע שהוא מספק HttpStatus ואופציונלי א סיבה ו גורם:

@GetMapping (value = "/ {id}") Foo findById ציבורי (@PathVariable ("id") מזהה ארוך, תגובת HttpServletResponse) {נסה {Foo resourceById = RestPreconditions.checkFound (service.findOne (id)); eventPublisher.publishEvent (SingleResourceRetrievedEvent חדש (זה, תגובה)); להחזיר resourceById; } לתפוס (MyResourceNotFoundException exc) {לזרוק ResponseStatusException חדש (HttpStatus.NOT_FOUND, "Foo לא נמצא", exc); }}

מהם היתרונות של השימוש ResponseStatusException?

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

ומה עם הפשרות?

  • אין דרך אחידה של טיפול בחריגים: קשה יותר לאכוף כמה מוסכמות הכוללות יישומים לעומת @ControllerAdvice, המספק גישה עולמית.
  • שכפול קוד: אנו עשויים למצוא את עצמנו משכפלים קוד במספר בקרים.

עלינו לציין כי ניתן לשלב גישות שונות בתוך יישום אחד.

לדוגמא, אנו יכולים ליישם א @ControllerAdvice בעולם אבל גם תגובה סטטוס חריגs באופן מקומי.

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

לפרטים נוספים ולדוגמאות נוספות, עיין במדריך שלנו בנושא ResponseStatusException.

6. לטפל בגישה שנדחתה באבטחת האביב

הגישה נדחתה מתרחשת כאשר משתמש מאומת מנסה לגשת למשאבים שאין לו מספיק רשויות לגשת אליהם.

6.1. MVC - דף שגיאה מותאם אישית

ראשית, בואו נסתכל על סגנון ה- MVC של הפתרון ונראה כיצד להתאים אישית דף שגיאה עבור Access Denied.

תצורת ה- XML:

  ...  

ותצורת Java:

התצורה הריקה המוגנת על ידי @Override (HttpSecurity http) זורקת חריגה {http.authorizeRequests () .antMatchers ("/ admin / *"). HasAnyRole ("ROLE_ADMIN") ... .and () .exceptionHandling (). AccessDeniedPage ("/ דף השגיאה שלי "); }

כאשר משתמשים מנסים לגשת למשאב מבלי שיהיו להם מספיק רשויות, הם יופנו אליו "/ My-error-page".

6.2. המותאם אישית AccessDeniedHandler

לאחר מכן, בואו נראה כיצד לכתוב את המנהג שלנו AccessDeniedHandler:

המחלקה הציבורית של @Component CustomAccessDeniedHandler מיישמת את AccessDeniedHandler {@Override handle public void (HttpServletRequest request, HttpServletResponse response, AccessDeniedException ex) זורק IOException, ServletException {respons.sendRedirect ("/ my-error-page) }}

ועכשיו בואו להגדיר את זה באמצעות תצורת XML:

  ...  

0r באמצעות תצורת Java:

@Autowired פרטי CustomAccessDeniedHandler accessDeniedHandler; התצורה הריקה מוגנת של @Override (HttpSecurity http) זורקת חריגה {http.authorizeRequests () .antMatchers ("/ admin / *"). HasAnyRole ("ROLE_ADMIN") ... .and () .exceptionHandling (). AccessDeniedHandler (accessDeniedHandler) }

שים לב איך שלנו CustomAccessDeniedHandler, אנו יכולים להתאים אישית את התגובה כרצוננו על ידי הפניה מחדש או הצגת הודעת שגיאה מותאמת אישית.

6.3. מנוחה ואבטחה ברמת השיטה

לבסוף, נראה כיצד לטפל באבטחה ברמת השיטה @ PreAuthorize, @PostAuthorize, ו @לבטח גישה נדחתה.

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

@ControllerAdvice מחלקה ציבורית RestResponseEntityExceptionHandler מרחיב את ResponseEntityExceptionHandler {@ExceptionHandler ({AccessDeniedException.class}) public ResponseEntity handleAccessDeniedException (חריג למשל, בקשת WebRequest) {להחזיר ResponseEntity חדש ("גישה נדחתה כאן). } ...}

7. תמיכה במגף האביב

מגף האביב מספק ErrorController יישום כדי לטפל בשגיאות בצורה הגיונית.

בקצרה, הוא משרת דף שגיאת נפילה עבור דפדפנים (aka דף שגיאת הוויתור) ותגובת JSON לבקשות RESTful שאינן HTML:

{"חותמת זמן": "2019-01-17T16: 12: 45.977 + 0000", "status": 500, "error": "שגיאת שרת פנימית", "message": "שגיאה בעיבוד הבקשה!", "path" : "/ my-endpoint-with-exceptions"}

כרגיל, Spring Boot מאפשר הגדרת תכונות אלה עם מאפיינים:

  • server.error.whitelabel.enabled: ניתן להשתמש בה כדי להשבית את דף השגיאה של תווית הלבנה ולהסתמך על מיכל השרת כדי לספק הודעת שגיאה ב- HTML
  • server.error.include-stacktrace: עם תמיד ערך; כולל את מסלול הערימה גם ב- HTML וגם בתגובת ברירת המחדל של JSON

מלבד נכסים אלה, אנו יכולים לספק מיפוי פתרונות מבט משלנו עבור /שְׁגִיאָה, עוקף את דף התווית הלבנה.

אנו יכולים גם להתאים אישית את התכונות שאנו רוצים להציג בתגובה על ידי הכללת תכונות שגיאה שעועית בהקשר. אנחנו יכולים להאריך את DefaultErrorAttributes שיעור שמספק Spring Boot כדי להקל על הדברים:

מחלקה ציבורית @Component MyCustomErrorAttributes מרחיב את DefaultErrorAttributes {@Override public map getErrorAttributes (WebRequest webRequest, boolean includeStackTrace) {Map errorAttributes = super.getErrorAttributes (webRequest, includeStackTrace); errorAttributes.put ("locale", webRequest.getLocale () .toString ()); errorAttributes.remove ("שגיאה"); // ... return errorAttributes; }}

אם נרצה להמשיך ולהגדיר (או לעקוף) כיצד היישום יטפל בשגיאות מסוג תוכן מסוים, נוכל לרשום ErrorController אפונה.

שוב, אנו יכולים לעשות שימוש בברירת המחדל BasicErrorController המסופק על ידי Spring Boot כדי לעזור לנו.

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

מחלקה ציבורית @Component MyErrorController מרחיב את BasicErrorController {public MyErrorController (ErrorAttributes errorAttributes) {super (errorAttribute, ErrorProperties new ()); } @RequestMapping (מפיק = MediaType.APPLICATION_XML_VALUE) תגובה ציבורית xmlError (בקשת HttpServletRequest) {// ...}}

8. מסקנה

מאמר זה דן בכמה דרכים ליישום מנגנון טיפול חריג עבור REST API באביב, החל מהמנגנון הישן והמשיך בתמיכת Spring 3.2 ועד 4.x ו- 5.x.

כמו תמיד, הקוד המוצג במאמר זה זמין באתר GitHub.

עבור הקוד הקשור לאבטחת אביב, אתה יכול לבדוק את מודול האביב-אבטחה.

REST תחתון

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

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

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