在 IOS 开发应用开发中使用 WebKit 加载网页资源或者在客户端中需要实现访问应用后端部分,希望采用 http cookie 打通账号系统应该是比较普遍的需求了。通常在后端接口中拿到的 http cookie 都是字符串,但是看苹果的开发文档能发现 WebKit 的 httpCookieStore.setCookie 函数参数是需要一个 HTTPCookie 对象的。
我看了一圈,发现好像居然没人写一个工具函数用于解析 http cookie 字符串…不知道是我搜索方向错了?还是这个需求太简单了大佬们都不愿意写,虽然说确实不难,但是其实我在使用过程中开始还是踩到一个子域共享 Http Cookie 不生效的坑(应该是我太菜了)下文细说,我还是写下来记录一下。
一个常见的 http cookie 字符串如下,这部分的文档可以参考 Using HTTP cookies Cookie specification: RFC 6265
Bin-Auth-Token=43533811-xxxx-xxxx-xxxx-xxxxxxx; Path=/; Domain=bin.zmide.com; Max-Age=604800; HttpOnly
其中,Bin-Auth-Token
就是这个 cookie 的 name 而 43533811-xxxx-xxxx-xxxx-xxxxxxx
就是 value 字段,Domain
是匹配域名,其他字段可以看看上面的文档这里就不一一介绍了。
然后我们看看苹果的文档 HTTPCookie 要通过 Cookie 字符串构建 HTTPCookie 就需要通过 HTTPCookie.init?(properties: [HTTPCookiePropertyKey : Any])
函数,这个函数只接收一个类型为[HTTPCookiePropertyKey : Any]
字典的 properties
参数,然后我们就是通过解析上面的 cookie 字符串为 [HTTPCookiePropertyKey : Any]
字典,然后用于构建 HTTPCookie 对象。
下面为具体实现函数:
func resolveCookie(cookie: String) -> HTTPCookie? {
if cookie == "" {
return nil
}
// 解析 cookie 字符串
var properties: [HTTPCookiePropertyKey: Any] = [
.secure: "TRUE",
]
// 分割 cookie 字符串
let cookieList = cookie.components(separatedBy: "; ")
cookieList.forEach {
cookieItem in
// 分割 cookie 项的 key 和 value
let cookieItemList = cookieItem.components(separatedBy: "=")
if cookieItemList.count <= 0 {
return
}
let key = cookieItemList[0]
var value = ""
if cookieItemList.count == 2 {
value = cookieItemList[1]
}
switch key.lowercased() {
case HTTPCookiePropertyKey.name.rawValue.lowercased():
properties.updateValue(value, forKey: HTTPCookiePropertyKey.name)
case HTTPCookiePropertyKey.domain.rawValue.lowercased():
// 如果需要匹配子域需要在域名前增加 . 前缀,如 .bin.zmide.com 用于将该 cookie 作用于 bin.zmide.com 及其全部子域
properties.updateValue(value, forKey: HTTPCookiePropertyKey.domain)
// properties.updateValue("." + value, forKey: HTTPCookiePropertyKey.domain)
case HTTPCookiePropertyKey.path.rawValue.lowercased():
properties.updateValue(value, forKey: HTTPCookiePropertyKey.path)
case HTTPCookiePropertyKey.maximumAge.rawValue.lowercased():
properties.updateValue(value, forKey: HTTPCookiePropertyKey.maximumAge)
case HTTPCookiePropertyKey.originURL.rawValue.lowercased():
properties.updateValue(value, forKey: HTTPCookiePropertyKey.originURL)
case HTTPCookiePropertyKey.version.rawValue.lowercased():
properties.updateValue(value, forKey: HTTPCookiePropertyKey.version)
case HTTPCookiePropertyKey.port.rawValue.lowercased():
properties.updateValue(value, forKey: HTTPCookiePropertyKey.port)
case HTTPCookiePropertyKey.comment.rawValue.lowercased():
properties.updateValue(value, forKey: HTTPCookiePropertyKey.comment)
case HTTPCookiePropertyKey.discard.rawValue.lowercased():
properties.updateValue(value, forKey: HTTPCookiePropertyKey.discard)
case HTTPCookiePropertyKey.secure.rawValue.lowercased():
properties.updateValue(value, forKey: HTTPCookiePropertyKey.secure)
case HTTPCookiePropertyKey.sameSitePolicy.rawValue.lowercased():
properties.updateValue(value, forKey: HTTPCookiePropertyKey.sameSitePolicy)
case "HttpOnly".lowercased():
properties.updateValue(true, forKey: HTTPCookiePropertyKey(rawValue: "HttpOnly"))
default:
properties.updateValue(key, forKey: HTTPCookiePropertyKey.name)
properties.updateValue(value, forKey: HTTPCookiePropertyKey.value)
}
}
return HTTPCookie(properties: properties)
}
在代码中,首先会将 cookie 字符串分割为 cookie 配置项,然后遍历每项,再将 cookie 配置项分割为 key 和 value,最后使用了一个 switch 匹配 key 项,并将值更新到 properties 字典中,最后调用 HTTPCookie init 函数实例化 HTTPCookie 对象。
我遇到的问题就是后端给我返回的 cookie 的 Domain 值为 bin.zmide.com
于是,当我访问 app.bin.zmide.com 时候就会出现没有带上 cookie 的情况,其实正确的做法应该是后端在返回数据时就返回正确的 .bin.zmide.com
要是我自己写后端肯定这样返回,但是和后端商量无果,于是我只能很脏的在代码里自己加前缀了,当然在上面的工具函数中我已经注释了,如果你遇到和我相同的需求也可以尝试取消注释(大概率不会)。
今天的划水文就先到这,如果后续有需要这块代码以后就可以直接拷贝了(Bushi