【问题标题】:Get Form Post Response Body in WKWebView DecidePolicy在 WKWebView DecidePolicy 中获取表单发布响应正文
【发布时间】:2018-09-13 13:13:17
【问题描述】:

我正在使用 Xamarin.iOS 开发 iOS 应用程序,如果我的 webview 如下所示,我已经实现了 WKNavigationDelegate 的 DecidePolicy:

        public override void DecidePolicy(WKWebView webView, WKNavigationResponse navigationResponse, Action<WKNavigationResponsePolicy> decisionHandler)
    {
        if (webView.Url.AbsoluteString == @"www.myurl.com")
        {
            var response = navigationResponse.Response as NSHttpUrlResponse;
            System.Diagnostics.Debug.WriteLine(response.ToString());

        }
        decisionHandler?.Invoke(WKNavigationResponsePolicy.Allow);
    }

此页面上有一个按钮,单击该按钮会调用表单“POST”。这反过来提交表单并返回 HTTP 状态 200 以及响应表单正文中的隐藏元素。我想检索这些隐藏元素,以便在我的应用中进行进一步处理。

我的问题是两个部分:

  1. 上面的响应对象只包含响应头,我需要获取响应的正文
  2. 我实际上不知道何时使用上面覆盖的 DecidePolicy(..) 表单是否已在该 url 上提交?我可以通过使用其他 DecidePolicy 来实现:

    public override void DecidePolicy(WKWebView webView, WKNavigationAction navigationAction, Action<WKNavigationActionPolicy>decisionHandler) 
    {
        if (navigationAction.NavigationType == WKNavigationType.FormSubmitted)
        {
            //perform logic
        } 
    }
    

但是我不知道如何从这种方法变体中检索响应正文。非常感谢您的帮助。

