Can't set headers on my WKWebView POST request

2019-01-16 11:49发布

I want to do a POST request to my WKWebView but the headers doesn't get set when I monitor the requests with Charles so the request fails. What is wrong here?

NSString *post = [NSString stringWithFormat: @"email=%@&password=%@", email, password];
NSData *postData = [post dataUsingEncoding:NSASCIIStringEncoding allowLossyConversion:YES];
NSString *contentLength = [NSString stringWithFormat:@"%d", postData.length];

NSURL *url = [NSURL URLWithString:@"http://materik.me/endpoint"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
[request setHTTPMethod:@"POST"];
[request setHTTPBody:postData];
[request setValue:contentLength forHTTPHeaderField:@"Content-Length"];
[request setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
[request setValue:@"application/json" forHTTPHeaderField:@"Accept"];

[webview loadRequest:request];

And this is what Charles says the request is like:

POST /endpoint HTTP/1.1
Host: materik.me
Content-Type: application/x-www-form-urlencoded
Origin: null
Connection: keep-alive
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
User-Agent: Mozilla/5.0 (iPhone; CPU OS 8_0 like Mac OS X)
Content-Length: 0
Accept-Language: en-us
Accept-Encoding: gzip, deflate

So, as you can see, Content-Length is 0, Accept is not application/json and no request body were sent.

Thanks for any help.

8条回答
再贱就再见
2楼-- · 2019-01-16 12:10

I had the same problem with WKWebView, that I decided to use instead of UIWebView to avoid the pickers crash in iOS 8. There are two ways that I can think of:

  1. Use NSURLConnection to make the request and then fill the WKWebView with it's response data. You can find an example here: https://stackoverflow.com/a/10077796/4116680 (You only need connection:didReceiveData: and connectionDidFinishLoading: from the delegate if you don't use a self signed SSL certificate)
  2. Use a JavaScript to make the POST request. Here is an example:

Create file eg. "POSTRequestJS.html":

<html>
    <head>
        <script>
            //POST request example:
            //post('URL', {key: 'value'});
            function post(path, params) {
                var method = "post";
                var form = document.createElement("form");
                form.setAttribute("method", method);
                form.setAttribute("action", path);

                for(var key in params) {
                    if(params.hasOwnProperty(key)) {
                        var hiddenField = document.createElement("input");
                        hiddenField.setAttribute("type", "hidden");
                        hiddenField.setAttribute("name", key);
                        hiddenField.setAttribute("value", params[key]);

                        form.appendChild(hiddenField);
                    }
                }

                document.body.appendChild(form);
                form.submit();
            }
        </script>
    </head>
    <body>
    </body>
</html>

And in your code after where you want to load your request:

NSString *path = [[NSBundle mainBundle] pathForResource:@"POSTRequestJS" ofType:@"html"];
NSString *html = [[NSString alloc] initWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil];
WKWebView.navigationDelegate = self;
[WKWebView loadHTMLString:html baseURL:[[NSBundle mainBundle] bundleURL]];

Add method:

- (void)makePostRequest
{
    NSString *postData = [NSString stringWithFormat: @"email=%@&password=%@", email, password];
    NSString *urlString = @"http://materik.me/endpoint";
    NSString *jscript = [NSString stringWithFormat:@"post('%@', {%@});", urlString, postData];

    DLog(@"Javascript: %@", jscript);

    [WKWebView evaluateJavaScript:jscript completionHandler:nil];

    didMakePostRequest = YES;
}

And last add the WKNavigationDelegate:

- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation
{
    if (!didMakePostRequest) {
        [self makePostRequest];
    }
}
查看更多
Juvenile、少年°
3楼-- · 2019-01-16 12:10

Swift3

Change the baseurl and token to your needs.

extension MyWebView: WKNavigationDelegate {
    func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Swift.Void) {
        let request: URLRequest = navigationAction.request
        let urlString: String = request.url?.absoluteString ?? ""
        let baseurl: String = "http://materik.me/"
        let needsAuthorization: Bool = urlString.hasPrefix(baseurl)

        if !needsAuthorization {
            print("url doesn't need authorization. \(urlString)")
            decisionHandler(.allow)
            return
        }

        let headerFields: [String : String] = request.allHTTPHeaderFields ?? [:]
        //print("url: \(urlString) fields: \(headerFields)")

        if headerFields["Authorization"] != nil {
            print("already has authorization header. \(urlString)")
            decisionHandler(.allow)
            return
        }

        print("add authorization header. \(urlString)")
        let token: String = "sw_gu6GpGXRG50PR9oxewI"
        var mutableRequest = request
        mutableRequest.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
        decisionHandler(.cancel)
        webView.load(mutableRequest)
    }
}
查看更多
萌系小妹纸
4楼-- · 2019-01-16 12:11

