Получение всех файлов cookie от WKWebView
Получая куки от UIWebView
кажется простым с помощью NSHTTPCookieStorage.sharedHTTPCookieStorage()
, похоже на то WKWebView
хранит печенье где-то еще.
Я провел некоторое исследование, и мне удалось получить несколько файлов cookie, взяв их у NSHTTPURLResponse
объект. это, однако, не содержит все куки, используемые WKWebView
:
func webView(webView: WKWebView, decidePolicyForNavigationResponse navigationResponse: WKNavigationResponse, decisionHandler: (WKNavigationResponsePolicy) -> Void) {
if let httpResponse = navigationResponse.response as? NSHTTPURLResponse {
if let headers = httpResponse.allHeaderFields as? [String: String], url = httpResponse.URL {
let cookies = NSHTTPCookie.cookiesWithResponseHeaderFields(headers, forURL: url)
for cookie in cookies {
logDebug(cookie.description)
logDebug("found cookie " + cookie.name + " " + cookie.value)
}
}
}
}
Странно, но есть и класс WKWebsiteDataStore
в IOS 9, который отвечает за управление куки в WKWebView
однако класс не содержит открытый метод для получения данных cookie:
let storage = WKWebsiteDataStore.defaultDataStore()
storage.fetchDataRecordsOfTypes([WKWebsiteDataTypeCookies], completionHandler: { (records) -> Void in
for record in records {
logDebug("cookie record is " + record.debugDescription)
for dataType in record.dataTypes {
logDebug("data type is " + dataType.debugDescription)
// get cookie data??
}
}
})
Есть ли обходной путь для получения данных cookie?
14 ответов
В заключение, httpCookieStore
за WKWebsiteDataStore
приземлился в iOS 11.
https://developer.apple.com/documentation/webkit/wkwebsitedatastore?changes=latest_minor
Файлы cookie, используемые (созданные) WKWebView
на самом деле правильно хранятся в NSHTTPCookieStorage.sharedHTTPCookieStorage()
,
Проблема в том, что WKWebView
не возвращает обратно куки немедленно. Я думаю, что это происходит по собственному графику. Например, когда WKWebView
закрыто или, может быть, периодически.
Так что в конечном итоге они оказываются там, но когда это непредсказуемо.
Возможно, вы сможете принудительно синхронизировать общий ресурс NSHTTPCookieStorage
закрыв WKWebView
, Пожалуйста, дайте нам знать, если это работает.
Обновление: я только что вспомнил, что в Firefox для iOS мы форсируем WKWebView
очистить свои внутренние данные, включая куки, заменив WKProcessPool
с новым. Официального API нет, но я уверен, что это самый надежный обходной путь на данный момент.
подробности
Xcode 9.2, Swift 4
Решение
extension WKWebView {
private var httpCookieStore: WKHTTPCookieStore {
return WKWebsiteDataStore.default().httpCookieStore
}
func getCookies(for domain: String? = nil, completion: @escaping ([String : Any])->()) {
var cookieDict = [String : AnyObject]()
httpCookieStore.getAllCookies { (cookies) in
for cookie in cookies {
if let domain = domain {
if cookie.domain.contains(domain) {
cookieDict[cookie.name] = cookie.properties as AnyObject?
}
} else {
cookieDict[cookie.name] = cookie.properties as AnyObject?
}
}
completion(cookieDict)
}
}
}
использование
// get cookies for domain
webView.getCookies(for: url.host) { data in
print("=========================================")
print("\(url.absoluteString)")
print(data)
}
// get all cookies
webView.getCookies() { data in
print("=========================================")
print("\(url.absoluteString)")
print(data)
}
Полный образец
- Не забудьте добавить код решения здесь
- ViewController имеет встроенный контроллер представления
import UIKit
import WebKit
class ViewController: UIViewController {
var urlString = "http://google.com"
var webView: WKWebView!
fileprivate var webViewIsInited = false
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillLayoutSubviews() {
if !webViewIsInited {
webViewIsInited = true
if webView == nil {
webView = WKWebView(frame: UIScreen.main.bounds, configuration: WKWebViewConfiguration())
}
view.addSubview(webView)
webView.navigationDelegate = self
webView.uiDelegate = self
webView.loadUrl(string: urlString)
}
}
}
extension ViewController: WKNavigationDelegate {
func webView(_ webView: WKWebView, decidePolicyFor navigationResponse: WKNavigationResponse, decisionHandler: @escaping (WKNavigationResponsePolicy) -> Void) {
decisionHandler(.allow)
}
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
if let url = webView.url {
webView.getCookies(for: url.host) { data in
print("=========================================")
print("\(url.absoluteString)")
print(data)
}
}
}
}
extension ViewController: WKUIDelegate {
func webView(_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration, for navigationAction: WKNavigationAction, windowFeatures: WKWindowFeatures) -> WKWebView? {
if navigationAction.targetFrame == nil {
let vc = ViewController()
vc.urlString = navigationAction.request.url?.absoluteString ?? "http://google.com"
vc.view.frame = UIScreen.main.bounds
vc.webView = WKWebView(frame: UIScreen.main.bounds, configuration: configuration)
navigationController?.pushViewController(vc, animated: false)
return vc.webView
}
return nil
}
}
extension WKWebView {
func loadUrl(string: String) {
if let url = URL(string: string) {
if self.url?.host == url.host {
self.reload()
} else {
load(URLRequest(url: url))
}
}
}
}
Я знаю, что это очень старый вопрос, и у нас есть решение, но мы работаем только на iOS 11 и выше. Для тех, кто имеет дело с iOS 10 и ниже (как я), вы можете рассмотреть этот метод. Это прекрасно работает для меня:
- Принудительно сбросить процесс:
extension WKWebView {
func refreshCookies() {
self.configuration.processPool = WKProcessPool()
// TO DO: Save your cookies,...
}
}
-> это работает только на реальном устройстве.
- Для симулятора вы должны добавить:
func webView(_ webView: WKWebView, decidePolicyFor navigationResponse: WKNavigationResponse, decisionHandler: @escaping (WKNavigationResponsePolicy) -> Void) {
if let response = navigationResponse.response as? HTTPURLResponse,
let allHttpHeaders = response.allHeaderFields as? [String: String],
let responseUrl = response.url {
let cookies = HTTPCookie.cookies(withResponseHeaderFields: allHttpHeaders, for: responseUrl)
for cookie in cookies {
HTTPCookieStorage.shared.setCookie(cookie)
}
}
decisionHandler(.allow)
}
Следуйте ответу Стефана Арента и Фенома.
Для iOS 11 без расширений:
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
self.webView.configuration.websiteDataStore.httpCookieStore.getAllCookies { cookies in
for cookie in cookies {
//...
}
}
}
if (@available(iOS 11.0, *)) {
[webView.configuration.websiteDataStore.httpCookieStore
getAllCookies:^(NSArray<NSHTTPCookie *> *_Nonnull cookies) {
NSURLRequest *request =
[[NSURLRequest alloc] initWithURL:self.URL]; //your URL
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *task = [session
dataTaskWithRequest:request
completionHandler:^(NSData *responseData, NSURLResponse *response,
NSError *error) {
//Do Something
}];
[task resume];
[session.configuration.HTTPCookieStorage storeCookies:cookies forTask:task];
}];
}
Я использовал WKHTTPCookieStore в Objective-C, это сработало для меня, чтобы получить как постоянные, так и сессионные куки, но это работает только в iOS 11+
if (@available(iOS 11.0, *)) {
WKHTTPCookieStore *cookieStore = _webView.configuration.websiteDataStore.httpCookieStore;
[cookieStore getAllCookies:^(NSArray* cookies) {
NSHTTPCookie *cookie;
for(cookie in cookies){
NSLog(@"cookie: %@", cookie);
}
}];
Заставить WKWebView сбрасывать свои внутренние данные, заменив его WKProcessPool, как описано в ответе Стефана, работал для меня в iOS 10 и 11, но только для постоянных файлов cookie; похоже, что сессионные куки удаляются, как описал J. Thoo
Swift 5
func webView(_ webView: WKWebView, decidePolicyFor navigationResponse: WKNavigationResponse, decisionHandler: @escaping (WKNavigationResponsePolicy) -> Void) {
webView.configuration.websiteDataStore.httpCookieStore.getAllCookies { cookies in
debugPrint(cookies.debugDescription)
}
decisionHandler(.allow)
}
Как отметил Стефан, куки хранятся в NSHTTPCookieStorage.sharedHTTPCookieStorage()
Однако из моих экспериментов я обнаружил, что куки-файлы сеанса, установленные сервером, не видны NSHTTPCookieStorage.sharedHTTPCookieStorage()
,
Пока каждый WKWebView
поделиться тем же экземпляром WKProcessPool
эти файлы cookie сеанса будут передаваться на сервер для каждого запроса. Если вы измените пул процессов для WKWebView
вы по существу удаляете сеансовые куки для всех будущих запросов.
Не тратьте свое время на извлечение куки из iOS 11 below device
шансов на успех очень мало. Из-за некоторых причин безопасности извлечение файлов cookie может быть заблокировано.
Смотрите эти журналы:
2019-02-07 00:05:45.548880+0530 MyApp[2278:280725] [BoringSSL] nw_protocol_boringssl_get_output_frames(1301) [C8.1:2][0x10fd776f0] get output frames failed, state 8196
2019-02-07 00:05:45.550915+0530 MyApp[2278:280725] TIC Read Status [8:0x0]: 1:57
Попробуйте этот код, созданный для устройств под iOS 11:
func webView(_ webView: WKWebView, decidePolicyFor navigationResponse: WKNavigationResponse, decisionHandler: @escaping (WKNavigationResponsePolicy) -> Void) {
let cookieValue = HTTPCookieStorage.shared.cookies(for: navigationResponse.response.url!)
print(cookieValue!)
let response = navigationResponse.response as! HTTPURLResponse
let headFields = response.allHeaderFields as! [String:String]
let cookies = HTTPCookie.cookies(withResponseHeaderFields: headFields, for: response.url!)
for cookie in cookies {
print("name: \(cookie.name) value: \(cookie.value)")
}
decisionHandler(.allow)
}
Приведенный выше код даст вам пустой массив файлов cookie, поскольку извлечение файлов cookie блокируется по некоторым причинам безопасности.
Я бы порекомендовал вам попробовать следующее, которое предназначено для iOS 11 и выше:
WKWebsiteDataStore.default().httpCookieStore.getAllCookies { (cookies) in
for cookie in cookies {
print(cookie)
}
}
В NSHTTPCookie.cookiesWithResponseHeaderFields(headers, forURL: url)
Что произойдет, если URL-адрес, на котором установлены файлы cookie, не является URL-адресом ответа навигации (URL, который вызывает навигацию)? Я замечаю, что URL обратного вызова, в котором установлены файлы cookie, никогда не вызывается в definePolicyFor navigationResponse.
func webView(_ webView: WKWebView, decidePolicyFor navigationResponse: WKNavigationResponse, decisionHandler: @escaping (WKNavigationResponsePolicy) -> Void) {
let response = navigationResponse.response as! HTTPURLResponse
let cookies = HTTPCookie.cookies(withResponseHeaderFields: response.allHeaderFields as! [String : String], for: response.url!)
}
Вышеупомянутый делегат никогда не выполняется для URL обратного вызова, так как сам обратный вызов не вызвал навигацию по странице.
Решено для iOS 11 или выше, единственное, что вам нужно сделать, это написать метод assíncronos, чтобы получить все файлы cookie следующим образом:
webView.configuration.processPool = [[WKProcessPool alloc] init];
[[WKWebsiteDataStore defaultDataStore].httpCookieStore getAllCookies:^(NSArray<NSHTTPCookie *> * cookies) {
//DO YOUR STUFF WITH THE 'cookies' VARIABLE
}
На практике я обнаружил, что в методе "managePolicyForNavigationResponse" вы можете использовать следующий способ получения файлов cookie, но, к сожалению, это не полный / полный список для сеанса.
let response = navigationResponse.response as! NSHTTPURLResponse
let headFields = response.allHeaderFields as! [String:String]
let cookies = NSHTTPCookie.cookiesWithResponseHeaderFields(headFields, forURL: response.URL!)
Этот пост содержит полезную информацию об обработке файлов cookie с помощью WKWebView. В соответствии с этим вы должны иметь возможность устанавливать и получать файлы cookie, используя стандартные NSURLCache и NSHTTPCookie. Он также ссылается на использование WKProccessPool в соответствии с комментарием Стефана.