mirror of https://github.com/vapor/docs.git
198 lines
6.6 KiB
Markdown
198 lines
6.6 KiB
Markdown
# 错误
|
||
|
||
Vapor 的错误处理基于 Swift 的 `Error` 协议。路由处理可以通过 `throw` 抛出或返回 `EventLoopFuture` 对象。抛出或返回 Swift 的 `Error` 将导致`500`状态响应并记录错误。`AbortError` 和 `DebuggableError` 分别用于更改响应结果和记录日志。错误的处理由 `ErrorMiddleware` 中间件完成。此中间件默认添加到应用程序中,如果需要,可以用自定义逻辑替换
|
||
|
||
## 中断(Abort)
|
||
|
||
Vapor 提供了名为 `Abort` 的默认错误结构。该结构遵循 `AbortError` 和 `DebuggableError` 协议。你可以使用 HTTP 状态和可选的失败原因对其进行初始化。
|
||
|
||
```swift
|
||
// 404 错误,默认原因”未找到“。
|
||
throw Abort(.notFound)
|
||
|
||
// 401 错误,自定义错误原因。
|
||
throw Abort(.unauthorized, reason: "Invalid Credentials")
|
||
```
|
||
|
||
在旧的异步情况下不支持抛出错误,你必须返回一个 `EventLoopFuture`,就像在 `flatMap` 闭包中一样,你可以返回一个失败的 future。
|
||
|
||
```swift
|
||
guard let user = user else {
|
||
req.eventLoop.makeFailedFuture(Abort(.notFound))
|
||
}
|
||
return user.save()
|
||
```
|
||
|
||
Vapor 提供了一个辅助扩展,用于解包具有可选值的 future 对象:`unwrap(or:)`。
|
||
|
||
```swift
|
||
User.find(id, on: db)
|
||
.unwrap(or: Abort(.notFound))
|
||
.flatMap
|
||
{ user in
|
||
// 非可选,提供给闭包的用户。
|
||
}
|
||
```
|
||
|
||
如果 `User.find` 返回 `nil`,future 将因提供的错误而失败。否则,`flatMap` 将提供一个非可选值。如果使用 `async`/`await` 那么你可以正常处理可选值:
|
||
|
||
```swift
|
||
guard let user = try await User.find(id, on: db) {
|
||
throw Abort(.notFound)
|
||
}
|
||
```
|
||
|
||
|
||
## 中断错误
|
||
|
||
默认情况下,路由闭包抛出或返回的任何 Swift 的 `Error` 都会导致`500 服务器内部错误`的响应。在调试模式下构建时,`ErrorMiddleware` 中间件将包含错误描述。当项目以发布模式构建时,出于安全原因将其删除。
|
||
|
||
要配置生成的 HTTP 状态响应或特定错误的原因,请将其遵循 `AbortError` 协议。
|
||
|
||
```swift
|
||
import Vapor
|
||
|
||
enum MyError {
|
||
case userNotLoggedIn
|
||
case invalidEmail(String)
|
||
}
|
||
|
||
extension MyError: AbortError {
|
||
var reason: String {
|
||
switch self {
|
||
case .userNotLoggedIn:
|
||
return "User is not logged in."
|
||
case .invalidEmail(let email):
|
||
return "Email address is not valid: \(email)."
|
||
}
|
||
}
|
||
|
||
var status: HTTPStatus {
|
||
switch self {
|
||
case .userNotLoggedIn:
|
||
return .unauthorized
|
||
case .invalidEmail:
|
||
return .badRequest
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
## 调试错误
|
||
|
||
`ErrorMiddleware` 中间件使用 `Logger.report(error:)` 方法记录路由抛出的错误。此方法将检查是否遵循 `CustomStringConvertible` 和 `LocalizedError` 等协议,以记录可读消息。
|
||
|
||
要自定义错误日志记录,你可以遵循 `DebuggableError` 协议。该协议包括许多有用的属性,例如唯一标识符、源位置和堆栈跟踪。大多数这些属性都是可选的,这使得采用一致性变得容易。
|
||
|
||
为了更好的遵循 `DebuggableError` 协议,你的错误应该是一个结构,以便它可以在需要时存储源和堆栈跟踪信息。下面是上述 `MyError` 枚举的示例,更新为使用 `struct` 并捕获错误源信息。
|
||
|
||
```swift
|
||
import Vapor
|
||
|
||
struct MyError: DebuggableError {
|
||
enum Value {
|
||
case userNotLoggedIn
|
||
case invalidEmail(String)
|
||
}
|
||
|
||
var identifier: String {
|
||
switch self.value {
|
||
case .userNotLoggedIn:
|
||
return "userNotLoggedIn"
|
||
case .invalidEmail:
|
||
return "invalidEmail"
|
||
}
|
||
}
|
||
|
||
var reason: String {
|
||
switch self.value {
|
||
case .userNotLoggedIn:
|
||
return "User is not logged in."
|
||
case .invalidEmail(let email):
|
||
return "Email address is not valid: \(email)."
|
||
}
|
||
}
|
||
|
||
var value: Value
|
||
var source: ErrorSource?
|
||
|
||
init(
|
||
_ value: Value,
|
||
file: String = #file,
|
||
function: String = #function,
|
||
line: UInt = #line,
|
||
column: UInt = #column
|
||
) {
|
||
self.value = value
|
||
self.source = .init(
|
||
file: file,
|
||
function: function,
|
||
line: line,
|
||
column: column
|
||
)
|
||
}
|
||
}
|
||
```
|
||
|
||
`DebuggableError` 协议有几个其他属性,如 `possibleCauses` 和 `suggestedFixes` 你可以使用它们来提高错误的可调试性。查看协议本身以获取更多信息。
|
||
|
||
## 堆栈跟踪
|
||
|
||
Vapor 支持查看正常 Swift 错误和崩溃的堆栈跟踪。
|
||
|
||
### Swift 回溯
|
||
|
||
在 Linux 上,当出现致命错误或断言时,Vapor 使用 [SwiftBacktrace](https://github.com/swift-server/swift-backtrace) 库提供堆栈跟踪。为了让它正常工作,你的应用程序必须在编译过程中包含调试符号。
|
||
|
||
```sh
|
||
swift build -c release -Xswiftc -g
|
||
```
|
||
|
||
### 错误跟踪
|
||
|
||
默认情况下,`Abort` 将在初始化时捕获当前堆栈跟踪。你的自定义错误类型可以通过遵循 `DebuggableError` 协议并存储 `StackTrace.capture()` 来实现。
|
||
|
||
```swift
|
||
import Vapor
|
||
|
||
struct MyError: DebuggableError {
|
||
var identifier: String
|
||
var reason: String
|
||
var stackTrace: StackTrace?
|
||
|
||
init(
|
||
identifier: String,
|
||
reason: String,
|
||
stackTrace: StackTrace? = .capture()
|
||
) {
|
||
self.identifier = identifier
|
||
self.reason = reason
|
||
self.stackTrace = stackTrace
|
||
}
|
||
}
|
||
```
|
||
|
||
当你的应用程序的[日志级别](logging.zh.md#level)设置为 `.debug` 或更低时,错误堆栈跟踪将包含在日志输出中。
|
||
|
||
当日志级别大于 `.debug` 时,不会捕获堆栈跟踪。要覆盖此行为,请在 `StackTrace.isCaptureEnabled` 中手动设置 `configure`。
|
||
|
||
```swift
|
||
// 无论日志级别如何,始终捕获堆栈跟踪。
|
||
StackTrace.isCaptureEnabled = true
|
||
```
|
||
|
||
## 错误中间件
|
||
|
||
`ErrorMiddleware` 是默认添加到应用程序的唯一中间件。该中间件将路由处理抛出或返回的 Swift 错误转换为 HTTP 响应。如果没有这个中间件,抛出的错误将导致连接被关闭而没有响应。
|
||
|
||
要定制 `AbortError` 和 `DebuggableError` 所提供的错误处理之外的错误处理,你可以用自己的错误处理逻辑替换 `ErrorMiddleware` 中间件。要做到这一点,首先通过设置 `app.middleware` 为空删除默认的错误中间件。然后,将你自己的错误处理中间件作为第一个中间件添加到应用程序中。
|
||
|
||
```swift
|
||
// 移除已存在的中间件。
|
||
app.middleware = .init()
|
||
// 首先添加自定义错误中间件。
|
||
app.middleware.use(MyErrorMiddleware())
|
||
```
|
||
|
||
很少有中间件应该放在错误处理中间件*之前*。但 `CORSMiddleware` 中间件不适用该规则。
|