必威-必威-欢迎您

必威,必威官网企业自成立以来,以策略先行,经营致胜,管理为本的商,业推广理念,一步一个脚印发展成为同类企业中经营范围最广,在行业内颇具影响力的企业。

实现了这个协议,② multipart request相关内容

2019-09-17 13:22 来源:未知

AFURLRequestSerialization 是 AFNetworking 用来序列化 HTTP 请求的模块,它本身是一个协议,声明如下:

前言

这个类的作用就是把请求的URL路径 及对应的参数 封装成OC中的NSURLRequest,兼容了GET HEAD DELETE 及其他方式如POST,也开放了属性可以设置请求体,如默认会携带cookies 超时时间。。

层级关系
AFHTTPRequestSerializer <AFURLRequestSerialization>
AFPropertyListRequestSerializer AFJSONRequestSerializer

前面两篇文章已经初步介绍了AFNetworking 2.x 的基本情况以及核心类AFXXXXRequestOperation的内容。主要是关于request是如何执行的,responseData如何获取的,然后关于requestSerialization&responseSerialization的内容直接跳过去的。对于requestSerialization的内容主要包括两大块:
① request的get&post参数格式化,http headers设置,请求相关其他属性的设置
② multipart request相关内容

概述

​ AFNetworking作为著名的网络请求框架,被开发者广泛运用。自从AF3.x开始,废弃了之前NSURLConnection的版本,使用NSURLSession,但是之前的AF2.x版本仍然有许多值得学习的地方。以2.6.2版本为例,本系列将对该框架进行全面的学习和分析。AFNetworking框架主要包含网络通信、序列化/反序列化、网络性能监听、网络通信安全四个模块,本文主要讲解AFURLRequestSerialization相关类。

图片 1

@protocol AFURLRequestSerialization <NSObject, NSSecureCoding, NSCopying>- (nullable NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request withParameters:(nullable id)parameters error:(NSError * _Nullable __autoreleasing *)error NS_SWIFT_NOTHROW;@end

一 初始化

可以看到在最初创建AFHTTPSessionManager的时候,也会创建一个默认的请求及响应的序列化类 从中可看出主要是设置了Accept-Language和userAgent两个请求体字段信息,另外也使用KVO技术监听了他6的属性的变化,

/**
    是否允许使用设备的蜂窝移动网络来创建request,默认为允许:
 */
@property (nonatomic, assign) BOOL allowsCellularAccess;

/**
 创建的request所使用的缓存策略,默认使用`NSURLRequestUseProtocolCachePolicy`,该策略表示
 如果缓存不存在,直接从服务端获取。如果缓存存在,会根据response中的Cache-Control字段判断
 下一步操作,如: Cache-Control字段为must-revalidata, 则 询问服务端该数据是否有更新,无更新话
 直接返回给用户缓存数据,若已更新,则请求服务端
 */
@property (nonatomic, assign) NSURLRequestCachePolicy cachePolicy;

/**
 如果设置HTTPShouldHandleCookies为YES,就处理存储在NSHTTPCookieStore中的cookies
 HTTPShouldHandleCookies表示是否应该给request设置cookie并随request一起发送出去
 */
@property (nonatomic, assign) BOOL HTTPShouldHandleCookies;

/**
 HTTPShouldUsePipelining表示receiver(理解为iOS客户端)的下一个信息是否必须等到上一个请求回复才能发送。
 如果为YES表示可以,NO表示必须等receiver收到先前的回复才能发送下个信息。
 默认 是NO
 */
@property (nonatomic, assign) BOOL HTTPShouldUsePipelining;

/**
 设定request的network service类型. 默认是`NSURLNetworkServiceTypeDefault`.
 这个network service是为了告诉系统网络层这个request使用的目的
 比如NSURLNetworkServiceTypeVoIP表示的就这个request是用来请求网际协议通话技术(Voice over IP)。
 系统能根据提供的信息来优化网络处理,从而优化电池寿命,网络性能等等
 */
@property (nonatomic, assign) NSURLRequestNetworkServiceType networkServiceType;

/**
   超时机制 默认60秒
 */
@property (nonatomic, assign) NSTimeInterval timeoutInterval;

图片 2

Paste_Image.png

具体涉及到的类的结构如下

初始化

AFHTTPRequestSerializer是用于构建NSURLRequest的类,通过-init:方法进行初始化,指定编码方式为NSUTF-8。HTTP请求分为请求头和请求体,请求头主要包含以下字段:

User-Agent、Pragma、Content-Type、Content-Length、Accept-Language、Accept、Accept-Encoding、Cookie

init方法,指定了请求头的Accept-Language,根据版本号、操作系统版本、设备屏幕scale等信息生成User-Agent,然后设置一些参数,如HTTPMethodsEncodingParametersInURI包含了指定的HTTP method,mutableObservedChangedKeyPaths存放监听到的属性,同时开始kvo监听。部分代码注释如下:

- (instancetype)init {
    self.stringEncoding = NSUTF8StringEncoding; //UTF-8
    self.mutableHTTPRequestHeaders = [NSMutableDictionary dictionary]; //请求头配置
    ...
    [self setValue:[acceptLanguagesComponents componentsJoinedByString:@", "] forHTTPHeaderField:@"Accept-Language"]; //设置Accept-Language字段
    ...
    [self setValue:userAgent forHTTPHeaderField:@"User-Agent"];//设置User-Agent字段
    ...
    self.HTTPMethodsEncodingParametersInURI = [NSSet setWithObjects:@"GET", @"HEAD", @"DELETE", nil]; //包含指定的http method

    self.mutableObservedChangedKeyPaths = [NSMutableSet set]; //kvo属性集合
    for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
        if ([self respondsToSelector:NSSelectorFromString(keyPath)]) { //kvo监听部分属性
            [self addObserver:self forKeyPath:keyPath options:NSKeyValueObservingOptionNew context:AFHTTPRequestSerializerObserverContext];
        }
    }
    return self;
}

