1 Overview
AFHTTPSessionManager is a subclass of AFURLSessionManager.We can make HTTP requests through this class.In fact, the whole AFHTTPSessionManager logic is simple, it just splits the requests in HTTP and calls the parent class to handle them.I'll talk about several basic formats of POST upload data through the AFHTTPSessionManager api, then I'll analyze the AFHTTPSessionManager at random.
2 Common formats for POST requests
The HTTP request methods specified in the HTTP/1.1 protocol are OPTIONS, GET, HEAD, POST, PUT, DELETE, TRACE, CONNECT.POST is generally used to submit data to the server. Next, we will discuss several ways POST can submit data.The protocol specifies that data submitted by POST must be placed in the message body, but it does not specify how the data must be encoded.In fact, the format of the message body is entirely up to the developer as long as the last HTTP request sent satisfies the above format.
However, it makes sense for the server to parse the data successfully before it is sent out.Common server-side languages such as php, python, and their framework all have built-in capabilities to automatically parse common data formats.The service side usually knows how the message body in the request is encoded based on the Content-Type field in the headers, and then parses the body.So when it comes to POST submission data scheme, there are two parts, Content-Type and encoding method of message body.
POST request in 2.1 application/x-www-form-urlencoded formatThis should be the most common way for POST to submit data.If the enctype property is not set on the browser's native form, the data will eventually be submitted as application/x-www-form-urlencoded.Content-Type is specified as application/x-www-form-urlencoded, and the submitted data is encoded as key1=val1&key2=val2, both of which are URL transcoded.
The following request is a request to the server POST automatically when a short book enters an article page, estimating the number of times the article has been read, etc.Look specifically at the following:
//Request sent, delete cookie related parts POST /notes/e15592ce40ae/mark_viewed.json HTTP/1.1 Host: www.jianshu.com User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.11; rv:52.0) Gecko/20100101 Firefox/52.0 Accept: */* Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3 Accept-Encoding: gzip, deflate X-CSRF-Token: vJvptva4Tqou/V3dd3nFCrcvRsb78FReHuIYZke5PVAnfR/tIAAMCfuaB2Z2/gaEohIZAsiEksUYyPqzg3DpSA== Content-Type: application/x-www-form-urlencoded; charset=UTF-8 X-Requested-With: XMLHttpRequest Referer: http://www.jianshu.com/p/e15592ce40ae Content-Length: 98 Connection: keep-alive Cache-Control: max-age=0 //Requestor uuid=4e3abc0f-1824-4a5d-982f-7d9dee92d9cd&referrer=http%3A%2F%2Fwww.jianshu.com%2Fu%2Fad726ba6935d
Use AFHTTPSessionManager to implement the above application/x-www-form-urlencoded request.
AFHTTPSessionManager *manager = [[AFHTTPSessionManager alloc]initWithSessionConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]]; NSDictionary *params = @{ @"uuid":@"4e3abc0f-1824-4a5d-982f-7d9dee92d9cd", @"referrer":@"http://www.jianshu.com/p/e15592ce40ae" }; NSURLSessionDataTask *task = [manager POST:@"http://www.jianshu.com//notes/e15592ce40ae/mark_viewed.json" parameters:params progress:^(NSProgress * _Nonnull uploadProgress) { NSLog(@"Progress Update"); } success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) { NSLog(@"Return data:%@",responseObject); } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) { NSLog(@"Return error:%@",error); }]; [task resume];POST request in 2.2 multipart/form-data format
The underlying method of Multipart/form-data is POST, which is a combination of POST methods.
Multipart/form-data differs from POST methods in the request header and body.
The request header for Multipart/form-data must contain a special header information: Content-Type, whose value must also be specified as multipart/form-data, and a content splitter to split the contents of multiple POST s in the request body, such as the file content and text content, which naturally need to be split up, otherwise the recipientThis file cannot be parsed and restored properly.
The request body of Multipart/form-data is also a string, but unlike the request body of post, it is constructed in a simple name=value connection, while Multipart/form-data is constructed with delimiters added.
The header information requested is as follows:
//Where XXXX is my custom separator, everyone can choose their own separator Content-Type: multipart/form-data; boundary=xxxxx
Let's take a look at one of my Multipart/form-data requests:
POST /uploadFile HTTP/1.1 Host: Here is url,No Exposure^_^ Content-Type: multipart/form-data; boundary=xxxxx Connection: keep-alive Accept: */* User-Agent: AFNetWorking3.X%E6%BA%90%E7%A0%81%E9%98%85%E8%AF%BB/1 CFNetwork/808.2.16 Darwin/15.6.0 Content-Length: 32175 Accept-Language: en-us Accept-Encoding: gzip, deflate --xxxxx Content-Disposition: form-data;name="file" img.jpeg --xxxxx Content-Disposition: form-data;name="businessType" CC_USER_CENTER --xxxxx Content-Disposition: form-data;name="fileType" image --xxxxx Content-Disposition:form-data;name="file";filename="img1.jpeg" Content-Type:image/png //Here's the picture data. It's too long. I'll delete it --xxxxx--
This request has three parameters file,businessType,fileType.For example, the file parameter and its values are transmitted in the following formats:
--xxxxx Content-Disposition: form-data;name="file" img.jpeg
This is the value of a parameter.The agreement stipulates this format, and there is no reason why.Let's look at the picture data section:
--xxxxx Content-Disposition:form-data;name="file";filename="img1.jpeg" Content-Type:image/png Here's the picture data. It's too long. I'll delete it --xxxxx--Among them, name="parameter name" filename="file name". In normal development which corresponds to the recipient side, you can ask the server side. File name means the name saved as a file on the server side. This parameter is concurrent because the server will handle the storage of files according to their own requirements.
The next line is the specified type, and I'm writing the PNG picture type here, which can be written to suit your actual needs.If we want to upload more than one picture or file, we just need to follow the specified format, such as the following request to upload two pictures:
POST /uploadFile HTTP/1.1 Host: Here is url,No Exposure^_^ Content-Type: multipart/form-data; boundary=xxxxx Connection: keep-alive Accept: */* User-Agent: AFNetWorking3.X%E6%BA%90%E7%A0%81%E9%98%85%E8%AF%BB/1 CFNetwork/808.2.16 Darwin/15.6.0 Content-Length: 32175 Accept-Language: en-us Accept-Encoding: gzip, deflate --xxxxx Content-Disposition: form-data;name="file" img.jpeg --xxxxx Content-Disposition: form-data;name="businessType" CC_USER_CENTER --xxxxx Content-Disposition: form-data;name="fileType" image --xxxxx Content-Disposition:form-data;name="file";filename="img1.jpeg" Content-Type:image/png //This is Picture 1 data. It's too long. I deleted it --xxxxx Content-Disposition:form-data;name="file";filename="img2.jpeg" Content-Type:image/png //This is Picture 1 data. It's too long. I deleted it --xxxxx--
Below is the implementation code for a multipart/form-data request in my Demo, implemented with NSRULDataTask and AFHTTPSessionManager, respectively. We can see that the second method is much easier because AFN has helped us do the splicing:
//Method One - (IBAction)updatePic:(id)sender { //Request Header Parameters NSDictionary *dic = @{ @"businessType":@"CC_USER_CENTER", @"fileType":@"image", @"file":@"img.jpeg" }; //Request volume picture data NSData *imageData = UIImagePNGRepresentation([UIImage imageNamed:@"1.png"]); //Create request NSMutableURLRequest *request = [[NSMutableURLRequest alloc]initWithURL:[NSURL URLWithString:url]]; //post method [request setHTTPMethod:@"POST"]; // Set the request header format to Content-Type:multipart/form-data; boundary=xxxx //[request setValue:@"multipart/form-data; boundary=xxxxx" forHTTPHeaderField:@"Content-Type"]; AFHTTPSessionManager *manager = [[AFHTTPSessionManager alloc]initWithSessionConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]]; NSURLSessionDataTask *task = [manager POST:url parameters:dic constructingBodyWithBlock:^(id<AFMultipartFormData> _Nonnull formData) { //Parameters in Request Body NSDictionary *bodyDic = @{ @"Content-Disposition":@"form-data;name=\"file\";filename=\"img.jpeg\"", @"Content-Type":@"image/png", }; [formData appendPartWithHeaders:bodyDic body:imageData]; } progress:^(NSProgress * _Nonnull uploadProgress) { NSLog(@"Download Progress"); } success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) { NSLog(@"Download Successful:%@",responseObject); } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) { NSLog(@"Download failed%@",error); }]; [task resume]; } //Method 2 - (IBAction)multipartformPost2:(id)sender { //parameter NSDictionary *dic = @{ @"businessType":@"CC_USER_CENTER", @"fileType":@"image", @"file":@"img.jpeg" }; NSString *boundaryString = @"xxxxx"; NSMutableString *str = [NSMutableString string]; [dic enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) { [str appendFormat:@"--%@\r\n",boundaryString]; [str appendFormat:@"%@name=\"%@\"\r\n\r\n",@"Content-Disposition: form-data;",key]; [str appendFormat:@"%@\r\n",obj]; }]; NSMutableData *requestMutableData=[NSMutableData data]; [str appendFormat:@"--%@\r\n",boundaryString]; [str appendFormat:@"%@:%@",@"Content-Disposition",@"form-data;"]; [str appendFormat:@"%@=\"%@\";",@"name",@"file"]; [str appendFormat:@"%@=\"%@\"\r\n",@"filename",@"img1.jpeg"]; [str appendFormat:@"%@:%@\r\n\r\n",@"Content-Type",@"image/png"]; //Convert to binary data [requestMutableData appendData:[str dataUsingEncoding:NSUTF8StringEncoding]]; NSData *imageData = UIImagePNGRepresentation([UIImage imageNamed:@"1.png"]); //File Data Section [requestMutableData appendData:imageData]; //Add Ending boundary [requestMutableData appendData:[[NSString stringWithFormat:@"\r\n--%@--\r\n",boundaryString] dataUsingEncoding:NSUTF8StringEncoding]]; //Create a request object NSMutableURLRequest *request = [[NSMutableURLRequest alloc]initWithURL:[NSURL URLWithString:url]]; //post method [request setHTTPMethod:@"POST"]; // Set the request header format to Content-Type:multipart/form-data; boundary=xxxx [request setValue:[NSString stringWithFormat:@"multipart/form-data; boundary=%@",boundaryString] forHTTPHeaderField:@"Content-Type"]; //session NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]]; NSURLSessionDataTask *task = [session uploadTaskWithRequest:request fromData:requestMutableData completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { NSString *result = [[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding]; NSLog(@"%@",result); }]; [task resume]; }
Summary of POST requests in Multipart/form-data format:
File type parameter name= "parameter name" must correspond to the server side, when developing, you can ask the server side personnel, I am a file here.
The data portion of the uploaded file is spliced using binary data (NSData).
The strings of the upper and lower boundaries are converted to binary data (NSData) and stitched together with the binary data of the file part to be sent to the server as the requester.
There needs to be a certain `rn@ at the end of each line.
Next, I will use NSURLSessionDataTask frequently to make a POST request for application/json.And I store the volume data in a test.txt file, read it from the file, and upload it.
//test.txt file content {"name":"huang","phone":"124"}
My requests through the package capture software are as follows, just like other POST requests, they are spliced in a different way and in a different format, with different Content-Type s:
POST /posts HTTP/1.1 Host: jsonplaceholder.typicode.com Content-Type: application/json Connection: keep-alive Accept: application/json User-Agent: AFNetWorking3.X%E6%BA%90%E7%A0%81%E9%98%85%E8%AF%BB/1 CFNetwork/808.2.16 Darwin/15.6.0 Content-Length: 31 Accept-Language: en-us Accept-Encoding: gzip, deflate {"name":"huang","phone":"124"}
Here's my Demo implementation
- (IBAction)applicationjsonPOST2:(id)sender { NSMutableURLRequest * request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"http://jsonplaceholder.typicode.com/posts"]]; //Refers to the type of requestor.Because the file inside our test.txt is a string in JSON format.So I'm specifying `application/json`here [request addValue:@"application/json" forHTTPHeaderField:@"Content-Type"]; [request addValue:@"application/json" forHTTPHeaderField:@"Accept"]; [request setHTTPMethod:@"POST"]; [request setCachePolicy:NSURLRequestReloadIgnoringCacheData]; [request setTimeoutInterval:20]; NSString *path = [[NSBundle mainBundle] pathForResource:@"test" ofType:@"txt"]; NSURL *url = [NSURL URLWithString:[path stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]]; NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]]; //Use Block to process returned data NSURLSessionDataTask *task = [session uploadTaskWithRequest:request fromFile:url completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { NSString *result = [[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding]; NSLog(@"%@",result); }]; [task resume]; }
AFHTTPSessionManager analysis
The analysis of POST requests is mainly discussed above, mainly because AFHTTPSessionManager does not have much logic, it mainly calls the implementation of AFURLSessionManager.Another way is to change the splicing process of URLs through baseURL s.Now I'll pick out their differences and analyze them:
1 First there's one more attribute
@property (nonatomic, strong) AFHTTPRequestSerializer <AFURLRequestSerialization> * requestSerializer;
The main purpose of this property is to help us stitch the request header and the request body. From Demo above, we found that many of the requests were stitched through the requestSerializer.If we do not set it manually, the default is an AFHTTPRequestSerializer object.You can see this in the Initialization Method.
2 Override the setter method of the security policy property to increase exception handling for SSLPinningMode.
- (void)setSecurityPolicy:(AFSecurityPolicy *)securityPolicy { //Adds exception handling for SSLPinningMode. if (securityPolicy.SSLPinningMode != AFSSLPinningModeNone && ![self.baseURL.scheme isEqualToString:@"https"]) { NSString *pinningMode = @"Unknown Pinning Mode"; switch (securityPolicy.SSLPinningMode) { case AFSSLPinningModeNone: pinningMode = @"AFSSLPinningModeNone"; break; case AFSSLPinningModeCertificate: pinningMode = @"AFSSLPinningModeCertificate"; break; case AFSSLPinningModePublicKey: pinningMode = @"AFSSLPinningModePublicKey"; break; } NSString *reason = [NSString stringWithFormat:@"A security policy configured with `%@` can only be applied on a manager with a secure base URL (i.e. https)", pinningMode]; @throw [NSException exceptionWithName:@"Invalid Security Policy" reason:reason userInfo:nil]; } //Call the setter method of the `securityPolicy'property of `AFURLSessionManager'. [super setSecurityPolicy:securityPolicy]; }
3 Implementation of NSCopying and NSSecureCoding protocols
The implementation of the NSCopying and NSSecureCoding protocols adds duplication of three attributes: requestSerializer, responseSerializer, and security policy.That is, the manager is copied using the copy method, and the configuration of these three attributes is copied along with it.The parent AFURSSessionManager only replicates the configuration.
+ (BOOL)supportsSecureCoding { return YES; } - (instancetype)initWithCoder:(NSCoder *)decoder { NSURL *baseURL = [decoder decodeObjectOfClass:[NSURL class] forKey:NSStringFromSelector(@selector(baseURL))]; //Get NSU LSessionConfiguration for the current manager NSURLSessionConfiguration *configuration = [decoder decodeObjectOfClass:[NSURLSessionConfiguration class] forKey:@"sessionConfiguration"]; if (!configuration) { NSString *configurationIdentifier = [decoder decodeObjectOfClass:[NSString class] forKey:@"identifier"]; if (configurationIdentifier) { //The NSURLSessionConfiguration method for iOS7 and iOS8 initialization is different.So separate processing #if (defined(__IPHONE_OS_VERSION_MIN_REQUIRED) && __IPHONE_OS_VERSION_MIN_REQUIRED >= 80000) || (defined(__MAC_OS_X_VERSION_MIN_REQUIRED) && __MAC_OS_X_VERSION_MIN_REQUIRED >= 1100) configuration = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:configurationIdentifier]; #else configuration = [NSURLSessionConfiguration backgroundSessionConfiguration:configurationIdentifier]; #endif } } //Initialize a new manager self = [self initWithBaseURL:baseURL sessionConfiguration:configuration]; if (!self) { return nil; } //Documents were added for the three properties `requestSerializer', `responseSerializer', `securityPolicy'. self.requestSerializer = [decoder decodeObjectOfClass:[AFHTTPRequestSerializer class] forKey:NSStringFromSelector(@selector(requestSerializer))]; self.responseSerializer = [decoder decodeObjectOfClass:[AFHTTPResponseSerializer class] forKey:NSStringFromSelector(@selector(responseSerializer))]; AFSecurityPolicy *decodedPolicy = [decoder decodeObjectOfClass:[AFSecurityPolicy class] forKey:NSStringFromSelector(@selector(securityPolicy))]; if (decodedPolicy) { self.securityPolicy = decodedPolicy; } return self; } - (void)encodeWithCoder:(NSCoder *)coder { [super encodeWithCoder:coder]; //Add an archive of baseURL properties [coder encodeObject:self.baseURL forKey:NSStringFromSelector(@selector(baseURL))]; if ([self.session.configuration conformsToProtocol:@protocol(NSCoding)]) { [coder encodeObject:self.session.configuration forKey:@"sessionConfiguration"]; } else { [coder encodeObject:self.session.configuration.identifier forKey:@"identifier"]; } //An archive was added for the three attributes `requestSerializer', `responseSerializer', `securityPolicy'. [coder encodeObject:self.requestSerializer forKey:NSStringFromSelector(@selector(requestSerializer))]; [coder encodeObject:self.responseSerializer forKey:NSStringFromSelector(@selector(responseSerializer))]; [coder encodeObject:self.securityPolicy forKey:NSStringFromSelector(@selector(securityPolicy))]; } #pragma mark - NSCopying - (instancetype)copyWithZone:(NSZone *)zone { AFHTTPSessionManager *HTTPClient = [[[self class] allocWithZone:zone] initWithBaseURL:self.baseURL sessionConfiguration:self.session.configuration]; //Copies of the three attributes `requestSerializer', `responseSerializer', `securityPolicy` were added. HTTPClient.requestSerializer = [self.requestSerializer copyWithZone:zone]; HTTPClient.responseSerializer = [self.responseSerializer copyWithZone:zone]; HTTPClient.securityPolicy = [self.securityPolicy copyWithZone:zone]; return HTTPClient; }
Implementation of methods such as 4 HEAD and PUT
I am not going to go into these two approaches here, because AFHTTPSessionManager mostly splits requests for HEAD and PUT through its requestSerializer property.As I prepare to analyze the AFHTTPRequestSerializer, I'll look at how this is implemented.
//The request object is stitched using the requestSerializer attribute. NSMutableURLRequest *request = [self.requestSerializer requestWithMethod:method URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString] parameters:parameters error:&serializationError];
Last Original Address,demo address.