Alamofire源碼學習(七): HTTPMethod與HTTPHeaders

往期導航:

Alamofire源碼學習目錄合集html

涉及文件:

  • HTTPMethod.swift
  • HTTPHeaders.swift
  • URLRequest+Alamofire.swift

簡介:

Alamofire使用結構體來封裝method與headers以及header對象,簡化處理操做:ios

HTTPMethod:

定義了一個結構體來封裝method,使用一個rawValue來保存字符串,並實現了三個必要協議swift

須要注意的是:封裝的字符串均爲大寫,所以比較HTTPMethod.get == HTTPMethod(rawValue: "get")會是false數組


public struct HTTPMethod: RawRepresentable, Equatable, Hashable {
    /// `CONNECT` method.
    public static let connect = HTTPMethod(rawValue: "CONNECT")
    /// `DELETE` method.
    public static let delete = HTTPMethod(rawValue: "DELETE")
    /// `GET` method.
    public static let get = HTTPMethod(rawValue: "GET")
    /// `HEAD` method.
    public static let head = HTTPMethod(rawValue: "HEAD")
    /// `OPTIONS` method.
    public static let options = HTTPMethod(rawValue: "OPTIONS")
    /// `PATCH` method.
    public static let patch = HTTPMethod(rawValue: "PATCH")
    /// `POST` method.
    public static let post = HTTPMethod(rawValue: "POST")
    /// `PUT` method.
    public static let put = HTTPMethod(rawValue: "PUT")
    /// `TRACE` method.
    public static let trace = HTTPMethod(rawValue: "TRACE")

    public let rawValue: String

    public init(rawValue: String) {
        self.rawValue = rawValue
    }
}
複製代碼

吐槽:整這麼麻煩,直接enum多簡單:markdown

enum HTTPMethod: String {
    case get = "GET"
    case post = "POST"
    //...其餘幾個
}
//並且使用方法同樣:
    HTTPMethod.get
    HTTPMethod.init(rawValue: "GET")
    HTTPMethod.get.rawValue
複製代碼

HTTPHeaders與HTTPHeader

方便管理http請求頭app

HTTPHeader

對應每一個請求頭,持有name跟valuepost

public struct HTTPHeader: Hashable {
    
    public let name: String

    public let value: String

    public init(name: String, value: String) {
        self.name = name
        self.value = value
    }
}
// 而後擴展了一下添加字符串描述方法
extension HTTPHeader: CustomStringConvertible {
    public var description: String {
        "\(name): \(value)"
    }
}
複製代碼

而後定義了一大堆static方法來快速建立默認請求頭:學習

extension HTTPHeader {
    
    public static func accept(_ value: String) -> HTTPHeader {
        HTTPHeader(name: "Accept", value: value)
    }
    public static func acceptCharset(_ value: String) -> HTTPHeader {
        HTTPHeader(name: "Accept-Charset", value: value)
    }
    /// 有默認值(系統語言)
    public static func acceptLanguage(_ value: String) -> HTTPHeader {
        HTTPHeader(name: "Accept-Language", value: value)
    }
    /// 有默認值(見HTTPHeader.defaultAcceptEncoding)
    public static func acceptEncoding(_ value: String) -> HTTPHeader {
        HTTPHeader(name: "Accept-Encoding", value: value)
    }
    /// Basic帳號密碼認證, 格式爲: Basic [用戶名:密碼]的base64編碼
    public static func authorization(username: String, password: String) -> HTTPHeader {
        let credential = Data("\(username):\(password)".utf8).base64EncodedString()

        return authorization("Basic \(credential)")
    }
    /// Bearer token認證
    public static func authorization(bearerToken: String) -> HTTPHeader {
        authorization("Bearer \(bearerToken)")
    }
    /// 其餘認證字段
    public static func authorization(_ value: String) -> HTTPHeader {
        HTTPHeader(name: "Authorization", value: value)
    }

    public static func contentDisposition(_ value: String) -> HTTPHeader {
        HTTPHeader(name: "Content-Disposition", value: value)
    }
    /// Alamofire的ParameterEncoding與ParameterEncoder會設置該請求頭, 因此不必手動設置
    public static func contentType(_ value: String) -> HTTPHeader {
        HTTPHeader(name: "Content-Type", value: value)
    }
    /// 有默認值(見HTTPHeader.defaultUserAgent)
    public static func userAgent(_ value: String) -> HTTPHeader {
        HTTPHeader(name: "User-Agent", value: value)
    }
}
複製代碼

而後定義了三個默認請求頭:ui

extension HTTPHeader {
    
    /// 默認設備支持的AcceptEncoding [Accept-Encoding HTTP header documentation](https://tools.ietf.org/html/rfc7230#section-4.2.3) .
    public static let defaultAcceptEncoding: HTTPHeader = {
        let encodings: [String]
        if #available(iOS 11.0, macOS 10.13, tvOS 11.0, watchOS 4.0, *) {
            encodings = ["br", "gzip", "deflate"]
        } else {
            encodings = ["gzip", "deflate"]
        }

