HTTP简介

HTTP基础结构

HTTP恳求体

HTTP 加载恳求

HTTP 模仿测试

HTTP 链式加载器

HTTP 动态修正恳求

HTTP 恳求选项

HTTP 重置

HTTP 撤销

HTTP 限流

HTTP 重试

HTTP 基础鉴权

HTTP 自动鉴权设置

HTTP 自动鉴权

HTTP 复合加载器

HTTP 脑筋风暴

HTTP 总结

到现在为止,咱们现已编写了足够多的代码来描绘 HTTPLoader 实例链,这些实例能够处理传入的 HTTPRequest 并最终生成 HTTPResult

然而,在某些情况下,咱们不希望每个恳求都以相同的办法加载。 上次咱们编写了 ApplyEnvironment,这是一个 HTTPLoader 子类,它将运用预界说的 ServerEnvironment 值来填充恳求中的任何缺失值。 咱们将以此作为咱们的事例研究。

让咱们幻想一下,咱们决定用星球大战 wiki“Wookieepedia”中的附加信息来补充咱们的 StarWarsAPI。 当然,咱们知道咱们能够手动设置主机和途径以及每个恳求发出时的所有内容,但最好不要这样做,你值得拥有夸姣的事物。

// it would be unfortunate to have to repeat this a lot
var request = HTTPRequest()
request.host = "starwars.fandom.com"
request.path = "/api/v1/Search/List"
request.queryItems = [
    URLQueryItem(name: "query", value: "anakin")
]

相反,让咱们增加在恳求进入链之前指定整个环境的才能,然后教 ApplyEnvironment 加载器寻找它。 或许它看起来像这样:

var request = HTTPRequest()
request.serverEnvironment = .wookieepedia
request.path = "Search/List"
request.queryItems = [
    URLQueryItem(name: "query", value: "anakin")
]

这看起来简直和曾经一样多的代码,但我信任它更具表现力。 咱们正在删去更多的魔法字符串(“starwars.fandom.com”和“/api/v1”)并在咱们的意图中更具描绘性(“咱们想要‘Wookieepedia’服务器环境”)。

咱们不想做的是回到咱们的 HTTPRequest 界说并为服务器环境增加一个新的存储特点。 并非每个恳求都需求指定一个服务器环境,并且在每个恳求上都为一个服务器环境腾出空间是一种糟蹋。 此外,假如咱们决定要指定其他每个恳求选项,则该办法不能很好地扩展。 (提示:有!)

相反,咱们将在恳求中界说一个私有转储场来存储这些选项,并为它们创建一个类型安全的接口。

SwiftUI 中罗致创意

AppleSwiftUI 结构中有一个名为 PreferenceKey 的简练小协议。 它基本上是视图在其父视图层次结构中传递类型安全的“首选项值”的一种办法,因此某些先人能够查找并读取它。

咱们将为咱们的恳求运用同样的东西。 咱们将从一个协议开端:

public protocol HTTPRequestOption {
    associatedtype Value
    /// The value to use if a request does not provide a customized value
    static var defaultOptionValue: Value { get }
}

该协议表示“选项”只是一种具有静态 defaultOptionValue 特点的类型,假如恳求未指定,咱们能够运用该特点。

接下来,咱们将教授 HTTPRequest 有关选项的知识:

public struct HTTPRequest {
    ...
    private var options = [ObjectIdentifier: Any]()
    public subscript<O: HTTPRequestOption>(option type: O.Type) -> O.Value {
        get {
            // create the unique identifier for this type as our lookup key
            let id = ObjectIdentifier(type)
            // pull out any specified value from the options dictionary, if it's the right type
            // if it's missing or the wrong type, return the defaultOptionValue
            guard let value = options[id] as? O.Value else { return type.defaultOptionValue }
            // return the value from the options dictionary
            return value
        }
        set {
            let id = ObjectIdentifier(type)
            // save the specified value into the options dictionary
            options[id] = newValue
        }
    }
}

这是持有期权价值的基础设施。 现在假定恳求能够专门保存 ServerEnvironment 值:

public struct ServerEnvironment: HTTPRequestOption {
    // the associated type is inferred to be "Optional<ServerEnvironment>"
    public static let defaultOptionValue: ServerEnvironment? = nil
    ...
}

咱们的 ServerEnvironment 结构,它包含默许主机、途径前缀等值,也是一个 HTTPRequestOption。 假如咱们没有在恳求上设置清晰的 ServerEnvironment,那么恳求“持有”的值是 nil(默许选项值),意思是“没有自界说的服务器环境”。

咱们能够增加的一件好事是对 HTTPRequest 的扩展,以使其更易于运用:

extension HTTPRequest {
    public var serverEnvironment: ServerEnvironment? {
        get { self[option: ServerEnvironment.self] }
        set { self[option: ServerEnvironment.self] = newValue }
    }
}

有了这个,咱们现在有办法在单个 HTTPRequest 上设置恣意数量的自界说值,并以类型安全的办法再次检索它们。

运用选项值

剩下的最后一件事是教您的ApplyEnvironment程序如何寻找要运用的环境。 假如您还记得,该类现在看起来像这样:

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)
    }
}

咱们只需求对 load(request:completion:) 办法做一个简单的调整:

    override public func load(request: HTTPRequest, completion: @escaping (HTTPResult) -> Void) {
        var copy = request
        // use the environment specified by the request, if it's present
        // if it doesn't have one, use the one passed to the initializer
        let requestEnvironment = request.serverEnvironment ?? environment
        if copy.host.isEmpty { 
            copy.host = requestEnvironment.host
        }
        if copy.path.hasPrefix("/") == false {
            // TODO: apply the requestEnvironment.pathPrefix
        }
        // TODO: apply the query items from the requestEnvironment
        for (header, value) in requestEnvironment.headers {
            // TODO: add these header values to the request
        }
        super.load(request: copy, completion: completion)
    }

就是这样!

咱们现在为咱们增加了一种通过声明选项来自界说单个恳求行为的办法:一个类型安全的值,它随恳求一起带着并由各种加载器查看,以便它们能够针对该特定恳求动态改动它们的行为。

在以后的帖子中,咱们将运用选项来自界说多种行为,包括指定应如何重试恳求、它们的身份验证机制是什么(假如有)、应如何缓存响应(假如有)等等。

在咱们的下一篇文章中,咱们将从自界说加载器完成中退一步,看看“重置”加载器的概念。