CustomHTTPProtocol

前言

在每一个 HTTP 请求开始时,URL 加载系统创建一个合适的 NSURLProtocol 对象处理对应的 URL 请求; 而我们需要做的就是写一个继承自 NSURLProtocol 的类,并通过 - registerClass: 方法注册我们的协议类; 然后 URL 加载系统就会在请求发出时使用我们创建的协议对象对该请求进行处理。 这样,我们需要解决的核心问题就变成了如何使用 NSURLProtocol 来处理所有的网络请求,

本文主要是对官网提供的CustomHTTPProtocol 进行分析它是如何实现 intercept the HTTP/HTTPS requests。

CustomHTTPProtocol shows how to use an NSURLProtocol subclass to intercept the HTTP/HTTPS requests made by a high-level subsystem that does not otherwise expose its network connections.  

目的是让我们以后更快速利用NSURLProtocol解决以下几个问题

  • 如何决定哪些请求需要当前协议对象处理?
    <tt>+canInitWithRequest:</tt>
    
  • 对当前的请求对象需要进行哪些处理?
+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request;
  • NSURLProtocol 如何实例化?
- (instancetype)initWithRequest:(NSURLRequest *)request cachedResponse:(nullable NSCachedURLResponse *)cachedResponse client:(nullable id <NSURLProtocolClient>)client NS_DESIGNATED_INITIALIZER;

NSURLProtocol

NSURLProtocol 是URL Loading System的的组成部分。

/*! 
    @method registerClass:
    @abstract This method registers a protocol class, making it visible
    to several other NSURLProtocol class methods.
    @discussion When the URL loading system begins to load a request,
    each protocol class that has been registered is consulted in turn to
    see if it can be initialized with a given request. The first
    protocol handler class to provide a YES answer to
    <tt>+canInitWithRequest:</tt> "wins" and that protocol
    implementation is used to perform the URL load. There is no
    guarantee that all registered protocol classes will be consulted.
    Hence, it should be noted that registering a class places it first
    on the list of classes that will be consulted in calls to
    <tt>+canInitWithRequest:</tt>, moving it in front of all classes
    that had been registered previously.
    <p>A similar design governs the process to create the canonical form
    of a request with the <tt>+canonicalRequestForRequest:</tt> class
    method.
    @param protocolClass the class to register.
    @result YES if the protocol was registered successfully, NO if not.
    The only way that failure can occur is if the given class is not a
    subclass of NSURLProtocol.
*/
+ (BOOL)registerClass:(Class)protocolClass;

CustomHTTPProtocol

CustomHTTPProtocol.h


@protocol CustomHTTPProtocolDelegate;

/*! An NSURLProtocol subclass that overrides the built-in HTTP/HTTPS protocol to intercept 
 *  authentication challenges for subsystems, ilke UIWebView, that don't otherwise allow it.  
 *  To use this class you should set up your delegate (+setDelegate:) and then call +start. 
 *  If you don't call +start the class is completely benign.
 *
 *  The really tricky stuff here is related to the authentication challenge delegate 
 *  callbacks; see the docs for CustomHTTPProtocolDelegate for the details.
 */

@interface CustomHTTPProtocol : NSURLProtocol

/*! Call this to start the module.  Prior to this the module is just dormant, and 
 *  all HTTP requests proceed as normal.  After this all HTTP and HTTPS requests 
 *  go through this module.
 */

+ (void)start;

CustomHTTPProtocol.m

+ (void)start
{
    [NSURLProtocol registerClass:self];
}
  • 修改NSURLRequest 对象,在CanonicalRequestForRequest 方法中,可以修改 request headers。
+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request
{
    NSURLRequest *      result;
    
    assert(request != nil);
    // can be called on any thread
    
    // Canonicalising a request is quite complex, so all the heavy lifting has 
    // been shuffled off to a separate module.
    
    result = CanonicalRequestForRequest(request);

    [self customHTTPProtocol:nil logWithFormat:@"canonicalized %@ to %@", [request URL], [result URL]];
    
    return result;
}

AppDelegate

