Как добавить HTTP-заголовки в запрос глобально для iOS в Swift

func webView(webView: WKWebView!, decidePolicyForNavigationAction navigationAction: WKNavigationAction!, decisionHandler: ((WKNavigationActionPolicy) -> Void)!) {
     var request = NSMutableURLRequest(URL: navigationAction.request.URL)
     request.setValue("value", forHTTPHeaderField: "key")
     decisionHandler(.Allow)
}

В приведенном выше коде я хочу добавить заголовок к запросу. Я пытался сделать navigationAction.request.setValue("IOS", forKey: "DEVICE_APP"), но это не сработало.

пожалуйста, помогите мне любым способом.


person sandip    schedule 11.03.2015    source источник
comment
Хотите подключить мыльную шапку?   -  person Gökhan Çokkeçeci    schedule 11.03.2015
comment
спасибо за ответ ... но на самом деле я создал один wkwebview. где мне нужно добавить заголовок для запроса. это было сделано только в первый раз. после этого он не будет добавлен. в соответствии с яблочным документом есть один api resolvePolicyForNavigationAction, где каждый раз запрос загружен. я хочу добавить заголовок для этого запроса   -  person sandip    schedule 11.03.2015


Ответы (7)


AFAIK, к сожалению, вы не можете сделать это с WKWebView.

Это, безусловно, не работает в webView:decidePolicyForNavigationAction:decisionHandler:, потому что navigationAction.request доступен только для чтения и является неизменяемым NSURLRequest экземпляром, который вы не можете изменить.

Если я правильно понимаю, WKWebView запускает изолированную программную среду в отдельном процессе содержимого и сети, и, по крайней мере, на iOS, нет способа перехватить или изменить его сетевые запросы.

Вы можете сделать это, если вернетесь к UIWebView.

person Stefan Arentz    schedule 12.03.2015
comment
Почему голос против? Поверьте мне, я много исследовал это. Это невозможно. - person Stefan Arentz; 12.03.2015
comment
Это не совсем так, проверьте мой ответ - person Gabriel Cartier; 27.05.2016
comment
Можно переопределить функцию загрузки WKWebView и обрабатывать все запросы самостоятельно. Пример этого здесь - novemberfive.co/blog/wkwebview-redirect-with-cookies < / а> - person Dmitry Dushkin; 27.01.2019
comment
@StefanArentz Могу я узнать, как добавить несколько заголовков для wkwebview? - person Shrikant K; 21.05.2019

Есть много разных способов сделать это, я обнаружил, что самым простым решением было создать подкласс WKWebView и переопределить метод loadRequest. Что-то вроде этого:

class CustomWebView: WKWebView {
    override func load(_ request: URLRequest) -> WKNavigation? {
        guard let mutableRequest: NSMutableURLRequest = request as? NSMutableURLRequest else {
            return super.load(request)
        }
        mutableRequest.setValue("custom value", forHTTPHeaderField: "custom field")
        return super.load(mutableRequest as URLRequest)
    }
}

Затем просто используйте класс CustomWebView, как если бы это был WKWebView.

РЕДАКТИРОВАТЬ ПРИМЕЧАНИЕ: это будет работать только по первому запросу, указанному @Stefan Arentz.

ПРИМЕЧАНИЕ. Некоторые поля нельзя переопределить и изменить. Я не проводил тщательного тестирования, но знаю, что поле User-Agent не может быть переопределено, если вы не выполните специальный взлом (ответ на этот вопрос можно найти здесь)

person Gabriel Cartier    schedule 27.05.2016
comment
На самом деле это не решает проблему, о которой здесь спрашивали. Потому что это работает только для начального запроса верхнего уровня. Пользовательский заголовок не является липким и не будет использоваться для загрузки дополнительных ресурсов или, например, XHR. - person Stefan Arentz; 06.06.2016
comment
Верно, добавлю заметку к своему посту. Я не копался глубоко в веб-просмотре, потому что это соответствовало моим потребностям, но я чувствую, что это можно сделать через делегата. Вы действительно проверяли это методом webView:decidePolicyForNavigationAction:decisionHandler? - person Gabriel Cartier; 09.06.2016
comment
Привет, Габриэль, есть много разных способов сделать это - например, что? Есть конкретные примеры? Вы можете проверить свои предложения. - person Stefan Arentz; 09.06.2016
comment
Я проведу еще немного тестов по методу принятия решения и обновлю ответ. - person Gabriel Cartier; 09.06.2016
comment
В настоящее время это будет override func load(_ request: URLRequest) -> WKNavigation? (Swift 4.2) - person Daniel; 22.11.2018

