1. 程式人生 > >Flask開發技巧之異常處理

Flask開發技巧之異常處理

# Flask開發技巧之異常處理 [TOC] 本人平時開發中使用的,或者學習到的一些flask開發技巧整理,需要已有較為紮實的flask基礎。 ## 1、Flask內建異常處理 要想在Flask中處理好異常,有一套自己的異常處理機制,首先,我們必須先知道Flask自己是如何處理異常的。去flask的原始碼裡找一找會發現,在flask原始碼的app.py檔案下,有很多會丟擲異常的方法,其中拿一個舉例: ```python def handle_exception(self, e): """Default exception handling that kicks in when an exception occurs that is not caught. In debug mode the exception will be re-raised immediately, otherwise it is logged and the handler for a 500 internal server error is used. If no such handler exists, a default 500 internal server error message is displayed. .. versionadded:: 0.3 """ exc_type, exc_value, tb = sys.exc_info() got_request_exception.send(self, exception=e) handler = self._find_error_handler(InternalServerError()) if self.propagate_exceptions: # if we want to repropagate the exception, we can attempt to # raise it with the whole traceback in case we can do that # (the function was actually called from the except part) # otherwise, we just raise the error again if exc_value is e: reraise(exc_type, exc_value, tb) else: raise e self.log_exception((exc_type, exc_value, tb)) if handler is None: return InternalServerError() return self.finalize_request(handler(e), from_error_handler=True) ``` 我們發現在flask內部對於500異常,會丟擲這樣一個錯誤類`InternalServerError()` ```python class InternalServerError(HTTPException): ...... ``` 至此我們發現flask內部異常通過繼承這個HTTPException類來處理,那麼這個HTTPException類就是我們研究的重點。 ## 2、HTTPException類分析 ```python @implements_to_string class HTTPException(Exception): """Baseclass for all HTTP exceptions. This exception can be called as WSGI application to render a default error page or you can catch the subclasses of it independently and render nicer error messages. """ code = None description = None def __init__(self, description=None, response=None): super(HTTPException, self).__init__() if description is not None: self.description = description self.response = response @classmethod def wrap(cls, exception, name=None): """Create an exception that is a subclass of the calling HTTP exception and the ``exception`` argument. The first argument to the class will be passed to the wrapped ``exception``, the rest to the HTTP exception. If ``e.args`` is not empty and ``e.show_exception`` is ``True``, the wrapped exception message is added to the HTTP error description. .. versionchanged:: 0.15.5 The ``show_exception`` attribute controls whether the description includes the wrapped exception message. .. versionchanged:: 0.15.0 The description includes the wrapped exception message. """ class newcls(cls, exception): _description = cls.description show_exception = False def __init__(self, arg=None, *args, **kwargs): super(cls, self).__init__(*args, **kwargs) if arg is None: exception.__init__(self) else: exception.__init__(self, arg) @property def description(self): if self.show_exception: return "{}\n{}: {}".format( self._description, exception.__name__, exception.__str__(self) ) return self._description @description.setter def description(self, value): self._description = value newcls.__module__ = sys._getframe(1).f_globals.get("__name__") name = name or cls.__name__ + exception.__name__ newcls.__name__ = newcls.__qualname__ = name return newcls @property def name(self): """The status name.""" from .http import HTTP_STATUS_CODES return HTTP_STATUS_CODES.get(self.code, "Unknown Error") def get_description(self, environ=None): """Get the description.""" return u"

%s

