Vapor(二)进阶篇

  1. Service
  2. Client
  3. Content
  4. Session

经过了解基础的概念后,可以再深一步来了解这些概念存在的意义和扩展的使用方法,包括Service、Client、Content、Session。

Service

Service是一个依赖注入的框架,以可维护的方式来进行注册、配置和创建应用依赖。其实Vapor里的这个Service的概念有点像iOS里面组件化的解耦方案,使用路由或者协议建立起一个调度中心,去给有功能调用需求的地方分发服务。那应用到的组件就不会散落在项目的各处,同时可以针对自己需要的情景、功能来配置、选择对应的服务。

注册服务

首先遵从一个空的协议Service(拿PrintLogger来举例)

extension PrintLogger: Service { }

然后在configure.swift将实现服务的类注册到Services结构体中,使用工厂方法动态地创建服务

services.register(Logger.self) { container in
    return PrintLogger()
}

另外,遵从并实现ServiceType协议后可以更简单地注册服务

extension PrintLogger: ServiceType {
    /// See `ServiceType`.
    static var serviceSupports: [Any.Type] {
        return [Logger.self]
    }

    /// See `ServiceType`.
    static func makeService(for worker: Container) throws -> PrintLogger {
        return PrintLogger()
    }
}

//In configure.swift
services.register(PrintLogger.self)

甚至可以指定注册的实例对象

services.register(PrintLogger(), as: Logger.self)

注意,如果使用引用类型(class)来注册服务的话,那么所有容器Container和子容器SubContainer都会共享这一个类型的这一个对象,同时要小心资源竞争的问题。

配置服务

如果同一个接口注册了多个服务,则需要指定优先的选择

services.register(PrintLogger.self)
services.register(FileLogger.self)

switch env {
case .production: config.prefer(FileLogger.self, for: Logger.self)
default: config.prefer(PrintLogger.self, for: Logger.self)
}

当然,通过环境变量就能动态地决定注册的服务,而无需使用到配置(config)。

创建服务

当你注册过服务,那么就可以用容器Container来创建服务

let logger = try someContainer.make(Logger.self)
logger.log("Hello, world!")

// PrintLogger or FileLogger depending on the container's environment
print(type(of: logger))

Provider

通过Provider协议可以更简单地整合外部服务到当前应用。像所有的Vapor官方包都使用它来展示它们的服务。Provider可以用来注册到Services结构体中,可以勾进容器的生命周期。

import Foo

try services.register(FooProvider())

Vapor的第三方库一般都会选择实现它来组织代码

public final class LoggerProvider: Provider {
    /// See `Provider`.
    public func register(_ services: inout Services) throws {
        services.register(PrintLogger.self)
        services.register(FileLogger.self)
    }

    /// See `Provider`.
    public func didBoot(_ container: Container) throws -> Future<Void> {
        let logger = try container.make(Logger.self)
        logger.log("Hello from LoggerProvider!")
        return .done(on: container)
    }
}

实现Provider协议后,当注册LoggerProvider到应用的Services结构体中,它就会自动注册上面的两个服务。同时当容器启动的时候,就可以验证provider是否已经添加(注册服务)了。

Client

作为客户端使用时,首先需要一个服务容器(Container)来创建客户端。通常如果像进入你服务器的请求结果一样请求外部API,你就应该使用请求容器来创建一个客户端

let res = try req.client().get("http://vapor.codes")
print(res) // Future<Response>

只要容器(app、req)就能创建客户端

// Creates a generic Client
let client = try container.client()

发送请求

let req: Request ...
let res = try client.send(req)
print(res) // Future<Response>

let user: User ...
let res = try client.post("http://api.vapor.codes/users") { post in
    try post.content.encode(user)
}
print(res) // Future<Response>

Content

有对应的编码器或解码器指定,模型才会按特定的序列形式在HTTP上进行通讯。

因为所有的HTTP请求都必须包含content type,所以Vapor能根据这自动选择合适的编码器或者报错。同时也可以在应用的配置设定Vapor默认的编码器和解码器

/// Create default content config
var contentConfig = ContentConfig.default()

/// Create custom JSON encoder
var jsonEncoder = JSONEncoder()
jsonEncoder.dateEncodingStrategy = .millisecondsSince1970

/// Register JSON encoder and content config
contentConfig.use(encoder: jsonEncoder, for: .json)
services.register(contentConfig)

便利解码
为了更方便解码HTTP请求,Vapor提供了扩展的Router方法

//原始方法
router.post("login") { req -> Future<HTTPStatus> in
    return req.content.decode(LoginRequest.self).map(to: HTTPStatus.self) { loginRequest in
        print(loginRequest.email) // user@vapor.codes
        print(loginRequest.password) // don't look!
        return .ok
    }
}
//效果同上的便利方法
router.post(LoginRequest.self, at: "login") { req, loginRequest in
    print(loginRequest.email) // user@vapor.codes
    print(loginRequest.password) // don't look!
    return HTTPStatus.ok
}

类型检测

自定义解码器和编码器(默认情况下都是会使用JSON解码器或者编码器)

let user = try req.content.decode(User.self, using: JSONDecoder())
print(user) // Future<User>