Я изменил ответ Au Ris, чтобы использовать NavigationAction вместо NavigationResponse, как предложил Джонни. Кроме того, это исправляет ситуации, когда один и тот же URL-адрес вызывается впоследствии, и вам больше не нужно отслеживать текущий URL-адрес. Это работает только для запросов GET, но при необходимости может быть адаптировано для других типов запросов.

import UIKit
import WebKit
class ViewController: UIViewController, WKNavigationDelegate  {
    var webView: WKWebView?

    override func viewDidLoad() {
        super.viewDidLoad()
        webView = WKWebView(frame: CGRect.zero)
        webView!.navigationDelegate = self
        view.addSubview(webView!)
        // [...] set constraints and stuff

        // Load first request with initial url
        loadWebPage(url: "https://my.url")
    }

    func loadWebPage(url: URL)  {
        var customRequest = URLRequest(url: url)
        customRequest.setValue("true", forHTTPHeaderField: "x-custom-header")
        webView!.load(customRequest)
    }

    func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping
    (WKNavigationActionPolicy) -> Void) {
        if navigationAction.request.httpMethod != "GET" || navigationAction.request.value(forHTTPHeaderField: "x-custom-header") != nil {
            // not a GET or already a custom request - continue
            decisionHandler(.allow)
            return
        }
        decisionHandler(.cancel)
        loadWebPage(url: navigationAction.request.url!)
    }

}

person Roben    schedule 13.09.2018

С некоторыми ограничениями, но можно. Перехватите ответ в функции делегата webView:decidePolicyFornavigationResponse:decisionHandler:, если изменится URL-адрес, отмените его, передав decisionHandler(.cancel), и перезагрузите веб-просмотр с помощью newURLRequest, который устанавливает настраиваемые заголовки и перехваченный URL-адрес. Таким образом, каждый раз, когда URL-адрес изменяется (например, пользователи нажимают на ссылки), вы отменяете этот запрос и создаете новый с настраиваемыми заголовками.

import UIKit
import WebKit
class ViewController: UIViewController, WKNavigationDelegate  {
    var webView: WKWebView?
    var loadUrl = URL(string: "https://www.google.com/")!

    override func viewDidLoad() {
        super.viewDidLoad()

        webView = WKWebView(frame: CGRect.zero)
        webView!.navigationDelegate = self
        view.addSubview(webView!)
        webView!.translatesAutoresizingMaskIntoConstraints = false
        webView!.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 0).isActive = true
        webView!.rightAnchor.constraint(equalTo: view.rightAnchor, constant: 0).isActive = true
        webView!.topAnchor.constraint(equalTo: view.topAnchor, constant: 0).isActive = true
        webView!.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: 0).isActive = true

        // Load first request with initial url
        loadWebPage(url: loadUrl)
    }

    func loadWebPage(url: URL)  {
        var customRequest = URLRequest(url: url)
        customRequest.setValue("some value", forHTTPHeaderField: "custom header key")
        webView!.load(customRequest)
    }

    // MARK: - WKNavigationDelegate

    func webView(_ webView: WKWebView, decidePolicyFor navigationResponse: WKNavigationResponse, decisionHandler: @escaping (WKNavigationResponsePolicy) -> Void) {
        guard let url = (navigationResponse.response as! HTTPURLResponse).url else {
            decisionHandler(.cancel)
            return
        }

        // If url changes, cancel current request which has no custom headers appended and load a new request with that url with custom headers
        if url != loadUrl {
            loadUrl = url
            decisionHandler(.cancel)
            loadWebPage(url: url)
        } else {
            decisionHandler(.allow)
        }
    }
}
person Au Ris    schedule 28.09.2017
comment
Просто собираюсь попробовать это. Сразу вопрос: зачем подключаться к navigationResponse? NavigationAction звучит как правильное время для этого. - person Jonny; 28.05.2018
comment
@Jonny, ты, наверное, прав, navigationAction может быть лучшим местом для этого. Если вы можете извлечь URL-адрес и обнаружить изменение. Думаю, вы можете сделать let url = navigationAction.request?.url ... Если это сработает для вас, я соответствующим образом исправлю свой ответ. - person Au Ris; 28.05.2018
comment
Ничего страшного, я опубликовал то, что в итоге использовал в качестве другого ответа. По сути, это то же самое, просто копируем существующий запрос и устанавливаем параметр. Оказалось, что urlrequest - это структура. - person Jonny; 28.05.2018

Вот как вы это делаете: стратегия состоит в том, чтобы ваш WKNavigationDelegate отменил запрос, изменил его изменяемую копию и повторно инициировал. If-else используется, чтобы разрешить выполнение запроса, если он уже имеет желаемый заголовок; в противном случае вы попадете в бесконечный цикл load / detectPolicy.

