AFNetwork 3.0 源码解读(三)AFURLRequestSerialization

开发中我们是使用的大多数是HTTP(HTTP,HyperText Transfer Protocol)协议,既然是协议就会有一些规范。每次客户端进行http/https请求时我们都要对请求进行设置,而而AFURLRequestSerialization就是帮助我们更快的完善请求的设置。

AFURLRequestSerialization顾名思义就是请求的序列化,用于帮助构建NSURLRequest,主要做了两个事情:
1.构建普通请求:格式化请求参数,生成HTTP Header。
2.构建multipart请求。


首先我们来看一下一个请求到底包含了那些东西。而AFURLRequestSerialization帮我们做了什么样的处理。随便找个app抓取一个请求。如下图一个普通的HTTP请求的样式。

这是截取了进入“蘑菇街”app登陆界面时的网络请求,在request下面可以看到下面的信息
GET /nmapi/user/v1/login/loginconfig_ab=1203&_app=mgj&_at=ee60c6c24e4df4fa&_atype=iphone&_av=728&_channel=NIMAppStore&_did=faf3eee959a29f4940eeb0d495567f88&_fs=NIMA
ppStore726&_lang=zh_CN&_network=2&_saveMode=0&_sdklevel=9.0&_swidth=1242&_t=1451446392&_version=7.2.8.1203&minfo=iPhone7%2C1  HTTP/1.1
Host: www.mogujie.com
Accept: */*
Cookie: __mgj_i_n=1; __mgjuuid=5068fb55-cd5e-c03b-cde9-4cba598825a1; _mg_tk=ebf1abe414
User-Agent: Mogujie4iPhone/7.2.8 (iPhone; iOS 9.0; Scale/3.00)
Accept-Language: zh-Hans-CN;q=1
Accept-Encoding: gzip, deflate
Connection: keep-alive

这里信息包括:请求方式(这里是GET),请求的URL ,HTTP的版本,Host, Accept, Cookie, User-Agent, Accept-Encoding,Connection,等,其中很大的一部分是于AFURLRequestSerialization来帮开发者构建。一般我们进行一个GET请求时,我们所传递的请求参数是拼接在URL中的,这部分的完成同样也是由AFNetworking来帮我们完成。


在AFURLRequestSerialization组成:

其它:

配置HTTP请求头相关:
  •   HTTPRequestHeaders  
  • + serializer
  • – setValue:forHTTPHeaderField:
  • – valueForHTTPHeaderField:
  • – setAuthorizationHeaderFieldWithUsername:password:
  • – clearAuthorizationHeader

@property (readonly, nonatomic, strong) NSDictionary<NSString*NSString*> *HTTPRequestHeaders
获取请求头的信息,  默认包含  Accept-Language 和  User-Agent 

+ (instancetype)serializer   /// 返回一个默认配置序列化对象

+ ( instancetype )serializer {
   
return [[ self alloc ] init ];
}

/// 默认的初始化
- ( instancetype)init {
   
self = [super init];
   
if (!self) {
       
return nil;
    }
     /// 默认编码为 NSUTF8StringEncoding
    self.stringEncoding = NSUTF8StringEncoding;

   
self.mutableHTTPRequestHeaders = [NSMutableDictionary dictionary];

     /// 设置语言
    // Accept-Language HTTP Header; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4
    NSMutableArray *acceptLanguagesComponents = [NSMutableArray array];
     
    [[NSLocale preferredLanguages] enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
       
float q = 1.0f - (idx * 0.1f);
        [acceptLanguagesComponents
addObject:[NSString stringWithFormat:@"%@;q=%0.1g", obj, q]];
        *stop = q <=
0.5f;
    }];
    [self setValue:[acceptLanguagesComponents componentsJoinedByString:@", "] forHTTPHeaderField:@"Accept-Language"];
     /// 设置语言

     /// 设置用户代理
    NSString *userAgent = nil;
#pragma clang diagnostic push   
#pragma clang diagnostic ignored "-Wgnu"
#if TARGET_OS_IOS
    // User-Agent Header; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.43
    userAgent = [
NSString stringWithFormat:@"%@/%@ (%@; iOS %@; Scale/%0.2f)", [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleExecutableKey] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleIdentifierKey], [[NSBundle mainBundle] infoDictionary][@"CFBundleShortVersionString"] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleVersionKey], [[UIDevice currentDevice] model], [[UIDevice currentDevice] systemVersion], [[UIScreen mainScreen] scale]];
#elif TARGET_OS_WATCH
    // User-Agent Header; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.43
    userAgent = [NSString stringWithFormat:
@"%@/%@ (%@; watchOS %@; Scale/%0.2f)", [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleExecutableKey] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleIdentifierKey], [[NSBundle mainBundle] infoDictionary][@"CFBundleShortVersionString"] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleVersionKey], [[WKInterfaceDevice currentDevice] model], [[WKInterfaceDevice currentDevice] systemVersion], [[WKInterfaceDevice currentDevice] screenScale]];
#elif defined(__MAC_OS_X_VERSION_MIN_REQUIRED)
    userAgent = [NSString stringWithFormat:@"%@/%@ (Mac OS X %@)", [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleExecutableKey] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleIdentifierKey], [[NSBundle mainBundle] infoDictionary][@"CFBundleShortVersionString"] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleVersionKey], [[NSProcessInfo processInfo] operatingSystemVersionString]];
#endif
#pragma clang diagnostic pop
    if (userAgent) {
       
if (![userAgent canBeConvertedToEncoding:NSASCIIStringEncoding]) {
            NSMutableString *mutableUserAgent = [userAgent mutableCopy];
                              /// 对useragent进行字符串的转换处理  http://nshipster.com/cfstringtransform/
            if (CFStringTransform((__bridge CFMutableStringRef)(mutableUserAgent), NULL, (__bridge CFStringRef)@"Any-Latin; Latin-ASCII; [:^ASCII:] Remove", false)) {
                userAgent = mutableUserAgent;
            }
        }
     /// 设置用户代理
        [self setValue:userAgent forHTTPHeaderField:@"User-Agent"];
    }

   
// HTTP Method Definitions; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html
   
self.HTTPMethodsEncodingParametersInURI = [NSSet setWithObjects:@"GET", @"HEAD", @"DELETE", nil];

   
self.mutableObservedChangedKeyPaths = [NSMutableSet set];
   
for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
       
if ([self respondsToSelector:NSSelectorFromString(keyPath)]) {
            [
self addObserver:self forKeyPath:keyPath options:NSKeyValueObservingOptionNew context:AFHTTPRequestSerializerObserverContext];
        }
    }
    return self;
}

- (void)setValue:(nullable NSString *) value  forHTTPHeaderField:(NSString *) field
注意:通过setvalue: forHTTPHeaderField:方法设置Header里面的字段,如果为field为空,那么这个字段会从Header里面移除

/// 根据key去除Hearder里面的值,返回字符串或者nil
- (nullable NSString *)valueForHTTPHeaderField:(NSString *) field 

///  设置 HTTP认证的用户名和密码(  basic authentication 貌似没怎么用过)
- (void)setAuthorizationHeaderFieldWithUsername:(NSString *) username  password:(NSString *) password=
   1. 客户端访问一个受http基本认证保护的资源。
     2. 服务器返回401状态,要求客户端提供用户名和密码进行认证。
     3. 客户端将输入的用户名密码用Base64进行编码后,采用非加密的明文方式传送给服务器。
     Authorization: Basic xxxxxxxxxx.
     4. 如果认证成功,则返回相应的资源。如果认证失败,则仍返回401状态,要求重新进行认证。

/// 相对应得还有移除验证的用户名和密码
- (void)clearAuthorizationHeader

Configuring Query String Parameter Serialization(配置查询字符参数序列化)

  •   HTTPMethodsEncodingParametersInURI
  • – setQueryStringSerializationWithStyle:
  • – setQueryStringSerializationWithBlock:

@property (nonatomic, strong) NSSet<NSString*> *HTTPMethodsEncodingParametersInURI
/// 返回HTTP的方法.序列化请求将参数编码成一个查询字符串(默认包含:GET、DELETE、HEAD)、


- (void)setQueryStringSerializationWithStyle:(AFHTTPRequestQueryStringSerializationStyle) style

根据之前预定义的style设置查询字符串序列化的方法

- (void)setQueryStringSerializationWithBlock:(nullable NSString *( ^ ) ( NSURLRequest *request , id parameters , NSError *__autoreleasing *error )) block
根据指定的block设置 一个自定义查询字符序列化的方法。
Block中传入一个 request, 编码的 参数 parameters 和一个error,返回 请求参数编码成一个查询字符串

创建请求对象
– requestWithMethod:URLString:parameters:error:
– multipartFormRequestWithMethod:URLString:parameters:constructingBodyWithBlock:error:
– requestWithMultipartFormRequest:writingStreamContentsToFile:completionHandler:


- (NSMutableURLRequest *)requestWithMethod:(NSString *) method  URLString:(NSString *) URLString parameters:(nullable id) parameters  error:(NSError *_Nullable __autoreleasing *) error

根据传入的Method 、 url、和paramters参数创建一个NSMutableURLRequest *请求;这里会个根据传入的Method,判断如果为 `GET`, `HEAD`, or `DELETE`。参数会拼接在Url的后面。否则参数会根据request指定的 parameterEncoding参数编码并设置成HTTP的请求体。

- (NSMutableURLRequest *)multipartFormRequestWithMethod:(NSString *) method  URLString:(NSString *) URLString  parameters:(nullable NSDictionary<NSString*,id> *) parameters  constructingBodyWithBlock:(nullable void ( ^ ) ( id<AFMultipartFormData> formData )) block error:(NSError *_Nullable __autoreleasing *) error  )) handler
根据指定的Method、urlString和一个根据Block构建好 multipart/form-data的HTTP请求体生成一个request。
参数: method 不能是 GET、 HEAD, 或者nil
           Block 中参数只有一个遵守 <AFMultipartFormData> 协议的id对象formData, formData是 用来将拼接数据。

- (NSMutableURLRequest *)requestWithMultipartFormRequest:(NSURLRequest *) request  writingStreamContentsToFile:(NSURL *) fileURL  completionHandler:(nullable void ( ^ ) ( NSError *_Nullable error )) handler
根据移除一个requestHTTPBodyStream,再异步HTTPBodyStream中内容写入指定的文件的方式创建request。当请求完成时completionHandler:调用。


AFMultipartFormData协议

某app的一个登录POST请求:

POST / HTTP/1.1
Host: log.nuomi.com
Content-Type: multipart/form-data; boundary=Boundary+6D3E56AA6EAA83B7
Cookie: access_log=7bde65268e2260bb0a85c7de2c67c468; BAIDUID=428D86FDBA6028DE2A5496BE3E7FC308:FG=1; BAINUOCUID=4368e1b7499c455dcd437da336ca1ca9feb8f57d; BDUSS=Ecwa3NvN1NjNWhsVGxWZktFfkc2bzJxQjZ3RFJpTFBiUzZqZUJZU0ZTSmZsN0ZXQVFBQUFBJCQAAAAAAAAAAAEAAABxbLRYWXV1dXV3dXV1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAF8KilZfCopWR; bn_na_copid=60139b4b2ba75706fc384d987c2e4007; bn_na_ctag=W3siayI6Imljb25fMSIsInMiOiJ0dWFuIiwidiI6IjMyNiIsInQiOiIxNDUxODg2OTE0In1d; channel=user_center%7C%7C; channel_content=; channel_webapp=webapp; condition=6.0.3; domainUrl=sh; na_qab=6be39bfce918bb7b51887412e009faa6; UID=1488219249
Connection: keep-alive
Accept: */*
User-Agent: Bainuo/6.1.0 (iPhone; iOS 9.0; Scale/2.00)
Accept-Language: zh-Hans-CN;q=1, en-CN;q=0.9
Content-Length: 22207
Accept-Encoding: gzip, deflate

