AFHTTPSessionManager for AFNetWorking Source

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 format

This 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.

POST request in 2.3 application/json format

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.

Tags: iOS JSON Session encoding Attribute

Posted on Sun, 07 Jul 2019 13:33:34 -0400 by chenggn