开发中我们是使用的大多数是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组成:
其它:
stringEncoding /// 返回参数编码的编码样式,默认为 NSUTF8StringEncoding
allowsCellularAccess /// 是否可以通过手机网络发送请求
cachePolicy /// 缓存策略
HTTPShouldHandleCookies /// 是否对cookies进行默认处理 默认为YES
HTTPShouldUsePipelining /// 是否可以在上个数据传输的请求完成后继续传输数据。 Default is No
networkServiceType /// 服务器的类型 默认为 NSURLNetworkServiceTypeVoIP
timeoutInterval
/// 一个请求的超时时长
配置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 ];
return [[ self alloc ] init ];
}
/// 默认的初始化
- (
instancetype)init {
self = [super init];
if (!self) {
return nil;
self = [super init];
if (!self) {
return nil;
}
/// 默认编码为
NSUTF8StringEncoding
self.stringEncoding = NSUTF8StringEncoding;
self.mutableHTTPRequestHeaders = [NSMutableDictionary dictionary];
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;
}];
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]) {
#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;
}
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];
}
}
// 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
根据移除一个request的HTTPBodyStream,再异步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 ()];
return [ NSString stringWithFormat : @"Boundary+%08X%08X" , arc4random (), arc4random ()];
}
http://cocoadocs.org/docsets/AFNetworking/3.0.4/Protocols/AFMultipartFormData.html#//api/name/appendPartWithFileURL:name:error:
以下是协议定义的所有方法:
– appendPartWithFileURL:name:error:
– appendPartWithFileURL:name:fileName:mimeType:error:
– appendPartWithInputStream:name:fileName:length:mimeType:
– appendPartWithFileData:name:fileName:mimeType:
– appendPartWithFormData:name:
required method– appendPartWithHeaders:body:
required method– throttleBandwidthWithPacketSize:delay:
其中:
- - (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和参考源码。