--Boundary+6D3E56AA6EAA83B7 /// 开始
Content-Disposition: form-data; name="app_version"

6.1.0
--Boundary+6D3E56AA6EAA83B7
Content-Disposition: form-data; name="channel"

com_dot_apple
--Boundary+6D3E56AA6EAA83B7
Content-Disposition: form-data; name="cityid"

200010000
--Boundary+6D3E56AA6EAA83B7
Content-Disposition: form-data; name="cuid"

4368e1b7499c455dcd437da336ca1ca9feb8f57d
--Boundary+6D3E56AA6EAA83B7
Content-Disposition: form-data; name="device_type"

iPhone8,1
--Boundary+6D3E56AA6EAA83B7
Content-Disposition: form-data; name="lat"

0
--Boundary+6D3E56AA6EAA83B7
Content-Disposition: form-data; name="lng"

0
--Boundary+6D3E56AA6EAA83B7
Content-Disposition: form-data; name="sysSign"

660da020ae6e152daa64cc23e3e09a1c
--Boundary+6D3E56AA6EAA83B7
Content-Disposition: form-data; name="terminal_type"

iphone
--Boundary+6D3E56AA6EAA83B7
Content-Disposition: form-data; name="logdata"

/// 二进制数据

--Boundary+6D3E56AA6EAA83B7--
可以看到上面:Content-Type: multipart/form-data; boundary=Boundary+6D3E56AA6EAA83B7
其中Boundary+6D3E56AA6EAA83B7就是本次上传标示字符串,可以一次传输多个参数值以上传标示字符串分割
例如:app_version = 6.1.0可如下表示:
--Boundary+6D3E56AA6EAA83B7 /// 开始
Content-Disposition: form-data; name="app_version"

