While using a 3rd party API, I have the requirement to cancel all traffic when a custom response header is set to a certain value. I am trying to find a nice place to do this check only once in my code (and not in every success/failure block, where it works fine). From what I understand, this could be done by overriding -(void)enqueueHTTPRequestOperation:(AFHTTPRequestOperation *)operation in my custom AFHTTPClient subclass, but when I implement it like that:
-(void)enqueueHTTPRequestOperation:(AFHTTPRequestOperation *)operation
{
NSLog(#"[REQUEST URL]\n%#\n", [operation.request.URL description]);
NSLog(#"[RESPONSE HEADERS]\n%#\n", [[operation.response allHeaderFields] descriptionInStringsFileFormat]);
[super enqueueHTTPRequestOperation:operation];
}
the response headers are nil. Can anybody help me with that?
At the moment when operations are being created and enqueued in AFHTTPClient, they will not have the response from the server--that will be assigned when the request operation is actually executed.
Although the requirement to cancel all traffic seems unorthodox (at least if outside of the conventions of HTTP), this is easy to accomplish:
In your AFHTTPClient subclass, add a BOOL property that stores if requests should be prevented, and then used in enqueueHTTPRequestOperation. Then, override HTTPRequestOperationWithRequest:success:failure: to execute the specified success block along with some logic to set the aforementioned property if the salient response is present.
Related
I have the following code below that loops through an array. I need to check if the finish or fail selector has been called iterating to the next object in my dataArray.
for (id object in dataArray) {
[client setDidFinishSelector:#selector(getDataFinish:)];
[client setDidFailSelector:#selector(getDataFail:)];
[client getData:object];
}
In my getDataFinish method I assign values and I am trying to keep it in order. If I use the above method, the values can get out of order since the client response time can be different for each request..
I see two possible solutions, depending on what you're actually trying to do. It sounds like you're making calls to the internet, so yes you will get varied response time (or no response at all). Because of this, I would recommend using NSNotification. See this answer for more information about that.
Another option is making a flag in your code (AKA a BOOL) that you set to YES when your method has completed. Again, if you're making calls to the web I would not recommend this method as you are setting yourself up for an infinite loop if the user has no service and the BOOL never changes.
If you are still having trouble let me know and I can provide a more detailed answer.
It seems to me that the proper place to do this is in AFURLSessionManager, in setting the taskWillPerformHTTPRedirection block, but I am unsure of the best way to handle it.
Currently, in my AFHTTPSessionManager subclass, I am setting the redirect block globally for all requests, and I know I can prevent redirects by returning nil here:
- (void)setupRedirectBlock {
[self setTaskWillPerformHTTPRedirectionBlock:^NSURLRequest *(NSURLSession *session, NSURLSessionTask *task, NSURLResponse *response, NSURLRequest *request) {
return nil;
}];
}
...but I need to only do this on specific tasks, and there doesn't appear to be a way to get this information from the task itself.
I guess I am looking for some sort of user info dictionary or something I can use to set a flag telling this method to either return the request or return nil. Currently, it looks like I would have to do a string comparison on the response/request URL in the client where it is far away from where the task and path is actually created.
So this begs the question, am I fighting convention, or is there really no better way to intercept an AFNetworking 2.0 redirect on a task-by-task basis?
setTaskWillPerformHTTPRedirectionBlock is the best way to intercept redirects. The session manager is responsible for determining when or when not to prevent redirects based on the request associated with the task. In most cases, the path of the request should be a sufficient determination, but the user could additionally tag information in a custom request header field.
I have the same question, but unfortunately I don't yet have a good enough answer. One workaround could be using taskDescription https://developer.apple.com/library/ios/documentation/Foundation/Reference/NSURLSessionTask_class/Reference/Reference.html#//apple_ref/occ/instp/NSURLSessionTask/taskDescription
Just put there some constant like DO_NOT_FOLLOW_REDIRECT and check for it in your setTaskWillPerformHTTPRedirectionBlock block.
My example on iOS 6:
10 Multi-Part requests need to be sent (in order) to the server.
(so the request forms a queue)
progress should be shown.
if one request fails all following should fail
a request queue should be cancellable
Can AFNetworking help me with this? Or should I try to build something with NSOperations and run the loops myself?
If I need to pass context data between theses requests for example a transaction id produced by the first request. Are there any considerations about thread visibility I need to consider?
AFNetworking can do this. I recommend that you use AFHTTPRequestOperationManager (which itself uses NSOperation), rather than AFHTTPSessionManager. There are ways to do it with AFHTTPSessionManager, but none as elegant as with operations.
Under the hood, here's what you'd do without the manager:
You will use a request serializer to make your NSMutableURLRequest (for example, [AFHTTPRequestSerializer -multipartFormRequestWithMethod:URLString:parameters:constructingBodyWithBlock:error:]; there's a similar JSON request serializer too).
Once you have a URL Request, make the operation with [AFHTTPRequestOperation -initWithRequest:]. You should also set its completion blocks.
Finally, add your operation to [AFHTTPRequestOperationManager manager].operationQueue and start it.
Now that you understand how this is basically all working together, here's a simpler approach:
Subclass AFHTTPRequestOperationManager, optionally setting the requestSerializer if you don't like the default
Override (or copy with new implementation) -POST:parameters:constructingBodyWithBlock:success:failure:] - what you want to do is NOT start your operation right away.
Set the NSOperation dependency chains
start the first one
I need to pass some extra informations along with UIWebView loadRequest: so that it reaches my implementation of NSURLProtocol. The information cannot be bound to NSURLRequest because the information must be retained with NSURLRequest mainDocumentURL as well. So i subclassed NSURL and constructed NSURLRequest with it. I already knew that the NSURLRequest which reaches NSURLProtocol startLoading is NOT the instance i have fed to UIWebView loadRequest, so i implemented NSURL copyWithZone too, naively expecting that URL loading system will use it.
Now, NSURLProtocol canInitWithRequest is called not once as one would reasonably expect, but at least 4 times before startLoading. First 2 times of that, the incoming NSURLRequest still contains my custom NSURL implementation. Then an unfortunate internal code called CFURLCopyAbsoluteURL asks for the absoluteURL of my custom NSURL and the next canInitWithRequest (and subsequent startLoading) already gets a completely new NSURLRequest with fresh NSURL in it. copyWithZone is never called and my subclassed NSURL is lost.
Before i give up and implement an inferior and fragile solution with attaching stuff directly to the URL string, i would like to ask the wizards of higher level, whether they see a way how to catch that initial blink on the NSURLProtocol radar or how to trick CFURLCopyAbsoluteURL into carrying my custom instance. I have tried to hack NSURL absoluteURL by returning again a new instance of my custom NSURL class, but it didn't help. I have seen some promise in NSURLProtocol setProperty functionality, but now it appears pretty useless. URL loading system creates new instances of everything happily and NSURLRequest arrived in NSURLProtocol seems to be the same as the one entered into UIWebView only accidentally.
UPDATE: ok i wanted to keep the post as short as possible, but the even the first reply is asking for technical background, so here we go: i've got multiple UIWebViews in app. These views may run requests concurrently and absolutely can run requests for the same URL. It's like tabs in desktop browser. But i need to distinguish which UIWebView was the origin of each particular NSURLRequest arriving to the NSURLProtocol. I need a context being carried with each URL request. I can't simply map the URLs to data, because multiple UIWebViews may be loading the same URL at any moment.
UPDATE 2: Attaching the context information to NSURL is preferred and, as far as my understanding goes, the only usable. The issue is that requests for resources referenced inside page (images etc.) do not go through UIWebViewDelegate at all and end up in NSURLProtocol directly. I don't have a chance to touch, inspect or modify such requests anywhere prior to NSURLProtocol. The only contextual link for such requests is their NSURLRequest mainDocumentURL.
If there's some way to get your original NSURL used as mainDocumentURL that would be ideal. If there's no way to prevent it being copied, I thought of the following hack as an alternative:
Before the creation of each UIWebView, set the user agent string to a unique value. Supposedly this change only affects UIWebView objects that are created subsequently, so each view will end up with its own distinctive user agent string.
In the NSURLProtocol implementation, you can check the user agent string to identify the associated UIWebView and pass it through to the real protocol handler using the actual user agent string (so the server will see nothing different).
All this depends on the views really ending up with different UA strings. Let me know if you manage to get it to work!
You say that you can't put it on the NSURLRequest, but I'm not clear why from your updated discussion. That would be the most natural place to put it.
Implement webView:shouldLoadWithRequest:navigationType:.
Attach an extra property to the provided request using objc_setAssociatedObject. Then return YES. (It would be nice to use setProperty:forKey:inRequest: here, but UIWebView passes us a non-mutable request, so we can only attach associated objects. Yet another way that UIWebView is a pale shadow of OS X's WebView, which can handle this).
In the NSProtocol, read your extra property using objc_getAssociatedObject. The request should be the same one you were presented earlier. You suggest that this isn't the case. Are you saying that the request at webView:shouldLoadWithRequest:navigationType: is different than the request at initWithRequest:cachedResponse:client:?
Am I missing another requirement or quirk?
You can pass options through custom request headers, assuming that the targeted website or service provider don't somehow strip those in transit.
The challenge there would be coming up with an encoding scheme that can be reasonable encoded into an ASCII string for the header field value and then decoded into the actual value you want. For this, a custom NSValueTransformer would seem most appropriate.
I had the same problem. I finally stuck to the solution suggested by Matthew (using the user agent string). However, as the solution is not fleshed out, I add a new answer with more details. Furthermore, I found out that you do not need to send a request to make the user agent "stick". It is sufficient to get via javascript as suggested here.
Following steps worked for me:
(1) Get the current default user agent. You need it later to put it back into the request in NSURLProtocol. You need to use a new webview insatnce, as getting the user agent will make it stick to the webview, so you can not change it later on.
UIWebView* myWebview = [[UIWebView alloc] init];
NSString* defaultUserAgent = [myWebview stringByEvaluatingJavaScriptFromString:#"navigator.userAgent"];
[myWebview release]; // no needed with ARC, but to emphasize, that the webview instance is not needed anymore
(2) Change the value in the standardUserDefaults (taken from here).
NSDictionary* userAgentDict = [NSDictionary dictionaryWithObjectsAndKeys:#"yourUserAgent", #"UserAgent", nil];
[[NSUserDefaults standardUserDefaults] registerDefaults:userAgentDict];
(3) Make the new user agent string stick to your webView by getting it via javascript as done in (1), but this time on the webview instance you actually work with.
(4) Revert the default user agent in the standardUserDefaults as done here.
I'm writing a ios app which has to send and receive data from the API at various screens in the app. Currently each view controller is calling this code
// AFAppDotNetAPIClient is a subclass of AFHTTPClient, which defines the base URL and default HTTP headers for NSURLRequests it creates
[[AFAppDotNetAPIClient sharedClient] getPath:#"stream/0/posts/stream/global" parameters:nil success:^(AFHTTPRequestOperation *operation, id JSON) {
NSLog(#"App.net Global Stream: %#", JSON);
} failure:nil];
I want to keep things DRY and so I created a requested builder and response handler to create request and parse responses. I also want to move all the API calls to one class but since it uses blocks I don't know how do this.
Can someone explain how this is done so I call one method with a enum and some params for request and I probably just get a NSDictionary back without having API calls and blocks in all view controllers. Thanks
This is a concern for the Model part of your MVC architecture. The example project has a good implementation of this:
Post.h
+ (void)globalTimelinePostsWithBlock:(void (^)(NSArray *posts, NSError *error))block;
Define class methods on the model that take care of making requests (translating any method parameters into request params) and serializing objects from the response.
Define your new interface to include your enum, parameters and a block. Define the block to have a BOOL (status) and an NSDictionary (result). This is basically just simplifying and abstracting the interface away from the client, path and operation. Then in the success and failure blocks you can call the outer block with appropriate parameters.
The block is required to maintain the asynchronous nature of the interface. You could use a target and selector but that will be more code and less flexible. You can't have the method just return the resulting dictionary.
There are obviously many other options for what parameters to pass to the method and in the block.