Swift中的HTTP(二) 基础结构

182 阅读6分钟

HTTP简介

HTTP基础结构

HTTP请求体

HTTP 加载请求

HTTP 模拟测试

HTTP 链式加载器

HTTP 动态修改请求

HTTP 请求选项

HTTP 重置

HTTP 取消

HTTP 限流

HTTP 重试

HTTP 基础鉴权

HTTP 自动鉴权设置

HTTP 自动鉴权

HTTP 复合加载器

HTTP 头脑风暴

HTTP 总结

在上一节中,我们对HTTP的request和response有了一个大致的结构性了解。在本节中,我们将把那些信息转化成我们在swift中所需要的模型

请求和响应

我们都看到了,HTTP是一个简单的request/response模型,对于每一个请求,都会有一个response与之对应。即使是重定向的东西也遵循这一条。例如:发送一个请求到具体的终端——Star Wars API:

GET /api HTTP/1.1
Host: swapi.dev
...

我们得到的响应代表的是我们要找的东西已经移动到了一个新的地方:

HTTP/1.1 301 MOVED PERMANENTLY
Location: http://swapi.dev/api/
...

许多网络堆栈会看到301 MOVED PERMANENTLY响应,提取Location标头中的值,然后将新请求发送到指定位置:

GET /api HTTP/1.1
Host: swapi.dev
...

所以,即使想重定向的东西也依然遵循”一个请求,一个响应“的模型。这样我们就可以直接的知道他是如何去执行了

请求 Requests

基于刚刚讲的,为了正确地形成 HTTPRequest,我们需要知道一些信息。出于以下几个原因,我们将构建自己的类型而不是依赖 URLRequest

  1. 我们有机会插入我们自己的逻辑来扩展或删减 URLRequest 提供的功能。例如,由于这是一个 HTTP API,我们可能不需要允许自定义 URL 方案,而是始终将其默认为“https”
  2. 我们可以更有选择性地构建请求所需的信息量。

我们的HTTPRequest结构体需要3个关键的信息片段:

  1. 请求行:
    • 方法
    • 路径/URL
  2. 请求头
  3. 请求体

在swift中,处理全结构性的URL的值可能会有点乏味,因为我们要一点一点的去建立。所以,我们将会使用URLComponents的值来代替,如下所示:

public struct HTTPRequest {
    private var urlComponets = URLComponents()
    public var method: HTTPMethod = .get // 之前定义的过
    public var headers: [String: String] = [:]
    public var body: Data?
    
    public init() {
        urlComponents.scheme = "https"
    }
}

在这里,我将urlComponents设置成了private,这样的话,我们就可以选择性的对其某些部分进行公开,例如:

public extension HTTPRequest {
    public var scheme: String { urlComponents.scheme ?? "https" }
    
    public var host: String? {
        get { urlComponents.host }
        set { urlComponents.host = newValue } 
    } 
    
    public var path: String {
        get { urlComponents.path }
        set { urlComponents.path = newValue }
    }
}

这样的话,我们就可以隐藏一些我们实际上并不需要的东西(可能是.port?或是代表复杂序列项的.query字符串),并且对他们根据需要进行重新扩展。我们也有明确的getter和setter方法在存储重要的值之前来执行额外的有效逻辑

现在,我们用简单的Dictionay<String: String>来呈现头部。它并不完全正确,因为header的名称在技术上不区分大小写(即,Content-Type 与 cOnTeNt-TyPe 相同)并且 Dictionary 不支持该功能,但我们可以稍后改进该功能。

我们定义的body是一个Data?的可选类型,因为body是原始的二进制数据,他是可选的。后面会对其进行改进以适应更多的东西。

响应 Responses

响应的结构体和刚创建的请求的结构体很相似,跟URLComponets也很像,我们可以使用现有的类型来为我们做很多繁重的工作:HTTPURLResponseHTTPURLResponse 表示除正文之外的所有响应。由于我们要使用一个简单的请求/响应模型,我们需要一个也可以容纳主体的类型:

public struct HTTPResponse {
    public let request: HTTPRequest
    private let response: HTTPURLResponse
    public let body: Data?
    
    public var status: HTTPStatus {
        // A struct of similar construction to HTTPMethod
        HTTPStatus(rawValue: response.statusCode)
    }

    public var message: String {
        HTTPURLResponse.localizedString(forStatusCode: response.statusCode)
    }

    public var headers: [AnyHashable: Any] { response.allHeaderFields }
}

HTTPRequest 一样,此 HTTPResponse 结构包装了系统提供的类型,以便我们可以有选择地公开和增强功能。 一个显而易见的区别是header类型为 [AnyHashable: Any],这与我们在请求中使用的 [String: String] 不同。 这是前面提到的不区分大小写的神器,也是后面的另一个改进机会。

您可能还考虑在响应中包含请求的副本(正如我所做的那样),因为它们是一对相互交织的数据。 在检查响应时手头有请求可能很有用。

请求结果 Result

请求和响应的整体形式直接映射到 HTTP 规范规定的形式。 随着框架和功能的成熟,我们将扩展和改进它们。

我们可以立即观察到的一个现象是,我们并不总能得到回应。 如果我们在用户离线时发送请求,或者如果在完全接收到响应之前连接断开,那么我们最终将处于没有响应或部分响应的情况。

因此,定义一个封装这些场景的结果类型也是值得的。 幸运的是,Swift 有一个很好的内置 Result 类型,我们可以在不修改的情况下重新利用它:

public typealias HTTPResult = Result<HTTPResponse, Error>

我们可以这样保留它,但我认为我们可以使用该 Error 类型做得更好。 因此,我们将创建一个 HTTPError 类型并更改类型别名以使用它:

public typealias HTTPResult = Result<HTTPResponse, HTTPError>

public struct HTTPError: Error {
    /// The high-level classification of this error
    public let code: Code

    /// The HTTPRequest that resulted in this error
    public let request: HTTPRequest

    /// Any HTTPResponse (partial or otherwise) that we might have
    public let response: HTTPResponse?

    /// If we have more information about the error that caused this, stash it here
    public let underlyingError: Error?

    public enum Code {
        case invalidRequest     // the HTTPRequest could not be turned into a URLRequest
        case cannotConnect      // some sort of connectivity problem
        case cancelled          // the user cancelled the request
        case insecureConnection // couldn't establish a secure connection to the server
        case invalidResponse    // the system did not receive a valid HTTP response
        ...                     // other scenarios we may wish to expose; fill them in as necessary
        case unknown            // we have no idea what the problem is
    }
}

最后,我们需要注意的是,如果我们有一个 HTTPResult,那么我们将始终有一个 HTTPRequest,要么因为这是一个 .success 案例,要么因为我们有错误的 .request。 此外,我们也可能会有回应。 因此,我们可以扩展我们的类型别名,使提取这些片段变得容易:

extension HTTPResult {
    
    public var request: HTTPRequest {
        switch self {
            case .success(let response): return response.request
            case .failure(let error): return error.request
        }
    }
    
    public var response: HTTPResponse? {
        switch self {
            case .success(let response): return response
            case .failure(let error): return error.response
        }
    }
    
}

这三种类型(HTTPRequestHTTPResponseHTTPError)构成了我们今后需要的一切的基础。

在下一篇文章中,我们将仔细研究请求的主体并修改它以支持更复杂的行为。