        return .acceptEncoding(encodings.qualityEncoded())
    }()

    /// 默認的AcceptLanguage爲設備的語言 [Accept-Language HTTP header documentation](https://tools.ietf.org/html/rfc7231#section-5.3.5).
    public static let defaultAcceptLanguage: HTTPHeader = {
        .acceptLanguage(Locale.preferredLanguages.prefix(6).qualityEncoded())
    }()

    /// 拼裝Alamofire默認的UA
    ///
    /// See the [User-Agent header documentation](https://tools.ietf.org/html/rfc7231#section-5.5.3).
    ///格式: app名字/app版本號 (bundleid; build:構件號; ios版本號) Alamofire版本號
    /// Example: `iOS Example/1.0 (org.alamofire.iOS-Example; build:1; iOS 13.0.0) Alamofire/5.0.0`
    public static let defaultUserAgent: HTTPHeader = {
        let info = Bundle.main.infoDictionary
        let executable = (info?[kCFBundleExecutableKey as String] as? String) ??
            (ProcessInfo.processInfo.arguments.first?.split(separator: "/").last.map(String.init)) ??
            "Unknown"
        let bundle = info?[kCFBundleIdentifierKey as String] as? String ?? "Unknown"
        let appVersion = info?["CFBundleShortVersionString"] as? String ?? "Unknown"
        let appBuild = info?[kCFBundleVersionKey as String] as? String ?? "Unknown"

        let osNameVersion: String = {
            let version = ProcessInfo.processInfo.operatingSystemVersion
            let versionString = "\(version.majorVersion).\(version.minorVersion).\(version.patchVersion)"
            let osName: String = {
                #if os(iOS)
                #if targetEnvironment(macCatalyst)
                return "macOS(Catalyst)"
                #else
                return "iOS"
                #endif
                #elseif os(watchOS)
                return "watchOS"
                #elseif os(tvOS)
                return "tvOS"
                #elseif os(macOS)
                return "macOS"
                #elseif os(Linux)
                return "Linux"
                #elseif os(Windows)
                return "Windows"
                #else
                return "Unknown"
                #endif
            }()

            return "\(osName) \(versionString)"
        }()

        let alamofireVersion = "Alamofire/\(version)"

        let userAgent = "\(executable)/\(appVersion) (\(bundle); build:\(appBuild); \(osNameVersion)) \(alamofireVersion)"

        return .userAgent(userAgent)
    }()
}

// 擴展下Collection用來拼接值
extension Collection where Element == String {
    func qualityEncoded() -> String {
        enumerated().map { index, encoding in
            let quality = 1.0 - (Double(index) * 0.1)
            return "\(encoding);q=\(quality)"
        }.joined(separator: ", ")
    }
}
複製代碼

HTTPHeaders:

定義了一個結構體來管理HTTPHeader請求頭對象數組, 並遵照了幾個方便操做的協議。編碼

初始化以及增刪改查操做:
public struct HTTPHeaders {
    /// 請求頭數組
    private var headers: [HTTPHeader] = []
    /// 空初始化方法
    public init() {}
    /// 使用請求頭數組初始化
    public init(_ headers: [HTTPHeader]) {
        self.init()
        //
        headers.forEach { update($0) }
    }
    /// 使用string格式的字段來初始化, 不區分大小寫
    public init(_ dictionary: [String: String]) {
        self.init()
        // 使用遍歷,並調update是由於: 在update時會把name所有轉爲小寫, 這樣就能夠過濾掉字典中大小寫不一樣致使的重複值
        dictionary.forEach { update(HTTPHeader(name: $0.key, value: $0.value)) }
    }
    /// 添加請求頭
    public mutating func add(name: String, value: String) {
        update(HTTPHeader(name: name, value: value))
    }
    /// 添加請求頭
    public mutating func add(_ header: HTTPHeader) {
        update(header)
    }
    /// 更新請求頭, 不區分大小寫, 若是存在就更新, 不存在就添加
    public mutating func update(name: String, value: String) {
        update(HTTPHeader(name: name, value: value))
    }
    /// 更新請求頭
    public mutating func update(_ header: HTTPHeader) {
        // index方法是本身實現的Array擴展, 會先把name轉爲小寫, 再找找有沒有存在的
        guard let index = headers.index(of: header.name) else {
            headers.append(header)
            return
        }
        
        headers.replaceSubrange(index...index, with: [header])
    }
    /// 刪除請求頭, 不區分大小寫
    public mutating func remove(name: String) {
        guard let index = headers.index(of: name) else { return }

        headers.remove(at: index)
    }
    /// 對本身升序排序, 不區分大小寫
    public mutating func sort() {
        headers.sort { $0.name.lowercased() < $1.name.lowercased() }
    }
    /// 返回一個排序後的數組, 不影響本身的順序
    public func sorted() -> HTTPHeaders {
        var headers = self
        headers.sort()

        return headers
    }
    /// 根據name返回value, 不存在返回nil, 不區分大小寫
    public func value(for name: String) -> String? {
        guard let index = headers.index(of: name) else { return nil }

        return headers[index].value
    }
    /// 下標返回
    public subscript(_ name: String) -> String? {
        get { value(for: name) }
        set {
            if let value = newValue {
                update(name: name, value: value)
            } else {
                remove(name: name)
            }
        }
    }
    /// 把所有的請求頭轉成string字典(準備加入到請求中)
    public var dictionary: [String: String] {
        let namesAndValues = headers.map { ($0.name, $0.value) }

        return Dictionary(namesAndValues, uniquingKeysWith: { _, last in last })
    }
}
複製代碼
擴展HTTPHeaders實現幾個協議方便增刪改查操做:
extension HTTPHeaders: ExpressibleByDictionaryLiteral {
    /// 快速使用string字典初始化
    /// 用法: let headers: HTTPHeaders = ["key": "value", "key2": "value2"]
    public init(dictionaryLiteral elements: (String, String)...) {
        self.init()

        elements.forEach { update(name: $0.0, value: $0.1) }
    }
}