- (BOOL)application:(UIApplication *)application willFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    #pragma unused(application)
    #pragma unused(launchOptions)
    WebViewController *   webViewController;
    
    assert(self.window != nil);
    
    sAppStartTime = [NSDate timeIntervalSinceReferenceDate];
    
    self.credentialsManager = [[CredentialsManager alloc] init];

    // Prepare the globals needed by our logging code.  The call to -threadInfoForCurrentThread 
    // sets up the main thread's thread info record and ensures it has a thread number of 0.

    self.threadInfoByThreadID = [[NSMutableDictionary alloc] init];
    (void) [self threadInfoForCurrentThread];
    
    // Start up the core code.  Change the if expression to NO to disable the CustomHTTPProtocol for 
    // comparative testing and so on.
    
    [CustomHTTPProtocol setDelegate:self];
    if (YES) {
        [CustomHTTPProtocol start];
    }
    
    // Create the web view controller and set up the UI.  We do this after setting 
    // up the core code in case this triggers any HTTP requests.
    // 
    // By default the Test button is not shown because this sample is focused on UIWebView.  
    // If you want to runs tests with NSURL{Session,Connection}, change the if expression to 
    // show the Test button and then configure the test by changing the code in -testAction:.
    
    webViewController = [[UIStoryboard storyboardWithName:@"Main" bundle:[NSBundle bundleForClass:[self class]]] instantiateViewControllerWithIdentifier:@"webView"];
    assert(webViewController != nil);
    webViewController.delegate = self;
    if (NO) {
        webViewController.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"Test" style:UIBarButtonItemStyleBordered target:self action:@selector(testAction:)];
    }
    [((UINavigationController *) self.window.rootViewController) pushViewController:webViewController animated:NO];

	[self.window makeKeyAndVisible];
    
    return YES;
}

CanonicalRequest

CanonicalRequest.h

extern NSMutableURLRequest * CanonicalRequestForRequest(NSURLRequest *request)

CanonicalRequest.m

extern NSMutableURLRequest * CanonicalRequestForRequest(NSURLRequest *request)
static void CanonicaliseHeaders(NSMutableURLRequest * request)

动手实践

写一个tweak ,修改请求的HTTPHeaderField

Q&A

  • createEncodedCachedResponseAndRequestForXPCTransmission
     ERROR: createEncodedCachedResponseAndRequestForXPCTransmission - Invalid protocol-property list - CFURLRequestRef. protoProps=<CFBasicHash 0x14526560 [0x39487460]>{type = mutable dict, count = 3,
      entries =>
          0 : <CFString 0xa8f968 [0x39487460]>{contents = "networkProtocolCount"} = <CFString 0xa81038 [0x39487460]>{contents = "1"}
          1 : <CFString 0xa8e4c8 [0x39487460]>{contents = "network-send-type"} = <CFString 0xa91228 [0x39487460]>{contents = "spdy"}
          2 : <CFString 0xa8f258 [0x39487460]>{contents = "connection_register"} = <TBSDKRequestDelegate: 0x1452d710>
      }
    
  • NSURLProtocol ``` %hook NSURLProtocol
  • (void)setProperty:(id)value forKey:(NSString *)key inRequest:(NSMutableURLRequest *)request{

    %log(); %orig; }

%end




### 注意

>* 1、NSURLProtocol 只能拦截 UIURLConnection、NSURLSession 和 UIWebView 中的请求;
对于 WKWebView 中发出的网络请求也无能为力,如果真的要拦截来自 WKWebView 中的请求,还是需要实现 WKWebView 对应的 WKNavigationDelegate,并在代理方法中获取请求。

>* 2、[OHHTTPStubs](https://github.com/zhangkn/OHHTTPStubs) 就是基于NSURLProtocol基础上实现的

提供不需要后端 API 的情况下,在本地对 HTTP 请求进行拦截,返回想要的 json 数据 ,常常运用于HTTP Mock 中。

  • OHHTTPStubsProtocol 拦截 HTTP 请求
  • OHHTTPStubs 单例管理 OHHTTPStubsDescriptor 实例
  • OHHTTPStubsResponse 伪造 HTTP 响应 ```

参考资源

转载请注明: > CustomHTTPProtocol

在操作过程或者文章有问题的话欢迎在 原文 里提问或指正。

赞赏支持

取消

感谢您的支持,我会继续努力的!

扫码支持
扫码支持
扫码打赏,你说多少就多少
最近的文章

dumpdecrypted

前言本文重点推荐使用frida-ios-dump-master,而非dumpdecrypted.dylib。frida-ios-dump-master使用frida-ios-dump-master 只需先用frida-ps查看applications Name ,之后执行dump.py 即可在dump.py 目录下生成砸壳之后的ipa包。Frida环境的搭建可以看下这篇文章dumpdecrypted.dylib *dumpdecrypted的原理:通过向宏 DYLD_INSERT_LIB...…

iOSre继续阅读
更早的文章

WebSocket

前言websocket 是一种网络通信协议,提供全双工通信信道,以基于事件的方式,赋予浏览器实时通信能力。协议标识符是ws(如果加密,则为wss),服务器网址就是 URL;Socket 与 WebScoketSocket 其实并不是一个协议。它工作在 OSI 模型会话层(第5层),是为了方便大家直接使用更底层协议(一般是 TCP 或 UDP )而存在的一个抽象层。而 WebSocket 则不同,它是一个完整的 应用层协议,包含一套标准的 API 。所以,从使用上来说,WebSocket 更...…

iOSre继续阅读
更多