diff --git a/SQDebug/AFNetworking/AFNetworking-4.0.1-Debug/AFNetworking-Debug.xcodeproj/project.pbxproj b/SQDebug/AFNetworking/AFNetworking-4.0.1-Debug/AFNetworking-Debug.xcodeproj/project.pbxproj index b8ffc74a..5a718f4f 100644 --- a/SQDebug/AFNetworking/AFNetworking-4.0.1-Debug/AFNetworking-Debug.xcodeproj/project.pbxproj +++ b/SQDebug/AFNetworking/AFNetworking-4.0.1-Debug/AFNetworking-Debug.xcodeproj/project.pbxproj @@ -7,6 +7,9 @@ objects = { /* Begin PBXBuildFile section */ + 143474D1256BFA8300149168 /* SQURLRequestSerializationTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 143474D0256BFA8300149168 /* SQURLRequestSerializationTest.m */; }; + 14963573256BEC5200B1E31B /* SQHTTPBodyPart.m in Sources */ = {isa = PBXBuildFile; fileRef = 14963572256BEC5200B1E31B /* SQHTTPBodyPart.m */; }; + 149635AB256BFA3600B1E31B /* AFNetworking_DebugTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 149635AA256BFA3600B1E31B /* AFNetworking_DebugTests.m */; }; 14A334872568D62400772436 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 14A334862568D62400772436 /* AppDelegate.m */; }; 14A3348A2568D62400772436 /* SceneDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 14A334892568D62400772436 /* SceneDelegate.m */; }; 14A3348D2568D62400772436 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 14A3348C2568D62400772436 /* ViewController.m */; }; @@ -19,8 +22,24 @@ 1E4C433D320B90193A1EE719 /* Pods_AFNetworking_Debug.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3E27C56A0FCB7F71F556F4D1 /* Pods_AFNetworking_Debug.framework */; }; /* End PBXBuildFile section */ +/* Begin PBXContainerItemProxy section */ + 149635AD256BFA3600B1E31B /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 14A3347A2568D62400772436 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 14A334812568D62400772436; + remoteInfo = "AFNetworking-Debug"; + }; +/* End PBXContainerItemProxy section */ + /* Begin PBXFileReference section */ 002F3802D60E85956ACBBB58 /* Pods-AFNetworking-Debug.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AFNetworking-Debug.release.xcconfig"; path = "Target Support Files/Pods-AFNetworking-Debug/Pods-AFNetworking-Debug.release.xcconfig"; sourceTree = ""; }; + 143474D0256BFA8300149168 /* SQURLRequestSerializationTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SQURLRequestSerializationTest.m; sourceTree = ""; }; + 14963571256BEC5200B1E31B /* SQHTTPBodyPart.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SQHTTPBodyPart.h; sourceTree = ""; }; + 14963572256BEC5200B1E31B /* SQHTTPBodyPart.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SQHTTPBodyPart.m; sourceTree = ""; }; + 149635A8256BFA3600B1E31B /* AFNetworking-DebugTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "AFNetworking-DebugTests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; + 149635AA256BFA3600B1E31B /* AFNetworking_DebugTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AFNetworking_DebugTests.m; sourceTree = ""; }; + 149635AC256BFA3600B1E31B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 14A334822568D62400772436 /* AFNetworking-Debug.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "AFNetworking-Debug.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 14A334852568D62400772436 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 14A334862568D62400772436 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; @@ -42,6 +61,13 @@ /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ + 149635A5256BFA3600B1E31B /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 14A3347F2568D62400772436 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -53,10 +79,21 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 149635A9256BFA3600B1E31B /* AFNetworking-DebugTests */ = { + isa = PBXGroup; + children = ( + 143474D0256BFA8300149168 /* SQURLRequestSerializationTest.m */, + 149635AA256BFA3600B1E31B /* AFNetworking_DebugTests.m */, + 149635AC256BFA3600B1E31B /* Info.plist */, + ); + path = "AFNetworking-DebugTests"; + sourceTree = ""; + }; 14A334792568D62400772436 = { isa = PBXGroup; children = ( 14A334842568D62400772436 /* AFNetworking-Debug */, + 149635A9256BFA3600B1E31B /* AFNetworking-DebugTests */, 14A334832568D62400772436 /* Products */, E8805DB42FDB0DFA806D2195 /* Pods */, D2153CAF6D5B087489DCB36C /* Frameworks */, @@ -67,6 +104,7 @@ isa = PBXGroup; children = ( 14A334822568D62400772436 /* AFNetworking-Debug.app */, + 149635A8256BFA3600B1E31B /* AFNetworking-DebugTests.xctest */, ); name = Products; sourceTree = ""; @@ -82,6 +120,8 @@ 14A3348C2568D62400772436 /* ViewController.m */, 14ED0254256A480700D370FD /* SQQueryStringPair.h */, 14ED0255256A480700D370FD /* SQQueryStringPair.m */, + 14963571256BEC5200B1E31B /* SQHTTPBodyPart.h */, + 14963572256BEC5200B1E31B /* SQHTTPBodyPart.m */, 14ED0265256A4AD700D370FD /* SQURLRequestSerialization.h */, 14ED0266256A4AD700D370FD /* SQURLRequestSerialization.m */, 14A3348E2568D62400772436 /* Main.storyboard */, @@ -113,6 +153,24 @@ /* End PBXGroup section */ /* Begin PBXNativeTarget section */ + 149635A7256BFA3600B1E31B /* AFNetworking-DebugTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 149635AF256BFA3600B1E31B /* Build configuration list for PBXNativeTarget "AFNetworking-DebugTests" */; + buildPhases = ( + 149635A4256BFA3600B1E31B /* Sources */, + 149635A5256BFA3600B1E31B /* Frameworks */, + 149635A6256BFA3600B1E31B /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 149635AE256BFA3600B1E31B /* PBXTargetDependency */, + ); + name = "AFNetworking-DebugTests"; + productName = "AFNetworking-DebugTests"; + productReference = 149635A8256BFA3600B1E31B /* AFNetworking-DebugTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; 14A334812568D62400772436 /* AFNetworking-Debug */ = { isa = PBXNativeTarget; buildConfigurationList = 14A3349B2568D62500772436 /* Build configuration list for PBXNativeTarget "AFNetworking-Debug" */; @@ -140,6 +198,10 @@ attributes = { LastUpgradeCheck = 1200; TargetAttributes = { + 149635A7256BFA3600B1E31B = { + CreatedOnToolsVersion = 12.0; + TestTargetID = 14A334812568D62400772436; + }; 14A334812568D62400772436 = { CreatedOnToolsVersion = 12.0; }; @@ -159,11 +221,19 @@ projectRoot = ""; targets = ( 14A334812568D62400772436 /* AFNetworking-Debug */, + 149635A7256BFA3600B1E31B /* AFNetworking-DebugTests */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ + 149635A6256BFA3600B1E31B /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 14A334802568D62400772436 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -219,11 +289,21 @@ /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ + 149635A4256BFA3600B1E31B /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 149635AB256BFA3600B1E31B /* AFNetworking_DebugTests.m in Sources */, + 143474D1256BFA8300149168 /* SQURLRequestSerializationTest.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 14A3347E2568D62400772436 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 14A3348D2568D62400772436 /* ViewController.m in Sources */, + 14963573256BEC5200B1E31B /* SQHTTPBodyPart.m in Sources */, 14A334872568D62400772436 /* AppDelegate.m in Sources */, 14ED0267256A4AD700D370FD /* SQURLRequestSerialization.m in Sources */, 14A334982568D62500772436 /* main.m in Sources */, @@ -234,6 +314,14 @@ }; /* End PBXSourcesBuildPhase section */ +/* Begin PBXTargetDependency section */ + 149635AE256BFA3600B1E31B /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 14A334812568D62400772436 /* AFNetworking-Debug */; + targetProxy = 149635AD256BFA3600B1E31B /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + /* Begin PBXVariantGroup section */ 14A3348E2568D62400772436 /* Main.storyboard */ = { isa = PBXVariantGroup; @@ -254,6 +342,44 @@ /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ + 149635B0256BFA3600B1E31B /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = NGPFU6GT2A; + INFOPLIST_FILE = "AFNetworking-DebugTests/Info.plist"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = "coderZsq.AFNetworking-DebugTests"; + PRODUCT_NAME = "$(TARGET_NAME)"; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/AFNetworking-Debug.app/AFNetworking-Debug"; + }; + name = Debug; + }; + 149635B1256BFA3600B1E31B /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = NGPFU6GT2A; + INFOPLIST_FILE = "AFNetworking-DebugTests/Info.plist"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = "coderZsq.AFNetworking-DebugTests"; + PRODUCT_NAME = "$(TARGET_NAME)"; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/AFNetworking-Debug.app/AFNetworking-Debug"; + }; + name = Release; + }; 14A334992568D62500772436 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -407,6 +533,15 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + 149635AF256BFA3600B1E31B /* Build configuration list for PBXNativeTarget "AFNetworking-DebugTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 149635B0256BFA3600B1E31B /* Debug */, + 149635B1256BFA3600B1E31B /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; 14A3347D2568D62400772436 /* Build configuration list for PBXProject "AFNetworking-Debug" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/SQDebug/AFNetworking/AFNetworking-4.0.1-Debug/AFNetworking-Debug/SQHTTPBodyPart.h b/SQDebug/AFNetworking/AFNetworking-4.0.1-Debug/AFNetworking-Debug/SQHTTPBodyPart.h new file mode 100644 index 00000000..eab80bbc --- /dev/null +++ b/SQDebug/AFNetworking/AFNetworking-4.0.1-Debug/AFNetworking-Debug/SQHTTPBodyPart.h @@ -0,0 +1,43 @@ +// +// SQHTTPBodyPart.h +// AFNetworking-Debug +// +// Created by 朱双泉 on 2020/11/23. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +NSString * SQCreateMultipartFormBoundary(void); + +NSString * SQMultipartFormInitialBoundary(NSString *boundary); + +NSString * SQMultipartFormEncapsulationBoundary(NSString *boundary); + +NSString * SQMultipartFormFinalBoundary(NSString *boundary); + +NSString * SQContentTypeForPathExtension(NSString *extension); + +@interface SQHTTPBodyPart : 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, assign) NSInputStream *inputStream; + +@property (nonatomic, assign) BOOL hasInitialBoundary; +@property (nonatomic, assign) BOOL hasFinalBoundary; + +@property (readonly, nonatomic, assign, getter=hasBytesAvailable) BOOL bytesAvailable; +@property (readonly, nonatomic, assign) unsigned long long contentLength; + +- (NSInteger)read:(uint8_t *)buffer maxLength:(NSUInteger)length; + +- (NSString *)stringForHeaders; + +@end + +NS_ASSUME_NONNULL_END diff --git a/SQDebug/AFNetworking/AFNetworking-4.0.1-Debug/AFNetworking-Debug/SQHTTPBodyPart.m b/SQDebug/AFNetworking/AFNetworking-4.0.1-Debug/AFNetworking-Debug/SQHTTPBodyPart.m new file mode 100644 index 00000000..4892112c --- /dev/null +++ b/SQDebug/AFNetworking/AFNetworking-4.0.1-Debug/AFNetworking-Debug/SQHTTPBodyPart.m @@ -0,0 +1,218 @@ +// +// SQHTTPBodyPart.m +// AFNetworking-Debug +// +// Created by 朱双泉 on 2020/11/23. +// + +#import "SQHTTPBodyPart.h" +#import + +NSString * SQCreateMultipartFormBoundary() { + return [NSString stringWithFormat:@"Boundary+%08X%08X", arc4random(), arc4random()]; +} + +NSString * const kSQMultipartFormCRLF = @"\r\n"; + +inline NSString * SQMultipartFormInitialBoundary(NSString *boundary) { + return [NSString stringWithFormat:@"--%@%@", boundary, kSQMultipartFormCRLF]; +} + +inline NSString * SQMultipartFormEncapsulationBoundary(NSString *boundary) { + return [NSString stringWithFormat:@"%@--%@%@", kSQMultipartFormCRLF, boundary, kSQMultipartFormCRLF]; // 细节 boundary前后有两个换行 +} + +inline NSString * SQMultipartFormFinalBoundary(NSString *boundary) { + return [NSString stringWithFormat:@"%@--%@--%@", kSQMultipartFormCRLF, boundary, kSQMultipartFormCRLF]; // 细节 boundary前后有两个换行 +} + +inline NSString * SQContentTypeForPathExtension(NSString *extension) { + NSString *UTI = (__bridge_transfer NSString *)UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (__bridge CFStringRef)extension, NULL); + NSString *contentType = (__bridge_transfer NSString *)UTTypeCopyPreferredTagWithClass((__bridge CFStringRef)UTI, kUTTagClassMIMEType); + if (!contentType) { + return @"application/octet-stream"; + } else { + return contentType; + } +} + +typedef enum { + SQEncapsulationBoundaryPhase = 1, + SQHeaderPhase = 2, + SQBodyPhase = 3, + SQFinalBoundaryPhase = 4, +} SQHTTPBodyPartReadPhase; + +@interface SQHTTPBodyPart () { + SQHTTPBodyPartReadPhase _phase; + NSInputStream *_inputStram; + unsigned long long _phaseReadOffset; +} + +/** + 过度到下一个阶段 + */ +- (BOOL)transitionToNextPhase; +- (NSInteger)readData:(NSData *)data intoBuffer:(uint8_t *)buffer maxLength:(NSUInteger)length; + +@end + +@implementation SQHTTPBodyPart + +- (BOOL)transitionToNextPhase { + if (![[NSThread currentThread] isMainThread]) { + dispatch_sync(dispatch_get_main_queue(), ^{ + [self transitionToNextPhase]; + }); + return YES; + } + switch (_phase) { + case SQEncapsulationBoundaryPhase: + _phase = SQHeaderPhase; + break; + case SQHeaderPhase: + [self.inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes]; + [self.inputStream open]; + _phase = SQBodyPhase; + break; + case SQBodyPhase: + [self.inputStream close]; + _phase = SQFinalBoundaryPhase; + break; + case SQFinalBoundaryPhase: + default: + _phase = SQEncapsulationBoundaryPhase; + break; + } + _phaseReadOffset = 0; + return YES; +} + +- (instancetype)init +{ + self = [super init]; + if (!self) { + return nil; + } + [self transitionToNextPhase]; + return self; +} + +- (instancetype)copyWithZone:(NSZone *)zone { + SQHTTPBodyPart *bodyPart = [[[self class] allocWithZone:zone] init]; + bodyPart.stringEncoding = self.stringEncoding; + bodyPart.headers = self.headers; + bodyPart.bodyContentLength = self.bodyContentLength; + bodyPart.body = self.body; + bodyPart.boundary = self.boundary; + return bodyPart; +} + +- (void)dealloc { + if (_inputStram) { + [_inputStram close]; + _inputStram = nil; + } +} + +- (NSInputStream *)inputStream { + if (!_inputStram) { + if ([self.body isKindOfClass:[NSData class]]) { + _inputStram = [NSInputStream inputStreamWithData:self.body]; + } else if ([self.body isKindOfClass:[NSURL class]]) { + _inputStram = [NSInputStream inputStreamWithURL:self.body]; + } else if ([self.body isKindOfClass:[NSInputStream class]]) { + _inputStram = self.body; + } else { + _inputStram = [NSInputStream inputStreamWithData:[NSData data]]; + } + } + return _inputStram; +} + +- (NSString *)stringForHeaders { + NSMutableString *headerString = [NSMutableString string]; + for (NSString *field in [self.headers allKeys]) { + [headerString appendString:[NSString stringWithFormat:@"%@: %@%@", field, [self.headers valueForKey:field], kSQMultipartFormCRLF]]; + } + [headerString appendString:kSQMultipartFormCRLF]; // 最后多加一个换行 + return [NSString stringWithString:headerString]; +} + +- (unsigned long long)contentLength { + unsigned long long length = 0; + NSData *encapsulationBoundaryData = [([self hasInitialBoundary] ? SQMultipartFormInitialBoundary(self.boundary) : SQMultipartFormEncapsulationBoundary(self.boundary)) dataUsingEncoding:self.stringEncoding]; + length += [encapsulationBoundaryData length]; + + NSData *headersData = [[self stringForHeaders] dataUsingEncoding:self.stringEncoding]; + length += [headersData length]; + + length += _bodyContentLength; + + NSData *closingBoundaryData = ([self hasFinalBoundary] ? [SQMultipartFormFinalBoundary(self.boundary) dataUsingEncoding:self.stringEncoding] : [NSData data]); + length += [closingBoundaryData length]; + return length; +} + +- (BOOL)hasBytesAvailable { + // 如果AFMultipartFormFinalBoundary不适合可用缓冲区,则允许再次调用read:maxLength: + if (_phase == SQFinalBoundaryPhase) { + return YES; + } + switch (self.inputStream.streamStatus) { + case NSStreamStatusNotOpen: + case NSStreamStatusOpening: + case NSStreamStatusOpen: + case NSStreamStatusReading: + case NSStreamStatusWriting: + return YES; + case NSStreamStatusAtEnd: + case NSStreamStatusClosed: + case NSStreamStatusError: + default: + return NO; + } +} + +- (NSInteger)readData:(NSData *)data intoBuffer:(uint8_t *)buffer maxLength:(NSUInteger)length { + NSRange range = NSMakeRange((NSUInteger)_phaseReadOffset, length); + [data getBytes:buffer range:range]; + + _phaseReadOffset += range.length; + + if (((NSUInteger)_phaseReadOffset) >= [data length]) { + [self transitionToNextPhase]; + } + return (NSInteger) range.length; +} + +- (NSInteger)read:(uint8_t *)buffer maxLength:(NSUInteger)length { + NSInteger totalNumberOfBytesRead = 0; + if (_phase == SQEncapsulationBoundaryPhase) { + NSData *encapsulationBoundaryData = [([self hasInitialBoundary] ? SQMultipartFormInitialBoundary(self.boundary) : SQMultipartFormEncapsulationBoundary(self.boundary)) dataUsingEncoding:self.stringEncoding]; + totalNumberOfBytesRead += [self readData:encapsulationBoundaryData intoBuffer:&buffer[totalNumberOfBytesRead] maxLength:length - (NSInteger)totalNumberOfBytesRead]; + } + if (_phase == SQHeaderPhase) { + NSData *headersData = [[self stringForHeaders] dataUsingEncoding:self.stringEncoding]; + totalNumberOfBytesRead += [self readData:headersData intoBuffer:&buffer[totalNumberOfBytesRead] maxLength:(length - (NSUInteger)totalNumberOfBytesRead)]; + } + if (_phase == SQBodyPhase) { + NSInteger numberOfBytesRead = 0; + 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 == SQFinalBoundaryPhase) { + NSData *closingBoundaryData = ([self hasFinalBoundary] ? [SQMultipartFormFinalBoundary(self.boundary) dataUsingEncoding:self.stringEncoding] : [NSData data]); + totalNumberOfBytesRead += [self readData:closingBoundaryData intoBuffer:&buffer[totalNumberOfBytesRead] maxLength:(length - (NSUInteger)totalNumberOfBytesRead)]; + } + return totalNumberOfBytesRead; +} + +@end diff --git a/SQDebug/AFNetworking/AFNetworking-4.0.1-Debug/AFNetworking-Debug/SQQueryStringPair.h b/SQDebug/AFNetworking/AFNetworking-4.0.1-Debug/AFNetworking-Debug/SQQueryStringPair.h index 31a9fdf5..839dbaaf 100644 --- a/SQDebug/AFNetworking/AFNetworking-4.0.1-Debug/AFNetworking-Debug/SQQueryStringPair.h +++ b/SQDebug/AFNetworking/AFNetworking-4.0.1-Debug/AFNetworking-Debug/SQQueryStringPair.h @@ -7,6 +7,12 @@ #import +NSString * SQQueryStringFromParameters(NSDictionary *parameters); + +NSArray * SQQueryStringPairFromDictionary(NSDictionary *dictionary); + +NSArray * SQQueryStringPairsFromKeyAndValue(NSString *key, id value); + @interface SQQueryStringPair : NSObject @property (readwrite, nonatomic, strong) id field; diff --git a/SQDebug/AFNetworking/AFNetworking-4.0.1-Debug/AFNetworking-Debug/SQQueryStringPair.m b/SQDebug/AFNetworking/AFNetworking-4.0.1-Debug/AFNetworking-Debug/SQQueryStringPair.m index 670dc9d7..4d7deaaa 100644 --- a/SQDebug/AFNetworking/AFNetworking-4.0.1-Debug/AFNetworking-Debug/SQQueryStringPair.m +++ b/SQDebug/AFNetworking/AFNetworking-4.0.1-Debug/AFNetworking-Debug/SQQueryStringPair.m @@ -7,6 +7,47 @@ #import "SQQueryStringPair.h" +NSString * SQQueryStringFromParameters(NSDictionary *parameters) { + NSMutableArray *mutablePairs = [NSMutableArray array]; + for (SQQueryStringPair *pair in SQQueryStringPairFromDictionary(parameters)) { + [mutablePairs addObject:[pair URLEncodedStringValue]]; + } + return [mutablePairs componentsJoinedByString:@"&"]; +} + +NSArray * SQQueryStringPairFromDictionary(NSDictionary *dictionary) { + return SQQueryStringPairsFromKeyAndValue(nil, dictionary); +} + +NSArray * SQQueryStringPairsFromKeyAndValue(NSString *key, id value) { + NSMutableArray *mutableQueryStringComponents = [NSMutableArray array]; + // 按照key字母进行排序 + NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"description" ascending:YES selector:@selector(compare:)]; + if ([value isKindOfClass:[NSDictionary class]]) { + NSDictionary *dictionary = value; + for (id nestedKey in [dictionary.allKeys sortedArrayUsingDescriptors:@[ sortDescriptor ]]) { + id nestedValue = dictionary[nestedKey]; + if (nestedValue) { + [mutableQueryStringComponents addObjectsFromArray:SQQueryStringPairsFromKeyAndValue((key ? [NSString stringWithFormat:@"%@[%@]", key, nestedKey] : nestedKey), nestedValue)]; + } + } + } else if ([value isKindOfClass:[NSArray class]]) { + NSArray *array = value; + for (id nestedValue in array) { + [mutableQueryStringComponents addObjectsFromArray:SQQueryStringPairsFromKeyAndValue([NSString stringWithFormat:@"%@[]", key], nestedValue)]; + } + } else if ([value isKindOfClass:[NSSet class]]) { + NSSet *set = value; + for (id obj in [set sortedArrayUsingDescriptors:@[ sortDescriptor ]]) { + [mutableQueryStringComponents addObjectsFromArray:SQQueryStringPairsFromKeyAndValue(key, obj)]; + } + } else { + [mutableQueryStringComponents addObject:[[SQQueryStringPair alloc] initWithField:key value:value]]; + } + return mutableQueryStringComponents; +} + + @implementation SQQueryStringPair NSString * SQPercentEscapedStringFromString(NSString *string) { diff --git a/SQDebug/AFNetworking/AFNetworking-4.0.1-Debug/AFNetworking-Debug/SQURLRequestSerialization.h b/SQDebug/AFNetworking/AFNetworking-4.0.1-Debug/AFNetworking-Debug/SQURLRequestSerialization.h index d506be4c..2ab1c214 100644 --- a/SQDebug/AFNetworking/AFNetworking-4.0.1-Debug/AFNetworking-Debug/SQURLRequestSerialization.h +++ b/SQDebug/AFNetworking/AFNetworking-4.0.1-Debug/AFNetworking-Debug/SQURLRequestSerialization.h @@ -9,10 +9,6 @@ @interface SQURLRequestSerialization : NSObject -NSString * SQQueryStringFromParameters(NSDictionary *parameters); -NSArray * SQQueryStringPairFromDictionary(NSDictionary *dictionary); - -NSArray * SQQueryStringPairsFromKeyAndValue(NSString *key, id value); @end diff --git a/SQDebug/AFNetworking/AFNetworking-4.0.1-Debug/AFNetworking-Debug/SQURLRequestSerialization.m b/SQDebug/AFNetworking/AFNetworking-4.0.1-Debug/AFNetworking-Debug/SQURLRequestSerialization.m index 81b76470..6007038a 100644 --- a/SQDebug/AFNetworking/AFNetworking-4.0.1-Debug/AFNetworking-Debug/SQURLRequestSerialization.m +++ b/SQDebug/AFNetworking/AFNetworking-4.0.1-Debug/AFNetworking-Debug/SQURLRequestSerialization.m @@ -10,43 +10,5 @@ @implementation SQURLRequestSerialization -NSString * SQQueryStringFromParameters(NSDictionary *parameters) { - NSMutableArray *mutablePairs = [NSMutableArray array]; - for (SQQueryStringPair *pair in SQQueryStringPairFromDictionary(parameters)) { - [mutablePairs addObject:[pair URLEncodedStringValue]]; - } - return [mutablePairs componentsJoinedByString:@"&"]; -} - -NSArray * SQQueryStringPairFromDictionary(NSDictionary *dictionary) { - return SQQueryStringPairsFromKeyAndValue(nil, dictionary); -} - -NSArray * SQQueryStringPairsFromKeyAndValue(NSString *key, id value) { - NSMutableArray *mutableQueryStringComponents = [NSMutableArray array]; - NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"description" ascending:YES selector:@selector(compare:)]; - if ([value isKindOfClass:[NSDictionary class]]) { - NSDictionary *dictionary = value; - for (id nestedKey in [dictionary.allKeys sortedArrayUsingDescriptors:@[ sortDescriptor ]]) { - id nestedValue = dictionary[nestedKey]; - if (nestedValue) { - [mutableQueryStringComponents addObjectsFromArray:SQQueryStringPairsFromKeyAndValue((key ? [NSString stringWithFormat:@"%@[%@]", key, nestedKey] : nestedKey), nestedValue)]; - } - } - } else if ([value isKindOfClass:[NSArray class]]) { - NSArray *array = value; - for (id nestedValue in array) { - [mutableQueryStringComponents addObjectsFromArray:SQQueryStringPairsFromKeyAndValue([NSString stringWithFormat:@"%@[]", key], nestedValue)]; - } - } else if ([value isKindOfClass:[NSSet class]]) { - NSSet *set = value; - for (id obj in [set sortedArrayUsingDescriptors:@[ sortDescriptor ]]) { - [mutableQueryStringComponents addObjectsFromArray:SQQueryStringPairsFromKeyAndValue(key, obj)]; - } - } else { - [mutableQueryStringComponents addObject:[[SQQueryStringPair alloc] initWithField:key value:value]]; - } - return mutableQueryStringComponents; -} @end diff --git a/SQDebug/AFNetworking/AFNetworking-4.0.1-Debug/AFNetworking-Debug/ViewController.m b/SQDebug/AFNetworking/AFNetworking-4.0.1-Debug/AFNetworking-Debug/ViewController.m index 0586dc35..e8d20677 100644 --- a/SQDebug/AFNetworking/AFNetworking-4.0.1-Debug/AFNetworking-Debug/ViewController.m +++ b/SQDebug/AFNetworking/AFNetworking-4.0.1-Debug/AFNetworking-Debug/ViewController.m @@ -7,8 +7,8 @@ #import "ViewController.h" #import -#import "SQQueryStringPair.h" #import "SQURLRequestSerialization.h" +#import "SQHTTPBodyPart.h" @interface ViewController () @property (nonatomic, weak) IBOutlet UIProgressView *progressView; @@ -184,46 +184,6 @@ - (void)debug_AFNURLRequestSerialSerialization { // kAFUploadStream3GSuggestedPacketSize: 16384 NSLog(@"kAFUploadStream3GSuggestedDelay: %f", kAFUploadStream3GSuggestedDelay); // kAFUploadStream3GSuggestedDelay: 0.200000 - - SQQueryStringPair *pair = [[SQQueryStringPair alloc] initWithField:@"a" value:@1]; - NSLog(@"%@", [pair URLEncodedStringValue]); - // a=1 - - SQQueryStringPair *pair2 = [[SQQueryStringPair alloc] initWithField:@"b" value:nil]; - NSLog(@"%@", [pair2 URLEncodedStringValue]); - // b - - NSLog(@"%@", SQQueryStringPairsFromKeyAndValue(@"a", @1)); - /** - ( - "a=1" - ) - */ - - NSLog(@"%@", SQQueryStringPairsFromKeyAndValue(@"a", @{@"b": @{@"c": @3}, @"d": @""})); - /** - ( - "a[b][c]=3", - "a[d]=" - ) - */ - NSLog(@"%@", SQQueryStringPairsFromKeyAndValue(@"a", @[@"b", @{@"c": @3}, @"d"])); - /** - ( - "a[]=b", - "a[][c]=3", - "a[]=d" - ) - */ - NSLog(@"%@", SQQueryStringPairFromDictionary(@{@"a": @{@"b": @{@"c": @3}, @"d": @""}})); - /** - ( - "a[b][c]=3", - "a[d]=" - ) - */ - NSLog(@"%@", SQQueryStringFromParameters(@{@"a": @{@"b": @{@"c": @3}, @"d": @""}})); - // a[b][c]=3&a[d]= } @end diff --git a/SQDebug/AFNetworking/AFNetworking-4.0.1-Debug/AFNetworking-DebugTests/AFNetworking_DebugTests.m b/SQDebug/AFNetworking/AFNetworking-4.0.1-Debug/AFNetworking-DebugTests/AFNetworking_DebugTests.m new file mode 100644 index 00000000..e65cf4df --- /dev/null +++ b/SQDebug/AFNetworking/AFNetworking-4.0.1-Debug/AFNetworking-DebugTests/AFNetworking_DebugTests.m @@ -0,0 +1,36 @@ +// +// AFNetworking_DebugTests.m +// AFNetworking-DebugTests +// +// Created by 朱双泉 on 2020/11/23. +// + +#import + +@interface AFNetworking_DebugTests : XCTestCase + +@end + +@implementation AFNetworking_DebugTests + +- (void)setUp { + // Put setup code here. This method is called before the invocation of each test method in the class. +} + +- (void)tearDown { + // Put teardown code here. This method is called after the invocation of each test method in the class. +} + +- (void)testExample { + // This is an example of a functional test case. + // Use XCTAssert and related functions to verify your tests produce the correct results. +} + +- (void)testPerformanceExample { + // This is an example of a performance test case. + [self measureBlock:^{ + // Put the code you want to measure the time of here. + }]; +} + +@end diff --git a/SQDebug/AFNetworking/AFNetworking-4.0.1-Debug/AFNetworking-DebugTests/Info.plist b/SQDebug/AFNetworking/AFNetworking-4.0.1-Debug/AFNetworking-DebugTests/Info.plist new file mode 100644 index 00000000..64d65ca4 --- /dev/null +++ b/SQDebug/AFNetworking/AFNetworking-4.0.1-Debug/AFNetworking-DebugTests/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + + diff --git a/SQDebug/AFNetworking/AFNetworking-4.0.1-Debug/AFNetworking-DebugTests/SQURLRequestSerializationTest.m b/SQDebug/AFNetworking/AFNetworking-4.0.1-Debug/AFNetworking-DebugTests/SQURLRequestSerializationTest.m new file mode 100644 index 00000000..a4cf135a --- /dev/null +++ b/SQDebug/AFNetworking/AFNetworking-4.0.1-Debug/AFNetworking-DebugTests/SQURLRequestSerializationTest.m @@ -0,0 +1,112 @@ +// +// SQURLRequestSerializationTest.m +// AFNetworking-DebugTests +// +// Created by 朱双泉 on 2020/11/23. +// + +#import +#import "SQQueryStringPair.h" +#import "SQHTTPBodyPart.h" + +@interface SQQueryStringPairTest : XCTestCase + +@end + +@implementation SQQueryStringPairTest + +- (void)setUp { + NSLog(@"==================DEBUG=================="); +} + +- (void)tearDown { + NSLog(@"==================DEBUG=================="); +} + +- (void)testStringPair { + SQQueryStringPair *pair = [[SQQueryStringPair alloc] initWithField:@"a" value:@1]; + NSLog(@"%@", [pair URLEncodedStringValue]); + // a=1 + + SQQueryStringPair *pair2 = [[SQQueryStringPair alloc] initWithField:@"b" value:nil]; + NSLog(@"%@", [pair2 URLEncodedStringValue]); + // b + + NSLog(@"%@", SQQueryStringPairsFromKeyAndValue(@"a", @1)); + /** + ( + "a=1" + ) + */ + + NSLog(@"%@", SQQueryStringPairsFromKeyAndValue(@"a", @{@"b": @{@"c": @3}, @"d": @""})); + /** + ( + "a[b][c]=3", + "a[d]=" + ) + */ + NSLog(@"%@", SQQueryStringPairsFromKeyAndValue(@"a", @[@"b", @{@"c": @3}, @"d"])); + /** + ( + "a[]=b", + "a[][c]=3", + "a[]=d" + ) + */ + NSLog(@"%@", SQQueryStringPairFromDictionary(@{@"a": @{@"b": @{@"c": @3}, @"d": @""}})); + /** + ( + "a[b][c]=3", + "a[d]=" + ) + */ + NSLog(@"%@", SQQueryStringFromParameters(@{@"a": @{@"b": @{@"c": @3}, @"d": @""}})); + // a[b][c]=3&a[d]= +} + +- (void)testHTTPBodyPart { + NSString *boundary = SQCreateMultipartFormBoundary(); + NSLog(@"InitialBoundary: %@", SQMultipartFormInitialBoundary(boundary)); + // InitialBoundary: --Boundary+97E7F63434C35636 + NSLog(@"EncapsulationBoundary: %@", SQMultipartFormEncapsulationBoundary(boundary)); + // EncapsulationBoundary: + // --Boundary+97E7F63434C35636 + // + NSLog(@"FinalBoundary: %@", SQMultipartFormFinalBoundary(boundary)); + // FinalBoundary: + // --Boundary+97E7F63434C35636-- + // + NSLog(@"ContentType: %@", SQContentTypeForPathExtension(@"jpg")); + // ContentType: image/jpeg + NSLog(@"ContentType: %@", SQContentTypeForPathExtension(@"mp4")); + // ContentType: video/mp4 + NSLog(@"ContentType: %@", SQContentTypeForPathExtension(@"zip")); + // ContentType: application/zip + + SQHTTPBodyPart *bodyPart = [[SQHTTPBodyPart alloc] init]; + bodyPart.headers = @{ + @"accept": @"application/json, text/javascript, */*; q=0.01", + @"accept-encoding": @"gzip, deflate, br", + @"accept-language": @"en-US,en;q=0.9,zh;q=0.8", + @"content-length": @"9", + @"content-type": @"application/json; charset=UTF-8" + }; + NSLog(@"%@", [bodyPart stringForHeaders]); + +// accept: application/json, text/javascript, */*; q=0.01 +// accept-language: en-US,en;q=0.9,zh;q=0.8 +// content-length: 9 +// accept-encoding: gzip, deflate, br +// content-type: application/json; charset=UTF-8 +// +} + +- (void)testPerformanceExample { + // This is an example of a performance test case. + [self measureBlock:^{ + // Put the code you want to measure the time of here. + }]; +} + +@end diff --git a/SQDebug/AFNetworking/AFNetworking-4.0.1/AFNetworking/AFURLRequestSerialization.m b/SQDebug/AFNetworking/AFNetworking-4.0.1/AFNetworking/AFURLRequestSerialization.m index 86167e29..ea2dd09d 100644 --- a/SQDebug/AFNetworking/AFNetworking-4.0.1/AFNetworking/AFURLRequestSerialization.m +++ b/SQDebug/AFNetworking/AFNetworking-4.0.1/AFNetworking/AFURLRequestSerialization.m @@ -1096,7 +1096,7 @@ - (unsigned long long)contentLength { } - (BOOL)hasBytesAvailable { - // Allows `read:maxLength:` to be called again if `AFMultipartFormFinalBoundary` doesn't fit into the available buffer + // 如果AFMultipartFormFinalBoundary不适合可用缓冲区,则允许再次调用read:maxLength: if (_phase == AFFinalBoundaryPhase) { return YES; } diff --git a/SQDebug/AFNetworking/README.md b/SQDebug/AFNetworking/README.md index d7cf81e0..8af0541e 100644 --- a/SQDebug/AFNetworking/README.md +++ b/SQDebug/AFNetworking/README.md @@ -6,6 +6,7 @@ - [AFNetworking 4.0.1 源码下载](https://github.com/AFNetworking/AFNetworking/archive/4.0.1.zip) - [关注我 获取中文版源码](https://github.com/coderZsq/coderZsq.project.ios/tree/master/SQDebug) +>2020-11-21 ## 0x00 准备工作 @@ -175,6 +176,8 @@ router.post('/uploadTask/upload', upload.single('afn'), (ctx, next) => { Success: { msg = 'upload success!' } ``` +>2020-11-22 + ```objc - (void)create_an_uploadTaskFor_a_MultiPartRequestWithProgress { NSMutableURLRequest *request = [[AFHTTPRequestSerializer serializer] multipartFormRequestWithMethod:@"POST" URLString:@"http://localhost:8080/afn/uploadTask/upload" parameters:nil constructingBodyWithBlock:^(id formData) { @@ -1206,6 +1209,305 @@ NSLog(@"%@", SQQueryStringFromParameters(@{@"a": @{@"b": @{@"c": @3}, @"d": @""} a[b][c]=3&a[d]= ``` +>2020-11-23 + +```objc +@interface AFStreamingMultipartFormData : NSObject +- (instancetype)initWithURLRequest:(NSMutableURLRequest *)urlRequest + stringEncoding:(NSStringEncoding)encoding; + +- (NSMutableURLRequest *)requestByFinalizingMultipartFormData; +@end +``` + +```objc +AFMultipartFormData -> AFURLRequestSerialization.h line 281 +AFStreamingMultipartFormData -> AFURLRequestSerialization.m line 663 +AFMultipartBodyStream -> AFURLRequestSerialization.m line 642 +AFHTTPBodyPart -> AFURLRequestSerialization.m line 624 +``` + +```objc +static NSString * AFCreateMultipartFormBoundary() { + return [NSString stringWithFormat:@"Boundary+%08X%08X", arc4random(), arc4random()]; +} + +static NSString * const kAFMultipartFormCRLF = @"\r\n"; + +static inline NSString * AFMultipartFormInitialBoundary(NSString *boundary) { + return [NSString stringWithFormat:@"--%@%@", boundary, kAFMultipartFormCRLF]; +} + +static inline NSString * AFMultipartFormEncapsulationBoundary(NSString *boundary) { + return [NSString stringWithFormat:@"%@--%@%@", kAFMultipartFormCRLF, boundary, kAFMultipartFormCRLF]; +} + +static inline NSString * AFMultipartFormFinalBoundary(NSString *boundary) { + return [NSString stringWithFormat:@"%@--%@--%@", kAFMultipartFormCRLF, boundary, kAFMultipartFormCRLF]; +} + +static inline NSString * AFContentTypeForPathExtension(NSString *extension) { + NSString *UTI = (__bridge_transfer NSString *)UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (__bridge CFStringRef)extension, NULL); + NSString *contentType = (__bridge_transfer NSString *)UTTypeCopyPreferredTagWithClass((__bridge CFStringRef)UTI, kUTTagClassMIMEType); + if (!contentType) { + return @"application/octet-stream"; + } else { + return contentType; + } +} + +NSUInteger const kAFUploadStream3GSuggestedPacketSize = 1024 * 16; +NSTimeInterval const kAFUploadStream3GSuggestedDelay = 0.2; +``` + +```objc +@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; + +@property (nonatomic, assign) BOOL hasInitialBoundary; +@property (nonatomic, assign) BOOL hasFinalBoundary; + +@property (readonly, nonatomic, assign, getter = hasBytesAvailable) BOOL bytesAvailable; +@property (readonly, nonatomic, assign) unsigned long long contentLength; + +- (NSInteger)read:(uint8_t *)buffer + maxLength:(NSUInteger)length; +@end +``` + +```objc +typedef enum { + AFEncapsulationBoundaryPhase = 1, + AFHeaderPhase = 2, + AFBodyPhase = 3, + AFFinalBoundaryPhase = 4, +} AFHTTPBodyPartReadPhase; + +@interface AFHTTPBodyPart () { + AFHTTPBodyPartReadPhase _phase; + NSInputStream *_inputStream; + unsigned long long _phaseReadOffset; +} + +- (BOOL)transitionToNextPhase; +- (NSInteger)readData:(NSData *)data + intoBuffer:(uint8_t *)buffer + maxLength:(NSUInteger)length; +@end + +@implementation AFHTTPBodyPart + +- (instancetype)init { + self = [super init]; + if (!self) { + return nil; + } + + [self transitionToNextPhase]; + + return self; +} + +- (void)dealloc { + if (_inputStream) { + [_inputStream close]; + _inputStream = nil; + } +} + +- (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; +} + +- (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]; +} + +- (unsigned long long)contentLength { + unsigned long long length = 0; + + NSData *encapsulationBoundaryData = [([self hasInitialBoundary] ? AFMultipartFormInitialBoundary(self.boundary) : AFMultipartFormEncapsulationBoundary(self.boundary)) dataUsingEncoding:self.stringEncoding]; + length += [encapsulationBoundaryData length]; + + NSData *headersData = [[self stringForHeaders] dataUsingEncoding:self.stringEncoding]; + length += [headersData length]; + + length += _bodyContentLength; + + NSData *closingBoundaryData = ([self hasFinalBoundary] ? [AFMultipartFormFinalBoundary(self.boundary) dataUsingEncoding:self.stringEncoding] : [NSData data]); + length += [closingBoundaryData length]; + + return length; +} + +- (BOOL)hasBytesAvailable { + // 如果AFMultipartFormFinalBoundary不适合可用缓冲区,则允许再次调用read:maxLength: + if (_phase == AFFinalBoundaryPhase) { + return YES; + } + + switch (self.inputStream.streamStatus) { + case NSStreamStatusNotOpen: + case NSStreamStatusOpening: + case NSStreamStatusOpen: + case NSStreamStatusReading: + case NSStreamStatusWriting: + return YES; + case NSStreamStatusAtEnd: + case NSStreamStatusClosed: + case NSStreamStatusError: + default: + return NO; + } +} + +- (NSInteger)read:(uint8_t *)buffer + maxLength:(NSUInteger)length +{ + NSInteger totalNumberOfBytesRead = 0; + + if (_phase == AFEncapsulationBoundaryPhase) { + NSData *encapsulationBoundaryData = [([self hasInitialBoundary] ? AFMultipartFormInitialBoundary(self.boundary) : AFMultipartFormEncapsulationBoundary(self.boundary)) dataUsingEncoding:self.stringEncoding]; + totalNumberOfBytesRead += [self readData:encapsulationBoundaryData intoBuffer:&buffer[totalNumberOfBytesRead] maxLength:(length - (NSUInteger)totalNumberOfBytesRead)]; + } + + if (_phase == AFHeaderPhase) { + NSData *headersData = [[self stringForHeaders] dataUsingEncoding:self.stringEncoding]; + totalNumberOfBytesRead += [self readData:headersData intoBuffer:&buffer[totalNumberOfBytesRead] maxLength:(length - (NSUInteger)totalNumberOfBytesRead)]; + } + + if (_phase == AFBodyPhase) { + NSInteger numberOfBytesRead = 0; + + 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) { + 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; +} + +- (NSInteger)readData:(NSData *)data + intoBuffer:(uint8_t *)buffer + maxLength:(NSUInteger)length +{ + NSRange range = NSMakeRange((NSUInteger)_phaseReadOffset, MIN([data length] - ((NSUInteger)_phaseReadOffset), length)); + [data getBytes:buffer range:range]; + + _phaseReadOffset += range.length; + + if (((NSUInteger)_phaseReadOffset) >= [data length]) { + [self transitionToNextPhase]; + } + + return (NSInteger)range.length; +} + +- (BOOL)transitionToNextPhase { + if (![[NSThread currentThread] isMainThread]) { + dispatch_sync(dispatch_get_main_queue(), ^{ + [self transitionToNextPhase]; + }); + return YES; + } + + switch (_phase) { + case AFEncapsulationBoundaryPhase: + _phase = AFHeaderPhase; + break; + case AFHeaderPhase: + [self.inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes]; + [self.inputStream open]; + _phase = AFBodyPhase; + break; + case AFBodyPhase: + [self.inputStream close]; + _phase = AFFinalBoundaryPhase; + break; + case AFFinalBoundaryPhase: + default: + _phase = AFEncapsulationBoundaryPhase; + break; + } + _phaseReadOffset = 0; + + return YES; +} + +#pragma mark - NSCopying + +- (instancetype)copyWithZone:(NSZone *)zone { + AFHTTPBodyPart *bodyPart = [[[self class] allocWithZone:zone] init]; + + bodyPart.stringEncoding = self.stringEncoding; + bodyPart.headers = self.headers; + bodyPart.bodyContentLength = self.bodyContentLength; + bodyPart.body = self.body; + bodyPart.boundary = self.boundary; + + return bodyPart; +} + +@end +``` + +```objc +SQHTTPBodyPart *bodyPart = [[SQHTTPBodyPart alloc] init]; +bodyPart.headers = @{ + @"accept": @"application/json, text/javascript, */*; q=0.01", + @"accept-encoding": @"gzip, deflate, br", + @"accept-language": @"en-US,en;q=0.9,zh;q=0.8", + @"content-length": @"9", + @"content-type": @"application/json; charset=UTF-8" +}; +NSLog(@"%@", [bodyPart stringForHeaders]); +``` + +``` +accept: application/json, text/javascript, */*; q=0.01 +accept-language: en-US,en;q=0.9,zh;q=0.8 +content-length: 9 +accept-encoding: gzip, deflate, br +content-type: application/json; charset=UTF-8 + +``` + + ```objc #import "AFURLSessionManager.h" ```