IOS development using NSURLProtocol to intercept HTTP requests

IOS development using NSURLProtocol to intercept HTTP requests
intercept

This article will provide a way to intercept all HTTP requests in the Cocoa layer, in fact, the title has been described to intercept the HTTP request needs to understand is NSURLProtocol.

Because the content of the article is longer, will be divided into two parts, this article introduces the principle of NSURLProtocol to intercept HTTP requests, another article about HTTP Mock introduces the application of this principle in OHHTTPStubs, it is how Mock (forgery) a HTTP request to the corresponding response.

NSURLProtocol

NSURLProtocol is part of Apple’s URL Loading System for us, this is a picture posted from the official documents:

IOS development using NSURLProtocol to intercept HTTP requests
URL-loading-syste

The official document describes the NSURLProtocol as such:

An NSURLProtocol object handles the loading of protocol-specific URL data. The NSURLProtocol class itself is an abstract class that provides the infrastructure for processing URLs with a specific URL scheme. You create subclasses for any custom protocols or URL schemes that your app supports.

In each HTTP request at the start of the URL loading system to create an appropriate NSURLProtocol object processing corresponding to the URL request, and we need to do is write a class that inherits from NSURLProtocol, and the registerClass: method to register our protocol, then URL loading system will be in the request when we create the protocol object to handle the request.

So, we need to address the core issue is how to use NSURLProtocol to handle all network requests, here use Apple’s official document in CustomHTTPProtocol, you can click here to download the source code.

In this project CustomHTTPProtocol.m is the need to focus on the document, CustomHTTPProtocol is a subclass of NSURLProtocol:

@interface CustomHTTPProtocol: NSURLProtocol... @end

Now back to the problem, that is, how to use NSURLProtocol to intercept HTTP requests? , there are several problems need to be resolved:

  • How do you decide which requests need to be processed by the current protocol object?
  • What needs to be done for the current request object?
  • NSURLProtocol how to instantiate?
  • How do I send a HTTP request and pass it on to the caller?

The above problems can actually be provided by the NSURLProtocol for us to solve the API, the decision to request the current protocol object processing methods are: + canInitWithRequest:

(BOOL) + canInitWithRequest: (NSURLRequest * request) {BOOL shouldAccept; NSURL *url; NSString *scheme; shouldAccept = (request! = Nil); if (shouldAccept) {URL [request = URL]; shouldAccept = (URL! = Nil);} return shouldAccept;}

Because of this method in the project is about more than and 60 lines, here only pasted a part of them, just to show that the method is: every request will have a NSURLRequest instance, the method will get all of the requested object, we can according to the corresponding request to choose whether or not the object; the above code will handle all the URL request is not empty.

After the request after + canInitWithRequest: filtering method, we get all the request to be processed, then need a certain operation request, and it will be at + canonicalRequestForRequest:, although it introduced and + canInitWithRequest: method of the request object is an object, but it is best not to operate in the + canInitWithRequest:, there may be the semantics of the problem; therefore, we need to override the request object + canonicalRequestForRequest: method provides a standard:

+ (NSURLRequest *) canonicalRequestForRequest: (NSURLRequest *) request {return request;}

Here do not make any changes to the request, direct return, of course, you can also add a header to the request, as long as the final return of a NSURLRequest object can be.

After getting the requested object, you can initialize a NSURLProtocol object:

- (ID) initWithRequest: (NSURLRequest * request) cachedResponse: (NSCachedURLResponse *) cachedResponse client: (ID < NSURLProtocolClient> client) {return [super initWithRequest:request cachedResponse:cachedResponse client:client];}

In this way, the constructor method of super is called directly, and an object is instantiated, and then it enters the stage of sending the network request, getting the data and returning:

- (void) startLoading [NSURLSession sessionWithConfiguration:[[NSURLSessionConfiguration alloc] {NSURLSession *session = init] delegate:self delegateQueue:nil]; NSURLSessionDataTask *task = [session dataTaskWithRequest:self.request]; [task resume];}

The use of CustomHTTPClient in the project code, can achieve almost the same effect.

You can use any method in startLoading to hold on the request forwarding protocol object, including NSURLSession, NSURLConnection and AFNetworking network library, as long as you can send data to the client in a callback method, which can help correct rendering, such as:

- (void) URLSession: (NSURLSession * session) dataTask: (NSURLSessionDataTask * dataTask) didReceiveResponse: (NSURLResponse * response) (completionHandler: (void ^) (NSURLSessionResponseDisposition)) completionHandler client] URLProtocol:self didReceiveResponse:response {[[self cacheStoragePolicy:NSURLCacheStorageAllowed]; completionHandler (NSURLSessionResponseAllow);} - (void) URLSession: (NSURLSession * session) dataTask: (NSURLSessionDataTask * didReceiveData: (dataTask) NSData * data [[self client] URLProtocol:self) {didLoadData:data]};

Of course, here omitted code only to ensure the correct implementation in most cases, just to give you a response to obtain the rough data of cognition, if you need more detailed code, I thought I’d better check CustomHTTPProtocol in response to HTTP processing code, which is part of the NSURLSessionDelegate protocol.

Client you can be understood as the initiator of the current network request, all client realization of the NSURLProtocolClient protocol, the protocol is the role in HTTP request and receiving the response when transmission to other data objects:

@protocol NSURLProtocolClient < NSObject>... - (void) URLProtocol: (NSURLProtocol * protocol) didReceiveResponse: (NSURLResponse *) response cacheStoragePolicy: (NSURLCacheStoragePolicy) policy; (void) - URLProtocol: (NSURLProtocol * protocol) didLoadData: (NSData * data); - (void) URLProtocolDidFinishLoading: (NSURLProtocol *) protocol; @end...

Of course, there are many other methods in this protocol, such as HTTPS verification, redirection, and response caching related methods, you need to call these agents at the appropriate time, the information transfer.

If you just inherited the NSURLProtocol and achieved the above method, still can not achieve the desired effect, to complete the interception of the HTTP request, you also need to register the current class in the URL loading system:

[NSURLProtocol registerClass:self];

Note that NSURLProtocol UIURLConnection, NSURLSession and UIWebView can only intercept the request for WKWebView in a network request also incapable of action, if you really want to intercept from WKWebView’s request, or need to implement the WKWebView corresponding to the WKNavigationDelegate, and the agent acquisition request method.
NSURLProtocol, NSURLConnection NSURLSession or both will go at the bottom of the socket, but may be due to WKWebView based on WebKit, and C socket will not perform the related function of HTTP request processing, the specific code will execute what temporarily is not very clear, if interested readers can contact the author discuss.

summary

If you just want to know how to intercept HTTP requests, you can see here, but if you want to use the contents of the article or want to learn how to fake the HTTP response, can see an article about HTTP Mock.

References

+ NSURLProtocol

Github Repo:iOS-Source-Code-Analyze Draveness Follow: Github Source: http://draveness.me/intercept