try res.content.encode(user, as: .urlEncodedForm)

响应默认返回的是200 OK的状态码,也可以进行自定义

//默认
router.get("user") { req -> User in
    return User(name: "Vapor User", email: "user@vapor.codes")
}
//自定义
router.get("user") { req -> Future<Response> in
    return User(name: "Vapor User", email: "user@vapor.codes").encode(status: .created)
}
//或者自定义
struct User: Content {
    /// See `Content`.
    static let defaultContentType: MediaType = .urlEncodedForm
    ...
}

客户端

客户端的HTTP编码就像服务端返回的HTTP响应编码一样。

//编码后的请求
POST /login HTTP/1.1
Host: api.vapor.codes
Content-Type: application/json
{
    "email": "user@vapor.codes",
    "password": "don't look!"
}

//发起编码的模型
import Vapor
struct LoginRequest: Content {
    var email: String
    var password: String
}

//编码HTTP请求
let loginRequest = LoginRequest(email: "user@vapor.codes", password: "don't look!")
let res = try req.client().post("https://api.vapor.codes/login") { loginReq in
    // encode the loginRequest before sending
    try loginReq.content.encode(loginRequest)
}
print(res) // Future<Response>
//解码前的响应
HTTP/1.1 200 OK
Content-Type: application/json
{
    "name": "Vapor User",
    "email": "user@vapor.codes"
}

//接收解码的模型
import Vapor
struct User: Content {
    var name: String
    var email: String
}

//解码HTTP响应
let res: Future<Response> // from the Client
let user = res.flatMap { try $0.content.decode(User.self) }
print(user) // Future<User>

整个客户端的请求和接收响应的示例

// Create the LoginRequest data
let loginRequest = LoginRequest(email: "user@vapor.codes", password: "don't look!")
// POST /login
let user = try req.client().post("https://api.vapor.codes/login") { loginReq in 
    // Encode Content before Request is sent
    return try loginReq.content.encode(loginRequest) 
}.flatMap { loginRes in
    // Decode Content after Response is received
    return try loginRes.content.decode(User.self) 
}
print(user) // Future<User>

Query String
当明确请求使用的编码是URL-Encoded Form时,可以直接使用所有Request都包含有的QueryContainer来解码Query String,对编码也一样

struct Flags: Content {
     var search: String?
     var isAdmin: Bool?
}

let flags = try req.query.decode(Flags.self)
print(flags) // Flags

let flags: Flags ...
try req.query.encode(flags)

便利JSON
由于有部分数据的编码没有规范标准,所以Vapor让自定义的JSON解码器更方便地处理这些数据

// Conforms to Encodable
let user: User ... 
// Encode JSON using custom date encoding strategy
try req.content.encode(json: user, using: .custom(dates: .millisecondsSince1970))

// Decode JSON using custom date encoding strategy
let user = try req.content.decode(json: User.self, using: .custom(dates: .millisecondsSince1970))

动态属性
为响应添加属性这样的处理,即是处理Content,是完全基于Codable的。

Session

session 主要是维护客户端的连接状态,其通过为每个客户端创建唯一标识,并要求客户端在每一次请求中提供这个标识。这个标识可以利用任何的格式传递,但基本上都是使用cookies来完成。

当一个新客户端的连接和session数据设置后,Vapor返回一个用于设置Cookieheader的值,然后客户端就会被要求在每次请求的Cookieheader上重复返回该值,所有浏览器都会自动完成这个流程。如果你想让session失效,Vapor就会删除相关的所有数据并通知客户端它们的cookie已经不再有效。

要实现session的功能,首先要将中间件MiddlewareConfig配置到全局

//In configure.swift.
var middlewares = MiddlewareConfig.default()
middlewares.use(SessionsMiddleware.self)
services.register(middlewares)

然后在每个路由中使用grouped(...)方法

// create a grouped router at /sessions w/ sessions enabled
let sessions = router.grouped("sessions").grouped(SessionsMiddleware.self)

// create a route at GET /sessions/foo
sessions.get("foo") { req in
    // use sessions
}

Vapor 默认下会将sessions保持在内存,也可以在配置中重写这个形式。还能使用Fluent的数据库或缓存来持久化sessions。

当中间件生效后,就可以使用req.session()去访问

// create a route at GET /sessions/get
sessions.get("get") { req -> String in
    // access "name" from session or return n/a
    return try req.session()["name"] ?? "n/a"
}

// create a route at GET /sessions/set/:name
sessions.get("set", String.parameter) { req -> String in
    // get router param
    let name = try req.parameters.next(String.self)

    // set name to session at key "name"
    try req.session()["name"] = name

    // return the newly set name
    return name
}

// create a route at GET /sessions/del
sessions.get("del") { req -> String in
    // destroy the session
    try req.destroySession()

    // signal success
    return "done"
}

转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 mingfungliu@gmail.com

文章标题:Vapor(二)进阶篇

文章字数:2.2k

本文作者:Mingfung

发布时间:2018-08-14, 21:20:00

最后更新:2018-08-28, 21:09:07

原始链接:http://blog.ifungfay.com/后端/Vapor(二)进阶篇/

版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。

目录
×

喜欢就点赞,疼爱就打赏

宝贝回家