6.1.0
--Boundary+6D3E56AA6EAA83B7
其中boundary由AFNetworking随机生成
static NSString * AFCreateMultipartFormBoundary() {
   
return [ NSString stringWithFormat : @"Boundary+%08X%08X" , arc4random (), arc4random ()];
}


以下是协议定义的所有方法:


其中:
  • - (BOOL)appendPartWithFileURL:(NSURL *)fileURL name:(NSString *)name error:(NSError *_Nullable __autoreleasing *)error
  • - (BOOL)appendPartWithFileURL:(NSURL *)fileURL name:(NSString *)name fileName:(NSString *)fileName mimeType:(NSString *)mimeType error:(NSError *_Nullable __autoreleasing *)error
  • 拼接的数据来源于本地的文件,区别在于第一个方法会根据FileURL截取最后的路径,自动的设置fileName和mimeType(http://www.iana.org/assignments/media-types/media-types.xhtml)


  • - (void)appendPartWithInputStream:(nullable NSInputStream *)inputStream name:(NSString *)name fileName:(NSString *)fileName length:(int64_t)length mimeType:(NSString *)mimeType
  • 拼接的数据来源于输入字节流AFN会完成构建请求头中的Content-Disposition和 Content-Type,


- (void)appendPartWithFileData:(NSData *) data  name:(NSString *) name  fileName:(NSString *) fileName  mimeType:(NSString *) mimeType 
拼接的数据来源于文件的二进制数据,
- (void)appendPartWithFormData:(NSData *) data  name:(NSString *) name
拼接  HTTP 请求头中 Content-Disposition,且在其后跟上编码后的数据和数据边界( multipart form boundary.

以上几个方法都会在HTTP的请求头中设置好:Content-Disposition: file; filename=#{filename}; name=#{name} Content-Type: #{mimeType}, 且在其后跟上编码后的数据和数据边界( multipart form boundary.

- (void)appendPartWithHeaders:(nullable NSDictionary<NSString*,NSString*> *) headers  body:(NSData *) body
根据提供的Headers直接设置HTTP的请求头,且拼接data
  • – appendPartWithFileData:name:fileName:mimeType:
  • – appendPartWithFormData:name: required method
  • 上述两个方法也是调用appendPartWithHeaders:行默认的构建

- (void)throttleBandwidthWithPacketSize:(NSUInteger) numberOfBytes  delay:(NSTimeInterval) delay
通过设置没个包的大小来限制请求的带宽。上传的数据流中 每次 读取块后增加延时时间。。ps:因为无法明确的区分  3G, EDGE,  LTE 等情况。所以并不推荐你仅以依靠网络是否可达来 限制带宽


写的越来越乱了。。。就当是自己做的笔记吧。
PS:  限于个人的水平,文中的些理解可能会有偏差,请参照 http://cocoadocs.org/docsets/AFNetworking/3.0.4/Classes/AFHTTPRequestSerializer.html和参考源码。

猜你喜欢

转载自blog.csdn.net/yuwuchaio/article/details/50455941