【问题标题】:django: raise BadRequest as exception?django:将 BadRequest 引发为异常?
【发布时间】:2014-10-14 20:11:45
【问题描述】:

是否可以在 django 中将 BadRequest 引发为异常?

我看到你可以提出 404 [1]。

用例:在辅助方法中,我从 request.GET 加载 json。如果在浏览器 (IE) 剪切 url 后 json 被剪切,我想提出一个匹配的异常。

BadRequest 异常看起来很合适,但到目前为止,django 中似乎没有这样的异常。

在 1.6 中有一个 SuspiciousOperation 异常。但这在我的情况下不匹配,因为它与安全无关。

当然,我可以在视图方法中的辅助方法周围放一个 try..except,但这不是 DRY。

有没有人解决我不需要尝试的解决方案。每次调用我的辅助方法时都会出现异常?

[1]https://docs.djangoproject.com/en/1.6/ref/exceptions/#django.core.urlresolvers.Resolver404

更新

代码示例:

def my_view(request):
    data=load_data_from_request(request) # I don't want a try..except here: DRY
    process_data(data)
    return django.http.HttpResponse('Thank you')

def load_data_from_request(request):
    try:
        data_raw=json.loads(...)
    except ValueError, exc:
        raise BadRequest(exc)
    ...
    return data

【问题讨论】:

  • 为什么不直接返回django.http.HttpResponseBadRequest
  • 如果你想干,让你的帮助方法返回适当的HttpResponse类,或者在它周围创建另一个快捷方式,比如load_json_or_bad_request()
  • @ambi 我不想返回django.http.HttpResponseBadRequest,因为我的方法返回解析后的数据,除了输入数据损坏。
  • @guettli 更新后,我认为您应该提出一个自定义异常并按照 Coldmind 的建议在中间件中处理它。这与get_object_or_404使用的机制相同

标签: python django exception-handling httpresponse


【解决方案1】:

从 3.2 开始,Django 提供了BadRequest 类。所以你现在可以做

try:
    data_raw = json.loads(...)
except ValueError:
    raise BadRequest('Invalid JSON')

问题在于,由于某种原因,错误响应中没有出现“无效 JSON”消息,只显示通用消息:

<!doctype html>
<html lang="en">
<head>
  <title>Bad Request (400)</title>
</head>
<body>
  <h1>Bad Request (400)</h1><p></p>
</body>
</html>

