【问题标题】:Django - exception handling best practice and sending customized error message [closed]Django - 异常处理最佳实践和发送自定义错误消息 [关闭]
【发布时间】:2016-12-15 07:19:17
【问题描述】:

我开始考虑在我的 Django 应用程序中进行适当的异常处理,我的目标是使其尽可能用户友好。通过用户友好性,我暗示用户必须始终详细说明到底出了什么问题。 按照this post,最佳做法是

使用状态为 200 的 JSON 响应作为您的正常响应,并且 为错误返回(适当的!)4xx/5xx 响应。这些可以携带 JSON 有效负载也是如此,因此您的服务器端可以添加其他详细信息 关于错误。

我试图通过这个答案中的关键词进行谷歌搜索,但我脑海中的问题仍然多于答案。

  1. 如何决定返回哪个错误代码(400 或 500)?我的意思是,Django 有许多预定义的错误类型,我该如何实现 Django 异常类型和 400-500 错误代码之间的这种映射,以使异常处理块尽可能 DRY 和可重用?
  2. @Reorx 在the post 中建议的中间件方法是否可行? (答案只得到了一个赞成票,因此让我不愿意深入研究细节并在我的项目中实施它
  3. 最重要的是,有时我可能希望提出与业务逻辑相关的错误,而不是不正确的语法或空值之类的标准。例如,如果我的法人实体中没有 CEO,我可能希望禁止用户添加合同。在这种情况下应该是什么错误状态,我如何向用户详细解释错误?

让我们从一个简单的角度来考虑它

def test_view (request):

   try:
          # Some code .... 
          if my_business_logic_is_violated():
              # How do I raise the error
              error_msg = "You violated bussiness logic because..."
              # How do I pass error_msg 
          my_response = {'my_field' : value}
  except ExpectedError as e:
          # what is the most appropriate way to pass both error status and custom message
          # How do I list all possible error types here (instead of ExpectedError to make the exception handling block as DRY and reusable as possible
      return JsonResponse({'status':'false','message':message}, status=500)

【问题讨论】:

    标签: python json ajax django


    【解决方案1】:

    状态代码在 HTTP 标准中得到了很好的定义。您可以找到very readable list on Wikipedia。基本上4XX范围内的错误是客户端的错误,即如果他们请求一个不存在的资源等。如果服务器端遇到错误,则应返回5XX范围内的错误。

    关于第 3 点,您应该在未满足前提条件的情况下选择 4XX 错误,例如 428 Precondition Required,但在服务器引发语法错误时返回 5XX 错误。

    您的示例的一个问题是,除非服务器引发特定异常,否则不会返回响应,即当代码正常执行且未引发异常时,消息和状态代码都不会显式发送到客户端。这可以通过 finally 块来处理,以使这部分代码尽可能通用。

    根据你的例子:

    def test_view (request):
       try:
           # Some code .... 
           status = 200
           msg = 'Everything is ok.'
           if my_business_logic_is_violated():
               # Here we're handling client side errors, and hence we return
               # status codes in the 4XX range
               status = 428
               msg = 'You violated bussiness logic because a precondition was not met'.
       except SomeException as e:
           # Here, we assume that exceptions raised are because of server
           # errors and hence we return status codes in the 5XX range
           status = 500
           msg = 'Server error, yo'
       finally:
           # Here we return the response to the client, regardless of whether
           # it was created in the try or the except block
           return JsonResponse({'message': msg}, status=status)
    

    但是,如 cmets 中所述,以相同的方式进行两种验证会更有意义,即通过异常,如下所示:

    def test_view (request):
       try:
           # Some code .... 
           status = 200
           msg = 'Everything is ok.'
           if my_business_logic_is_violated():
               raise MyPreconditionException()
       except MyPreconditionException as e:
           # Here we're handling client side errors, and hence we return
           # status codes in the 4XX range
           status = 428
           msg = 'Precondition not met.'
       except MyServerException as e:
           # Here, we assume that exceptions raised are because of server
           # errors and hence we return status codes in the 5XX range
           status = 500
           msg = 'Server error, yo.'
       finally:
           # Here we return the response to the client, regardless of whether
           # it was created in the try or the except block
           return JsonResponse({'message': msg}, status=status)
    

    【讨论】:

    • my_business_logic_is_violated() 应该抛出一个异常,该异常将在 except 块中处理。
    • 您当前正在将该函数用作 if 句中的条件 - 在此示例中,您应该选择让它抛出异常,或者将其用作 if 句中的条件, 不是两者。如果你想让它抛出异常,只需将 if 语句的主体移动到一个异常块中,该块捕获函数引发的特定异常
    • 是的,我知道,而且答案显然是正确的。只是为了保持一致性,我认为最好以相同的方式验证所有内容。
    • 是的,我同意 - 以相同的方式处理这两个错误更有意义。我已经编辑了我的答案以举一个例子。
    • 您应该为您的消息创建一个参数,然后在引发异常时调用它。在link I posted in my answer 你可以很容易地看到一些例子。
    【解决方案2】:

    首先,您应该考虑要暴露哪些错误:

    • 通常会公开 4xx 错误(归因于客户端的错误),以便用户更正请求。

    • 另一方面,5xx 错误(归因于服务器端的错误)通常仅在没有信息的情况下呈现。在我看来,对于那些你应该使用像Sentry 这样的工具来监控和解决这个错误,这些错误可能嵌入了安全问题。

    在我看来,对于正确的 Ajax 请求,您应该返回一个状态代码,然后返回一些 json 来帮助理解发生的事情,例如消息和解释(如果适用)。

    如果您的目标是使用 ajax 提交信息,我建议为您想要的设置一个 form。这样您就可以轻松通过一些验证过程。我将假设示例中的情况是这样的。

    第一 - 请求是否正确?

    def test_view(request):
        message = None
        explanation = None
        status_code = 500
        # First, is the request correct?
        if request.is_ajax() and request.method == "POST":
            ....
        else: 
            status_code = 400
            message = "The request is not valid."
            # You should log this error because this usually means your front end has a bug.
            # do you whant to explain anything?
            explanation = "The server could not accept your request because it was not valid. Please try again and if the error keeps happening get in contact with us."
    
        return JsonResponse({'message':message,'explanation':explanation}, status=status_code)
    

    第二 - 表格是否有错误?

    form = TestForm(request.POST)
    if form.is_valid():
        ...
    else:
        message = "The form has errors"
        explanation = form.errors.as_data()
        # Also incorrect request but this time the only flag for you should be that maybe JavaScript validation can be used.
        status_code = 400
    

    您甚至可能逐个字段地得到错误,因此您可以在表单本身中以更好的方式呈现。

    第三次 - 让我们处理请求

            try:
                test_method(form.cleaned_data)
            except `PermissionError` as e:
                status_code= 403
                message= "Your account doesn't have permissions to go so far!"
            except `Conflict` as e:
                status_code= 409
                message= "Other user is working in the same information, he got there first"
            ....
            else:
                status_code= 201
                message= "Object created with success!"
    

    根据您定义的例外情况,可能需要不同的代码。转到Wikipedia 并查看列表。 不要忘记响应也因代码而异。如果你向数据库中添加一些东西,你应该返回一个201。如果您刚刚获得信息,那么您正在寻找 GET 请求。

    回答问题

    1. 如果不处理,Django 异常将返回 500 个错误,因为如果您不知道会发生异常,那么它就是服务器中的错误。除了 404 和登录要求外,我会为所有事情做 try catch 块。 (对于 404,您可以提出它,如果您这样做 @login_required 或需要权限,django 将使用适当的代码进行响应,而无需您做任何事情)。

    2. 我不完全同意这种方法。正如您所说,错误应该是明确的,因此您应该始终知道假设会发生什么以及如何解释它,并使其依赖于所执行的操作。

    3. 我会说 400 错误是可以的。这是一个糟糕的请求,您只需要解释原因,错误代码适用于您和您的 js 代码,因此请保持一致。

    4. (提供的示例)- 在text_view 中,您应该拥有test_method,如第三个示例所示。

    测试方法应具有以下结构:

    def test_method(validated_data):
        try: 
            my_business_logic_is_violated():
        catch BusinessLogicViolation:
            raise
        else:
            ... #your code
    

    在我的例子中:

       try:
            test_method(form.cleaned_data)
        except `BusinessLogicViolation` as e:
            status_code= 400
            message= "You violated the business logic"
            explanation = e.explanation
       ...
    

    我认为业务逻辑违规是客户端错误,因为如果在该请求之前需要某些东西,客户端应该意识到这一点并要求用户先做。 (来自Error Definition):

    400(Bad Request)状态码表示服务器不能或 由于某些被认为是 客户端错误(例如,格式错误的请求语法、无效请求
    消息框架或欺骗性请求路由)。

    顺便说一句,您可以看到Python Docs on User-defined Exceptions,这样您就可以给出适当的错误消息。此示例背后的想法是,您根据生成位置在my_business_logic_is_violated() 中引发带有不同消息的BusinessLogicViolationexception。

    【讨论】:

    • 这是我在网站上看到的最好的答案之一。非常感谢您的解释。我会选择将此答案勾选为已接受。只是一个小小的要求。您能否通过提交代码 sn-p 明确解释 my_business_logic 违规如何融入整个画面
    • NBajanca,Google Chrome 无法识别 428 错误(尽管 IE 可以)。当您仅仅因为业务逻辑不正确而想引发错误时,您能否建议我最适合哪种错误代码?
    • @EdgarNavasardyan 我认为应该是错误 400。我找不到更好的代码来描述这一点,从服务器的角度来看,这显然是一个无效请求。我更新了答案以更好地解释它。
    猜你喜欢
    • 2010-10-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-09-07
    • 2014-11-28
    • 2010-11-29
    • 2018-01-03
    • 1970-01-01
    相关资源
    最近更新 更多