【问题标题】:How to add HTTP headers in request globally for iOS in swift如何在 swift 中为 iOS 全局添加 HTTP 标头
【发布时间】:2015-05-13 02:24:49
【问题描述】:
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"),但它不起作用。

请以任何方式帮助我。

【问题讨论】:

  • 要连接soap头吗?
  • 感谢回复...但实际上我创建了一个 wkwebview.where 我必须为请求添加标头。它只是第一次完成。之后它不会添加。根据苹果文档每次加载请求时都有一个 api decisionPolicyForNavigationAction 。我想为该请求添加标头

标签: ios swift http wkwebview request-headers


【解决方案1】:

为了向 AJAX 请求添加自定义标头,我使用了 two 三个技巧的组合。 first 在我的原生 Swift 代码和 javascript 之间提供了一个同步通信通道。 second 覆盖 XMLHttpRequest send() 方法。 third 将覆盖注入到加载到我的 WKWebView 中的网页中。

所以,组合是这样的:

而不是 request.setValue("value", forHTTPHeaderField: "key"):

在 ViewController 中:

func webView(_ webView: WKWebView, runJavaScriptTextInputPanelWithPrompt headerName: String, defaultText _: String?, initiatedByFrame _: WKFrameInfo, completionHandler: @escaping (String?) -> Void) {
  if headerName == "key" {
    completionHandler("value")
  } else {
    completionHandler(nil)
  }
}}

在 viewDidLoad 中:

let script = 
  "XMLHttpRequest.prototype.realSend = XMLHttpRequest.prototype.send;"
  "XMLHttpRequest.prototype.send = function (body) {"
    "let value = window.prompt('key');"
    "this.setRequestHeader('key', value);"
    "this.realSend(body)"
  "};"
webView.configuration.userContentController.addUserScript(WKUserScript(source: script, injectionTime: .atDocumentEnd, forMainFrameOnly: true))

这是测试 HTML 文件:

<html>
<head>
  <script>
    function loadAjax() {
      const xmlhttp = new XMLHttpRequest()
      xmlhttp.onload = function() {
         document.getElementById("load").innerHTML = this.responseText
      }
      xmlhttp.open("GET", "/ajax")
      xmlhttp.send()
    }
  </script>
</head>
<body>
  <button onClick="loadAjax()">Change Content</button> <br />
  <pre id="load">load…</pre>
</body>
</html>

调用/ajax 会带来一个通用的回显,包括所有请求标头。这样我就知道任务完成了。

