mirror of https://github.com/vapor/docs.git
Support Chinese doc for Vapor. (#664)
* # Support Chinese doc for Vapor. * # Added `zh/` path for searchIndex file.
This commit is contained in:
parent
7451709cfb
commit
fee911560f
|
|
@ -0,0 +1,129 @@
|
|||
# 指令
|
||||
|
||||
Vapor 的 Command API 允许你打造自定义命令行函数并且与终端进行交互。Vapor的默认指令,例如 `serve`, `routes` 和 `migrate`, 都是通过这个 Api 实现的。
|
||||
|
||||
## 默认指令
|
||||
|
||||
通过 `--help` 选项你可以了解更多 Vapor 的默认指令。
|
||||
|
||||
```sh
|
||||
vapor run --help
|
||||
```
|
||||
|
||||
你同样可以使用 `--help` 在特定的指令上以查看这个指令接受的参数和选项。
|
||||
|
||||
```sh
|
||||
vapor run serve --help
|
||||
```
|
||||
|
||||
### Xcode
|
||||
|
||||
你可以通过加入参数到 Xcode 的 `Run` scheme 以运行指令。通过一下三步做到这点:
|
||||
|
||||
- 选择 `Run` scheme (在 运行/停止 按钮的右边)
|
||||
- 选择 "Edit Scheme"
|
||||
- 选择 "Run"
|
||||
- 选择 "Arguments" 这一栏
|
||||
- 将指令的名词添加到 "Arguments Passed On Launch" (例如, `serve`)
|
||||
|
||||
## 自定义指令
|
||||
|
||||
你可以通过一个符合 `Command` 协议的类型创建你自己的命令
|
||||
|
||||
```swift
|
||||
import Vapor
|
||||
|
||||
struct HelloCommand: Command {
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
将自定义指令加入到 `app.commands` 将允许你使用这个指令通过 `vapor run`。
|
||||
|
||||
```swift
|
||||
app.commands.use(HelloCommand(), as: "hello")
|
||||
```
|
||||
|
||||
为了符合 `Command` ,你必须实现 `run` 方法。这个方法需要你定义一个 `Signature` 。你还需要提供一个默认的帮助文本。
|
||||
|
||||
```swift
|
||||
import Vapor
|
||||
|
||||
struct HelloCommand: Command {
|
||||
struct Signature: CommandSignature { }
|
||||
|
||||
var help: String {
|
||||
"Says hello"
|
||||
}
|
||||
|
||||
func run(using context: CommandContext, signature: Signature) throws {
|
||||
context.console.print("Hello, world!")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
这个简单的指令例子没有参数或者选项,所以让 signature 为空。
|
||||
|
||||
你可以通过 context 访问当前的 console(控制台)。console 有许多有帮助的方法来提示用户输入,格式化输出,还有更多。
|
||||
|
||||
```swift
|
||||
let name = context.console.ask("What is your \("name", color: .blue)?")
|
||||
context.console.print("Hello, \(name) 👋")
|
||||
```
|
||||
|
||||
通过运行你的命令来测试:
|
||||
|
||||
```sh
|
||||
vapor run hello
|
||||
```
|
||||
|
||||
### Cowsay
|
||||
|
||||
看一下这个著名的 [`cowsay`](https://en.wikipedia.org/wiki/Cowsay) 指令的重制版。它将作为 `@Argument` 和 `@Option` 使用的一个例子。
|
||||
|
||||
```swift
|
||||
import Vapor
|
||||
|
||||
struct Cowsay: Command {
|
||||
struct Signature: CommandSignature {
|
||||
@Argument(name: "message")
|
||||
var message: String
|
||||
|
||||
@Option(name: "eyes", short: "e")
|
||||
var eyes: String?
|
||||
|
||||
@Option(name: "tongue", short: "t")
|
||||
var tongue: String?
|
||||
}
|
||||
|
||||
var help: String {
|
||||
"Generates ASCII picture of a cow with a message."
|
||||
}
|
||||
|
||||
func run(using context: CommandContext, signature: Signature) throws {
|
||||
let eyes = signature.eyes ?? "oo"
|
||||
let tongue = signature.tongue ?? " "
|
||||
let cow = #"""
|
||||
< $M >
|
||||
\ ^__^
|
||||
\ ($E)\_______
|
||||
(__)\ )\/\
|
||||
$T ||----w |
|
||||
|| ||
|
||||
"""#.replacingOccurrences(of: "$M", with: signature.message)
|
||||
.replacingOccurrences(of: "$E", with: eyes)
|
||||
.replacingOccurrences(of: "$T", with: tongue)
|
||||
context.console.print(cow)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
尝试将这个指令加入到程序然后运行它。
|
||||
|
||||
```swift
|
||||
app.commands.use(Cowsay(), as: "cowsay")
|
||||
```
|
||||
|
||||
```sh
|
||||
vapor run cowsay sup --eyes ^^ --tongue "U "
|
||||
```
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
# Middleware
|
||||
|
||||
Middleware 是 client 和路由处理程序间的一个逻辑链。它允许你在传入请求到达路由处理程序之前对传入请求执行操作,并且在输出响应到达 client 之前对传出响应执行操作。
|
||||
|
||||
## Configuration
|
||||
|
||||
可以使用 `app.middleware` 在 `configure(_:)` 中全局(针对每条路由)注册 Middleware。
|
||||
|
||||
```swift
|
||||
app.middleware.use(MyMiddleware())
|
||||
```
|
||||
|
||||
你也可以通过路由组的方式给单个路由添加 Middleware。
|
||||
|
||||
```swift
|
||||
let group = app.grouped(MyMiddleware())
|
||||
group.get("foo") { req in
|
||||
// 该请求通过 MyMiddleware 传递。
|
||||
}
|
||||
```
|
||||
|
||||
### Order
|
||||
|
||||
Middleware 的添加顺序非常重要。进入应用程序的请求将按照在 middleware 添加的顺序依次执行。
|
||||
离开应用程序的响应将以相反的顺序通过 Middleware 返回。特定的路由 Middleware 始终在应用程序 Middleware 之后运行。
|
||||
|
||||
请看以下示例:
|
||||
|
||||
```swift
|
||||
app.middleware.use(MiddlewareA())
|
||||
app.middleware.use(MiddlewareB())
|
||||
|
||||
app.group(MiddlewareC()) {
|
||||
$0.get("hello") { req in
|
||||
"Hello, middleware."
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`GET /hello` 这个请求将按照以下顺序访问 Middleware:
|
||||
|
||||
```
|
||||
Request → A → B → C → Handler → C → B → A → Response
|
||||
```
|
||||
|
||||
## File Middleware
|
||||
|
||||
`FileMiddleware` 允许从项目的 Public 文件夹向 client 提供资源。你可以在这里存放 css 或者位图图片等静态文件。
|
||||
|
||||
```swif
|
||||
let file = FileMiddleware(publicDirectory: app.directory.publicDirectory)
|
||||
app.middleware.use(file)
|
||||
```
|
||||
|
||||
一旦注册 `FileMiddleware`,比如 `Public/images/logo.png` 的文件可以在 Leaf 模板通过 `<img src="/images/logo.png"/>` 方式引用。
|
||||
|
||||
|
||||
## CORS Middleware
|
||||
|
||||
跨域资源共享(Cross-origin resource sharing,缩写:CORS),用于让网页的受限资源能够被其他域名的页面访问的一种机制。通过该机制,页面能够自由地使用不同源(英語:cross-origin)的图片、样式、脚本、iframes 以及视频。Vapor 内置的 REST API 需要 CORS 策略,以便将请求安全地返回到 Web 浏览器。
|
||||
|
||||
配置示例如下所示:
|
||||
|
||||
```swift
|
||||
let corsConfiguration = CORSMiddleware.Configuration(
|
||||
allowedOrigin: .all,
|
||||
allowedMethods: [.GET, .POST, .PUT, .OPTIONS, .DELETE, .PATCH],
|
||||
allowedHeaders: [.accept, .authorization, .contentType, .origin, .xRequestedWith, .userAgent, .accessControlAllowOrigin]
|
||||
)
|
||||
let cors = CORSMiddleware(configuration: corsConfiguration)
|
||||
let error = ErrorMiddleware.default(environment: app.environment)
|
||||
// 清除现有的 middleware。
|
||||
app.middleware = .init()
|
||||
app.middleware.use(cors)
|
||||
app.middleware.use(error)
|
||||
```
|
||||
|
||||
由于抛出的错误会立即返回给客户端,因此必须在 `ErrorMiddleware` 之前注册 `CORSMiddleware`。否则,将返回不带 CORS 标头的 HTTP 错误响应,且浏览器无法读取该错误响应。
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
Sessions allow you to persist a user's data between multiple requests. Sessions work by creating and returning a unique cookie alongside the HTTP response when a new session is initialized. Browsers will automatically detect this cookie and include it in future requests. This allows Vapor to automatically restore a specific user's session in your request handler.
|
||||
|
||||
Sessions are great for front-end web applications built in Vapor that serve HTML directly to web browsers. For APIs, we recommend using stateless, [token-based authentication](authentication.md) to persist user data between requests.
|
||||
Sessions are great for front-end web applications built in Vapor that serve HTML directly to web browsers. For APIs, we recommend using stateless, [token-based authentication](../security/authentication.md) to persist user data between requests.
|
||||
|
||||
## Configuration
|
||||
|
||||
|
|
@ -51,7 +51,7 @@ For production use cases, take a look at the other session drivers which utilize
|
|||
|
||||
### Fluent
|
||||
|
||||
Fluent includes support for storing session data in your application's database. This section assumes you have [configured Fluent](fluent/overview.md) and can connect to a database. The first step is to enable the Fluent sessions driver.
|
||||
Fluent includes support for storing session data in your application's database. This section assumes you have [configured Fluent](../fluent/overview.md) and can connect to a database. The first step is to enable the Fluent sessions driver.
|
||||
|
||||
```swift
|
||||
import Fluent
|
||||
|
|
@ -75,7 +75,7 @@ Make sure to run your application's migrations after adding the new migration. S
|
|||
|
||||
### Redis
|
||||
|
||||
Redis provides support for storing session data in your configured Redis instance. This section assumes you have [configured Redis](redis/overview.md) and can send commands to the Redis instance.
|
||||
Redis provides support for storing session data in your configured Redis instance. This section assumes you have [configured Redis](../redis/overview.md) and can send commands to the Redis instance.
|
||||
|
||||
To use Redis for Sessions, select it when configuring your application:
|
||||
|
||||
|
|
@ -88,7 +88,7 @@ app.sessions.use(.redis)
|
|||
This will configure sessions to use the Redis sessions driver with the default behavior.
|
||||
|
||||
!!! seealso
|
||||
Refer to [Redis → Sessions](redis/sessions.md) for more detailed information about Redis and Sessions.
|
||||
Refer to [Redis → Sessions](../redis/sessions.md) for more detailed information about Redis and Sessions.
|
||||
|
||||
## Session Data
|
||||
|
||||
|
|
@ -65,7 +65,7 @@ try app.test(.GET, "hello") { res in
|
|||
|
||||
The first two parameters are the HTTP method and URL to request. The trailing closure accepts the HTTP response which you can verify using `XCTAssert` methods.
|
||||
|
||||
For more complex requests, you can supply a `beforeRequest` closure to modify headers or encode content. Vapor's [Content API](content.md) is available on both the test request and response.
|
||||
For more complex requests, you can supply a `beforeRequest` closure to modify headers or encode content. Vapor's [Content API](../basics/content.md) is available on both the test request and response.
|
||||
|
||||
```swift
|
||||
try app.test(.POST, "todos", beforeRequest: { req in
|
||||
|
|
@ -0,0 +1,102 @@
|
|||
# 测试
|
||||
|
||||
Vapor 包含一个名为 `XCTVapor` 的模块,它提供了基于 `XCTest` 的测试帮助程序。这些测试辅助程序允许你以编程方式或通过 HTTP 服务器将测试请求发送至 Vapor 应用程序。
|
||||
|
||||
## 入门
|
||||
|
||||
要使用 `XCTVapor` 模块,请确保在你的项目 `Package.swift` 文件已添加了对应的 **testTarget**。
|
||||
|
||||
```swift
|
||||
let package = Package(
|
||||
...
|
||||
dependencies: [
|
||||
.package(url: "https://github.com/vapor/vapor.git", from: "4.0.0")
|
||||
],
|
||||
targets: [
|
||||
...
|
||||
.testTarget(name: "AppTests", dependencies: [
|
||||
.target(name: "App"),
|
||||
.product(name: "XCTVapor", package: "vapor"),
|
||||
])
|
||||
]
|
||||
)
|
||||
```
|
||||
|
||||
然后,在测试文件的顶部添加 `import XCTVapor`,创建继承于 `XCTestCase` 的子类来编写测试用例。
|
||||
|
||||
```swift
|
||||
import XCTVapor
|
||||
|
||||
final class MyTests: XCTestCase {
|
||||
func testStub() throws {
|
||||
// 在这里测试。
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
当你的应用程序执行测试时,每个以 `test` 开头的函数都会自动运行。
|
||||
|
||||
### 运行测试
|
||||
|
||||
在使用 `Package` 方案的情况下,使用 `cmd+u` 在 Xcode 中运行测试用例。
|
||||
或使用 `swift test --enable-test-discovery` 通过 CLI 进行测试。
|
||||
|
||||
## 可测试的应用程序
|
||||
|
||||
使用 `.testing` 环境初始化一个 `Application` 实例。你必须在此应用程序初始化之前,调用 `app.shutdown()`。
|
||||
|
||||
```swift
|
||||
let app = Application(.testing)
|
||||
defer { app.shutdown() }
|
||||
try configure(app)
|
||||
```
|
||||
|
||||
将 `Application` 实例对象作为入参传到 `configure(_:)` 方法来应用你的配置,之后可以应用到任何仅测试的配置。
|
||||
|
||||
### 发送请求
|
||||
|
||||
要向你的应用程序发送一个测试请求,请使用 `test` 方法。
|
||||
|
||||
```swift
|
||||
try app.test(.GET, "hello") { res in
|
||||
XCTAssertEqual(res.status, .ok)
|
||||
XCTAssertEqual(res.body.string, "Hello, world!")
|
||||
}
|
||||
```
|
||||
|
||||
前两个参数是 HTTP 方法和请求的 URL。后面的尾随闭包接受 HTTP 响应,你可以使用 `XCTAssert` 方法进行验证。
|
||||
|
||||
对于更复杂的请求,你可以提供一个 `beforeRequest` 闭包来修改请求头或编码内容。Vapor 的 [Content API](../basics/content.md) 可以在测试请求和响应中使用。
|
||||
|
||||
```swift
|
||||
try app.test(.POST, "todos", beforeRequest: { req in
|
||||
try req.content.encode(["title": "Test"])
|
||||
}, afterResponse: { res in
|
||||
XCTAssertEqual(res.status, .created)
|
||||
let todo = try res.content.decode(Todo.self)
|
||||
XCTAssertEqual(todo.title, "Test")
|
||||
})
|
||||
```
|
||||
|
||||
### 可测试的方法
|
||||
|
||||
Vapor 的测试 API 支持以编程方式并通过实时 HTTP 服务器发送测试请求。
|
||||
你可以通过使用 `testable` 方法来指定你想要使用的方法。
|
||||
|
||||
```swift
|
||||
// 使用程序化测试。
|
||||
app.testable(method: .inMemory).test(...)
|
||||
|
||||
// 通过一个实时的 HTTP 服务器运行测试。
|
||||
app.testable(method: .running).test(...)
|
||||
```
|
||||
|
||||
默认情况下使用 `inMemory` 选项。
|
||||
|
||||
`running` 选项支持传递一个特定的端口来使用。默认情况下使用的是 `8080`。
|
||||
|
||||
```swift
|
||||
.running(port: 8123)
|
||||
```
|
||||
|
||||
当然,你也可以修改为其他端口进行测试。
|
||||
|
|
@ -0,0 +1,124 @@
|
|||
# WebSockets
|
||||
|
||||
[WebSockets](https://zh.wikipedia.org/wiki/WebSocket) 允许客户端和服务器之间进行双向通信。与 HTTP 的请求和响应模式不同,WebSocket 可以在两端之间发送任意数量的消息。Vapor的WebSocket API允许你创建异步处理消息的客户端和服务器。
|
||||
|
||||
## 服务器
|
||||
|
||||
你可以使用 [Routing API](../basics/routing.md) 将 WebSocket 端点添加到现有的 Vapor 应用程序中。使用 `webSocket` 的方法就像使用 `get` 或 `post` 一样。
|
||||
|
||||
```swift
|
||||
app.webSocket("echo") { req, ws in
|
||||
// Connected WebSocket.
|
||||
print(ws)
|
||||
}
|
||||
```
|
||||
|
||||
WebSocket 路由可以像普通路由一样由中间件进行分组和保护。
|
||||
|
||||
除了接受传入的 HTTP 请求之外,WebSocket 处理程序还可以接受新建立的 WebSocket 连接。有关使用此 WebSocket 发送和阅读消息的更多信息,请参考下文。
|
||||
|
||||
## 客户端
|
||||
|
||||
要连接到远程 WebSocket 端口,请使用 `WebSocket.connect` 。
|
||||
|
||||
```swift
|
||||
WebSocket.connect(to: "ws://echo.websocket.org", on: eventLoop) { ws in
|
||||
// Connected WebSocket.
|
||||
print(ws)
|
||||
}
|
||||
```
|
||||
|
||||
`connect` 方法返回建立连接后完成的 future。 连接后将使用新连接的 WebSocket 调用提供的闭包。有关使用 WebSocket 发送和阅读消息的更多信息,请参见下文。
|
||||
|
||||
## 消息
|
||||
|
||||
`WebSocket` 类具有发送和接收消息以及侦听诸如关闭之类的方法。WebSocket 可以通过两种协议传输数据:文本以及二进制数据。文本消息为 UTF-8 字符串,而二进制数据为字节数组。
|
||||
|
||||
### 发送
|
||||
|
||||
可以使用 WebSocket 的 `send` 方法来发送消息。
|
||||
|
||||
```swift
|
||||
ws.send("Hello, world")
|
||||
```
|
||||
|
||||
将 `String` 传递给此方法即可发送文本消息。二进制消息可以通过如下传递 `[UInt8]` 数据来发送:
|
||||
|
||||
```swift
|
||||
ws.send([1, 2, 3])
|
||||
```
|
||||
|
||||
发送消息是异步处理,你可以向 send 方法提供一个 `EventLoopPromise`,以便在消息发送完成或发送失败时得到通知。
|
||||
|
||||
```swift
|
||||
let promise = eventLoop.makePromise(of: Void.self)
|
||||
ws.send(..., promise: promise)
|
||||
promise.futureResult.whenComplete { result in
|
||||
// 发送成功或失败。
|
||||
}
|
||||
```
|
||||
|
||||
### 接收
|
||||
|
||||
接收的消息通过 `onText` 和 `onBinary` 回调进行处理。
|
||||
|
||||
```swift
|
||||
ws.onText { ws, text in
|
||||
// 这个方法接收的是字符串。
|
||||
print(text)
|
||||
}
|
||||
|
||||
ws.onBinary { ws, binary in
|
||||
// 这个方法接收二进制数组。
|
||||
print(binary)
|
||||
}
|
||||
```
|
||||
|
||||
WebSocket 对象本身作为这些回调的第一个参数提供,以防止循环引用。接收数据后,使用此引用对 WebSocket 采取对应操作。例如,发送回复信息:
|
||||
|
||||
```swift
|
||||
// Echoes received messages.
|
||||
ws.onText { ws, text in
|
||||
ws.send(text)
|
||||
}
|
||||
```
|
||||
|
||||
## 关闭
|
||||
|
||||
如果要关闭 WebSocket,请调用 `close` 方法。
|
||||
|
||||
```swift
|
||||
ws.close()
|
||||
```
|
||||
|
||||
该方法返回的 future 将在 WebSocket 关闭时完成。你也可以像 `send` 方法一样,向该方法传递一个 promise。
|
||||
|
||||
```swift
|
||||
ws.close(promise: nil)
|
||||
```
|
||||
|
||||
要在对方关闭连接时收到通知,请使用 `onClose`。这样当客户端或服务器关闭 WebSocket 时,将会触发此 future 方法。
|
||||
|
||||
```swift
|
||||
ws.onClose.whenComplete { result in
|
||||
// 关闭成功或失败。
|
||||
}
|
||||
```
|
||||
|
||||
当 WebSocket 关闭时会返回 `closeCode` 属性,可用于确定对方关闭连接的原因。
|
||||
|
||||
## Ping / Pong
|
||||
|
||||
客户端和服务器会自动发送 ping 和 pong 心跳消息,来保持 WebSocket 的连接。你的程序可以使用 `onPing` 和 `onPong` 回调监听这些事件。
|
||||
|
||||
```swift
|
||||
ws.onPing { ws in
|
||||
// 接收到了 Ping 消息。
|
||||
}
|
||||
|
||||
ws.onPong { ws in
|
||||
// 接收到了 Pong 消息。
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,431 @@
|
|||
# Async
|
||||
|
||||
## Async Await
|
||||
|
||||
Swift 5.5 在语言层面上以 `async`/`await` 的形式引进了并发性。它提供了优秀的方式去处理异步在 Swift 以及 Vapor 应用中。
|
||||
|
||||
Vapor 是在 [SwiftNIO](https://github.com/apple/swift-nio.git) 的基础上构建的, SwiftNIO 为低层面的异步编程提供了基本类型。这些类型曾经是(现在依然是)贯穿整个 Vapor 在 `async`/`await` 到来之前。现在大部分代码可以用 `async`/`await` 编写来代替 `EventLoopFuture`。这将简化您的代码,使其更容易推理。
|
||||
|
||||
现在大部分的 Vapor 的 APIs 同时提供 `EventLoopFuture` and `async`/`await` 两个版本供你选择。通常,你应该只选择一种编程方式在单个路由 handler 中,而不应该混用。对于应该显示控制 event loops,或者非常需要高性能的应用,应该继续使用 `EventLoopFuture` 在自定义运行器被实现之前(until custom executors are implemented)。 对于其他应用,你应该使用 `async`/`await` 因为它的好处、可读性和可维护性远远超过了任何小的性能损失。
|
||||
|
||||
### 迁徙到 async/await
|
||||
|
||||
为了适配 async/await 这里有几个步骤需要做。第一步,如果你使用 macOS 你必须使用 macOS 12 Monterey 或者更高以及 Xcode13.1 或者更高。 对于其他平台你需要运行 Swift5.5 或者更高,然后情确认你已经更新了所有依赖。
|
||||
|
||||
在你的 Package.swift, 在第一行把 swift-tools-version 设置为 5.5:
|
||||
|
||||
```swift
|
||||
// swift-tools-version:5.5
|
||||
import PackageDescription
|
||||
|
||||
// ...
|
||||
```
|
||||
|
||||
接下来,设置 platform version 为 macOS 12:
|
||||
|
||||
```swift
|
||||
platforms: [
|
||||
.macOS(.v12)
|
||||
],
|
||||
```
|
||||
|
||||
最后 更新 `Run` 目标让它变成一个可运行的目标:
|
||||
|
||||
```swift
|
||||
.executableTarget(name: "Run", dependencies: [.target(name: "App")]),
|
||||
```
|
||||
|
||||
注意:如果你部署在Linux环境请确保你更新到了最新的Swift版本。比如在 Heroku 或者在你的 Dockerfile。举个例子你的 Dockerfile 应该变为:
|
||||
|
||||
```diff
|
||||
-FROM swift:5.2-focal as build
|
||||
+FROM swift:5.5-focal as build
|
||||
...
|
||||
-FROM swift:5.2-focal-slim
|
||||
+FROM swift:5.5-focal-slim
|
||||
```
|
||||
|
||||
现在你可以迁徙现存的代码。通常返回 `EventLoopFuture` 的方法现在变为返回 `async`。比如:
|
||||
|
||||
```swift
|
||||
routes.get("firstUser") { req -> EventLoopFuture<String> in
|
||||
User.query(on: req.db).first().unwrap(or: Abort(.notFound)).flatMap { user in
|
||||
user.lastAccessed = Date()
|
||||
return user.update(on: req.db).map {
|
||||
return user.name
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
现在变为:
|
||||
|
||||
```swift
|
||||
routes.get("firstUser") { req async throws -> String in
|
||||
guard let user = try await User.query(on: req.db).first() else {
|
||||
throw Abort(.notFound)
|
||||
}
|
||||
user.lastAccessed = Date()
|
||||
try await user.update(on: req.db)
|
||||
return user.name
|
||||
}
|
||||
```
|
||||
|
||||
### 使用新旧api
|
||||
|
||||
如果你遇到还未支持 `async`/`await` 的API,你可以调用 `.get()` 方法来返回一个 `EventLoopFuture`。
|
||||
|
||||
比如
|
||||
|
||||
```swift
|
||||
return someMethodCallThatReturnsAFuture().flatMap { futureResult in
|
||||
// use futureResult
|
||||
}
|
||||
```
|
||||
|
||||
可以变为
|
||||
|
||||
```swift
|
||||
let futureResult = try await someMethodThatReturnsAFuture().get()
|
||||
```
|
||||
|
||||
如果你需要反过来,你可以把
|
||||
|
||||
```swift
|
||||
let myString = try await someAsyncFunctionThatGetsAString()
|
||||
```
|
||||
|
||||
变为
|
||||
|
||||
```swift
|
||||
let promise = request.eventLoop.makePromise(of: String.self)
|
||||
promise.completeWithTask {
|
||||
try await someAsyncFunctionThatGetsAString()
|
||||
}
|
||||
let futureString: EventLoopFuture<String> = promise.futureResult
|
||||
```
|
||||
|
||||
## `EventLoopFuture`
|
||||
|
||||
你可能已经注意到在 Vapor 中一些API返回一个 `EventLoopFuture` 的泛型。如果这是你第一次听到这个特性,它们一开始可能看起来有点令人困惑。但是别担心这个手册会教你怎么利用这些强大的API。
|
||||
|
||||
Promises 和 futures 是相关的, 但是截然不同的类型。
|
||||
|
||||
|类型|描述|是否可修改|
|
||||
|-|-|-|
|
||||
|`EventLoopFuture`|代表一个现在还不可用的值|read-only|
|
||||
|`EventLoopPromise`|一个可以异步提供值的promise|read/write|
|
||||
|
||||
|
||||
Futures 是基于回调的异步api的替代方案。可以以简单的闭包所不能的方式进行链接和转换。
|
||||
|
||||
## 转换
|
||||
|
||||
就像Swift中的可选选项和数组一样,futures 可以被映射和平映射。这些是你在 futures 中最基本的操作。
|
||||
|
||||
|method|argument|description|
|
||||
|-|-|-|
|
||||
|[`map`](#map)|`(T) -> U`|Maps a future value to a different value.|
|
||||
|[`flatMapThrowing`](#flatmapthrowing)|`(T) throws -> U`|Maps a future value to a different value or an error.|
|
||||
|[`flatMap`](#flatmap)|`(T) -> EventLoopFuture<U>`|Maps a future value to different _future_ value.|
|
||||
|[`transform`](#transform)|`U`|Maps a future to an already available value.|
|
||||
|
||||
如果你看一下 `map` 和 `flatMap` 在 `Optional<T>` 和 `Array<T>` 中的方法签名(method signatures)。你会看到他们和在 `EventLoopFuture<T>` 中的方法非常相似。
|
||||
|
||||
### map
|
||||
|
||||
`map` 方法允许你把一个未来值转换成另外一个值。 因为这个未来的值可能现在还不可用,我们必须提供一个闭包来接受它的值。
|
||||
|
||||
```swift
|
||||
/// 假设我们将来从某些API得到一个字符串。
|
||||
let futureString: EventLoopFuture<String> = ...
|
||||
|
||||
/// 把这个字符串转换成整形
|
||||
let futureInt = futureString.map { string in
|
||||
print(string) // The actual String
|
||||
return Int(string) ?? 0
|
||||
}
|
||||
|
||||
/// We now have a future integer
|
||||
print(futureInt) // EventLoopFuture<Int>
|
||||
```
|
||||
|
||||
### flatMapThrowing
|
||||
|
||||
`flatMapThrowing` 方法允许你把一个未来值转换成另一个值或者抛出一个错误。
|
||||
|
||||
!!! 信息
|
||||
因为抛出错误必须在内部创建一个新的future,所以这个方法前缀为 `flatMap`,即使闭包不接受future返回。
|
||||
|
||||
```swift
|
||||
/// 假设我们将来从某些API得到一个字符串。
|
||||
let futureString: EventLoopFuture<String> = ...
|
||||
|
||||
/// 把这个字符串转换成整形
|
||||
let futureInt = futureString.flatMapThrowing { string in
|
||||
print(string) // The actual String
|
||||
// 将字符串转换为整数或抛出错误
|
||||
guard let int = Int(string) else {
|
||||
throw Abort(...)
|
||||
}
|
||||
return int
|
||||
}
|
||||
|
||||
/// We now have a future integer
|
||||
print(futureInt) // EventLoopFuture<Int>
|
||||
```
|
||||
|
||||
### flatMap
|
||||
|
||||
flatMap方法允许你将未来值转换为另一个未来值。它得到的名称“扁平”映射,因为它允许你避免创建嵌套的未来(例如,`EventLoopFuture<EventLoopFuture<T>>`)。换句话说,它帮助您保持泛型平坦。
|
||||
|
||||
```swift
|
||||
/// Assume we get a future string back from some API
|
||||
let futureString: EventLoopFuture<String> = ...
|
||||
|
||||
/// Assume we have created an HTTP client
|
||||
let client: Client = ...
|
||||
|
||||
/// flatMap the future string to a future response
|
||||
let futureResponse = futureString.flatMap { string in
|
||||
client.get(string) // EventLoopFuture<ClientResponse>
|
||||
}
|
||||
|
||||
/// We now have a future response
|
||||
print(futureResponse) // EventLoopFuture<ClientResponse>
|
||||
```
|
||||
|
||||
!!! 信息
|
||||
如果我们在上面的例子中使用 `map`,我们将会得到: `EventLoopFuture<EventLoopFuture<ClientResponse>>`。
|
||||
|
||||
要在 `flatMap` 中调用一个抛出方法,使用Swift的 `do` / `catch` 关键字并创建一个[completed future](#makefuture)。
|
||||
To call a throwing method inside of a `flatMap`, use Swift's `do` / `catch` keywords and create a [completed future](#makefuture).
|
||||
|
||||
```swift
|
||||
/// Assume future string and client from previous example.
|
||||
let futureResponse = futureString.flatMap { string in
|
||||
let url: URL
|
||||
do {
|
||||
// Some synchronous throwing method.
|
||||
url = try convertToURL(string)
|
||||
} catch {
|
||||
// Use event loop to make pre-completed future.
|
||||
return eventLoop.makeFailedFuture(error)
|
||||
}
|
||||
return client.get(url) // EventLoopFuture<ClientResponse>
|
||||
}
|
||||
```
|
||||
|
||||
### transform
|
||||
`transform` 方法允许您修改 future 的值,而忽略现有值。这对于转换 `EventLoopFuture<Void>` 的结果特别有用,在这种情况下未来的实际值并不重要。
|
||||
|
||||
!!! 提示
|
||||
`EventLoopFuture<Void>`, 有时也被称为信号,它的唯一目的是通知您某些异步操作的完成或失败。
|
||||
|
||||
```swift
|
||||
/// Assume we get a void future back from some API
|
||||
let userDidSave: EventLoopFuture<Void> = ...
|
||||
|
||||
/// Transform the void future to an HTTP status
|
||||
let futureStatus = userDidSave.transform(to: HTTPStatus.ok)
|
||||
print(futureStatus) // EventLoopFuture<HTTPStatus>
|
||||
```
|
||||
|
||||
即使我们提供了一个已经可用的值为 `transform`,它仍然是一个 __transformation__ 。直到所有先前的 future 都完成(或失败),future 才会完成。
|
||||
|
||||
### 链接(Chaining)
|
||||
|
||||
关于 transformations,最重要的一点是它们可以被链接起来。这允许您轻松地表示许多转换和子任务。
|
||||
|
||||
让我们修改上面的示例,看看如何利用链接。
|
||||
|
||||
```swift
|
||||
/// Assume we get a future string back from some API
|
||||
let futureString: EventLoopFuture<String> = ...
|
||||
|
||||
/// Assume we have created an HTTP client
|
||||
let client: Client = ...
|
||||
|
||||
/// Transform the string to a url, then to a response
|
||||
let futureResponse = futureString.flatMapThrowing { string in
|
||||
guard let url = URL(string: string) else {
|
||||
throw Abort(.badRequest, reason: "Invalid URL string: \(string)")
|
||||
}
|
||||
return url
|
||||
}.flatMap { url in
|
||||
client.get(url)
|
||||
}
|
||||
|
||||
print(futureResponse) // EventLoopFuture<ClientResponse>
|
||||
```
|
||||
|
||||
在初始调用 map 之后,创建了一个临时的 `EventLoopFuture<URL>`。然后,这个future立即平映射(flat-mapped)到 `EventLoopFuture<Response>`
|
||||
|
||||
## Future
|
||||
|
||||
让我们看看使用 `EventLoopFuture<T>` 的一些其他方法。
|
||||
|
||||
### makeFuture
|
||||
|
||||
You can use an event loop to create pre-completed future with either the value or an error.
|
||||
|
||||
```swift
|
||||
// Create a pre-succeeded future.
|
||||
let futureString: EventLoopFuture<String> = eventLoop.makeSucceededFuture("hello")
|
||||
|
||||
// Create a pre-failed future.
|
||||
let futureString: EventLoopFuture<String> = eventLoop.makeFailedFuture(error)
|
||||
```
|
||||
|
||||
### whenComplete
|
||||
|
||||
|
||||
你可以使用 `whenComplete` 来添加一个回调函数,它将在未来的成功或失败时执行。
|
||||
|
||||
```swift
|
||||
/// Assume we get a future string back from some API
|
||||
let futureString: EventLoopFuture<String> = ...
|
||||
|
||||
futureString.whenComplete { result in
|
||||
switch result {
|
||||
case .success(let string):
|
||||
print(string) // The actual String
|
||||
case .failure(let error):
|
||||
print(error) // A Swift Error
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
!!! note
|
||||
您可以向 future 添加任意数量的回调。
|
||||
|
||||
### Wait
|
||||
|
||||
您可以使用 `.wait()` 来同步等待future完成。由于future可能会失败,这个调用是可抛出错误的。
|
||||
|
||||
```swift
|
||||
/// Assume we get a future string back from some API
|
||||
let futureString: EventLoopFuture<String> = ...
|
||||
|
||||
/// Block until the string is ready
|
||||
let string = try futureString.wait()
|
||||
print(string) /// String
|
||||
```
|
||||
|
||||
`wait()` 只能在后台线程或主线程中使用,也就是在 `configure.swift` 中。它不能在事件循环线程(event loop)上使用,也就是在路由闭包中。
|
||||
|
||||
!!! 警告
|
||||
试图在事件循环线程上调用 `wait()` 将导致断言失败。
|
||||
|
||||
|
||||
## Promise
|
||||
|
||||
大多数时候,您将转换 Vapor 的 api 返回的 futures。然而,在某些情况下,你可能需要创造自己的 promise。
|
||||
|
||||
要创建一个 promise,你需要访问一个 `EventLoop`。你可以根据上下文(context)从 `Application` 或 `Request` 获得一个 event loop。
|
||||
|
||||
```swift
|
||||
let eventLoop: EventLoop
|
||||
|
||||
// Create a new promise for some string.
|
||||
let promiseString = eventLoop.makePromise(of: String.self)
|
||||
print(promiseString) // EventLoopPromise<String>
|
||||
print(promiseString.futureResult) // EventLoopFuture<String>
|
||||
|
||||
// Completes the associated future.
|
||||
promiseString.succeed("Hello")
|
||||
|
||||
// Fails the associated future.
|
||||
promiseString.fail(...)
|
||||
```
|
||||
|
||||
!!! info
|
||||
一个 promise 只能 completed 一次。任何后续的 completions 都将被忽略。
|
||||
|
||||
promises 可以从任何线程 completed(`succeed` / `fail`)。这就是为什么 promises 需要初始化一个 event loop。promises 确保完成操作(completion action)返回到其 event loop 中执行。
|
||||
|
||||
## Event Loop
|
||||
|
||||
当应用程序启动时,它通常会为运行它的CPU中的每个核心创建一个 event loop。每个 event loop 只有一个线程。如果您熟悉 Node.js 中的 event loops,那么 Vapor 中的 event loop也是类似的。主要的区别是 Vapor 可以在一个进程(process)中运行多个 event loop,因为 Swift 支持多线程。
|
||||
|
||||
每次客户端连接到服务器时,它将被分配给一个event loops。从这时候开始,服务器和客户端之间的所有通信都将发生在同一个 event loop 上(通过关联,该 event loop 的线程)。
|
||||
|
||||
event loop 负责跟踪每个连接的客户机的状态。如果客户端有一个等待读取的请求,event loop 触发一个读取通知,然后数据被读取。一旦读取了整个请求,等待该请求数据的任何 futures 都将完成。
|
||||
|
||||
在路由闭包中,你可以通过 `Request` 访问当前事件循环。
|
||||
|
||||
```swift
|
||||
req.eventLoop.makePromise(of: ...)
|
||||
```
|
||||
|
||||
!!! warning
|
||||
Vapor 预期路由闭包(route closures)将保持在 `req.eventLoop` 上。如果您跳转线程,您必须确保对`Request`的访问和最终的响应都发生在请求的 event loop 中。
|
||||
|
||||
在路由闭包(route closures)之外,你可以通过 `Application` 获得一个可用的event loops。
|
||||
Outside of route closures, you can get one of the available event loops via `Application`.
|
||||
|
||||
```swift
|
||||
app.eventLoopGroup.next().makePromise(of: ...)
|
||||
```
|
||||
|
||||
### hop
|
||||
|
||||
你可以通过 `hop` 来改变一个 future 的 event loop。
|
||||
|
||||
```swift
|
||||
futureString.hop(to: otherEventLoop)
|
||||
```
|
||||
|
||||
## Blocking
|
||||
|
||||
在 event loop 线程上调用阻塞代码会阻止应用程序及时响应传入请求。阻塞调用的一个例子是' libc.sleep(_:) '。
|
||||
|
||||
```swift
|
||||
app.get("hello") { req in
|
||||
/// Puts the event loop's thread to sleep.
|
||||
sleep(5)
|
||||
|
||||
/// Returns a simple string once the thread re-awakens.
|
||||
return "Hello, world!"
|
||||
}
|
||||
```
|
||||
|
||||
`sleep(_:)` 是一个命令,用于阻塞当前线程的秒数。如果您直接在 event loop 上执行这样的阻塞工作,event loop 将无法在阻塞工作期间响应分配给它的任何其他客户端。换句话说,如果你在一个 event loop 上调用 `sleep(5)`,所有连接到该 event loop 的其他客户端(可能是数百或数千)将延迟至少5秒。
|
||||
|
||||
确保在后台运行任何阻塞工作。当这项工作以非阻塞方式完成时,使用 promises 来通知 event loop。
|
||||
|
||||
```swift
|
||||
app.get("hello") { req -> EventLoopFuture<String> in
|
||||
/// Dispatch some work to happen on a background thread
|
||||
return req.application.threadPool.runIfActive(eventLoop: req.eventLoop) {
|
||||
/// Puts the background thread to sleep
|
||||
/// This will not affect any of the event loops
|
||||
sleep(5)
|
||||
|
||||
/// When the "blocking work" has completed,
|
||||
/// return the result.
|
||||
return "Hello world!"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
并不是所有的阻塞调用都像 `sleep(_:)` 那样明显。如果你怀疑你正在使用的调用可能是阻塞的,研究方法本身或询问别人。下面的部分将更详细地讨论方法如何阻塞。
|
||||
|
||||
### I/O 约束
|
||||
|
||||
I/O 约束阻塞意味着等待较慢的资源,如网络或硬盘,这些资源可能比 CPU 慢几个数量级。在等待这些资源时阻塞 CPU 会导致时间的浪费。
|
||||
|
||||
!!! danger
|
||||
不要在事件循环中直接进行阻塞I/O约束调用.
|
||||
|
||||
所有的 Vapor 包都构建在 SwiftNIO 上,并使用非阻塞 I/O。然而,现在有很多 Swift 包和 C 库使用了阻塞 I/O。如果一个函数正在进行磁盘或网络 IO 并使用同步 API (没有使用 callbacks 或 future),那么它很有可能是阻塞的。
|
||||
|
||||
### CPU 约束
|
||||
|
||||
请求期间的大部分时间都花在等待数据库查询和网络请求等外部资源加载上。因为 Vapor 和 SwiftNIO 是非阻塞的,所以这种停机时间可以用于满足其他传入请求。然而,应用程序中的一些路由可能需要执行大量 CPU 约束的工作。
|
||||
|
||||
当 event loop 处理CPU约束的工作时,它将无法响应其他传入请求。这通常是没问题的,因为CPU是快速的,大多数CPU工作是轻量级的web应用程序。但是,如果需要大量CPU资源的路由阻止了对更快路由的请求的快速响应,这就会成为一个问题。
|
||||
|
||||
识别应用程序中长时间运行的CPU工作,并将其转移到后台线程,可以帮助提高服务的可靠性和响应能力。与I/O约束的工作相比,CPU约束的工作更多的是一个灰色区域,最终由您决定在哪里划定界限。
|
||||
|
||||
大量CPU约束工作的一个常见示例是用户注册和登录期间的Bcrypt哈希。出于安全原因,Bcrypt被故意设置为非常慢和CPU密集型。这可能是一个简单的web应用程序所做的最耗费CPU的工作。将哈希移到后台线程可以允许CPU在计算哈希时交错事件循环工作,从而获得更高的并发性。
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
# Client
|
||||
|
||||
Vapor的 `Client` API 允许您使用 HTTP 调用外部资源,它基于 [async-http-client](https://github.com/swift-server/async-http-client) 构建,并集成了 [Content](./content.md) API。
|
||||
|
||||
|
||||
## 概述
|
||||
|
||||
你可以通过 `Application` 或通过 `Request` 在路由处理回调中访问默认 `Client`。
|
||||
|
||||
```swift
|
||||
app.client // Client
|
||||
|
||||
app.get("test") { req in
|
||||
req.client // Client
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
`Application` 的 `client` 对于在配置期间发起 HTTP 请求非常有用,如果要在路由处理程序中发起 HTTP 请求,请使用 `req.client`。
|
||||
|
||||
|
||||
### 方法
|
||||
|
||||
如果你要发起一个 GET 请求,请将所需的 URL 地址传给 `client` 的 `get` 方法,如下所示:
|
||||
|
||||
```swift
|
||||
let response = try await req.client.get("https://httpbin.org/status/200")
|
||||
```
|
||||
|
||||
HTTP 的常用方法(例如 `get`, `post`, `delete`)都有便捷的调用方式,`client` 的响应会以一个 future 的形式返回,它包含了 HTTP 返回的状态、头部信息和内容。
|
||||
|
||||
|
||||
### Content
|
||||
|
||||
Vapor 的 [Content](./content.md) API 可用于处理客户请求和响应中的数据,如果要在请求体中添加参数或编码,请在 `beforeSend` 闭包中进行。
|
||||
|
||||
```swift
|
||||
let response = try await req.client.post("https://httpbin.org/status/200") { req in
|
||||
// Encode query string to the request URL.
|
||||
try req.query.encode(["q": "test"])
|
||||
|
||||
// Encode JSON to the request body.
|
||||
try req.content.encode(["hello": "world"])
|
||||
|
||||
// Add auth header to the request
|
||||
let auth = BasicAuthorization(username: "something", password: "somethingelse")
|
||||
req.headers.basicAuthorization = auth
|
||||
}
|
||||
// Handle the response.
|
||||
```
|
||||
|
||||
你可以用 `Content` 对 response body 解码采用熟悉的方式:
|
||||
```swift
|
||||
let response = try await req.client.get("https://httpbin.org/json")
|
||||
let json = try response.content.decode(MyJSONResponse.self)
|
||||
```
|
||||
|
||||
如果要解码响应的数据,请在 `flatMapThrowing` 回调中处理。
|
||||
|
||||
```swift
|
||||
req.client.get("https://httpbin.org/json").flatMapThrowing { res in
|
||||
try res.content.decode(MyJSONResponse.self)
|
||||
}.map { json in
|
||||
// 处理返回的JSON信息
|
||||
}
|
||||
```
|
||||
|
||||
## 配置
|
||||
|
||||
你可以通过 `application` 来配置 HTTP `client` 的基础参数。
|
||||
|
||||
```swift
|
||||
// 禁止自动跳转
|
||||
app.http.client.configuration.redirectConfiguration = .disallow
|
||||
```
|
||||
|
||||
请注意,你必须在首次使用默认的 `client` 之前对其进行配置。
|
||||
|
||||
|
|
@ -0,0 +1,274 @@
|
|||
# 内容
|
||||
|
||||
基于 Vapor 的 content API,你可以轻松地对 HTTP 消息中的可编码结构进行编码/解码。默认使用 [JSON](https://tools.ietf.org/html/rfc7159) 编码,并支持 [URL-Encoded Form](https://en.wikipedia.org/wiki/Percent-encoding#The_application/x-www-form-urlencoded_type) 和 [Multipart](https://tools.ietf.org/html/rfc2388)。content API 可以灵活配置,允许你为某些 HTTP 请求类型添加、修改或替换编码策略。
|
||||
|
||||
|
||||
## 总览
|
||||
|
||||
要了解 Vapor 的 content API 是如何工作的,你应该先了解一些关于 HTTP 的基础知识。
|
||||
看看下面这个请求的示例:
|
||||
|
||||
```http
|
||||
POST /greeting HTTP/1.1
|
||||
content-type: application/json
|
||||
content-length: 18
|
||||
|
||||
{"hello": "world"}
|
||||
```
|
||||
|
||||
该请求表明,它包含使用 `content-type` 标头和 `application/json` 媒体类型的JSON编码数据。如前所述,JSON 数据在正文中的标头之后。
|
||||
|
||||
### 内容结构
|
||||
|
||||
解码此HTTP消息的第一步是创建匹配预期结构的可编码类型。
|
||||
|
||||
```swift
|
||||
struct Greeting: Content {
|
||||
var hello: String
|
||||
}
|
||||
```
|
||||
|
||||
使上面的 `Greeting` 数据类型遵循 `Content` 协议,将同时支持 `Codable` 协议规则,符合 Content API 的其他程序代码。
|
||||
|
||||
然后就可以使用 `req.content` 从传入的请求中对数据进行解码,如下所示:
|
||||
|
||||
```swift
|
||||
app.post("greeting") { req in
|
||||
let greeting = try req.content.decode(Greeting.self)
|
||||
print(greeting.hello) // "world"
|
||||
return HTTPStatus.ok
|
||||
}
|
||||
```
|
||||
|
||||
解码方法使用请求的 content 类型来寻找合适的解码器,如果没有找到解码器,或者请求中不包含 content 类型标头,将抛出 `415` 错误。
|
||||
|
||||
这意味着该路由自动接受所有其他支持的内容类型,如url编码形式:
|
||||
|
||||
```http
|
||||
POST /greeting HTTP/1.1
|
||||
content-type: application/x-www-form-urlencoded
|
||||
content-length: 11
|
||||
|
||||
hello=world
|
||||
```
|
||||
|
||||
### 支持的媒体类型
|
||||
|
||||
以下是 content API 默认支持的媒体类型:
|
||||
|
||||
|name|header value|media type|
|
||||
|-|-|-|
|
||||
|JSON|application/json|`.json`|
|
||||
|Multipart|multipart/form-data|`.formData`|
|
||||
|URL-Encoded Form|application/x-www-form-urlencoded|`.urlEncodedForm`|
|
||||
|Plaintext|text/plain|`.plainText`|
|
||||
|HTML|text/html|`.html`|
|
||||
|
||||
不是所有的媒体类型都支持所有的 Codable 协议。例如,JSON 不支持顶层片段,Plaintext 不支持嵌套数据。
|
||||
|
||||
## 查询
|
||||
|
||||
Vapor的 Content API 支持处理 URL 查询字符串中的 URL 编码数据。
|
||||
|
||||
### 解码
|
||||
|
||||
要了解 URL 查询字符串的解码是如何工作的,请看下面的示例请求:
|
||||
|
||||
```http
|
||||
GET /hello?name=Vapor HTTP/1.1
|
||||
content-length: 0
|
||||
```
|
||||
|
||||
就像处理 HTTP 消息正文内容的 API 一样,解析 URL 查询字符串的第一步是创建一个与预期结构相匹配的 `struct` 。
|
||||
|
||||
```swift
|
||||
struct Hello: Content {
|
||||
var name: String?
|
||||
}
|
||||
```
|
||||
|
||||
注意:`name` 是一个可选的 `String`,因为 URL 查询字符串应该是可选的。如果你需要一个参数,请用路由参数代替。
|
||||
|
||||
现在,你已经为该路由的预期查询字符串提供了 `Content` 结构,可以对其进行解码了。
|
||||
|
||||
```swift
|
||||
app.get("hello") { req -> String in
|
||||
let hello = try req.query.decode(Hello.self)
|
||||
return "Hello, \(hello.name ?? "Anonymous")"
|
||||
}
|
||||
```
|
||||
|
||||
给定上面的请求,此路由将触发以下响应:
|
||||
|
||||
```http
|
||||
HTTP/1.1 200 OK
|
||||
content-length: 12
|
||||
|
||||
Hello, Vapor
|
||||
```
|
||||
|
||||
如果省略了查询字符串,如以下请求中所示,将使用"匿名"来代替。
|
||||
|
||||
```http
|
||||
GET /hello HTTP/1.1
|
||||
content-length: 0
|
||||
```
|
||||
|
||||
### 单值
|
||||
|
||||
除了对 `Content` 结构进行解码外,Vapor 还支持使用下标从查询字符串中获取单个参数值。
|
||||
|
||||
```swift
|
||||
let name: String? = req.query["name"]
|
||||
```
|
||||
|
||||
## 钩子
|
||||
|
||||
Vapor 会自动调用 `Content` 类型的 `beforeDecode` 和 `afterDecode`。提供了默认的实现,但你可以使用这些方法来自定义逻辑实现:
|
||||
|
||||
```swift
|
||||
// 在此内容被解码后运行。
|
||||
// 此内容解码后运行。只有 Struct 才需要 'mutating',而 Class 则不需要。
|
||||
mutating func afterDecode() throws {
|
||||
// 名称可能没有传入,但如果传入了,那就不能是空字符串。
|
||||
self.name = self.name?.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
if let name = self.name, name.isEmpty {
|
||||
throw Abort(.badRequest, reason: "Name must not be empty.")
|
||||
}
|
||||
}
|
||||
|
||||
// 在对该内容进行编码之前运行。只有 Struct 才需要 'mutating',而 Class 则不需要。
|
||||
mutating func beforeEncode() throws {
|
||||
// 必须*总是*传递一个名称回来,它不能是一个空字符串。
|
||||
guard
|
||||
let name = self.name?.trimmingCharacters(in: .whitespacesAndNewlines),
|
||||
!name.isEmpty
|
||||
else {
|
||||
throw Abort(.badRequest, reason: "Name must not be empty.")
|
||||
}
|
||||
self.name = name
|
||||
}
|
||||
```
|
||||
|
||||
## 覆盖默认值
|
||||
|
||||
可以配置 Vapor 的 Content API 所使用的默认编码器和解码器。
|
||||
|
||||
### 全局
|
||||
|
||||
`ContentConfiguration.global`允许你修改 Vapor 默认使用的编码器和解码器。这对于改变整个应用程序的数据解析和序列化方式非常有用。
|
||||
|
||||
```swift
|
||||
// 创建一个新的 JSON 编码器,使用 unix-timestamp 日期编码
|
||||
let encoder = JSONEncoder()
|
||||
encoder.dateEncodingStrategy = .secondsSince1970
|
||||
|
||||
// 覆盖用于媒体类型 `.json` 的全局编码器。
|
||||
ContentConfiguration.global.use(encoder: encoder, for: .json)
|
||||
```
|
||||
|
||||
通常是在 `configure.swift` 文件中修改 `ContentConfiguration`。
|
||||
|
||||
### 单次生效
|
||||
|
||||
对编码和解码方法的调用,如 `req.content.decode` ,支持为单次使用配置自定义编码器。
|
||||
|
||||
```swift
|
||||
// 创建一个新的 JSON 解码器,使用 unix-timestamp 日期的时间戳
|
||||
let decoder = JSONDecoder()
|
||||
decoder.dateDecodingStrategy = .secondsSince1970
|
||||
|
||||
// 使用自定义解码器对 `Hello` 结构进行解码
|
||||
let hello = try req.content.decode(Hello.self, using: decoder)
|
||||
```
|
||||
|
||||
## 定制编码器
|
||||
|
||||
应用程序和第三方软件包可以通过创建自定义编码器,对 Vapor 默认不支持的媒体类型进行扩展支持。
|
||||
|
||||
### 内容
|
||||
|
||||
Vapor 为能够处理 HTTP 消息体中内容的编码器指定了两种协议:`ContentDecoder` 和 `ContentEncoder`。
|
||||
|
||||
```swift
|
||||
public protocol ContentEncoder {
|
||||
func encode<E>(_ encodable: E, to body: inout ByteBuffer, headers: inout HTTPHeaders) throws
|
||||
where E: Encodable
|
||||
}
|
||||
|
||||
public protocol ContentDecoder {
|
||||
func decode<D>(_ decodable: D.Type, from body: ByteBuffer, headers: HTTPHeaders) throws -> D
|
||||
where D: Decodable
|
||||
}
|
||||
```
|
||||
|
||||
遵循这些协议,允许你的自定义编码器注册到上面指定的 `ContentConfiguration`。
|
||||
|
||||
### URL 查询
|
||||
|
||||
Vapor 为能够处理 URL 查询字符串中的内容的编码器指定了两个协议: `URLQueryDecoder` 和 `URLQueryEncoder`。
|
||||
|
||||
```swift
|
||||
public protocol URLQueryDecoder {
|
||||
func decode<D>(_ decodable: D.Type, from url: URI) throws -> D
|
||||
where D: Decodable
|
||||
}
|
||||
|
||||
public protocol URLQueryEncoder {
|
||||
func encode<E>(_ encodable: E, to url: inout URI) throws
|
||||
where E: Encodable
|
||||
}
|
||||
```
|
||||
|
||||
遵循这些协议,可以将你的自定义编码器注册到 `ContentConfiguration` 中,以使用 `use(urlEncoder:)` 和 `use(urlDecoder:)` 方法处理 URL 查询字符串。
|
||||
|
||||
### Custom `ResponseEncodable`
|
||||
|
||||
另一种方法涉及到在你的类型上实现 `ResponseEncodable`,请看下面这个 `HTML` 包装类型。
|
||||
|
||||
```swift
|
||||
struct HTML {
|
||||
let value: String
|
||||
}
|
||||
```
|
||||
|
||||
它的 `ResponseEncodable` 实现看起来像这样:
|
||||
|
||||
```swift
|
||||
extension HTML: ResponseEncodable {
|
||||
public func encodeResponse(for request: Request) -> EventLoopFuture<Response> {
|
||||
var headers = HTTPHeaders()
|
||||
headers.add(name: .contentType, value: "text/html")
|
||||
return request.eventLoop.makeSucceededFuture(.init(
|
||||
status: .ok, headers: headers, body: .init(string: value)
|
||||
))
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
如果你正在使用 `async`/`await` 你可以使用 `AsyncResponseEncodable`:
|
||||
|
||||
```swift
|
||||
extension HTML: AsyncResponseEncodable {
|
||||
public func encodeResponse(for request: Request) async throws -> Response {
|
||||
var headers = HTTPHeaders()
|
||||
headers.add(name: .contentType, value: "text/html")
|
||||
return .init(status: .ok, headers: headers, body: .init(string: value))
|
||||
}
|
||||
}
|
||||
```
|
||||
注意,它允许自定义“Content-Type”头,查看更多请查阅 [`HTTPHeaders` reference](https://api.vapor.codes/vapor/master/Vapor/)
|
||||
|
||||
接下来,你可以在你的路由中使用 `HTML` 作为 response:
|
||||
|
||||
```swift
|
||||
app.get { _ in
|
||||
HTML(value: """
|
||||
<html>
|
||||
<body>
|
||||
<h1>Hello, World!</h1>
|
||||
</body>
|
||||
</html>
|
||||
""")
|
||||
}
|
||||
```
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
Controllers are a great way to organize your code. They are collections of methods that accept a request and return a response.
|
||||
|
||||
A good place to put your controllers is in the [Controllers](folder-structure.md#controllers) folder.
|
||||
A good place to put your controllers is in the [Controllers](../gettingstarted/folder-structure.md#controllers) folder.
|
||||
|
||||
## Overview
|
||||
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
# Controllers
|
||||
|
||||
`Controller` 是将应用程序的不同逻辑进行分组的优秀方案,大多数 Controller 都具备接受多种请求的功能,并根据需要进行响应。
|
||||
|
||||
建议将其放在 [Controllers](../gettingstarted/folder-structure.md#controllers) 文件夹下,具体情况可以根据需求划分模块。
|
||||
|
||||
|
||||
## 概述
|
||||
|
||||
让我们看一个示例 Controller:
|
||||
|
||||
```swift
|
||||
import Vapor
|
||||
|
||||
struct TodosController: RouteCollection {
|
||||
func boot(routes: RoutesBuilder) throws {
|
||||
let todos = routes.grouped("todos")
|
||||
todos.get(use: index)
|
||||
todos.post(use: create)
|
||||
|
||||
todos.group(":id") { todo in
|
||||
todo.get(use: show)
|
||||
todo.put(use: update)
|
||||
todo.delete(use: delete)
|
||||
}
|
||||
}
|
||||
|
||||
func index(req: Request) async throws -> String {
|
||||
// ...
|
||||
}
|
||||
|
||||
func create(req: Request) throws -> EventLoopFuture<String> {
|
||||
// ...
|
||||
}
|
||||
|
||||
func show(req: Request) throws -> String {
|
||||
guard let id = req.parameters.get("id") else {
|
||||
throw Abort(.internalServerError)
|
||||
}
|
||||
// ...
|
||||
}
|
||||
|
||||
func update(req: Request) throws -> String {
|
||||
guard let id = req.parameters.get("id") else {
|
||||
throw Abort(.internalServerError)
|
||||
}
|
||||
// ...
|
||||
}
|
||||
|
||||
func delete(req: Request) throws -> String {
|
||||
guard let id = req.parameters.get("id") else {
|
||||
throw Abort(.internalServerError)
|
||||
}
|
||||
// ...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`Controller` 的方法接受 `Request` 参数,并返回 `ResponseEncodable` 对象。该方法可以是异步或者同步(或者返回一个 `EventLoopFuture`)
|
||||
|
||||
!!! 注意
|
||||
[EventLoopFuture](async.md) 期望返回值为 `ResponseEncodable` (i.e, `EventLoopFuture<String>`) 或 `ResponseEncodable`.
|
||||
|
||||
最后,你需要在 `routes.swift` 中注册 Controller:
|
||||
|
||||
```swift
|
||||
try app.register(collection: TodosController())
|
||||
```
|
||||
|
|
@ -0,0 +1,140 @@
|
|||
# 环境
|
||||
|
||||
Vapor 的环境API帮助您动态配置您的应用程序。默认情况下,你的应用程序将使用 `development` 环境。你可以定义其他有用的环境,如 `production` 或 `staging`,并在每种情况下改变你的应用是如何配置的。您还可以从进程的环境或 `.Env` (dotenv)文件读取配置取决于您的需要。
|
||||
|
||||
要访问当前环境,请使用 `app.environment`。你可以在 `configure(_:)` 中通过这个属性来执行不同的配置逻辑。
|
||||
To access the current environment, use `app.environment`. You can switch on this property in `configure(_:)` to execute different configuration logic.
|
||||
|
||||
```swift
|
||||
switch app.environment {
|
||||
case .production:
|
||||
app.databases.use(....)
|
||||
default:
|
||||
app.databases.use(...)
|
||||
}
|
||||
```
|
||||
|
||||
## 改变环境
|
||||
|
||||
默认情况下,你的应用程序将在 `development` 环境中运行。你可以通过在应用程序引导期间传递`--env` (`-e`)标志来改变这一点。
|
||||
|
||||
```swift
|
||||
vapor run serve --env production
|
||||
```
|
||||
|
||||
Vapor 包含下列环境:
|
||||
|
||||
|name|short|description|
|
||||
|-|-|-|
|
||||
|production|prod|Deployed to your users.|
|
||||
|development|dev|Local development.|
|
||||
|testing|test|For unit testing.|
|
||||
|
||||
!!! info
|
||||
`production` 环境将默认为 `notice` 级别的日志记录,除非另有说明。所有其他环境默认为 `info`。
|
||||
|
||||
您可以将全名或短名传递给`--env` (`-e`)标志。
|
||||
|
||||
```swift
|
||||
vapor run serve -e prod
|
||||
```
|
||||
|
||||
## 进程变量
|
||||
|
||||
`Environment` 提供了一个简单的、基于字符串的API来访问进程的环境变量。
|
||||
|
||||
```swift
|
||||
let foo = Environment.get("FOO")
|
||||
print(foo) // String?
|
||||
```
|
||||
|
||||
除了 `get` 之外,`Environment` 还通过 `process` 提供了一个动态成员查找API。
|
||||
|
||||
```swift
|
||||
let foo = Environment.process.FOO
|
||||
print(foo) // String?
|
||||
```
|
||||
|
||||
当在终端运行应用程序时,你可以使用 `export` 设置环境变量。
|
||||
|
||||
```sh
|
||||
export FOO=BAR
|
||||
vapor run serve
|
||||
```
|
||||
|
||||
当在Xcode中运行应用程序时,你可以通过编辑 `Run` scheme来设置环境变量。
|
||||
|
||||
## .env (dotenv)
|
||||
|
||||
Dotenv文件包含一个键值对列表,这些键值对将自动加载到环境中。这些文件使配置环境变量变得很容易,而不需要手动设置它们。
|
||||
|
||||
Vapor 将在当前工作目录中查找dotenv文件。如果你使用Xcode,确保通过编辑 `Run` scheme 设置工作目录。
|
||||
|
||||
Assume the following `.env` file placed in your projects root folder:
|
||||
假设以下 `.env` 文件放在你的项目根文件夹中:
|
||||
|
||||
```sh
|
||||
FOO=BAR
|
||||
```
|
||||
|
||||
当您的应用程序启动时,您将能够像访问其他进程环境变量一样访问该文件的内容。
|
||||
|
||||
```swift
|
||||
let foo = Environment.get("FOO")
|
||||
print(foo) // String?
|
||||
```
|
||||
|
||||
!!! info
|
||||
在 `.env` 文件中指定的变量不会覆盖进程环境中已经存在的变量。
|
||||
|
||||
在`.env`旁边,Vapor 还将尝试为当前环境加载一个dotenv文件。例如,在 `development` 环境中,蒸汽将加载 `.env.development`。特定环境文件中的任何值都将优先于 `.env` 文件内的值。
|
||||
|
||||
一个典型的模式是项目包含一个 `.env` 文件作为带有默认值的模板。在 `.gitignore` 中使用以下模式忽略特定的环境文件
|
||||
|
||||
```gitignore
|
||||
.env.*
|
||||
```
|
||||
|
||||
当项目被 cloned 到新计算机时,已经带有正确的值的`.env`模板可以被复制。
|
||||
|
||||
```sh
|
||||
cp .env .env.development
|
||||
vim .env.development
|
||||
```
|
||||
|
||||
!!! warning
|
||||
带有敏感信息(如密码)的Dotenv文件不应提交给版本控制。
|
||||
|
||||
如果你在加载dotenv文件时遇到了困难,尝试使用 `--log debug` 来启用调试日志以获取更多信息。
|
||||
|
||||
## 自定义环境
|
||||
|
||||
要定义自定义的环境名称,请扩展 `Environment`。
|
||||
|
||||
```swift
|
||||
extension Environment {
|
||||
static var staging: Environment {
|
||||
.custom(name: "staging")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
应用程序的环境通常使用 `main.swift` 中的 `environment .detect()` 来设置。
|
||||
|
||||
```swift
|
||||
import Vapor
|
||||
|
||||
var env = try Environment.detect()
|
||||
try LoggingSystem.bootstrap(from: &env)
|
||||
|
||||
let app = Application(env)
|
||||
defer { app.shutdown() }
|
||||
```
|
||||
|
||||
`detect` 方法使用进程的命令行参数并自动解析 `--env`标志。您可以通过初始化自定义的 `Environment` 结构来覆盖此行为。
|
||||
|
||||
```swift
|
||||
let env = Environment(name: "testing", arguments: ["vapor"])
|
||||
```
|
||||
|
||||
参数数组必须包含至少一个表示可执行名称的参数。可以提供进一步的参数来模拟通过命令行传递参数。这对于测试特别有用。
|
||||
|
|
@ -0,0 +1,113 @@
|
|||
# Logging
|
||||
|
||||
Vapor 的 `Logging` API 是基于 Apple 的 [SwiftLog](https://github.com/apple/swift-log) 而构建。意味着 Vapor 兼容所有基于 `SwiftLog` 实现的[后端框架](https://github.com/apple/swift-log#backends)。
|
||||
|
||||
## Logger
|
||||
|
||||
`Logger` 的实例用于输出日志消息,Vapor 提供了一些便捷的方法使用日志记录器。
|
||||
|
||||
### Request
|
||||
|
||||
每个传入 `Request` 都有一个单独的日志记录器,你可以在该请求中使用任何类型日志。
|
||||
|
||||
```swift
|
||||
app.get("hello") { req -> String in
|
||||
req.logger.info("Hello, logs!")
|
||||
return "Hello, world!"
|
||||
}
|
||||
```
|
||||
|
||||
请求的日志记录器都有一个单独的`UUID`用于标识该请求,便于追踪该日志。
|
||||
|
||||
```
|
||||
[ INFO ] Hello, logs! [request-id: C637065A-8CB0-4502-91DC-9B8615C5D315] (App/routes.swift:10)
|
||||
```
|
||||
|
||||
!!! info
|
||||
日志记录器的元数据仅在调试日志级别或者更低级别显示。
|
||||
|
||||
|
||||
### 应用
|
||||
|
||||
关于应用程序启动和配置过程中的日志消息,可以使用 `Application` 的日志记录器:
|
||||
|
||||
```swift
|
||||
app.logger.info("Setting up migrations...")
|
||||
app.migrations.use(...)
|
||||
```
|
||||
|
||||
### 自定义日志记录器
|
||||
|
||||
在无法访问 `Application` 或者 `Request` 情况下,你可以初始化一个新的 `Logger`。
|
||||
|
||||
```swift
|
||||
let logger = Logger(label: "dev.logger.my")
|
||||
logger.info(...)
|
||||
```
|
||||
|
||||
尽管自定义的日志记录器仍将输出你配置的后端日志记录,但是他们没有附带重要的元数据,比如 `request` 的 `UUID`。所以尽量使用 `application` 或者 `request` 的日志记录器。
|
||||
|
||||
## 日志级别
|
||||
|
||||
`SwiftLog` 支持多种日志级别。
|
||||
<!-- ~~SwiftLog supports several different logging levels.~~ -->
|
||||
|
||||
|名称|说明|
|
||||
|-|-|
|
||||
|trace|用户级基本输出信息|
|
||||
|debug|用户级调试信息|
|
||||
|info|用户级重要信息|
|
||||
|notice|表明会出现非错误的情形,需要关注处理|
|
||||
|warning|表明会出现潜在错误的情形,比 `notice` 的消息严重|
|
||||
|error|指出发生错误事件,但不影响系统的继续运行|
|
||||
|critical|系统级危险,需要立即关注错误信息并处理|
|
||||
|
||||
出现 `critical` 消息时,日志框架可以自由的执行权限更重的操作来捕获系统状态(比如捕获跟踪堆栈)以方便调试。
|
||||
|
||||
默认情况下,Vapor 使用 `info` 级别日志。当运行在 `production` 环境时,将使用 `notice` 提高性能。
|
||||
|
||||
### 修改日志级别
|
||||
|
||||
不管环境模式如何,你都可以通过修改日志级别来增加或减少生成的日志数量。
|
||||
|
||||
第一种方法,在启动应用程序时传递可选参数 `--log` 标志:
|
||||
|
||||
```sh
|
||||
vapor run serve --log debug
|
||||
```
|
||||
|
||||
第二种方法,通过设置 `LOG_LEVEL` 环境变量:
|
||||
|
||||
```sh
|
||||
export LOG_LEVEL=debug
|
||||
vapor run serve
|
||||
```
|
||||
|
||||
这两种方法可以在 Xcode 中编辑 `Run` (scheme)模式进行修改。
|
||||
|
||||
## 配置
|
||||
|
||||
`SwiftLog` 可以通过每次进程启动 `LoggingSystem` 时进行配置。Vapor 项目通常在 `main.swift` 执行操作。
|
||||
|
||||
```swift
|
||||
import Vapor
|
||||
|
||||
var env = try Environment.detect()
|
||||
try LoggingSystem.bootstrap(from: &env)
|
||||
```
|
||||
|
||||
`bootstrap(from:)` 是 Vapor 提供的调用方法,它将基于命令行参数和环境变量来配置默认日志处理操作。默认的日志处理操作支持使用 ANSI 颜色将消息输出到终端。
|
||||
|
||||
### 自定义操作
|
||||
|
||||
你可以覆盖 Vapor 的默认日志处理并注册自己的日志处理操作。
|
||||
|
||||
```swift
|
||||
import Logging
|
||||
|
||||
LoggingSystem.bootstrap { label in
|
||||
StreamLogHandler.standardOutput(label: label)
|
||||
}
|
||||
```
|
||||
|
||||
所有 SwiftLog 支持的后端框架均可与 Vapor 一起工作。但是,使用命令行参数和环境变量更改日志级别只支持 Vapor 的默认日志处理操作。
|
||||
|
|
@ -0,0 +1,430 @@
|
|||
# 路由
|
||||
|
||||
路由是为到来的请求(incoming requetst)找到合适的请求处理程序(request handler)的过程。Vapor 路由的核心是基于 [RoutingKit](https://github.com/vapor/routing-kit) 的高性能 trie-node 路由器。
|
||||
|
||||
## 概述
|
||||
|
||||
要了解路由在 Vapor 中的工作方式,你首先应该了解有关 HTTP 请求的一些基础知识。
|
||||
看一下以下示例请求:
|
||||
|
||||
```http
|
||||
GET /hello/vapor HTTP/1.1
|
||||
host: vapor.codes
|
||||
content-length: 0
|
||||
```
|
||||
|
||||
这是对 URL `/hello/vapor` 的一个简单的 HTTP 请求。 如果你将其指向以下 URL,则浏览器将发出这样的 HTTP 请求:
|
||||
|
||||
```
|
||||
http://vapor.codes/hello/vapor
|
||||
```
|
||||
|
||||
### HTTP 方法
|
||||
|
||||
请求的第一部分是 HTTP 方法。其中 GET 是最常见的 HTTP 方法,以下这些是经常会使用几种方法,这些 HTTP 方法通常与 [CRUD](https://en.wikipedia.org/wiki/Create,_read,_update_and_delete) 语义相关联。
|
||||
|
||||
|
||||
|Method|CURD|
|
||||
|:--|:--|
|
||||
|`GET`|Read|
|
||||
|`POST`|Create|
|
||||
|`PUT`|Replace|
|
||||
|`PATCH`|Update|
|
||||
|`DELETE`|Delete|
|
||||
|
||||
|
||||
### 请求路径
|
||||
|
||||
在 HTTP 方法之后是请求的 URI。它由以 `/` 开头的路径和在 `?` 之后的可选查询字符串组成。HTTP 方法和路径是 Vapor 用于路由请求的方法。
|
||||
|
||||
URI 之后是 HTTP 版本,后跟零个或多个标头,最后是正文。由于这是一个 `GET` 请求,因此没有主体(body)。
|
||||
|
||||
|
||||
### 路由方法
|
||||
|
||||
让我们看一下如何在 Vapor 中处理此请求。
|
||||
|
||||
```swift
|
||||
app.get("hello", "vapor") { req in
|
||||
return "Hello, vapor!"
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
所有常见的 HTTP 方法都可以作为 `Application` 的方法使用。它们接受一个或多个字符串参数,这些字符串参数表示请求路径,以 `/` 分隔。
|
||||
|
||||
请注意,你也可以在方法之后使用 `on` 编写此代码。
|
||||
|
||||
|
||||
```swift
|
||||
app.on(.GET, "hello", "vapor") { ... }
|
||||
```
|
||||
|
||||
注册此路由后,上面的示例 HTTP 请求将导致以下 HTTP 响应。
|
||||
|
||||
```http
|
||||
HTTP/1.1 200 OK
|
||||
content-length: 13
|
||||
content-type: text/plain; charset=utf-8
|
||||
|
||||
Hello, vapor!
|
||||
```
|
||||
|
||||
### 路由参数
|
||||
|
||||
现在,我们已经成功地基于 HTTP 方法和路径路由了请求,让我们尝试使路径动态化。注意,名称 “vapor” 在路径和响应中都是硬编码的。让我们对它进行动态化,以便你可以访问 `/hello/<any name>` 并获得响应。
|
||||
|
||||
|
||||
```swift
|
||||
app.get("hello", ":name") { req -> String in
|
||||
let name = req.parameters.get("name")!
|
||||
return "Hello, \(name)!"
|
||||
}
|
||||
```
|
||||
|
||||
通过使用前缀为 `:` 的路径组件,我们向路由器指示这是动态组件。现在,此处提供的任何字符串都将与此路由匹配。 然后,我们可以使用 `req.parameters` 访问字符串的值。
|
||||
|
||||
如果再次运行示例请求,你仍然会收到一条响应,向 vapor 打招呼。 但是,你现在可以在 `/hello/` 之后添加任何名称,并在响应中看到它。 让我们尝试 `/hello/swift`。
|
||||
|
||||
|
||||
```http
|
||||
GET /hello/swift HTTP/1.1
|
||||
content-length: 0
|
||||
```
|
||||
```http
|
||||
HTTP/1.1 200 OK
|
||||
content-length: 13
|
||||
content-type: text/plain; charset=utf-8
|
||||
|
||||
Hello, swift!
|
||||
```
|
||||
|
||||
现在你已经了解了基础知识,请查看每个部分以了解有关参数,分组等的更多信息。
|
||||
|
||||
## 路径
|
||||
|
||||
路由为给定的 HTTP 方法和 URI 路径指定请求处理程序(request handler)。它还可以存储其他元数据。
|
||||
|
||||
### 方法
|
||||
|
||||
可以使用多种 HTTP 方法帮助程序将路由直接注册到你的 `Application` 。
|
||||
|
||||
```swift
|
||||
// responds to GET /foo/bar/baz
|
||||
app.get("foo", "bar", "baz") { req in
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
路由处理程序支持返回 `ResponseEncodable` 的任何内容。这包括 `Content`,一个 `async` 闭包,以及未来值为 `ResponseEncodable` 的 `EventLoopFuture`。
|
||||
|
||||
你可以在 `in` 之前使用 `-> T` 来指定路线的返回类型。这在编译器无法确定返回类型的情况下很有用。
|
||||
|
||||
```swift
|
||||
app.get("foo") { req -> String in
|
||||
return "bar"
|
||||
}
|
||||
```
|
||||
|
||||
这些是受支持的路由器方法:
|
||||
|
||||
- `get`
|
||||
- `post`
|
||||
- `patch`
|
||||
- `put`
|
||||
- `delete`
|
||||
|
||||
除了 HTTP 方法协助程序外,还有一个 `on` 函数可以接受 HTTP 方法作为输入参数。
|
||||
|
||||
```swift
|
||||
// responds to OPTIONS /foo/bar/baz
|
||||
app.on(.OPTIONS, "foo", "bar", "baz") { req in
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
### 路径组件
|
||||
|
||||
每种路由注册方法都接受 `PathComponent` 的可变列表。此类型可以用字符串文字表示,并且有四种情况:
|
||||
|
||||
|
||||
- 常量 (`foo`)
|
||||
- 参数路径 (`:foo`)
|
||||
- 任何路径 (`*`)
|
||||
- 通配路径 (`**`)
|
||||
|
||||
#### 常量
|
||||
|
||||
这是静态路由组件。仅允许在此位置具有完全匹配的字符串的请求。
|
||||
|
||||
```swift
|
||||
// responds to GET /foo/bar/baz
|
||||
app.get("foo", "bar", "baz") { req in
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
#### 参数路径
|
||||
|
||||
这是一个动态路由组件。此位置的任何字符串都将被允许。参数路径组件以 `:` 前缀指定。`:` 后面的字符串将用作参数名称。你可以使用该名称稍后从请求中获取参数值。
|
||||
|
||||
```swift
|
||||
// responds to GET /foo/bar/baz
|
||||
// responds to GET /foo/qux/baz
|
||||
// ...
|
||||
app.get("foo", ":bar", "baz") { req in
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
#### 任何路径
|
||||
|
||||
除了丢弃值之外,这与参数路径非常相似。此路径组件仅需指定为 `*` 。
|
||||
|
||||
```swift
|
||||
// responds to GET /foo/bar/baz
|
||||
// responds to GET /foo/qux/baz
|
||||
// ...
|
||||
app.get("foo", "*", "baz") { req in
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
#### 通配路径
|
||||
|
||||
这是与一个或多个组件匹配的动态路由组件,仅使用 `**` 指定。请求中将允许匹配此位置或更高位置的任何字符串。
|
||||
|
||||
```swift
|
||||
// responds to GET /foo/bar
|
||||
// responds to GET /foo/bar/baz
|
||||
// ...
|
||||
app.get("foo", "**") { req in
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
### 参数
|
||||
|
||||
使用参数路径组件(以 `:` 前缀)时,该位置的 URI 值将存储在 `req.parameters` 中。 你可以使用路径组件中的名称来访问。
|
||||
|
||||
|
||||
```swift
|
||||
// responds to GET /hello/foo
|
||||
// responds to GET /hello/bar
|
||||
// ...
|
||||
app.get("hello", ":name") { req -> String in
|
||||
let name = req.parameters.get("name")!
|
||||
return "Hello, \(name)!"
|
||||
}
|
||||
```
|
||||
|
||||
!!! 提示
|
||||
我们可以确定 `req.parameters.get` 在这里绝不会返回 `nil` ,因为我们的路径包含 `:name`。 但是,如果要访问中间件中的路由参数或由多个路由触发的代码中的路由参数,则需要处理 `nil` 的可能性。
|
||||
|
||||
`req.parameters.get` 还支持将参数自动转换为 `LosslessStringConvertible` 类型。
|
||||
|
||||
|
||||
```swift
|
||||
// responds to GET /number/42
|
||||
// responds to GET /number/1337
|
||||
// ...
|
||||
app.get("number", ":x") { req -> String in
|
||||
guard let int = req.parameters.get("x", as: Int.self) else {
|
||||
throw Abort(.badRequest)
|
||||
}
|
||||
return "\(int) is a great number"
|
||||
}
|
||||
```
|
||||
|
||||
### Body 数据流
|
||||
|
||||
当使用 `on` 方法注册一个路由时,你可以设置 request body 应该如何被处理。默认情况下,request bodies 被收集到内存中在调用你的 handler 之前。这很有用,因为它允许同步解码请求内容,即使您的应用程序异步读取传入请求。
|
||||
|
||||
默认情况下,Vapor 将会限制 streaming body collection 的大小为16KB,你可以使用 `app.routes` 来配置它。
|
||||
|
||||
```swift
|
||||
// Increases the streaming body collection limit to 500kb
|
||||
app.routes.defaultMaxBodySize = "500kb"
|
||||
```
|
||||
如果收集到的 streaming body 大小超过了配置的限制,`413 Payload Too Large` 错误将会被抛出。
|
||||
|
||||
使用 `body` 参数来为一个单独的路由设置 request body 收集策略。
|
||||
|
||||
```swift
|
||||
// Collects streaming bodies (up to 1mb in size) before calling this route.
|
||||
app.on(.POST, "listings", body: .collect(maxSize: "1mb")) { req in
|
||||
// Handle request.
|
||||
}
|
||||
```
|
||||
如果一个 `maxSize` 被传到 `collect`,它将会覆盖应用的默认配置对这个路由。如果要使用应用的默认配置,请忽略 `maxSize` 参数.
|
||||
|
||||
对于像文件上传这样的大请求,在缓冲区中收集 request body 可能会占用系统内存。为了防止 request body 收集,使用 `stream` 策略。
|
||||
|
||||
```swift
|
||||
// Request body will not be collected into a buffer.
|
||||
app.on(.POST, "upload", body: .stream) { req in
|
||||
...
|
||||
}
|
||||
```
|
||||
当请 request body 被流处理时,`req.body.data` 会是 `nil`, 你必须使用 `req.body.drain` 来处理每个被发送到你的路由数据块。
|
||||
|
||||
### 大小写敏感
|
||||
|
||||
路由的默认行为是区分大小写和保留大小写的。
|
||||
若想不区分大小写方式处理`常量`路径组件;启用此行为,请在应用程序启动之前进行配置:
|
||||
```swift
|
||||
app.routes.caseInsensitive = true
|
||||
```
|
||||
不需要对原始请求未做任何更改,路由处理程序将接收未经修改的请求路由。
|
||||
|
||||
|
||||
### 查看路由
|
||||
|
||||
你可以通过 making `Routes` 服务或使用 `app.routes` 来访问应用程序的路由。
|
||||
|
||||
```swift
|
||||
print(app.routes.all) // [Route]
|
||||
```
|
||||
|
||||
Vapor 还附带了一个 `routes` 命令,该命令以 ASCII 格式的表格打印所有可用的路由。
|
||||
|
||||
```sh
|
||||
$ swift run Run routes
|
||||
+--------+----------------+
|
||||
| GET | / |
|
||||
+--------+----------------+
|
||||
| GET | /hello |
|
||||
+--------+----------------+
|
||||
| GET | /todos |
|
||||
+--------+----------------+
|
||||
| POST | /todos |
|
||||
+--------+----------------+
|
||||
| DELETE | /todos/:todoID |
|
||||
+--------+----------------+
|
||||
```
|
||||
|
||||
### Metadata
|
||||
|
||||
所有路线注册方法都会返回创建的 `Route`。 这使你可以将元数据添加到路由的 `userInfo` 字典中。有一些默认方法可用,例如添加描述。
|
||||
|
||||
|
||||
```swift
|
||||
app.get("hello", ":name") { req in
|
||||
...
|
||||
}.description("says hello")
|
||||
```
|
||||
|
||||
## 路由组
|
||||
|
||||
通过路由分组,你可以创建带有路径前缀或特定中间件的一组路由。分组支持基于构建器和闭包的语法。
|
||||
|
||||
所有分组方法都返回一个 `RouteBuilder` ,这意味着你可以将组与其他路由构建方法无限地混合、匹配和嵌套。
|
||||
|
||||
### 路径前缀
|
||||
|
||||
路径前缀路由组允许你在一个路由组之前添加一个或多个路径组件。
|
||||
|
||||
```swift
|
||||
let users = app.grouped("users")
|
||||
// GET /users
|
||||
users.get { req in
|
||||
...
|
||||
}
|
||||
// POST /users
|
||||
users.post { req in
|
||||
...
|
||||
}
|
||||
// GET /users/:id
|
||||
users.get(":id") { req in
|
||||
let id = req.parameters.get("id")!
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
你可以传递给诸如 `get` 或 `post` 这样的方法的任何路径成分都可以传递给 `grouped`。 还有另一种基于闭包的语法。
|
||||
|
||||
```swift
|
||||
app.group("users") { users in
|
||||
// GET /users
|
||||
users.get { req in
|
||||
...
|
||||
}
|
||||
// POST /users
|
||||
users.post { req in
|
||||
...
|
||||
}
|
||||
// GET /users/:id
|
||||
users.get(":id") { req in
|
||||
let id = req.parameters.get("id")!
|
||||
...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
嵌套路径前缀路由组使你可以简洁地定义 CRUD API。
|
||||
|
||||
```swift
|
||||
app.group("users") { users in
|
||||
// GET /users
|
||||
users.get { ... }
|
||||
// POST /users
|
||||
users.post { ... }
|
||||
|
||||
users.group(":id") { user in
|
||||
// GET /users/:id
|
||||
user.get { ... }
|
||||
// PATCH /users/:id
|
||||
user.patch { ... }
|
||||
// PUT /users/:id
|
||||
user.put { ... }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 中间件
|
||||
|
||||
除了为路径组件添加前缀之外,你还可以将中间件添加到路由组。
|
||||
|
||||
```swift
|
||||
app.get("fast-thing") { req in
|
||||
...
|
||||
}
|
||||
app.group(RateLimitMiddleware(requestsPerMinute: 5)) { rateLimited in
|
||||
rateLimited.get("slow-thing") { req in
|
||||
...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
这对于使用不同的身份验证中间件保护路由的子集特别有用。
|
||||
|
||||
```swift
|
||||
app.post("login") { ... }
|
||||
let auth = app.grouped(AuthMiddleware())
|
||||
auth.get("dashboard") { ... }
|
||||
auth.get("logout") { ... }
|
||||
```
|
||||
|
||||
## 重定向
|
||||
|
||||
重定向在很多场景中很有用,像转发旧页面到新页面为了 SEO,把未认证的用户转发到登录页面或保持与API的新版本的向后兼容性。
|
||||
|
||||
要转发一个请求,请用:
|
||||
|
||||
```swift
|
||||
req.redirect(to: "/some/new/path")
|
||||
```
|
||||
|
||||
你可以设置重定向的类型,比如说永久的重定向一个页面(来使你的 SEO 正确的更新),请使用:
|
||||
|
||||
```swift
|
||||
req.redirect(to: "/some/new/path", type: .permanent)
|
||||
```
|
||||
|
||||
不同的 `RedirectType` 有:
|
||||
|
||||
* `.permanent` - 返回一个 **301 Permanent** 重定向。
|
||||
* `.normal` - 返回一个 **303 see other** 重定向。这是 Vapor 的默认行为,来告诉客户端去使用一个 **GET** 请求来重定向。
|
||||
* `.temporary` - 返回一个 **307 Temporary** 重定向. 这告诉客户端保留请求中使用的HTTP方法。
|
||||
|
||||
> 要选择正确的重定向状态码,请参考 [the full list](https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#3xx_redirection)
|
||||
|
|
@ -0,0 +1,206 @@
|
|||
# Validation API
|
||||
|
||||
Vapor 的 **Validation API** 可帮助你在使用 [Content](content.md) API 解码数据之前,对传入的请求进行验证。
|
||||
|
||||
## 介绍
|
||||
|
||||
Vapor 对 Swift 的类型安全的`可编码`协议进行了深度集成,这意味着与动态类型的语言相比,你无需担心数据验证。但是,出于某些原因,你可能想选择使用 **Validation API** 进行显式验证。
|
||||
|
||||
|
||||
### 语义可读错误
|
||||
|
||||
如果取得的数据无效,使用 [Content](content.md) API 对其解码将产生错误。但是,这些错误消息有时可能缺乏可读性。例如,采用以下字符串支持的枚举:
|
||||
|
||||
```swift
|
||||
enum Color: String, Codable {
|
||||
case red, blue, green
|
||||
}
|
||||
```
|
||||
|
||||
如果用户尝试将字符串“purple”传递给“Color”类型的属性,则将收到类似于以下内容的错误:
|
||||
|
||||
```
|
||||
Cannot initialize Color from invalid String value purple for key favoriteColor
|
||||
```
|
||||
|
||||
尽管此错误在技术上是正确的,并且可以成功地保护端点免受无效值的影响,但它可以更好地通知用户该错误以及可用的选项。通过使用 **Validation API**,你可以生成类似以下的错误:
|
||||
|
||||
```
|
||||
favoriteColor is not red, blue, or green
|
||||
```
|
||||
|
||||
此外,一旦遇到第一个错误,`Codable` 将停止尝试解码。这意味着即使请求中有许多无效属性,用户也只会看到第一个错误。 **Validation API** 将在单个请求中抛出所有的验证失败信息。
|
||||
|
||||
### 特殊验证
|
||||
|
||||
`Codable` 可以很好地处理类型验证,但是有时候你还想要更多的验证方式。例如,验证字符串的内容或验证整数的大小。**Validation API** 具有此类验证器,可帮助验证电子邮件、字符集、整数范围等数据。
|
||||
|
||||
## 验证
|
||||
|
||||
为了验证请求,你需要生成一个 `Validations` 集合。最常见的做法是使现有类型继承 **Validatable**。
|
||||
|
||||
让我们看一下如何向这个简单的 `POST/users` 请求添加验证。本指南假定你已经熟悉 [Content](content.md) API。
|
||||
|
||||
|
||||
```swift
|
||||
enum Color: String, Codable {
|
||||
case red, blue, green
|
||||
}
|
||||
|
||||
struct CreateUser: Content {
|
||||
var name: String
|
||||
var username: String
|
||||
var age: Int
|
||||
var email: String
|
||||
var favoriteColor: Color?
|
||||
}
|
||||
|
||||
app.post("users") { req -> CreateUser in
|
||||
let user = try req.content.decode(CreateUser.self)
|
||||
// Do something with user.
|
||||
return user
|
||||
}
|
||||
```
|
||||
|
||||
### 添加验证
|
||||
|
||||
第一步是在你要解码的类型(在本例中为 CreateUser)继承 **Validatable** 协议并实现 `validations` 静态方法,可在 `extension` 中完成。
|
||||
|
||||
```swift
|
||||
extension CreateUser: Validatable {
|
||||
static func validations(_ validations: inout Validations) {
|
||||
// Validations go here.
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
验证`CreateUser`后,将调用静态方法 `validations(_ :)`。你要执行的所有验证都应添加到 **Validations** 集合中。让我们添加一个简单的验证,以验证用户的电子邮件是否有效。
|
||||
|
||||
```swift
|
||||
validations.add("email", as: String.self, is: .email)
|
||||
```
|
||||
|
||||
第一个参数是参数值的预期键,在本例中为`email`。这应与正在验证的类型上的属性名称匹配。第二个参数`as`是预期的类型,在这种情况下为`String`。该类型通常与属性的类型匹配。最后,可以在第三个参数`is`之后添加一个或多个验证器。在这种情况下,我们添加一个验证器,以检查该值是否为电子邮件地址。
|
||||
|
||||
|
||||
### 验证请求的 `Content`
|
||||
|
||||
当你的数据类型继承了 **Validatable**,就可以使用 `validate(content:)` 静态方法来验证请求的 `content`。在路由处理程序中 `req.content.decode(CreateUser.self)` 之前添加以下行:
|
||||
|
||||
```swift
|
||||
try CreateUser.validate(content: req)
|
||||
```
|
||||
|
||||
现在,尝试发送以下包含无效电子邮件的请求:
|
||||
|
||||
```http
|
||||
POST /users HTTP/1.1
|
||||
Content-Length: 67
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"age": 4,
|
||||
"email": "foo",
|
||||
"favoriteColor": "green",
|
||||
"name": "Foo",
|
||||
"username": "foo"
|
||||
}
|
||||
```
|
||||
|
||||
你应该能看到返回以下错误:
|
||||
|
||||
```
|
||||
email is not a valid email address
|
||||
```
|
||||
|
||||
### 验证请求的 `Query`
|
||||
|
||||
当你的数据类型继承了 **Validatable**,就可以使用 `validate(query:)` 静态方法来验证请求的 `query`。在路由处理程序中添加以下行:
|
||||
|
||||
```swift
|
||||
try CreateUser.validate(query: req)
|
||||
req.query.decode(CreateUser.self)
|
||||
```
|
||||
|
||||
现在,尝试发送一下包含错误的 email 在 query 的请求。
|
||||
|
||||
```http
|
||||
GET /users?age=4&email=foo&favoriteColor=green&name=Foo&username=foo HTTP/1.1
|
||||
|
||||
```
|
||||
|
||||
你将会看到下面的错误:
|
||||
|
||||
```
|
||||
email is not a valid email address
|
||||
```
|
||||
### 整数验证
|
||||
|
||||
现在让我们尝试添加一个针对整数年龄的验证:
|
||||
|
||||
```swift
|
||||
validations.add("age", as: Int.self, is: .range(13...))
|
||||
```
|
||||
|
||||
年龄验证要求年龄大于或等于`13`。如果你尝试发送一个和上面相同的请求,现在应该会看到一个新错误:
|
||||
|
||||
```
|
||||
age is less than minimum of 13, email is not a valid email address
|
||||
```
|
||||
|
||||
### 字符串验证
|
||||
|
||||
接下来,让我们添加对“名称”和“用户名”的验证。
|
||||
|
||||
```swift
|
||||
validations.add("name", as: String.self, is: !.empty)
|
||||
validations.add("username", as: String.self, is: .count(3...) && .alphanumeric)
|
||||
```
|
||||
|
||||
|
||||
名称验证使用 `!` 运算符将 `.empty` 验证反转。这要求该字符串不为空。
|
||||
用户名验证使用`&&`组合了两个验证器。这将要求该字符串的长度至少为3个字符,并且使用 && 来包含字母数字字符。
|
||||
|
||||
|
||||
### 枚举验证
|
||||
|
||||
最后,让我们看一下更高级的验证,以检查提供的`favoriteColor`是否有效:
|
||||
|
||||
```swift
|
||||
validations.add("favoriteColor", as: String.self,is: .in("red", "blue","green"),required: false)
|
||||
|
||||
```
|
||||
|
||||
由于无法从无效值中解码“颜色”,因此此验证将“字符串”用作基本类型。它使用 .in 验证器来验证该值是有效的选项:红色、蓝色或绿色。由于该值是可选的,因此将`required`设置为 false 表示如果请求数据中缺少此字段,则验证不会失败。
|
||||
|
||||
请注意,如果缺少此字段,则收藏夹颜色验证将通过,但如果提供 `null`,则不会通过。 如果要支持`null`,请将验证类型更改为`String?`,并使用 `.nil ||`。
|
||||
|
||||
```swift
|
||||
validations.add("favoriteColor", as: String?.self,is: .nil || .in("red", "blue", "green"),required: false)
|
||||
```
|
||||
|
||||
|
||||
## 验证器
|
||||
|
||||
以下是当前支持的验证器的列表,并简要说明了它们的作用:
|
||||
|
||||
|验证方式|描述|
|
||||
|:--|:--|
|
||||
|`.ascii`|仅包含ASCII字符|
|
||||
|`.alphanumeric`|仅包含字母数字字符|
|
||||
|`.characterSet(_:)`|仅包含提供的 `CharacterSet` 中的字符|
|
||||
|`.count(_:)`|在提供范围内的集合计数|
|
||||
|`.email`|包含有效的电子邮件|
|
||||
|`.empty`|集合为空|
|
||||
|`.in(_:)`|值在提供的“集合”中|
|
||||
|`.nil`|值为`null`|
|
||||
|`.range(_:)`|值在提供的范围内|
|
||||
|`.url`|包含有效的URL|
|
||||
|
||||
验证器也可以使用运算符组合起来以构建复杂的验证:
|
||||
|
||||
|操作符|位置|描述|
|
||||
|:--|:--|:--|
|
||||
|`!`|前面|反转验证器,要求相反|
|
||||
|`&&`|中间|组合两个验证器,需要同时满足|
|
||||
|`||`|中间|组合两个验证器,至少满足一个|
|
||||
|
|
@ -0,0 +1,190 @@
|
|||
# 部署到 DigitalOcean
|
||||
|
||||
本指南将引导你将一个简单的 Hello, world Vapor 应用程序部署到 [Droplet](https://www.digitalocean.com/products/droplets/)。要遵循本指南,你需要有一个付费的 [DigitalOcean](https://www.digitalocean.com) 帐户。
|
||||
|
||||
## 创建服务器
|
||||
|
||||
让我们从在 Linux 服务器上安装 Swift 开始。 使用创建菜单创建一个新的 Droplet。
|
||||
|
||||

|
||||
|
||||
在发行版下,选择 Ubuntu 18.04 LTS。以下指南将以此版本为例。
|
||||
|
||||

|
||||
|
||||
!!! 注意
|
||||
你也可以选择 Swift 支持的其它 Linux 发行版。在撰写本文时, Swift 5.2.4 支持 Ubuntu 16.04、18.04、20.04、CentOS 8, 和 Amazon Linux 2。你可以在 [Swift Releases](https://swift.org/download/#releases) 页面上查看官方支持哪些操作系统。
|
||||
|
||||
选择完发行版后,选择你喜欢的套餐和数据中心所在区域。然后设置一个 SSH 密钥以在创建服务器后访问它。最后, 点击创建 Droplet 并等待新服务器启动。
|
||||
|
||||
新服务器准备完毕后,鼠标悬停在 Droplet 的 IP 地址上,然后单击复制。
|
||||
|
||||

|
||||
|
||||
## 初始化设置
|
||||
|
||||
打开你的终端,使用 SSH 通过 root 身份登录到服务器。
|
||||
|
||||
```sh
|
||||
ssh root@your_server_ip
|
||||
```
|
||||
|
||||
在 [Ubuntu 18.04](https://www.digitalocean.com/community/tutorials/initial-server-setup-with-ubuntu-18-04) 上初始化服务器设置,DigitalOcean 提供了深入指南。 本指南将快速介绍一些基础知识。
|
||||
|
||||
### 配置防火墙
|
||||
|
||||
允许 OpenSSH 通过防火墙并且启用它。
|
||||
|
||||
```sh
|
||||
ufw allow OpenSSH
|
||||
ufw enable
|
||||
```
|
||||
|
||||
### 添加用户
|
||||
|
||||
除了 `root` 用户在创建一个新用户。本指南创建了一个 `vapor` 用户。
|
||||
|
||||
```sh
|
||||
adduser vapor
|
||||
```
|
||||
|
||||
允许新创建的用户使用 `sudo`。
|
||||
|
||||
```sh
|
||||
usermod -aG sudo vapor
|
||||
```
|
||||
|
||||
复制 root 用户的 SSH 密钥到新创建的用户。允许新用户通过 SSH 登录。
|
||||
|
||||
```sh
|
||||
rsync --archive --chown=vapor:vapor ~/.ssh /home/vapor
|
||||
```
|
||||
|
||||
最后,退出当前 SSH 会话,用新创建的用户进行登录。
|
||||
|
||||
```sh
|
||||
exit
|
||||
ssh vapor@your_server_ip
|
||||
```
|
||||
|
||||
## 安装 Swift
|
||||
|
||||
现在你已经创建了一个新的 Ubuntu 服务器并且通过非 root 身份登录到服务器,你可以安装 Swift。
|
||||
|
||||
### Swift 依赖项
|
||||
|
||||
安装 Swift 所需要的依赖项。
|
||||
|
||||
```sh
|
||||
sudo apt-get update
|
||||
sudo apt-get install clang libicu-dev libatomic1 build-essential pkg-config
|
||||
```
|
||||
|
||||
### 下载 Toolchain
|
||||
|
||||
本指南将安装 Swift 5.2.4。访问 [Swift Releases](https://swift.org/download/#releases) 页面获取最新版本的链接。复制 Ubuntu 18.04 的下载链接。
|
||||
|
||||

|
||||
|
||||
下载并解压 Swift toolchain。
|
||||
|
||||
```sh
|
||||
wget https://swift.org/builds/swift-5.2.4-release/ubuntu1804/swift-5.2.4-RELEASE/swift-5.2.4-RELEASE-ubuntu18.04.tar.gz
|
||||
tar xzf swift-5.2.4-RELEASE-ubuntu18.04.tar.gz
|
||||
```
|
||||
|
||||
!!! 注意
|
||||
Swift 的[使用下载指南](https://swift.org/download/#using-downloads)包含有关如何使用 PGP 签名验证下载的信息。
|
||||
|
||||
### 安装 Toolchain
|
||||
|
||||
将 Swift 移到易于访问的地方。本指南将 `/swift` 与子文件夹中的每个编译器版本一起使用。
|
||||
|
||||
```sh
|
||||
sudo mkdir /swift
|
||||
sudo mv swift-5.2.4-RELEASE-ubuntu18.04 /swift/5.2.4
|
||||
```
|
||||
|
||||
将 Swift 添加到 `/usr/bin` 以便 `vapor` 和 `root` 用户可以执行。
|
||||
|
||||
```sh
|
||||
sudo ln -s /swift/5.2.4/usr/bin/swift /usr/bin/swift
|
||||
```
|
||||
|
||||
验证 Swift 是否正确安装。
|
||||
|
||||
```sh
|
||||
swift --version
|
||||
```
|
||||
|
||||
## 设置项目
|
||||
|
||||
现在已经安装了 Swift,让我们克隆并编译项目。本示例,我们使用 Vapor 的 [API 模板](https://github.com/vapor/api-template/)。
|
||||
|
||||
首先安装 Vapor 的系统依赖项。
|
||||
|
||||
```sh
|
||||
sudo apt-get install openssl libssl-dev zlib1g-dev libsqlite3-dev
|
||||
```
|
||||
|
||||
允许 HTTP 通过防火墙。
|
||||
|
||||
```sh
|
||||
sudo ufw allow http
|
||||
```
|
||||
|
||||
### 克隆和构建
|
||||
|
||||
现在克隆项目并构建它。
|
||||
|
||||
```sh
|
||||
git clone https://github.com/vapor/api-template.git
|
||||
cd api-template
|
||||
swift build --enable-test-discovery
|
||||
```
|
||||
|
||||
!!! 建议
|
||||
如果生产环境进行构建, 请使用 `swift build -c release --enable-test-discovery`
|
||||
|
||||
### 运行
|
||||
|
||||
项目编译完成后,在服务器的 IP 端口80上运行它。本示例的 IP 地址为 `157.245.244.228`。
|
||||
|
||||
```sh
|
||||
sudo .build/debug/Run serve -b 157.245.244.228:80
|
||||
```
|
||||
|
||||
如果你使用 `swift build -c release --enable-test-discovery`进行构建, 然后你需要运行:
|
||||
```sh
|
||||
sudo .build/release/Run serve -b 157.245.244.228:80
|
||||
```
|
||||
|
||||
通过浏览器或者本地终端访问服务器的 IP, 你应该会看到 “It works!”。
|
||||
|
||||
```
|
||||
$ curl http://157.245.244.228
|
||||
It works!
|
||||
```
|
||||
|
||||
回到服务器上,你应该会看到测试请求的日志。
|
||||
|
||||
```
|
||||
[ NOTICE ] Server starting on http://157.245.244.228:80
|
||||
[ INFO ] GET /
|
||||
```
|
||||
|
||||
使用 `CTRL+C` 退出服务器。可能需要一秒钟才能关闭。
|
||||
|
||||
恭喜你的 Vapor 应用程序运行在 DigitalOcean Droplet 上了!
|
||||
|
||||
## 下一步
|
||||
|
||||
本指南的其余部分指向的资源用于改进你的部署。
|
||||
|
||||
### Supervisor
|
||||
|
||||
Supervisor 是一个进程控制系统,可以运行和监控你的 Vapor 可执行文件。通过设置 supervisor, 服务器启动时应用程序自动启动,并在崩溃是重新启动。了解有关 [Supervisor](../deploy/supervisor.md) 的更多信息。
|
||||
|
||||
### Nginx
|
||||
|
||||
Nginx 是一个速度极快、经过实战考验并且易于配置的 HTTP 服务器和代理。虽然 Vapor 支持直接的 HTTP 请求,但 Nginx 背后的代理可以提供更高的性能、安全性和易用性。了解有关 [Nginx](../deploy/nginx.md) 的更多信息。
|
||||
|
|
@ -154,7 +154,7 @@ You can bring your services up with
|
|||
```shell
|
||||
LOG_LEVEL=trace docker-compose up app
|
||||
```
|
||||
to get `trace` level logging (the most granular). You can use this environment variable to set the logging to [any available level](../logging.md#levels).
|
||||
to get `trace` level logging (the most granular). You can use this environment variable to set the logging to [any available level](../basics/logging.md#levels).
|
||||
|
||||
#### All Service Logs
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,231 @@
|
|||
# Heroku 是什么
|
||||
|
||||
Heroku 是一个一站式程序托管平台,你可以通过[heroku.com](https://www.heroku.com)获取更多信息
|
||||
|
||||
## 注册
|
||||
|
||||
你需要一个 heroku 帐户,如果你还没有,请通过此链接注册:[https://signup.heroku.com/](https://signup.heroku.com/)
|
||||
|
||||
## 安装命令行应用
|
||||
|
||||
请确保你已安装 heroku 命令行工具
|
||||
|
||||
### HomeBrew
|
||||
|
||||
```bash
|
||||
brew install heroku/brew/heroku
|
||||
```
|
||||
|
||||
### 其他安装方式
|
||||
|
||||
在此处查看其他安装选项: [https://devcenter.heroku.com/articles/heroku-cli#download-and-install](https://devcenter.heroku.com/articles/heroku-cli#download-and-install).
|
||||
|
||||
### 登录
|
||||
|
||||
安装命令行工具后,使用以下命令登录:
|
||||
|
||||
```bash
|
||||
heroku login
|
||||
```
|
||||
|
||||
查看当前登录的 heroku 电子邮件账户:
|
||||
|
||||
```bash
|
||||
heroku auth:whoami
|
||||
```
|
||||
|
||||
### 创建一个应用
|
||||
|
||||
通过访问 heroku.com 来访问你的帐户,然后从右上角的下拉菜单中创建一个新应用程序。Heroku 会问一些问题,例如区域和应用程序名称,只需按照提示操作即可。
|
||||
|
||||
### Git
|
||||
|
||||
Heroku 使用 Git 来部署你的应用程序,因此你需要将你的项目放入 Git 存储库(如果还没有的话)。
|
||||
|
||||
#### 初始化 Git
|
||||
|
||||
如果你需要将 Git 添加到你的项目中,在终端中输入以下命令:
|
||||
|
||||
```bash
|
||||
git init
|
||||
```
|
||||
|
||||
#### Master
|
||||
|
||||
默认情况下,Heroku 部署 **master** 分支。 确保在推送之前将所有更改都加入此分支。
|
||||
|
||||
通过以下命令检查你当前的分支:
|
||||
|
||||
```bash
|
||||
git branch
|
||||
```
|
||||
|
||||
星号表示当前分支。
|
||||
|
||||
```bash
|
||||
* master
|
||||
commander
|
||||
other-branches
|
||||
```
|
||||
|
||||
> **提示**:如果你没有看到任何输出并且你刚刚执行了 `git init`。 你需要先提交(commit)你的代码,然后你会看到 `git branch` 命令的输出。
|
||||
|
||||
|
||||
如果你当前 _不在_ **master** 上,请输入以下命令来切换:
|
||||
|
||||
```bash
|
||||
git checkout master
|
||||
```
|
||||
|
||||
#### 提交更改
|
||||
|
||||
如果此命令有输出,那么你有未提交的改动。
|
||||
|
||||
```bash
|
||||
git status --porcelain
|
||||
```
|
||||
|
||||
通过以下命令来提交
|
||||
|
||||
```bash
|
||||
git add .
|
||||
git commit -m "a description of the changes I made"
|
||||
```
|
||||
|
||||
#### 与 Heroku 进行连接
|
||||
|
||||
将你的应用与 heroku 连接(替换为你的应用名称)。
|
||||
|
||||
```bash
|
||||
$ heroku git:remote -a your-apps-name-here
|
||||
```
|
||||
|
||||
### 设置运行包(Buildpack)
|
||||
|
||||
设置运行包来告知 heroku 如何处理 Vapor。
|
||||
|
||||
```bash
|
||||
heroku buildpacks:set vapor/vapor
|
||||
```
|
||||
|
||||
### Swift 版本文件
|
||||
|
||||
我们添加的运行包会查找 **.swift-version** 文件以了解要使用的 swift 版本。 (将 5.2.1 替换为你的项目需要的任何版本。)
|
||||
|
||||
```bash
|
||||
echo "5.2.1" > .swift-version
|
||||
```
|
||||
|
||||
这将创建 **.swift-version** ,内容为 `5.2.1`。
|
||||
|
||||
|
||||
### Procfile
|
||||
|
||||
Heroku 使用 **Procfile** 来知道如何运行你的应用程序,在我们的示例中它需要这样配置:
|
||||
|
||||
```
|
||||
web: Run serve --env production --hostname 0.0.0.0 --port $PORT
|
||||
```
|
||||
|
||||
我们可以使用以下终端命令来创建它
|
||||
|
||||
```bash
|
||||
echo "web: Run serve --env production" \
|
||||
"--hostname 0.0.0.0 --port \$PORT" > Procfile
|
||||
```
|
||||
|
||||
### 提交更改
|
||||
|
||||
我们刚刚只是更改了这些文件,但它们没有被提交。 如果我们推送(push),heroku 将无法看到这些更改。
|
||||
|
||||
使用以下命令提交它们。
|
||||
|
||||
```bash
|
||||
git add .
|
||||
git commit -m "adding heroku build files"
|
||||
```
|
||||
|
||||
### 部署到 Heroku
|
||||
|
||||
你已准备好开始部署,从终端运行以下命令。 构建过程可能会需要一些时间,不必担心。
|
||||
|
||||
```none
|
||||
git push heroku master
|
||||
```
|
||||
|
||||
### 扩展
|
||||
|
||||
成功构建后,你需要添加至少一台服务器,一个网站服务是免费的,你可以通过以下方式获得它:
|
||||
|
||||
```bash
|
||||
heroku ps:scale web=1
|
||||
```
|
||||
|
||||
### 继续部署
|
||||
|
||||
当你想更新时只需将最新的更改推入 master 分支并推送到 heroku,它就会重新部署。
|
||||
|
||||
## Postgres
|
||||
|
||||
### 添加 PostgreSQL 数据库
|
||||
|
||||
在 dashboard.heroku.com 上访问你的应用程序,然后转到 **Add-ons** 部分。
|
||||
|
||||
从这里输入`postgress`,你会看到`Heroku Postgres`的选项。 选择它。
|
||||
|
||||
选择爱好开发免费计划(hobby dev free plan)。 Heroku 将自动完成剩下的工作。
|
||||
|
||||
完成后,你会看到数据库出现在 **Resources** 选项卡下。
|
||||
|
||||
### 配置数据库
|
||||
|
||||
我们现在必须告诉我们的应用程序如何访问数据库。 在 app 目录中运行。
|
||||
|
||||
```bash
|
||||
heroku config
|
||||
```
|
||||
|
||||
这会输出类似以下内容的内容:
|
||||
|
||||
```none
|
||||
=== today-i-learned-vapor Config Vars
|
||||
DATABASE_URL: postgres://cybntsgadydqzm:2d9dc7f6d964f4750da1518ad71hag2ba729cd4527d4a18c70e024b11cfa8f4b@ec2-54-221-192-231.compute-1.amazonaws.com:5432/dfr89mvoo550b4
|
||||
```
|
||||
|
||||
**DATABASE_URL** 这里将代表 postgres 数据库。 请**从不** 硬编码静态 url,heroku 会变更这个 url,并破坏你的应用程序。
|
||||
|
||||
以下是一个示例数据库配置
|
||||
|
||||
```swift
|
||||
if let databaseURL = Environment.get("DATABASE_URL") {
|
||||
app.databases.use(try .postgres(
|
||||
url: databaseURL
|
||||
), as: .psql)
|
||||
} else {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
如果你使用 Heroku Postgres 的标准计划,则需要开始未验证的 TLS。
|
||||
|
||||
不要忘记提交这些更改
|
||||
|
||||
```none
|
||||
git add .
|
||||
git commit -m "configured heroku database"
|
||||
```
|
||||
|
||||
### 重置你的数据库
|
||||
|
||||
你可以使用 `run` 命令在 heroku 上恢复或运行其他命令。 Vapor 的项目默认也被命名为 `Run`,所以读起来有点怪。
|
||||
要重置你的数据库请运行:
|
||||
|
||||
```bash
|
||||
heroku run Run -- revert --all --yes --env production
|
||||
```
|
||||
|
||||
如要迁移请运行以下命令:
|
||||
|
||||
```bash
|
||||
heroku run Run -- migrate --env production
|
||||
```
|
||||
|
|
@ -0,0 +1,152 @@
|
|||
# 使用 Nginx 部署
|
||||
|
||||
Nginx 是一款高性能、高可靠性、易于配置的 HTTP 服务器和 HTTP 反向代理服务器。
|
||||
尽管 Vapor 可以直接处理 HTTP 请求,并且支持 TLS。但将 Vapor 应用置于 Nginx 反向代理之后,可以提高性能、安全性、以及易用性。
|
||||
|
||||
!!! note
|
||||
我们推荐你将 Vapor 应用配置在 Nginx 的反向代理之后。
|
||||
|
||||
## 概述
|
||||
|
||||
HTTP 反向代理是什么意思?简而言之,反向代理服务器就是外部网络和你的真实的 HTTP 服务器之间的一个中间人,反向代理服务器处理所有进入的 HTTP 请求,并将它们转发给 Vapor 服务器。
|
||||
|
||||
反向代理的一个重要特性就是,它可以修改用户的请求,以及对其进行重定向。通过这个特性,反向代理服务器可以配置 TLS (https)、限制请求速率、甚至越过你的 Vapor 应用直接管理 Vapor 应用中的静态文件。
|
||||
|
||||

|
||||
|
||||
### 更多细节
|
||||
|
||||
默认的接收 HTTP 请求的端口是 `80` (HTTPS 是 `443`)。如果你将 Vapor 服务器绑定到 `80` 端口,它就可以直接处理和响应 HTTP 请求。如果你想要使用反向代理 (比如 Nginx),你就需要将 Vapor 服务器绑定到一个内部端口上,比如 `8080`。
|
||||
|
||||
!!! note
|
||||
绑定到大于 1024 的端口号无需使用 `sudo` 命令。
|
||||
|
||||
一旦你的 Vapor 应用被绑定到 `80` 或 `443` 以外的端口,那么外部网络将无法直接访问它 (没有配置防火墙的情况下,带上端口号仍然可以访问)。然后将 Nginx 服务器绑定到 `80` 端口上,并配置它转发请求到 `8080` 端口上的 Vapor 应用。
|
||||
|
||||
就这样,如果你正确配置了 Nginx,你可以看到你的 Vapor 应用已经可以响应 `80` 端口上的请求了,而外部网络和你的 Vapor 应用都不会感知到 Nginx 的存在。
|
||||
|
||||
## 安装 Nginx
|
||||
|
||||
首先是安装 Nginx。网络上有着大量资源和文档来描述如何安装 Nginx,因此在这里不再赘述。不论你使用哪个平台、操作系统、或服务供应商,你都能找到相应的文档或教程。
|
||||
|
||||
教程:
|
||||
|
||||
- [如何在 Ubuntu 14.04 LTS 上安装 Nginx?](https://www.digitalocean.com/community/tutorials/how-to-install-nginx-on-ubuntu-14-04-lts) (英文)
|
||||
- [如何在 Ubuntu 16.04 上安装 Nginx?](https://www.digitalocean.com/community/tutorials/how-to-install-nginx-on-ubuntu-16-04) (英文)
|
||||
- [如何在 Heroku 上部署 Nginx?](https://blog.codeship.com/how-to-deploy-nginx-on-heroku/) (英文)
|
||||
- [如何在 Ubuntu 14.04 上用 Docker 容器运行 Nginx?](https://www.digitalocean.com/community/tutorials/how-to-run-nginx-in-a-docker-container-on-ubuntu-14-04) (英文)
|
||||
|
||||
|
||||
### APT
|
||||
|
||||
可以通过 APT 工具安装 Nginx
|
||||
|
||||
```sh
|
||||
sudo apt-get update
|
||||
sudo apt-get install nginx
|
||||
```
|
||||
|
||||
你可以在浏览器中访问你的服务器的 IP 地址,来检查你的 Nginx 是否被正确安装.
|
||||
|
||||
|
||||
```sh
|
||||
http://server_domain_name_or_IP
|
||||
```
|
||||
|
||||
### Service
|
||||
|
||||
如何停止/启动/重启 Nginx 服务 (service)
|
||||
|
||||
```sh
|
||||
sudo service nginx stop
|
||||
sudo service nginx start
|
||||
sudo service nginx restart
|
||||
```
|
||||
|
||||
## 启动 Vapor
|
||||
|
||||
Nginx 可以通过 `sudo service nginx ...` 命令来启动或停止。同样的,你也需要一些类似的操作来启动或停止你的 Vapor 服务器。
|
||||
|
||||
有许多方法可以做到这一点,这通常取决于你使用的是哪个平台或系统。Supervisor 是其中一个较为通用的方式,你可以查看 [Supervisor](supervisor.md) 的配置方法,来配置启动或停止你的 Vapor 应用的命令。
|
||||
|
||||
## 配置 Nginx
|
||||
|
||||
要启用的站点的配置需要放在 `/etc/nginx/sites-enabled/` 目录下。
|
||||
|
||||
创建一个新的文件或者从 `/etc/nginx/sites-available/` 目录下的模版文件中拷贝一份配置,然后你就可以开始配置 Nginx 了。
|
||||
|
||||
这是一份配置文件的样例,它为一个 Vapor 项目进行了配置,这个项目位于 Home 目录下的一个名为 `Hello` 目录中。
|
||||
|
||||
```sh
|
||||
server {
|
||||
server_name hello.com;
|
||||
listen 80;
|
||||
|
||||
root /home/vapor/Hello/Public/;
|
||||
|
||||
location @proxy {
|
||||
proxy_pass http://127.0.0.1:8080;
|
||||
proxy_pass_header Server;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_pass_header Server;
|
||||
proxy_connect_timeout 3s;
|
||||
proxy_read_timeout 10s;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
这份配置假定你的 `Hello` 程序绑定到了 `8080` 端口上,并启用了生产模式 (production mode)。
|
||||
|
||||
### 管理文件
|
||||
|
||||
Nginx 可以越过你的 Vapor 应用,直接管理静态资源文件。这样可以为你的 Vapor 进程减轻一些不必要的压力,以提高一些性能。
|
||||
|
||||
```sh
|
||||
server {
|
||||
...
|
||||
|
||||
# nginx 直接处理所有静态资源文件的请求,其余请求则回落 (fallback) 到 Vapor 应用
|
||||
location / {
|
||||
try_files $uri @proxy;
|
||||
}
|
||||
|
||||
location @proxy {
|
||||
...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### TLS
|
||||
|
||||
如果你已经获取了 TLS 证书 (certification),那么配置 TLS 相对来说是比较简单的。如果想要获取免费的 TLS 证书,可以看看 [Let's Encrypt](https://letsencrypt.org/getting-started/)。
|
||||
|
||||
```sh
|
||||
server {
|
||||
...
|
||||
|
||||
listen 443 ssl;
|
||||
|
||||
ssl_certificate /etc/letsencrypt/live/hello.com/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/hello.com/privkey.pem;
|
||||
|
||||
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
|
||||
ssl_prefer_server_ciphers on;
|
||||
ssl_dhparam /etc/ssl/certs/dhparam.pem;
|
||||
ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA';
|
||||
ssl_session_timeout 1d;
|
||||
ssl_session_cache shared:SSL:50m;
|
||||
ssl_stapling on;
|
||||
ssl_stapling_verify on;
|
||||
add_header Strict-Transport-Security max-age=15768000;
|
||||
|
||||
...
|
||||
|
||||
location @proxy {
|
||||
...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
上面这份 Nginx 的 TLS 配置是相对比较严格的。其中一些配置不是必须的,但能提高安全性。
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
# Supervisor
|
||||
|
||||
[Supervisor](http://supervisord.org) 是一个进程控制系统,可让你轻松启动、停止和重启你的 Vapor 应用程序。
|
||||
|
||||
## 安装
|
||||
|
||||
Supervisor 可以通过 Linux 上的包管理器安装。
|
||||
|
||||
### Ubuntu
|
||||
|
||||
```sh
|
||||
sudo apt-get update
|
||||
sudo apt-get install supervisor
|
||||
```
|
||||
|
||||
### CentOS and Amazon Linux
|
||||
|
||||
```sh
|
||||
sudo yum install supervisor
|
||||
```
|
||||
|
||||
### Fedora
|
||||
|
||||
```sh
|
||||
sudo dnf install supervisor
|
||||
```
|
||||
|
||||
## 配置
|
||||
|
||||
服务器上的每个 Vapor 应用程序都应该有自己的配置文件。例如 `Hello` 项目,配置文件位于 `/etc/supervisor/conf.d/hello.conf`
|
||||
|
||||
```sh
|
||||
[program:hello]
|
||||
command=/home/vapor/hello/.build/release/Run serve --env production
|
||||
directory=/home/vapor/hello/
|
||||
user=vapor
|
||||
stdout_logfile=/var/log/supervisor/%(program_name)-stdout.log
|
||||
stderr_logfile=/var/log/supervisor/%(program_name)-stderr.log
|
||||
```
|
||||
|
||||
正如我们的配置文件中所指定的, `Hello` 项目位于用户 `vapor` 的主文件夹中。确保 `directory` 指向 `Package.swift` 文件所在项目的根目录。
|
||||
|
||||
`--env production` 标志会禁用详细日志记录。
|
||||
|
||||
### 环境
|
||||
|
||||
你可以使用 supervisor 将变量导出到你的 Vapor 应用程序。要导出多个环境值,请将它们全部放在一行上。根据 [Supervisor 文档](http://supervisord.org/configuration.html#program-x-section-values):
|
||||
|
||||
> 包含非字母数字字符的值应该用引号括起来(e.g. KEY="val:123",KEY2="val,456")。否则,引用值是可选的,但是推荐使用。
|
||||
|
||||
```sh
|
||||
environment=PORT=8123,ANOTHERVALUE="/something/else"
|
||||
```
|
||||
|
||||
可以在 Vapor 中使用 `Environment.get` 导出变量
|
||||
|
||||
```swift
|
||||
let port = Environment.get("PORT")
|
||||
```
|
||||
|
||||
## 开始
|
||||
|
||||
你现在可以加载并启动你的应用程序。
|
||||
|
||||
```sh
|
||||
supervisorctl reread
|
||||
supervisorctl add hello
|
||||
supervisorctl start hello
|
||||
```
|
||||
|
||||
!!! 注意
|
||||
`add` 命令可能已经启动了你的应用程序。
|
||||
|
|
@ -60,7 +60,7 @@ To migrate your database, run the `migrate` command.
|
|||
vapor run migrate
|
||||
```
|
||||
|
||||
You can also run this [command through Xcode](../commands.md#xcode). The migrate command will check the database to see if any new migrations have been registered since it was last run. If there are new migrations, it will ask for a confirmation before running them.
|
||||
You can also run this [command through Xcode](../advanced/commands.md#xcode). The migrate command will check the database to see if any new migrations have been registered since it was last run. If there are new migrations, it will ask for a confirmation before running them.
|
||||
|
||||
### Revert
|
||||
|
||||
|
|
|
|||
|
|
@ -346,7 +346,7 @@ In the database, `@Group` is stored as a flat structure with keys joined by `_`.
|
|||
|
||||
## Codable
|
||||
|
||||
Models conform to `Codable` by default. This means you can use your models with Vapor's [content API](../content.md) by adding conformance to the `Content` protocol.
|
||||
Models conform to `Codable` by default. This means you can use your models with Vapor's [content API](../basics/content.md) by adding conformance to the `Content` protocol.
|
||||
|
||||
```swift
|
||||
extension Planet: Content { }
|
||||
|
|
@ -593,4 +593,4 @@ public static let space: String? = "mirror_universe"
|
|||
// ...
|
||||
```
|
||||
|
||||
Fluent will use this when building all database queries.
|
||||
Fluent will use this when building all database queries.
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ When creating a project using `vapor new`, answer "yes" to including Fluent and
|
|||
|
||||
### Existing Project
|
||||
|
||||
If you have an existing project that you want to add Fluent to, you will need to add two dependencies to your [package](../spm.md):
|
||||
If you have an existing project that you want to add Fluent to, you will need to add two dependencies to your [package](../gettingstarted/spm.md):
|
||||
|
||||
- [vapor/fluent](https://github.com/vapor/fluent)@4.0.0
|
||||
- One (or more) Fluent driver(s) of your choice
|
||||
|
|
@ -366,7 +366,7 @@ app.post("galaxies") { req -> EventLoopFuture<Galaxy> in
|
|||
```
|
||||
|
||||
!!! seealso
|
||||
See [Content → Overview](../content.md) for more information about decoding request bodies.
|
||||
See [Content → Overview](../basics/content.md) for more information about decoding request bodies.
|
||||
|
||||
Once you have an instance of the model, calling `create(on:)` saves the model to the database. This returns an `EventLoopFuture<Void>` which signals that the save has completed. Once the save completes, return the newly created model using `map`.
|
||||
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ let earth = try await Planet.query(on: database)
|
|||
```
|
||||
|
||||
!!! tip
|
||||
If using `EventLoopFuture`s, this method can be combined with [`unwrap(or:)`](../errors.md#abort) to return a non-optional model or throw an error.
|
||||
If using `EventLoopFuture`s, this method can be combined with [`unwrap(or:)`](../basics/errors.md#abort) to return a non-optional model or throw an error.
|
||||
|
||||
## Filter
|
||||
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ Der Ordner _App_ beinhaltet die Anwendungslogik.
|
|||
|
||||
#### Controllers
|
||||
|
||||
Der Ordner _Controllers_ beinhaltet die Definitionen der Endpunkte der Anwendung. Mehr dazu findest du im Abschnitt [Controllers](controllers.md).
|
||||
Der Ordner _Controllers_ beinhaltet die Definitionen der Endpunkte der Anwendung. Mehr dazu findest du im Abschnitt [Controllers](../basics/controllers.md).
|
||||
|
||||
#### Migrations
|
||||
|
||||
|
|
@ -0,0 +1,90 @@
|
|||
# 项目结构
|
||||
|
||||
现在,你已经创建并运行了第一个 Vapor 应用程序,让我们稍微花点时间熟悉一下 Vapor 的项目结构。
|
||||
|
||||
该结构是在 [SPM](spm.md) 的基础上演化而来;因此,如果你曾经使用过 SPM,应该会很熟悉。
|
||||
|
||||
|
||||
```
|
||||
.
|
||||
├── Public
|
||||
├── Sources
|
||||
│ ├── App
|
||||
│ │ ├── Controllers
|
||||
│ │ ├── Migrations
|
||||
│ │ ├── Models
|
||||
│ │ ├── app.swift
|
||||
│ │ ├── configure.swift
|
||||
│ │ └── routes.swift
|
||||
│ └── Run
|
||||
│ └── main.swift
|
||||
├── Tests
|
||||
│ └── AppTests
|
||||
└── Package.swift
|
||||
```
|
||||
|
||||
下面将详细地解释每个文件夹的作用。
|
||||
|
||||
## Public
|
||||
|
||||
如果你使用了 `FileMiddleware` 中间件,那么此文件夹包含你的应用程序提供的所有公共文件,通常是图片、`.css`样式和浏览器脚本等。
|
||||
|
||||
例如,对 `localhost:8080/favicon.ico` 发起的请求将检查是否存在 `Public/favicon.ico` 图片并回应。
|
||||
|
||||
在 Vapor 可以提供公共文件之前,你需要在 `configure.swift` 文件中启用`FileMiddleware`,参考如下所示:
|
||||
|
||||
```swift
|
||||
// 从 'Public/' 目录提供文件
|
||||
let fileMiddleware = FileMiddleware(
|
||||
publicDirectory: app.directory.publicDirectory
|
||||
)
|
||||
app.middleware.use(fileMiddleware)
|
||||
```
|
||||
|
||||
## Sources
|
||||
|
||||
该文件夹包含项目的所有 Swift 代码源文件。文件夹 `App`和 `Run`反应软件包的模块,例如这篇 [SPM](spm.md) 文章中所述。
|
||||
|
||||
### App
|
||||
|
||||
应用程序的所有核心代码都包含在这里。
|
||||
|
||||
#### Controllers
|
||||
|
||||
控制器是将应用程序的不同逻辑进行分组的优秀方案,大多数控制器都具备接受多种请求的功能,并根据需要进行响应。
|
||||
|
||||
#### Migrations
|
||||
|
||||
如果你使用 Fluent,则可以在 Migrations 文件夹中进行数据库迁移。
|
||||
|
||||
#### Models
|
||||
|
||||
models 文件夹常用于存放 `Content` 和 Fluent `Model` 的类或结构体。
|
||||
|
||||
#### configure.swift
|
||||
|
||||
这个文件包含 `configure(_:)` 函数,`main.swift` 调用这个方法用以配置新创建的 `Application` 实例。你可以在这里注册诸如路由、数据库、providers 等服务。
|
||||
|
||||
#### routes.swift
|
||||
|
||||
这个文件包含 `routes(_:)` 方法,它会在 `configure(_:)` 结尾处被调用,用以将路由注册到你的`Application`。
|
||||
|
||||
### Run
|
||||
|
||||
这是主要的可执行目标,只包含启动和运行应用程序所需的代码。
|
||||
|
||||
## Tests
|
||||
|
||||
`Sources` 文件夹中的每个不可运行的模块在 `Tests` 中都可以创建一个对应的文件夹,包含 `XCTest` 模块上构建的用例,用来测试你的代码。
|
||||
|
||||
可以在命令行使用 `swift test`或在 Xcode 中按 `⌘+U` 来进行测试。
|
||||
|
||||
|
||||
### AppTests
|
||||
|
||||
此文件夹包含 `App` 模块中代码的单元测试。
|
||||
|
||||
## Package.swift
|
||||
|
||||
最后,是这个项目运行所依赖的第三方库配置。
|
||||
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
# Hallo Welt!
|
||||
|
||||
In diesem Abschnitt erklären wir dir Schritt für Schritt, wie du ein Vapor-Projekt erstellst und ausführst. Sollte dir _Swift_ oder die _Vapor Toolbox_ noch fehlen, werfe zuerst einen Blick in die beiden Abschnitte [Installation → macOS](install/macos.md) und [Installation → Linux](install/linux.md).
|
||||
In diesem Abschnitt erklären wir dir Schritt für Schritt, wie du ein Vapor-Projekt erstellst und ausführst. Sollte dir _Swift_ oder die _Vapor Toolbox_ noch fehlen, werfe zuerst einen Blick in die beiden Abschnitte [Installation → macOS](../install/macos.md) und [Installation → Linux](../install/linux.md).
|
||||
|
||||
|
||||
## Projekt erstellen
|
||||
|
|
@ -55,4 +55,4 @@ Bei der Erstausführung werden die Paketverweise nachgeladen. Dementsprechend ka
|
|||
|
||||
Rufe die Seite über den Link <a href="http://localhost:8080/hello" target="_blank">localhost:8080/hello</a> oder <a href="http://127.0.0.1:8080" target="_blank">http://127.0.0.1:8080</a> im Browser auf. Als Ergebnis sollte "Hello World" im Browser erscheinen.
|
||||
|
||||
Das wars! Geschafft! Gratulation zur ersten Vapor-Anwendung. 🎉
|
||||
Das wars! Geschafft! Gratulation zur ersten Vapor-Anwendung. 🎉
|
||||
|
|
@ -4,8 +4,8 @@ This guide will take you step by step through creating a new Vapor project, buil
|
|||
|
||||
If you have not yet installed Swift or Vapor Toolbox, check out the install section.
|
||||
|
||||
- [Install → macOS](install/macos.md)
|
||||
- [Install → Linux](install/linux.md)
|
||||
- [Install → macOS](../install/macos.md)
|
||||
- [Install → Linux](../install/linux.md)
|
||||
|
||||
## New Project
|
||||
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
|
||||
# 你好,世界
|
||||
|
||||
本文将指引你逐步创建、编译并运行 Vapor 的项目。
|
||||
|
||||
如果尚未安装 Swift 和 Vapor Toolbox,请查看安装部分。
|
||||
|
||||
- [安装 → macOS](../install/macos.md)
|
||||
- [安装 → Linux](../install/linux.md)
|
||||
|
||||
## 创建
|
||||
|
||||
首先,在电脑上创建 Vapor 项目。
|
||||
|
||||
打开终端并使用以下 Toolbox 的命令行,这将会在当前目录创建一个包含 Vapor 项目的文件夹。
|
||||
|
||||
```sh
|
||||
vapor new hello -n
|
||||
```
|
||||
|
||||
!!! tip
|
||||
使用 `-n` 为所有的问题自动选择 no 来为您提供一个基本的模板。
|
||||
|
||||
|
||||
命令完成后,切换到新创建的文件夹
|
||||
|
||||
```sh
|
||||
cd hello
|
||||
```
|
||||
|
||||
## 编译 & 运行
|
||||
|
||||
### Xcode
|
||||
|
||||
首先,在Xcode打开项目:
|
||||
|
||||
```sh
|
||||
open Package.swift
|
||||
```
|
||||
|
||||
|
||||
Xcode 将自动开始下载Swift包管理器依赖,在第一次打开一个项目时,这可能需要一些时间,当依赖下载后,Xcode将显示可以用的 Scheme。
|
||||
|
||||
在窗口的顶部,在Play和Stop按钮的右侧,单击项目名称以选择项目的Scheme,并选择一个适当的target——大概率是“My Mac”。单击play按钮编译并运行项目。
|
||||
|
||||
你应该会在Xcode窗口的底部看到控制台弹出。
|
||||
|
||||
```sh
|
||||
[ INFO ] Server starting on http://127.0.0.1:8080
|
||||
```
|
||||
|
||||
### Linux
|
||||
|
||||
在 Linux 和其他操作系统上(甚至在 macOS 上如果你不想使用 Xcode ),你可以在你喜欢的编辑器中编辑项目,比如 Vim 或 VSCode 。关于设置其他ide的最新细节,请参阅 [Swift Server Guides](https://github.com/swift-server/guides/blob/main/docs/setup-and-ide-alternatives.md)。
|
||||
|
||||
在终端运行以下命令来编译和运行你的项目。
|
||||
|
||||
```sh
|
||||
swift run
|
||||
```
|
||||
它将构建并运行项目。第一次运行时,需要花费一些时间来获取和下载依赖项。一旦运行,你应该在你的控制台中看到以下内容:
|
||||
|
||||
```sh
|
||||
[ INFO ] Server starting on http://127.0.0.1:8080
|
||||
```
|
||||
|
||||
## Visit Localhost
|
||||
|
||||
打开你的浏览器,然后访问 <a href="http://localhost:8080/hello" target="_blank">localhost:8080/hello</a> 或者 <a href="http://127.0.0.1:8080" target="_blank">http://127.0.0.1:8080</a>
|
||||
|
||||
你将看见以下页面
|
||||
|
||||
```html
|
||||
Hello, world!
|
||||
```
|
||||
|
||||
恭喜你创建,构建,运行了你的第一个 Vapor 应用!🎉
|
||||
|
|
@ -0,0 +1,100 @@
|
|||
# Swift Package Manager
|
||||
|
||||
[Swift Package Manager](https://swift.org/package-manager/)(SPM)用于构建项目的源代码和依赖项。由于 Vapor 严重依赖 SPM,因此最好了解其工作原理。
|
||||
|
||||
SPM 与 Cocoapods,Ruby gems 和 NPM 相似。您可以在命令行中将 SPM 与 `swift build`、`swift test` 等命令或兼容的 IDE 结合使用。但是,与其他软件包管理器不同,SPM 软件包没有中央软件包索引。SPM 使用 [Git 标签](https://git-scm.com/book/en/v2/Git-Basics-Tagging) 和 URL 来获取 Git 存储库和依赖版本。
|
||||
|
||||
## Package Manifest
|
||||
|
||||
SPM 在项目中查找的第一项是 package 清单。它应始终位于项目的根目录中,并命名为 `Package.swift`。
|
||||
|
||||
看一下这个示例:
|
||||
|
||||
```swift
|
||||
// swift-tools-version:5.2
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "app",
|
||||
platforms: [
|
||||
.macOS(.v10_15)
|
||||
],
|
||||
products: [
|
||||
.executable(name: "Run", targets: ["Run"]),
|
||||
.library(name: "App", targets: ["App"]),
|
||||
],
|
||||
dependencies: [
|
||||
.package(url: "https://github.com/vapor/vapor.git", from: "4.0.0"),
|
||||
],
|
||||
targets: [
|
||||
.target(name: "App", dependencies: [.product(name: "Vapor", package: "vapor")]),
|
||||
.target(name: "Run", dependencies: ["App"]),
|
||||
.testTarget(name: "AppTests", dependencies: ["App"])
|
||||
]
|
||||
)
|
||||
|
||||
```
|
||||
|
||||
下面将对这段代码的各部分进行说明。
|
||||
|
||||
### Tools Version
|
||||
|
||||
第一行表示需要使用的 Swift tools 版本号,它指明了 Swift 的最低可用版本。Package 描述 API 可能随着 Swift 版本而改变,所以这一行将让 Swift 确认怎么取解析你的配置文件。
|
||||
|
||||
### Package Name
|
||||
|
||||
`Package` 的第一个参数代表当前 package 的名字。如果软件包是公共的,你应该使用 Git 存储库的 URL 的最后一段作为名称
|
||||
|
||||
### Platforms
|
||||
|
||||
`platforms` 数组指定此程序包支持的平台和版本。通过指定 `.macOS(.v10_14)`,说明此软件包需要 macOS Mojave 或更高版本。 Xcode 加载该项目时,它将最低部署版本设置为 10.14,以便您可以使用所有可用的 API。
|
||||
|
||||
### Products
|
||||
|
||||
products 字段代表 package 构建的时候要生成的 targets。示例中,有两个 target,一个是 `library`,另一个是 `executable`。
|
||||
|
||||
### Dependencies
|
||||
|
||||
dependencies 字段代表项目需要依赖的 package。所有 Vapor 应用都依赖 Vapor package ,但是你也可以添加其它想要的 dependency。
|
||||
|
||||
如上面这个示例,[vapor/vapor](https://github.com/vapor/vapor) 4.0 或以上版本是这个 package 的 dependency。当在 package 中添加了 dependency 后,接下来你必须设置是哪个 targets 依赖了新的可用模块。
|
||||
|
||||
### Targets
|
||||
|
||||
Targets 是你的 package 里包含 modules、executables 以及 tests 总和。虽然可以添加任意多的 targets 来组织代码,但大部分 Vapor 应用有 3 个 target 就足够了。每个 target 声明了它依赖的 module。为了在代码中可以 import 这些 modules ,你必须在这里添加 module 名字。一个 target 可以依赖于工程中其它的 target 或者任意你添加在 [dependencies](#dependencies) 数组中且暴露出来的 modules。
|
||||
|
||||
!!! tip
|
||||
可运行 targets (包含 `main.swift` 文件的 target) 不能被其它 modules 导入。这就是为什么 Vapor 会有 `App` 和 `Run` 两种 target。任何包含在 App 中的代码都可以在 `AppTests` 中被测试验证。
|
||||
|
||||
## Folder Structure
|
||||
|
||||
以下是典型的 SPM package 目录结构。
|
||||
|
||||
```
|
||||
.
|
||||
├── Sources
|
||||
│ ├── App
|
||||
│ │ └── (Source code)
|
||||
│ └── Run
|
||||
│ └── main.swift
|
||||
├── Tests
|
||||
│ └── AppTests
|
||||
└── Package.swift
|
||||
```
|
||||
|
||||
每个 `.target` 对应 `Sources` 中的一个文件夹。
|
||||
每个 `.testTarget` 对应 `Tests` 中的一个文件夹。
|
||||
|
||||
## Package.resolved
|
||||
|
||||
第一次构建成功后,SPM 将会自动创建一个 `Package.resolved` 文件。`Package.resolved` 保存了当前项目所有用到的 `dependency` 版本。下一次当你构建你的项目时将会同样的版本,甚至是这些依赖有更新的版本也不会也使用更新的版本。
|
||||
|
||||
更新依赖, 运行 `swift package update`.
|
||||
|
||||
## Xcode
|
||||
|
||||
如果使用 Xcode 11 或更高版本,则在修改 `Package.swift` 文件时,将自动更改 dependencies、targets、products 等。
|
||||
|
||||
如果要更新到最新的依赖项,请使用 File → Swift Packages → 更新到最新的 Swift Package 版本。
|
||||
|
||||
您可能还想将 `.swiftpm` 文件添加到您的 `.gitignore` 文件中(Xcode 在此处存储 Xcode 项目配置)。
|
||||
|
|
@ -16,15 +16,15 @@ To fix this, set a custom working directory in the Xcode scheme for your project
|
|||
|
||||
First, edit your project's scheme by clicking on the scheme selector by the play and stop buttons.
|
||||
|
||||

|
||||

|
||||
|
||||
Select _Edit Scheme..._ from the dropdown.
|
||||
|
||||

|
||||

|
||||
|
||||
In the scheme editor, choose the _Run_ action and the _Options_ tab. Check _Use custom working directory_ and enter the path to your project's root folder.
|
||||
|
||||

|
||||

|
||||
|
||||
You can get the full path to your project's root by running `pwd` from a terminal window open there.
|
||||
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
# Xcode
|
||||
|
||||
这页将显示一下使用Xcode的提示和技巧。如果你使用不同的开发环境,你可以跳过此页。
|
||||
|
||||
## 自定义工作目录(Working directory)
|
||||
|
||||
Xcode 将默认在 _DerivedData_ 目录运行项目。这与项目的根目录(你的 _Package.swift_ 文件所在的目录)不在同一个目录,这意味着 Vapor 将找不到像 _.env_ 或者 _Public_ 等一些文件和目录。
|
||||
|
||||
如果在运行应用程序时看到以下警告,您就可以知道这正在发生。
|
||||
|
||||
```fish
|
||||
[ WARNING ] No custom working directory set for this scheme, using /path/to/DerivedData/project-abcdef/Build/
|
||||
```
|
||||
|
||||
要解决这个问题,你可以在 Xcode schem 中为你的项目设置一个自定义的工作目录。
|
||||
|
||||
首先,编辑项目的 scheme。
|
||||
|
||||

|
||||
|
||||
在下拉框中选择 _Edit Scheme..._
|
||||
|
||||

|
||||
|
||||
在 scheme 编辑器中,选择 _Run_ action 以及 _Options_ tab页。选中 _Use custom working directory_ 然后输入你项目根目录。
|
||||
|
||||

|
||||
|
||||
你可以在终端中运行 `pwd` 来获取你项目根目录的绝对目录
|
||||
|
||||
```fish
|
||||
# 确认我们在 vapor 项目目录
|
||||
vapor --version
|
||||
# get path to this folder
|
||||
pwd
|
||||
```
|
||||
|
||||
你应该能看见类似下面的输出。
|
||||
|
||||
```
|
||||
framework: 4.x.x
|
||||
toolbox: 18.x.x
|
||||
/path/to/project
|
||||
```
|
||||
|
|
@ -4,7 +4,7 @@ Willkommen zur Dokumentation von Vapor! Vapor ist ein Web-Framework für Swift,
|
|||
|
||||
## Einstieg
|
||||
|
||||
Für die Installation, folge den Anweisungen im Abschnitt [Installation → macOS](install/macos.de.md). Nach der Installation, folge den Anweisungen [Erste Schritte → Hello, world](hello-world.de.md) um deine erste Vapor-Anwendungen zu erstellen.
|
||||
Für die Installation, folge den Anweisungen im Abschnitt [Installation → macOS](install/macos.de.md). Nach der Installation, folge den Anweisungen [Erste Schritte → Hello, world](gettingstarted/hello-world.de.md) um deine erste Vapor-Anwendungen zu erstellen.
|
||||
|
||||
## Hilfen
|
||||
|
||||
|
|
@ -25,4 +25,4 @@ Die Dokumentationen zu älteren Versionen von Vapor findest du unter [https://le
|
|||
|
||||
## Authoren
|
||||
|
||||
Das Vapor-Kernteam und viele weitere Mitglieder der Vapor-Community.
|
||||
Das Vapor-Kernteam und viele weitere Mitglieder der Vapor-Community.
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ Welcome to the Vapor Documentation! Vapor is a web framework for Swift, allowing
|
|||
|
||||
If this is your first time using Vapor, head to [Install → macOS](install/macos.md) to install Swift and Vapor.
|
||||
|
||||
Once you have Vapor installed, check out [Getting Started → Hello, world](hello-world.md) to create your first Vapor app!
|
||||
Once you have Vapor installed, check out [Getting Started → Hello, world](gettingstarted/hello-world.md) to create your first Vapor app!
|
||||
|
||||
## Other Sources
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ Welkom bij de Vapor Documentatie! Vapor is een web framework voor Swift, dat u t
|
|||
|
||||
Als dit je eerste keer is dat je Vapor gebruikt, ga naar [Install → macOS](install/macos.md) om Swift en Vapor te installeren.
|
||||
|
||||
Wanneer dat je Vapor hebt geinstalleerd, bekijk dan [Getting Started → Hello, world](hello-world.md) om je eerste Vapor app te maken.
|
||||
Wanneer dat je Vapor hebt geinstalleerd, bekijk dan [Getting Started → Hello, world](gettingstarted/hello-world.md) om je eerste Vapor app te maken.
|
||||
|
||||
## Andere bronnen
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,37 @@
|
|||
|
||||
Vapor 是 Swift 最流行的 Web 网络框架。它可以为你的网站或 API 提供精美的页面展示和简易的使用方式。
|
||||
|
||||
|
||||
!!! 招募-翻译爱好者
|
||||
Vapor 中文翻译小组现招募翻译爱好者参与文档的翻译和校对,你可以查看中文节点下尚未完成的部分,然后选一篇你感兴趣的文档进行翻译,之后 [在此](https://github.com/vapor/docs) 提交 PR,我们将在校对无误后同步更新在这个文档。
|
||||
|
||||
|
||||
## 开始
|
||||
|
||||
如果你是第一次使用 Vapor,请前往 [安装 → macOS](install/macos.md) 安装 Swift 和 Vapor 开发环境。
|
||||
|
||||
Vapor 安装完成后,请查看 [开始 → 你好,世界](gettingstarted/hello-world.md) 示例,以创建你的第一个 Vapor 应用程序!
|
||||
|
||||
|
||||
## 其他资源
|
||||
|
||||
这里是其他一些有关 Vapor 框架的内容,以供学习与交流。
|
||||
|
||||
| 名称 | 描述 | 链接 |
|
||||
|----------------|--------------------------------------------------|-----------------------------------------------------------------|
|
||||
| Vapor Discord | 与数千名 Vapor 开发人员交流。 | [访问 →](http://vapor.team) |
|
||||
| API docs | 通过代码注释自动生成的文档。 | [访问 →](http://api.vapor.codes) |
|
||||
| Stack Overflow | 使用 `Vapor` 标签提问和回答相关问题。 | [访问 →](http://stackoverflow.com/questions/tagged/vapor) |
|
||||
| Swift Forums |在 Swift.org 论坛的 Vapor 专题发布。 | [访问 →](https://forums.swift.org/c/related-projects/vapor) |
|
||||
| Source Code | 了解 Vapor 的工作原理。 | [访问 →](https://github.com/vapor/vapor) |
|
||||
| GitHub Issues | 在 GitHub 上报告错误或提交功能。 | [访问 →](https://github.com/vapor/vapor/issues) |
|
||||
|
||||
|
||||
## 旧版文档
|
||||
|
||||
旧版文档已废弃,如有需要可在此查看:[https://legacy.docs.vapor.codes/](https://legacy.docs.vapor.codes/)。
|
||||
|
||||
|
||||
## 作者
|
||||
|
||||
Vapor 核心团队以及 Vapor 社区的数百名成员。
|
||||
|
|
@ -60,4 +60,4 @@ You should see a list of available commands.
|
|||
|
||||
## Next
|
||||
|
||||
After you have installed Swift, create your first app in [Getting Started → Hello, world](../hello-world.md).
|
||||
After you have installed Swift, create your first app in [Getting Started → Hello, world](../gettingstarted/hello-world.md).
|
||||
|
|
|
|||
|
|
@ -0,0 +1,65 @@
|
|||
|
||||
# 在 Linux 上面安装
|
||||
|
||||
你需要 Swift 5.2 或更高版本来使用Vapor。 Swift5.2可以通过[Swift.org](https://swift.org/download/)上面的工具链来安装。
|
||||
|
||||
## 支持的发行版和版本
|
||||
|
||||
Vapor 与 Swift 5.2 或者更高的版本对 Linux 的版本支持保持一致。
|
||||
|
||||
!!! 提示
|
||||
下面列出的版本可能会随时过期。你可以到 [Swift Releases](https://swift.org/download/#releases) 官方网站去确认官方支持的操作系统。
|
||||
|
||||
|Distribution|Version|Swift Version|
|
||||
|-|-|-|
|
||||
|Ubuntu|16.04, 18.04|>= 5.2|
|
||||
|Ubuntu|20.04|>= 5.2.4|
|
||||
|Fedora|>= 30|>= 5.2|
|
||||
|CentOS|8|>= 5.2.4|
|
||||
|Amazon Linux|2|>= 5.2.4|
|
||||
|
||||
官方不支持的 Linux 发行版可能可以通过编译源码来运行 Swift,但是 Vapor 不能保证其稳定性。可以在 [Swift repo](https://github.com/apple/swift#getting-started) 学习更多关于编译 Swift。
|
||||
|
||||
## 安装 Swift
|
||||
|
||||
访问 Swift.org's [Using Downloads](https://swift.org/download/#using-downloads) 手册来学习如何在 Linux 安装 Swift。
|
||||
|
||||
### Fedora
|
||||
|
||||
Fedora 用户可以简单的通过下面的命令来安装 Swift:
|
||||
|
||||
```sh
|
||||
sudo dnf install swift-lang
|
||||
```
|
||||
|
||||
如果你正在使用 Fedora 30,你需要添加添加 EPEL 8 来获取 Swift 5.2 或更新的版本。
|
||||
|
||||
|
||||
## Docker
|
||||
|
||||
你也可以使用预装了编译器的 Swift 官方 Docker 镜像,可以在[Swift's Docker Hub](https://hub.docker.com/_/swift)了解更多。
|
||||
|
||||
## 安装 Toolbox
|
||||
|
||||
现在你已经安装了Swift,让我们安装 [Vapor Toolbox](https://github.com/vapor/toolbox)。使用 Vapor 不是必须要使用此 CLI 工具,但它包含有用的实用程序。
|
||||
|
||||
在 Linux 系统上,你需要通过源码来编译toolbox,访问 toolbox 在Github上的 <a href="https://github.com/vapor/toolbox/releases" target="_blank">releases</a> 来获取最新版本
|
||||
|
||||
```sh
|
||||
git clone https://github.com/vapor/toolbox.git
|
||||
cd toolbox
|
||||
git checkout <desired version>
|
||||
make install
|
||||
```
|
||||
|
||||
通过打印信息来再次确认是否已经安装成功。
|
||||
|
||||
```sh
|
||||
vapor --help
|
||||
```
|
||||
|
||||
你应该能看见可用命令的列表。
|
||||
|
||||
## 下一步
|
||||
|
||||
在你安装完 Swift 之后,通过 [开始 → Hello, world](../gettingstarted/hello-world.md) 来学习创建你的第一个应用。
|
||||
|
|
@ -29,4 +29,4 @@ brew install vapor
|
|||
|
||||
##
|
||||
|
||||
Nach den Installationen kannst du mit der Erstellung deiner ersten Vapor-Anwendung beginnen. Folge dazu den Anweisungen im Abschnitt [Erste Schritte → Hello, world](../hello-world.de.md).
|
||||
Nach den Installationen kannst du mit der Erstellung deiner ersten Vapor-Anwendung beginnen. Folge dazu den Anweisungen im Abschnitt [Erste Schritte → Hello, world](../gettingstarted/hello-world.de.md).
|
||||
|
|
|
|||
|
|
@ -45,4 +45,4 @@ You should see a list of available commands.
|
|||
|
||||
## Next
|
||||
|
||||
Now that you have installed Swift and Vapor Toolbox, create your first app in [Getting Started → Hello, world](../hello-world.md).
|
||||
Now that you have installed Swift and Vapor Toolbox, create your first app in [Getting Started → Hello, world](../gettingstarted/hello-world.md).
|
||||
|
|
|
|||
|
|
@ -0,0 +1,48 @@
|
|||
# 在 macOS 上安装
|
||||
|
||||
要在 macOS 上使用 Vapor,你将需要 Swift 5.2 或更高版本。 Swift 及其所有依赖项都与 Xcode 捆绑。
|
||||
|
||||
## 安装 Xcode
|
||||
|
||||
从 [Mac App Store](https://itunes.apple.com/us/app/xcode/id497799835?mt=12) 安装 Xcode 11.4 或更高版本。
|
||||
|
||||

|
||||
|
||||
下载 Xcode 之后,必须将其打开以完成安装。可能还需要耐心等待一会儿。
|
||||
|
||||
安装后,打开 Terminal 输入以下命令打印 Swift 的版本,检查版本号以确保安装成功。
|
||||
|
||||
```sh
|
||||
swift --version
|
||||
```
|
||||
|
||||
你应该能够看到 Swift 的版本信息已打印。
|
||||
|
||||
```sh
|
||||
Apple Swift version 5.2 (swiftlang-1100.0.270.13 clang-1100.0.33.7)
|
||||
Target: x86_64-apple-darwin19.0.0
|
||||
```
|
||||
|
||||
Vapor 4 需要 Swift 5.2 或更高版本。
|
||||
|
||||
## 安装 Toolbox
|
||||
|
||||
现在你已经安装了 Swift,让我们安装 [Vapor Toolbox](https://github.com/vapor/toolbox)。 使用 Vapor 不需要此 CLI 工具,但是它包含一些实用的程序,例如新项目创建。
|
||||
|
||||
Toolbox 通过 Homebrew 分发。如果你还没有安装 Homebrew,请访问 <a href="https://brew.sh" target="_blank">brew.sh</a> 查看安装说明。
|
||||
|
||||
```sh
|
||||
brew install vapor
|
||||
```
|
||||
|
||||
通过输出帮助内容以确保安装成功。
|
||||
|
||||
```sh
|
||||
vapor --help
|
||||
```
|
||||
|
||||
你应该可以看到 Vapor 包含的可用命令列表。
|
||||
|
||||
## 下一步
|
||||
|
||||
现在你已经安装了 Swift and Vapor Toolbox,在 [开始 → 你好,世界](../gettingstarted/hello-world.md) 中创建你的第一个 Vapor 应用程序。
|
||||
|
|
@ -31,7 +31,7 @@ let package = Package(
|
|||
|
||||
## Configure
|
||||
|
||||
Once you have added the package to your project, you can configure Vapor to use it. This is usually done in [`configure.swift`](../folder-structure.md#configureswift).
|
||||
Once you have added the package to your project, you can configure Vapor to use it. This is usually done in [`configure.swift`](../gettingstarted/folder-structure.md#configureswift).
|
||||
|
||||
```swift
|
||||
import Leaf
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ This library is an integration between Vapor and [**RediStack**](https://gitlab.
|
|||
|
||||
The first step to using Redis is adding it as a dependency to your project in your Swift package manifest.
|
||||
|
||||
> This example is for an existing package. For help on starting a new project, see the main [Getting Started](../hello-world.md) guide.
|
||||
> This example is for an existing package. For help on starting a new project, see the main [Getting Started](../gettingstarted/hello-world.md) guide.
|
||||
|
||||
```swift
|
||||
dependencies: [
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# Redis & Sessions
|
||||
|
||||
Redis can act as a storage provider for caching [session data](../sessions.md#session-data) such as user credentials.
|
||||
Redis can act as a storage provider for caching [session data](../advanced/sessions.md#session-data) such as user credentials.
|
||||
|
||||
If a custom [`RedisSessionsDelegate`](https://api.vapor.codes/redis/master/Redis/RedisSessionsDelegate/) isn't provided, a default will be used.
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ Authentication is the act of verifying a user's identity. This is done through t
|
|||
|
||||
## Introduction
|
||||
|
||||
Vapor's Authentication API provides support for authenticating a user via the `Authorization` header, using [Basic](https://tools.ietf.org/html/rfc7617) and [Bearer](https://tools.ietf.org/html/rfc6750). It also supports authenticating a user via the data decoded from the [Content](/content.md) API.
|
||||
Vapor's Authentication API provides support for authenticating a user via the `Authorization` header, using [Basic](https://tools.ietf.org/html/rfc7617) and [Bearer](https://tools.ietf.org/html/rfc6750). It also supports authenticating a user via the data decoded from the [Content](../basics/content.md) API.
|
||||
|
||||
Authentication is implemented by creating an `Authenticator` which contains the verification logic. An authenticator can be used to protect individual route groups or an entire app. The following authenticator helpers ship with Vapor:
|
||||
|
||||
|
|
@ -256,11 +256,11 @@ req.auth.logout(User.self)
|
|||
|
||||
## Fluent
|
||||
|
||||
[Fluent](fluent/overview.md) defines two protocols `ModelAuthenticatable` and `ModelTokenAuthenticatable` which can be added to your existing models. Conforming your models to these protocols allows for the creation of authenticators for protecting endpoints.
|
||||
[Fluent](../fluent/overview.md) defines two protocols `ModelAuthenticatable` and `ModelTokenAuthenticatable` which can be added to your existing models. Conforming your models to these protocols allows for the creation of authenticators for protecting endpoints.
|
||||
|
||||
`ModelTokenAuthenticatable` authenticates with a Bearer token. This is what you use to protect most of your endpoints. `ModelAuthenticatable` authenticates with username and password and is used by a single endpoint for generating tokens.
|
||||
|
||||
This guide assumes you are familiar with Fluent and have successfully configured your app to use a database. If you are new to Fluent, start with the [overview](fluent/overview.md).
|
||||
This guide assumes you are familiar with Fluent and have successfully configured your app to use a database. If you are new to Fluent, start with the [overview](../fluent/overview.md).
|
||||
|
||||
### User
|
||||
|
||||
|
|
@ -329,7 +329,7 @@ Don't forget to add the migration to `app.migrations`.
|
|||
app.migrations.add(User.Migration())
|
||||
```
|
||||
|
||||
The first thing you will need is an endpoint to create new users. Let's use `POST /users`. Create a [Content](content.md) struct representing the data this endpoint expects.
|
||||
The first thing you will need is an endpoint to create new users. Let's use `POST /users`. Create a [Content](../basics/content.md) struct representing the data this endpoint expects.
|
||||
|
||||
```swift
|
||||
import Vapor
|
||||
|
|
@ -344,7 +344,7 @@ extension User {
|
|||
}
|
||||
```
|
||||
|
||||
If you like, you can conform this struct to [Validatable](validation.md) to add validation requirements.
|
||||
If you like, you can conform this struct to [Validatable](../basics/validation.md) to add validation requirements.
|
||||
|
||||
```swift
|
||||
import Vapor
|
||||
|
|
@ -468,7 +468,7 @@ final class UserToken: Model, Content {
|
|||
}
|
||||
```
|
||||
|
||||
This model must have a `value` field for storing the token's unique string. It must also have a [parent-relation](fluent/overview.md#parent) to the user model. You may add additional properties to this token as you see fit, such as an expiration date.
|
||||
This model must have a `value` field for storing the token's unique string. It must also have a [parent-relation](../fluent/overview.md#parent) to the user model. You may add additional properties to this token as you see fit, such as an expiration date.
|
||||
|
||||
Next, create a migration for this model.
|
||||
|
||||
|
|
@ -584,7 +584,7 @@ You should see the authenticated `User` returned.
|
|||
|
||||
## Session
|
||||
|
||||
Vapor's [Session API](sessions.md) can be used to automatically persist user authentication between requests. This works by storing a unique identifier for the user in the request's session data after successful login. On subsequent requests, the user's identifier is fetched from the session and used to authenticate the user before calling your route handler.
|
||||
Vapor's [Session API](../advanced/sessions.md) can be used to automatically persist user authentication between requests. This works by storing a unique identifier for the user in the request's session data after successful login. On subsequent requests, the user's identifier is fetched from the session and used to authenticate the user before calling your route handler.
|
||||
|
||||
Sessions are great for front-end web applications built in Vapor that serve HTML directly to web browsers. For APIs, we recommend using stateless, token-based authentication to persist user data between requests.
|
||||
|
||||
|
|
@ -674,7 +674,7 @@ protected.get("me") { req -> String in
|
|||
}
|
||||
```
|
||||
|
||||
`SessionsMiddleware` is added first to enable session support on the application. More information about configuring sessions can be found in the [Session API](sessions.md) section.
|
||||
`SessionsMiddleware` is added first to enable session support on the application. More information about configuring sessions can be found in the [Session API](../advanced/sessions.md) section.
|
||||
|
||||
Next, the `SessionAuthenticator` is added. This handles authenticating the user if a session is active.
|
||||
|
||||
|
|
@ -5,7 +5,7 @@ JSON Web Token (JWT) is an open standard ([RFC 7519](https://tools.ietf.org/html
|
|||
|
||||
## Getting Started
|
||||
|
||||
The first step to using JWT is adding the dependency to your [Package.swift](spm.md#package-manifest).
|
||||
The first step to using JWT is adding the dependency to your [Package.swift](../gettingstarted/spm.md#package-manifest).
|
||||
|
||||
```swift
|
||||
// swift-tools-version:5.2
|
||||
|
|
@ -38,7 +38,8 @@ var newSearchIndex = searchIndex
|
|||
var searchIndexDocs = [SearchIndexDocs]()
|
||||
|
||||
for doc in newSearchIndex.docs {
|
||||
if !doc.location.starts(with: "en/")
|
||||
if !doc.location.starts(with: "en/")
|
||||
&& !doc.location.starts(with: "zh/")
|
||||
&& !doc.location.starts(with: "de/")
|
||||
&& !doc.location.starts(with: "fr/")
|
||||
&& !doc.location.starts(with: "nl/") {
|
||||
|
|
|
|||
105
mkdocs.yml
105
mkdocs.yml
|
|
@ -77,6 +77,10 @@ plugins:
|
|||
en:
|
||||
name: English
|
||||
build: true
|
||||
zh:
|
||||
name: 简体中文
|
||||
site_name: Vapor 中文文档
|
||||
build: true
|
||||
nl:
|
||||
name: Nederlands
|
||||
site_name: Vapor Documentatie
|
||||
|
|
@ -97,6 +101,55 @@ plugins:
|
|||
Welcome: Bienvenue
|
||||
de:
|
||||
Welcome: Willkommen
|
||||
zh:
|
||||
Welcome: 序言
|
||||
Install: 安装
|
||||
Getting Started: 开始
|
||||
Hello, world: 你好世界
|
||||
Folder Structure: 项目结构
|
||||
SPM: SPM
|
||||
Xcode: Xcode
|
||||
Basics: 入门
|
||||
Routing: 路由
|
||||
Content: 内容
|
||||
Client: 客户端
|
||||
Validation: 验证
|
||||
Async: 异步
|
||||
Logging: 日志
|
||||
Environment: 环境
|
||||
Errors: 错误
|
||||
Fluent: Fluent
|
||||
Overview: 概述
|
||||
Model: 模型
|
||||
Relations: 关联
|
||||
Migrations: 迁移
|
||||
Query: 查询
|
||||
Transactions: 事务
|
||||
Schema: 模式
|
||||
Leaf: Leaf
|
||||
Redis: Redis
|
||||
Advanced: 进阶
|
||||
Middleware: 中间件
|
||||
Testing: 测试
|
||||
Server: 服务
|
||||
Files: 文件
|
||||
Commands: 命令
|
||||
Queues: 队列
|
||||
WebSockets: 即时通讯
|
||||
Sessions: 会话
|
||||
Services: 拓展
|
||||
Security: 安全
|
||||
APNS: 推送
|
||||
Deploy: 部署
|
||||
Authentication: 认证
|
||||
Crypto: 加密
|
||||
Passwords: 密码
|
||||
JWT: 认证
|
||||
Version (4.0): 版本
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
nav:
|
||||
|
|
@ -105,22 +158,22 @@ nav:
|
|||
- macOS: "install/macos.md"
|
||||
- Linux: "install/linux.md"
|
||||
- Getting Started:
|
||||
- Hello, world: "hello-world.md"
|
||||
- Folder Structure: "folder-structure.md"
|
||||
- SPM: "spm.md"
|
||||
- Xcode: "xcode.md"
|
||||
- Hello, world: "gettingstarted/hello-world.md"
|
||||
- Folder Structure: "gettingstarted/folder-structure.md"
|
||||
- SPM: "gettingstarted/spm.md"
|
||||
- Xcode: "gettingstarted/xcode.md"
|
||||
- Basics:
|
||||
- Routing: "routing.md"
|
||||
- Routing: "basics/routing.md"
|
||||
# TODO: Improve quality
|
||||
# Mostly just a code sample with little explanation.
|
||||
# - Controllers: "controllers.md"
|
||||
- Content: "content.md"
|
||||
- Client: "client.md"
|
||||
- Validation: "validation.md"
|
||||
- Async: "async.md"
|
||||
- Logging: "logging.md"
|
||||
- Environment: "environment.md"
|
||||
- Errors: "errors.md"
|
||||
- Content: "basics/content.md"
|
||||
- Client: "basics/client.md"
|
||||
- Validation: "basics/validation.md"
|
||||
- Async: "basics/async.md"
|
||||
- Logging: "basics/logging.md"
|
||||
- Environment: "basics/environment.md"
|
||||
- Errors: "basics/errors.md"
|
||||
- Fluent:
|
||||
- Overview: "fluent/overview.md"
|
||||
- Model: "fluent/model.md"
|
||||
|
|
@ -138,21 +191,21 @@ nav:
|
|||
- Overview: "redis/overview.md"
|
||||
- Sessions: "redis/sessions.md"
|
||||
- Advanced:
|
||||
- Middleware: "middleware.md"
|
||||
- Testing: "testing.md"
|
||||
- Server: "server.md"
|
||||
- Files: "files.md"
|
||||
- Commands: "commands.md"
|
||||
- Queues: "queues.md"
|
||||
- WebSockets: "websockets.md"
|
||||
- Sessions: "sessions.md"
|
||||
- Services: "services.md"
|
||||
- APNS: "apns.md"
|
||||
- Middleware: "advanced/middleware.md"
|
||||
- Testing: "advanced/testing.md"
|
||||
- Server: "advanced/server.md"
|
||||
- Files: "advanced/files.md"
|
||||
- Commands: "advanced/commands.md"
|
||||
- Queues: "advanced/queues.md"
|
||||
- WebSockets: "advanced/websockets.md"
|
||||
- Sessions: "advanced/sessions.md"
|
||||
- Services: "advanced/services.md"
|
||||
- APNS: "advanced/apns.md"
|
||||
- Security:
|
||||
- Authentication: "authentication.md"
|
||||
- Crypto: "crypto.md"
|
||||
- Passwords: "passwords.md"
|
||||
- JWT: "jwt.md"
|
||||
- Authentication: "security/authentication.md"
|
||||
- Crypto: "security/crypto.md"
|
||||
- Passwords: "security/passwords.md"
|
||||
- JWT: "security/jwt.md"
|
||||
- Deploy:
|
||||
- DigitalOcean: "deploy/digital-ocean.md"
|
||||
- Heroku: "deploy/heroku.md"
|
||||
|
|
|
|||
Loading…
Reference in New Issue