HTTP简介

HTTP基础结构

HTTP恳求体

HTTP 加载恳求

HTTP 模仿测验

HTTP 链式加载器

HTTP 动态修正恳求

HTTP 恳求选项

HTTP 重置

HTTP 撤销

HTTP 限流

HTTP 重试

HTTP 基础鉴权

HTTP 自动鉴权设置

HTTP 自动鉴权

HTTP 复合加载器

HTTP 头脑风暴

HTTP 总结

在这篇文章中,咱们将创立一个 HTTPLoader 子类,它答应咱们动态修正恳求。

咱们已经看到 HTTPLoader 接口有十分宽松的要求“一个恳求进来,一个完结块被执行”。 咱们还看到了如何将 API 职责的一部分托付给其他加载器,从而答应咱们创立加载器“链”。

让咱们细心看看上次声明的 PrintLoader

public class PrintLoader: HTTPLoader {
    override func load(request: HTTPRequest, completion: @escaping (HTTPResult) -> Void) {
        print("Loading (request)")
        super.load(request: request, completion: { result in
            print("Got result: (result)")
            completion(result)
        })
    }
}

敏锐的观察者会看到咱们在将完结块发送到下一个加载器之前修正了它。 是的,咱们仍在调用原始的完结处理程序,但发送的闭包在技术上与咱们收到的闭包不同。 相反,咱们创立了一个新的闭包并运用了那个闭包。

相同的主意也可以应用于恳求。 HTTPLoader 接口没有任何关于强制咱们收到的恳求有必要与咱们发送的恳求相同的要求。 咱们可以彻底自由地修正(甚至替换)恳求,然后再将其发送到链下。 让咱们运行这个主意,看看它能把咱们带到哪里。

修正恳求可以被认为是一个专门的“映射”函数,其中输入和输出类型都是 HTTPRequest 值。 将其建模为应用于每个恳求的闭包似乎是一种自然的方式,运用它的加载器或许如下所示:

public class ModifyRequest: HTTPLoader {
    private let modifier: (HTTPRequest) -> HTTPRequest
    public init(modifier: @escaping (HTTPRequest) -> HTTPRequest) {
        self.modifier = modifier
        super.init()
    }
    override public func load(request: HTTPRequest, completion: @escaping (HTTPResult) -> Void) {
        let modifiedRequest = modifier(request)
        super.load(request: modifiedRequest, completion: completion)
    }
}

为什么这很有用或许不是很明显,所以让咱们回想一下咱们的 StarWarsAPI 类:

public class StarWarsAPI {
    private let loader: HTTPLoader
    public init(loader: HTTPLoader) {
        self.loader = loader
    }
    public func requestPeople(completion: @escaping (...) -> Void) {
        var r = HTTPRequest()
        r.host = "swapi.dev"
        r.path = "/api/people"
        loader.load(request: r) { result in
            // TODO: interpret the result
            completion(...)
        }
    }
}

目前这十分简略,但您可以幻想,假如咱们增加更多办法来恳求飞船、行星或机器人(尤其是咱们正在寻找的机器人)或武器,那么咱们终究会得到许多重复的 代码。 具体来说,咱们会一遍又一遍地看到这段代码:

var r = HTTPRequest()
r.host = "swapi.dev"
r.path = "/api/..."

因为咱们现在有办法修正飞行中的恳求,所以咱们不需要在任何地方重复这段代码。 让咱们修正初始化程序来构建一个加载链:

public init(loader: HTTPLoader) {
    let modifier = ModifyRequest { request in
        var copy = request
        if copy.host.isEmpty { 
            copy.host = "swapi.dev"
        }
        if copy.path.hasPrefix("/") == false {
            copy.path = "/api/" + copy.path
        }
        return copy
    }
    self.loader = modifier --> loader
}

咱们的 ModifyRequest 加载器将查看传入的恳求并填写缺失的信息。 假如恳求没有主机,它将供给一个。 假如恳求的 .path 不是绝对途径,它会为咱们增加一个默许途径。

现在,咱们的 requestPeople 办法可以变得更简略:

public func requestPeople(completion: @escaping (...) -> Void) {
    loader.load(request: HTTPRequest(path: "people")) { result in
        // TODO: interpret the result
        completion(...)
    }
}

随着咱们增加更多的request...办法,每个办法的实现将只包含该实现独有的部分。 填写恳求所需的一切通用样板文件都将由 ModifyRequest 加载程序处理。

服务器环境

咱们可以经过形式化某些类型的恳求修正来更进一步。 咱们上面显现的是一般的“更改任何内容”修正。 咱们倾向于对恳求进行的最常见的一种修正会集在针对不同的后端。 咱们将拥有一组用于开发的服务器,另一组用于测验,另一组用于暂存到上线,然后是出产服务器。

可以指定这些“环境”的各种细节而不用直接对转换进行硬编码十分有用。 ServerEnvironment 结构或许看起来像这样:

public struct ServerEnvironment {
    public var host: String
    public var pathPrefix: String
    public var headers: [String: String]
    public var query: [URLQueryItem]
    public init(host: String, pathPrefix: String = "/", headers: [String: String] = [:], query: [URLQueryItem] = []) {
        // make sure the pathPrefix starts with a /
        let prefix = pathPrefix.hasPrefix("/") ? "" : "/"
        self.host = host
        self.pathPrefix = prefix + pathPrefix
        self.headers = headers
        self.query = query
    }
}

运用这样的结构,咱们可以预先定义众所周知的服务器环境,或运用动态检索的环境列表,或两者兼而有之。

extension ServerEnvironment {
    public static let development = ServerEnvironment(host: "development.example.com", pathPrefix: "/api-dev")
    public static let qa = ServerEnvironment(host: "qa-1.example.com", pathPrefix: "/api")
    public static let staging = ServerEnvironment(host: "api-staging.example.com", pathPrefix: "/api")
    public static let production = ServerEnvironment(host: "api.example.com", pathPrefix: "/api")
}

将此 ServerEnvironment 值应用于 HTTPRequest 是对上述闭包的扩展。 咱们将测验传入 HTTPRequest 的各个部分并填写缺失的部分。 咱们运用它的加载器看起来像这样:

public class ApplyEnvironment: HTTPLoader {
    private let environment: ServerEnvironment
    public init(environment: ServerEnvironment) {
        environment = environment
        super.init()
    }
    override public func load(request: HTTPRequest, completion: @escaping (HTTPResult) -> Void) {
        var copy = request
        if copy.host.isEmpty { 
            copy.host = environment.host
        }
        if copy.path.hasPrefix("/") == false {
            // TODO: apply the environment.pathPrefix
        }
        // TODO: apply the query items from the environment
        for (header, value) in environment.headers {
            // TODO: add these header values to the request
        }
        super.load(request: copy, completion: completion)
    }
}

在恳求经过链时修正恳求是一个十分强壮的东西。 咱们可以用它来填充标头(User-Agent?每个恳求的标识符?),检查和更新主体,将恳求重定向到其他地方等等。 咱们将在今后的帖子中看到更多。

在咱们的下一篇文章中,咱们将构建一个功用,答应单个恳求供给自定义的每个恳求“选项”,以改变它们的加载行为,使其远离链的默许行为。