How can we retrieve the NSURLRequest that failed to load when the UIWebViewDelegate is called back with didFailLoadWithError (which gets only the NSError) ? We could stash the NSURLRequest when shouldStartLoadWithRequest is called, but what if the UIWebView is loading multiple requests simultaneously (e.g., a page with scripts, stylesheets, etc.) How do we determine which URL failed to load?
Hmm... looks like the NSError has a NSErrorFailingURLKey key with the URL which is good enough for now... but is there a way to directly get the NSURLRequest?
Can you get it from the webView? webView.request?
Related
I sent request to url1. url1 will redirect to url2 with cookie. url2 is for authorization. And I get code "302 found", which is correct.
But when url2 redirect back to url1, the cookie lost. This results my request keeping redirect to url2, looping until failed.
Does any one met this kind of problem and know a solution? Thanks in advance.
This is a WKWebView problem. UIWebView will work fine. But somehow, I need to change to use WKWebView.
I already tried many solutions, such as Can I set the cookies to be used by a WKWebView?. These methods could handle the cookie in same domain. My problem is url1 and url2 have different domains. When redirect, the cookie is missing, which made the authorization failed, and resulted in looping between url1 and url2.
Does anybody met this problem and found out a workaround? Thanks in advance.
Unfortunately, there is no official support for handling 302 redirect response in WKWebView.I came across the same issue too.There is a way to work around but may take some risks and not elegant.
Override the WKWebView.load(_ request: URLRequest) and use URLSession to make the request instead.
Make sure your URLSession can deal with 302 redirect by yourself which means you need to implement func urlSession(_ session: URLSession, task: URLSessionTask, willPerformHTTPRedirection response: HTTPURLResponse, newRequest request: URLRequest, completionHandler: #escaping (URLRequest?) -> Void) to specify that you don't want the URLSession to handle 302 redirect automatically.
Once you get the 302 response using URLSession the attached cookie will be set to HTTPCookieStorage.shared automatically.And then make a new request with those cookies and the url of response header field Location by WKWebView itself.
The code sample is Here.I subclass WKWebView and also deal with cookies in the most case including your 302 redirect case.If it works for you, please give me a star thx!
It is a really a hack way and be careful if you want use it in product.
If you need subsequent requests from 'url2' to have their cookies set, you can try using 'WKUserScript' to set the values programmatically via javascript at document start like so:
WKUserContentController* userContentController = WKUserContentController.new;
WKUserScript * cookieScript = [[WKUserScript alloc]
initWithSource: #"document.cookie = 'TeskCookieKey1=TeskCookieValue1';document.cookie = 'TeskCookieKey2=TeskCookieValue2';"
injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:NO];
// again, use stringWithFormat: in the above line to inject your values programmatically
[userContentController addUserScript:cookieScript];
WKWebViewConfiguration* webViewConfig = WKWebViewConfiguration.new;
webViewConfig.userContentController = userContentController;
WKWebView * webView = [[WKWebView alloc] initWithFrame:CGRectMake(/*set your values*/) configuration:webViewConfig];
However, if you require your cookies to be set on the initial load request, you can set them on NSMutableURLRequest like so:
WKWebView * webView = /*initialization stuff*/
NSMutableURLRequest * request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:#"YOUR URL"]];
[request addValue:#"TestCookieKey1=TestCookieValue1;TestCookieKey2=TestCookieValue2;" forHTTPHeaderField:#"Cookie"];
// use stringWithFormat: in the above line to inject your values programmatically
[webView loadRequest:request];
You can combine above two techniques to transfer cookie values from Native environment to WebView environment.
You can refer to this link for more advanced information on cookies.
Maybe late to the party, but I faced something pretty close to the above question last week and also from my side I concluded that for some reason cookies on 302 response are not handled correctly by WKWebView.
If your problem is similar to the one i'm facing with:
you need to pass some cookies from a redirect required for a subsequent authentication stage
Then you can easily interchange your WKWebView with the one I provided into this gist.
and use it like follow:
let myWebView = SAWKWebView(frame: .zero)
...
myWebView.load(aRequest, handleRedirect: true, cookies: [myCookies])
cookies is not required and can be nil, depending on your setup.
SAWKWebView class inheriting from WKWebView and workaround the above problem by using directly URLSession and its delegate.
This way I could rely into a sufficient and safe way to break into the response 302 (aka redirect) and handle cookies manually with HTTPStorageCookie without relying onto javascript and document since in my experience it doesn't always work reliable.
Once the required redirect is performed I render the content as HTML and continue as needed.
Another solution worked for me:
Adopt WKNavigationDelegate, webView.navigationDelegate = self, then:
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: #escaping (WKNavigationActionPolicy) -> Void) {
/// Cancel the navigation action
decisionHandler(.cancel)
/// Navigate yourself
let request = NSMutableURLRequest(url: url)
request.allHTTPHeaderFields = HTTPCookie.requestHeaderFields(with: Auth.getAuthCookies())
webView.load(request as URLRequest)
}
This was all that was needed for my simple case, but I imagine others may also need to copy the other request info that's stored in navigationAction.request like the navigationAction.request.allHTTPHeaderFields
I've tried the solutions suggested in the other answers but finally the one that seems to work better in my case was implement this WKNavigationDelegate's method:
func webView(_ webView: WKWebView, didReceiveServerRedirectForProvisionalNavigation navigation: WKNavigation!) {
if let url = webView.url {
webView.load(authenticatedRequest(URLRequest(url: url)))
}
}
Where authenticatedRequest() is a private function that given a request, it returns the same request with the headers/cookies/whatever your service needs to authenticate.
Hope someone find this solution helpful.
recently i faced same problem and i fixed this problem by doing this.I am sure cookie lost happen only if you are using iOS < 11, for this you need to apply some work around as i applied recently.
1- pass updated cookies in response from server side.Whenever it changed from server,
Read cookie from response like this way.
if let allHttpHeaders = response.allHeaderFields as? [String: String] {
let cookies = HTTPCookie.cookies(withResponseHeaderFields: allHttpHeaders, for: responseUrl)
for cookie in cookies {
if cookie.name == "cookie name" {
// compare with old cookie value so it will show your cookie is changed now.
if "your_old_cookie_value" != cookie.value {
//update cookie in Script and request header
// reload your page here again.
break
}
}
}
}
This is the minimal change by that you can fix cookie lost issue, for iOS >=11 should not be an issue, for ios 11 and above use httpcookiestore
Let me know if you have any question.
I suppose the proper solution for iOS 11 is to use the WKHTTPCookieStore. It's designed to solve exactly this issue as per this keynote.
I need response headers of the request URL that I need to recieve and process in UIWebview Delegate method webViewDidFinishLoad: ? But I don't understand how to achieve it. Please note that I don't need request headers but response headers.
One idea is to create a NSUrlConnection and fire the request again to recieve it in completion block but I don't want to make two calls. There must be some way that it is handled by UIWebView and response headers can be retrieved . I just don't know how to go about it. Any ideas are welcome.
Try this:
NSCachedURLResponse *resp = [[NSURLCache sharedURLCache] cachedResponseForRequest:webView.request];
But this only works for cacheable urls!
I have a UIWebview in my iOS app that needs to monitor the response headers for each page it goes to, I have tried to look for it on
webViewDidFinishLoad
and
shouldStartLoadWithRequest
but have not gotten far, further research has lead me to
didReceiveResponse
for NSURLConnection, but my understanding is that this can only get the first response, and can not get the second, or further responses.
If it would help any, I'm using Rubymotion to implement this.
It's not possible, I'm afraid. You can get the current web URL using JavaScript:
https://stackoverflow.com/a/2494051/204044
(or anything else that JS can access, for that matter)...but you're not able to get the headers. This is a security thing.
If you have control over the web server, you could pass in information that you need in a javascript object like this:
// In your webserver's code, assuming it's PHP:
<script type="text/javascript">
window.customDataString = "<?= $custom_data ?>";
</script>
// In Objective-C
NSString *customDataString = [webView stringByEvaluatingJavaScriptFromString:#"window.customDataString"];
I do something similar by using AFNetworking.
I use webView:shouldStartLoadWithRequest:navigationType: to capture all the link clicked by users. I return NO at shouldStartLoadWithRequest so the UIWebview will stay idle. Then, I get the NSURLRequest and get everything I need including HTML source by AFNetworking.
At last, I put HTML string back to UIWebView by loadHTMLString:baseURL:.
User just appear to surf webpage as usual. They don't know I bypass UIWebview and just use UIWebview to display HTML.
In delegate, register your custom NSURLProtocol subclass, then you can monitor responses with:
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
I have a webview to show web content, let said google.com. I use
NSURLRequest *request = [NSURLRequest requestWithURL:url
cachePolicy:NSURLRequestReturnCacheDataElseLoad timeoutInterval:30];
[storeWebView loadRequest:request];
on the viewDidLoad function. BUT when I click another button, which is in the web page (not the UIButton), how to I 'save' the cache and when I click another button, or back button, it will find and retrieve the cache?
My understanding is the system will cache the page for you, and if you use same request again, it will first look in the system cache, and if the page is there it will load it up from cache...
to clear cache, use [[NSURLCache sharedURLCache] removeAllCachedResponses];
if you want more control, take a look at NSURLCache Class Reference
I am trying to get an html response from a server(aspx).
NSString *urlstr = [[NSString alloc] initWithFormat:#"http://www.someSiteThatRedirectsWithGetParameters.com?parameter1=1¶meter2=2"];
NSURL *url = [ NSURL URLWithString:urlstr];
NSMutableURLRequest *urlRequest = [ NSURLRequest requestWithURL: url];
NSData data = [NSURLConnection sendSynchronousRequest:urlRequest returningResponse:&response error:&error];
NSURL *lastURL=[response URL];
NSLog(#"Last url %#", lastURL);
I am getting lastURL as www.someSiteThatRedirectsWithGetParameters.com and therefore not getting the html page for the original requested url.
Why is this so and how can I solve this?
EDIT
This is all what I have tried so far : Get the last redirected url in iOS 5?
but I am still not able to achieve what I am trying to i.e : get the html page source of the original GET request(going through redirections) having parameters in the url ?
I am getting lastURL as www.someSiteThatRedirectsWithGetParameters.com
and therefore not getting the html page for the original requested
url.
If your request is being redirected, it's because the server to which you're connecting is telling you that there is no page at the URL to which you first made the request, and you should use the redirected URL instead. That's the whole point of a redirect response.
If the response that you get is a HTTP 303, the new request will properly be changed from POST to GET. This is sometimes done to avoid cases where refreshing the page would resubmit the original request with undesirable consequences. In this case, you POST your request to some URL, and the server accepts the request but responds by redirecting the client to a different URL which ultimately displays the server's response.
Why is this so and how can I solve this?
I've explained the 'why' above. The "solution" is to accept the response from the server and follow the redirect to the URL that contains the server's response.
I would try the approach of sending two requests. Send the first request, then the redirectURL will be the response. Then make a new URL of the response, sending a request to that url with the GET-parameters. I haven't tried it, but I suppose it could work.