MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Swift服务器端开发Vapor框架入门

2024-03-036.0k 阅读

Vapor 框架简介

Vapor 是一款基于 Swift 语言的开源、异步、非阻塞的服务器端框架,专为构建现代、高性能的网络应用程序而设计。它汲取了 Swift 语言简洁、安全和强大的特性,为开发者提供了一套便捷且高效的工具集来搭建服务器端应用。

Vapor 框架的设计理念注重开发者体验,其 API 设计简洁直观,易于理解和使用。同时,它充分利用了 Swift 的并发编程模型,能够高效地处理大量并发请求,这使得 Vapor 非常适合构建面向互联网的高流量应用,如 Web 应用、API 服务器等。

Vapor 还提供了丰富的插件生态系统,开发者可以轻松集成数据库访问、身份验证、文件存储等常见功能。这极大地加快了开发速度,使开发者能够专注于业务逻辑的实现,而无需从头编写大量基础代码。

安装 Vapor

在开始使用 Vapor 进行开发之前,需要确保你的开发环境满足一定的条件,并完成 Vapor 的安装。

系统要求

  • 操作系统:Vapor 支持 macOS、Linux 等操作系统。在 macOS 上开发通常比较方便,因为 Swift 开发环境在 macOS 上的安装和配置相对简单。
  • Swift 版本:Vapor 对 Swift 版本有一定要求,建议使用最新稳定版本的 Swift。你可以通过 Swift 官方网站下载并安装最新版本。

安装 Vapor Toolbox

Vapor Toolbox 是一个命令行工具,用于创建、管理和运行 Vapor 项目。可以通过以下步骤安装:

  1. 使用 Homebrew(在 macOS 上): 如果你的 macOS 系统安装了 Homebrew(一款流行的包管理器),可以使用以下命令安装 Vapor Toolbox:
brew install vapor/tap/vapor
  1. 使用 Linuxbrew(在 Linux 上): 对于 Linux 系统,可以使用 Linuxbrew 来安装 Vapor Toolbox。首先安装 Linuxbrew,然后执行类似 macOS 上的安装命令:
brew install vapor/tap/vapor
  1. 手动安装: 如果上述方法不适用,也可以从 Vapor 官方 GitHub 仓库手动下载并安装 Vapor Toolbox。具体步骤可以参考 Vapor 官方文档。

安装完成后,可以通过以下命令验证 Vapor Toolbox 是否安装成功:

vapor --version

如果能正确输出版本号,则说明安装成功。

创建第一个 Vapor 项目

安装好 Vapor Toolbox 后,就可以创建一个新的 Vapor 项目了。

使用 Vapor Toolbox 创建项目

打开终端,进入你希望创建项目的目录,然后执行以下命令:

vapor new MyFirstVaporApp

这里的 MyFirstVaporApp 是项目的名称,你可以根据实际需求进行更改。执行该命令后,Vapor Toolbox 会自动创建一个新的 Vapor 项目模板,包含项目的基本结构和必要的文件。

项目结构

进入新创建的项目目录 MyFirstVaporApp,可以看到以下主要目录和文件:

  • Sources:这个目录包含项目的源代码。其中,App 子目录是应用程序的主要代码所在,包括路由定义、控制器实现等。Run 文件是应用程序的入口,负责启动服务器。
  • Package.swift:这是 Swift 包管理器的配置文件,用于定义项目的依赖关系、目标等信息。
  • Tests:包含项目的测试代码,用于对应用程序的功能进行单元测试和集成测试。

运行项目

在项目目录中,执行以下命令可以运行 Vapor 项目:

vapor run

默认情况下,Vapor 会在 http://localhost:8080 启动一个服务器。打开浏览器,访问 http://localhost:8080,你应该能看到 Vapor 提供的默认欢迎页面,这表明你的第一个 Vapor 项目已经成功运行起来了。

路由与控制器

路由和控制器是构建 Web 应用的核心概念,Vapor 提供了简洁而强大的方式来定义路由和实现控制器。

路由定义

路由定义了客户端请求的 URL 与服务器端处理函数之间的映射关系。在 Vapor 中,路由定义通常在 Sources/App/routes.swift 文件中进行。

例如,要定义一个简单的 GET 请求路由,返回 “Hello, Vapor!”,可以这样写:

import Vapor

func routes(_ app: Application) throws {
    app.get { req in
        return "Hello, Vapor!"
    }
}

在上述代码中,app.get 表示定义一个 GET 请求的路由。花括号内的闭包是处理该请求的函数,req 是请求对象,在这里我们直接返回一个字符串作为响应。