Не уверен, что случилось, но странные вещи случаются, если вы устанавливаете заголовок для каждого запроса, поэтому для достижения наилучших результатов устанавливайте заголовок только для запросов к доменам, которые вам интересны.

Пример здесь устанавливает поле заголовка для запросов на header.domain.com и разрешает все остальные запросы без заголовка:

- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler {
    NSURL * actionURL = navigationAction.request.URL;
    if ([actionURL.host isEqualToString:@"header.domain.com"]) {
        NSString * headerField = @"x-header-field";
        NSString * headerValue = @"value";
        if ([[navigationAction.request valueForHTTPHeaderField:headerField] isEqualToString:headerValue]) {
            decisionHandler(WKNavigationActionPolicyAllow);
        } else {
            NSMutableURLRequest * newRequest = [navigationAction.request mutableCopy];
            [newRequest setValue:headerValue forHTTPHeaderField:headerField];
            decisionHandler(WKNavigationActionPolicyCancel);
            [webView loadRequest:newRequest];
        }
    } else {
        decisionHandler(WKNavigationActionPolicyAllow);
    }
}
person jbelkins    schedule 09.06.2016
comment
P.S. Простите мой ObjC. Должно быть достаточно легко сделать то же самое в Swift. ;) - person jbelkins; 10.06.2016
comment
Это просто загрузит страницу верхнего уровня. Он не будет добавлять заголовки ни к каким ресурсам на странице. Или к XHR запросам страница сделает. Это ничем не отличается от вашего предыдущего ответа. - person Stefan Arentz; 10.06.2016
comment
правильно, что этот метод изменяет только заголовки запроса html-страницы. однако в последующем запросе HTML-страницы также будут изменены заголовки. это не относится к методу @ gabriel-cartier. loadRequest не вызывается, когда пользователь нажимает на ссылки. - person dreamlab; 24.09.2017
comment
вы также должны проверить, находится ли запрос в основном фрейме navigationAction.targetFrame?.isMainFrame. в противном случае вы загрузите новую страницу для запросов iframe. - person dreamlab; 24.09.2017
comment
Хотя это сработало как шарм на iOS 13 и iOS 14. Это вызывает плохое поведение на iOS ‹13, когда любой заголовок CSRF (например, ASP.NET AntiForgery Token) не отправляется в заголовках запроса, что приводит к сбою проверок на стороне сервера. Я использую Xamarin.iOS, поэтому не уверен, что это ошибка в вашем коде, привязка Xamarin WKWebView или ошибка от Apple. Я изо всех сил пытаюсь решить эту проблему, пока не повезло @jbelkins - person Mohammad Zekrallah; 22.10.2020
comment
даже только отмена повторного запроса и его повторная отправка (без изменения каких-либо заголовков) вызывает проблему ... так что, я думаю, это проблемы либо Xamarin, либо Apple. Кто-нибудь знает другое решение? - person Mohammad Zekrallah; 22.10.2020
comment
Оказалось, что это известная ошибка в WKWebView и WebKit, из-за которой navigationLink.Request.Body всегда равен нулю !! очень расстраивает! - person Mohammad Zekrallah; 22.10.2020

Мое решение - запрос на копирование и добавление заголовков, а затем повторная загрузка

    if navigationAction.request.value(forHTTPHeaderField: "key") == nil {
        decisionHandler(.cancel)
        
        var req:URLRequest = navigationAction.request;
        req.addValue("value", forHTTPHeaderField: "key");
        webView.load(req);
    } else {
        decisionHandler(.allow)
    }
person LocHansome    schedule 17.07.2020
comment
Повлияет ли это на что-нибудь вроде производительности? Поскольку вы всегда отменяете текущий запрос и повторно загружаете текущий URL-адрес. - person Drew; 22.01.2021

Вышеупомянутые решения, похоже, работают на iOS 14, но на iOS ‹14 тело запроса POST всегда имеет значение null, что приводит к отклонению запроса на стороне сервера. Оказалось, что это известная ошибка в WKWebView и WebKit, из-за которой navigationLink.Request.Body всегда равен нулю !! очень неприятная и глупая ошибка от Apple, вынуждающая мигрировать UIWebView на нестабильный WKWebView!

В любом случае решение состоит в том, что вы должны (перед отменой запроса) захватить тело POST, запустив функцию javascript, а затем назначить результат обратно в navigationAction.Request (если navigationAction.Request.Body имеет значение null), а затем отменить действие и запросить это снова с обновленным navigationAction.Request:

Решение находится в Xamarin, но родная iOS очень близка.

[Foundation.Export("webView:decidePolicyForNavigationAction:decisionHandler:")]
    public async void DecidePolicy(WebKit.WKWebView webView, WebKit.WKNavigationAction navigationAction, Action<WebKit.WKNavigationActionPolicy> decisionHandler)
    {
        try
        {
            var url = navigationAction.Request.Url;

            // only apply to requests being made to your domain
            if (url.Host.ToLower().Contains("XXXXX"))
            {
                if (navigationAction.Request.Headers.ContainsKey((NSString)"Accept-Language"))
                {
                    var languageHeaderValue = (NSString)navigationAction.Request.Headers[(NSString)"Accept-Language"];

                    if (languageHeaderValue == Globalization.ActiveLocaleId)
                    {
                       decisionHandler.Invoke(WKNavigationActionPolicy.Allow);
                        return;
                    }
                    else
                    {
                        decisionHandler(WKNavigationActionPolicy.Cancel);
                        var updatedRequest = SetHeaders((NSMutableUrlRequest)navigationAction.Request);

                        // Temp fix for navigationAction.Request.Body always null on iOS < 14
                        // causing form not to submit correctly
                        updatedRequest = await FixNullPostBody(updatedRequest);

                        WebView.LoadRequest(updatedRequest);
                    }
                }
                else
                {
                    decisionHandler(WKNavigationActionPolicy.Cancel);

                    var updatedRequest = SetHeaders((NSMutableUrlRequest)navigationAction.Request);

                    // Temp fix for navigationAction.Request.Body always null on iOS < 14
                    // causing form not to submit correctly
                    updatedRequest = await FixNullPostBody(updatedRequest);

                    WebView.LoadRequest(updatedRequest);
                }
            }
            else
            {
                decisionHandler.Invoke(WKNavigationActionPolicy.Allow);
            }
        }
        catch (Exception ex)
        {
            Logger.LogException(ex);
            decisionHandler?.Invoke(WKNavigationActionPolicy.Allow);
        }
    }
}


    private async Task<NSMutableUrlRequest> FixNullPostBody(NSMutableUrlRequest urlRequest)
    {
        try
        {
            // if on iOS 14 and higher, don't do this
            //if (UIDevice.CurrentDevice.CheckSystemVersion(14, 0))
                //return urlRequest;

            // only resume on POST http methods
            if (urlRequest.HttpMethod.ToLowerSafe() != "post")
                return urlRequest;

            // if post body is already there, exit
            if(urlRequest.Body != null)
                return urlRequest;

            if (WebView == null)
                return urlRequest;

            // get body post by running javascript
            var body = await WebView.EvaluateJavaScriptAsync("$('form').serialize()");//.ConfigureAwait(true);

            if (body != null)
            {
                //urlRequest.Body = urlRequest.Body; // always null on iOS < 14
                var bodyString = body.ToString();

                if (!bodyString.IsNullOrEmpty())
                    urlRequest.Body = NSData.FromString(bodyString);
            }

        }
        //This method will throw a NSErrorException if the JavaScript is not evaluated successfully.
        catch (NSErrorException ex)
        {
            DialogHelper.ShowErrorAlert(Logger.HandleExceptionAndGetErrorMsg(ex));
        }
        catch (Exception ex)
        {
            DialogHelper.ShowErrorAlert(Logger.HandleExceptionAndGetErrorMsg(ex));
        }

        return urlRequest;
    }


private NSMutableUrlRequest SetHeaders(NSMutableUrlRequest urlRequest)
    {
        try
        {
            if (this.UsePOST)
            {
                urlRequest.HttpMethod = "POST";
                urlRequest.Body = postParameters.Encode(NSStringEncoding.UTF8, false);
            }

            var keys = new object[] { "Accept-Language" };
            var objects = new object[] { Globalization.ActiveLocaleId };

            var dictionnary = NSDictionary.FromObjectsAndKeys(objects, keys);

            if (urlRequest.Headers == null)
            {
                urlRequest.Headers = dictionnary;
            }
            else
            {
                NSMutableDictionary httpHeadersCopy = new NSMutableDictionary(urlRequest.Headers);

                httpHeadersCopy.Remove((NSString)"Accept-Language");
                httpHeadersCopy.Add((NSString)"Accept-Language", (NSString)Globalization.ActiveLocaleId);

                urlRequest.Headers = null;
                urlRequest.Headers = (NSDictionary)httpHeadersCopy;
            }
        }
        catch (Exception ex)
        {
            Logger.LogException(ex);
        }
        return urlRequest;
    }
person Mohammad Zekrallah    schedule 22.10.2020