【讨论】:

    【解决方案2】:

    上述解决方案似乎适用于 iOS 14,但在 iOS

    无论如何,解决方案是您应该(在取消请求之前)通过运行 javascript 函数获取 POST 正文,然后将结果分配回 navigationAction.Request(如果 navigationAction.Request.Body 为空),然后取消操作并使用更新后的 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;
        }
    

    【讨论】:

      【解决方案3】:

      我的解决方案是复制请求并添加标头然后再次加载它

          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)
          }
      

      【讨论】:

      • 它会影响性能吗?由于您总是取消当前请求并重新加载当前 URL。
      【解决方案4】:

      有很多不同的方法可以做到这一点,我发现最简单的解决方案是继承 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 字段不能被覆盖,除非您执行特定的 hack (check here for an answer to that)

      【讨论】:

      • 这实际上并没有解决这里提出的问题。因为这仅适用于初始“顶级”请求。自定义标头不具有粘性,不会用于子资源加载或例如 XHR。
      • 确实如此,我会在我的帖子中添加注释。我没有深入研究 webview,因为这适合我的需要,但我觉得可以通过委托来完成。有没有通过webView:decidePolicyForNavigationAction:decisionHandler方法实际测试过?
      • 嘿加布里埃尔,“有很多不同的方法可以做到这一点” - 比如什么?有什么具体的例子吗?您可能需要检查您的建议。
      • 我将对决策策略方法进行更多测试并更新答案
      • 现在是override func load(_ request: URLRequest) -&gt; WKNavigation? (Swift 4.2)
      【解决方案5】:

      正如 jonny 建议的那样,我已修改 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!)
          }
      

      }

      【讨论】:

        【解决方案6】:
        private var urlrequestCurrent: URLRequest?
        
        func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
            //print("WEB decidePolicyFor navigationAction: \(navigationAction)")
            if let currentrequest = self.urlrequestCurrent {
                //print("currentrequest: \(currentrequest), navigationAction.request: \(navigationAction.request)")
                if currentrequest == navigationAction.request {
                    self.urlrequestCurrent = nil
                    decisionHandler(.allow)
                    return
                }
            }
        
            decisionHandler(.cancel)
        
            var customRequest = navigationAction.request
            customRequest.setValue("myvaluefffs", forHTTPHeaderField: "mykey")
            self.urlrequestCurrent = customRequest
            webView.load(customRequest)
        }
        

        【讨论】:

          【解决方案7】:

          有一些限制,但你可以做到。在委托函数webView:decidePolicyFornavigationResponse:decisionHandler: 中拦截响应,如果 url 更改通过传递 decisionHandler(.cancel) 取消它并使用 newURLRequest 重新加载 webview,它设置自定义标头和拦截的 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)
                  }
              }
          }
          

          【讨论】:

          • 正要试试这个。只是一个问题:为什么要加入navigationResponse? navigationAction 听起来是正确的时机。
          • @Jonny 你可能是正确的navigationAction 可能是一个更好的地方。只要您可以提取url并检测到更改。我想你可以做let url = navigationAction.request?.url ... 如果它适合你,我会相应地纠正我的答案。
          • 没关系,我发布了我最终用作另一个答案的内容。基本一样,只是复制已有的请求,设置参数。原来 urlrequest 是一个结构体。
          【解决方案8】:

          您的操作方法如下: 策略是让您的 WKNavigationDelegate 取消请求,修改它的可变副本并重新启动它。如果已经有所需的标头,则使用 if-else 允许请求继续;否则你最终会陷入无休止的加载/决定策略循环。

          不确定发生了什么,但是如果您在每个请求上设置标头,就会发生奇怪的事情,因此为了获得最佳结果,请仅在对您关心的域的请求上设置标头。

          这里的例子为header.domain.com的请求设置了一个header字段,并允许所有其他没有header的请求:

          - (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);
              }
          }
          

          【讨论】:

          • P.S.原谅我的 ObjC。在 Swift 中做同样的事情应该很容易。 ;)
          • 这只会加载顶级页面。它不会向页面上的任何资源添加标题。或者页面将发出的 XHR 请求。这与您之前的答案没有什么不同。
          • 这个方法只改变html页面请求的headers是正确的。但是随后的 html 页面请求也将更改其标题。 @gabriel-cartier 的方法并非如此。用户点击链接时不会调用loadRequest
          • 您还应该检查请求是否在主框架上navigationAction.targetFrame?.isMainFrame。否则,您将为 iframe 请求加载一个新页面。
          • 虽然这在 iOS 13 和 iOS 14 上就像一个魅力。它会导致 iOS
          【解决方案9】:

          AFAIK 很遗憾你不能用 WKWebView 做到这一点。

          它肯定在 webView:decidePolicyForNavigationAction:decisionHandler: 中不起作用,因为 navigationAction.request 是只读的,并且是一个不可更改的 NSURLRequest 实例,您无法更改。

          如果我理解正确,WKWebView 在单独的内容和网络进程中运行沙盒,至少在 iOS 上,没有办法拦截或更改它的网络请求。

          如果您退回到UIWebView,您可以这样做。

          【讨论】:

          • 为什么投反对票?相信我,我已经对此进行了很多调查。这是不可能的。
          • 这不完全正确,检查我的答案
          • 可以重写WKWebView的加载函数,自己处理所有请求。这里的例子——novemberfive.co/blog/wkwebview-redirect-with-cookies
          • @StefanArentz 我可以知道如何为 wkwebview 添加多个标题吗?
          猜你喜欢
          • 1970-01-01
          • 2019-05-11
          • 2018-07-03
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2016-01-30
          • 1970-01-01
          相关资源
          最近更新 更多