如果要定义一个带参数的路由,可以这样:

app.get("users", ":id") { req in
    guard let userId = req.parameters.get("id", as: Int.self) else {
        throw Abort(.badRequest)
    }
    return "User with ID \(userId)"
}

这里的 :id 是一个参数占位符,req.parameters.get("id", as: Int.self) 用于获取参数值,并尝试将其转换为 Int 类型。如果参数获取失败,抛出一个 400 Bad Request 的错误。

控制器

控制器是处理业务逻辑的地方,将路由的处理逻辑封装在控制器中可以使代码结构更清晰,便于维护和扩展。

首先,在 Sources/App/Controllers 目录下创建一个控制器文件,例如 UserController.swift

import Vapor

struct UserController {
    func getUsers(req: Request) throws -> EventLoopFuture<[User]> {
        // 这里可以编写从数据库获取用户列表的逻辑
        let users: [User] = []
        return req.eventLoop.future(users)
    }
}

这里定义了一个 UserController 结构体,其中 getUsers 方法用于处理获取用户列表的请求。假设 User 是一个自定义的用户模型。

然后,在 routes.swift 文件中关联路由和控制器:

import Vapor

func routes(_ app: Application) throws {
    let userController = UserController()
    app.get("users") { req in
        try userController.getUsers(req: req)
    }
}

这样,当客户端发送一个 GET 请求到 /users 时,就会调用 UserControllergetUsers 方法来处理请求。

处理 HTTP 请求与响应

Vapor 提供了丰富的功能来处理 HTTP 请求和生成响应。

处理请求数据

对于不同类型的请求(如 GET、POST、PUT 等),Vapor 提供了相应的方式来获取请求中的数据。

GET 请求参数: 如前面提到的,对于 GET 请求中的路径参数,可以通过 req.parameters 获取。对于查询字符串参数,可以这样获取:

app.get("search") { req in
    guard let query = req.query.get("q") else {
        throw Abort(.badRequest)
    }
    // 根据查询参数进行搜索逻辑
    return "Search results for \(query)"
}

这里通过 req.query.get("q") 获取名为 q 的查询字符串参数。

POST 请求体: 对于 POST 请求体中的数据,如果是 JSON 格式,可以这样解析:

struct LoginRequest: Content {
    var username: String
    var password: String
}

app.post("login") { req in
    let loginRequest = try req.content.decode(LoginRequest.self)
    // 验证用户名和密码逻辑
    return "Login successful for \(loginRequest.username)"
}

首先定义一个符合 Content 协议的结构体 LoginRequest,用于表示登录请求的数据结构。然后通过 req.content.decode(LoginRequest.self) 解析请求体中的 JSON 数据。

生成响应

Vapor 可以生成多种类型的响应,如文本、JSON、HTML 等。

文本响应: 前面已经展示过简单的文本响应,直接返回一个字符串即可:

app.get { req in
    return "This is a text response"
}

JSON 响应: 假设我们有一个 User 结构体:

struct User: Content {
    var id: Int
    var name: String
}

app.get("user") { req in
    let user = User(id: 1, name: "John")
    return user
}

由于 User 结构体符合 Content 协议,Vapor 会自动将其转换为 JSON 格式的响应。

HTML 响应: 要生成 HTML 响应,需要先设置模板引擎。Vapor 支持多种模板引擎,如 Leaf。首先安装 Leaf 依赖,在 Package.swift 文件中添加:

.package(url: "https://github.com/vapor/leaf.git", from: "4.0.0"),

然后在 targets 中添加依赖:

.target(
    name: "App",
    dependencies: [
       .product(name: "Leaf", package: "leaf")
    ]
),

Sources/App/views 目录下创建一个 HTML 模板文件,例如 index.leaf

<!DOCTYPE html>
<html>
<head>
    <title>My Vapor App</title>
</head>
<body>
    <h1>Welcome to my Vapor app!</h1>
</body>
</html>

routes.swift 文件中使用 Leaf 模板生成 HTML 响应:

import Vapor
import Leaf

func routes(_ app: Application) throws {
    app.get { req in
        return try req.view.render("index")
    }
}

这样,当客户端访问根路径时,会返回渲染后的 HTML 页面。

数据库操作

在实际应用开发中,数据库操作是必不可少的。Vapor 提供了对多种数据库的支持,如 SQLite、MySQL、PostgreSQL 等。这里以 SQLite 为例介绍数据库操作。

配置数据库

首先,在 Package.swift 文件中添加 SQLite 驱动依赖:

.package(url: "https://github.com/vapor/sqlite.git", from: "4.0.0"),

然后在 targets 中添加依赖:

.target(
    name: "App",
    dependencies: [
       .product(name: "SQLite", package: "sqlite")
    ]
),

config/database.swift 文件中配置 SQLite 数据库:

import Vapor
import SQLite

/// Configures your application's databases.
public func configureDatabases(_ app: Application) throws {
    app.databases.use(.sqlite(.file("db.sqlite")), as:.sqlite)
}

这里配置了一个 SQLite 数据库,数据库文件名为 db.sqlite

创建数据库表

定义一个数据库迁移来创建表。在 App/Migrations 目录下创建一个迁移文件,例如 CreateUsers.swift

import Vapor
import SQLite

struct CreateUsers: Migration {
    func prepare(on database: Database) -> EventLoopFuture<Void> {
        return database.schema("users")
           .id()
           .field("name",.string, required: true)
           .create()
    }

    func revert(on database: Database) -> EventLoopFuture<Void> {
        return database.schema("users").delete()
    }
}

然后在 main.swift 文件中注册迁移:

import Vapor

@main
public struct AppMain {
    public static func main() async throws {
        let app = Application(.detect())
        try await app.boot()

        try app.migrations.add(CreateUsers())
        try await app.autoMigrate().wait()

        defer {
            try? await app.shutdown()
        }

        try await app.run()
    }
}

这样,当应用启动时,会自动创建 users 表。

数据库增删改查操作

插入数据

struct User: Content, SQLiteModel {
    static let schema = "users"

    @ID(key:.id)
    var id: UUID?
    @Field(key: "name")
    var name: String

    init() {}

    init(id: UUID? = nil, name: String) {
        self.id = id
        self.name = name
    }
}

app.post("users") { req in
    let user = try req.content.decode(User.self)
    return user.save(on: req.db).map {
        return user
    }
}

这里定义了一个 User 结构体,符合 SQLiteModelContent 协议。通过 user.save(on: req.db) 将用户数据插入到数据库。

查询数据

app.get("users") { req in
    return User.query(on: req.db).all()
}

通过 User.query(on: req.db).all() 查询所有用户数据。

更新数据

app.put("users", ":id") { req in
    guard let userId = req.parameters.get("id", as: UUID.self) else {
        throw Abort(.badRequest)
    }
    let updatedUser = try req.content.decode(User.self)
    return User.find(userId, on: req.db)
      .unwrap(or: Abort(.notFound))
      .flatMap { user in
            user.name = updatedUser.name
            return user.save(on: req.db).map {
                return user
            }
        }
}

这里先根据 URL 参数获取要更新的用户 ID,然后解析请求体中的更新数据,找到用户并更新其名称。

删除数据

app.delete("users", ":id") { req in
    guard let userId = req.parameters.get("id", as: UUID.self) else {
        throw Abort(.badRequest)
    }
    return User.find(userId, on: req.db)
      .unwrap(or: Abort(.notFound))
      .flatMap { user in
            return user.delete(on: req.db).transform(to: HTTPStatus.noContent)
        }
}

通过 user.delete(on: req.db) 删除指定 ID 的用户。

中间件

中间件是在请求到达路由处理函数之前或之后执行的代码。Vapor 提供了灵活的中间件机制,用于实现诸如日志记录、身份验证、错误处理等功能。

自定义中间件

创建一个自定义中间件非常简单。例如,创建一个用于记录请求时间的中间件:

struct RequestTimeMiddleware: Middleware {
    func respond(to request: Request, chainingTo next: Responder) -> EventLoopFuture<Response> {
        let startTime = Date()
        return next.respond(to: request).map { response in
            let endTime = Date()
            let elapsedTime = endTime.timeIntervalSince(startTime)
            print("Request took \(elapsedTime) seconds")
            return response
        }
    }
}

然后在 Application 中注册这个中间件:

func configure(_ app: Application) throws {
    app.middleware.use(RequestTimeMiddleware())
    // 其他配置和路由定义
}

这样,每次请求都会记录其处理时间。

内置中间件

Vapor 还提供了一些内置中间件,如 ErrorMiddleware 用于处理错误,LogMiddleware 用于记录请求日志等。

例如,启用 LogMiddleware

func configure(_ app: Application) throws {
    app.middleware.use(LogMiddleware())
    // 其他配置和路由定义
}

启用后,每次请求的相关信息(如请求方法、URL、响应状态码等)都会被记录下来。

身份验证与授权