【讨论】:

    【解决方案2】:

    我找到的更简单的解决方案是引发BadRequest 异常。如果请求中未传递所需的参数,这是我用来返回 Http400 的方法。引发 BadRequest 异常将使请求返回 400 状态码。

    from django.core.exceptions import BadRequest
    from django.http import HttpResponse
    from django.views import View
    
    def get_param_or_return_400(kwargs: dict, key: str):
        if key in kwargs:
            return kwargs[key]
        raise BadRequest(f'The parameter "{key}" must be provided')
    
    class MyView(View):
    
        def post(self, request, *args, **kwargs):
            query = get_param_or_return_400(kwargs, 'query')
            # At this point we know query is not None
            do_some_operation(query)
            return HttpResponse('OK')
    

    【讨论】:

      【解决方案3】:

      我认为一种简单的方法是定义自己的 BadRequestException 并在被调用函数中引发它。

      from django.http import HttpResponseBadRequest, HttpResponse
      
      class BadRequestException(Exception):
          def __init__(self, message='', *args, **kwargs):
              self.message = message
      
      def my_view(request):
          try:
              data = get_data_from_another_func()
          except BadRequestException as e:
              return HttpResponseBadRequest(e.message)
          process_data(data)
          return HttpResponse('Thank you')
      
      def get_data_from_another_func():
          raise BadRequestException(message='something wrong')
      
      def process_data(data):
          pass
      

      【讨论】:

        【解决方案4】:

        其他答案正在解释如何返回具有 400 状态的 HTTP 响应。

        如果你想挂钩到 Django 的 400 error handling,你可以引发一个 SuspiciousOperation 异常或其子类。

        请参阅文档 herehere

        在您的示例中,它看起来像:

        from django.core.exceptions import SuspiciousOperation
        
        def load_data_from_request(request):
            try:
                data_raw = json.loads(...)
            except ValueError:
                raise SuspiciousOperation('Invalid JSON')
            # ...
            return data
        

        【讨论】:

        • SuspiciousOperation 似乎不合适。请求无效,与安全无关。
        • 然后你可以将它子类化并称之为别的东西。我认为“实用胜于纯粹”在这里适用......
        • 这在客观上是个坏主意。任何出于安全原因试图捕捉 SuspiciousOperation 的人也会捕捉到您的异常。
        • 您如何区分安全原因和错误输入?如果缺少某个字段,则显然应该是“错误请求”。但是,如果格式错误的 json 是有人试图破坏您的系统怎么办? (即编写自定义处理程序对我来说似乎有点矫枉过正,但否则我可以相信:))
        • 公平地说,我可以看到您的解决方案需要的代码比我的“自定义处理程序”解决方案少 很多,这是一个很大的优势,也导致了我的回答在此页面上显示得更低。 :-) 有些应用程序需要自定义处理程序提供的细粒度灵活性,但我同意这不适用于大多数项目。
        【解决方案5】:

        您需要自定义中间件来处理您提出的异常。 利用自定义异常检查中间件中的这种情况。

        class ErrorHandlingMiddleware(object):
            def process_exception(self, request, exception):
                if not isinstance(exception, errors.ApiException): # here you check if it yours exception
                    logger.error('Internal Server Error: %s', request.path,
                        exc_info=traceback.format_exc(),
                        extra={
                            'request': request
                        }
                    )
                # if it yours exception, return response with error description
                try:
                    return formatters.render_formatted_error(request, exception) # here you return response you need
                except Exception, e:
                    return HttpResponseServerError("Error During Error Processing")
        

        【讨论】:

        • 好点,请记住注释行不正确,因为您在任何情况下都返回响应(以相同方式),而不仅仅是在“您的例外”情况下。这可能是完美的,也可能是错误的,我只是对评论行做一个澄清
        • 这也适用于版本 docs.djangoproject.com/en/1.10/releases/1.10/… 和 github.com/wagtail/wagtail/issues/2924
        • 我认为这里的总体模式很棒,但是对于错误的渲染器决定返回什么 HTTP 代码的想法,我真的很不高兴。我在此页面底部附近有一个答案,它做了类似的事情,但在视图上使用装饰器而不是中间件。也许我最喜欢的答案是使用上面的coldmind的中间件,但是在其中插入了类似于我的“HTTP返回值的异常处理程序”代码。
        【解决方案6】:

        作为@coldmind 的答案(在中间件层中转换异常)的替代方案,您可以在视图函数上放置一个装饰器来做同样的事情。我个人更喜欢这个,因为它只是普通的 Python,不需要我对 Django 中间件如何工作的知识抹去灰尘。

        您不想在视图函数中内联所有功能的意识流(这会使您的视图模块依赖于您项目的所有其他模块,从而导致“一切都依赖于其他一切”架构)相反,它更好如果视图只知道http。它从请求中提取您需要的内容,委托给其他一些“业务逻辑”功能。业务逻辑可能会委托给其他模块(例如,数据库代码或其他外部系统的接口)。最后,您的业务逻辑的返回值会被视图函数转换为 http 响应。

        但是如何将错误从业务逻辑(或它委托给的任何东西)传达回视图函数?由于许多原因,使用返回值令人讨厌。例如,这些错误返回值必须通过整个代码库传播回视图。这通常非常混乱,因为您已经将函数的返回值用于其他目的。

        处理这个问题的自然方法是使用异常,但 Django 视图本身不会将未捕获的异常转换为返回的 HTTP 状态代码(除了一些特殊情况,正如 OP 所说。)

        所以。我写了一个装饰器来应用我的视图。装饰器将各种引发的异常类型转换为不同的返回 django.http.HttpResponseXXX 值。例如:

        # This might be raised by your business logic or database code, if they get
        # called with parameters that turn out to be invalid. The database code needs
        # know nothing about http to do this. It might be best to define these exception
        # types in a module of their own to prevent cycles, because many modules 
        # might need to import them.
        class InvalidData(Exception):
            pass
        
        # This decorator is defined in the view module, and it knows to convert
        # InvalidData exceptions to http status 400. Add whatever other exception types
        # and http return values you need. We end with a 'catch-all' conversion of
        # Exception into http 500.
        def exceptions_to_http_status(view_func):
            @wraps(view_func)
            def inner(*args, **kwargs):
                try:
                    return view_func(*args, **kwargs)
                except InvalidData as e:
                    return django.http.HttpResponseBadRequest(str(e))   
                except Exception as e:
                    return django.http.HttpResponseServerError(str(e))
             return inner
        
         # Then finally we define our view, using the decorator.
        
         @exceptions_to_http_status
         def myview(self, request):
             # The view parses what we need out of incoming requests
             data = request.GET['somearg']
        
             # Here in the middle of your view, delegate to your business logic,
             # which can just raise exceptions if there is an error.
             result = myusecase(data)
        
             # and finally the view constructs responses
             return HttpResponse(result.summary)
        

        根据具体情况,您可能会发现相同的装饰器可以用于您的许多或全部视图函数。

        【讨论】:

          【解决方案7】:

          HttpResponseBadRequest 可以使用了。 It is implemented 为:

          class HttpResponseBadRequest(HttpResponse):
              status_code = 400
          

          已编辑由于 OP 更新问题。

          您可以创建自己的助手并将 try-catch 块封装到其中。

          def myJsonDec(str):
              try:
                  ...
          

          【讨论】:

          • 我更新了问题。请看代码示例。
          • HttpResponseBadRequest 异常很棒,但开箱即用的 Django 并没有泛化以这种方式处理其他 HTTP 状态值。 (也许他们很难这样做 - 将异常类型映射到 http 状态代码可能是特定于应用程序的,除非您只想为每个 http 状态代码显式创建一个异常类型,这意味着您的业务逻辑和代码代表将被有关 http 状态代码的知识所困扰。)我在此页面下方的某个地方有一个答案,试图解决这个问题,但它是一堆代码。
          • HttpResponseBadRequest 也不例外,不能引发。
          【解决方案8】:

          我不确定您将 BadRequest 作为例外是什么意思。

          您可以通过显式使用 HttpResponse 的相关子类或在正常响应中添加 status 参数来返回带有任何您喜欢的状态代码的响应。

          【讨论】:

          • 我添加了一个代码示例。我希望现在更清楚了。
          • 而不是raise Http404 raise Http400,原因是,我不想检查函数返回类型,然后基于该返回响应
          猜你喜欢
          • 2015-10-24
          • 2014-10-27
          • 1970-01-01
          • 2017-09-07
          • 2017-11-19
          • 2018-02-15
          • 2017-11-11
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多