extension HTTPHeaders: ExpressibleByArrayLiteral {
    /// 快速使用HTTPHeader數組初始化
    /// 用法: let headers: HTTPHeaders = [header1, header2]//(header1, header2均爲HTTPHeader對象)
    public init(arrayLiteral elements: HTTPHeader...) {
        self.init(elements)
    }
}

extension HTTPHeaders: Sequence {
    // 遵循迭代器協議, 爲了實現下面的Collection協議
    public func makeIterator() -> IndexingIterator<[HTTPHeader]> {
        headers.makeIterator()
    }
}

extension HTTPHeaders: Collection {
    // 實現Collection協議
    public var startIndex: Int {
        headers.startIndex
    }

    public var endIndex: Int {
        headers.endIndex
    }

    public subscript(position: Int) -> HTTPHeader {
        headers[position]
    }

    public func index(after i: Int) -> Int {
        headers.index(after: i)
    }
}

extension HTTPHeaders: CustomStringConvertible {
    /// string格式的描述信息
    public var description: String {
        headers.map { $0.description }
            .joined(separator: "\n")
    }
}

複製代碼
擴展HTTPHeaders快速建立默認請求頭:

默認請求頭有三個默認頭:

extension HTTPHeaders {
    public static let `default`: HTTPHeaders = [.defaultAcceptEncoding,
                                                .defaultAcceptLanguage,
                                                .defaultUserAgent]
}
複製代碼
擴展Array,用來不區分大小寫的查找已存在的請求頭的index:
extension Array where Element == HTTPHeader {
    func index(of name: String) -> Int? {
        //先把名字轉爲小寫
        let lowercasedName = name.lowercased()
        //返回index,不存在會返回nil
        return firstIndex { $0.name.lowercased() == lowercasedName }
    }
}
複製代碼
擴展系統的一個結構體+兩個類,用來把allHttpHeaders轉換爲HTTPHeaders對象:
//URLRequest在swift中是結構體,想不到吧(~ ̄▽ ̄)~
extension URLRequest {
    /// Returns `allHTTPHeaderFields` as `HTTPHeaders`.
    public var headers: HTTPHeaders {
        get { allHTTPHeaderFields.map(HTTPHeaders.init) ?? HTTPHeaders() }
        set { allHTTPHeaderFields = newValue.dictionary }
    }
}

extension HTTPURLResponse {
    /// Returns `allHeaderFields` as `HTTPHeaders`.
    public var headers: HTTPHeaders {
        (allHeaderFields as? [String: String]).map(HTTPHeaders.init) ?? HTTPHeaders()
    }
}

extension URLSessionConfiguration {
    /// Returns `httpAdditionalHeaders` as `HTTPHeaders`.
    public var headers: HTTPHeaders {
        get { (httpAdditionalHeaders as? [String: String]).map(HTTPHeaders.init) ?? HTTPHeaders() }
        set { httpAdditionalHeaders = newValue.dictionary }
    }
}
複製代碼

URLRequest+Alamofire

擴展了URLRequest結構體,對method進行使用HTTPMethod對象進行讀寫,並使用method進行了URLRequest有效性進行了判斷(判斷會在Session中調用)

extension URLRequest {
    public var method: HTTPMethod? {
        get { httpMethod.flatMap(HTTPMethod.init) }
        set { httpMethod = newValue?.rawValue }
    }

    public func validate() throws {
        if method == .get, let bodyData = httpBody {
            // get方法不容許有httpbody, 若是有, 會拋出錯誤, 並把這個data給一塊兒拋出去, 方便調試
            throw AFError.urlRequestValidationFailed(reason: .bodyDataInGETRequest(bodyData))
        }
    }
}
複製代碼

以上純屬我的理解,不免有錯誤,若是發現有錯,歡迎評論指出~~將第一時間改正,也歡迎評論討論,很是感謝~~

相關文章
相關標籤/搜索