在实际应用中,身份验证和授权是保护应用资源的重要手段。Vapor 提供了多种方式来实现身份验证和授权。

基于令牌的身份验证

以 JWT(JSON Web Token)为例,首先安装 JWT 相关依赖,在 Package.swift 文件中添加:

.package(url: "https://github.com/vapor/jwt.git", from: "4.0.0"),

然后在 targets 中添加依赖:

.target(
    name: "App",
    dependencies: [
       .product(name: "JWT", package: "jwt")
    ]
),

创建一个用于生成和验证 JWT 的服务。在 App/Services 目录下创建 JwtService.swift

import Vapor
import JWT

struct JwtService {
    let key: JWK

    init(key: JWK) {
        self.key = key
    }

    func generateToken(for user: User) throws -> String {
        let claims = UserClaims(user: user)
        let token = try JWTEncoder().encode(claims, using: key)
        return token.serialized
    }

    func verifyToken(_ token: String) throws -> UserClaims {
        let decoded = try JWTDecoder().decode(UserClaims.self, from: token, using: key)
        return decoded
    }
}

struct UserClaims: Claims {
    let sub: String
    let exp: ExpirationClaim
    let user: User

    init(user: User) {
        self.sub = user.id!.uuidString
        self.exp = ExpirationClaim(value: Date().addingTimeInterval(3600))
        self.user = user
    }
}

routes.swift 文件中实现登录路由,生成 JWT:

func routes(_ app: Application) throws {
    let jwtService = try app.make(JwtService.self)
    app.post("login") { req in
        let loginRequest = try req.content.decode(LoginRequest.self)
        // 验证用户名和密码
        let user = User(id: UUID(), name: loginRequest.username)
        let token = try jwtService.generateToken(for: user)
        return ["token": token]
    }
}

对于需要身份验证的路由,可以创建一个中间件来验证 JWT:

struct JwtAuthMiddleware: Middleware {
    let jwtService: JwtService

    init(jwtService: JwtService) {
        self.jwtService = jwtService
    }

    func respond(to request: Request, chainingTo next: Responder) -> EventLoopFuture<Response> {
        guard let token = request.headers.bearerAuthorization?.token else {
            throw Abort(.unauthorized)
        }
        do {
            _ = try jwtService.verifyToken(token)
            return next.respond(to: request)
        } catch {
            throw Abort(.unauthorized)
        }
    }
}

然后在需要身份验证的路由上使用这个中间件:

func routes(_ app: Application) throws {
    let jwtService = try app.make(JwtService.self)
    let jwtAuthMiddleware = JwtAuthMiddleware(jwtService: jwtService)
    app.group("protected") { group in
        group.middleware.use(jwtAuthMiddleware)
        group.get { req in
            return "This is a protected route"
        }
    }
}

这样,只有提供了有效 JWT 的请求才能访问 /protected 路由下的资源。

授权

授权是在身份验证的基础上,确定用户是否有权限访问特定资源。可以通过在中间件或控制器中添加逻辑来实现授权。

例如,假设用户有不同的角色(如管理员、普通用户),只有管理员才能删除用户:

struct AdminOnlyMiddleware: Middleware {
    let jwtService: JwtService

    init(jwtService: JwtService) {
        self.jwtService = jwtService
    }

    func respond(to request: Request, chainingTo next: Responder) -> EventLoopFuture<Response> {
        guard let token = request.headers.bearerAuthorization?.token else {
            throw Abort(.unauthorized)
        }
        do {
            let claims = try jwtService.verifyToken(token)
            if claims.user.role!= "admin" {
                throw Abort(.forbidden)
            }
            return next.respond(to: request)
        } catch {
            throw Abort(.unauthorized)
        }
    }
}

在删除用户的路由上使用这个中间件:

func routes(_ app: Application) throws {
    let jwtService = try app.make(JwtService.self)
    let adminOnlyMiddleware = AdminOnlyMiddleware(jwtService: jwtService)
    app.delete("users", ":id") { req in
        // 删除用户逻辑
    }.middleware(using: adminOnlyMiddleware)
}

这样,只有管理员角色的用户才能执行删除用户的操作。

通过以上步骤,你已经对 Vapor 框架的基础功能有了深入了解,包括路由、控制器、请求响应处理、数据库操作、中间件以及身份验证和授权等方面。这将为你构建复杂的服务器端应用奠定坚实的基础。随着对 Vapor 框架的进一步探索和实践,你可以利用其更多高级特性和插件来开发出功能强大、性能卓越的网络应用程序。