" % escape(self.description).replace("\n", "
") def get_body(self, environ=None): """Get the HTML body.""" return text_type( ( u'\n' u"\n" u"

%(name)s

\n" u"%(description)s\n" ) % { "code": self.code, "name": escape(self.name), "description": self.get_description(environ), } ) def get_headers(self, environ=None): """Get a list of headers.""" return [("Content-Type", "text/html; charset=utf-8")] def get_response(self, environ=None): """Get a response object. If one was passed to the exception it's returned directly. :param environ: the optional environ for the request. This can be used to modify the response depending on how the request looked like. :return: a :class:`Response` object or a subclass thereof. """ from .wrappers.response import Response if self.response is not None: return self.response if environ is not None: environ = _get_environ(environ) headers = self.get_headers(environ) return Response(self.get_body(environ), self.code, headers) ...... ``` - 擷取這個類比較重要的幾個方法分析,`get_headers`方法定義了這個返回的響應頭,返回的是html文件。 - `get_body`方法定義了返回的響應體,對應也是一段html的內容。 - 最後在Response中將響應體,狀態碼,響應頭定義好返回。 分析至此,其實這個HTTPException中做的事也不難理解,就是定義好響應體,狀態碼,還有響應頭,做了一個返回。當然這個類返回是html型別的,現在前後端分離互動都是json形式的返回,所以我們可以繼承自這個類,定義我們自己的異常處理類。 ## 3、自定義異常處理類 首先我們理解我們自己的這個異常處理類,應該繼承自HTTPException來改寫。而我們自定義的內容應該包含以下幾點: - 需要定義我們自己想要返回的錯誤資訊的json格式,比如內部錯誤碼、錯誤資訊等我們想記錄的資訊。 - 需要更改返回的響應頭,返回json格式的資訊響應頭就應該設為`'Content-Type': 'application/json'` - 同樣需要和HTTPException一樣定義好狀態碼 如下定義我們自己的異常類APIException,返回的資訊包括內部錯誤碼,錯誤資訊,請求的url ```python class APIException(HTTPException): code = 500 msg = 'sorry, we made a mistake!' error_code = 999 def __init__(self, msg=None, code=None, error_code=None, headers=None): if code: self.code = code if error_code: self.error_code = error_code if msg: self.msg = msg super(APIException, self).__init__(msg, None) def get_body(self, environ=None): body = dict( msg=self.msg, error_code=self.error_code, request=request.method + ' ' + self.get_url_no_param() ) text = json.dumps(body) return text def get_headers(self, environ=None): """Get a list of headers.""" return [('Content-Type', 'application/json')] @staticmethod def get_url_no_param(): full_path = str(request.full_path) main_path = full_path.split('?') return main_path[0] ``` ## 4、方便的定義自己的錯誤類 有了上面我們改寫好的APIException類,我們就可以自由的定義各種狀態碼的錯誤以及對應的錯誤資訊,然後在合適的位置丟擲。比如: ```python class Success(APIException): code = 201 msg = 'ok' error_code = 0 class DeleteSuccess(APIException): code = 202 msg = 'delete ok' error_code = 1 class UpdateSuccess(APIException): code = 200 msg = 'update ok' error_code = 2 class ServerError(APIException): code = 500 msg = 'sorry, we made a mistake!' error_code = 999 class ParameterException(APIException): code = 400 msg = 'invalid parameter' error_code = 1000 class NotFound(APIException): code = 404 msg = 'the resource are not found' error_code = 1001 class AuthFailed(APIException): code = 401 msg = 'authorization failed' error_code = 1005 class Forbidden(APIException): code = 403 error_code = 1004 msg = 'forbidden, not in scope' ``` 有了這些自定義的錯誤類,我們不僅可以直接在需要的地方丟擲,而且有了自定義的錯誤碼,發生錯誤時,只要對照錯誤碼去查詢對應的錯誤類,非常方便。而且特別說明的是,雖然說是錯誤類,但是也是可以定義響應成功的返回的,比如上面定義的200,201的類,同樣可以作為一個成功的返回。 使用演示: ```python user = User.query.first() if not user: raise NotFound() ``` ## 5、注意事項 儘管我們可以在我們認為可能出錯的所有地方,繼承自己的異常類,定義自己的錯誤類,然後丟擲,但是也不是所有的異常都是我們可以提前預知的。比如我們接受前端傳來的引數,引數型別或取值範圍不正確,這些我們可以預知並處理好,但是如果是邏輯處理中出現了問題,這些不是我們程式設計師可以控制並處理。所以光有自定義錯誤類還不夠,我們還需要在全域性捕獲異常來判斷,利用AOP思想。 ```python # 全域性錯誤AOP處理 @app.errorhandler(Exception) def framework_error(e): api_logger.error("error info: %s" % e) # 對錯誤進行日誌記錄 if isinstance(e, APIException): return e if isinstance(e, HTTPException): code = e.code msg = e.description error_code = 1007 return APIException(msg, code, error_code) else: if not app.config['DEBUG']: return ServerError() else: return e ``` 這裡對於flask中丟擲的所有的錯誤進行捕獲,然後先進行日誌的記錄。然後判斷如果是我們自定義的APIException,就直接返回。如果不是我們自定義的,但是是flask處理的HTTPException,包裝成我們自定義的APIException再返回。如果都不是的話,說明是伺服器出現的其他錯誤,問題一般出在我們的程式碼上,在生產環境下,一般統一返回一個500錯誤,在除錯模式下,可以原樣返回,便於我們定位修改自己的程式碼。 關於flask的異常處理,以上就是我目前掌握的一些技巧,如有錯誤歡迎指出。 ----- 部落格園: https://www.cnblogs.com/luyuze95/ GitHub: https://github.com/