【问题讨论】:

    标签: ios swift xamarin xamarin.ios


    【解决方案1】:

    我也必须实现这一点,我想我会发布它也许可以帮助其他人。

    我的用例是我需要将 Authorization 标头注入一些请求,我设法使用相当标准的 WKNavigationDelegate 实现来完成。为此,我将取消现有请求,创建一个副本并注入我更改的标头,然后在保留 cookie 和现有连接属性的同一浏览器中重新提交请求。然而,有些调用失败了,我发现 POST 没有返回任何正文数据。

    这就是我今天的工作方式;

    public override async void DecidePolicy(WKWebView webView, WKNavigationAction navigationAction, Action<WKNavigationActionPolicy> decisionHandler)
            {  
                if (ShouldInjectAuthorizationHeader(navigationAction.Request))
                {
                    decisionHandler(WKNavigationActionPolicy.Cancel);
                    webView.LoadRequest(await CreateRequestWithAuthorisationHeader(webView, navigationAction.Request));                
                }
                else
                {
                    decisionHandler(WKNavigationActionPolicy.Allow);
                }
            }
    

    这只是为了封装我正在检查是否需要修改调用的决策策略的逻辑。

    上面的ShouldInjectAuthorizationHeader 只是检查request.Url 是否存在于已知url 列表中,然后我检查现有标题是否已经存在授权。

        private async Task<NSUrlRequest> CreateRequestWithAuthorisationHeader(WKWebView webView, NSUrlRequest request)
    {
        var newRequest = (NSMutableUrlRequest)request.MutableCopy();
        newRequest.ShouldHandleCookies = request.ShouldHandleCookies;
    
        var requestModified = false;
        //This is not needed usually but i recheck here that i have an accesstoken to use and that the url requires it
        if (string.IsNullOrEmpty(AccessToken) == false && WebEx.DoesUrlMatchAnyPartial(request.Url.ToString(), InterceptUrls))
        {
            // inject the HTTP header
            var headers = request.Headers == null ? new NSMutableDictionary() : new NSMutableDictionary(request.Headers);
            headers.Add(new NSString("Authorization"), new NSString($"Bearer {AccessToken}"));
    
            newRequest.Headers = headers;
            if(request.HttpMethod == "POST")
            {
                try
                {
                    //WKWebview has a 5 year running bug Apple https://bugs.webkit.org/show_bug.cgi?id=140188 
                    //that the POST form data is not returned and not available so we run some custom javascript to serialise and expose it
                    //then manually copy it over and set up the correct headers for it
                    //Serilise form library from https://code.google.com/archive/p/form-serialize/
                    var javascript = @"
    function serialize(form){if(!form||form.nodeName!==""FORM""){return }var i,j,q=[];for(i=form.elements.length-1;i>=0;i=i-1){if(form.elements[i].name===""""){continue}switch(form.elements[i].nodeName){case""INPUT"":switch(form.elements[i].type){case""text"":case""hidden"":case""password"":case""button"":case""reset"":case""submit"":q.push(form.elements[i].name+""=""+encodeURIComponent(form.elements[i].value));break;case""checkbox"":case""radio"":if(form.elements[i].checked){q.push(form.elements[i].name+""=""+encodeURIComponent(form.elements[i].value))}break;case""file"":break}break;case""TEXTAREA"":q.push(form.elements[i].name+""=""+encodeURIComponent(form.elements[i].value));break;case""SELECT"":switch(form.elements[i].type){case""select-one"":q.push(form.elements[i].name+""=""+encodeURIComponent(form.elements[i].value));break;case""select-multiple"":for(j=form.elements[i].options.length-1;j>=0;j=j-1){if(form.elements[i].options[j].selected){q.push(form.elements[i].name+""=""+encodeURIComponent(form.elements[i].options[j].value))}}break}break;case""BUTTON"":switch(form.elements[i].type){case""reset"":case""submit"":case""button"":q.push(form.elements[i].name+""=""+encodeURIComponent(form.elements[i].value));break}break}}return q.join(""&"")};
    
    serialize(document.querySelector('form'));";
                    var result = await webView.EvaluateJavaScriptAsync(javascript);
    
                    var data = result?.ToString();
                    if (string.IsNullOrWhiteSpace(data) == false)
                    {
                        newRequest.Body = data;
                        newRequest["Content-Length"] = $"{newRequest.Body.Length}";
                        newRequest["Content-Type"] = "application/x-www-form-urlencoded";
                    }
                }
                catch(NSErrorException ex)
                {
                    Log.Error(ex, $"WKWebViewNavigationDelegate.CreateRequestWithAuthorisationHeader: JavaScript evaluation exception when trying to get Form post data, {ex.Message}");
                }
            }
            requestModified = true;
        }
        if (requestModified)
        {
            return newRequest;
        }
        return request;
    }
    

    我已经为帖子稍微简化了上述两种方法,删除了调试输出并清理了变量,但大多数情况下它在我们的系统中运行

    看起来堆栈溢出不喜欢双引号缩小的 js if 你想下载它并从minified js for extracting form data重新插入它我把它放到一个文本编辑器中并用“”替换所有“以允许它在@”“字符串中解析内联

    如果你不需要授权承载的东西,它会减少很多代码

    基本上我将 WKWebView 传递给该方法并在其中运行 javascript 以在其为 POST 时提取表单数据,然后将其复制到新请求中,使用取消旧请求

    decisionHandler(WKNavigationActionPolicy.Cancel);
    

    并使用 LoadRequest 将 webview 重定向到原始的新 MutableCopy,其中包含额外的标头和手动插入的 POST 数据

    webView.LoadRequest(await CreateRequestWithAuthorisationHeader(webView, navigationAction.Request));
    

    这似乎现在可以正常工作

    根据您的要求,John 您应该能够在现有代码中非常轻松地获取数据

    public override void DecidePolicy(WKWebView webView, WKNavigationAction navigationAction, Action<WKNavigationActionPolicy>decisionHandler) 
    {
        if (navigationAction.NavigationType == WKNavigationType.FormSubmitted)
        {
            //perform logic
    var javascript = @"
    function serialize(form){if(!form||form.nodeName!==""FORM""){return }var i,j,q=[];for(i=form.elements.length-1;i>=0;i=i-1){if(form.elements[i].name===""""){continue}switch(form.elements[i].nodeName){case""INPUT"":switch(form.elements[i].type){case""text"":case""hidden"":case""password"":case""button"":case""reset"":case""submit"":q.push(form.elements[i].name+""=""+encodeURIComponent(form.elements[i].value));break;case""checkbox"":case""radio"":if(form.elements[i].checked){q.push(form.elements[i].name+""=""+encodeURIComponent(form.elements[i].value))}break;case""file"":break}break;case""TEXTAREA"":q.push(form.elements[i].name+""=""+encodeURIComponent(form.elements[i].value));break;case""SELECT"":switch(form.elements[i].type){case""select-one"":q.push(form.elements[i].name+""=""+encodeURIComponent(form.elements[i].value));break;case""select-multiple"":for(j=form.elements[i].options.length-1;j>=0;j=j-1){if(form.elements[i].options[j].selected){q.push(form.elements[i].name+""=""+encodeURIComponent(form.elements[i].options[j].value))}}break}break;case""BUTTON"":switch(form.elements[i].type){case""reset"":case""submit"":case""button"":q.push(form.elements[i].name+""=""+encodeURIComponent(form.elements[i].value));break}break}}return q.join(""&"")};
    
    serialize(document.querySelector('form'));";
                    var result = await webView.EvaluateJavaScriptAsync(javascript);
    
                    var data = result?.ToString();
                    if (string.IsNullOrWhiteSpace(data) == false)
                    {
    /*
    url encoded form data in data
    You could replace the & with something else if you want it in a different format or try different javascript method, I found a few but I was after Url encoded so didn't experiment further, there were some alternatives that allowed for json format instead of url format
    */
    }
        } 
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-12-15
      • 1970-01-01
      • 1970-01-01
      • 2017-08-23
      • 1970-01-01
      • 2017-09-01
      • 2016-01-18
      相关资源
      最近更新 更多