Overview:

This article mainly introduces AFNetworking request parameters in serialized parts, specific code in AFURLRequestSerialization. AFURLRequestSerialization contains four parts:

  • AFHTTPRequestSerializaiton
  • AFJSONRequestSerializer
  • AFPropertyListRequestSerializer

AFHTTPRequestSerialization mainly set HTTP request header, set the timeout, BA certification, deal with the user name password, and so on. The main functions are divided into three parts:

  1. Handle all GET,HEAD, and DELETE requests
  2. Handle POST requests where content-Type is application/ X-www-form-urlencoded
  3. Processing is the content-type multipart/form – the data type of a POST request, the request is build through AFStreamingMultipartFormData object implementation.

AFJSONRequestSerializer inherited from AFHTTPRequestSerialization class, using NSJSONSerialization serialized json format (application/json) parameters, Convert a Dictionary object to NSData, which only handles POST requests.

AFPropertyListRequestSerializer also inherited AFHTTPRequestSerialization class, Use NSPropertyListSerialization object to serialize the XML format (application/x – plist) parameters, it can only handle a POST request.

Above all, AFHTTPRequestSerialization is the most important and the most complicated part of source is mainly aimed at this part for analysis.

Source code analysis:

Parameter serialization and encoding

- (NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request withParameters:(id)parameters error:(NSError *__autoreleasing *)error { NSParameterAssert(request); NSMutableURLRequest *mutableRequest = [request mutableCopy]; / / set the HTTP request header [self HTTPRequestHeaders enumerateKeysAndObjectsUsingBlock: ^ (id field, id value, BOOL * __unused stop) { if (! [request valueForHTTPHeaderField:field]) { [mutableRequest setValue:value forHTTPHeaderField:field]; } }]; If (parameters) {NSString *query = nil; / / queryStringSerilization is a block, is mainly used to customize parameter serialization logic, Return a serialized complete results if (self. QueryStringSerialization) {NSError * serializationError; query = self.queryStringSerialization(request, parameters, & serializationError); if (serializationError) { if (error) { *error = serializationError; } return nil; }} else {switch (self queryStringSerializationStyle) {/ / using AFNetworking default serialization format, A = 1 & b = 2 this case AFHTTPRequestQueryStringDefaultStyle: Query = = = stringEncoding = = = = = = = = = = = = AFQueryStringFromParametersWithEncoding(parameters, self.stringEncoding); break; }} / / HTTPMethodsEncodingParametersInURI defines a GET, if the DELETE, HEAD3 methods ([self. HTTPMethodsEncodingParametersInURI containsObject:[[request HTTPMethod] uppercaseString]]) { mutableRequest.URL = [NSURL URLWithString:[[mutableRequest.URL  absoluteString] stringByAppendingFormat:mutableRequest.URL.query ? @"&%@" : @"?%@", query]]; } else { //POST if (! [mutableRequest valueForHTTPHeaderField:@"Content-Type"]) { [mutableRequest setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"]; } / / setHTTPBody accept a NSData parameters, the query into a NSData [mutableRequest setHTTPBody: [query dataUsingEncoding: self. StringEncoding]].  } } return mutableRequest; }Copy the code
/ / for AFURLRequestSerialization inside the method called static nsstrings * AFQueryStringFromParametersWithEncoding (NSDictionary * parameters,  NSStringEncoding stringEncoding) { NSMutableArray *mutablePairs = [NSMutableArray array]; //AFQueryStringPair is an encapsulated key-value pair object that converts key-value pairs in the dictionary to query //string. Including some special character encoding / / AFQueryStringPairsFromDictionary dictionary into AFQueryStringPair collection for (AFQueryStringPair * pair in AFQueryStringPairsFromDictionary(parameters)) { [mutablePairs addObject:[pair URLEncodedStringValueWithEncoding:stringEncoding]]; } return [mutablePairs componentsJoinedByString:@"&"]; }Copy the code
- (nsstrings *) URLEncodedStringValueWithEncoding: NSStringEncoding stringEncoding {/ / met nil or NSNULL boundary value of the if (! self.value || [self.value isEqual:[NSNull null]]) { return AFPercentEscapedQueryStringKeyFromStringWithEncoding([self.field description], stringEncoding); } else { return [NSString stringWithFormat:@"%@=%@", AFPercentEscapedQueryStringKeyFromStringWithEncoding([self.field description], stringEncoding), AFPercentEscapedQueryStringValueFromStringWithEncoding([self.value description], stringEncoding)]; }}Copy the code
/ / the main foundation function called CFURLCreateStringByAddingPercentEscapes, it basically is to querystri / / ng special characters in (&,?) Encode in %+ASCII format. According to the document, it is recommended to use nsstrings / / stringByAddingPercentEncodingWithAllowedCharacters:] method, this method USES the utf-8 encoding. static NSString * AFPercentEscapedQueryStringValueFromStringWithEncoding( NSString *string, NSStringEncoding encoding) { return (__bridge_transfer NSString *)CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (__bridge CFStringRef)string, NULL, (__bridge CFStringRef)kAFCharactersToBeEscapedInQueryString, CFStringConvertNSStringEncodingToEncoding(encoding)); }Copy the code

AFStreamingMutipartFormData

Let’s start with a request in mutipart/form-data format

POST http://www.example.com HTTP / 1.1 the content-type: multipart/form - data; boundary=----WebKitFormBoundaryrGKCBY7qhFd3TrwA ------WebKitFormBoundaryrGKCBY7qhFd3TrwA Content-Disposition: form-data; name="text" title ------WebKitFormBoundaryrGKCBY7qhFd3TrwA Content-Disposition: form-data; name="file"; filename="chrome.png" Content-Type: image/png PNG ... content of chrome.png ... ------WebKitFormBoundaryrGKCBY7qhFd3TrwA--Copy the code
  1. Its content-Type contains two parts, the first part is multipart/form-data, and the second part is boundary. Boundary is generally generated randomly, so it can be kept unique. You also need to have content-Length up there, which is not in the diagram.
  2. The part of each argument has content-disposition and begins with –boundary and ends with –boundary– for the last argument.

AFNetworking by dealing with multipart/AFStreamingMutipartFormData form – the data format of the POST request, it through NSInputStream to build the body of the HTTP request. The information for each parameter is encapsulated in AFHTTPBodyPart, and the information for all parameters is encapsulated in AFMulipartBodyStream. AFHTTPBodyPart has an NSInputStream object that writes each parameter to the body, while AFMutipartBodyStream inherits NSInputStream to handle all parameter writes to the body. Its structure is shown as follows:

/ / processing multilpart/form - data (POST) request - (NSMutableURLRequest *) multipartFormRequestWithMethod (nsstrings *) method URLString:(NSString *)URLString parameters:(NSDictionary *)parameters constructingBodyWithBlock:(void (^)(id <AFMultipartFormData> formData))block error:(NSError *__autoreleasing *)error { NSParameterAssert(method); NSParameterAssert(! [method isEqualToString:@"GET"] && ! [method isEqualToString:@"HEAD"]); / / parameters travels here is nil, because the current format (mutilpart/form - data) / / need to build parameters by AFStreamingMulitpartFormData. NSMutableURLRequest *mutableRequest = [self requestWithMethod:method URLString:URLString parameters:nil error:error]; __block AFStreamingMultipartFormData *formData = [[AFStreamingMultipartFormData alloc] initWithURLRequest:mutableRequest  stringEncoding:NSUTF8StringEncoding]; {if (parameters) for (AFQueryStringPair * pair in AFQueryStringPairsFromDictionary (parameters)) {/ / for the parameters of the dictionary, I'm just going to convert its value to NSData, 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]; } the if (data) {/ / appendPartWithFormData AFStreamingMultipartFormData object key/value pair into AFHTTPBodyP //art and put it into the AFMultipartBodyStream collection. [formData appendPartWithFormData:data name:[pair.field description]]; }}} if (block) {// The current block is used to handle non-dictionary cases, such as an NSURL argument, or simply an NSInputStream. block(formData); } / / AFStreamingMultipartFormData requestByFinalizingMultipartFormData mainly set/cont ent - Type and the content - Length and boundary return [formData requestByFinalizingMultipartFormData]; }Copy the code
- (NSMutableURLRequest *)requestByFinalizingMultipartFormData { if ([self.bodyStream isEmpty]) { return self.request; } / / set a boundary [self bodyStream setInitialAndFinalBoundaries]; / / set the AFStreamingMultipartFormData AFMultipartBodyStream to HTTP body on stream [self. The request setHTTPBodyStream:self.bodyStream]; [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; }Copy the code
- (void)setInitialAndFinalBoundaries { if ([self.HTTPBodyParts count] > 0) { for (AFHTTPBodyPart *bodyPart in self.HTTPBodyParts) { bodyPart.hasInitialBoundary = NO; bodyPart.hasFinalBoundary = NO; } / / set a boundary head [[self. HTTPBodyParts objectAtIndex: 0] setHasInitialBoundary: YES]; // Set the end of the boundary [[self.httpbodyParts lastObject] setHasFinalBoundary:YES]; }}Copy the code
Content-disposition - (void)appendPartWithFormData:(NSData *)data name:(NSString *)name {content-disposition - (void)appendPartWithFormData:(NSData *)data name:(NSString *)name { NSParameterAssert(name); NSMutableDictionary *mutableHeaders = [NSMutableDictionary dictionary]; [mutableHeaders setValue:[NSString stringWithFormat:@"form-data; name=\"%@ \"", name] forKey:@"Content-Disposition"]; [self appendPartWithHeaders:mutableHeaders body:data]; }Copy the code
// Build the AFHTTPBodyPart object, in which case, AppendPartWithHeaders :(NSDictionary *)headers body:(nsdata *)body { NSParameterAssert(body); AFHTTPBodyPart *bodyPart = [[AFHTTPBodyPart alloc] init]; bodyPart.stringEncoding = self.stringEncoding; bodyPart.headers = headers; bodyPart.boundary = self.boundary; bodyPart.bodyContentLength = [body length]; bodyPart.body = body; [self.bodyStream appendHTTPBodyPart:bodyPart]; }Copy the code

In addition to NSData, you can also pass NSInputStream,NSURL to build an AFHTTPBodyPart object, similar to NSData, by setting Content-Disposition and Content-Type, AFHTTPBodyPart is then constructed using data.

AFMultipartBodyStream

- (NSInteger)read:(uint8_t *)buffer maxLength:(NSUInteger)length { / / AFStreamingMutipartFormData set AFMultipartBodyStream to NSUrlRequest httpbodys / / tream, foundation will automatically calls read: maxLength: method, The method implementation iterates through all the previously built AFHTTP //BodyPart objects and calls their read:maxLength: methods to retrieve the data. if ([self streamStatus] == NSStreamStatusClosed) { return 0; } NSInteger totalNumberOfBytesRead = 0; #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wgnu" 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.0 f) {[NSThread sleepForTimeInterval: self. The delay]; } } } } return totalNumberOfBytesRead; }Copy the code

AFHTTPBodyPart

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; }Copy the code
- (NSInteger)read:(uint8_t *)buffer maxLength:(NSUInteger)length { NSInteger totalNumberOfBytesRead = 0; If (_phase = = AFEncapsulationBoundaryPhase) {/ / boundary section, To NSData NSData * encapsulationBoundaryData = [([self hasInitialBoundary]? AFMultipartFormInitialBoundary (self. A boundary)  : AFMultipartFormEncapsulationBoundary(self.boundary)) dataUsingEncoding:self.stringEncoding]; totalNumberOfBytesRead += [self readData:encapsulationBoundaryData intoBuffer:&buffer[totalNumberOfBytesRead] maxLength:(length - ( NSUInteger)totalNumberOfBytesRead)]; } if (_phase == AFHeaderPhase) {content-content-length NSData *headersData = [[self stringForHeaders] dataUsingEncoding:self. stringEncoding]; totalNumberOfBytesRead += [self readData:headersData intoBuffer:&buffer [totalNumberOfBytesRead] maxLength:(length - (NSUInteger) totalNumberOfBytesRead)]; } if (_phase == AFBodyPhase) { NSInteger numberOfBytesRead = 0; // The body part of each AFHTTPBodyPart is converted to an NSInputStream object based on the actual // data type in the inputStream method. So all you need to do is call the read:maxLength method that comes with Foundation. 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]; }} if (_phase == AFFinalBoundaryPhase) {// If the current AFHTTPBodyPart is the last parameter, There will be one more part of boundary than the other parameters, which will be converted to NSData NSData *closingBoundaryData = ([self hasFinalBoundary]? [ AFMultipartFormFinalBoundary(self.boundary) dataUsingEncoding:self. stringEncoding] : [NSData data]); totalNumberOfBytesRead += [self readData:closingBoundaryData intoBuffer :&buffer[totalNumberOfBytesRead] maxLength:(length - (NSUInteger) totalNumberOfBytesRead)]; } return totalNumberOfBytesRead; }Copy the code
- (NSInteger)readData:(NSData *)data intoBuffer:(uint8_t *)buffer maxLength:(NSUInteger)length { #pragma Clang Diagnostic push #pragma clang diagnostic ignored "-wgnu" /* Do not understand why range starts from _phaseReadOffset when reading data. Shouldn't we start at zero, Range = NSMakeRange((NSUInteger)_phaseReadOffset, MIN([data length] - ((NSUInteger)_phaseReadOffset), length)); [data getBytes:buffer range:range]; #pragma clang diagnostic pop _phaseReadOffset += range.length; if (((NSUInteger)_phaseReadOffset) >= [data length]) { [self transitionToNextPhase]; } return (NSInteger)range.length; }Copy the code
// Serialize the headers part of AFHTTPBodyPart - (NSString *)stringForHeaders {NSMutableString *headerString = [NSMutableString String];  for (NSString *field in [self.headers allKeys]) { [headerString appendString:[NSString stringWithFormat:@"%@: %@%@", field, [self.headers valueForKey:field], kAFMultipartFormCRLF]]; } [headerString appendString:kAFMultipartFormCRLF]; return [NSString stringWithString:headerString]; }Copy the code
// Calculate the content length of each AFHTTPBodyPart, including transmission parameters, request headers and boundary information. In AFMultiStream, the content-Length of all afHttpBodyParts is added up. Content-length for the entire POST request body. - (unsigned long long)contentLength { unsigned long long length = 0; //boundary NSData *encapsulationBoundaryData = [([self hasInitialBoundary] ? AFMultipartFormInitialBoundary(self.boundary) : AFMultipartFormEncapsulationBoundary(self.boundary)) dataUsingEncoding:self .stringEncoding]; length += [encapsulationBoundaryData length]; //headers NSData *headersData = [[self stringForHeaders] dataUsingEncoding:self. stringEncoding]; length += [headersData length]; //data length += _bodyContentLength; //close boudary NSData *closingBoundaryData = ([self hasFinalBoundary] ? [ AFMultipartFormFinalBoundary(self.boundary) dataUsingEncoding:self. stringEncoding] : [NSData data]); length += [closingBoundaryData length]; return length; }Copy the code