This appears to be a bug.
https://bugs.webkit.org/show_bug.cgi?id=140188

Hopefully it will be addressed soon. In the meantime, reverting to UIWebView or implementing the workaround proposed by Spas Bilyarski in his answer seems to be the best options.

查看更多
5楼-- · 2019-01-16 12:17

workaround: trick by using html5 & javascript.

Add a html5 file with content below to your xcode project. To post data by using javascript & h5 form:

<html>
    <head>
        <script>
            //how to call: post('URL', {"key": "value"});
            function post(path, params) {
                var method = "post";
                var form = document.createElement("form");
                form.setAttribute("method", method);
                form.setAttribute("action", path);
                for(var key in params) {
                    if(params.hasOwnProperty(key)) {
                        var hiddenField = document.createElement("input");
                        hiddenField.setAttribute("type", "hidden");
                        hiddenField.setAttribute("name", key);
                        hiddenField.setAttribute("value", params[key]);
                        form.appendChild(hiddenField);
                    }
                }
                document.body.appendChild(form);
                form.submit();
            }
        </script>
    </head>
    <body>
    </body>
</html>

Load the h5 file to WKWebView:

WKWebViewConfiguration* config = [[WKWebViewConfiguration alloc] init];
config.preferences = [[WKPreferences alloc]init];
config.preferences.javaScriptEnabled = YES;
WKWebView* webView = [[WKWebView alloc] initWithFrame:[UIScreen mainScreen].bounds configuration:config];
webView.navigationDelegate = self;
[self.view addSubview:webView];
NSString *path = [[NSBundle mainBundle] pathForResource:@"JSPOST" ofType:@"html"];
NSString *html = [[NSString alloc] initWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil];
[webView loadHTMLString:html baseURL:[[NSBundle mainBundle] bundleURL]];

Prepare the parameters to post. ie. a string & an array of dictionary Note: when turn array to json string by using NSJSONSerialization, '\r' may be added automaticly. You must remove all the '\r' in the json string, or the javascript cannot be parsed correctly.

// parameters to post
NSString* name = @"Swift";
NSArray* array = @[@{@"id":@"1", @"age":@"12"}, @{@"id":@"2", @"age":@"22"}];
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:array options:NSJSONWritingPrettyPrinted error:nil];
NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
jsonString = [jsonString stringByReplacingOccurrencesOfString:@"\"" withString:@"\\\'"];
// trim spaces and newline characters
jsonString = [jsonString stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
jsonString = [jsonString stringByReplacingOccurrencesOfString:@"\r" withString:@""];
jsonString = [jsonString stringByReplacingOccurrencesOfString:@"\n" withString:@""];
NSString *postData = [NSString stringWithFormat: @"'name':'%@', 'contacts':'%@'", name, jsonString];
// page url to request
NSString *urlStr = @"http:api.example.com/v1/detail";
// javascript to evalute
NSString *jscript = [NSString stringWithFormat:@"post('%@',{%@});", urlStr, postData];
//NSLog(@"Javzascript: %@", jscript);

Put this in the WKWebView's delegate: didFinishNavigation

// call the javascript in step 3
(void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation {
     GCD_MAIN((^{
          [_web evaluateJavaScript:jscript completionHandler:^(id object, NSError * _Nullable error) {
               if (error) {
                   NSLog(@"----------->>>>>>>>>>>>> evaluateJavaScript error : %@", [error localizedDescription]);
               }
          }];
     }));
 }
查看更多
混吃等死
6楼-- · 2019-01-16 12:19

WKWebView.load method doesn't work with post request with post body. You have to use JavaScript to do the trick, check WKWebView.evaluateJavascript.

It maybe a bug, but Apple hasn't addressed it till now.

查看更多
祖国的老花朵
7楼-- · 2019-01-16 12:23

As the OP stated, I have also confirmed in Charles that the body is 0 bytes after webView.load(request).

There's a workaround for this WKWebView bug, we will initiate a POST request using URLSession convert the data returned by the server to String and instead of loading the url we will use loadHTMLString which will:

Set the webpage contents and base URL.

and the content is our converted string:

var request = URLRequest(url: URL(string: "http://www.yourWebsite")!)
request.httpMethod = "POST"
let params = "do=something&andAgain=something"
request.httpBody = params.data(using: .utf8)

let task = URLSession.shared.dataTask(with: request) { (data : Data?, response : URLResponse?, error : Error?) in
        if data != nil
        {
            if let returnString = String(data: data!, encoding: .utf8)
            {
                self.webView.loadHTMLString(returnString, baseURL: URL(string: "http://www.yourWebsite.com")!)
            }
        }
}
task.resume()
查看更多
登录 后发表回答