【问题标题】:Send a specific response, or at least a specific HTTP status code, using an exception使用异常发送特定的响应,或者至少是特定的 HTTP 状态代码
【发布时间】:2014-09-30 23:35:36
【问题描述】:

在 Django 中,有几个异常被设计为被框架拦截并转化为特定的 HTTP 响应代码,例如 404 Not Found 和 403 Forbidden。

这对于请求验证特别有用,因为它允许您将常见的验证逻辑分解到实用程序函数中并清理您的控制器操作。

当实用程序函数决定必须使用特定 HTTP 错误代码中止当前请求时,它们可以通过抛出相关异常来实现,控制器操作中没有任何支持代码, return 语句或 try/catch 的形式。

例如,给定一棵嵌套的 REST 资源树:

static mappings = {
    "/authors" (resources: "author") {
        "/sagas" (resources: "saga") {
            "/books" (resources: "book") {
        }
    }
}

那么 Book 资源的 URL 模式是 /authors/$authorId/sagas/$sagaId/books/$id,这意味着 BookController 中的任何 show()、delete() 或 update() 操作都具有此签名,并且必须包含一些样板验证逻辑:

def actionName(int authorId, int sagaId, Book book) {

    // -- common validation logic ----------
    // fetch parent objects
    def author = Author.get(authorId)
    def saga = Saga.get(sagaId)
    // check that they exists
    if (author == null || saga == null || book == null) {
        return render(status: NOT_FOUND)
    }
    // check consistency
    if (book.author != author || book.saga != saga || saga.author != author) {
        return render(status: BAD_REQUEST)
    }
    // -- end of commond code --------------

    ...
}

Grails 以什么方式将其分解为一个通用方法,同时仍然允许它在异常情况发生时终止请求处理

我认为最好的方法是 NotFoundException、ForbiddenException、BadRequestException 等,或者可能是接受 HTTP 状态代码的通用异常。 Grails中有类似的东西吗?如果没有,添加它的最佳位置在哪里?过滤器?


编辑:我现在看到标准方法是添加一个带有匹配 URL 模式的错误控制器,例如:

"500" (controller: "error")

问题在于 Grails 仍会为所有异常记录完整的堆栈跟踪,包括那些不是编程错误的异常。这种垃圾邮件日志文件包含各种无用的回溯。

有解决办法吗?

【问题讨论】:

    标签: exception grails dry http-status-codes


    【解决方案1】:

    您在控制器的 beforeInterceptor 闭包中捕获异常。我通过检查抛出的异常然后采取相应措施解决了同样的问题。例如:

    class BaseController {
    
    /**
     * Define DRA exception handlers. This prevents the default Grails
     * behavior of returning an HTTP 500 error for every exception.
     *
     * Instead the exceptions are intercepted and modified according to
     * the exception that was thrown. These exceptions are not logged
     * whereas application exceptions are.
     */
    def beforeInterceptor = {
    
        request.exceptionHandler = { exception ->
    
            def cause = exception.cause
            def exceptionBody = [:]
    
            if(cause.class == BadRequestException) {
                response.setStatus(HttpStatus.BAD_REQUEST.value()) // HTTP 400 BAD REQUEST
                exceptionBody.httpStatus = HttpStatus.BAD_REQUEST.value()
                exceptionBody.error = cause.message
            }
    
            // render the exception body, the status code is set above.
            render exceptionBody as JSON
    
            return true
    
        }
    
    }
    }
    

    为了让它工作,你必须创建一个 ErrorController 或其他东西来处理和呈现所有服务器错误。例如:

    class ErrorController {
    
    def serverError() {
        def handler = request.exceptionHandler
        if(handler) {
            request.exceptionHandler = null
            if(handler.call(request.exception)) {
                return
            }
        }
    }
    

    我已经对此进行了测试,它确实有效。我从我一直在做的一个正在运行的项目中复制了代码。您可以在 beforeInterceptor 中构建 if 语句来捕获您希望的任何类型的异常。

    【讨论】:

    • 谢谢,但我想区分一下 1. 我想捕获并以某种方式返回的异常类(某个 JSON,包括异常消息,以及某个 HTTP 状态码) ,以及 2. 其他我想对用户隐藏的异常,登录到日志文件并返回一个普通的 500 错误。您使用 ErrorController 的解决方案(我认为,匹配的 url "500" (controller: "error"))有一个缺陷:Grails 仍然使用完整的堆栈跟踪记录“已识别”异常,就好像它们是内部错误一样 - 但它们不是。
    • 对不起,你错了。 Grails 不会记录已识别的异常。您可以自己尝试一下。请注意从拦截器获取“true”返回值的 if(handler) 语句。如果处理程序返回 true,则 request.exceptionHandler 被分配为 null,这意味着“500”不被视为这样。此外,您可以修改 beforeInterceptor 来做任何您想做的事情。您不必逐字使用此代码。我在我构建的应用程序中使用了类似的代码,它的工作方式与我上面描述的一样,而且据我所知,你希望它在你的问题中如何。
    • 无论我尝试什么,我识别的异常仍然被记录下来。我尝试了您的代码和许多变体,但没有任何效果,我仍然可以看到所有已识别异常的堆栈跟踪。事实上,如果我在 ErrorController 操作的开头放置 println,我可以看到异常在进入控制器之前被记录。因此,无论我在错误控制器中做什么,都为时已晚,异常已被记录。
    • 我可以确认这适用于使用 Grails 2.2.5 的项目。但是,在尝试使用 Grails 2.4.3 制作示例项目时,确实在调用错误操作之前记录了堆栈跟踪。经过一番研究,我确实找到了这篇文章java.dzone.com/articles/grails-goodness-exception,其中指出自 Grails 2.3 以来可以在控制器中定义异常方法。如果您在控制器或基本控制器中定义异常方法,您可以在不记录堆栈跟踪的情况下捕获异常。
    • 真是太甜了!我想知道它是否记录在任何地方。我注意到它在拦截器上不起作用,但使用自定义装饰器 (def beforeInterceptor = checkExceptions {...}) 很容易解决这个问题。感谢您的帮助。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-04-23
    • 2018-01-21
    • 1970-01-01
    • 2016-12-18
    • 1970-01-01
    • 1970-01-01
    • 2011-10-18
    相关资源
    最近更新 更多