AFHTTPRequestSerializer重写了属性的set方法,手动实现kvo通知,这样当调用这些set方法时,会监听到这些属性值的改变,存入mutableObservedChangedKeyPaths中,例如:

- (void)setAllowsCellularAccess:(BOOL)allowsCellularAccess {
    [self willChangeValueForKey:NSStringFromSelector(@selector(allowsCellularAccess))];
    _allowsCellularAccess = allowsCellularAccess;
    [self didChangeValueForKey:NSStringFromSelector(@selector(allowsCellularAccess))];
}

下面开始构建NSURLRequest,构建的类型主要有两种,普通类型和multipart类型。首先看一下构建普通类型的方法。

AFNetworking 是 Objective-C 中用于网络请求的第三方框架,我们一般使用它来封装网络请求,这篇文章记录了阅读 AFNetworking(Version 3.1.0) 源码的笔记,简单的研究了它的实现细节。

AFNetworking 又通过了三个类 AFHTTPRequestSerializer、AFJSONRequestSerializer、AFPropertyListRequestSerializer 实现了这个协议,其中 AFHTTPRequestSerializer 是 AFJSONRequestSerializer、AFPropertyListRequestSerializer 两个类的父类,接下来将分别解析这三个类的代码。

AFURLRequestSerialization协议

图片 3

Paste_Image.png

AFHTTPRequestSerializer init 方法实现

- (instancetype)init
{
 if (self = [super init])
 {
  self.stringEncoding = NSUTF8StringEncoding;

  self.mutableHTTPRequestHeaders = [NSMutableDictionary dictionary];
  //  创建一个并行队列
  self.requestHeaderModificationQueue = dispatch_queue_create("requestHeaderModificationQueue", DISPATCH_QUEUE_CONCURRENT);

  // Accept-Language HTTP Header; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4
//  传递可接受的语言,q代表对语言的喜好程度,默认是取出前5个的数据,不足5个,取实际的个数
  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;
  }];
  /*
   acceptLanguagesComponents:<__NSArrayM 0x17024a500>(
   zh-Hans-CN;q=1,    简体中文
   en-CN;q=0.9,
   zh-Hant-CN;q=0.8   繁体中文
   )

   */
  [self setValue:[acceptLanguagesComponents componentsJoinedByString:@", "] forHTTPHeaderField:@"Accept-Language"];

  NSString *userAgent = nil;
#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
  // 例如 userAgent:     JZAXBZS/1.1.3 (iPhone; iOS 10.2; Scale/2.00)
  if (userAgent)
  {
   // 不可以无损化转码
   if (![userAgent canBeConvertedToEncoding:NSASCIIStringEncoding])
   {
    NSMutableString *mutableUserAgent = [userAgent mutableCopy];
    if (CFStringTransform((__bridge CFMutableStringRef)(mutableUserAgent), NULL, (__bridge CFStringRef)@"Any-Latin; Latin-ASCII; [:^ASCII:] Remove", false))
    {
     userAgent = mutableUserAgent;
    }
   }
   // User-Agent意义:
   //使得服务器能够识别客户使用的操作系统及版本、CPU 类型、浏览器及版本、浏览器渲染引擎、浏览器语言、浏览器插件等
   [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];
  //  对 AFHTTPRequestSerialize  的各项属性进行KVO监听
  for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths())
  {
   if ([self respondsToSelector:NSSelectorFromString(keyPath)])
   {
    [self addObserver:self forKeyPath:keyPath
        options:NSKeyValueObservingOptionNew
        context:AFHTTPRequestSerializerObserverContext];
   }
  }

 }
    return self;
}

图片 4

普通类型

通过-requestWithMethod:URLString:parameters:error方法构建NSURLRequest,下面是代码注释:

- (NSMutableURLRequest *)requestWithMethod:(NSString *)method
                                 URLString:(NSString *)URLString
                                parameters:(id)parameters
                                     error:(NSError *__autoreleasing *)error
{
    NSParameterAssert(method);
    NSParameterAssert(URLString);
    NSURL *url = [NSURL URLWithString:URLString]; //构建URL
    NSParameterAssert(url);
    //根据url创建URLRequest对象
    NSMutableURLRequest *mutableRequest = [[NSMutableURLRequest alloc] initWithURL:url];
    mutableRequest.HTTPMethod = method; //http method

    //监听的发生变化的属性,设置为http的请求头
    for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
        if ([self.mutableObservedChangedKeyPaths containsObject:keyPath]) {
            [mutableRequest setValue:[self valueForKeyPath:keyPath] forKey:keyPath];
        }
    }
    //进一步处理NSURLRequest
    mutableRequest = [[self requestBySerializingRequest:mutableRequest withParameters:parameters error:error] mutableCopy];
    return mutableRequest;
}

首先创建NSURLRequest对象,然后调用-requestBySerializingRequest:withParameters:error:方法根据参数进一步设置NSURLRequest对象,下面是代码注释:

- (NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request
                               withParameters:(id)parameters
                                        error:(NSError *__autoreleasing *)error
{
    ...
    //构建请求头参数
    [self.HTTPRequestHeaders enumerateKeysAndObjectsUsingBlock:^(id field, id value, BOOL * __unused stop) {
        if (![request valueForHTTPHeaderField:field]) {
            [mutableRequest setValue:value forHTTPHeaderField:field];
        }
    }];

    NSString *query = nil;
    if (parameters) {
        if (self.queryStringSerialization) {
            NSError *serializationError;
            //自定义方法,构建query
            query = self.queryStringSerialization(request, parameters, &serializationError);
            ...
        } else {
            switch (self.queryStringSerializationStyle) {
                case AFHTTPRequestQueryStringDefaultStyle:
                    //按照一定规则构建query
                    query = AFQueryStringFromParameters(parameters);
                    break;
            }
        }
    }
    if ([self.HTTPMethodsEncodingParametersInURI containsObject:[[request HTTPMethod] uppercaseString]]) {
        if (query) { //将query拼在url后面
            mutableRequest.URL = [NSURL URLWithString:[[mutableRequest.URL absoluteString] stringByAppendingFormat:mutableRequest.URL.query ? @"&%@" : @"?%@", query]];
        }
    } else {
        ...
        if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"]) {
            [mutableRequest setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
        }
        //query设置为httpBody
        [mutableRequest setHTTPBody:[query dataUsingEncoding:self.stringEncoding]];
    }
    return mutableRequest;
}

该方法首先设置http请求的head参数,然后将parameters转化为url的query,通过AFQueryStringFromParameters方法返回一个字符串,格式是:"key1=value1&key2=value2",代码注释如下:

static NSString * AFQueryStringFromParameters(NSDictionary *parameters) {
    NSMutableArray *mutablePairs = [NSMutableArray array];
    for (AFQueryStringPair *pair in AFQueryStringPairsFromDictionary(parameters)) {
        [mutablePairs addObject:[pair URLEncodedStringValue]]; //url encode,返回key=value格式的字符串
    }
    return [mutablePairs componentsJoinedByString:@"&"];
}

思路是首先根据AFQueryStringPairsFromDictionary方法的会根据parameters创建一个数组,无论parameters是否嵌套,创建后的数组中的元素在同一层级。数组中的元素是AFQueryStringPair对象,包装了key/value,然后调用URLEncodedStringValue方法将pair对象转化为key=value格式的字符串,且key和value都做了encode。最后将返回的字符传用"&"符号连接,生成query字符串。

接着判断当前http请求的method是否包含在HTTPMethodsEncodingParametersInURI中,如果是GET请求,则包含在内,会将生成的将query拼在url后面,因为GET请求不设置http的request body。如果是POST请求,则将query字符串序列化成nsdata后,设置为http的request body。

另外,有两个子类继承了AFHTTPRequestSerializer类,即AFJSONRequestSerializer和AFPropertyListRequestSerializer,都重写了父类的方法,下面看一下AFJSONRequestSerializer的方法:

- (NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request
                               withParameters:(id)parameters
                                        error:(NSError *__autoreleasing *)error
{
    NSParameterAssert(request);
    //如果是"GET、HEAD、DELETE"方法,和父类一样
    if ([self.HTTPMethodsEncodingParametersInURI containsObject:[[request HTTPMethod] uppercaseString]]) {
        return [super requestBySerializingRequest:request withParameters:parameters error:error];
    }
    NSMutableURLRequest *mutableRequest = [request mutableCopy];
    [self.HTTPRequestHeaders enumerateKeysAndObjectsUsingBlock:^(id field, id value, BOOL * __unused stop) {
        if (![request valueForHTTPHeaderField:field]) {
            //设置http请求头参数
            [mutableRequest setValue:value forHTTPHeaderField:field];
        }
    }];
    //parameters,设置http请求体
    if (parameters) {
        if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"]) {
            [mutableRequest setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
        }
        [mutableRequest setHTTPBody:[NSJSONSerialization dataWithJSONObject:parameters options:self.writingOptions error:error]];
    }
    return mutableRequest;
}

分析该方法,当http的method是POST类型时,AFJSONRequestSerializer和父类的处理不同,该方法将parameters序列化为JSON数据,设置为http的请求体,且请求头的"Content-Type"参数设置为"application/json"。AFPropertyListRequestSerializer的方法如下:

- (NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request
                               withParameters:(id)parameters
                                        error:(NSError *__autoreleasing *)error
{
    ...
    if (parameters) {
        if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"]) {
            [mutableRequest setValue:@"application/x-plist" forHTTPHeaderField:@"Content-Type"];
        }
        [mutableRequest setHTTPBody:[NSPropertyListSerialization dataWithPropertyList:parameters format:self.format options:self.writeOptions error:error]];
    }
    return mutableRequest;
}

该方法逻辑类似,当http的method是POST类型时,调用NSPropertyListSerialization的方法将parameters序列化成NSData,赋值给http的请求体。

AFURLRequestSerialization

AFURLRequestSerialization 只是个协议,其内部提供了一个方法用以序列化请求数据。

@protocol AFURLRequestSerialization <NSObject, NSSecureCoding, NSCopying>

- (nullable NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request
                               withParameters:(nullable id)parameters
                                        error:(NSError * _Nullable __autoreleasing *)error NS_SWIFT_NOTHROW;

@end

同时也要遵循 NSObject、NSSecureCoding 和 NSCopying 这三个协议,实现安全编码、拷贝等。

在 AFNetworking 内部里遵循这个协议并返回特定的数据类型有 3 种,分别是:

  • AFHTTPRequestSerializer
  • AFJSONRequestSerializer
  • AFPropertyListRequestSerializer

一般来说请求都会按 key=value 的方式带上各种参数,GET 方法参数会直接加在 URL 上,POST 方法放在 body 上,而 NSURLRequest 没有直接对这个参数的解析,所以都是拼好字符串再往上添加的。但 AFURLRequestSerialization 提供了接口,让参数可以是 NSDictionary, NSArray, NSSet 这些类型,再由内部解析成字符串后赋给 NSURLRequest。因为 AFHTTPRequestSerializer 为 AFHTTPSessionManager 创建时默认的 requestSerializer,下面具体过程以 AFHTTPRequestSerializer 的相关方法为例。

先来看 AFHTTPRequestSerializer 的初始化方法:

二 根据参数创建请求

这是这个类最核心的方法,看下他是怎么实现的

    NSURL *url = [NSURL URLWithString:URLString];
    NSParameterAssert(url);
    NSMutableURLRequest *mutableRequest = [[NSMutableURLRequest alloc] initWithURL:url];
    // 设置请求方式
    mutableRequest.HTTPMethod = method;
    // 根据初始化 AFHTTPRequestSerializer  产生的属性  设置请求头
    for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths())
    {
        if ([self.mutableObservedChangedKeyPaths containsObject:keyPath])
        {
            id value = [self valueForKeyPath:keyPath];
            [mutableRequest setValue:value forKey:keyPath];
        }
    }

Content-Type 默认值:application/x-www-form-urlencoded

        if (!query)
        {
            query = @"";
        }
        if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"])
        {
            [mutableRequest setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
        }
        NSData *httpBody = [query dataUsingEncoding:self.stringEncoding];
        [mutableRequest setHTTPBody:httpBody];

requestSerialization.png

multipart类型

Multipart是HTTP协议为web表单新增的上传文件的协议,基于POST方法,数据放在http请求体,不同于普通post请求的key/value格式或者,json格式,multipart类型的请求体较长,且遵循一定的格式,下面是格式:

--BoundaryAaB03x 
content-disposition: form-data; name="name"
//空行
abcdef... 
--BoundaryAaB03x 
content-disposition: form-data; name=”pic”; filename=“content.txt” 
Content-Type: text/plain
//空行 
...contents of abc.txt...
--BoundaryAaB03x 
content-disposition: form-data; name=”pic”; filename=“content.txt” 
Content-Type: text/plain
//空行 
...contents of abc.txt...
--BoundaryAaB03x--

可以看出请求的数据可以分成若干个段落,每个段落由分隔符隔开,且顶部和中间的分隔符前面有"--",底部的分隔符前后都有"--",每个段落的格式固定如下:

头部字段
//空行
数据实体

下面来分析一下multipart类型的请求方法:

- (NSMutableURLRequest *)multipartFormRequestWithMethod:(NSString *)method
                                              URLString:(NSString *)URLString
                                             parameters:(NSDictionary *)parameters
                              constructingBodyWithBlock:(void (^)(id <AFMultipartFormData> formData))block
                                                  error:(NSError *__autoreleasing *)error
{
    NSParameterAssert(method);
    //不支持GET、HEAD类型
    NSParameterAssert(![method isEqualToString:@"GET"] && ![method isEqualToString:@"HEAD"]);
    //创建NSURLRequest对象
    NSMutableURLRequest *mutableRequest = [self requestWithMethod:method URLString:URLString parameters:nil error:error];
    //创建AFStreamingMultipartFormData对象
    __block AFStreamingMultipartFormData *formData = [[AFStreamingMultipartFormData alloc] initWithURLRequest:mutableRequest stringEncoding:NSUTF8StringEncoding];

    if (parameters) {
        for (AFQueryStringPair *pair in AFQueryStringPairsFromDictionary(parameters)) {
            NSData *data = nil;
            if ([pair.value isKindOfClass:[NSData class]]) {
                data = pair.value;
            } else if ([pair.value isEqual:[NSNull null]]) {
                data = [NSData data];
            } else {
                data = [[pair.value description] dataUsingEncoding:self.stringEncoding];
            }

            if (data) {
                //formData对象追加参数
                [formData appendPartWithFormData:data name:[pair.field description]];
            }
        }
    }
    //formData对象追加参数
    if (block) {
        block(formData);
    }
    //设置request header、request body
    return [formData requestByFinalizingMultipartFormData];
}

首先排除了GET和HEAD类型的请求,然后根据url创建NSURLRequest对象,然后创建创建AFStreamingMultipartFormData对象,该对象负责为NSURLRequest对象拼接请求体数据,下面主要分析AFStreamingMultipartFormData对象的结构和方法:

@interface AFStreamingMultipartFormData ()
@property (readwrite, nonatomic, copy) NSMutableURLRequest *request; //URLRequest
@property (readwrite, nonatomic, assign) NSStringEncoding stringEncoding; //字符串编码
@property (readwrite, nonatomic, copy) NSString *boundary; //边界标识
@property (readwrite, nonatomic, strong) AFMultipartBodyStream *bodyStream;//body数据
@end

//初始化
- (id)initWithURLRequest:(NSMutableURLRequest *)urlRequest
          stringEncoding:(NSStringEncoding)encoding
{
    self = [super init];
    if (!self) {
        return nil;
    }
    self.request = urlRequest;
    self.stringEncoding = encoding;
    self.boundary = AFCreateMultipartFormBoundary(); //生成边界字符串
    self.bodyStream = [[AFMultipartBodyStream alloc] initWithStringEncoding:encoding]; //创建请求体数据
    return self;
}

AFStreamingMultipartFormData内部维护了一个NSMutableURLRequest对象,编码方式(字符串转成NAData的编码方式),边界字符串和AFMultipartBodyStream对象,其中AFMultipartBodyStream对象负责维护请求体的各个数据段落。下面是该对象的定义注释:

@interface AFMultipartBodyStream () <NSCopying>
@property (readwrite, nonatomic, assign) NSStringEncoding stringEncoding;
@property (readwrite, nonatomic, strong) NSMutableArray *HTTPBodyParts;
@property (readwrite, nonatomic, strong) NSEnumerator *HTTPBodyPartEnumerator;
@property (readwrite, nonatomic, strong) AFHTTPBodyPart *currentHTTPBodyPart;
@property (readwrite, nonatomic, strong) NSOutputStream *outputStream;
@property (readwrite, nonatomic, strong) NSMutableData *buffer;
@end

其中HTTPBodyParts是一个数组,数组中每个元素是一个AFHTTPBodyPart对象,AFHTTPBodyPart对象封装了请求体数据中每个段落的信息,代码注释如下:

@interface AFHTTPBodyPart : NSObject
@property (nonatomic, assign) NSStringEncoding stringEncoding; //编码方式
@property (nonatomic, strong) NSDictionary *headers; //段落头部信息
@property (nonatomic, copy) NSString *boundary; //边界字符串
@property (nonatomic, strong) id body; //段落体数据
@property (nonatomic, assign) unsigned long long bodyContentLength; //段落体数据长度
@property (nonatomic, strong) NSInputStream *inputStream; //数据流,用于读取body内如进buffer
@property (nonatomic, assign) BOOL hasInitialBoundary; //是否是开始边界
@property (nonatomic, assign) BOOL hasFinalBoundary; //是否是结束边界
...
@end

-multipartFormRequestWithMethod:URLString:parameters:constructingBodyWithBlock:方法接下来将parameters参数转化成AFQueryStringPair对象数组,将每个pair对象的key和value添加到formData对象维护的段落数组中。formData支持三种格式的数据,即NData、FileURL和NSInputStream,下面依次分析一下:

  1. NSData格式:

    例如-appendPartWithFormData:name:方法和-appendPartWithFileData:name:fileName:mimeType:方法,首先拼装段落头部信息,设置Content-Disposition字段和Content-Type字段,然后将data作为body构建AFHTTPBodyPart对象,添加进bodyStream中的段落数组HTTPBodyParts中。下面是代码注释:

    - (void)appendPartWithFileData:(NSData *)data
                              name:(NSString *)name
                          fileName:(NSString *)fileName
                          mimeType:(NSString *)mimeType
    {
        ...
        NSMutableDictionary *mutableHeaders = [NSMutableDictionary dictionary];
        [mutableHeaders setValue:[NSString stringWithFormat:@"form-data; name="%@"; filename="%@"", name, fileName] forKey:@"Content-Disposition"];
        [mutableHeaders setValue:mimeType forKey:@"Content-Type"];
        [self appendPartWithHeaders:mutableHeaders body:data]; //mutableHeaders和data包装成AFHTTPBodyPart对象,加入HTTPBodyParts数组中
    }
    
  2. FileURL格式:

    例如-appendPartWithFileURL:name:fileName:mimeType:error:方法,首先构建段落头部信息,设置Content-Disposition字段和Content-Type字段,然后构建AFHTTPBodyPart对象,body属性是fileURL,即本地文件路径,bodyContentLength属性是文件的大小,最后添加进HTTPBodyParts中。下面是代码注释:

    - (BOOL)appendPartWithFileURL:(NSURL *)fileURL
                             name:(NSString *)name
                         fileName:(NSString *)fileName
                         mimeType:(NSString *)mimeType
                            error:(NSError * __autoreleasing *)error
    {
        ...
        NSMutableDictionary *mutableHeaders = [NSMutableDictionary dictionary];
        [mutableHeaders setValue:[NSString stringWithFormat:@"form-data; name="%@"; filename="%@"", name, fileName] forKey:@"Content-Disposition"];
        [mutableHeaders setValue:mimeType forKey:@"Content-Type"]; //段落头部headers
        AFHTTPBodyPart *bodyPart = [[AFHTTPBodyPart alloc] init]; //创建AFHTTPBodyPart对象
        bodyPart.stringEncoding = self.stringEncoding;
        bodyPart.headers = mutableHeaders;
        bodyPart.boundary = self.boundary;
        bodyPart.body = fileURL; //body属性是文件路径
        bodyPart.bodyContentLength = [fileAttributes[NSFileSize] unsignedLongLongValue];
        [self.bodyStream appendHTTPBodyPart:bodyPart]; //添加进HTTPBodyParts中
        return YES;
    }
    
  3. NSInputStream格式:

    例如-appendPartWithInputStream:name:fileName:length:mimeType:方法,首先构建段落信息,设置Content-Disposition字段和Content-Type字段,然后构建AFHTTPBodyPart对象,body属性是NSInputStream对象,添加进HTTPBodyParts中。下面是代码注释:

    - (void)appendPartWithInputStream:(NSInputStream *)inputStream
                                 name:(NSString *)name
                             fileName:(NSString *)fileName
                               length:(int64_t)length
                             mimeType:(NSString *)mimeType
    {
        ...
        NSMutableDictionary *mutableHeaders = [NSMutableDictionary dictionary];
        [mutableHeaders setValue:[NSString stringWithFormat:@"form-data; name="%@"; filename="%@"", name, fileName] forKey:@"Content-Disposition"];
        [mutableHeaders setValue:mimeType forKey:@"Content-Type"];//段落头部headers
        AFHTTPBodyPart *bodyPart = [[AFHTTPBodyPart alloc] init]; //创建AFHTTPBodyPart对象
        bodyPart.stringEncoding = self.stringEncoding;
        bodyPart.headers = mutableHeaders;
        bodyPart.boundary = self.boundary;
        bodyPart.body = inputStream; //body属性是NSInputStream
        bodyPart.bodyContentLength = (unsigned long long)length;
        [self.bodyStream appendHTTPBodyPart:bodyPart]; //添加进HTTPBodyParts中
    }
    

以上3种格式的数据添加进HTTPBodyParts中时,在接下来的过程中,都会转化二进制数据拼装在http的请求体报文中。

-multipartFormRequestWithMethod:URLString:parameters:constructingBodyWithBlock:方法接下来调用block(formData)供外界调用层继续添加数据,方式可以使以上3种中的任意一种。然后调用-requestByFinalizingMultipartFormData方法为NSURLRequest对象添加请求报文的头,因为采用multipart类型的报文格式,所以设置Content-Type为"multipart/form-data",下面是代码注释:

- (NSMutableURLRequest *)requestByFinalizingMultipartFormData {
    if ([self.bodyStream isEmpty]) {
        return self.request;
    }
    //设置出初始和结尾的边界
    [self.bodyStream setInitialAndFinalBoundaries];
    //将bodyStream作为请求报文体
    [self.request setHTTPBodyStream:self.bodyStream];
    //设置请求头的Content-Type和Content-Length字段
    [self.request setValue:[NSString stringWithFormat:@"multipart/form-data; boundary=%@", self.boundary] forHTTPHeaderField:@"Content-Type"];
    [self.request setValue:[NSString stringWithFormat:@"%llu", [self.bodyStream contentLength]] forHTTPHeaderField:@"Content-Length"];
    return self.request;
}

其中self.bodyStream属性是AFMultipartBodyStream类型,继承于NSInputStream,将该属性设置为http的HTTPBodyStream属性,这样在发送http请求时,会按照文件流的方式发送请求报文,且上传数据是分片的,不会一次性将报文体数据读入内存中后发送,关于这种方式的比较,可以参考JSPatch大神bang的文章。

接下来通过NSURLSession发送请求时,调用-uploadTaskWithStreamedRequest:方法构建NSURLSessionUploadTask对象uploadTask,调用resume开始发送请求。由于已经设置bodyStream为NSInputStream对象,这种方式发送请求时会读取bodyStream的数据,调用NSInputStream对象的-read:maxLength:方法,而AFMultipartBodyStream对象重写了-read:maxLength:方法,代码注释如下:

- (NSInteger)read:(uint8_t *)buffer
        maxLength:(NSUInteger)length
{
    ...
    while ((NSUInteger)totalNumberOfBytesRead < MIN(length, self.numberOfBytesInPacket)) {
        if (!self.currentHTTPBodyPart || ![self.currentHTTPBodyPart hasBytesAvailable]) {
            if (!(self.currentHTTPBodyPart = [self.HTTPBodyPartEnumerator nextObject])) {
                break;
            }
        } else {
            NSUInteger maxLength = length - (NSUInteger)totalNumberOfBytesRead;
            NSInteger numberOfBytesRead = [self.currentHTTPBodyPart read:&buffer[totalNumberOfBytesRead] maxLength:maxLength];
            if (numberOfBytesRead == -1) {
                self.streamError = self.currentHTTPBodyPart.inputStream.streamError;
                break;
            } else {
                totalNumberOfBytesRead += numberOfBytesRead;
                if (self.delay > 0.0f) {
                    [NSThread sleepForTimeInterval:self.delay];
                }
            }
        }
    }
#pragma clang diagnostic pop
    return totalNumberOfBytesRead;
}

该方法遍历HTTPBodyParts数组中的各个元素,其中HTTPBodyPartEnumerator是枚举器,依附于HTTPBodyParts数组,调用nextObject方法取出数组中的对象作为当前对象currentHTTPBodyPart。然后调用AFHTTPBodyPart的-read:maxLength:方法将AFHTTPBodyPart对象中的各个部分读入buffer中。下面是代码注释:

- (NSInteger)read:(uint8_t *)buffer
        maxLength:(NSUInteger)length
{
    NSInteger totalNumberOfBytesRead = 0;
    if (_phase == AFEncapsulationBoundaryPhase) { //边界部分
        ...
    }
    if (_phase == AFHeaderPhase) { //header部分
        ...
    }
    if (_phase == AFBodyPhase) { //body部分
        ...
    }
    if (_phase == AFFinalBoundaryPhase) { //尾部边界
        ...
    }
    return totalNumberOfBytesRead;
}

通过一组枚举类型AFHTTPBodyPartReadPhase表示段落中的不同部分,依次将它们生成报文数据,然后读入buffer中,生成数据的时候需要按照rfc1867规定,在相应地方添加rn换行符。当前阶段读完数据后调用-transitionToNextPhase方法进入下一个部分。需要注意的是,_phase==AFBodyPhase阶段时,通过self.inputStream对象读取数据进buffer中,self.inputStream是内部持有的一个属性,body中的数据由inputStream负责加载,通过懒加载的方式初始化:

- (NSInputStream *)inputStream {
    if (!_inputStream) {
        if ([self.body isKindOfClass:[NSData class]]) {
            _inputStream = [NSInputStream inputStreamWithData:self.body];
        } else if ([self.body isKindOfClass:[NSURL class]]) {
            _inputStream = [NSInputStream inputStreamWithURL:self.body];
        } else if ([self.body isKindOfClass:[NSInputStream class]]) {
            _inputStream = self.body;
        } else {
            _inputStream = [NSInputStream inputStreamWithData:[NSData data]];
        }
    }
    return _inputStream;
}

inputStream会根据之前存入AFHTTPBodyPart对象中的body数据类型来初始化inputStream,说明之前3种格式的数据最终都会转化成NSInputStream对象inputStream,然后读取inputStream就可以。关于读取body数据进buffer的代码注释如下:

if (_phase == AFBodyPhase) {
    NSInteger numberOfBytesRead = 0;
    //从inputStream中读取数据
    numberOfBytesRead = [self.inputStream read:&buffer[totalNumberOfBytesRead] maxLength:(length - (NSUInteger)totalNumberOfBytesRead)];
    if (numberOfBytesRead == -1) {
        return -1;
    } else {
        totalNumberOfBytesRead += numberOfBytesRead;
        if ([self.inputStream streamStatus] >= NSStreamStatusAtEnd) {
            [self transitionToNextPhase];
        }
    }
}

totalNumberOfBytesRead表示总共读取的字节数。AFMultipartBodyStream对象通过重写父类的-read:maxLength:方法实现了拼接请求报文的body数据。

requestWithMethod 方法

requestWithMethod:方法是 AFHTTPSessionManager 在创建 dataTask 前用来创建 request 的,方法具体实现如下:

- (NSMutableURLRequest *)requestWithMethod:(NSString *)method
                                 URLString:(NSString *)URLString
                                parameters:(id)parameters
                                     error:(NSError *__autoreleasing *)error
{
    // 进行参数断言
    NSParameterAssert(method);
    NSParameterAssert(URLString);

    NSURL *url = [NSURL URLWithString:URLString];

    NSParameterAssert(url);

    // 使用url构建并初始化NSMutableURLRequest,然后设置HTTPMethod
    NSMutableURLRequest *mutableRequest = [[NSMutableURLRequest alloc] initWithURL:url];
    mutableRequest.HTTPMethod = method;

    // 给NSMutableURLRequest自带的属性赋值
    // 然后通过判断mutableObservedChangedKeyPaths(NSMutableSet)中是否有这个keyPath,来设定mutableRequest对应的keyPath值
    // AFHTTPRequestSerializerObservedKeyPaths这个数组里的属性是固定的,且在 init 方法里全都 KVO 了
    /**
     *  当 AFHTTPRequestSerializerObserverContext 中有 value 变化了(且变化后的新值不为 NSNull null),就会响应 observerValueForKeyPath 这个函数,从而mutableObservedChangedKeyPaths就会添加这个 keyPath
     */
    for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
        if ([self.mutableObservedChangedKeyPaths containsObject:keyPath]) {
            [mutableRequest setValue:[self valueForKeyPath:keyPath] forKey:keyPath];
        }
    }

    // 将传入的parameters进行编码,并添加到request中
    mutableRequest = [[self requestBySerializingRequest:mutableRequest withParameters:parameters error:error] mutableCopy];

 return mutableRequest;
}

主要是使用指定的 HTTP method 和 URLString 来构建一个 NSMutableURLRequest 对象实例,如果 method 是 GET、HEAD、DELETE,那么 parameter 将会被用来构建一个基于 url 编码的查询字符串,并且这个字符串会直接加到 request 的 url 后面。对于其他的Method,比如POST/PUT,会根据 parameterEncoding 属性进行编码,而后加到 request 的 http body 上,这里具体是调用 requestBySerializingRequest: 这个方法。

- (instancetype)init { self = [super init]; if  { return nil; } self.stringEncoding = NSUTF8StringEncoding; self.mutableHTTPRequestHeaders = [NSMutableDictionary dictionary]; self.requestHeaderModificationQueue = dispatch_queue_create("requestHeaderModificationQueue", DISPATCH_QUEUE_CONCURRENT); // 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;#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 if (userAgent) { if (![userAgent canBeConvertedToEncoding:NSASCIIStringEncoding]) { NSMutableString *mutableUserAgent = [userAgent mutableCopy]; 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]) { [self addObserver:self forKeyPath:keyPath options:NSKeyValueObservingOptionNew context:AFHTTPRequestSerializerObserverContext]; } } return self;}

分两篇文章分析这两部分内容,本篇主要是第一部分——参数格式化,headers,timeout等

requestBySerializingRequest 方法

requestBySerializingRequest: 方法做了以下事情:

  • 1.设置 HTTP 的头部
  • 2.构建查询字符串
  • 3.拼接参数
  • 4.将 query 设置到 http body 上
- (NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request
                               withParameters:(id)parameters
                                        error:(NSError *__autoreleasing *)error
{
    NSParameterAssert(request);

    NSMutableURLRequest *mutableRequest = [request mutableCopy];

    // 1.设置request的http header field
    [self.HTTPRequestHeaders enumerateKeysAndObjectsUsingBlock:^(id field, id value, BOOL * __unused stop) {
        if (![request valueForHTTPHeaderField:field]) {
            [mutableRequest setValue:value forHTTPHeaderField:field];
        }
    }];


    // 2.构建查询字符串 query
    NSString *query = nil;
    if (parameters) {
        // 第一种,自定义了queryStringSerialization
        if (self.queryStringSerialization) {
            NSError *serializationError;
            query = self.queryStringSerialization(request, parameters, &serializationError);

            if (serializationError) {
                if (error) {
                    *error = serializationError;
                }

                return nil;
            }
        } else {
            // 第二种,使用AFQueryStringFromParameters() 方法
            switch (self.queryStringSerializationStyle) {
                case AFHTTPRequestQueryStringDefaultStyle:
                    query = AFQueryStringFromParameters(parameters);
                    break;
            }
        }
    }
    // 3.拼接参数
    // 这几个method的quey是拼接到url后面的。而POST、PUT是把query拼接到http body中的。
    // 判断该request中是否包含了GET、HEAD、DELETE
    if ([self.HTTPMethodsEncodingParametersInURI containsObject:[[request HTTPMethod] uppercaseString]]) {
        if (query && query.length > 0) {
            // 将query合并到mutbleRequest的query url 上
            mutableRequest.URL = [NSURL URLWithString:[[mutableRequest.URL absoluteString] stringByAppendingFormat:mutableRequest.URL.query ? @"&%@" : @"?%@", query]];
        }
    } else {
        // 4.将query设置到http body上
        if (!query) {
            query = @"";
        }
        if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"]) {
            [mutableRequest setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
        }
        [mutableRequest setHTTPBody:[query dataUsingEncoding:self.stringEncoding]];
    }

    return mutableRequest;
}

其中在第二部构建查询字符串时,提供了一个queryStringSerialization block,可以自定义这个构建方法。当然如果没设置这个 block,就会使用默认的 AFHTTPRequestQueryStringDefaultStyle case,调用 AFQueryStringFromParameters()方法来构建字符串。

TAG标签:
版权声明:本文由必威发布于必威-编程,转载请注明出处:实现了这个协议,② multipart request相关内容