房间内播放背景音乐上传音乐
This commit is contained in:
14
xplan-ios/Base/Tool/CocoaHttpServer/Core/Categories/DDData.h
Executable file
14
xplan-ios/Base/Tool/CocoaHttpServer/Core/Categories/DDData.h
Executable file
@@ -0,0 +1,14 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@interface NSData (DDData)
|
||||
|
||||
- (NSData *)md5Digest;
|
||||
|
||||
- (NSData *)sha1Digest;
|
||||
|
||||
- (NSString *)hexStringValue;
|
||||
|
||||
- (NSString *)base64Encoded;
|
||||
- (NSData *)base64Decoded;
|
||||
|
||||
@end
|
158
xplan-ios/Base/Tool/CocoaHttpServer/Core/Categories/DDData.m
Executable file
158
xplan-ios/Base/Tool/CocoaHttpServer/Core/Categories/DDData.m
Executable file
@@ -0,0 +1,158 @@
|
||||
#import "DDData.h"
|
||||
#import <CommonCrypto/CommonDigest.h>
|
||||
|
||||
|
||||
@implementation NSData (DDData)
|
||||
|
||||
static char encodingTable[64] = {
|
||||
'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P',
|
||||
'Q','R','S','T','U','V','W','X','Y','Z','a','b','c','d','e','f',
|
||||
'g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v',
|
||||
'w','x','y','z','0','1','2','3','4','5','6','7','8','9','+','/' };
|
||||
|
||||
- (NSData *)md5Digest
|
||||
{
|
||||
unsigned char result[CC_MD5_DIGEST_LENGTH];
|
||||
|
||||
CC_MD5([self bytes], (CC_LONG)[self length], result);
|
||||
return [NSData dataWithBytes:result length:CC_MD5_DIGEST_LENGTH];
|
||||
}
|
||||
|
||||
- (NSData *)sha1Digest
|
||||
{
|
||||
unsigned char result[CC_SHA1_DIGEST_LENGTH];
|
||||
|
||||
CC_SHA1([self bytes], (CC_LONG)[self length], result);
|
||||
return [NSData dataWithBytes:result length:CC_SHA1_DIGEST_LENGTH];
|
||||
}
|
||||
|
||||
- (NSString *)hexStringValue
|
||||
{
|
||||
NSMutableString *stringBuffer = [NSMutableString stringWithCapacity:([self length] * 2)];
|
||||
|
||||
const unsigned char *dataBuffer = [self bytes];
|
||||
int i;
|
||||
|
||||
for (i = 0; i < [self length]; ++i)
|
||||
{
|
||||
[stringBuffer appendFormat:@"%02x", (unsigned int)dataBuffer[i]];
|
||||
}
|
||||
|
||||
return [stringBuffer copy];
|
||||
}
|
||||
|
||||
- (NSString *)base64Encoded
|
||||
{
|
||||
const unsigned char *bytes = [self bytes];
|
||||
NSMutableString *result = [NSMutableString stringWithCapacity:[self length]];
|
||||
unsigned long ixtext = 0;
|
||||
unsigned long lentext = [self length];
|
||||
long ctremaining = 0;
|
||||
unsigned char inbuf[3], outbuf[4];
|
||||
unsigned short i = 0;
|
||||
unsigned short charsonline = 0, ctcopy = 0;
|
||||
unsigned long ix = 0;
|
||||
|
||||
while( YES )
|
||||
{
|
||||
ctremaining = lentext - ixtext;
|
||||
if( ctremaining <= 0 ) break;
|
||||
|
||||
for( i = 0; i < 3; i++ ) {
|
||||
ix = ixtext + i;
|
||||
if( ix < lentext ) inbuf[i] = bytes[ix];
|
||||
else inbuf [i] = 0;
|
||||
}
|
||||
|
||||
outbuf [0] = (inbuf [0] & 0xFC) >> 2;
|
||||
outbuf [1] = ((inbuf [0] & 0x03) << 4) | ((inbuf [1] & 0xF0) >> 4);
|
||||
outbuf [2] = ((inbuf [1] & 0x0F) << 2) | ((inbuf [2] & 0xC0) >> 6);
|
||||
outbuf [3] = inbuf [2] & 0x3F;
|
||||
ctcopy = 4;
|
||||
|
||||
switch( ctremaining )
|
||||
{
|
||||
case 1:
|
||||
ctcopy = 2;
|
||||
break;
|
||||
case 2:
|
||||
ctcopy = 3;
|
||||
break;
|
||||
}
|
||||
|
||||
for( i = 0; i < ctcopy; i++ )
|
||||
[result appendFormat:@"%c", encodingTable[outbuf[i]]];
|
||||
|
||||
for( i = ctcopy; i < 4; i++ )
|
||||
[result appendString:@"="];
|
||||
|
||||
ixtext += 3;
|
||||
charsonline += 4;
|
||||
}
|
||||
|
||||
return [NSString stringWithString:result];
|
||||
}
|
||||
|
||||
- (NSData *)base64Decoded
|
||||
{
|
||||
const unsigned char *bytes = [self bytes];
|
||||
NSMutableData *result = [NSMutableData dataWithCapacity:[self length]];
|
||||
|
||||
unsigned long ixtext = 0;
|
||||
unsigned long lentext = [self length];
|
||||
unsigned char ch = 0;
|
||||
unsigned char inbuf[4] = {0, 0, 0, 0};
|
||||
unsigned char outbuf[3] = {0, 0, 0};
|
||||
short i = 0, ixinbuf = 0;
|
||||
BOOL flignore = NO;
|
||||
BOOL flendtext = NO;
|
||||
|
||||
while( YES )
|
||||
{
|
||||
if( ixtext >= lentext ) break;
|
||||
ch = bytes[ixtext++];
|
||||
flignore = NO;
|
||||
|
||||
if( ( ch >= 'A' ) && ( ch <= 'Z' ) ) ch = ch - 'A';
|
||||
else if( ( ch >= 'a' ) && ( ch <= 'z' ) ) ch = ch - 'a' + 26;
|
||||
else if( ( ch >= '0' ) && ( ch <= '9' ) ) ch = ch - '0' + 52;
|
||||
else if( ch == '+' ) ch = 62;
|
||||
else if( ch == '=' ) flendtext = YES;
|
||||
else if( ch == '/' ) ch = 63;
|
||||
else flignore = YES;
|
||||
|
||||
if( ! flignore )
|
||||
{
|
||||
short ctcharsinbuf = 3;
|
||||
BOOL flbreak = NO;
|
||||
|
||||
if( flendtext )
|
||||
{
|
||||
if( ! ixinbuf ) break;
|
||||
if( ( ixinbuf == 1 ) || ( ixinbuf == 2 ) ) ctcharsinbuf = 1;
|
||||
else ctcharsinbuf = 2;
|
||||
ixinbuf = 3;
|
||||
flbreak = YES;
|
||||
}
|
||||
|
||||
inbuf [ixinbuf++] = ch;
|
||||
|
||||
if( ixinbuf == 4 )
|
||||
{
|
||||
ixinbuf = 0;
|
||||
outbuf [0] = ( inbuf[0] << 2 ) | ( ( inbuf[1] & 0x30) >> 4 );
|
||||
outbuf [1] = ( ( inbuf[1] & 0x0F ) << 4 ) | ( ( inbuf[2] & 0x3C ) >> 2 );
|
||||
outbuf [2] = ( ( inbuf[2] & 0x03 ) << 6 ) | ( inbuf[3] & 0x3F );
|
||||
|
||||
for( i = 0; i < ctcharsinbuf; i++ )
|
||||
[result appendBytes:&outbuf[i] length:1];
|
||||
}
|
||||
|
||||
if( flbreak ) break;
|
||||
}
|
||||
}
|
||||
|
||||
return [NSData dataWithData:result];
|
||||
}
|
||||
|
||||
@end
|
12
xplan-ios/Base/Tool/CocoaHttpServer/Core/Categories/DDNumber.h
Executable file
12
xplan-ios/Base/Tool/CocoaHttpServer/Core/Categories/DDNumber.h
Executable file
@@ -0,0 +1,12 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
|
||||
@interface NSNumber (DDNumber)
|
||||
|
||||
+ (BOOL)parseString:(NSString *)str intoSInt64:(SInt64 *)pNum;
|
||||
+ (BOOL)parseString:(NSString *)str intoUInt64:(UInt64 *)pNum;
|
||||
|
||||
+ (BOOL)parseString:(NSString *)str intoNSInteger:(NSInteger *)pNum;
|
||||
+ (BOOL)parseString:(NSString *)str intoNSUInteger:(NSUInteger *)pNum;
|
||||
|
||||
@end
|
88
xplan-ios/Base/Tool/CocoaHttpServer/Core/Categories/DDNumber.m
Executable file
88
xplan-ios/Base/Tool/CocoaHttpServer/Core/Categories/DDNumber.m
Executable file
@@ -0,0 +1,88 @@
|
||||
#import "DDNumber.h"
|
||||
|
||||
|
||||
@implementation NSNumber (DDNumber)
|
||||
|
||||
+ (BOOL)parseString:(NSString *)str intoSInt64:(SInt64 *)pNum
|
||||
{
|
||||
if(str == nil)
|
||||
{
|
||||
*pNum = 0;
|
||||
return NO;
|
||||
}
|
||||
|
||||
errno = 0;
|
||||
|
||||
// On both 32-bit and 64-bit machines, long long = 64 bit
|
||||
|
||||
*pNum = strtoll([str UTF8String], NULL, 10);
|
||||
|
||||
if(errno != 0)
|
||||
return NO;
|
||||
else
|
||||
return YES;
|
||||
}
|
||||
|
||||
+ (BOOL)parseString:(NSString *)str intoUInt64:(UInt64 *)pNum
|
||||
{
|
||||
if(str == nil)
|
||||
{
|
||||
*pNum = 0;
|
||||
return NO;
|
||||
}
|
||||
|
||||
errno = 0;
|
||||
|
||||
// On both 32-bit and 64-bit machines, unsigned long long = 64 bit
|
||||
|
||||
*pNum = strtoull([str UTF8String], NULL, 10);
|
||||
|
||||
if(errno != 0)
|
||||
return NO;
|
||||
else
|
||||
return YES;
|
||||
}
|
||||
|
||||
+ (BOOL)parseString:(NSString *)str intoNSInteger:(NSInteger *)pNum
|
||||
{
|
||||
if(str == nil)
|
||||
{
|
||||
*pNum = 0;
|
||||
return NO;
|
||||
}
|
||||
|
||||
errno = 0;
|
||||
|
||||
// On LP64, NSInteger = long = 64 bit
|
||||
// Otherwise, NSInteger = int = long = 32 bit
|
||||
|
||||
*pNum = strtol([str UTF8String], NULL, 10);
|
||||
|
||||
if(errno != 0)
|
||||
return NO;
|
||||
else
|
||||
return YES;
|
||||
}
|
||||
|
||||
+ (BOOL)parseString:(NSString *)str intoNSUInteger:(NSUInteger *)pNum
|
||||
{
|
||||
if(str == nil)
|
||||
{
|
||||
*pNum = 0;
|
||||
return NO;
|
||||
}
|
||||
|
||||
errno = 0;
|
||||
|
||||
// On LP64, NSUInteger = unsigned long = 64 bit
|
||||
// Otherwise, NSUInteger = unsigned int = unsigned long = 32 bit
|
||||
|
||||
*pNum = strtoul([str UTF8String], NULL, 10);
|
||||
|
||||
if(errno != 0)
|
||||
return NO;
|
||||
else
|
||||
return YES;
|
||||
}
|
||||
|
||||
@end
|
56
xplan-ios/Base/Tool/CocoaHttpServer/Core/Categories/DDRange.h
Executable file
56
xplan-ios/Base/Tool/CocoaHttpServer/Core/Categories/DDRange.h
Executable file
@@ -0,0 +1,56 @@
|
||||
/**
|
||||
* DDRange is the functional equivalent of a 64 bit NSRange.
|
||||
* The HTTP Server is designed to support very large files.
|
||||
* On 32 bit architectures (ppc, i386) NSRange uses unsigned 32 bit integers.
|
||||
* This only supports a range of up to 4 gigabytes.
|
||||
* By defining our own variant, we can support a range up to 16 exabytes.
|
||||
*
|
||||
* All effort is given such that DDRange functions EXACTLY the same as NSRange.
|
||||
**/
|
||||
|
||||
#import <Foundation/NSValue.h>
|
||||
#import <Foundation/NSObjCRuntime.h>
|
||||
|
||||
@class NSString;
|
||||
|
||||
typedef struct _DDRange {
|
||||
UInt64 location;
|
||||
UInt64 length;
|
||||
} DDRange;
|
||||
|
||||
typedef DDRange *DDRangePointer;
|
||||
|
||||
NS_INLINE DDRange DDMakeRange(UInt64 loc, UInt64 len) {
|
||||
DDRange r;
|
||||
r.location = loc;
|
||||
r.length = len;
|
||||
return r;
|
||||
}
|
||||
|
||||
NS_INLINE UInt64 DDMaxRange(DDRange range) {
|
||||
return (range.location + range.length);
|
||||
}
|
||||
|
||||
NS_INLINE BOOL DDLocationInRange(UInt64 loc, DDRange range) {
|
||||
return (loc - range.location < range.length);
|
||||
}
|
||||
|
||||
NS_INLINE BOOL DDEqualRanges(DDRange range1, DDRange range2) {
|
||||
return ((range1.location == range2.location) && (range1.length == range2.length));
|
||||
}
|
||||
|
||||
FOUNDATION_EXPORT DDRange DDUnionRange(DDRange range1, DDRange range2);
|
||||
FOUNDATION_EXPORT DDRange DDIntersectionRange(DDRange range1, DDRange range2);
|
||||
FOUNDATION_EXPORT NSString *DDStringFromRange(DDRange range);
|
||||
FOUNDATION_EXPORT DDRange DDRangeFromString(NSString *aString);
|
||||
|
||||
NSInteger DDRangeCompare(DDRangePointer pDDRange1, DDRangePointer pDDRange2);
|
||||
|
||||
@interface NSValue (NSValueDDRangeExtensions)
|
||||
|
||||
+ (NSValue *)valueWithDDRange:(DDRange)range;
|
||||
- (DDRange)ddrangeValue;
|
||||
|
||||
- (NSInteger)ddrangeCompare:(NSValue *)ddrangeValue;
|
||||
|
||||
@end
|
104
xplan-ios/Base/Tool/CocoaHttpServer/Core/Categories/DDRange.m
Executable file
104
xplan-ios/Base/Tool/CocoaHttpServer/Core/Categories/DDRange.m
Executable file
@@ -0,0 +1,104 @@
|
||||
#import "DDRange.h"
|
||||
#import "DDNumber.h"
|
||||
|
||||
DDRange DDUnionRange(DDRange range1, DDRange range2)
|
||||
{
|
||||
DDRange result;
|
||||
|
||||
result.location = MIN(range1.location, range2.location);
|
||||
result.length = MAX(DDMaxRange(range1), DDMaxRange(range2)) - result.location;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
DDRange DDIntersectionRange(DDRange range1, DDRange range2)
|
||||
{
|
||||
DDRange result;
|
||||
|
||||
if((DDMaxRange(range1) < range2.location) || (DDMaxRange(range2) < range1.location))
|
||||
{
|
||||
return DDMakeRange(0, 0);
|
||||
}
|
||||
|
||||
result.location = MAX(range1.location, range2.location);
|
||||
result.length = MIN(DDMaxRange(range1), DDMaxRange(range2)) - result.location;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
NSString *DDStringFromRange(DDRange range)
|
||||
{
|
||||
return [NSString stringWithFormat:@"{%qu, %qu}", range.location, range.length];
|
||||
}
|
||||
|
||||
DDRange DDRangeFromString(NSString *aString)
|
||||
{
|
||||
DDRange result = DDMakeRange(0, 0);
|
||||
|
||||
// NSRange will ignore '-' characters, but not '+' characters
|
||||
NSCharacterSet *cset = [NSCharacterSet characterSetWithCharactersInString:@"+0123456789"];
|
||||
|
||||
NSScanner *scanner = [NSScanner scannerWithString:aString];
|
||||
[scanner setCharactersToBeSkipped:[cset invertedSet]];
|
||||
|
||||
NSString *str1 = nil;
|
||||
NSString *str2 = nil;
|
||||
|
||||
BOOL found1 = [scanner scanCharactersFromSet:cset intoString:&str1];
|
||||
BOOL found2 = [scanner scanCharactersFromSet:cset intoString:&str2];
|
||||
|
||||
if(found1) [NSNumber parseString:str1 intoUInt64:&result.location];
|
||||
if(found2) [NSNumber parseString:str2 intoUInt64:&result.length];
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
NSInteger DDRangeCompare(DDRangePointer pDDRange1, DDRangePointer pDDRange2)
|
||||
{
|
||||
// Comparison basis:
|
||||
// Which range would you encouter first if you started at zero, and began walking towards infinity.
|
||||
// If you encouter both ranges at the same time, which range would end first.
|
||||
|
||||
if(pDDRange1->location < pDDRange2->location)
|
||||
{
|
||||
return NSOrderedAscending;
|
||||
}
|
||||
if(pDDRange1->location > pDDRange2->location)
|
||||
{
|
||||
return NSOrderedDescending;
|
||||
}
|
||||
if(pDDRange1->length < pDDRange2->length)
|
||||
{
|
||||
return NSOrderedAscending;
|
||||
}
|
||||
if(pDDRange1->length > pDDRange2->length)
|
||||
{
|
||||
return NSOrderedDescending;
|
||||
}
|
||||
|
||||
return NSOrderedSame;
|
||||
}
|
||||
|
||||
@implementation NSValue (NSValueDDRangeExtensions)
|
||||
|
||||
+ (NSValue *)valueWithDDRange:(DDRange)range
|
||||
{
|
||||
return [NSValue valueWithBytes:&range objCType:@encode(DDRange)];
|
||||
}
|
||||
|
||||
- (DDRange)ddrangeValue
|
||||
{
|
||||
DDRange result;
|
||||
[self getValue:&result];
|
||||
return result;
|
||||
}
|
||||
|
||||
- (NSInteger)ddrangeCompare:(NSValue *)other
|
||||
{
|
||||
DDRange r1 = [self ddrangeValue];
|
||||
DDRange r2 = [other ddrangeValue];
|
||||
|
||||
return DDRangeCompare(&r1, &r2);
|
||||
}
|
||||
|
||||
@end
|
45
xplan-ios/Base/Tool/CocoaHttpServer/Core/HTTPAuthenticationRequest.h
Executable file
45
xplan-ios/Base/Tool/CocoaHttpServer/Core/HTTPAuthenticationRequest.h
Executable file
@@ -0,0 +1,45 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#if TARGET_OS_IPHONE
|
||||
// Note: You may need to add the CFNetwork Framework to your project
|
||||
#import <CFNetwork/CFNetwork.h>
|
||||
#endif
|
||||
|
||||
@class HTTPMessage;
|
||||
|
||||
|
||||
@interface HTTPAuthenticationRequest : NSObject
|
||||
{
|
||||
BOOL isBasic;
|
||||
BOOL isDigest;
|
||||
|
||||
NSString *base64Credentials;
|
||||
|
||||
NSString *username;
|
||||
NSString *realm;
|
||||
NSString *nonce;
|
||||
NSString *uri;
|
||||
NSString *qop;
|
||||
NSString *nc;
|
||||
NSString *cnonce;
|
||||
NSString *response;
|
||||
}
|
||||
- (id)initWithRequest:(HTTPMessage *)request;
|
||||
|
||||
- (BOOL)isBasic;
|
||||
- (BOOL)isDigest;
|
||||
|
||||
// Basic
|
||||
- (NSString *)base64Credentials;
|
||||
|
||||
// Digest
|
||||
- (NSString *)username;
|
||||
- (NSString *)realm;
|
||||
- (NSString *)nonce;
|
||||
- (NSString *)uri;
|
||||
- (NSString *)qop;
|
||||
- (NSString *)nc;
|
||||
- (NSString *)cnonce;
|
||||
- (NSString *)response;
|
||||
|
||||
@end
|
194
xplan-ios/Base/Tool/CocoaHttpServer/Core/HTTPAuthenticationRequest.m
Executable file
194
xplan-ios/Base/Tool/CocoaHttpServer/Core/HTTPAuthenticationRequest.m
Executable file
@@ -0,0 +1,194 @@
|
||||
#import "HTTPAuthenticationRequest.h"
|
||||
#import "HTTPMessage.h"
|
||||
|
||||
#if ! __has_feature(objc_arc)
|
||||
#warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
|
||||
#endif
|
||||
|
||||
@interface HTTPAuthenticationRequest (PrivateAPI)
|
||||
- (NSString *)quotedSubHeaderFieldValue:(NSString *)param fromHeaderFieldValue:(NSString *)header;
|
||||
- (NSString *)nonquotedSubHeaderFieldValue:(NSString *)param fromHeaderFieldValue:(NSString *)header;
|
||||
@end
|
||||
|
||||
|
||||
@implementation HTTPAuthenticationRequest
|
||||
|
||||
- (id)initWithRequest:(HTTPMessage *)request
|
||||
{
|
||||
if ((self = [super init]))
|
||||
{
|
||||
NSString *authInfo = [request headerField:@"Authorization"];
|
||||
|
||||
isBasic = NO;
|
||||
if ([authInfo length] >= 6)
|
||||
{
|
||||
isBasic = [[authInfo substringToIndex:6] caseInsensitiveCompare:@"Basic "] == NSOrderedSame;
|
||||
}
|
||||
|
||||
isDigest = NO;
|
||||
if ([authInfo length] >= 7)
|
||||
{
|
||||
isDigest = [[authInfo substringToIndex:7] caseInsensitiveCompare:@"Digest "] == NSOrderedSame;
|
||||
}
|
||||
|
||||
if (isBasic)
|
||||
{
|
||||
NSMutableString *temp = [[authInfo substringFromIndex:6] mutableCopy];
|
||||
CFStringTrimWhitespace((__bridge CFMutableStringRef)temp);
|
||||
|
||||
base64Credentials = [temp copy];
|
||||
}
|
||||
|
||||
if (isDigest)
|
||||
{
|
||||
username = [self quotedSubHeaderFieldValue:@"username" fromHeaderFieldValue:authInfo];
|
||||
realm = [self quotedSubHeaderFieldValue:@"realm" fromHeaderFieldValue:authInfo];
|
||||
nonce = [self quotedSubHeaderFieldValue:@"nonce" fromHeaderFieldValue:authInfo];
|
||||
uri = [self quotedSubHeaderFieldValue:@"uri" fromHeaderFieldValue:authInfo];
|
||||
|
||||
// It appears from RFC 2617 that the qop is to be given unquoted
|
||||
// Tests show that Firefox performs this way, but Safari does not
|
||||
// Thus we'll attempt to retrieve the value as nonquoted, but we'll verify it doesn't start with a quote
|
||||
qop = [self nonquotedSubHeaderFieldValue:@"qop" fromHeaderFieldValue:authInfo];
|
||||
if(qop && ([qop characterAtIndex:0] == '"'))
|
||||
{
|
||||
qop = [self quotedSubHeaderFieldValue:@"qop" fromHeaderFieldValue:authInfo];
|
||||
}
|
||||
|
||||
nc = [self nonquotedSubHeaderFieldValue:@"nc" fromHeaderFieldValue:authInfo];
|
||||
cnonce = [self quotedSubHeaderFieldValue:@"cnonce" fromHeaderFieldValue:authInfo];
|
||||
response = [self quotedSubHeaderFieldValue:@"response" fromHeaderFieldValue:authInfo];
|
||||
}
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark Accessors:
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
- (BOOL)isBasic {
|
||||
return isBasic;
|
||||
}
|
||||
|
||||
- (BOOL)isDigest {
|
||||
return isDigest;
|
||||
}
|
||||
|
||||
- (NSString *)base64Credentials {
|
||||
return base64Credentials;
|
||||
}
|
||||
|
||||
- (NSString *)username {
|
||||
return username;
|
||||
}
|
||||
|
||||
- (NSString *)realm {
|
||||
return realm;
|
||||
}
|
||||
|
||||
- (NSString *)nonce {
|
||||
return nonce;
|
||||
}
|
||||
|
||||
- (NSString *)uri {
|
||||
return uri;
|
||||
}
|
||||
|
||||
- (NSString *)qop {
|
||||
return qop;
|
||||
}
|
||||
|
||||
- (NSString *)nc {
|
||||
return nc;
|
||||
}
|
||||
|
||||
- (NSString *)cnonce {
|
||||
return cnonce;
|
||||
}
|
||||
|
||||
- (NSString *)response {
|
||||
return response;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark Private API:
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Retrieves a "Sub Header Field Value" from a given header field value.
|
||||
* The sub header field is expected to be quoted.
|
||||
*
|
||||
* In the following header field:
|
||||
* Authorization: Digest username="Mufasa", qop=auth, response="6629fae4939"
|
||||
* The sub header field titled 'username' is quoted, and this method would return the value @"Mufasa".
|
||||
**/
|
||||
- (NSString *)quotedSubHeaderFieldValue:(NSString *)param fromHeaderFieldValue:(NSString *)header
|
||||
{
|
||||
if (header==nil || header.length == 0) return @"";
|
||||
NSRange startRange = [header rangeOfString:[NSString stringWithFormat:@"%@=\"", param]];
|
||||
if(startRange.location == NSNotFound){
|
||||
// The param was not found anywhere in the header
|
||||
return nil;
|
||||
}
|
||||
NSUInteger postStartRangeLocation = startRange.location + startRange.length;
|
||||
NSUInteger postStartRangeLength = [header length] - postStartRangeLocation;
|
||||
NSRange postStartRange = NSMakeRange(postStartRangeLocation, postStartRangeLength);
|
||||
|
||||
NSRange endRange = [header rangeOfString:@"\"" options:0 range:postStartRange];
|
||||
if(endRange.location == NSNotFound)
|
||||
{
|
||||
// The ending double-quote was not found anywhere in the header
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSRange subHeaderRange = NSMakeRange(postStartRangeLocation, endRange.location - postStartRangeLocation);
|
||||
return [header substringWithRange:subHeaderRange];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a "Sub Header Field Value" from a given header field value.
|
||||
* The sub header field is expected to not be quoted.
|
||||
*
|
||||
* In the following header field:
|
||||
* Authorization: Digest username="Mufasa", qop=auth, response="6629fae4939"
|
||||
* The sub header field titled 'qop' is nonquoted, and this method would return the value @"auth".
|
||||
**/
|
||||
- (NSString *)nonquotedSubHeaderFieldValue:(NSString *)param fromHeaderFieldValue:(NSString *)header
|
||||
{
|
||||
NSRange startRange = [header rangeOfString:[NSString stringWithFormat:@"%@=", param]];
|
||||
if(startRange.location == NSNotFound)
|
||||
{
|
||||
// The param was not found anywhere in the header
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSUInteger postStartRangeLocation = startRange.location + startRange.length;
|
||||
NSUInteger postStartRangeLength = [header length] - postStartRangeLocation;
|
||||
NSRange postStartRange = NSMakeRange(postStartRangeLocation, postStartRangeLength);
|
||||
|
||||
NSRange endRange = [header rangeOfString:@"," options:0 range:postStartRange];
|
||||
if(endRange.location == NSNotFound)
|
||||
{
|
||||
// The ending comma was not found anywhere in the header
|
||||
// However, if the nonquoted param is at the end of the string, there would be no comma
|
||||
// This is only possible if there are no spaces anywhere
|
||||
NSRange endRange2 = [header rangeOfString:@" " options:0 range:postStartRange];
|
||||
if(endRange2.location != NSNotFound)
|
||||
{
|
||||
return nil;
|
||||
}
|
||||
else
|
||||
{
|
||||
return [header substringWithRange:postStartRange];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
NSRange subHeaderRange = NSMakeRange(postStartRangeLocation, endRange.location - postStartRangeLocation);
|
||||
return [header substringWithRange:subHeaderRange];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
119
xplan-ios/Base/Tool/CocoaHttpServer/Core/HTTPConnection.h
Executable file
119
xplan-ios/Base/Tool/CocoaHttpServer/Core/HTTPConnection.h
Executable file
@@ -0,0 +1,119 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@class GCDAsyncSocket;
|
||||
@class HTTPMessage;
|
||||
@class HTTPServer;
|
||||
@class WebSocket;
|
||||
@protocol HTTPResponse;
|
||||
|
||||
|
||||
#define HTTPConnectionDidDieNotification @"HTTPConnectionDidDie"
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark -
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@interface HTTPConfig : NSObject
|
||||
{
|
||||
HTTPServer __unsafe_unretained *server;
|
||||
NSString __strong *documentRoot;
|
||||
dispatch_queue_t queue;
|
||||
}
|
||||
|
||||
- (id)initWithServer:(HTTPServer *)server documentRoot:(NSString *)documentRoot;
|
||||
- (id)initWithServer:(HTTPServer *)server documentRoot:(NSString *)documentRoot queue:(dispatch_queue_t)q;
|
||||
|
||||
@property (nonatomic, unsafe_unretained, readonly) HTTPServer *server;
|
||||
@property (nonatomic, strong, readonly) NSString *documentRoot;
|
||||
@property (nonatomic, readonly) dispatch_queue_t queue;
|
||||
|
||||
@end
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark -
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@interface HTTPConnection : NSObject
|
||||
{
|
||||
dispatch_queue_t connectionQueue;
|
||||
GCDAsyncSocket *asyncSocket;
|
||||
HTTPConfig *config;
|
||||
|
||||
BOOL started;
|
||||
|
||||
HTTPMessage *request;
|
||||
unsigned int numHeaderLines;
|
||||
|
||||
BOOL sentResponseHeaders;
|
||||
|
||||
NSString *nonce;
|
||||
long lastNC;
|
||||
|
||||
NSObject<HTTPResponse> *httpResponse;
|
||||
|
||||
NSMutableArray *ranges;
|
||||
NSMutableArray *ranges_headers;
|
||||
NSString *ranges_boundry;
|
||||
int rangeIndex;
|
||||
|
||||
UInt64 requestContentLength;
|
||||
UInt64 requestContentLengthReceived;
|
||||
UInt64 requestChunkSize;
|
||||
UInt64 requestChunkSizeReceived;
|
||||
|
||||
NSMutableArray *responseDataSizes;
|
||||
}
|
||||
|
||||
- (id)initWithAsyncSocket:(GCDAsyncSocket *)newSocket configuration:(HTTPConfig *)aConfig;
|
||||
|
||||
- (void)start;
|
||||
- (void)stop;
|
||||
|
||||
- (void)startConnection;
|
||||
|
||||
- (BOOL)supportsMethod:(NSString *)method atPath:(NSString *)path;
|
||||
- (BOOL)expectsRequestBodyFromMethod:(NSString *)method atPath:(NSString *)path;
|
||||
|
||||
- (BOOL)isSecureServer;
|
||||
- (NSArray *)sslIdentityAndCertificates;
|
||||
|
||||
- (BOOL)isPasswordProtected:(NSString *)path;
|
||||
- (BOOL)useDigestAccessAuthentication;
|
||||
- (NSString *)realm;
|
||||
- (NSString *)passwordForUser:(NSString *)username;
|
||||
|
||||
- (NSDictionary *)parseParams:(NSString *)query;
|
||||
- (NSDictionary *)parseGetParams;
|
||||
|
||||
- (NSString *)requestURI;
|
||||
|
||||
- (NSArray *)directoryIndexFileNames;
|
||||
- (NSString *)filePathForURI:(NSString *)path;
|
||||
- (NSString *)filePathForURI:(NSString *)path allowDirectory:(BOOL)allowDirectory;
|
||||
- (NSObject<HTTPResponse> *)httpResponseForMethod:(NSString *)method URI:(NSString *)path;
|
||||
- (WebSocket *)webSocketForURI:(NSString *)path;
|
||||
|
||||
- (void)prepareForBodyWithSize:(UInt64)contentLength;
|
||||
- (void)processBodyData:(NSData *)postDataChunk;
|
||||
- (void)finishBody;
|
||||
|
||||
- (void)handleVersionNotSupported:(NSString *)version;
|
||||
- (void)handleAuthenticationFailed;
|
||||
- (void)handleResourceNotFound;
|
||||
- (void)handleInvalidRequest:(NSData *)data;
|
||||
- (void)handleUnknownMethod:(NSString *)method;
|
||||
|
||||
- (NSData *)preprocessResponse:(HTTPMessage *)response;
|
||||
- (NSData *)preprocessErrorResponse:(HTTPMessage *)response;
|
||||
|
||||
- (void)finishResponse;
|
||||
|
||||
- (BOOL)shouldDie;
|
||||
- (void)die;
|
||||
|
||||
@end
|
||||
|
||||
@interface HTTPConnection (AsynchronousHTTPResponse)
|
||||
- (void)responseHasAvailableData:(NSObject<HTTPResponse> *)sender;
|
||||
- (void)responseDidAbort:(NSObject<HTTPResponse> *)sender;
|
||||
@end
|
2708
xplan-ios/Base/Tool/CocoaHttpServer/Core/HTTPConnection.m
Executable file
2708
xplan-ios/Base/Tool/CocoaHttpServer/Core/HTTPConnection.m
Executable file
File diff suppressed because it is too large
Load Diff
136
xplan-ios/Base/Tool/CocoaHttpServer/Core/HTTPLogging.h
Executable file
136
xplan-ios/Base/Tool/CocoaHttpServer/Core/HTTPLogging.h
Executable file
@@ -0,0 +1,136 @@
|
||||
/**
|
||||
* In order to provide fast and flexible logging, this project uses Cocoa Lumberjack.
|
||||
*
|
||||
* The Google Code page has a wealth of documentation if you have any questions.
|
||||
* https://github.com/robbiehanson/CocoaLumberjack
|
||||
*
|
||||
* Here's what you need to know concerning how logging is setup for CocoaHTTPServer:
|
||||
*
|
||||
* There are 4 log levels:
|
||||
* - Error
|
||||
* - Warning
|
||||
* - Info
|
||||
* - Verbose
|
||||
*
|
||||
* In addition to this, there is a Trace flag that can be enabled.
|
||||
* When tracing is enabled, it spits out the methods that are being called.
|
||||
*
|
||||
* Please note that tracing is separate from the log levels.
|
||||
* For example, one could set the log level to warning, and enable tracing.
|
||||
*
|
||||
* All logging is asynchronous, except errors.
|
||||
* To use logging within your own custom files, follow the steps below.
|
||||
*
|
||||
* Step 1:
|
||||
* Import this header in your implementation file:
|
||||
*
|
||||
* #import "HTTPLogging.h"
|
||||
*
|
||||
* Step 2:
|
||||
* Define your logging level in your implementation file:
|
||||
*
|
||||
* // Log levels: off, error, warn, info, verbose
|
||||
* static const int httpLogLevel = HTTP_LOG_LEVEL_VERBOSE;
|
||||
*
|
||||
* If you wish to enable tracing, you could do something like this:
|
||||
*
|
||||
* // Debug levels: off, error, warn, info, verbose
|
||||
* static const int httpLogLevel = HTTP_LOG_LEVEL_INFO | HTTP_LOG_FLAG_TRACE;
|
||||
*
|
||||
* Step 3:
|
||||
* Replace your NSLog statements with HTTPLog statements according to the severity of the message.
|
||||
*
|
||||
* NSLog(@"Fatal error, no dohickey found!"); -> HTTPLogError(@"Fatal error, no dohickey found!");
|
||||
*
|
||||
* HTTPLog works exactly the same as NSLog.
|
||||
* This means you can pass it multiple variables just like NSLog.
|
||||
**/
|
||||
|
||||
#import "DDLog.h"
|
||||
|
||||
// Define logging context for every log message coming from the HTTP server.
|
||||
// The logging context can be extracted from the DDLogMessage from within the logging framework,
|
||||
// which gives loggers, formatters, and filters the ability to optionally process them differently.
|
||||
|
||||
#define HTTP_LOG_CONTEXT 80
|
||||
|
||||
// Configure log levels.
|
||||
|
||||
#define HTTP_LOG_FLAG_ERROR (1 << 0) // 0...00001
|
||||
#define HTTP_LOG_FLAG_WARN (1 << 1) // 0...00010
|
||||
#define HTTP_LOG_FLAG_INFO (1 << 2) // 0...00100
|
||||
#define HTTP_LOG_FLAG_VERBOSE (1 << 3) // 0...01000
|
||||
|
||||
#define HTTP_LOG_LEVEL_OFF 0 // 0...00000
|
||||
#define HTTP_LOG_LEVEL_ERROR (HTTP_LOG_LEVEL_OFF | HTTP_LOG_FLAG_ERROR) // 0...00001
|
||||
#define HTTP_LOG_LEVEL_WARN (HTTP_LOG_LEVEL_ERROR | HTTP_LOG_FLAG_WARN) // 0...00011
|
||||
#define HTTP_LOG_LEVEL_INFO (HTTP_LOG_LEVEL_WARN | HTTP_LOG_FLAG_INFO) // 0...00111
|
||||
#define HTTP_LOG_LEVEL_VERBOSE (HTTP_LOG_LEVEL_INFO | HTTP_LOG_FLAG_VERBOSE) // 0...01111
|
||||
|
||||
// Setup fine grained logging.
|
||||
// The first 4 bits are being used by the standard log levels (0 - 3)
|
||||
//
|
||||
// We're going to add tracing, but NOT as a log level.
|
||||
// Tracing can be turned on and off independently of log level.
|
||||
|
||||
#define HTTP_LOG_FLAG_TRACE (1 << 4) // 0...10000
|
||||
|
||||
// Setup the usual boolean macros.
|
||||
|
||||
#define HTTP_LOG_ERROR (httpLogLevel & HTTP_LOG_FLAG_ERROR)
|
||||
#define HTTP_LOG_WARN (httpLogLevel & HTTP_LOG_FLAG_WARN)
|
||||
#define HTTP_LOG_INFO (httpLogLevel & HTTP_LOG_FLAG_INFO)
|
||||
#define HTTP_LOG_VERBOSE (httpLogLevel & HTTP_LOG_FLAG_VERBOSE)
|
||||
#define HTTP_LOG_TRACE (httpLogLevel & HTTP_LOG_FLAG_TRACE)
|
||||
|
||||
// Configure asynchronous logging.
|
||||
// We follow the default configuration,
|
||||
// but we reserve a special macro to easily disable asynchronous logging for debugging purposes.
|
||||
|
||||
#define HTTP_LOG_ASYNC_ENABLED YES
|
||||
|
||||
#define HTTP_LOG_ASYNC_ERROR ( NO && HTTP_LOG_ASYNC_ENABLED)
|
||||
#define HTTP_LOG_ASYNC_WARN (YES && HTTP_LOG_ASYNC_ENABLED)
|
||||
#define HTTP_LOG_ASYNC_INFO (YES && HTTP_LOG_ASYNC_ENABLED)
|
||||
#define HTTP_LOG_ASYNC_VERBOSE (YES && HTTP_LOG_ASYNC_ENABLED)
|
||||
#define HTTP_LOG_ASYNC_TRACE (YES && HTTP_LOG_ASYNC_ENABLED)
|
||||
|
||||
// Define logging primitives.
|
||||
|
||||
#define HTTPLogError(frmt, ...) LOG_OBJC_MAYBE(HTTP_LOG_ASYNC_ERROR, httpLogLevel, HTTP_LOG_FLAG_ERROR, \
|
||||
HTTP_LOG_CONTEXT, frmt, ##__VA_ARGS__)
|
||||
|
||||
#define HTTPLogWarn(frmt, ...) LOG_OBJC_MAYBE(HTTP_LOG_ASYNC_WARN, httpLogLevel, HTTP_LOG_FLAG_WARN, \
|
||||
HTTP_LOG_CONTEXT, frmt, ##__VA_ARGS__)
|
||||
|
||||
#define HTTPLogInfo(frmt, ...) LOG_OBJC_MAYBE(HTTP_LOG_ASYNC_INFO, httpLogLevel, HTTP_LOG_FLAG_INFO, \
|
||||
HTTP_LOG_CONTEXT, frmt, ##__VA_ARGS__)
|
||||
|
||||
#define HTTPLogVerbose(frmt, ...) LOG_OBJC_MAYBE(HTTP_LOG_ASYNC_VERBOSE, httpLogLevel, HTTP_LOG_FLAG_VERBOSE, \
|
||||
HTTP_LOG_CONTEXT, frmt, ##__VA_ARGS__)
|
||||
|
||||
#define HTTPLogTrace() LOG_OBJC_MAYBE(HTTP_LOG_ASYNC_TRACE, httpLogLevel, HTTP_LOG_FLAG_TRACE, \
|
||||
HTTP_LOG_CONTEXT, @"%@[%p]: %@", THIS_FILE, self, THIS_METHOD)
|
||||
|
||||
#define HTTPLogTrace2(frmt, ...) LOG_OBJC_MAYBE(HTTP_LOG_ASYNC_TRACE, httpLogLevel, HTTP_LOG_FLAG_TRACE, \
|
||||
HTTP_LOG_CONTEXT, frmt, ##__VA_ARGS__)
|
||||
|
||||
|
||||
#define HTTPLogCError(frmt, ...) LOG_C_MAYBE(HTTP_LOG_ASYNC_ERROR, httpLogLevel, HTTP_LOG_FLAG_ERROR, \
|
||||
HTTP_LOG_CONTEXT, frmt, ##__VA_ARGS__)
|
||||
|
||||
#define HTTPLogCWarn(frmt, ...) LOG_C_MAYBE(HTTP_LOG_ASYNC_WARN, httpLogLevel, HTTP_LOG_FLAG_WARN, \
|
||||
HTTP_LOG_CONTEXT, frmt, ##__VA_ARGS__)
|
||||
|
||||
#define HTTPLogCInfo(frmt, ...) LOG_C_MAYBE(HTTP_LOG_ASYNC_INFO, httpLogLevel, HTTP_LOG_FLAG_INFO, \
|
||||
HTTP_LOG_CONTEXT, frmt, ##__VA_ARGS__)
|
||||
|
||||
#define HTTPLogCVerbose(frmt, ...) LOG_C_MAYBE(HTTP_LOG_ASYNC_VERBOSE, httpLogLevel, HTTP_LOG_FLAG_VERBOSE, \
|
||||
HTTP_LOG_CONTEXT, frmt, ##__VA_ARGS__)
|
||||
|
||||
#define HTTPLogCTrace() LOG_C_MAYBE(HTTP_LOG_ASYNC_TRACE, httpLogLevel, HTTP_LOG_FLAG_TRACE, \
|
||||
HTTP_LOG_CONTEXT, @"%@[%p]: %@", THIS_FILE, self, __FUNCTION__)
|
||||
|
||||
#define HTTPLogCTrace2(frmt, ...) LOG_C_MAYBE(HTTP_LOG_ASYNC_TRACE, httpLogLevel, HTTP_LOG_FLAG_TRACE, \
|
||||
HTTP_LOG_CONTEXT, frmt, ##__VA_ARGS__)
|
||||
|
48
xplan-ios/Base/Tool/CocoaHttpServer/Core/HTTPMessage.h
Executable file
48
xplan-ios/Base/Tool/CocoaHttpServer/Core/HTTPMessage.h
Executable file
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* The HTTPMessage class is a simple Objective-C wrapper around Apple's CFHTTPMessage class.
|
||||
**/
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#if TARGET_OS_IPHONE
|
||||
// Note: You may need to add the CFNetwork Framework to your project
|
||||
#import <CFNetwork/CFNetwork.h>
|
||||
#endif
|
||||
|
||||
#define HTTPVersion1_0 ((NSString *)kCFHTTPVersion1_0)
|
||||
#define HTTPVersion1_1 ((NSString *)kCFHTTPVersion1_1)
|
||||
|
||||
|
||||
@interface HTTPMessage : NSObject
|
||||
{
|
||||
CFHTTPMessageRef message;
|
||||
}
|
||||
|
||||
- (id)initEmptyRequest;
|
||||
|
||||
- (id)initRequestWithMethod:(NSString *)method URL:(NSURL *)url version:(NSString *)version;
|
||||
|
||||
- (id)initResponseWithStatusCode:(NSInteger)code description:(NSString *)description version:(NSString *)version;
|
||||
|
||||
- (BOOL)appendData:(NSData *)data;
|
||||
|
||||
- (BOOL)isHeaderComplete;
|
||||
|
||||
- (NSString *)version;
|
||||
|
||||
- (NSString *)method;
|
||||
- (NSURL *)url;
|
||||
|
||||
- (NSInteger)statusCode;
|
||||
|
||||
- (NSDictionary *)allHeaderFields;
|
||||
- (NSString *)headerField:(NSString *)headerField;
|
||||
|
||||
- (void)setHeaderField:(NSString *)headerField value:(NSString *)headerFieldValue;
|
||||
|
||||
- (NSData *)messageData;
|
||||
|
||||
- (NSData *)body;
|
||||
- (void)setBody:(NSData *)body;
|
||||
|
||||
@end
|
113
xplan-ios/Base/Tool/CocoaHttpServer/Core/HTTPMessage.m
Executable file
113
xplan-ios/Base/Tool/CocoaHttpServer/Core/HTTPMessage.m
Executable file
@@ -0,0 +1,113 @@
|
||||
#import "HTTPMessage.h"
|
||||
|
||||
#if ! __has_feature(objc_arc)
|
||||
#warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
|
||||
#endif
|
||||
|
||||
|
||||
@implementation HTTPMessage
|
||||
|
||||
- (id)initEmptyRequest
|
||||
{
|
||||
if ((self = [super init]))
|
||||
{
|
||||
message = CFHTTPMessageCreateEmpty(NULL, YES);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (id)initRequestWithMethod:(NSString *)method URL:(NSURL *)url version:(NSString *)version
|
||||
{
|
||||
if ((self = [super init]))
|
||||
{
|
||||
message = CFHTTPMessageCreateRequest(NULL,
|
||||
(__bridge CFStringRef)method,
|
||||
(__bridge CFURLRef)url,
|
||||
(__bridge CFStringRef)version);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (id)initResponseWithStatusCode:(NSInteger)code description:(NSString *)description version:(NSString *)version
|
||||
{
|
||||
if ((self = [super init]))
|
||||
{
|
||||
message = CFHTTPMessageCreateResponse(NULL,
|
||||
(CFIndex)code,
|
||||
(__bridge CFStringRef)description,
|
||||
(__bridge CFStringRef)version);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
if (message)
|
||||
{
|
||||
CFRelease(message);
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)appendData:(NSData *)data
|
||||
{
|
||||
return CFHTTPMessageAppendBytes(message, [data bytes], [data length]);
|
||||
}
|
||||
|
||||
- (BOOL)isHeaderComplete
|
||||
{
|
||||
return CFHTTPMessageIsHeaderComplete(message);
|
||||
}
|
||||
|
||||
- (NSString *)version
|
||||
{
|
||||
return (__bridge_transfer NSString *)CFHTTPMessageCopyVersion(message);
|
||||
}
|
||||
|
||||
- (NSString *)method
|
||||
{
|
||||
return (__bridge_transfer NSString *)CFHTTPMessageCopyRequestMethod(message);
|
||||
}
|
||||
|
||||
- (NSURL *)url
|
||||
{
|
||||
return (__bridge_transfer NSURL *)CFHTTPMessageCopyRequestURL(message);
|
||||
}
|
||||
|
||||
- (NSInteger)statusCode
|
||||
{
|
||||
return (NSInteger)CFHTTPMessageGetResponseStatusCode(message);
|
||||
}
|
||||
|
||||
- (NSDictionary *)allHeaderFields
|
||||
{
|
||||
return (__bridge_transfer NSDictionary *)CFHTTPMessageCopyAllHeaderFields(message);
|
||||
}
|
||||
|
||||
- (NSString *)headerField:(NSString *)headerField
|
||||
{
|
||||
return (__bridge_transfer NSString *)CFHTTPMessageCopyHeaderFieldValue(message, (__bridge CFStringRef)headerField);
|
||||
}
|
||||
|
||||
- (void)setHeaderField:(NSString *)headerField value:(NSString *)headerFieldValue
|
||||
{
|
||||
CFHTTPMessageSetHeaderFieldValue(message,
|
||||
(__bridge CFStringRef)headerField,
|
||||
(__bridge CFStringRef)headerFieldValue);
|
||||
}
|
||||
|
||||
- (NSData *)messageData
|
||||
{
|
||||
return (__bridge_transfer NSData *)CFHTTPMessageCopySerializedMessage(message);
|
||||
}
|
||||
|
||||
- (NSData *)body
|
||||
{
|
||||
return (__bridge_transfer NSData *)CFHTTPMessageCopyBody(message);
|
||||
}
|
||||
|
||||
- (void)setBody:(NSData *)body
|
||||
{
|
||||
CFHTTPMessageSetBody(message, (__bridge CFDataRef)body);
|
||||
}
|
||||
|
||||
@end
|
149
xplan-ios/Base/Tool/CocoaHttpServer/Core/HTTPResponse.h
Executable file
149
xplan-ios/Base/Tool/CocoaHttpServer/Core/HTTPResponse.h
Executable file
@@ -0,0 +1,149 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
|
||||
@protocol HTTPResponse
|
||||
|
||||
/**
|
||||
* Returns the length of the data in bytes.
|
||||
* If you don't know the length in advance, implement the isChunked method and have it return YES.
|
||||
**/
|
||||
- (UInt64)contentLength;
|
||||
|
||||
/**
|
||||
* The HTTP server supports range requests in order to allow things like
|
||||
* file download resumption and optimized streaming on mobile devices.
|
||||
**/
|
||||
- (UInt64)offset;
|
||||
- (void)setOffset:(UInt64)offset;
|
||||
|
||||
/**
|
||||
* Returns the data for the response.
|
||||
* You do not have to return data of the exact length that is given.
|
||||
* You may optionally return data of a lesser length.
|
||||
* However, you must never return data of a greater length than requested.
|
||||
* Doing so could disrupt proper support for range requests.
|
||||
*
|
||||
* To support asynchronous responses, read the discussion at the bottom of this header.
|
||||
**/
|
||||
- (NSData *)readDataOfLength:(NSUInteger)length;
|
||||
|
||||
/**
|
||||
* Should only return YES after the HTTPConnection has read all available data.
|
||||
* That is, all data for the response has been returned to the HTTPConnection via the readDataOfLength method.
|
||||
**/
|
||||
- (BOOL)isDone;
|
||||
|
||||
@optional
|
||||
|
||||
/**
|
||||
* If you need time to calculate any part of the HTTP response headers (status code or header fields),
|
||||
* this method allows you to delay sending the headers so that you may asynchronously execute the calculations.
|
||||
* Simply implement this method and return YES until you have everything you need concerning the headers.
|
||||
*
|
||||
* This method ties into the asynchronous response architecture of the HTTPConnection.
|
||||
* You should read the full discussion at the bottom of this header.
|
||||
*
|
||||
* If you return YES from this method,
|
||||
* the HTTPConnection will wait for you to invoke the responseHasAvailableData method.
|
||||
* After you do, the HTTPConnection will again invoke this method to see if the response is ready to send the headers.
|
||||
*
|
||||
* You should only delay sending the headers until you have everything you need concerning just the headers.
|
||||
* Asynchronously generating the body of the response is not an excuse to delay sending the headers.
|
||||
* Instead you should tie into the asynchronous response architecture, and use techniques such as the isChunked method.
|
||||
*
|
||||
* Important: You should read the discussion at the bottom of this header.
|
||||
**/
|
||||
- (BOOL)delayResponseHeaders;
|
||||
|
||||
/**
|
||||
* Status code for response.
|
||||
* Allows for responses such as redirect (301), etc.
|
||||
**/
|
||||
- (NSInteger)status;
|
||||
|
||||
/**
|
||||
* If you want to add any extra HTTP headers to the response,
|
||||
* simply return them in a dictionary in this method.
|
||||
**/
|
||||
- (NSDictionary *)httpHeaders;
|
||||
|
||||
/**
|
||||
* If you don't know the content-length in advance,
|
||||
* implement this method in your custom response class and return YES.
|
||||
*
|
||||
* Important: You should read the discussion at the bottom of this header.
|
||||
**/
|
||||
- (BOOL)isChunked;
|
||||
|
||||
/**
|
||||
* This method is called from the HTTPConnection class when the connection is closed,
|
||||
* or when the connection is finished with the response.
|
||||
* If your response is asynchronous, you should implement this method so you know not to
|
||||
* invoke any methods on the HTTPConnection after this method is called (as the connection may be deallocated).
|
||||
**/
|
||||
- (void)connectionDidClose;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
/**
|
||||
* Important notice to those implementing custom asynchronous and/or chunked responses:
|
||||
*
|
||||
* HTTPConnection supports asynchronous responses. All you have to do in your custom response class is
|
||||
* asynchronously generate the response, and invoke HTTPConnection's responseHasAvailableData method.
|
||||
* You don't have to wait until you have all of the response ready to invoke this method. For example, if you
|
||||
* generate the response in incremental chunks, you could call responseHasAvailableData after generating
|
||||
* each chunk. Please see the HTTPAsyncFileResponse class for an example of how to do this.
|
||||
*
|
||||
* The normal flow of events for an HTTPConnection while responding to a request is like this:
|
||||
* - Send http resopnse headers
|
||||
* - Get data from response via readDataOfLength method.
|
||||
* - Add data to asyncSocket's write queue.
|
||||
* - Wait for asyncSocket to notify it that the data has been sent.
|
||||
* - Get more data from response via readDataOfLength method.
|
||||
* - ... continue this cycle until the entire response has been sent.
|
||||
*
|
||||
* With an asynchronous response, the flow is a little different.
|
||||
*
|
||||
* First the HTTPResponse is given the opportunity to postpone sending the HTTP response headers.
|
||||
* This allows the response to asynchronously execute any code needed to calculate a part of the header.
|
||||
* An example might be the response needs to generate some custom header fields,
|
||||
* or perhaps the response needs to look for a resource on network-attached storage.
|
||||
* Since the network-attached storage may be slow, the response doesn't know whether to send a 200 or 404 yet.
|
||||
* In situations such as this, the HTTPResponse simply implements the delayResponseHeaders method and returns YES.
|
||||
* After returning YES from this method, the HTTPConnection will wait until the response invokes its
|
||||
* responseHasAvailableData method. After this occurs, the HTTPConnection will again query the delayResponseHeaders
|
||||
* method to see if the response is ready to send the headers.
|
||||
* This cycle will continue until the delayResponseHeaders method returns NO.
|
||||
*
|
||||
* You should only delay sending the response headers until you have everything you need concerning just the headers.
|
||||
* Asynchronously generating the body of the response is not an excuse to delay sending the headers.
|
||||
*
|
||||
* After the response headers have been sent, the HTTPConnection calls your readDataOfLength method.
|
||||
* You may or may not have any available data at this point. If you don't, then simply return nil.
|
||||
* You should later invoke HTTPConnection's responseHasAvailableData when you have data to send.
|
||||
*
|
||||
* You don't have to keep track of when you return nil in the readDataOfLength method, or how many times you've invoked
|
||||
* responseHasAvailableData. Just simply call responseHasAvailableData whenever you've generated new data, and
|
||||
* return nil in your readDataOfLength whenever you don't have any available data in the requested range.
|
||||
* HTTPConnection will automatically detect when it should be requesting new data and will act appropriately.
|
||||
*
|
||||
* It's important that you also keep in mind that the HTTP server supports range requests.
|
||||
* The setOffset method is mandatory, and should not be ignored.
|
||||
* Make sure you take into account the offset within the readDataOfLength method.
|
||||
* You should also be aware that the HTTPConnection automatically sorts any range requests.
|
||||
* So if your setOffset method is called with a value of 100, then you can safely release bytes 0-99.
|
||||
*
|
||||
* HTTPConnection can also help you keep your memory footprint small.
|
||||
* Imagine you're dynamically generating a 10 MB response. You probably don't want to load all this data into
|
||||
* RAM, and sit around waiting for HTTPConnection to slowly send it out over the network. All you need to do
|
||||
* is pay attention to when HTTPConnection requests more data via readDataOfLength. This is because HTTPConnection
|
||||
* will never allow asyncSocket's write queue to get much bigger than READ_CHUNKSIZE bytes. You should
|
||||
* consider how you might be able to take advantage of this fact to generate your asynchronous response on demand,
|
||||
* while at the same time keeping your memory footprint small, and your application lightning fast.
|
||||
*
|
||||
* If you don't know the content-length in advanced, you should also implement the isChunked method.
|
||||
* This means the response will not include a Content-Length header, and will instead use "Transfer-Encoding: chunked".
|
||||
* There's a good chance that if your response is asynchronous and dynamic, it's also chunked.
|
||||
* If your response is chunked, you don't need to worry about range requests.
|
||||
**/
|
215
xplan-ios/Base/Tool/CocoaHttpServer/Core/HTTPServer.h
Executable file
215
xplan-ios/Base/Tool/CocoaHttpServer/Core/HTTPServer.h
Executable file
@@ -0,0 +1,215 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "MyHTTPConnection.h"
|
||||
@class GCDAsyncSocket;
|
||||
@class WebSocket;
|
||||
|
||||
#if TARGET_OS_IPHONE
|
||||
#if __IPHONE_OS_VERSION_MIN_REQUIRED >= 40000 // iPhone 4.0
|
||||
#define IMPLEMENTED_PROTOCOLS <NSNetServiceDelegate>
|
||||
#else
|
||||
#define IMPLEMENTED_PROTOCOLS
|
||||
#endif
|
||||
#else
|
||||
#if MAC_OS_X_VERSION_MIN_REQUIRED >= 1060 // Mac OS X 10.6
|
||||
#define IMPLEMENTED_PROTOCOLS <NSNetServiceDelegate>
|
||||
#else
|
||||
#define IMPLEMENTED_PROTOCOLS
|
||||
#endif
|
||||
#endif
|
||||
|
||||
|
||||
@interface HTTPServer : NSObject IMPLEMENTED_PROTOCOLS
|
||||
{
|
||||
// Underlying asynchronous TCP/IP socket
|
||||
GCDAsyncSocket *asyncSocket;
|
||||
|
||||
// Dispatch queues
|
||||
dispatch_queue_t serverQueue;
|
||||
dispatch_queue_t connectionQueue;
|
||||
void *IsOnServerQueueKey;
|
||||
void *IsOnConnectionQueueKey;
|
||||
|
||||
// HTTP server configuration
|
||||
NSString *documentRoot;
|
||||
Class connectionClass;
|
||||
NSString *interface;
|
||||
UInt32 port;
|
||||
|
||||
// NSNetService and related variables
|
||||
NSNetService *netService;
|
||||
NSString *domain;
|
||||
NSString *type;
|
||||
NSString *name;
|
||||
NSString *publishedName;
|
||||
NSDictionary *txtRecordDictionary;
|
||||
|
||||
// Connection management
|
||||
NSMutableArray *connections;
|
||||
NSMutableArray *webSockets;
|
||||
NSLock *connectionsLock;
|
||||
NSLock *webSocketsLock;
|
||||
|
||||
BOOL isRunning;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies the document root to serve files from.
|
||||
* For example, if you set this to "/Users/<your_username>/Sites",
|
||||
* then it will serve files out of the local Sites directory (including subdirectories).
|
||||
*
|
||||
* The default value is nil.
|
||||
* The default server configuration will not serve any files until this is set.
|
||||
*
|
||||
* If you change the documentRoot while the server is running,
|
||||
* the change will affect future incoming http connections.
|
||||
**/
|
||||
- (NSString *)documentRoot;
|
||||
- (void)setDocumentRoot:(NSString *)value;
|
||||
|
||||
/**
|
||||
* The connection class is the class used to handle incoming HTTP connections.
|
||||
*
|
||||
* The default value is [HTTPConnection class].
|
||||
* You can override HTTPConnection, and then set this to [MyHTTPConnection class].
|
||||
*
|
||||
* If you change the connectionClass while the server is running,
|
||||
* the change will affect future incoming http connections.
|
||||
**/
|
||||
- (Class)connectionClass;
|
||||
- (void)setConnectionClass:(Class)value;
|
||||
|
||||
/**
|
||||
* Set what interface you'd like the server to listen on.
|
||||
* By default this is nil, which causes the server to listen on all available interfaces like en1, wifi etc.
|
||||
*
|
||||
* The interface may be specified by name (e.g. "en1" or "lo0") or by IP address (e.g. "192.168.4.34").
|
||||
* You may also use the special strings "localhost" or "loopback" to specify that
|
||||
* the socket only accept connections from the local machine.
|
||||
**/
|
||||
- (NSString *)interface;
|
||||
- (void)setInterface:(NSString *)value;
|
||||
|
||||
/**
|
||||
* The port number to run the HTTP server on.
|
||||
*
|
||||
* The default port number is zero, meaning the server will automatically use any available port.
|
||||
* This is the recommended port value, as it avoids possible port conflicts with other applications.
|
||||
* Technologies such as Bonjour can be used to allow other applications to automatically discover the port number.
|
||||
*
|
||||
* Note: As is common on most OS's, you need root privledges to bind to port numbers below 1024.
|
||||
*
|
||||
* You can change the port property while the server is running, but it won't affect the running server.
|
||||
* To actually change the port the server is listening for connections on you'll need to restart the server.
|
||||
*
|
||||
* The listeningPort method will always return the port number the running server is listening for connections on.
|
||||
* If the server is not running this method returns 0.
|
||||
**/
|
||||
- (UInt32)port;
|
||||
- (UInt32)listeningPort;
|
||||
- (void)setPort:(UInt32)value;
|
||||
|
||||
/**
|
||||
* Bonjour domain for publishing the service.
|
||||
* The default value is "local.".
|
||||
*
|
||||
* Note: Bonjour publishing requires you set a type.
|
||||
*
|
||||
* If you change the domain property after the bonjour service has already been published (server already started),
|
||||
* you'll need to invoke the republishBonjour method to update the broadcasted bonjour service.
|
||||
**/
|
||||
- (NSString *)domain;
|
||||
- (void)setDomain:(NSString *)value;
|
||||
|
||||
/**
|
||||
* Bonjour name for publishing the service.
|
||||
* The default value is "".
|
||||
*
|
||||
* If using an empty string ("") for the service name when registering,
|
||||
* the system will automatically use the "Computer Name".
|
||||
* Using an empty string will also handle name conflicts
|
||||
* by automatically appending a digit to the end of the name.
|
||||
*
|
||||
* Note: Bonjour publishing requires you set a type.
|
||||
*
|
||||
* If you change the name after the bonjour service has already been published (server already started),
|
||||
* you'll need to invoke the republishBonjour method to update the broadcasted bonjour service.
|
||||
*
|
||||
* The publishedName method will always return the actual name that was published via the bonjour service.
|
||||
* If the service is not running this method returns nil.
|
||||
**/
|
||||
- (NSString *)name;
|
||||
- (NSString *)publishedName;
|
||||
- (void)setName:(NSString *)value;
|
||||
|
||||
/**
|
||||
* Bonjour type for publishing the service.
|
||||
* The default value is nil.
|
||||
* The service will not be published via bonjour unless the type is set.
|
||||
*
|
||||
* If you wish to publish the service as a traditional HTTP server, you should set the type to be "_http._tcp.".
|
||||
*
|
||||
* If you change the type after the bonjour service has already been published (server already started),
|
||||
* you'll need to invoke the republishBonjour method to update the broadcasted bonjour service.
|
||||
**/
|
||||
- (NSString *)type;
|
||||
- (void)setType:(NSString *)value;
|
||||
|
||||
/**
|
||||
* Republishes the service via bonjour if the server is running.
|
||||
* If the service was not previously published, this method will publish it (if the server is running).
|
||||
**/
|
||||
- (void)republishBonjour;
|
||||
|
||||
/**
|
||||
*
|
||||
**/
|
||||
- (NSDictionary *)TXTRecordDictionary;
|
||||
- (void)setTXTRecordDictionary:(NSDictionary *)dict;
|
||||
|
||||
/**
|
||||
* Attempts to starts the server on the configured port, interface, etc.
|
||||
*
|
||||
* If an error occurs, this method returns NO and sets the errPtr (if given).
|
||||
* Otherwise returns YES on success.
|
||||
*
|
||||
* Some examples of errors that might occur:
|
||||
* - You specified the server listen on a port which is already in use by another application.
|
||||
* - You specified the server listen on a port number below 1024, which requires root priviledges.
|
||||
*
|
||||
* Code Example:
|
||||
*
|
||||
* NSError *err = nil;
|
||||
* if (![httpServer start:&err])
|
||||
* {
|
||||
* NSLog(@"Error starting http server: %@", err);
|
||||
* }
|
||||
**/
|
||||
- (BOOL)start:(NSError **)errPtr;
|
||||
|
||||
/**
|
||||
* Stops the server, preventing it from accepting any new connections.
|
||||
* You may specify whether or not you want to close the existing client connections.
|
||||
*
|
||||
* The default stop method (with no arguments) will close any existing connections. (It invokes [self stop:NO])
|
||||
**/
|
||||
- (void)stop;
|
||||
- (void)stop:(BOOL)keepExistingConnections;
|
||||
|
||||
- (BOOL)isRunning;
|
||||
|
||||
- (void)addWebSocket:(WebSocket *)ws;
|
||||
|
||||
- (NSUInteger)numberOfHTTPConnections;
|
||||
- (NSUInteger)numberOfWebSocketConnections;
|
||||
|
||||
/**
|
||||
Get the connections in the httpserver
|
||||
|
||||
@return the connections with array
|
||||
*/
|
||||
- (NSMutableArray *)getConnectionsInServer;
|
||||
|
||||
///添加代理使用
|
||||
@property (nonatomic,weak) UIViewController<MyHTTPConnectionDelegate> * viewcontroller;
|
||||
|
||||
@end
|
781
xplan-ios/Base/Tool/CocoaHttpServer/Core/HTTPServer.m
Executable file
781
xplan-ios/Base/Tool/CocoaHttpServer/Core/HTTPServer.m
Executable file
@@ -0,0 +1,781 @@
|
||||
#import "HTTPServer.h"
|
||||
#import "GCDAsyncSocket.h"
|
||||
#import "HTTPConnection.h"
|
||||
#import "WebSocket.h"
|
||||
#import "HTTPLogging.h"
|
||||
#if ! __has_feature(objc_arc)
|
||||
#warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
|
||||
#endif
|
||||
|
||||
// Log levels: off, error, warn, info, verbose
|
||||
// Other flags: trace
|
||||
static const int httpLogLevel = HTTP_LOG_LEVEL_INFO; // | HTTP_LOG_FLAG_TRACE;
|
||||
|
||||
@interface HTTPServer (PrivateAPI)
|
||||
|
||||
- (void)unpublishBonjour;
|
||||
- (void)publishBonjour;
|
||||
|
||||
+ (void)startBonjourThreadIfNeeded;
|
||||
+ (void)performBonjourBlock:(dispatch_block_t)block;
|
||||
|
||||
@end
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark -
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@implementation HTTPServer
|
||||
|
||||
/**
|
||||
* Standard Constructor.
|
||||
* Instantiates an HTTP server, but does not start it.
|
||||
**/
|
||||
- (id)init
|
||||
{
|
||||
if ((self = [super init]))
|
||||
{
|
||||
HTTPLogTrace();
|
||||
|
||||
// Setup underlying dispatch queues
|
||||
serverQueue = dispatch_queue_create("HTTPServer", NULL);
|
||||
connectionQueue = dispatch_queue_create("HTTPConnection", NULL);
|
||||
|
||||
IsOnServerQueueKey = &IsOnServerQueueKey;
|
||||
IsOnConnectionQueueKey = &IsOnConnectionQueueKey;
|
||||
|
||||
void *nonNullUnusedPointer = (__bridge void *)self; // Whatever, just not null
|
||||
|
||||
dispatch_queue_set_specific(serverQueue, IsOnServerQueueKey, nonNullUnusedPointer, NULL);
|
||||
dispatch_queue_set_specific(connectionQueue, IsOnConnectionQueueKey, nonNullUnusedPointer, NULL);
|
||||
|
||||
// Initialize underlying GCD based tcp socket
|
||||
asyncSocket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:serverQueue];
|
||||
|
||||
// Use default connection class of HTTPConnection
|
||||
connectionClass = [HTTPConnection self];
|
||||
|
||||
// By default bind on all available interfaces, en1, wifi etc
|
||||
interface = nil;
|
||||
|
||||
// Use a default port of 0
|
||||
// This will allow the kernel to automatically pick an open port for us
|
||||
port = 0;
|
||||
// port = 12306;
|
||||
// Configure default values for bonjour service
|
||||
|
||||
// Bonjour domain. Use the local domain by default
|
||||
domain = @"local.";
|
||||
|
||||
// If using an empty string ("") for the service name when registering,
|
||||
// the system will automatically use the "Computer Name".
|
||||
// Passing in an empty string will also handle name conflicts
|
||||
// by automatically appending a digit to the end of the name.
|
||||
name = @"";
|
||||
|
||||
// Initialize arrays to hold all the HTTP and webSocket connections
|
||||
connections = [[NSMutableArray alloc] init];
|
||||
webSockets = [[NSMutableArray alloc] init];
|
||||
|
||||
connectionsLock = [[NSLock alloc] init];
|
||||
webSocketsLock = [[NSLock alloc] init];
|
||||
|
||||
// Register for notifications of closed connections
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(connectionDidDie:)
|
||||
name:HTTPConnectionDidDieNotification
|
||||
object:nil];
|
||||
|
||||
// Register for notifications of closed websocket connections
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||
selector:@selector(webSocketDidDie:)
|
||||
name:WebSocketDidDieNotification
|
||||
object:nil];
|
||||
|
||||
isRunning = NO;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
/**
|
||||
* Standard Deconstructor.
|
||||
* Stops the server, and clients, and releases any resources connected with this instance.
|
||||
**/
|
||||
- (void)dealloc
|
||||
{
|
||||
HTTPLogTrace();
|
||||
|
||||
// Remove notification observer
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
||||
|
||||
// Stop the server if it's running
|
||||
[self stop];
|
||||
|
||||
// Release all instance variables
|
||||
|
||||
#if !OS_OBJECT_USE_OBJC
|
||||
dispatch_release(serverQueue);
|
||||
dispatch_release(connectionQueue);
|
||||
#endif
|
||||
|
||||
[asyncSocket setDelegate:nil delegateQueue:NULL];
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark Server Configuration
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* The document root is filesystem root for the webserver.
|
||||
* Thus requests for /index.html will be referencing the index.html file within the document root directory.
|
||||
* All file requests are relative to this document root.
|
||||
**/
|
||||
- (NSString *)documentRoot
|
||||
{
|
||||
__block NSString *result;
|
||||
|
||||
dispatch_sync(serverQueue, ^{
|
||||
result = documentRoot;
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
- (void)setDocumentRoot:(NSString *)value
|
||||
{
|
||||
HTTPLogTrace();
|
||||
|
||||
// Document root used to be of type NSURL.
|
||||
// Add type checking for early warning to developers upgrading from older versions.
|
||||
|
||||
if (value && ![value isKindOfClass:[NSString class]])
|
||||
{
|
||||
HTTPLogWarn(@"%@: %@ - Expecting NSString parameter, received %@ parameter",
|
||||
THIS_FILE, THIS_METHOD, NSStringFromClass([value class]));
|
||||
return;
|
||||
}
|
||||
|
||||
NSString *valueCopy = [value copy];
|
||||
|
||||
dispatch_async(serverQueue, ^{
|
||||
documentRoot = valueCopy;
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* The connection class is the class that will be used to handle connections.
|
||||
* That is, when a new connection is created, an instance of this class will be intialized.
|
||||
* The default connection class is HTTPConnection.
|
||||
* If you use a different connection class, it is assumed that the class extends HTTPConnection
|
||||
**/
|
||||
- (Class)connectionClass
|
||||
{
|
||||
__block Class result;
|
||||
|
||||
dispatch_sync(serverQueue, ^{
|
||||
result = connectionClass;
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
- (void)setConnectionClass:(Class)value
|
||||
{
|
||||
HTTPLogTrace();
|
||||
|
||||
dispatch_async(serverQueue, ^{
|
||||
connectionClass = value;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* What interface to bind the listening socket to.
|
||||
**/
|
||||
- (NSString *)interface
|
||||
{
|
||||
__block NSString *result;
|
||||
|
||||
dispatch_sync(serverQueue, ^{
|
||||
result = interface;
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
- (void)setInterface:(NSString *)value
|
||||
{
|
||||
NSString *valueCopy = [value copy];
|
||||
|
||||
dispatch_async(serverQueue, ^{
|
||||
interface = valueCopy;
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* The port to listen for connections on.
|
||||
* By default this port is initially set to zero, which allows the kernel to pick an available port for us.
|
||||
* After the HTTP server has started, the port being used may be obtained by this method.
|
||||
**/
|
||||
- (UInt32)port
|
||||
{
|
||||
__block UInt32 result;
|
||||
|
||||
dispatch_sync(serverQueue, ^{
|
||||
result = port;
|
||||
// result = 12306;
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
- (UInt32)listeningPort
|
||||
{
|
||||
__block UInt32 result;
|
||||
|
||||
dispatch_sync(serverQueue, ^{
|
||||
if (isRunning)
|
||||
result = [asyncSocket localPort];
|
||||
else
|
||||
result = 0;
|
||||
// result = 12306;
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
- (void)setPort:(UInt32)value
|
||||
{
|
||||
HTTPLogTrace();
|
||||
|
||||
dispatch_async(serverQueue, ^{
|
||||
port = value;
|
||||
// port = 12306;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Domain on which to broadcast this service via Bonjour.
|
||||
* The default domain is @"local".
|
||||
**/
|
||||
- (NSString *)domain
|
||||
{
|
||||
__block NSString *result;
|
||||
|
||||
dispatch_sync(serverQueue, ^{
|
||||
result = domain;
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
- (void)setDomain:(NSString *)value
|
||||
{
|
||||
HTTPLogTrace();
|
||||
|
||||
NSString *valueCopy = [value copy];
|
||||
|
||||
dispatch_async(serverQueue, ^{
|
||||
domain = valueCopy;
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* The name to use for this service via Bonjour.
|
||||
* The default name is an empty string,
|
||||
* which should result in the published name being the host name of the computer.
|
||||
**/
|
||||
- (NSString *)name
|
||||
{
|
||||
__block NSString *result;
|
||||
|
||||
dispatch_sync(serverQueue, ^{
|
||||
result = name;
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
- (NSString *)publishedName
|
||||
{
|
||||
__block NSString *result;
|
||||
|
||||
dispatch_sync(serverQueue, ^{
|
||||
|
||||
if (netService == nil)
|
||||
{
|
||||
result = nil;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
dispatch_block_t bonjourBlock = ^{
|
||||
result = [[netService name] copy];
|
||||
};
|
||||
|
||||
[[self class] performBonjourBlock:bonjourBlock];
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
- (void)setName:(NSString *)value
|
||||
{
|
||||
NSString *valueCopy = [value copy];
|
||||
|
||||
dispatch_async(serverQueue, ^{
|
||||
name = valueCopy;
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* The type of service to publish via Bonjour.
|
||||
* No type is set by default, and one must be set in order for the service to be published.
|
||||
**/
|
||||
- (NSString *)type
|
||||
{
|
||||
__block NSString *result;
|
||||
|
||||
dispatch_sync(serverQueue, ^{
|
||||
result = type;
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
- (void)setType:(NSString *)value
|
||||
{
|
||||
NSString *valueCopy = [value copy];
|
||||
|
||||
dispatch_async(serverQueue, ^{
|
||||
type = valueCopy;
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* The extra data to use for this service via Bonjour.
|
||||
**/
|
||||
- (NSDictionary *)TXTRecordDictionary
|
||||
{
|
||||
__block NSDictionary *result;
|
||||
|
||||
dispatch_sync(serverQueue, ^{
|
||||
result = txtRecordDictionary;
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
- (void)setTXTRecordDictionary:(NSDictionary *)value
|
||||
{
|
||||
HTTPLogTrace();
|
||||
|
||||
NSDictionary *valueCopy = [value copy];
|
||||
|
||||
dispatch_async(serverQueue, ^{
|
||||
|
||||
txtRecordDictionary = valueCopy;
|
||||
|
||||
// Update the txtRecord of the netService if it has already been published
|
||||
if (netService)
|
||||
{
|
||||
NSNetService *theNetService = netService;
|
||||
NSData *txtRecordData = nil;
|
||||
if (txtRecordDictionary)
|
||||
txtRecordData = [NSNetService dataFromTXTRecordDictionary:txtRecordDictionary];
|
||||
|
||||
dispatch_block_t bonjourBlock = ^{
|
||||
[theNetService setTXTRecordData:txtRecordData];
|
||||
};
|
||||
|
||||
[[self class] performBonjourBlock:bonjourBlock];
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark Server Control
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
- (BOOL)start:(NSError **)errPtr
|
||||
{
|
||||
HTTPLogTrace();
|
||||
|
||||
__block BOOL success = YES;
|
||||
__block NSError *err = nil;
|
||||
|
||||
dispatch_sync(serverQueue, ^{ @autoreleasepool {
|
||||
// port = 12306;
|
||||
success = [asyncSocket acceptOnInterface:interface port:port error:&err];
|
||||
if (success)
|
||||
{
|
||||
HTTPLogInfo(@"%@: Started HTTP server on port %hu", THIS_FILE, [asyncSocket localPort]);
|
||||
//HTTPLogInfo(@"%@: Started HTTP server on port %d", THIS_FILE, 12306);
|
||||
|
||||
isRunning = YES;
|
||||
[self publishBonjour];
|
||||
}
|
||||
else
|
||||
{
|
||||
HTTPLogError(@"%@: Failed to start HTTP Server: %@", THIS_FILE, err);
|
||||
}
|
||||
}});
|
||||
|
||||
if (errPtr)
|
||||
*errPtr = err;
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
- (void)stop
|
||||
{
|
||||
[self stop:NO];
|
||||
}
|
||||
|
||||
- (void)stop:(BOOL)keepExistingConnections
|
||||
{
|
||||
HTTPLogTrace();
|
||||
|
||||
dispatch_sync(serverQueue, ^{ @autoreleasepool {
|
||||
|
||||
// First stop publishing the service via bonjour
|
||||
[self unpublishBonjour];
|
||||
|
||||
// Stop listening / accepting incoming connections
|
||||
[asyncSocket disconnect];
|
||||
isRunning = NO;
|
||||
|
||||
if (!keepExistingConnections)
|
||||
{
|
||||
// Stop all HTTP connections the server owns
|
||||
[connectionsLock lock];
|
||||
for (HTTPConnection *connection in connections)
|
||||
{
|
||||
[connection stop];
|
||||
}
|
||||
[connections removeAllObjects];
|
||||
[connectionsLock unlock];
|
||||
|
||||
// Stop all WebSocket connections the server owns
|
||||
[webSocketsLock lock];
|
||||
for (WebSocket *webSocket in webSockets)
|
||||
{
|
||||
[webSocket stop];
|
||||
}
|
||||
[webSockets removeAllObjects];
|
||||
[webSocketsLock unlock];
|
||||
}
|
||||
}});
|
||||
}
|
||||
|
||||
- (BOOL)isRunning
|
||||
{
|
||||
__block BOOL result;
|
||||
|
||||
dispatch_sync(serverQueue, ^{
|
||||
result = isRunning;
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
- (void)addWebSocket:(WebSocket *)ws
|
||||
{
|
||||
[webSocketsLock lock];
|
||||
|
||||
HTTPLogTrace();
|
||||
[webSockets addObject:ws];
|
||||
|
||||
[webSocketsLock unlock];
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark Server Status
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Returns the number of http client connections that are currently connected to the server.
|
||||
**/
|
||||
- (NSUInteger)numberOfHTTPConnections
|
||||
{
|
||||
NSUInteger result = 0;
|
||||
|
||||
[connectionsLock lock];
|
||||
result = [connections count];
|
||||
[connectionsLock unlock];
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of websocket client connections that are currently connected to the server.
|
||||
**/
|
||||
- (NSUInteger)numberOfWebSocketConnections
|
||||
{
|
||||
NSUInteger result = 0;
|
||||
|
||||
[webSocketsLock lock];
|
||||
result = [webSockets count];
|
||||
[webSocketsLock unlock];
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark Incoming Connections
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
- (HTTPConfig *)config
|
||||
{
|
||||
// Override me if you want to provide a custom config to the new connection.
|
||||
//
|
||||
// Generally this involves overriding the HTTPConfig class to include any custom settings,
|
||||
// and then having this method return an instance of 'MyHTTPConfig'.
|
||||
|
||||
// Note: Think you can make the server faster by putting each connection on its own queue?
|
||||
// Then benchmark it before and after and discover for yourself the shocking truth!
|
||||
//
|
||||
// Try the apache benchmark tool (already installed on your Mac):
|
||||
// $ ab -n 1000 -c 1 http://localhost:<port>/some_path.html
|
||||
|
||||
return [[HTTPConfig alloc] initWithServer:self documentRoot:documentRoot queue:connectionQueue];
|
||||
}
|
||||
|
||||
- (void)socket:(GCDAsyncSocket *)sock didAcceptNewSocket:(GCDAsyncSocket *)newSocket
|
||||
{
|
||||
HTTPConnection *newConnection = (HTTPConnection *)[[connectionClass alloc] initWithAsyncSocket:newSocket
|
||||
configuration:[self config]];
|
||||
((MyHTTPConnection *)newConnection).delegate = self.viewcontroller;
|
||||
[connectionsLock lock];
|
||||
[connections addObject:newConnection];
|
||||
[connectionsLock unlock];
|
||||
|
||||
[newConnection start];
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark Bonjour
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
- (void)publishBonjour
|
||||
{
|
||||
HTTPLogTrace();
|
||||
|
||||
NSAssert(dispatch_get_specific(IsOnServerQueueKey) != NULL, @"Must be on serverQueue");
|
||||
|
||||
if (type)
|
||||
{
|
||||
netService = [[NSNetService alloc] initWithDomain:domain type:type name:name port:[asyncSocket localPort]];
|
||||
// netService = [[NSNetService alloc] initWithDomain:domain type:type name:name port:12306];
|
||||
[netService setDelegate:self];
|
||||
|
||||
NSNetService *theNetService = netService;
|
||||
NSData *txtRecordData = nil;
|
||||
if (txtRecordDictionary)
|
||||
txtRecordData = [NSNetService dataFromTXTRecordDictionary:txtRecordDictionary];
|
||||
|
||||
dispatch_block_t bonjourBlock = ^{
|
||||
|
||||
[theNetService removeFromRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
|
||||
[theNetService scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
|
||||
[theNetService publish];
|
||||
|
||||
// Do not set the txtRecordDictionary prior to publishing!!!
|
||||
// This will cause the OS to crash!!!
|
||||
if (txtRecordData)
|
||||
{
|
||||
[theNetService setTXTRecordData:txtRecordData];
|
||||
}
|
||||
};
|
||||
|
||||
[[self class] startBonjourThreadIfNeeded];
|
||||
[[self class] performBonjourBlock:bonjourBlock];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)unpublishBonjour
|
||||
{
|
||||
HTTPLogTrace();
|
||||
|
||||
NSAssert(dispatch_get_specific(IsOnServerQueueKey) != NULL, @"Must be on serverQueue");
|
||||
|
||||
if (netService)
|
||||
{
|
||||
NSNetService *theNetService = netService;
|
||||
|
||||
dispatch_block_t bonjourBlock = ^{
|
||||
|
||||
[theNetService stop];
|
||||
};
|
||||
|
||||
[[self class] performBonjourBlock:bonjourBlock];
|
||||
|
||||
netService = nil;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Republishes the service via bonjour if the server is running.
|
||||
* If the service was not previously published, this method will publish it (if the server is running).
|
||||
**/
|
||||
- (void)republishBonjour
|
||||
{
|
||||
HTTPLogTrace();
|
||||
|
||||
dispatch_async(serverQueue, ^{
|
||||
|
||||
[self unpublishBonjour];
|
||||
[self publishBonjour];
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when our bonjour service has been successfully published.
|
||||
* This method does nothing but output a log message telling us about the published service.
|
||||
**/
|
||||
- (void)netServiceDidPublish:(NSNetService *)ns
|
||||
{
|
||||
// Override me to do something here...
|
||||
//
|
||||
// Note: This method is invoked on our bonjour thread.
|
||||
|
||||
HTTPLogInfo(@"Bonjour Service Published: domain(%@) type(%@) name(%@)", [ns domain], [ns type], [ns name]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called if our bonjour service failed to publish itself.
|
||||
* This method does nothing but output a log message telling us about the published service.
|
||||
**/
|
||||
- (void)netService:(NSNetService *)ns didNotPublish:(NSDictionary *)errorDict
|
||||
{
|
||||
// Override me to do something here...
|
||||
//
|
||||
// Note: This method in invoked on our bonjour thread.
|
||||
|
||||
HTTPLogWarn(@"Failed to Publish Service: domain(%@) type(%@) name(%@) - %@",
|
||||
[ns domain], [ns type], [ns name], errorDict);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark Notifications
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* This method is automatically called when a notification of type HTTPConnectionDidDieNotification is posted.
|
||||
* It allows us to remove the connection from our array.
|
||||
**/
|
||||
- (void)connectionDidDie:(NSNotification *)notification
|
||||
{
|
||||
// Note: This method is called on the connection queue that posted the notification
|
||||
|
||||
[connectionsLock lock];
|
||||
|
||||
HTTPLogTrace();
|
||||
[connections removeObject:[notification object]];
|
||||
|
||||
[connectionsLock unlock];
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is automatically called when a notification of type WebSocketDidDieNotification is posted.
|
||||
* It allows us to remove the websocket from our array.
|
||||
**/
|
||||
- (void)webSocketDidDie:(NSNotification *)notification
|
||||
{
|
||||
// Note: This method is called on the connection queue that posted the notification
|
||||
|
||||
[webSocketsLock lock];
|
||||
|
||||
HTTPLogTrace();
|
||||
[webSockets removeObject:[notification object]];
|
||||
|
||||
[webSocketsLock unlock];
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark Bonjour Thread
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* NSNetService is runloop based, so it requires a thread with a runloop.
|
||||
* This gives us two options:
|
||||
*
|
||||
* - Use the main thread
|
||||
* - Setup our own dedicated thread
|
||||
*
|
||||
* Since we have various blocks of code that need to synchronously access the netservice objects,
|
||||
* using the main thread becomes troublesome and a potential for deadlock.
|
||||
**/
|
||||
|
||||
static NSThread *bonjourThread;
|
||||
|
||||
+ (void)startBonjourThreadIfNeeded
|
||||
{
|
||||
HTTPLogTrace();
|
||||
|
||||
static dispatch_once_t predicate;
|
||||
dispatch_once(&predicate, ^{
|
||||
|
||||
HTTPLogVerbose(@"%@: Starting bonjour thread...", THIS_FILE);
|
||||
|
||||
bonjourThread = [[NSThread alloc] initWithTarget:self
|
||||
selector:@selector(bonjourThread)
|
||||
object:nil];
|
||||
[bonjourThread start];
|
||||
});
|
||||
}
|
||||
|
||||
+ (void)bonjourThread
|
||||
{
|
||||
@autoreleasepool {
|
||||
|
||||
HTTPLogVerbose(@"%@: BonjourThread: Started", THIS_FILE);
|
||||
|
||||
// We can't run the run loop unless it has an associated input source or a timer.
|
||||
// So we'll just create a timer that will never fire - unless the server runs for 10,000 years.
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wundeclared-selector"
|
||||
[NSTimer scheduledTimerWithTimeInterval:[[NSDate distantFuture] timeIntervalSinceNow]
|
||||
target:self
|
||||
selector:@selector(donothingatall:)
|
||||
userInfo:nil
|
||||
repeats:YES];
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
[[NSRunLoop currentRunLoop] run];
|
||||
|
||||
HTTPLogVerbose(@"%@: BonjourThread: Aborted", THIS_FILE);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
+ (void)executeBonjourBlock:(dispatch_block_t)block
|
||||
{
|
||||
HTTPLogTrace();
|
||||
|
||||
NSAssert([NSThread currentThread] == bonjourThread, @"Executed on incorrect thread");
|
||||
|
||||
block();
|
||||
}
|
||||
|
||||
+ (void)performBonjourBlock:(dispatch_block_t)block
|
||||
{
|
||||
HTTPLogTrace();
|
||||
|
||||
[self performSelector:@selector(executeBonjourBlock:)
|
||||
onThread:bonjourThread
|
||||
withObject:block
|
||||
waitUntilDone:YES];
|
||||
}
|
||||
|
||||
- (NSMutableArray *)getConnectionsInServer {
|
||||
return connections.count > 0 ? connections : [NSMutableArray array];
|
||||
}
|
||||
|
||||
@end
|
65
xplan-ios/Base/Tool/CocoaHttpServer/Core/Mime/MultipartFormDataParser.h
Executable file
65
xplan-ios/Base/Tool/CocoaHttpServer/Core/Mime/MultipartFormDataParser.h
Executable file
@@ -0,0 +1,65 @@
|
||||
|
||||
#import "MultipartMessageHeader.h"
|
||||
|
||||
/*
|
||||
Part one: http://tools.ietf.org/html/rfc2045 (Format of Internet Message Bodies)
|
||||
Part two: http://tools.ietf.org/html/rfc2046 (Media Types)
|
||||
Part three: http://tools.ietf.org/html/rfc2047 (Message Header Extensions for Non-ASCII Text)
|
||||
Part four: http://tools.ietf.org/html/rfc4289 (Registration Procedures)
|
||||
Part five: http://tools.ietf.org/html/rfc2049 (Conformance Criteria and Examples)
|
||||
|
||||
Internet message format: http://tools.ietf.org/html/rfc2822
|
||||
|
||||
Multipart/form-data http://tools.ietf.org/html/rfc2388
|
||||
*/
|
||||
|
||||
@class MultipartFormDataParser;
|
||||
|
||||
//-----------------------------------------------------------------
|
||||
// protocol MultipartFormDataParser
|
||||
//-----------------------------------------------------------------
|
||||
|
||||
@protocol MultipartFormDataParserDelegate <NSObject>
|
||||
@optional
|
||||
- (void) processContent:(NSData*) data WithHeader:(MultipartMessageHeader*) header;
|
||||
- (void) processEndOfPartWithHeader:(MultipartMessageHeader*) header;
|
||||
- (void) processPreambleData:(NSData*) data;
|
||||
- (void) processEpilogueData:(NSData*) data;
|
||||
- (void) processStartOfPartWithHeader:(MultipartMessageHeader*) header;
|
||||
@end
|
||||
|
||||
//-----------------------------------------------------------------
|
||||
// interface MultipartFormDataParser
|
||||
//-----------------------------------------------------------------
|
||||
|
||||
@interface MultipartFormDataParser : NSObject {
|
||||
NSMutableData* pendingData;
|
||||
NSData* boundaryData;
|
||||
MultipartMessageHeader* currentHeader;
|
||||
|
||||
BOOL waitingForCRLF;
|
||||
BOOL reachedEpilogue;
|
||||
BOOL processedPreamble;
|
||||
BOOL checkForContentEnd;
|
||||
|
||||
#if __has_feature(objc_arc_weak)
|
||||
__weak id<MultipartFormDataParserDelegate> delegate;
|
||||
#else
|
||||
__unsafe_unretained id<MultipartFormDataParserDelegate> delegate;
|
||||
#endif
|
||||
int currentEncoding;
|
||||
NSStringEncoding formEncoding;
|
||||
}
|
||||
|
||||
- (BOOL) appendData:(NSData*) data;
|
||||
|
||||
- (id) initWithBoundary:(NSString*) boundary formEncoding:(NSStringEncoding) formEncoding;
|
||||
|
||||
#if __has_feature(objc_arc_weak)
|
||||
@property(weak, readwrite) id delegate;
|
||||
#else
|
||||
@property(unsafe_unretained, readwrite) id delegate;
|
||||
#endif
|
||||
@property(readwrite) NSStringEncoding formEncoding;
|
||||
|
||||
@end
|
529
xplan-ios/Base/Tool/CocoaHttpServer/Core/Mime/MultipartFormDataParser.m
Executable file
529
xplan-ios/Base/Tool/CocoaHttpServer/Core/Mime/MultipartFormDataParser.m
Executable file
@@ -0,0 +1,529 @@
|
||||
|
||||
#import "MultipartFormDataParser.h"
|
||||
#import "DDData.h"
|
||||
#import "HTTPLogging.h"
|
||||
|
||||
#pragma mark log level
|
||||
|
||||
#ifdef DEBUG
|
||||
static const int httpLogLevel = HTTP_LOG_LEVEL_WARN;
|
||||
#else
|
||||
static const int httpLogLevel = HTTP_LOG_LEVEL_WARN;
|
||||
#endif
|
||||
|
||||
#ifdef __x86_64__
|
||||
#define FMTNSINT "li"
|
||||
#else
|
||||
#define FMTNSINT "i"
|
||||
#endif
|
||||
|
||||
|
||||
//-----------------------------------------------------------------
|
||||
// interface MultipartFormDataParser (private)
|
||||
//-----------------------------------------------------------------
|
||||
|
||||
|
||||
@interface MultipartFormDataParser (private)
|
||||
+ (NSData*) decodedDataFromData:(NSData*) data encoding:(int) encoding;
|
||||
|
||||
- (int) findHeaderEnd:(NSData*) workingData fromOffset:(int) offset;
|
||||
- (int) findContentEnd:(NSData*) data fromOffset:(int) offset;
|
||||
|
||||
- (int) numberOfBytesToLeavePendingWithData:(NSData*) data length:(NSUInteger) length encoding:(int) encoding;
|
||||
- (int) offsetTillNewlineSinceOffset:(int) offset inData:(NSData*) data;
|
||||
|
||||
- (int) processPreamble:(NSData*) workingData;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
//-----------------------------------------------------------------
|
||||
// implementation MultipartFormDataParser
|
||||
//-----------------------------------------------------------------
|
||||
|
||||
|
||||
@implementation MultipartFormDataParser
|
||||
@synthesize delegate,formEncoding;
|
||||
|
||||
- (id) initWithBoundary:(NSString*) boundary formEncoding:(NSStringEncoding) _formEncoding {
|
||||
if( nil == (self = [super init]) ){
|
||||
return self;
|
||||
}
|
||||
if( nil == boundary ) {
|
||||
HTTPLogWarn(@"MultipartFormDataParser: init with zero boundary");
|
||||
return nil;
|
||||
}
|
||||
boundaryData = [[@"\r\n--" stringByAppendingString:boundary] dataUsingEncoding:NSASCIIStringEncoding];
|
||||
|
||||
pendingData = [[NSMutableData alloc] init];
|
||||
currentEncoding = contentTransferEncoding_binary;
|
||||
currentHeader = nil;
|
||||
|
||||
formEncoding = _formEncoding;
|
||||
reachedEpilogue = NO;
|
||||
processedPreamble = NO;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
|
||||
- (BOOL) appendData:(NSData *)data {
|
||||
// Can't parse without boundary;
|
||||
if( nil == boundaryData ) {
|
||||
HTTPLogError(@"MultipartFormDataParser: Trying to parse multipart without specifying a valid boundary");
|
||||
assert(false);
|
||||
return NO;
|
||||
}
|
||||
NSData* workingData = data;
|
||||
|
||||
if( pendingData.length ) {
|
||||
[pendingData appendData:data];
|
||||
workingData = pendingData;
|
||||
}
|
||||
|
||||
// the parser saves parse stat in the offset variable, which indicates offset of unhandled part in
|
||||
// currently received chunk. Before returning, we always drop all data up to offset, leaving
|
||||
// only unhandled for the next call
|
||||
|
||||
int offset = 0;
|
||||
|
||||
// don't parse data unless its size is greater then boundary length, so we couldn't
|
||||
// misfind the boundary, if it got split into different data chunks
|
||||
NSUInteger sizeToLeavePending = boundaryData.length;
|
||||
|
||||
if( !reachedEpilogue && workingData.length <= sizeToLeavePending ) {
|
||||
// not enough data even to start parsing.
|
||||
// save to pending data.
|
||||
if( !pendingData.length ) {
|
||||
[pendingData appendData:data];
|
||||
}
|
||||
if( checkForContentEnd ) {
|
||||
if( pendingData.length >= 2 ) {
|
||||
if( *(uint16_t*)(pendingData.bytes + offset) == 0x2D2D ) {
|
||||
// we found the multipart end. all coming next is an epilogue.
|
||||
HTTPLogVerbose(@"MultipartFormDataParser: End of multipart message");
|
||||
waitingForCRLF = YES;
|
||||
reachedEpilogue = YES;
|
||||
offset+= 2;
|
||||
}
|
||||
else {
|
||||
checkForContentEnd = NO;
|
||||
waitingForCRLF = YES;
|
||||
return YES;
|
||||
}
|
||||
} else {
|
||||
return YES;
|
||||
}
|
||||
|
||||
}
|
||||
else {
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
while( true ) {
|
||||
if( checkForContentEnd ) {
|
||||
// the flag will be raised to check if the last part was the last one.
|
||||
if( offset < workingData.length -1 ) {
|
||||
char* bytes = (char*) workingData.bytes;
|
||||
if( *(uint16_t*)(bytes + offset) == 0x2D2D ) {
|
||||
// we found the multipart end. all coming next is an epilogue.
|
||||
HTTPLogVerbose(@"MultipartFormDataParser: End of multipart message");
|
||||
checkForContentEnd = NO;
|
||||
reachedEpilogue = YES;
|
||||
// still wait for CRLF, that comes after boundary, but before epilogue.
|
||||
waitingForCRLF = YES;
|
||||
offset += 2;
|
||||
}
|
||||
else {
|
||||
// it's not content end, we have to wait till separator line end before next part comes
|
||||
waitingForCRLF = YES;
|
||||
checkForContentEnd = NO;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// we haven't got enough data to check for content end.
|
||||
// save current unhandled data (it may be 1 byte) to pending and recheck on next chunk received
|
||||
if( offset < workingData.length ) {
|
||||
[pendingData setData:[NSData dataWithBytes:workingData.bytes + workingData.length-1 length:1]];
|
||||
}
|
||||
else {
|
||||
// there is no unhandled data now, wait for more chunks
|
||||
[pendingData setData:[NSData data]];
|
||||
}
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
if( waitingForCRLF ) {
|
||||
|
||||
// the flag will be raised in the code below, meaning, we've read the boundary, but
|
||||
// didnt find the end of boundary line yet.
|
||||
|
||||
offset = [self offsetTillNewlineSinceOffset:offset inData:workingData];
|
||||
if( -1 == offset ) {
|
||||
// didnt find the endl again.
|
||||
if( offset ) {
|
||||
// we still have to save the unhandled data (maybe it's 1 byte CR)
|
||||
if( *((char*)workingData.bytes + workingData.length -1) == '\r' ) {
|
||||
[pendingData setData:[NSData dataWithBytes:workingData.bytes + workingData.length-1 length:1]];
|
||||
}
|
||||
else {
|
||||
// or save nothing if it wasnt
|
||||
[pendingData setData:[NSData data]];
|
||||
}
|
||||
}
|
||||
return YES;
|
||||
}
|
||||
waitingForCRLF = NO;
|
||||
}
|
||||
if( !processedPreamble ) {
|
||||
// got to find the first boundary before the actual content begins.
|
||||
offset = [self processPreamble:workingData];
|
||||
// wait for more data for preamble
|
||||
if( -1 == offset )
|
||||
return YES;
|
||||
// invoke continue to skip newline after boundary.
|
||||
continue;
|
||||
}
|
||||
|
||||
if( reachedEpilogue ) {
|
||||
// parse all epilogue data to delegate.
|
||||
if( [delegate respondsToSelector:@selector(processEpilogueData:)] ) {
|
||||
NSData* epilogueData = [NSData dataWithBytesNoCopy: (char*) workingData.bytes + offset length: workingData.length - offset freeWhenDone:NO];
|
||||
[delegate processEpilogueData: epilogueData];
|
||||
}
|
||||
return YES;
|
||||
}
|
||||
|
||||
if( nil == currentHeader ) {
|
||||
// nil == currentHeader is a state flag, indicating we are waiting for header now.
|
||||
// whenever part is over, currentHeader is set to nil.
|
||||
|
||||
// try to find CRLFCRLF bytes in the data, which indicates header end.
|
||||
// we won't parse header parts, as they won't be too large.
|
||||
int headerEnd = [self findHeaderEnd:workingData fromOffset:offset];
|
||||
if( -1 == headerEnd ) {
|
||||
// didn't recieve the full header yet.
|
||||
if( !pendingData.length) {
|
||||
// store the unprocessed data till next chunks come
|
||||
[pendingData appendBytes:data.bytes + offset length:data.length - offset];
|
||||
}
|
||||
else {
|
||||
if( offset ) {
|
||||
// save the current parse state; drop all handled data and save unhandled only.
|
||||
pendingData = [[NSMutableData alloc] initWithBytes: (char*) workingData.bytes + offset length:workingData.length - offset];
|
||||
}
|
||||
}
|
||||
return YES;
|
||||
}
|
||||
else {
|
||||
|
||||
// let the header parser do it's job from now on.
|
||||
NSData * headerData = [NSData dataWithBytesNoCopy: (char*) workingData.bytes + offset length:headerEnd + 2 - offset freeWhenDone:NO];
|
||||
currentHeader = [[MultipartMessageHeader alloc] initWithData:headerData formEncoding:formEncoding];
|
||||
|
||||
if( nil == currentHeader ) {
|
||||
// we've found the data is in wrong format.
|
||||
HTTPLogError(@"MultipartFormDataParser: MultipartFormDataParser: wrong input format, coulnd't get a valid header");
|
||||
return NO;
|
||||
}
|
||||
if( [delegate respondsToSelector:@selector(processStartOfPartWithHeader:)] ) {
|
||||
[delegate processStartOfPartWithHeader:currentHeader];
|
||||
}
|
||||
|
||||
HTTPLogVerbose(@"MultipartFormDataParser: MultipartFormDataParser: Retrieved part header.");
|
||||
}
|
||||
// skip the two trailing \r\n, in addition to the whole header.
|
||||
offset = headerEnd + 4;
|
||||
}
|
||||
// after we've got the header, we try to
|
||||
// find the boundary in the data.
|
||||
int contentEnd = [self findContentEnd:workingData fromOffset:offset];
|
||||
|
||||
if( contentEnd == -1 ) {
|
||||
|
||||
// this case, we didn't find the boundary, so the data is related to the current part.
|
||||
// we leave the sizeToLeavePending amount of bytes to make sure we don't include
|
||||
// boundary part in processed data.
|
||||
NSUInteger sizeToPass = workingData.length - offset - sizeToLeavePending;
|
||||
|
||||
// if we parse BASE64 encoded data, or Quoted-Printable data, we will make sure we don't break the format
|
||||
int leaveTrailing = [self numberOfBytesToLeavePendingWithData:data length:sizeToPass encoding:currentEncoding];
|
||||
sizeToPass -= leaveTrailing;
|
||||
|
||||
if( sizeToPass <= 0 ) {
|
||||
// wait for more data!
|
||||
if( offset ) {
|
||||
[pendingData setData:[NSData dataWithBytes:(char*) workingData.bytes + offset length:workingData.length - offset]];
|
||||
}
|
||||
return YES;
|
||||
}
|
||||
// decode the chunk and let the delegate use it (store in a file, for example)
|
||||
NSData* decodedData = [MultipartFormDataParser decodedDataFromData:[NSData dataWithBytesNoCopy:(char*)workingData.bytes + offset length:workingData.length - offset - sizeToLeavePending freeWhenDone:NO] encoding:currentEncoding];
|
||||
|
||||
if( [delegate respondsToSelector:@selector(processContent:WithHeader:)] ) {
|
||||
HTTPLogVerbose(@"MultipartFormDataParser: Processed %"FMTNSINT" bytes of body",sizeToPass);
|
||||
|
||||
[delegate processContent: decodedData WithHeader:currentHeader];
|
||||
}
|
||||
|
||||
// store the unprocessed data till the next chunks come.
|
||||
[pendingData setData:[NSData dataWithBytes:(char*)workingData.bytes + workingData.length - sizeToLeavePending length:sizeToLeavePending]];
|
||||
return YES;
|
||||
}
|
||||
else {
|
||||
|
||||
// Here we found the boundary.
|
||||
// let the delegate process it, and continue going to the next parts.
|
||||
if( [delegate respondsToSelector:@selector(processContent:WithHeader:)] ) {
|
||||
[delegate processContent:[NSData dataWithBytesNoCopy:(char*) workingData.bytes + offset length:contentEnd - offset freeWhenDone:NO] WithHeader:currentHeader];
|
||||
}
|
||||
|
||||
if( [delegate respondsToSelector:@selector(processEndOfPartWithHeader:)] ){
|
||||
[delegate processEndOfPartWithHeader:currentHeader];
|
||||
HTTPLogVerbose(@"MultipartFormDataParser: End of body part");
|
||||
}
|
||||
currentHeader = nil;
|
||||
|
||||
// set up offset to continue with the remaining data (if any)
|
||||
// cast to int because above comment suggests a small number
|
||||
offset = contentEnd + (int)boundaryData.length;
|
||||
checkForContentEnd = YES;
|
||||
// setting the flag tells the parser to skip all the data till CRLF
|
||||
}
|
||||
}
|
||||
return YES;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------
|
||||
#pragma mark private methods
|
||||
|
||||
- (int) offsetTillNewlineSinceOffset:(int) offset inData:(NSData*) data {
|
||||
char* bytes = (char*) data.bytes;
|
||||
NSUInteger length = data.length;
|
||||
if( offset >= length - 1 )
|
||||
return -1;
|
||||
|
||||
while ( *(uint16_t*)(bytes + offset) != 0x0A0D ) {
|
||||
// find the trailing \r\n after the boundary. The boundary line might have any number of whitespaces before CRLF, according to rfc2046
|
||||
|
||||
// in debug, we might also want to know, if the file is somehow misformatted.
|
||||
#ifdef DEBUG
|
||||
if( !isspace(*(bytes+offset)) ) {
|
||||
HTTPLogWarn(@"MultipartFormDataParser: Warning, non-whitespace character '%c' between boundary bytes and CRLF in boundary line",*(bytes+offset) );
|
||||
}
|
||||
if( !isspace(*(bytes+offset+1)) ) {
|
||||
HTTPLogWarn(@"MultipartFormDataParser: Warning, non-whitespace character '%c' between boundary bytes and CRLF in boundary line",*(bytes+offset+1) );
|
||||
}
|
||||
#endif
|
||||
offset++;
|
||||
if( offset >= length ) {
|
||||
// no endl found within current data
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
offset += 2;
|
||||
return offset;
|
||||
}
|
||||
|
||||
|
||||
- (int) processPreamble:(NSData*) data {
|
||||
int offset = 0;
|
||||
|
||||
char* boundaryBytes = (char*) boundaryData.bytes + 2; // the first boundary won't have CRLF preceding.
|
||||
char* dataBytes = (char*) data.bytes;
|
||||
NSUInteger boundaryLength = boundaryData.length - 2;
|
||||
NSUInteger dataLength = data.length;
|
||||
|
||||
// find the boundary without leading CRLF.
|
||||
while( offset < dataLength - boundaryLength +1 ) {
|
||||
int i;
|
||||
for( i = 0;i < boundaryLength; i++ ) {
|
||||
if( boundaryBytes[i] != dataBytes[offset + i] )
|
||||
break;
|
||||
}
|
||||
if( i == boundaryLength ) {
|
||||
break;
|
||||
}
|
||||
offset++;
|
||||
}
|
||||
|
||||
if( offset == dataLength ) {
|
||||
// the end of preamble wasn't found in this chunk
|
||||
NSUInteger sizeToProcess = dataLength - boundaryLength;
|
||||
if( sizeToProcess > 0) {
|
||||
if( [delegate respondsToSelector:@selector(processPreambleData:)] ) {
|
||||
NSData* preambleData = [NSData dataWithBytesNoCopy: (char*) data.bytes length: data.length - offset - boundaryLength freeWhenDone:NO];
|
||||
[delegate processPreambleData:preambleData];
|
||||
HTTPLogVerbose(@"MultipartFormDataParser: processed preamble");
|
||||
}
|
||||
pendingData = [NSMutableData dataWithBytes: data.bytes + data.length - boundaryLength length:boundaryLength];
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
else {
|
||||
if ( offset && [delegate respondsToSelector:@selector(processPreambleData:)] ) {
|
||||
NSData* preambleData = [NSData dataWithBytesNoCopy: (char*) data.bytes length: offset freeWhenDone:NO];
|
||||
[delegate processPreambleData:preambleData];
|
||||
}
|
||||
offset +=boundaryLength;
|
||||
// tells to skip CRLF after the boundary.
|
||||
processedPreamble = YES;
|
||||
waitingForCRLF = YES;
|
||||
}
|
||||
return offset;
|
||||
}
|
||||
|
||||
|
||||
|
||||
- (int) findHeaderEnd:(NSData*) workingData fromOffset:(int)offset {
|
||||
char* bytes = (char*) workingData.bytes;
|
||||
NSUInteger inputLength = workingData.length;
|
||||
uint16_t separatorBytes = 0x0A0D;
|
||||
|
||||
while( true ) {
|
||||
if(inputLength < offset + 3 ) {
|
||||
// wait for more data
|
||||
return -1;
|
||||
}
|
||||
if( (*((uint16_t*) (bytes+offset)) == separatorBytes) && (*((uint16_t*) (bytes+offset)+1) == separatorBytes) ) {
|
||||
return offset;
|
||||
}
|
||||
offset++;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
- (int) findContentEnd:(NSData*) data fromOffset:(int) offset {
|
||||
char* boundaryBytes = (char*) boundaryData.bytes;
|
||||
char* dataBytes = (char*) data.bytes;
|
||||
NSUInteger boundaryLength = boundaryData.length;
|
||||
NSUInteger dataLength = data.length;
|
||||
|
||||
while( offset < dataLength - boundaryLength +1 ) {
|
||||
int i;
|
||||
for( i = 0;i < boundaryLength; i++ ) {
|
||||
if( boundaryBytes[i] != dataBytes[offset + i] )
|
||||
break;
|
||||
}
|
||||
if( i == boundaryLength ) {
|
||||
return offset;
|
||||
}
|
||||
offset++;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
- (int) numberOfBytesToLeavePendingWithData:(NSData*) data length:(int) length encoding:(int) encoding {
|
||||
// If we have BASE64 or Quoted-Printable encoded data, we have to be sure
|
||||
// we don't break the format.
|
||||
int sizeToLeavePending = 0;
|
||||
|
||||
if( encoding == contentTransferEncoding_base64 ) {
|
||||
char* bytes = (char*) data.bytes;
|
||||
int i;
|
||||
for( i = length - 1; i > 0; i++ ) {
|
||||
if( * (uint16_t*) (bytes + i) == 0x0A0D ) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
// now we've got to be sure that the length of passed data since last line
|
||||
// is multiplier of 4.
|
||||
sizeToLeavePending = (length - i) & ~0x11; // size to leave pending = length-i - (length-i) %4;
|
||||
return sizeToLeavePending;
|
||||
}
|
||||
|
||||
if( encoding == contentTransferEncoding_quotedPrintable ) {
|
||||
// we don't pass more less then 3 bytes anyway.
|
||||
if( length <= 2 )
|
||||
return length;
|
||||
// check the last bytes to be start of encoded symbol.
|
||||
const char* bytes = data.bytes + length - 2;
|
||||
if( bytes[0] == '=' )
|
||||
return 2;
|
||||
if( bytes[1] == '=' )
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------
|
||||
#pragma mark decoding
|
||||
|
||||
|
||||
+ (NSData*) decodedDataFromData:(NSData*) data encoding:(int) encoding {
|
||||
switch (encoding) {
|
||||
case contentTransferEncoding_base64: {
|
||||
return [data base64Decoded];
|
||||
} break;
|
||||
|
||||
case contentTransferEncoding_quotedPrintable: {
|
||||
return [self decodedDataFromQuotedPrintableData:data];
|
||||
} break;
|
||||
|
||||
default: {
|
||||
return data;
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+ (NSData*) decodedDataFromQuotedPrintableData:(NSData *)data {
|
||||
// http://tools.ietf.org/html/rfc2045#section-6.7
|
||||
|
||||
const char hex [] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', };
|
||||
|
||||
NSMutableData* result = [[NSMutableData alloc] initWithLength:data.length];
|
||||
const char* bytes = (const char*) data.bytes;
|
||||
int count = 0;
|
||||
NSUInteger length = data.length;
|
||||
while( count < length ) {
|
||||
if( bytes[count] == '=' ) {
|
||||
[result appendBytes:bytes length:count];
|
||||
bytes = bytes + count + 1;
|
||||
length -= count + 1;
|
||||
count = 0;
|
||||
|
||||
if( length < 3 ) {
|
||||
HTTPLogWarn(@"MultipartFormDataParser: warning, trailing '=' in quoted printable data");
|
||||
}
|
||||
// soft newline
|
||||
if( bytes[0] == '\r' ) {
|
||||
bytes += 1;
|
||||
if(bytes[1] == '\n' ) {
|
||||
bytes += 2;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
char encodedByte = 0;
|
||||
|
||||
for( int i = 0; i < sizeof(hex); i++ ) {
|
||||
if( hex[i] == bytes[0] ) {
|
||||
encodedByte += i << 4;
|
||||
}
|
||||
if( hex[i] == bytes[1] ) {
|
||||
encodedByte += i;
|
||||
}
|
||||
}
|
||||
[result appendBytes:&encodedByte length:1];
|
||||
bytes += 2;
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
if( (unsigned char) bytes[count] > 126 ) {
|
||||
HTTPLogWarn(@"MultipartFormDataParser: Warning, character with code above 126 appears in quoted printable encoded data");
|
||||
}
|
||||
#endif
|
||||
|
||||
count++;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@end
|
33
xplan-ios/Base/Tool/CocoaHttpServer/Core/Mime/MultipartMessageHeader.h
Executable file
33
xplan-ios/Base/Tool/CocoaHttpServer/Core/Mime/MultipartMessageHeader.h
Executable file
@@ -0,0 +1,33 @@
|
||||
//
|
||||
// MultipartMessagePart.h
|
||||
// HttpServer
|
||||
//
|
||||
// Created by Валерий Гаврилов on 29.03.12.
|
||||
// Copyright (c) 2012 LLC "Online Publishing Partners" (onlinepp.ru). All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
|
||||
//-----------------------------------------------------------------
|
||||
// interface MultipartMessageHeader
|
||||
//-----------------------------------------------------------------
|
||||
enum {
|
||||
contentTransferEncoding_unknown,
|
||||
contentTransferEncoding_7bit,
|
||||
contentTransferEncoding_8bit,
|
||||
contentTransferEncoding_binary,
|
||||
contentTransferEncoding_base64,
|
||||
contentTransferEncoding_quotedPrintable,
|
||||
};
|
||||
|
||||
@interface MultipartMessageHeader : NSObject {
|
||||
NSMutableDictionary* fields;
|
||||
int encoding;
|
||||
NSString* contentDispositionName;
|
||||
}
|
||||
@property (strong,readonly) NSDictionary* fields;
|
||||
@property (readonly) int encoding;
|
||||
|
||||
- (id) initWithData:(NSData*) data formEncoding:(NSStringEncoding) encoding;
|
||||
@end
|
86
xplan-ios/Base/Tool/CocoaHttpServer/Core/Mime/MultipartMessageHeader.m
Executable file
86
xplan-ios/Base/Tool/CocoaHttpServer/Core/Mime/MultipartMessageHeader.m
Executable file
@@ -0,0 +1,86 @@
|
||||
//
|
||||
// MultipartMessagePart.m
|
||||
// HttpServer
|
||||
//
|
||||
// Created by Валерий Гаврилов on 29.03.12.
|
||||
// Copyright (c) 2012 LLC "Online Publishing Partners" (onlinepp.ru). All rights reserved.
|
||||
|
||||
#import "MultipartMessageHeader.h"
|
||||
#import "MultipartMessageHeaderField.h"
|
||||
|
||||
#import "HTTPLogging.h"
|
||||
|
||||
//-----------------------------------------------------------------
|
||||
#pragma mark log level
|
||||
|
||||
#ifdef DEBUG
|
||||
static const int httpLogLevel = HTTP_LOG_LEVEL_WARN;
|
||||
#else
|
||||
static const int httpLogLevel = HTTP_LOG_LEVEL_WARN;
|
||||
#endif
|
||||
|
||||
//-----------------------------------------------------------------
|
||||
// implementation MultipartMessageHeader
|
||||
//-----------------------------------------------------------------
|
||||
|
||||
|
||||
@implementation MultipartMessageHeader
|
||||
@synthesize fields,encoding;
|
||||
|
||||
|
||||
- (id) initWithData:(NSData *)data formEncoding:(NSStringEncoding) formEncoding {
|
||||
if( nil == (self = [super init]) ) {
|
||||
return self;
|
||||
}
|
||||
|
||||
fields = [[NSMutableDictionary alloc] initWithCapacity:1];
|
||||
|
||||
// In case encoding is not mentioned,
|
||||
encoding = contentTransferEncoding_unknown;
|
||||
|
||||
char* bytes = (char*)data.bytes;
|
||||
NSUInteger length = data.length;
|
||||
int offset = 0;
|
||||
|
||||
// split header into header fields, separated by \r\n
|
||||
uint16_t fields_separator = 0x0A0D; // \r\n
|
||||
while( offset < length - 2 ) {
|
||||
|
||||
// the !isspace condition is to support header unfolding
|
||||
if( (*(uint16_t*) (bytes+offset) == fields_separator) && ((offset == length - 2) || !(isspace(bytes[offset+2])) )) {
|
||||
NSData* fieldData = [NSData dataWithBytesNoCopy:bytes length:offset freeWhenDone:NO];
|
||||
MultipartMessageHeaderField* field = [[MultipartMessageHeaderField alloc] initWithData: fieldData contentEncoding:formEncoding];
|
||||
if( field ) {
|
||||
[fields setObject:field forKey:field.name];
|
||||
HTTPLogVerbose(@"MultipartFormDataParser: Processed Header field '%@'",field.name);
|
||||
}
|
||||
else {
|
||||
NSString* fieldStr = [[NSString alloc] initWithData:fieldData encoding:NSASCIIStringEncoding];
|
||||
HTTPLogWarn(@"MultipartFormDataParser: Failed to parse MIME header field. Input ASCII string:%@",fieldStr);
|
||||
}
|
||||
|
||||
// move to the next header field
|
||||
bytes += offset + 2;
|
||||
length -= offset + 2;
|
||||
offset = 0;
|
||||
continue;
|
||||
}
|
||||
++ offset;
|
||||
}
|
||||
|
||||
if( !fields.count ) {
|
||||
// it was an empty header.
|
||||
// we have to set default values.
|
||||
// default header.
|
||||
[fields setObject:@"text/plain" forKey:@"Content-Type"];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSString *)description {
|
||||
return [NSString stringWithFormat:@"%@",fields];
|
||||
}
|
||||
|
||||
|
||||
@end
|
23
xplan-ios/Base/Tool/CocoaHttpServer/Core/Mime/MultipartMessageHeaderField.h
Executable file
23
xplan-ios/Base/Tool/CocoaHttpServer/Core/Mime/MultipartMessageHeaderField.h
Executable file
@@ -0,0 +1,23 @@
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
//-----------------------------------------------------------------
|
||||
// interface MultipartMessageHeaderField
|
||||
//-----------------------------------------------------------------
|
||||
|
||||
@interface MultipartMessageHeaderField : NSObject {
|
||||
NSString* name;
|
||||
NSString* value;
|
||||
NSMutableDictionary* params;
|
||||
}
|
||||
|
||||
@property (strong, readonly) NSString* value;
|
||||
@property (strong, readonly) NSDictionary* params;
|
||||
@property (strong, readonly) NSString* name;
|
||||
|
||||
//- (id) initWithLine:(NSString*) line;
|
||||
//- (id) initWithName:(NSString*) paramName value:(NSString*) paramValue;
|
||||
|
||||
- (id) initWithData:(NSData*) data contentEncoding:(NSStringEncoding) encoding;
|
||||
|
||||
@end
|
211
xplan-ios/Base/Tool/CocoaHttpServer/Core/Mime/MultipartMessageHeaderField.m
Executable file
211
xplan-ios/Base/Tool/CocoaHttpServer/Core/Mime/MultipartMessageHeaderField.m
Executable file
@@ -0,0 +1,211 @@
|
||||
|
||||
#import "MultipartMessageHeaderField.h"
|
||||
#import "HTTPLogging.h"
|
||||
|
||||
//-----------------------------------------------------------------
|
||||
#pragma mark log level
|
||||
|
||||
#ifdef DEBUG
|
||||
static const int httpLogLevel = HTTP_LOG_LEVEL_WARN;
|
||||
#else
|
||||
static const int httpLogLevel = HTTP_LOG_LEVEL_WARN;
|
||||
#endif
|
||||
|
||||
|
||||
// helpers
|
||||
int findChar(const char* str,NSUInteger length, char c);
|
||||
NSString* extractParamValue(const char* bytes, NSUInteger length, NSStringEncoding encoding);
|
||||
|
||||
//-----------------------------------------------------------------
|
||||
// interface MultipartMessageHeaderField (private)
|
||||
//-----------------------------------------------------------------
|
||||
|
||||
|
||||
@interface MultipartMessageHeaderField (private)
|
||||
-(BOOL) parseHeaderValueBytes:(char*) bytes length:(NSUInteger) length encoding:(NSStringEncoding) encoding;
|
||||
@end
|
||||
|
||||
|
||||
//-----------------------------------------------------------------
|
||||
// implementation MultipartMessageHeaderField
|
||||
//-----------------------------------------------------------------
|
||||
|
||||
@implementation MultipartMessageHeaderField
|
||||
@synthesize name,value,params;
|
||||
|
||||
- (id) initWithData:(NSData *)data contentEncoding:(NSStringEncoding)encoding {
|
||||
params = [[NSMutableDictionary alloc] initWithCapacity:1];
|
||||
|
||||
char* bytes = (char*)data.bytes;
|
||||
NSUInteger length = data.length;
|
||||
|
||||
int separatorOffset = findChar(bytes, length, ':');
|
||||
if( (-1 == separatorOffset) || (separatorOffset >= length-2) ) {
|
||||
HTTPLogError(@"MultipartFormDataParser: Bad format.No colon in field header.");
|
||||
// tear down
|
||||
return nil;
|
||||
}
|
||||
|
||||
// header name is always ascii encoded;
|
||||
name = [[NSString alloc] initWithBytes: bytes length: separatorOffset encoding: NSASCIIStringEncoding];
|
||||
if( nil == name ) {
|
||||
HTTPLogError(@"MultipartFormDataParser: Bad MIME header name.");
|
||||
// tear down
|
||||
return nil;
|
||||
}
|
||||
|
||||
// skip the separator and the next ' ' symbol
|
||||
bytes += separatorOffset + 2;
|
||||
length -= separatorOffset + 2;
|
||||
|
||||
separatorOffset = findChar(bytes, length, ';');
|
||||
if( separatorOffset == -1 ) {
|
||||
// couldn't find ';', means we don't have extra params here.
|
||||
value = [[NSString alloc] initWithBytes:bytes length: length encoding:encoding];
|
||||
|
||||
if( nil == value ) {
|
||||
HTTPLogError(@"MultipartFormDataParser: Bad MIME header value for header name: '%@'",name);
|
||||
// tear down
|
||||
return nil;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
value = [[NSString alloc] initWithBytes:bytes length: separatorOffset encoding:encoding];
|
||||
HTTPLogVerbose(@"MultipartFormDataParser: Processing header field '%@' : '%@'",name,value);
|
||||
// skipe the separator and the next ' ' symbol
|
||||
bytes += separatorOffset + 2;
|
||||
length -= separatorOffset + 2;
|
||||
|
||||
// parse the "params" part of the header
|
||||
if( ![self parseHeaderValueBytes:bytes length:length encoding:encoding] ) {
|
||||
NSString* paramsStr = [[NSString alloc] initWithBytes:bytes length:length encoding:NSASCIIStringEncoding];
|
||||
HTTPLogError(@"MultipartFormDataParser: Bad params for header with name '%@' and value '%@'",name,value);
|
||||
HTTPLogError(@"MultipartFormDataParser: Params str: %@",paramsStr);
|
||||
|
||||
return nil;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
-(BOOL) parseHeaderValueBytes:(char*) bytes length:(NSUInteger) length encoding:(NSStringEncoding) encoding {
|
||||
int offset = 0;
|
||||
NSString* currentParam = nil;
|
||||
BOOL insideQuote = NO;
|
||||
while( offset < length ) {
|
||||
if( bytes[offset] == '\"' ) {
|
||||
if( !offset || bytes[offset-1] != '\\' ) {
|
||||
insideQuote = !insideQuote;
|
||||
}
|
||||
}
|
||||
|
||||
// skip quoted symbols
|
||||
if( insideQuote ) {
|
||||
++ offset;
|
||||
continue;
|
||||
}
|
||||
if( bytes[offset] == '=' ) {
|
||||
if( currentParam ) {
|
||||
// found '=' before terminating previous param.
|
||||
return NO;
|
||||
}
|
||||
currentParam = [[NSString alloc] initWithBytes:bytes length:offset encoding:NSASCIIStringEncoding];
|
||||
|
||||
bytes+=offset + 1;
|
||||
length -= offset + 1;
|
||||
offset = 0;
|
||||
continue;
|
||||
}
|
||||
if( bytes[offset] == ';' ) {
|
||||
if( !currentParam ) {
|
||||
// found ; before stating '='.
|
||||
HTTPLogError(@"MultipartFormDataParser: Unexpected ';' when parsing header");
|
||||
return NO;
|
||||
}
|
||||
NSString* paramValue = extractParamValue(bytes, offset,encoding);
|
||||
if( nil == paramValue ) {
|
||||
HTTPLogWarn(@"MultipartFormDataParser: Failed to exctract paramValue for key %@ in header %@",currentParam,name);
|
||||
}
|
||||
else {
|
||||
#ifdef DEBUG
|
||||
if( [params objectForKey:currentParam] ) {
|
||||
HTTPLogWarn(@"MultipartFormDataParser: param %@ mentioned more then once in header %@",currentParam,name);
|
||||
}
|
||||
#endif
|
||||
[params setObject:paramValue forKey:currentParam];
|
||||
HTTPLogVerbose(@"MultipartFormDataParser: header param: %@ = %@",currentParam,paramValue);
|
||||
}
|
||||
|
||||
currentParam = nil;
|
||||
|
||||
// ';' separator has ' ' following, skip them.
|
||||
bytes+=offset + 2;
|
||||
length -= offset + 2;
|
||||
offset = 0;
|
||||
}
|
||||
++ offset;
|
||||
}
|
||||
|
||||
// add last param
|
||||
if( insideQuote ) {
|
||||
HTTPLogWarn(@"MultipartFormDataParser: unterminated quote in header %@",name);
|
||||
// return YES;
|
||||
}
|
||||
if( currentParam ) {
|
||||
NSString* paramValue = extractParamValue(bytes, length, encoding);
|
||||
|
||||
if( nil == paramValue ) {
|
||||
HTTPLogError(@"MultipartFormDataParser: Failed to exctract paramValue for key %@ in header %@",currentParam,name);
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
if( [params objectForKey:currentParam] ) {
|
||||
HTTPLogWarn(@"MultipartFormDataParser: param %@ mentioned more then once in one header",currentParam);
|
||||
}
|
||||
#endif
|
||||
[params setObject:paramValue?paramValue:@"" forKey:currentParam];
|
||||
HTTPLogVerbose(@"MultipartFormDataParser: header param: %@ = %@",currentParam,paramValue);
|
||||
currentParam = nil;
|
||||
}
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (NSString *)description {
|
||||
return [NSString stringWithFormat:@"%@:%@\n params: %@",name,value,params];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
int findChar(const char* str, NSUInteger length, char c) {
|
||||
int offset = 0;
|
||||
while( offset < length ) {
|
||||
if( str[offset] == c )
|
||||
return offset;
|
||||
++ offset;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
NSString* extractParamValue(const char* bytes, NSUInteger length, NSStringEncoding encoding) {
|
||||
if( !length )
|
||||
return nil;
|
||||
NSMutableString* value = nil;
|
||||
|
||||
if( bytes[0] == '"' ) {
|
||||
// values may be quoted. Strip the quotes to get what we need.
|
||||
value = [[NSMutableString alloc] initWithBytes:bytes + 1 length: length - 2 encoding:encoding];
|
||||
}
|
||||
else {
|
||||
value = [[NSMutableString alloc] initWithBytes:bytes length: length encoding:encoding];
|
||||
}
|
||||
// restore escaped symbols
|
||||
NSRange range= [value rangeOfString:@"\\"];
|
||||
while ( range.length ) {
|
||||
[value deleteCharactersInRange:range];
|
||||
range.location ++;
|
||||
range = [value rangeOfString:@"\\" options:NSLiteralSearch range: range];
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
75
xplan-ios/Base/Tool/CocoaHttpServer/Core/Responses/HTTPAsyncFileResponse.h
Executable file
75
xplan-ios/Base/Tool/CocoaHttpServer/Core/Responses/HTTPAsyncFileResponse.h
Executable file
@@ -0,0 +1,75 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "HTTPResponse.h"
|
||||
|
||||
@class HTTPConnection;
|
||||
|
||||
/**
|
||||
* This is an asynchronous version of HTTPFileResponse.
|
||||
* It reads data from the given file asynchronously via GCD.
|
||||
*
|
||||
* It may be overriden to allow custom post-processing of the data that has been read from the file.
|
||||
* An example of this is the HTTPDynamicFileResponse class.
|
||||
**/
|
||||
|
||||
@interface HTTPAsyncFileResponse : NSObject <HTTPResponse>
|
||||
{
|
||||
HTTPConnection *connection;
|
||||
|
||||
NSString *filePath;
|
||||
UInt64 fileLength;
|
||||
UInt64 fileOffset; // File offset as pertains to data given to connection
|
||||
UInt64 readOffset; // File offset as pertains to data read from file (but maybe not returned to connection)
|
||||
|
||||
BOOL aborted;
|
||||
|
||||
NSData *data;
|
||||
|
||||
int fileFD;
|
||||
void *readBuffer;
|
||||
NSUInteger readBufferSize; // Malloced size of readBuffer
|
||||
NSUInteger readBufferOffset; // Offset within readBuffer where the end of existing data is
|
||||
NSUInteger readRequestLength;
|
||||
dispatch_queue_t readQueue;
|
||||
dispatch_source_t readSource;
|
||||
BOOL readSourceSuspended;
|
||||
}
|
||||
|
||||
- (id)initWithFilePath:(NSString *)filePath forConnection:(HTTPConnection *)connection;
|
||||
- (NSString *)filePath;
|
||||
|
||||
@end
|
||||
|
||||
/**
|
||||
* Explanation of Variables (excluding those that are obvious)
|
||||
*
|
||||
* fileOffset
|
||||
* This is the number of bytes that have been returned to the connection via the readDataOfLength method.
|
||||
* If 1KB of data has been read from the file, but none of that data has yet been returned to the connection,
|
||||
* then the fileOffset variable remains at zero.
|
||||
* This variable is used in the calculation of the isDone method.
|
||||
* Only after all data has been returned to the connection are we actually done.
|
||||
*
|
||||
* readOffset
|
||||
* Represents the offset of the file descriptor.
|
||||
* In other words, the file position indidcator for our read stream.
|
||||
* It might be easy to think of it as the total number of bytes that have been read from the file.
|
||||
* However, this isn't entirely accurate, as the setOffset: method may have caused us to
|
||||
* jump ahead in the file (lseek).
|
||||
*
|
||||
* readBuffer
|
||||
* Malloc'd buffer to hold data read from the file.
|
||||
*
|
||||
* readBufferSize
|
||||
* Total allocation size of malloc'd buffer.
|
||||
*
|
||||
* readBufferOffset
|
||||
* Represents the position in the readBuffer where we should store new bytes.
|
||||
*
|
||||
* readRequestLength
|
||||
* The total number of bytes that were requested from the connection.
|
||||
* It's OK if we return a lesser number of bytes to the connection.
|
||||
* It's NOT OK if we return a greater number of bytes to the connection.
|
||||
* Doing so would disrupt proper support for range requests.
|
||||
* If, however, the response is chunked then we don't need to worry about this.
|
||||
* Chunked responses inheritly don't support range requests.
|
||||
**/
|
405
xplan-ios/Base/Tool/CocoaHttpServer/Core/Responses/HTTPAsyncFileResponse.m
Executable file
405
xplan-ios/Base/Tool/CocoaHttpServer/Core/Responses/HTTPAsyncFileResponse.m
Executable file
@@ -0,0 +1,405 @@
|
||||
#import "HTTPAsyncFileResponse.h"
|
||||
#import "HTTPConnection.h"
|
||||
#import "HTTPLogging.h"
|
||||
|
||||
#import <unistd.h>
|
||||
#import <fcntl.h>
|
||||
|
||||
#if ! __has_feature(objc_arc)
|
||||
#warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
|
||||
#endif
|
||||
|
||||
// Log levels : off, error, warn, info, verbose
|
||||
// Other flags: trace
|
||||
static const int httpLogLevel = HTTP_LOG_LEVEL_WARN; // | HTTP_LOG_FLAG_TRACE;
|
||||
|
||||
#define NULL_FD -1
|
||||
|
||||
/**
|
||||
* Architecure overview:
|
||||
*
|
||||
* HTTPConnection will invoke our readDataOfLength: method to fetch data.
|
||||
* We will return nil, and then proceed to read the data via our readSource on our readQueue.
|
||||
* Once the requested amount of data has been read, we then pause our readSource,
|
||||
* and inform the connection of the available data.
|
||||
*
|
||||
* While our read is in progress, we don't have to worry about the connection calling any other methods,
|
||||
* except the connectionDidClose method, which would be invoked if the remote end closed the socket connection.
|
||||
* To safely handle this, we do a synchronous dispatch on the readQueue,
|
||||
* and nilify the connection as well as cancel our readSource.
|
||||
*
|
||||
* In order to minimize resource consumption during a HEAD request,
|
||||
* we don't open the file until we have to (until the connection starts requesting data).
|
||||
**/
|
||||
|
||||
@implementation HTTPAsyncFileResponse
|
||||
|
||||
- (id)initWithFilePath:(NSString *)fpath forConnection:(HTTPConnection *)parent
|
||||
{
|
||||
if ((self = [super init]))
|
||||
{
|
||||
HTTPLogTrace();
|
||||
|
||||
connection = parent; // Parents retain children, children do NOT retain parents
|
||||
|
||||
fileFD = NULL_FD;
|
||||
filePath = [fpath copy];
|
||||
if (filePath == nil)
|
||||
{
|
||||
HTTPLogWarn(@"%@: Init failed - Nil filePath", THIS_FILE);
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSDictionary *fileAttributes = [[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:NULL];
|
||||
if (fileAttributes == nil)
|
||||
{
|
||||
HTTPLogWarn(@"%@: Init failed - Unable to get file attributes. filePath: %@", THIS_FILE, filePath);
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
fileLength = (UInt64)[[fileAttributes objectForKey:NSFileSize] unsignedLongLongValue];
|
||||
fileOffset = 0;
|
||||
|
||||
aborted = NO;
|
||||
|
||||
// We don't bother opening the file here.
|
||||
// If this is a HEAD request we only need to know the fileLength.
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)abort
|
||||
{
|
||||
HTTPLogTrace();
|
||||
|
||||
[connection responseDidAbort:self];
|
||||
aborted = YES;
|
||||
}
|
||||
|
||||
- (void)processReadBuffer
|
||||
{
|
||||
// This method is here to allow superclasses to perform post-processing of the data.
|
||||
// For an example, see the HTTPDynamicFileResponse class.
|
||||
//
|
||||
// At this point, the readBuffer has readBufferOffset bytes available.
|
||||
// This method is in charge of updating the readBufferOffset.
|
||||
// Failure to do so will cause the readBuffer to grow to fileLength. (Imagine a 1 GB file...)
|
||||
|
||||
// Copy the data out of the temporary readBuffer.
|
||||
data = [[NSData alloc] initWithBytes:readBuffer length:readBufferOffset];
|
||||
|
||||
// Reset the read buffer.
|
||||
readBufferOffset = 0;
|
||||
|
||||
// Notify the connection that we have data available for it.
|
||||
[connection responseHasAvailableData:self];
|
||||
}
|
||||
|
||||
- (void)pauseReadSource
|
||||
{
|
||||
if (!readSourceSuspended)
|
||||
{
|
||||
HTTPLogVerbose(@"%@[%p]: Suspending readSource", THIS_FILE, self);
|
||||
|
||||
readSourceSuspended = YES;
|
||||
dispatch_suspend(readSource);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)resumeReadSource
|
||||
{
|
||||
if (readSourceSuspended)
|
||||
{
|
||||
HTTPLogVerbose(@"%@[%p]: Resuming readSource", THIS_FILE, self);
|
||||
|
||||
readSourceSuspended = NO;
|
||||
dispatch_resume(readSource);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)cancelReadSource
|
||||
{
|
||||
HTTPLogVerbose(@"%@[%p]: Canceling readSource", THIS_FILE, self);
|
||||
|
||||
dispatch_source_cancel(readSource);
|
||||
|
||||
// Cancelling a dispatch source doesn't
|
||||
// invoke the cancel handler if the dispatch source is paused.
|
||||
|
||||
if (readSourceSuspended)
|
||||
{
|
||||
readSourceSuspended = NO;
|
||||
dispatch_resume(readSource);
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)openFileAndSetupReadSource
|
||||
{
|
||||
HTTPLogTrace();
|
||||
|
||||
fileFD = open([filePath UTF8String], (O_RDONLY | O_NONBLOCK));
|
||||
if (fileFD == NULL_FD)
|
||||
{
|
||||
HTTPLogError(@"%@: Unable to open file. filePath: %@", THIS_FILE, filePath);
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
HTTPLogVerbose(@"%@[%p]: Open fd[%i] -> %@", THIS_FILE, self, fileFD, filePath);
|
||||
|
||||
readQueue = dispatch_queue_create("HTTPAsyncFileResponse", NULL);
|
||||
readSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, fileFD, 0, readQueue);
|
||||
|
||||
|
||||
dispatch_source_set_event_handler(readSource, ^{
|
||||
|
||||
HTTPLogTrace2(@"%@: eventBlock - fd[%i]", THIS_FILE, fileFD);
|
||||
|
||||
// Determine how much data we should read.
|
||||
//
|
||||
// It is OK if we ask to read more bytes than exist in the file.
|
||||
// It is NOT OK to over-allocate the buffer.
|
||||
|
||||
unsigned long long _bytesAvailableOnFD = dispatch_source_get_data(readSource);
|
||||
|
||||
UInt64 _bytesLeftInFile = fileLength - readOffset;
|
||||
|
||||
NSUInteger bytesAvailableOnFD;
|
||||
NSUInteger bytesLeftInFile;
|
||||
|
||||
bytesAvailableOnFD = (_bytesAvailableOnFD > NSUIntegerMax) ? NSUIntegerMax : (NSUInteger)_bytesAvailableOnFD;
|
||||
bytesLeftInFile = (_bytesLeftInFile > NSUIntegerMax) ? NSUIntegerMax : (NSUInteger)_bytesLeftInFile;
|
||||
|
||||
NSUInteger bytesLeftInRequest = readRequestLength - readBufferOffset;
|
||||
|
||||
NSUInteger bytesLeft = MIN(bytesLeftInRequest, bytesLeftInFile);
|
||||
|
||||
NSUInteger bytesToRead = MIN(bytesAvailableOnFD, bytesLeft);
|
||||
|
||||
// Make sure buffer is big enough for read request.
|
||||
// Do not over-allocate.
|
||||
|
||||
if (readBuffer == NULL || bytesToRead > (readBufferSize - readBufferOffset))
|
||||
{
|
||||
readBufferSize = bytesToRead;
|
||||
readBuffer = reallocf(readBuffer, (size_t)bytesToRead);
|
||||
|
||||
if (readBuffer == NULL)
|
||||
{
|
||||
HTTPLogError(@"%@[%p]: Unable to allocate buffer", THIS_FILE, self);
|
||||
|
||||
[self pauseReadSource];
|
||||
[self abort];
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Perform the read
|
||||
|
||||
HTTPLogVerbose(@"%@[%p]: Attempting to read %lu bytes from file", THIS_FILE, self, (unsigned long)bytesToRead);
|
||||
|
||||
ssize_t result = read(fileFD, readBuffer + readBufferOffset, (size_t)bytesToRead);
|
||||
|
||||
// Check the results
|
||||
if (result < 0)
|
||||
{
|
||||
HTTPLogError(@"%@: Error(%i) reading file(%@)", THIS_FILE, errno, filePath);
|
||||
|
||||
[self pauseReadSource];
|
||||
[self abort];
|
||||
}
|
||||
else if (result == 0)
|
||||
{
|
||||
HTTPLogError(@"%@: Read EOF on file(%@)", THIS_FILE, filePath);
|
||||
|
||||
[self pauseReadSource];
|
||||
[self abort];
|
||||
}
|
||||
else // (result > 0)
|
||||
{
|
||||
HTTPLogVerbose(@"%@[%p]: Read %lu bytes from file", THIS_FILE, self, (unsigned long)result);
|
||||
|
||||
readOffset += result;
|
||||
readBufferOffset += result;
|
||||
|
||||
[self pauseReadSource];
|
||||
[self processReadBuffer];
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
int theFileFD = fileFD;
|
||||
#if !OS_OBJECT_USE_OBJC
|
||||
dispatch_source_t theReadSource = readSource;
|
||||
#endif
|
||||
|
||||
dispatch_source_set_cancel_handler(readSource, ^{
|
||||
|
||||
// Do not access self from within this block in any way, shape or form.
|
||||
//
|
||||
// Note: You access self if you reference an iVar.
|
||||
|
||||
HTTPLogTrace2(@"%@: cancelBlock - Close fd[%i]", THIS_FILE, theFileFD);
|
||||
|
||||
#if !OS_OBJECT_USE_OBJC
|
||||
dispatch_release(theReadSource);
|
||||
#endif
|
||||
close(theFileFD);
|
||||
});
|
||||
|
||||
readSourceSuspended = YES;
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)openFileIfNeeded
|
||||
{
|
||||
if (aborted)
|
||||
{
|
||||
// The file operation has been aborted.
|
||||
// This could be because we failed to open the file,
|
||||
// or the reading process failed.
|
||||
return NO;
|
||||
}
|
||||
|
||||
if (fileFD != NULL_FD)
|
||||
{
|
||||
// File has already been opened.
|
||||
return YES;
|
||||
}
|
||||
|
||||
return [self openFileAndSetupReadSource];
|
||||
}
|
||||
|
||||
- (UInt64)contentLength
|
||||
{
|
||||
HTTPLogTrace2(@"%@[%p]: contentLength - %llu", THIS_FILE, self, fileLength);
|
||||
|
||||
return fileLength;
|
||||
}
|
||||
|
||||
- (UInt64)offset
|
||||
{
|
||||
HTTPLogTrace();
|
||||
|
||||
return fileOffset;
|
||||
}
|
||||
|
||||
- (void)setOffset:(UInt64)offset
|
||||
{
|
||||
HTTPLogTrace2(@"%@[%p]: setOffset:%llu", THIS_FILE, self, offset);
|
||||
|
||||
if (![self openFileIfNeeded])
|
||||
{
|
||||
// File opening failed,
|
||||
// or response has been aborted due to another error.
|
||||
return;
|
||||
}
|
||||
|
||||
fileOffset = offset;
|
||||
readOffset = offset;
|
||||
|
||||
off_t result = lseek(fileFD, (off_t)offset, SEEK_SET);
|
||||
if (result == -1)
|
||||
{
|
||||
HTTPLogError(@"%@[%p]: lseek failed - errno(%i) filePath(%@)", THIS_FILE, self, errno, filePath);
|
||||
|
||||
[self abort];
|
||||
}
|
||||
}
|
||||
|
||||
- (NSData *)readDataOfLength:(NSUInteger)length
|
||||
{
|
||||
HTTPLogTrace2(@"%@[%p]: readDataOfLength:%lu", THIS_FILE, self, (unsigned long)length);
|
||||
|
||||
if (data)
|
||||
{
|
||||
NSUInteger dataLength = [data length];
|
||||
|
||||
HTTPLogVerbose(@"%@[%p]: Returning data of length %lu", THIS_FILE, self, (unsigned long)dataLength);
|
||||
|
||||
fileOffset += dataLength;
|
||||
|
||||
NSData *result = data;
|
||||
data = nil;
|
||||
|
||||
return result;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (![self openFileIfNeeded])
|
||||
{
|
||||
// File opening failed,
|
||||
// or response has been aborted due to another error.
|
||||
return nil;
|
||||
}
|
||||
|
||||
dispatch_sync(readQueue, ^{
|
||||
|
||||
NSAssert(readSourceSuspended, @"Invalid logic - perhaps HTTPConnection has changed.");
|
||||
|
||||
readRequestLength = length;
|
||||
[self resumeReadSource];
|
||||
});
|
||||
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)isDone
|
||||
{
|
||||
BOOL result = (fileOffset == fileLength);
|
||||
|
||||
HTTPLogTrace2(@"%@[%p]: isDone - %@", THIS_FILE, self, (result ? @"YES" : @"NO"));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
- (NSString *)filePath
|
||||
{
|
||||
return filePath;
|
||||
}
|
||||
|
||||
- (BOOL)isAsynchronous
|
||||
{
|
||||
HTTPLogTrace();
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)connectionDidClose
|
||||
{
|
||||
HTTPLogTrace();
|
||||
|
||||
if (fileFD != NULL_FD)
|
||||
{
|
||||
dispatch_sync(readQueue, ^{
|
||||
|
||||
// Prevent any further calls to the connection
|
||||
connection = nil;
|
||||
|
||||
// Cancel the readSource.
|
||||
// We do this here because the readSource's eventBlock has retained self.
|
||||
// In other words, if we don't cancel the readSource, we will never get deallocated.
|
||||
|
||||
[self cancelReadSource];
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
HTTPLogTrace();
|
||||
|
||||
#if !OS_OBJECT_USE_OBJC
|
||||
if (readQueue) dispatch_release(readQueue);
|
||||
#endif
|
||||
|
||||
if (readBuffer)
|
||||
free(readBuffer);
|
||||
}
|
||||
|
||||
@end
|
13
xplan-ios/Base/Tool/CocoaHttpServer/Core/Responses/HTTPDataResponse.h
Executable file
13
xplan-ios/Base/Tool/CocoaHttpServer/Core/Responses/HTTPDataResponse.h
Executable file
@@ -0,0 +1,13 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "HTTPResponse.h"
|
||||
|
||||
|
||||
@interface HTTPDataResponse : NSObject <HTTPResponse>
|
||||
{
|
||||
NSUInteger offset;
|
||||
NSData *data;
|
||||
}
|
||||
|
||||
- (id)initWithData:(NSData *)data;
|
||||
|
||||
@end
|
79
xplan-ios/Base/Tool/CocoaHttpServer/Core/Responses/HTTPDataResponse.m
Executable file
79
xplan-ios/Base/Tool/CocoaHttpServer/Core/Responses/HTTPDataResponse.m
Executable file
@@ -0,0 +1,79 @@
|
||||
#import "HTTPDataResponse.h"
|
||||
#import "HTTPLogging.h"
|
||||
|
||||
#if ! __has_feature(objc_arc)
|
||||
#warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
|
||||
#endif
|
||||
|
||||
// Log levels : off, error, warn, info, verbose
|
||||
// Other flags: trace
|
||||
static const int httpLogLevel = HTTP_LOG_LEVEL_OFF; // | HTTP_LOG_FLAG_TRACE;
|
||||
|
||||
|
||||
@implementation HTTPDataResponse
|
||||
|
||||
- (id)initWithData:(NSData *)dataParam
|
||||
{
|
||||
if((self = [super init]))
|
||||
{
|
||||
HTTPLogTrace();
|
||||
|
||||
offset = 0;
|
||||
data = dataParam;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
HTTPLogTrace();
|
||||
|
||||
}
|
||||
|
||||
- (UInt64)contentLength
|
||||
{
|
||||
UInt64 result = (UInt64)[data length];
|
||||
|
||||
HTTPLogTrace2(@"%@[%p]: contentLength - %llu", THIS_FILE, self, result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
- (UInt64)offset
|
||||
{
|
||||
HTTPLogTrace();
|
||||
|
||||
return offset;
|
||||
}
|
||||
|
||||
- (void)setOffset:(UInt64)offsetParam
|
||||
{
|
||||
HTTPLogTrace2(@"%@[%p]: setOffset:%lu", THIS_FILE, self, (unsigned long)offset);
|
||||
|
||||
offset = (NSUInteger)offsetParam;
|
||||
}
|
||||
|
||||
- (NSData *)readDataOfLength:(NSUInteger)lengthParameter
|
||||
{
|
||||
HTTPLogTrace2(@"%@[%p]: readDataOfLength:%lu", THIS_FILE, self, (unsigned long)lengthParameter);
|
||||
|
||||
NSUInteger remaining = [data length] - offset;
|
||||
NSUInteger length = lengthParameter < remaining ? lengthParameter : remaining;
|
||||
|
||||
void *bytes = (void *)([data bytes] + offset);
|
||||
|
||||
offset += length;
|
||||
|
||||
return [NSData dataWithBytesNoCopy:bytes length:length freeWhenDone:NO];
|
||||
}
|
||||
|
||||
- (BOOL)isDone
|
||||
{
|
||||
BOOL result = (offset == [data length]);
|
||||
|
||||
HTTPLogTrace2(@"%@[%p]: isDone - %@", THIS_FILE, self, (result ? @"YES" : @"NO"));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@end
|
52
xplan-ios/Base/Tool/CocoaHttpServer/Core/Responses/HTTPDynamicFileResponse.h
Executable file
52
xplan-ios/Base/Tool/CocoaHttpServer/Core/Responses/HTTPDynamicFileResponse.h
Executable file
@@ -0,0 +1,52 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "HTTPResponse.h"
|
||||
#import "HTTPAsyncFileResponse.h"
|
||||
|
||||
/**
|
||||
* This class is designed to assist with dynamic content.
|
||||
* Imagine you have a file that you want to make dynamic:
|
||||
*
|
||||
* <html>
|
||||
* <body>
|
||||
* <h1>ComputerName Control Panel</h1>
|
||||
* ...
|
||||
* <li>System Time: SysTime</li>
|
||||
* </body>
|
||||
* </html>
|
||||
*
|
||||
* Now you could generate the entire file in Objective-C,
|
||||
* but this would be a horribly tedious process.
|
||||
* Beside, you want to design the file with professional tools to make it look pretty.
|
||||
*
|
||||
* So all you have to do is escape your dynamic content like this:
|
||||
*
|
||||
* ...
|
||||
* <h1>%%ComputerName%% Control Panel</h1>
|
||||
* ...
|
||||
* <li>System Time: %%SysTime%%</li>
|
||||
*
|
||||
* And then you create an instance of this class with:
|
||||
*
|
||||
* - separator = @"%%"
|
||||
* - replacementDictionary = { "ComputerName"="Black MacBook", "SysTime"="2010-04-30 03:18:24" }
|
||||
*
|
||||
* This class will then perform the replacements for you, on the fly, as it reads the file data.
|
||||
* This class is also asynchronous, so it will perform the file IO using its own GCD queue.
|
||||
*
|
||||
* All keys for the replacementDictionary must be NSString's.
|
||||
* Values for the replacementDictionary may be NSString's, or any object that
|
||||
* returns what you want when its description method is invoked.
|
||||
**/
|
||||
|
||||
@interface HTTPDynamicFileResponse : HTTPAsyncFileResponse
|
||||
{
|
||||
NSData *separator;
|
||||
NSDictionary *replacementDict;
|
||||
}
|
||||
|
||||
- (id)initWithFilePath:(NSString *)filePath
|
||||
forConnection:(HTTPConnection *)connection
|
||||
separator:(NSString *)separatorStr
|
||||
replacementDictionary:(NSDictionary *)dictionary;
|
||||
|
||||
@end
|
292
xplan-ios/Base/Tool/CocoaHttpServer/Core/Responses/HTTPDynamicFileResponse.m
Executable file
292
xplan-ios/Base/Tool/CocoaHttpServer/Core/Responses/HTTPDynamicFileResponse.m
Executable file
@@ -0,0 +1,292 @@
|
||||
#import "HTTPDynamicFileResponse.h"
|
||||
#import "HTTPConnection.h"
|
||||
#import "HTTPLogging.h"
|
||||
|
||||
#if ! __has_feature(objc_arc)
|
||||
#warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
|
||||
#endif
|
||||
|
||||
// Log levels : off, error, warn, info, verbose
|
||||
// Other flags: trace
|
||||
static const int httpLogLevel = HTTP_LOG_LEVEL_WARN; // | HTTP_LOG_FLAG_TRACE;
|
||||
|
||||
#define NULL_FD -1
|
||||
|
||||
|
||||
@implementation HTTPDynamicFileResponse
|
||||
|
||||
- (id)initWithFilePath:(NSString *)fpath
|
||||
forConnection:(HTTPConnection *)parent
|
||||
separator:(NSString *)separatorStr
|
||||
replacementDictionary:(NSDictionary *)dict
|
||||
{
|
||||
if ((self = [super initWithFilePath:fpath forConnection:parent]))
|
||||
{
|
||||
HTTPLogTrace();
|
||||
|
||||
separator = [separatorStr dataUsingEncoding:NSUTF8StringEncoding];
|
||||
replacementDict = dict;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (BOOL)isChunked
|
||||
{
|
||||
HTTPLogTrace();
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (UInt64)contentLength
|
||||
{
|
||||
// This method shouldn't be called since we're using a chunked response.
|
||||
// We override it just to be safe.
|
||||
|
||||
HTTPLogTrace();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
- (void)setOffset:(UInt64)offset
|
||||
{
|
||||
// This method shouldn't be called since we're using a chunked response.
|
||||
// We override it just to be safe.
|
||||
|
||||
HTTPLogTrace();
|
||||
}
|
||||
|
||||
- (BOOL)isDone
|
||||
{
|
||||
BOOL result = (readOffset == fileLength) && (readBufferOffset == 0);
|
||||
|
||||
HTTPLogTrace2(@"%@[%p]: isDone - %@", THIS_FILE, self, (result ? @"YES" : @"NO"));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
- (void)processReadBuffer
|
||||
{
|
||||
HTTPLogTrace();
|
||||
|
||||
// At this point, the readBuffer has readBufferOffset bytes available.
|
||||
// This method is in charge of updating the readBufferOffset.
|
||||
|
||||
NSUInteger bufLen = readBufferOffset;
|
||||
NSUInteger sepLen = [separator length];
|
||||
|
||||
// We're going to start looking for the separator at the beginning of the buffer,
|
||||
// and stop when we get to the point where the separator would no longer fit in the buffer.
|
||||
|
||||
NSUInteger offset = 0;
|
||||
NSUInteger stopOffset = (bufLen > sepLen) ? bufLen - sepLen + 1 : 0;
|
||||
|
||||
// In order to do the replacement, we need to find the starting and ending separator.
|
||||
// For example:
|
||||
//
|
||||
// %%USER_NAME%%
|
||||
//
|
||||
// Where "%%" is the separator.
|
||||
|
||||
BOOL found1 = NO;
|
||||
BOOL found2 = NO;
|
||||
|
||||
NSUInteger s1 = 0;
|
||||
NSUInteger s2 = 0;
|
||||
|
||||
const void *sep = [separator bytes];
|
||||
|
||||
while (offset < stopOffset)
|
||||
{
|
||||
const void *subBuffer = readBuffer + offset;
|
||||
|
||||
if (memcmp(subBuffer, sep, sepLen) == 0)
|
||||
{
|
||||
if (!found1)
|
||||
{
|
||||
// Found the first separator
|
||||
|
||||
found1 = YES;
|
||||
s1 = offset;
|
||||
offset += sepLen;
|
||||
|
||||
HTTPLogVerbose(@"%@[%p]: Found s1 at %lu", THIS_FILE, self, (unsigned long)s1);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Found the second separator
|
||||
|
||||
found2 = YES;
|
||||
s2 = offset;
|
||||
offset += sepLen;
|
||||
|
||||
HTTPLogVerbose(@"%@[%p]: Found s2 at %lu", THIS_FILE, self, (unsigned long)s2);
|
||||
}
|
||||
|
||||
if (found1 && found2)
|
||||
{
|
||||
// We found our separators.
|
||||
// Now extract the string between the two separators.
|
||||
|
||||
NSRange fullRange = NSMakeRange(s1, (s2 - s1 + sepLen));
|
||||
NSRange strRange = NSMakeRange(s1 + sepLen, (s2 - s1 - sepLen));
|
||||
|
||||
// Wish we could use the simple subdataWithRange method.
|
||||
// But that method copies the bytes...
|
||||
// So for performance reasons, we need to use the methods that don't copy the bytes.
|
||||
|
||||
void *strBuf = readBuffer + strRange.location;
|
||||
NSUInteger strLen = strRange.length;
|
||||
|
||||
NSString *key = [[NSString alloc] initWithBytes:strBuf length:strLen encoding:NSUTF8StringEncoding];
|
||||
if (key)
|
||||
{
|
||||
// Is there a given replacement for this key?
|
||||
|
||||
id value = [replacementDict objectForKey:key];
|
||||
if (value)
|
||||
{
|
||||
// Found the replacement value.
|
||||
// Now perform the replacement in the buffer.
|
||||
|
||||
HTTPLogVerbose(@"%@[%p]: key(%@) -> value(%@)", THIS_FILE, self, key, value);
|
||||
|
||||
NSData *v = [[value description] dataUsingEncoding:NSUTF8StringEncoding];
|
||||
NSUInteger vLength = [v length];
|
||||
|
||||
if (fullRange.length == vLength)
|
||||
{
|
||||
// Replacement is exactly the same size as what it is replacing
|
||||
|
||||
// memcpy(void *restrict dst, const void *restrict src, size_t n);
|
||||
|
||||
memcpy(readBuffer + fullRange.location, [v bytes], vLength);
|
||||
}
|
||||
else // (fullRange.length != vLength)
|
||||
{
|
||||
NSInteger diff = (NSInteger)vLength - (NSInteger)fullRange.length;
|
||||
|
||||
if (diff > 0)
|
||||
{
|
||||
// Replacement is bigger than what it is replacing.
|
||||
// Make sure there is room in the buffer for the replacement.
|
||||
|
||||
if (diff > (readBufferSize - bufLen))
|
||||
{
|
||||
NSUInteger inc = MAX(diff, 256);
|
||||
|
||||
readBufferSize += inc;
|
||||
readBuffer = reallocf(readBuffer, readBufferSize);
|
||||
}
|
||||
}
|
||||
|
||||
// Move the data that comes after the replacement.
|
||||
//
|
||||
// If replacement is smaller than what it is replacing,
|
||||
// then we are shifting the data toward the beginning of the buffer.
|
||||
//
|
||||
// If replacement is bigger than what it is replacing,
|
||||
// then we are shifting the data toward the end of the buffer.
|
||||
//
|
||||
// memmove(void *dst, const void *src, size_t n);
|
||||
//
|
||||
// The memmove() function copies n bytes from src to dst.
|
||||
// The two areas may overlap; the copy is always done in a non-destructive manner.
|
||||
|
||||
void *src = readBuffer + fullRange.location + fullRange.length;
|
||||
void *dst = readBuffer + fullRange.location + vLength;
|
||||
|
||||
NSUInteger remaining = bufLen - (fullRange.location + fullRange.length);
|
||||
|
||||
memmove(dst, src, remaining);
|
||||
|
||||
// Now copy the replacement into its location.
|
||||
//
|
||||
// memcpy(void *restrict dst, const void *restrict src, size_t n)
|
||||
//
|
||||
// The memcpy() function copies n bytes from src to dst.
|
||||
// If the two areas overlap, behavior is undefined.
|
||||
|
||||
memcpy(readBuffer + fullRange.location, [v bytes], vLength);
|
||||
|
||||
// And don't forget to update our indices.
|
||||
|
||||
bufLen += diff;
|
||||
offset += diff;
|
||||
stopOffset += diff;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
found1 = found2 = NO;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
offset++;
|
||||
}
|
||||
}
|
||||
|
||||
// We've gone through our buffer now, and performed all the replacements that we could.
|
||||
// It's now time to update the amount of available data we have.
|
||||
|
||||
if (readOffset == fileLength)
|
||||
{
|
||||
// We've read in the entire file.
|
||||
// So there can be no more replacements.
|
||||
|
||||
data = [[NSData alloc] initWithBytes:readBuffer length:bufLen];
|
||||
readBufferOffset = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
// There are a couple different situations that we need to take into account here.
|
||||
//
|
||||
// Imagine the following file:
|
||||
// My name is %%USER_NAME%%
|
||||
//
|
||||
// Situation 1:
|
||||
// The first chunk of data we read was "My name is %%".
|
||||
// So we found the first separator, but not the second.
|
||||
// In this case we can only return the data that precedes the first separator.
|
||||
//
|
||||
// Situation 2:
|
||||
// The first chunk of data we read was "My name is %".
|
||||
// So we didn't find any separators, but part of a separator may be included in our buffer.
|
||||
|
||||
NSUInteger available;
|
||||
if (found1)
|
||||
{
|
||||
// Situation 1
|
||||
available = s1;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Situation 2
|
||||
available = stopOffset;
|
||||
}
|
||||
|
||||
// Copy available data
|
||||
|
||||
data = [[NSData alloc] initWithBytes:readBuffer length:available];
|
||||
|
||||
// Remove the copied data from the buffer.
|
||||
// We do this by shifting the remaining data toward the beginning of the buffer.
|
||||
|
||||
NSUInteger remaining = bufLen - available;
|
||||
|
||||
memmove(readBuffer, readBuffer + available, remaining);
|
||||
readBufferOffset = remaining;
|
||||
}
|
||||
|
||||
[connection responseHasAvailableData:self];
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
HTTPLogTrace();
|
||||
|
||||
|
||||
}
|
||||
|
||||
@end
|
9
xplan-ios/Base/Tool/CocoaHttpServer/Core/Responses/HTTPErrorResponse.h
Executable file
9
xplan-ios/Base/Tool/CocoaHttpServer/Core/Responses/HTTPErrorResponse.h
Executable file
@@ -0,0 +1,9 @@
|
||||
#import "HTTPResponse.h"
|
||||
|
||||
@interface HTTPErrorResponse : NSObject <HTTPResponse> {
|
||||
NSInteger _status;
|
||||
}
|
||||
|
||||
- (id)initWithErrorCode:(int)httpErrorCode;
|
||||
|
||||
@end
|
38
xplan-ios/Base/Tool/CocoaHttpServer/Core/Responses/HTTPErrorResponse.m
Executable file
38
xplan-ios/Base/Tool/CocoaHttpServer/Core/Responses/HTTPErrorResponse.m
Executable file
@@ -0,0 +1,38 @@
|
||||
#import "HTTPErrorResponse.h"
|
||||
|
||||
@implementation HTTPErrorResponse
|
||||
|
||||
-(id)initWithErrorCode:(int)httpErrorCode
|
||||
{
|
||||
if ((self = [super init]))
|
||||
{
|
||||
_status = httpErrorCode;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (UInt64) contentLength {
|
||||
return 0;
|
||||
}
|
||||
|
||||
- (UInt64) offset {
|
||||
return 0;
|
||||
}
|
||||
|
||||
- (void)setOffset:(UInt64)offset {
|
||||
;
|
||||
}
|
||||
|
||||
- (NSData*) readDataOfLength:(NSUInteger)length {
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (BOOL) isDone {
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (NSInteger) status {
|
||||
return _status;
|
||||
}
|
||||
@end
|
25
xplan-ios/Base/Tool/CocoaHttpServer/Core/Responses/HTTPFileResponse.h
Executable file
25
xplan-ios/Base/Tool/CocoaHttpServer/Core/Responses/HTTPFileResponse.h
Executable file
@@ -0,0 +1,25 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "HTTPResponse.h"
|
||||
|
||||
@class HTTPConnection;
|
||||
|
||||
|
||||
@interface HTTPFileResponse : NSObject <HTTPResponse>
|
||||
{
|
||||
HTTPConnection *connection;
|
||||
|
||||
NSString *filePath;
|
||||
UInt64 fileLength;
|
||||
UInt64 fileOffset;
|
||||
|
||||
BOOL aborted;
|
||||
|
||||
int fileFD;
|
||||
void *buffer;
|
||||
NSUInteger bufferSize;
|
||||
}
|
||||
|
||||
- (id)initWithFilePath:(NSString *)filePath forConnection:(HTTPConnection *)connection;
|
||||
- (NSString *)filePath;
|
||||
|
||||
@end
|
237
xplan-ios/Base/Tool/CocoaHttpServer/Core/Responses/HTTPFileResponse.m
Executable file
237
xplan-ios/Base/Tool/CocoaHttpServer/Core/Responses/HTTPFileResponse.m
Executable file
@@ -0,0 +1,237 @@
|
||||
#import "HTTPFileResponse.h"
|
||||
#import "HTTPConnection.h"
|
||||
#import "HTTPLogging.h"
|
||||
|
||||
#import <unistd.h>
|
||||
#import <fcntl.h>
|
||||
|
||||
#if ! __has_feature(objc_arc)
|
||||
#warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
|
||||
#endif
|
||||
|
||||
// Log levels : off, error, warn, info, verbose
|
||||
// Other flags: trace
|
||||
static const int httpLogLevel = HTTP_LOG_LEVEL_WARN; // | HTTP_LOG_FLAG_TRACE;
|
||||
|
||||
#define NULL_FD -1
|
||||
|
||||
|
||||
@implementation HTTPFileResponse
|
||||
|
||||
- (id)initWithFilePath:(NSString *)fpath forConnection:(HTTPConnection *)parent
|
||||
{
|
||||
if((self = [super init]))
|
||||
{
|
||||
HTTPLogTrace();
|
||||
|
||||
connection = parent; // Parents retain children, children do NOT retain parents
|
||||
|
||||
fileFD = NULL_FD;
|
||||
filePath = [[fpath copy] stringByResolvingSymlinksInPath];
|
||||
if (filePath == nil)
|
||||
{
|
||||
HTTPLogWarn(@"%@: Init failed - Nil filePath", THIS_FILE);
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSDictionary *fileAttributes = [[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:nil];
|
||||
if (fileAttributes == nil)
|
||||
{
|
||||
HTTPLogWarn(@"%@: Init failed - Unable to get file attributes. filePath: %@", THIS_FILE, filePath);
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
fileLength = (UInt64)[[fileAttributes objectForKey:NSFileSize] unsignedLongLongValue];
|
||||
fileOffset = 0;
|
||||
|
||||
aborted = NO;
|
||||
|
||||
// We don't bother opening the file here.
|
||||
// If this is a HEAD request we only need to know the fileLength.
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)abort
|
||||
{
|
||||
HTTPLogTrace();
|
||||
|
||||
[connection responseDidAbort:self];
|
||||
aborted = YES;
|
||||
}
|
||||
|
||||
- (BOOL)openFile
|
||||
{
|
||||
HTTPLogTrace();
|
||||
|
||||
fileFD = open([filePath UTF8String], O_RDONLY);
|
||||
if (fileFD == NULL_FD)
|
||||
{
|
||||
HTTPLogError(@"%@[%p]: Unable to open file. filePath: %@", THIS_FILE, self, filePath);
|
||||
|
||||
[self abort];
|
||||
return NO;
|
||||
}
|
||||
|
||||
HTTPLogVerbose(@"%@[%p]: Open fd[%i] -> %@", THIS_FILE, self, fileFD, filePath);
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)openFileIfNeeded
|
||||
{
|
||||
if (aborted)
|
||||
{
|
||||
// The file operation has been aborted.
|
||||
// This could be because we failed to open the file,
|
||||
// or the reading process failed.
|
||||
return NO;
|
||||
}
|
||||
|
||||
if (fileFD != NULL_FD)
|
||||
{
|
||||
// File has already been opened.
|
||||
return YES;
|
||||
}
|
||||
|
||||
return [self openFile];
|
||||
}
|
||||
|
||||
- (UInt64)contentLength
|
||||
{
|
||||
HTTPLogTrace();
|
||||
|
||||
return fileLength;
|
||||
}
|
||||
|
||||
- (UInt64)offset
|
||||
{
|
||||
HTTPLogTrace();
|
||||
|
||||
return fileOffset;
|
||||
}
|
||||
|
||||
- (void)setOffset:(UInt64)offset
|
||||
{
|
||||
HTTPLogTrace2(@"%@[%p]: setOffset:%llu", THIS_FILE, self, offset);
|
||||
|
||||
if (![self openFileIfNeeded])
|
||||
{
|
||||
// File opening failed,
|
||||
// or response has been aborted due to another error.
|
||||
return;
|
||||
}
|
||||
|
||||
fileOffset = offset;
|
||||
|
||||
off_t result = lseek(fileFD, (off_t)offset, SEEK_SET);
|
||||
if (result == -1)
|
||||
{
|
||||
HTTPLogError(@"%@[%p]: lseek failed - errno(%i) filePath(%@)", THIS_FILE, self, errno, filePath);
|
||||
|
||||
[self abort];
|
||||
}
|
||||
}
|
||||
|
||||
- (NSData *)readDataOfLength:(NSUInteger)length
|
||||
{
|
||||
HTTPLogTrace2(@"%@[%p]: readDataOfLength:%lu", THIS_FILE, self, (unsigned long)length);
|
||||
|
||||
if (![self openFileIfNeeded])
|
||||
{
|
||||
// File opening failed,
|
||||
// or response has been aborted due to another error.
|
||||
return nil;
|
||||
}
|
||||
|
||||
// Determine how much data we should read.
|
||||
//
|
||||
// It is OK if we ask to read more bytes than exist in the file.
|
||||
// It is NOT OK to over-allocate the buffer.
|
||||
|
||||
UInt64 bytesLeftInFile = fileLength - fileOffset;
|
||||
|
||||
NSUInteger bytesToRead = (NSUInteger)MIN(length, bytesLeftInFile);
|
||||
|
||||
// Make sure buffer is big enough for read request.
|
||||
// Do not over-allocate.
|
||||
|
||||
if (buffer == NULL || bufferSize < bytesToRead)
|
||||
{
|
||||
bufferSize = bytesToRead;
|
||||
buffer = reallocf(buffer, (size_t)bufferSize);
|
||||
|
||||
if (buffer == NULL)
|
||||
{
|
||||
HTTPLogError(@"%@[%p]: Unable to allocate buffer", THIS_FILE, self);
|
||||
|
||||
[self abort];
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
|
||||
// Perform the read
|
||||
|
||||
HTTPLogVerbose(@"%@[%p]: Attempting to read %lu bytes from file", THIS_FILE, self, (unsigned long)bytesToRead);
|
||||
|
||||
ssize_t result = read(fileFD, buffer, bytesToRead);
|
||||
|
||||
// Check the results
|
||||
|
||||
if (result < 0)
|
||||
{
|
||||
HTTPLogError(@"%@: Error(%i) reading file(%@)", THIS_FILE, errno, filePath);
|
||||
|
||||
[self abort];
|
||||
return nil;
|
||||
}
|
||||
else if (result == 0)
|
||||
{
|
||||
HTTPLogError(@"%@: Read EOF on file(%@)", THIS_FILE, filePath);
|
||||
|
||||
[self abort];
|
||||
return nil;
|
||||
}
|
||||
else // (result > 0)
|
||||
{
|
||||
HTTPLogVerbose(@"%@[%p]: Read %ld bytes from file", THIS_FILE, self, (long)result);
|
||||
|
||||
fileOffset += result;
|
||||
|
||||
return [NSData dataWithBytes:buffer length:result];
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)isDone
|
||||
{
|
||||
BOOL result = (fileOffset == fileLength);
|
||||
|
||||
HTTPLogTrace2(@"%@[%p]: isDone - %@", THIS_FILE, self, (result ? @"YES" : @"NO"));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
- (NSString *)filePath
|
||||
{
|
||||
return filePath;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
HTTPLogTrace();
|
||||
|
||||
if (fileFD != NULL_FD)
|
||||
{
|
||||
HTTPLogVerbose(@"%@[%p]: Close fd[%i]", THIS_FILE, self, fileFD);
|
||||
|
||||
close(fileFD);
|
||||
}
|
||||
|
||||
if (buffer)
|
||||
free(buffer);
|
||||
|
||||
}
|
||||
|
||||
@end
|
12
xplan-ios/Base/Tool/CocoaHttpServer/Core/Responses/HTTPRedirectResponse.h
Executable file
12
xplan-ios/Base/Tool/CocoaHttpServer/Core/Responses/HTTPRedirectResponse.h
Executable file
@@ -0,0 +1,12 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "HTTPResponse.h"
|
||||
|
||||
|
||||
@interface HTTPRedirectResponse : NSObject <HTTPResponse>
|
||||
{
|
||||
NSString *redirectPath;
|
||||
}
|
||||
|
||||
- (id)initWithPath:(NSString *)redirectPath;
|
||||
|
||||
@end
|
73
xplan-ios/Base/Tool/CocoaHttpServer/Core/Responses/HTTPRedirectResponse.m
Executable file
73
xplan-ios/Base/Tool/CocoaHttpServer/Core/Responses/HTTPRedirectResponse.m
Executable file
@@ -0,0 +1,73 @@
|
||||
#import "HTTPRedirectResponse.h"
|
||||
#import "HTTPLogging.h"
|
||||
|
||||
#if ! __has_feature(objc_arc)
|
||||
#warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
|
||||
#endif
|
||||
|
||||
// Log levels : off, error, warn, info, verbose
|
||||
// Other flags: trace
|
||||
static const int httpLogLevel = HTTP_LOG_LEVEL_OFF; // | HTTP_LOG_FLAG_TRACE;
|
||||
|
||||
|
||||
@implementation HTTPRedirectResponse
|
||||
|
||||
- (id)initWithPath:(NSString *)path
|
||||
{
|
||||
if ((self = [super init]))
|
||||
{
|
||||
HTTPLogTrace();
|
||||
|
||||
redirectPath = [path copy];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (UInt64)contentLength
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
- (UInt64)offset
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
- (void)setOffset:(UInt64)offset
|
||||
{
|
||||
// Nothing to do
|
||||
}
|
||||
|
||||
- (NSData *)readDataOfLength:(NSUInteger)length
|
||||
{
|
||||
HTTPLogTrace();
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (BOOL)isDone
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (NSDictionary *)httpHeaders
|
||||
{
|
||||
HTTPLogTrace();
|
||||
|
||||
return [NSDictionary dictionaryWithObject:redirectPath forKey:@"Location"];
|
||||
}
|
||||
|
||||
- (NSInteger)status
|
||||
{
|
||||
HTTPLogTrace();
|
||||
|
||||
return 302;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
HTTPLogTrace();
|
||||
|
||||
}
|
||||
|
||||
@end
|
105
xplan-ios/Base/Tool/CocoaHttpServer/Core/WebSocket.h
Executable file
105
xplan-ios/Base/Tool/CocoaHttpServer/Core/WebSocket.h
Executable file
@@ -0,0 +1,105 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@class HTTPMessage;
|
||||
@class GCDAsyncSocket;
|
||||
|
||||
|
||||
#define WebSocketDidDieNotification @"WebSocketDidDie"
|
||||
|
||||
@interface WebSocket : NSObject
|
||||
{
|
||||
dispatch_queue_t websocketQueue;
|
||||
|
||||
HTTPMessage *request;
|
||||
GCDAsyncSocket *asyncSocket;
|
||||
|
||||
NSData *term;
|
||||
|
||||
BOOL isStarted;
|
||||
BOOL isOpen;
|
||||
BOOL isVersion76;
|
||||
|
||||
id __unsafe_unretained delegate;
|
||||
}
|
||||
|
||||
+ (BOOL)isWebSocketRequest:(HTTPMessage *)request;
|
||||
|
||||
- (id)initWithRequest:(HTTPMessage *)request socket:(GCDAsyncSocket *)socket;
|
||||
|
||||
/**
|
||||
* Delegate option.
|
||||
*
|
||||
* In most cases it will be easier to subclass WebSocket,
|
||||
* but some circumstances may lead one to prefer standard delegate callbacks instead.
|
||||
**/
|
||||
@property (/* atomic */ unsafe_unretained) id delegate;
|
||||
|
||||
/**
|
||||
* The WebSocket class is thread-safe, generally via it's GCD queue.
|
||||
* All public API methods are thread-safe,
|
||||
* and the subclass API methods are thread-safe as they are all invoked on the same GCD queue.
|
||||
**/
|
||||
@property (nonatomic, readonly) dispatch_queue_t websocketQueue;
|
||||
|
||||
/**
|
||||
* Public API
|
||||
*
|
||||
* These methods are automatically called by the HTTPServer.
|
||||
* You may invoke the stop method yourself to close the WebSocket manually.
|
||||
**/
|
||||
- (void)start;
|
||||
- (void)stop;
|
||||
|
||||
/**
|
||||
* Public API
|
||||
*
|
||||
* Sends a message over the WebSocket.
|
||||
* This method is thread-safe.
|
||||
**/
|
||||
- (void)sendMessage:(NSString *)msg;
|
||||
|
||||
/**
|
||||
* Public API
|
||||
*
|
||||
* Sends a message over the WebSocket.
|
||||
* This method is thread-safe.
|
||||
**/
|
||||
- (void)sendData:(NSData *)msg;
|
||||
|
||||
/**
|
||||
* Subclass API
|
||||
*
|
||||
* These methods are designed to be overriden by subclasses.
|
||||
**/
|
||||
- (void)didOpen;
|
||||
- (void)didReceiveMessage:(NSString *)msg;
|
||||
- (void)didClose;
|
||||
|
||||
@end
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark -
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* There are two ways to create your own custom WebSocket:
|
||||
*
|
||||
* - Subclass it and override the methods you're interested in.
|
||||
* - Use traditional delegate paradigm along with your own custom class.
|
||||
*
|
||||
* They both exist to allow for maximum flexibility.
|
||||
* In most cases it will be easier to subclass WebSocket.
|
||||
* However some circumstances may lead one to prefer standard delegate callbacks instead.
|
||||
* One such example, you're already subclassing another class, so subclassing WebSocket isn't an option.
|
||||
**/
|
||||
|
||||
@protocol WebSocketDelegate
|
||||
@optional
|
||||
|
||||
- (void)webSocketDidOpen:(WebSocket *)ws;
|
||||
|
||||
- (void)webSocket:(WebSocket *)ws didReceiveMessage:(NSString *)msg;
|
||||
|
||||
- (void)webSocketDidClose:(WebSocket *)ws;
|
||||
|
||||
@end
|
792
xplan-ios/Base/Tool/CocoaHttpServer/Core/WebSocket.m
Executable file
792
xplan-ios/Base/Tool/CocoaHttpServer/Core/WebSocket.m
Executable file
@@ -0,0 +1,792 @@
|
||||
#import "WebSocket.h"
|
||||
#import "HTTPMessage.h"
|
||||
#import "GCDAsyncSocket.h"
|
||||
#import "DDNumber.h"
|
||||
#import "DDData.h"
|
||||
#import "HTTPLogging.h"
|
||||
|
||||
#if ! __has_feature(objc_arc)
|
||||
#warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
|
||||
#endif
|
||||
|
||||
// Log levels: off, error, warn, info, verbose
|
||||
// Other flags : trace
|
||||
static const int httpLogLevel = HTTP_LOG_LEVEL_WARN; // | HTTP_LOG_FLAG_TRACE;
|
||||
|
||||
#define TIMEOUT_NONE -1
|
||||
#define TIMEOUT_REQUEST_BODY 10
|
||||
|
||||
#define TAG_HTTP_REQUEST_BODY 100
|
||||
#define TAG_HTTP_RESPONSE_HEADERS 200
|
||||
#define TAG_HTTP_RESPONSE_BODY 201
|
||||
|
||||
#define TAG_PREFIX 300
|
||||
#define TAG_MSG_PLUS_SUFFIX 301
|
||||
#define TAG_MSG_WITH_LENGTH 302
|
||||
#define TAG_MSG_MASKING_KEY 303
|
||||
#define TAG_PAYLOAD_PREFIX 304
|
||||
#define TAG_PAYLOAD_LENGTH 305
|
||||
#define TAG_PAYLOAD_LENGTH16 306
|
||||
#define TAG_PAYLOAD_LENGTH64 307
|
||||
|
||||
#define WS_OP_CONTINUATION_FRAME 0
|
||||
#define WS_OP_TEXT_FRAME 1
|
||||
#define WS_OP_BINARY_FRAME 2
|
||||
#define WS_OP_CONNECTION_CLOSE 8
|
||||
#define WS_OP_PING 9
|
||||
#define WS_OP_PONG 10
|
||||
|
||||
static inline BOOL WS_OP_IS_FINAL_FRAGMENT(UInt8 frame)
|
||||
{
|
||||
return (frame & 0x80) ? YES : NO;
|
||||
}
|
||||
|
||||
static inline BOOL WS_PAYLOAD_IS_MASKED(UInt8 frame)
|
||||
{
|
||||
return (frame & 0x80) ? YES : NO;
|
||||
}
|
||||
|
||||
static inline NSUInteger WS_PAYLOAD_LENGTH(UInt8 frame)
|
||||
{
|
||||
return frame & 0x7F;
|
||||
}
|
||||
|
||||
@interface WebSocket (PrivateAPI)
|
||||
|
||||
- (void)readRequestBody;
|
||||
- (void)sendResponseBody;
|
||||
- (void)sendResponseHeaders;
|
||||
|
||||
@end
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark -
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@implementation WebSocket
|
||||
{
|
||||
BOOL isRFC6455;
|
||||
BOOL nextFrameMasked;
|
||||
NSUInteger nextOpCode;
|
||||
NSData *maskingKey;
|
||||
}
|
||||
|
||||
+ (BOOL)isWebSocketRequest:(HTTPMessage *)request
|
||||
{
|
||||
// Request (Draft 75):
|
||||
//
|
||||
// GET /demo HTTP/1.1
|
||||
// Upgrade: WebSocket
|
||||
// Connection: Upgrade
|
||||
// Host: example.com
|
||||
// Origin: http://example.com
|
||||
// WebSocket-Protocol: sample
|
||||
//
|
||||
//
|
||||
// Request (Draft 76):
|
||||
//
|
||||
// GET /demo HTTP/1.1
|
||||
// Upgrade: WebSocket
|
||||
// Connection: Upgrade
|
||||
// Host: example.com
|
||||
// Origin: http://example.com
|
||||
// Sec-WebSocket-Protocol: sample
|
||||
// Sec-WebSocket-Key1: 4 @1 46546xW%0l 1 5
|
||||
// Sec-WebSocket-Key2: 12998 5 Y3 1 .P00
|
||||
//
|
||||
// ^n:ds[4U
|
||||
|
||||
// Look for Upgrade: and Connection: headers.
|
||||
// If we find them, and they have the proper value,
|
||||
// we can safely assume this is a websocket request.
|
||||
|
||||
NSString *upgradeHeaderValue = [request headerField:@"Upgrade"];
|
||||
NSString *connectionHeaderValue = [request headerField:@"Connection"];
|
||||
|
||||
BOOL isWebSocket = YES;
|
||||
|
||||
if (!upgradeHeaderValue || !connectionHeaderValue) {
|
||||
isWebSocket = NO;
|
||||
}
|
||||
else if (![upgradeHeaderValue caseInsensitiveCompare:@"WebSocket"] == NSOrderedSame) {
|
||||
isWebSocket = NO;
|
||||
}
|
||||
else if ([connectionHeaderValue rangeOfString:@"Upgrade" options:NSCaseInsensitiveSearch].location == NSNotFound) {
|
||||
isWebSocket = NO;
|
||||
}
|
||||
|
||||
HTTPLogTrace2(@"%@: %@ - %@", THIS_FILE, THIS_METHOD, (isWebSocket ? @"YES" : @"NO"));
|
||||
|
||||
return isWebSocket;
|
||||
}
|
||||
|
||||
+ (BOOL)isVersion76Request:(HTTPMessage *)request
|
||||
{
|
||||
NSString *key1 = [request headerField:@"Sec-WebSocket-Key1"];
|
||||
NSString *key2 = [request headerField:@"Sec-WebSocket-Key2"];
|
||||
|
||||
BOOL isVersion76;
|
||||
|
||||
if (!key1 || !key2) {
|
||||
isVersion76 = NO;
|
||||
}
|
||||
else {
|
||||
isVersion76 = YES;
|
||||
}
|
||||
|
||||
HTTPLogTrace2(@"%@: %@ - %@", THIS_FILE, THIS_METHOD, (isVersion76 ? @"YES" : @"NO"));
|
||||
|
||||
return isVersion76;
|
||||
}
|
||||
|
||||
+ (BOOL)isRFC6455Request:(HTTPMessage *)request
|
||||
{
|
||||
NSString *key = [request headerField:@"Sec-WebSocket-Key"];
|
||||
BOOL isRFC6455 = (key != nil);
|
||||
|
||||
HTTPLogTrace2(@"%@: %@ - %@", THIS_FILE, THIS_METHOD, (isRFC6455 ? @"YES" : @"NO"));
|
||||
|
||||
return isRFC6455;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark Setup and Teardown
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@synthesize websocketQueue;
|
||||
|
||||
- (id)initWithRequest:(HTTPMessage *)aRequest socket:(GCDAsyncSocket *)socket
|
||||
{
|
||||
HTTPLogTrace();
|
||||
|
||||
if (aRequest == nil)
|
||||
{
|
||||
return nil;
|
||||
}
|
||||
|
||||
if ((self = [super init]))
|
||||
{
|
||||
if (HTTP_LOG_VERBOSE)
|
||||
{
|
||||
NSData *requestHeaders = [aRequest messageData];
|
||||
|
||||
NSString *temp = [[NSString alloc] initWithData:requestHeaders encoding:NSUTF8StringEncoding];
|
||||
HTTPLogVerbose(@"%@[%p] Request Headers:\n%@", THIS_FILE, self, temp);
|
||||
}
|
||||
|
||||
websocketQueue = dispatch_queue_create("WebSocket", NULL);
|
||||
request = aRequest;
|
||||
|
||||
asyncSocket = socket;
|
||||
[asyncSocket setDelegate:self delegateQueue:websocketQueue];
|
||||
|
||||
isOpen = NO;
|
||||
isVersion76 = [[self class] isVersion76Request:request];
|
||||
isRFC6455 = [[self class] isRFC6455Request:request];
|
||||
|
||||
term = [[NSData alloc] initWithBytes:"\xFF" length:1];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
HTTPLogTrace();
|
||||
|
||||
#if !OS_OBJECT_USE_OBJC
|
||||
dispatch_release(websocketQueue);
|
||||
#endif
|
||||
|
||||
[asyncSocket setDelegate:nil delegateQueue:NULL];
|
||||
[asyncSocket disconnect];
|
||||
}
|
||||
|
||||
- (id)delegate
|
||||
{
|
||||
__block id result = nil;
|
||||
|
||||
dispatch_sync(websocketQueue, ^{
|
||||
result = delegate;
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
- (void)setDelegate:(id)newDelegate
|
||||
{
|
||||
dispatch_async(websocketQueue, ^{
|
||||
delegate = newDelegate;
|
||||
});
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark Start and Stop
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Starting point for the WebSocket after it has been fully initialized (including subclasses).
|
||||
* This method is called by the HTTPConnection it is spawned from.
|
||||
**/
|
||||
- (void)start
|
||||
{
|
||||
// This method is not exactly designed to be overriden.
|
||||
// Subclasses are encouraged to override the didOpen method instead.
|
||||
|
||||
dispatch_async(websocketQueue, ^{ @autoreleasepool {
|
||||
|
||||
if (isStarted) return;
|
||||
isStarted = YES;
|
||||
|
||||
if (isVersion76)
|
||||
{
|
||||
[self readRequestBody];
|
||||
}
|
||||
else
|
||||
{
|
||||
[self sendResponseHeaders];
|
||||
[self didOpen];
|
||||
}
|
||||
}});
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called by the HTTPServer if it is asked to stop.
|
||||
* The server, in turn, invokes stop on each WebSocket instance.
|
||||
**/
|
||||
- (void)stop
|
||||
{
|
||||
// This method is not exactly designed to be overriden.
|
||||
// Subclasses are encouraged to override the didClose method instead.
|
||||
|
||||
dispatch_async(websocketQueue, ^{ @autoreleasepool {
|
||||
|
||||
[asyncSocket disconnect];
|
||||
}});
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark HTTP Response
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
- (void)readRequestBody
|
||||
{
|
||||
HTTPLogTrace();
|
||||
|
||||
NSAssert(isVersion76, @"WebSocket version 75 doesn't contain a request body");
|
||||
|
||||
[asyncSocket readDataToLength:8 withTimeout:TIMEOUT_NONE tag:TAG_HTTP_REQUEST_BODY];
|
||||
}
|
||||
|
||||
- (NSString *)originResponseHeaderValue
|
||||
{
|
||||
HTTPLogTrace();
|
||||
|
||||
NSString *origin = [request headerField:@"Origin"];
|
||||
|
||||
if (origin == nil)
|
||||
{
|
||||
NSString *port = [NSString stringWithFormat:@"%hu", [asyncSocket localPort]];
|
||||
//NSString * port = @"12306";
|
||||
|
||||
return [NSString stringWithFormat:@"http://localhost:%@", port];
|
||||
}
|
||||
else
|
||||
{
|
||||
return origin;
|
||||
}
|
||||
}
|
||||
|
||||
- (NSString *)locationResponseHeaderValue
|
||||
{
|
||||
HTTPLogTrace();
|
||||
|
||||
NSString *location;
|
||||
|
||||
NSString *scheme = [asyncSocket isSecure] ? @"wss" : @"ws";
|
||||
NSString *host = [request headerField:@"Host"];
|
||||
|
||||
NSString *requestUri = [[request url] relativeString];
|
||||
|
||||
if (host == nil)
|
||||
{
|
||||
NSString *port = [NSString stringWithFormat:@"%hu", [asyncSocket localPort]];
|
||||
//NSString * port = @"12306";
|
||||
location = [NSString stringWithFormat:@"%@://localhost:%@%@", scheme, port, requestUri];
|
||||
}
|
||||
else
|
||||
{
|
||||
location = [NSString stringWithFormat:@"%@://%@%@", scheme, host, requestUri];
|
||||
}
|
||||
|
||||
return location;
|
||||
}
|
||||
|
||||
- (NSString *)secWebSocketKeyResponseHeaderValue {
|
||||
NSString *key = [request headerField: @"Sec-WebSocket-Key"];
|
||||
NSString *guid = @"258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
|
||||
return [[key stringByAppendingString: guid] dataUsingEncoding: NSUTF8StringEncoding].sha1Digest.base64Encoded;
|
||||
}
|
||||
|
||||
- (void)sendResponseHeaders
|
||||
{
|
||||
HTTPLogTrace();
|
||||
|
||||
// Request (Draft 75):
|
||||
//
|
||||
// GET /demo HTTP/1.1
|
||||
// Upgrade: WebSocket
|
||||
// Connection: Upgrade
|
||||
// Host: example.com
|
||||
// Origin: http://example.com
|
||||
// WebSocket-Protocol: sample
|
||||
//
|
||||
//
|
||||
// Request (Draft 76):
|
||||
//
|
||||
// GET /demo HTTP/1.1
|
||||
// Upgrade: WebSocket
|
||||
// Connection: Upgrade
|
||||
// Host: example.com
|
||||
// Origin: http://example.com
|
||||
// Sec-WebSocket-Protocol: sample
|
||||
// Sec-WebSocket-Key2: 12998 5 Y3 1 .P00
|
||||
// Sec-WebSocket-Key1: 4 @1 46546xW%0l 1 5
|
||||
//
|
||||
// ^n:ds[4U
|
||||
|
||||
|
||||
// Response (Draft 75):
|
||||
//
|
||||
// HTTP/1.1 101 Web Socket Protocol Handshake
|
||||
// Upgrade: WebSocket
|
||||
// Connection: Upgrade
|
||||
// WebSocket-Origin: http://example.com
|
||||
// WebSocket-Location: ws://example.com/demo
|
||||
// WebSocket-Protocol: sample
|
||||
//
|
||||
//
|
||||
// Response (Draft 76):
|
||||
//
|
||||
// HTTP/1.1 101 WebSocket Protocol Handshake
|
||||
// Upgrade: WebSocket
|
||||
// Connection: Upgrade
|
||||
// Sec-WebSocket-Origin: http://example.com
|
||||
// Sec-WebSocket-Location: ws://example.com/demo
|
||||
// Sec-WebSocket-Protocol: sample
|
||||
//
|
||||
// 8jKS'y:G*Co,Wxa-
|
||||
|
||||
|
||||
HTTPMessage *wsResponse = [[HTTPMessage alloc] initResponseWithStatusCode:101
|
||||
description:@"Web Socket Protocol Handshake"
|
||||
version:HTTPVersion1_1];
|
||||
|
||||
[wsResponse setHeaderField:@"Upgrade" value:@"WebSocket"];
|
||||
[wsResponse setHeaderField:@"Connection" value:@"Upgrade"];
|
||||
|
||||
// Note: It appears that WebSocket-Origin and WebSocket-Location
|
||||
// are required for Google's Chrome implementation to work properly.
|
||||
//
|
||||
// If we don't send either header, Chrome will never report the WebSocket as open.
|
||||
// If we only send one of the two, Chrome will immediately close the WebSocket.
|
||||
//
|
||||
// In addition to this it appears that Chrome's implementation is very picky of the values of the headers.
|
||||
// They have to match exactly with what Chrome sent us or it will close the WebSocket.
|
||||
|
||||
NSString *originValue = [self originResponseHeaderValue];
|
||||
NSString *locationValue = [self locationResponseHeaderValue];
|
||||
|
||||
NSString *originField = isVersion76 ? @"Sec-WebSocket-Origin" : @"WebSocket-Origin";
|
||||
NSString *locationField = isVersion76 ? @"Sec-WebSocket-Location" : @"WebSocket-Location";
|
||||
|
||||
[wsResponse setHeaderField:originField value:originValue];
|
||||
[wsResponse setHeaderField:locationField value:locationValue];
|
||||
|
||||
NSString *acceptValue = [self secWebSocketKeyResponseHeaderValue];
|
||||
if (acceptValue) {
|
||||
[wsResponse setHeaderField: @"Sec-WebSocket-Accept" value: acceptValue];
|
||||
}
|
||||
|
||||
NSData *responseHeaders = [wsResponse messageData];
|
||||
|
||||
|
||||
if (HTTP_LOG_VERBOSE)
|
||||
{
|
||||
NSString *temp = [[NSString alloc] initWithData:responseHeaders encoding:NSUTF8StringEncoding];
|
||||
HTTPLogVerbose(@"%@[%p] Response Headers:\n%@", THIS_FILE, self, temp);
|
||||
}
|
||||
|
||||
[asyncSocket writeData:responseHeaders withTimeout:TIMEOUT_NONE tag:TAG_HTTP_RESPONSE_HEADERS];
|
||||
}
|
||||
|
||||
- (NSData *)processKey:(NSString *)key
|
||||
{
|
||||
HTTPLogTrace();
|
||||
|
||||
unichar c;
|
||||
NSUInteger i;
|
||||
NSUInteger length = [key length];
|
||||
|
||||
// Concatenate the digits into a string,
|
||||
// and count the number of spaces.
|
||||
|
||||
NSMutableString *numStr = [NSMutableString stringWithCapacity:10];
|
||||
long long numSpaces = 0;
|
||||
|
||||
for (i = 0; i < length; i++)
|
||||
{
|
||||
c = [key characterAtIndex:i];
|
||||
|
||||
if (c >= '0' && c <= '9')
|
||||
{
|
||||
[numStr appendFormat:@"%C", c];
|
||||
}
|
||||
else if (c == ' ')
|
||||
{
|
||||
numSpaces++;
|
||||
}
|
||||
}
|
||||
|
||||
long long num = strtoll([numStr UTF8String], NULL, 10);
|
||||
|
||||
long long resultHostNum;
|
||||
|
||||
if (numSpaces == 0)
|
||||
resultHostNum = 0;
|
||||
else
|
||||
resultHostNum = num / numSpaces;
|
||||
|
||||
HTTPLogVerbose(@"key(%@) -> %qi / %qi = %qi", key, num, numSpaces, resultHostNum);
|
||||
|
||||
// Convert result to 4 byte big-endian (network byte order)
|
||||
// and then convert to raw data.
|
||||
|
||||
UInt32 result = OSSwapHostToBigInt32((uint32_t)resultHostNum);
|
||||
|
||||
return [NSData dataWithBytes:&result length:4];
|
||||
}
|
||||
|
||||
- (void)sendResponseBody:(NSData *)d3
|
||||
{
|
||||
HTTPLogTrace();
|
||||
|
||||
NSAssert(isVersion76, @"WebSocket version 75 doesn't contain a response body");
|
||||
NSAssert([d3 length] == 8, @"Invalid requestBody length");
|
||||
|
||||
NSString *key1 = [request headerField:@"Sec-WebSocket-Key1"];
|
||||
NSString *key2 = [request headerField:@"Sec-WebSocket-Key2"];
|
||||
|
||||
NSData *d1 = [self processKey:key1];
|
||||
NSData *d2 = [self processKey:key2];
|
||||
|
||||
// Concatenated d1, d2 & d3
|
||||
|
||||
NSMutableData *d0 = [NSMutableData dataWithCapacity:(4+4+8)];
|
||||
[d0 appendData:d1];
|
||||
[d0 appendData:d2];
|
||||
[d0 appendData:d3];
|
||||
|
||||
// Hash the data using MD5
|
||||
|
||||
NSData *responseBody = [d0 md5Digest];
|
||||
|
||||
[asyncSocket writeData:responseBody withTimeout:TIMEOUT_NONE tag:TAG_HTTP_RESPONSE_BODY];
|
||||
|
||||
if (HTTP_LOG_VERBOSE)
|
||||
{
|
||||
NSString *s1 = [[NSString alloc] initWithData:d1 encoding:NSASCIIStringEncoding];
|
||||
NSString *s2 = [[NSString alloc] initWithData:d2 encoding:NSASCIIStringEncoding];
|
||||
NSString *s3 = [[NSString alloc] initWithData:d3 encoding:NSASCIIStringEncoding];
|
||||
|
||||
NSString *s0 = [[NSString alloc] initWithData:d0 encoding:NSASCIIStringEncoding];
|
||||
|
||||
NSString *sH = [[NSString alloc] initWithData:responseBody encoding:NSASCIIStringEncoding];
|
||||
|
||||
HTTPLogVerbose(@"key1 result : raw(%@) str(%@)", d1, s1);
|
||||
HTTPLogVerbose(@"key2 result : raw(%@) str(%@)", d2, s2);
|
||||
HTTPLogVerbose(@"key3 passed : raw(%@) str(%@)", d3, s3);
|
||||
HTTPLogVerbose(@"key0 concat : raw(%@) str(%@)", d0, s0);
|
||||
HTTPLogVerbose(@"responseBody: raw(%@) str(%@)", responseBody, sH);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark Core Functionality
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
- (void)didOpen
|
||||
{
|
||||
HTTPLogTrace();
|
||||
|
||||
// Override me to perform any custom actions once the WebSocket has been opened.
|
||||
// This method is invoked on the websocketQueue.
|
||||
//
|
||||
// Don't forget to invoke [super didOpen] in your method.
|
||||
|
||||
// Start reading for messages
|
||||
[asyncSocket readDataToLength:1 withTimeout:TIMEOUT_NONE tag:(isRFC6455 ? TAG_PAYLOAD_PREFIX : TAG_PREFIX)];
|
||||
|
||||
// Notify delegate
|
||||
if ([delegate respondsToSelector:@selector(webSocketDidOpen:)])
|
||||
{
|
||||
[delegate webSocketDidOpen:self];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)sendMessage:(NSString *)msg
|
||||
{
|
||||
NSData *msgData = [msg dataUsingEncoding:NSUTF8StringEncoding];
|
||||
[self sendData:msgData];
|
||||
}
|
||||
|
||||
- (void)sendData:(NSData *)msgData
|
||||
{
|
||||
HTTPLogTrace();
|
||||
|
||||
NSMutableData *data = nil;
|
||||
|
||||
if (isRFC6455)
|
||||
{
|
||||
NSUInteger length = msgData.length;
|
||||
if (length <= 125)
|
||||
{
|
||||
data = [NSMutableData dataWithCapacity:(length + 2)];
|
||||
[data appendBytes: "\x81" length:1];
|
||||
UInt8 len = (UInt8)length;
|
||||
[data appendBytes: &len length:1];
|
||||
[data appendData:msgData];
|
||||
}
|
||||
else if (length <= 0xFFFF)
|
||||
{
|
||||
data = [NSMutableData dataWithCapacity:(length + 4)];
|
||||
[data appendBytes: "\x81\x7E" length:2];
|
||||
UInt16 len = (UInt16)length;
|
||||
[data appendBytes: (UInt8[]){len >> 8, len & 0xFF} length:2];
|
||||
[data appendData:msgData];
|
||||
}
|
||||
else
|
||||
{
|
||||
data = [NSMutableData dataWithCapacity:(length + 10)];
|
||||
[data appendBytes: "\x81\x7F" length:2];
|
||||
[data appendBytes: (UInt8[]){0, 0, 0, 0, (UInt8)(length >> 24), (UInt8)(length >> 16), (UInt8)(length >> 8), length & 0xFF} length:8];
|
||||
[data appendData:msgData];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
data = [NSMutableData dataWithCapacity:([msgData length] + 2)];
|
||||
|
||||
[data appendBytes:"\x00" length:1];
|
||||
[data appendData:msgData];
|
||||
[data appendBytes:"\xFF" length:1];
|
||||
}
|
||||
|
||||
// Remember: GCDAsyncSocket is thread-safe
|
||||
|
||||
[asyncSocket writeData:data withTimeout:TIMEOUT_NONE tag:0];
|
||||
}
|
||||
|
||||
- (void)didReceiveMessage:(NSString *)msg
|
||||
{
|
||||
HTTPLogTrace();
|
||||
|
||||
// Override me to process incoming messages.
|
||||
// This method is invoked on the websocketQueue.
|
||||
//
|
||||
// For completeness, you should invoke [super didReceiveMessage:msg] in your method.
|
||||
|
||||
// Notify delegate
|
||||
if ([delegate respondsToSelector:@selector(webSocket:didReceiveMessage:)])
|
||||
{
|
||||
[delegate webSocket:self didReceiveMessage:msg];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)didClose
|
||||
{
|
||||
HTTPLogTrace();
|
||||
|
||||
// Override me to perform any cleanup when the socket is closed
|
||||
// This method is invoked on the websocketQueue.
|
||||
//
|
||||
// Don't forget to invoke [super didClose] at the end of your method.
|
||||
|
||||
// Notify delegate
|
||||
if ([delegate respondsToSelector:@selector(webSocketDidClose:)])
|
||||
{
|
||||
[delegate webSocketDidClose:self];
|
||||
}
|
||||
|
||||
// Notify HTTPServer
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:WebSocketDidDieNotification object:self];
|
||||
}
|
||||
|
||||
#pragma mark WebSocket Frame
|
||||
|
||||
- (BOOL)isValidWebSocketFrame:(UInt8)frame
|
||||
{
|
||||
NSUInteger rsv = frame & 0x70;
|
||||
NSUInteger opcode = frame & 0x0F;
|
||||
if (rsv || (3 <= opcode && opcode <= 7) || (0xB <= opcode && opcode <= 0xF))
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
return YES;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark AsyncSocket Delegate
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// 0 1 2 3
|
||||
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
// +-+-+-+-+-------+-+-------------+-------------------------------+
|
||||
// |F|R|R|R| opcode|M| Payload len | Extended payload length |
|
||||
// |I|S|S|S| (4) |A| (7) | (16/64) |
|
||||
// |N|V|V|V| |S| | (if payload len==126/127) |
|
||||
// | |1|2|3| |K| | |
|
||||
// +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
|
||||
// | Extended payload length continued, if payload len == 127 |
|
||||
// + - - - - - - - - - - - - - - - +-------------------------------+
|
||||
// | |Masking-key, if MASK set to 1 |
|
||||
// +-------------------------------+-------------------------------+
|
||||
// | Masking-key (continued) | Payload Data |
|
||||
// +-------------------------------- - - - - - - - - - - - - - - - +
|
||||
// : Payload Data continued ... :
|
||||
// + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
|
||||
// | Payload Data continued ... |
|
||||
// +---------------------------------------------------------------+
|
||||
|
||||
- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag
|
||||
{
|
||||
HTTPLogTrace();
|
||||
|
||||
if (tag == TAG_HTTP_REQUEST_BODY)
|
||||
{
|
||||
[self sendResponseHeaders];
|
||||
[self sendResponseBody:data];
|
||||
[self didOpen];
|
||||
}
|
||||
else if (tag == TAG_PREFIX)
|
||||
{
|
||||
UInt8 *pFrame = (UInt8 *)[data bytes];
|
||||
UInt8 frame = *pFrame;
|
||||
|
||||
if (frame <= 0x7F)
|
||||
{
|
||||
[asyncSocket readDataToData:term withTimeout:TIMEOUT_NONE tag:TAG_MSG_PLUS_SUFFIX];
|
||||
}
|
||||
else
|
||||
{
|
||||
// Unsupported frame type
|
||||
[self didClose];
|
||||
}
|
||||
}
|
||||
else if (tag == TAG_PAYLOAD_PREFIX)
|
||||
{
|
||||
UInt8 *pFrame = (UInt8 *)[data bytes];
|
||||
UInt8 frame = *pFrame;
|
||||
|
||||
if ([self isValidWebSocketFrame: frame])
|
||||
{
|
||||
nextOpCode = (frame & 0x0F);
|
||||
[asyncSocket readDataToLength:1 withTimeout:TIMEOUT_NONE tag:TAG_PAYLOAD_LENGTH];
|
||||
}
|
||||
else
|
||||
{
|
||||
// Unsupported frame type
|
||||
[self didClose];
|
||||
}
|
||||
}
|
||||
else if (tag == TAG_PAYLOAD_LENGTH)
|
||||
{
|
||||
UInt8 frame = *(UInt8 *)[data bytes];
|
||||
BOOL masked = WS_PAYLOAD_IS_MASKED(frame);
|
||||
NSUInteger length = WS_PAYLOAD_LENGTH(frame);
|
||||
nextFrameMasked = masked;
|
||||
maskingKey = nil;
|
||||
if (length <= 125)
|
||||
{
|
||||
if (nextFrameMasked)
|
||||
{
|
||||
[asyncSocket readDataToLength:4 withTimeout:TIMEOUT_NONE tag:TAG_MSG_MASKING_KEY];
|
||||
}
|
||||
[asyncSocket readDataToLength:length withTimeout:TIMEOUT_NONE tag:TAG_MSG_WITH_LENGTH];
|
||||
}
|
||||
else if (length == 126)
|
||||
{
|
||||
[asyncSocket readDataToLength:2 withTimeout:TIMEOUT_NONE tag:TAG_PAYLOAD_LENGTH16];
|
||||
}
|
||||
else
|
||||
{
|
||||
[asyncSocket readDataToLength:8 withTimeout:TIMEOUT_NONE tag:TAG_PAYLOAD_LENGTH64];
|
||||
}
|
||||
}
|
||||
else if (tag == TAG_PAYLOAD_LENGTH16)
|
||||
{
|
||||
UInt8 *pFrame = (UInt8 *)[data bytes];
|
||||
NSUInteger length = ((NSUInteger)pFrame[0] << 8) | (NSUInteger)pFrame[1];
|
||||
if (nextFrameMasked) {
|
||||
[asyncSocket readDataToLength:4 withTimeout:TIMEOUT_NONE tag:TAG_MSG_MASKING_KEY];
|
||||
}
|
||||
[asyncSocket readDataToLength:length withTimeout:TIMEOUT_NONE tag:TAG_MSG_WITH_LENGTH];
|
||||
}
|
||||
else if (tag == TAG_PAYLOAD_LENGTH64)
|
||||
{
|
||||
// FIXME: 64bit data size in memory?
|
||||
[self didClose];
|
||||
}
|
||||
else if (tag == TAG_MSG_WITH_LENGTH)
|
||||
{
|
||||
NSUInteger msgLength = [data length];
|
||||
if (nextFrameMasked && maskingKey) {
|
||||
NSMutableData *masked = data.mutableCopy;
|
||||
UInt8 *pData = (UInt8 *)masked.mutableBytes;
|
||||
UInt8 *pMask = (UInt8 *)maskingKey.bytes;
|
||||
for (NSUInteger i = 0; i < msgLength; i++)
|
||||
{
|
||||
pData[i] = pData[i] ^ pMask[i % 4];
|
||||
}
|
||||
data = masked;
|
||||
}
|
||||
if (nextOpCode == WS_OP_TEXT_FRAME)
|
||||
{
|
||||
NSString *msg = [[NSString alloc] initWithBytes:[data bytes] length:msgLength encoding:NSUTF8StringEncoding];
|
||||
[self didReceiveMessage:msg];
|
||||
}
|
||||
else
|
||||
{
|
||||
[self didClose];
|
||||
return;
|
||||
}
|
||||
|
||||
// Read next frame
|
||||
[asyncSocket readDataToLength:1 withTimeout:TIMEOUT_NONE tag:TAG_PAYLOAD_PREFIX];
|
||||
}
|
||||
else if (tag == TAG_MSG_MASKING_KEY)
|
||||
{
|
||||
maskingKey = data.copy;
|
||||
}
|
||||
else
|
||||
{
|
||||
NSUInteger msgLength = [data length] - 1; // Excluding ending 0xFF frame
|
||||
|
||||
NSString *msg = [[NSString alloc] initWithBytes:[data bytes] length:msgLength encoding:NSUTF8StringEncoding];
|
||||
|
||||
[self didReceiveMessage:msg];
|
||||
|
||||
|
||||
// Read next message
|
||||
[asyncSocket readDataToLength:1 withTimeout:TIMEOUT_NONE tag:TAG_PREFIX];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)error
|
||||
{
|
||||
HTTPLogTrace2(@"%@[%p]: socketDidDisconnect:withError: %@", THIS_FILE, self, error);
|
||||
|
||||
[self didClose];
|
||||
}
|
||||
|
||||
@end
|
58
xplan-ios/Base/Tool/CocoaHttpServer/MyHTTPConnection.h
Executable file
58
xplan-ios/Base/Tool/CocoaHttpServer/MyHTTPConnection.h
Executable file
@@ -0,0 +1,58 @@
|
||||
|
||||
#import "HTTPConnection.h"
|
||||
|
||||
@class MultipartFormDataParser;
|
||||
@class MyHTTPConnection;
|
||||
|
||||
@protocol MyHTTPConnectionDelegate <NSObject>
|
||||
@required
|
||||
|
||||
/**
|
||||
设置目标地址
|
||||
|
||||
@param server 服务器 ftp self
|
||||
@return 目标地址str
|
||||
*/
|
||||
- (NSString *)onSetDestinationPathHttpFileTranSportDestination:(MyHTTPConnection *)server;
|
||||
@optional
|
||||
|
||||
/**
|
||||
文件传输并存储成功
|
||||
|
||||
@param server 服务器 ftp self
|
||||
@param filePath 文件路径(含文件名)
|
||||
*/
|
||||
- (void)onHttpFileTranSportServer:(MyHTTPConnection *)server successWithPath:(NSString *)filePath;
|
||||
|
||||
/**
|
||||
文件判断
|
||||
|
||||
@param server 服务器 ftp self
|
||||
@param filePath 文件路径(含文件名)
|
||||
@return 如果是重复文件,就返回NO
|
||||
*/
|
||||
- (BOOL)onHttpFileDataEstimateDuplicateCanPassTranSportServer:(MyHTTPConnection *)server withPath:(NSString *)filePath andFileName:(NSString *)fileName;
|
||||
|
||||
@end
|
||||
|
||||
@interface MyHTTPConnection : HTTPConnection {
|
||||
MultipartFormDataParser* parser;
|
||||
NSFileHandle* storeFile;
|
||||
NSMutableArray* uploadedFiles;
|
||||
}
|
||||
|
||||
/**
|
||||
文件路径(含文件名)
|
||||
*/
|
||||
@property (nonatomic, strong) NSString *filePath;
|
||||
|
||||
/**
|
||||
文件夹路径(不含文件名)
|
||||
*/
|
||||
@property (strong, nonatomic) NSString *destinationPath;
|
||||
|
||||
/**
|
||||
delegate:MyHTTPConnectionDelegate
|
||||
*/
|
||||
@property (nonatomic, weak) id <MyHTTPConnectionDelegate> delegate;
|
||||
@end
|
208
xplan-ios/Base/Tool/CocoaHttpServer/MyHTTPConnection.m
Executable file
208
xplan-ios/Base/Tool/CocoaHttpServer/MyHTTPConnection.m
Executable file
@@ -0,0 +1,208 @@
|
||||
|
||||
#import "MyHTTPConnection.h"
|
||||
#import "HTTPMessage.h"
|
||||
#import "HTTPDataResponse.h"
|
||||
#import "DDNumber.h"
|
||||
#import "HTTPLogging.h"
|
||||
|
||||
#import "MultipartFormDataParser.h"
|
||||
#import "MultipartMessageHeaderField.h"
|
||||
#import "HTTPDynamicFileResponse.h"
|
||||
#import "HTTPFileResponse.h"
|
||||
|
||||
|
||||
// Log levels : off, error, warn, info, verbose
|
||||
// Other flags: trace
|
||||
static const int httpLogLevel = HTTP_LOG_LEVEL_VERBOSE; // | HTTP_LOG_FLAG_TRACE;
|
||||
|
||||
|
||||
/**
|
||||
* All we have to do is override appropriate methods in HTTPConnection.
|
||||
**/
|
||||
|
||||
@implementation MyHTTPConnection
|
||||
|
||||
- (BOOL)supportsMethod:(NSString *)method atPath:(NSString *)path {
|
||||
|
||||
HTTPLogTrace();
|
||||
|
||||
// Add support for POST
|
||||
|
||||
if ([method isEqualToString:@"POST"])
|
||||
{
|
||||
if ([path isEqualToString:@"/upload.html"])
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
|
||||
return [super supportsMethod:method atPath:path];
|
||||
}
|
||||
|
||||
- (BOOL)expectsRequestBodyFromMethod:(NSString *)method atPath:(NSString *)path {
|
||||
|
||||
HTTPLogTrace();
|
||||
|
||||
// Inform HTTP server that we expect a body to accompany a POST request
|
||||
|
||||
if([method isEqualToString:@"POST"] && [path isEqualToString:@"/upload.html"]) {
|
||||
// here we need to make sure, boundary is set in header
|
||||
NSString* contentType = [request headerField:@"Content-Type"];
|
||||
NSUInteger paramsSeparator = [contentType rangeOfString:@";"].location;
|
||||
if( NSNotFound == paramsSeparator ) {
|
||||
return NO;
|
||||
}
|
||||
if( paramsSeparator >= contentType.length - 1 ) {
|
||||
return NO;
|
||||
}
|
||||
NSString* type = [contentType substringToIndex:paramsSeparator];
|
||||
if( ![type isEqualToString:@"multipart/form-data"] ) {
|
||||
// we expect multipart/form-data content type
|
||||
return NO;
|
||||
}
|
||||
|
||||
// enumerate all params in content-type, and find boundary there
|
||||
NSArray* params = [[contentType substringFromIndex:paramsSeparator + 1] componentsSeparatedByString:@";"];
|
||||
for( NSString* param in params ) {
|
||||
paramsSeparator = [param rangeOfString:@"="].location;
|
||||
if( (NSNotFound == paramsSeparator) || paramsSeparator >= param.length - 1 ) {
|
||||
continue;
|
||||
}
|
||||
NSString* paramName = [param substringWithRange:NSMakeRange(1, paramsSeparator-1)];
|
||||
NSString* paramValue = [param substringFromIndex:paramsSeparator+1];
|
||||
|
||||
if( [paramName isEqualToString: @"boundary"] ) {
|
||||
// let's separate the boundary from content-type, to make it more handy to handle
|
||||
[request setHeaderField:@"boundary" value:paramValue];
|
||||
}
|
||||
}
|
||||
// check if boundary specified
|
||||
if( nil == [request headerField:@"boundary"] ) {
|
||||
return NO;
|
||||
}
|
||||
return YES;
|
||||
}
|
||||
return [super expectsRequestBodyFromMethod:method atPath:path];
|
||||
}
|
||||
|
||||
- (NSObject<HTTPResponse> *)httpResponseForMethod:(NSString *)method URI:(NSString *)path {
|
||||
|
||||
HTTPLogTrace();
|
||||
|
||||
if ([method isEqualToString:@"POST"] && [path isEqualToString:@"/upload.html"]) {
|
||||
|
||||
// this method will generate response with links to uploaded file
|
||||
NSMutableString* filesStr = [[NSMutableString alloc] init];
|
||||
|
||||
for( NSString* filePath in uploadedFiles ) {
|
||||
//generate links
|
||||
[filesStr appendFormat:@"<a href=\"%@\"> %@ </a><br/>",filePath, [filePath lastPathComponent]];
|
||||
}
|
||||
NSString* templatePath = [[config documentRoot] stringByAppendingPathComponent:@"upload.html"];
|
||||
NSDictionary* replacementDict = [NSDictionary dictionaryWithObject:filesStr forKey:@"MyFiles"];
|
||||
// use dynamic file response to apply our links to response template
|
||||
return [[HTTPDynamicFileResponse alloc] initWithFilePath:templatePath forConnection:self separator:@"%" replacementDictionary:replacementDict];
|
||||
}
|
||||
if( [method isEqualToString:@"GET"] && [path hasPrefix:@"/upload/"] ) {
|
||||
// let download the uploaded files
|
||||
return [[HTTPFileResponse alloc] initWithFilePath: [[config documentRoot] stringByAppendingString:path] forConnection:self];
|
||||
}
|
||||
|
||||
return [super httpResponseForMethod:method URI:path];
|
||||
}
|
||||
|
||||
- (void)prepareForBodyWithSize:(UInt64)contentLength {
|
||||
HTTPLogTrace();
|
||||
|
||||
// set up mime parser
|
||||
NSString* boundary = [request headerField:@"boundary"];
|
||||
parser = [[MultipartFormDataParser alloc] initWithBoundary:boundary formEncoding:NSUTF8StringEncoding];
|
||||
parser.delegate = self;
|
||||
|
||||
uploadedFiles = [[NSMutableArray alloc] init];
|
||||
}
|
||||
|
||||
- (void)processBodyData:(NSData *)postDataChunk {
|
||||
HTTPLogTrace();
|
||||
// append data to the parser. It will invoke callbacks to let us handle
|
||||
// parsed data.
|
||||
[parser appendData:postDataChunk];
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------
|
||||
#pragma mark multipart form data parser delegate
|
||||
|
||||
|
||||
- (void) processStartOfPartWithHeader:(MultipartMessageHeader*) header {
|
||||
// in this sample, we are not interested in parts, other then file parts.
|
||||
// check content disposition to find out filename
|
||||
|
||||
MultipartMessageHeaderField* disposition = [header.fields objectForKey:@"Content-Disposition"];
|
||||
NSString* filename = [[disposition.params objectForKey:@"filename"] lastPathComponent];
|
||||
|
||||
if ( (nil == filename) || [filename isEqualToString: @""] ) {
|
||||
// it's either not a file part, or
|
||||
// an empty form sent. we won't handle it.
|
||||
return;
|
||||
}
|
||||
|
||||
// 在这里修改文件存储的位置
|
||||
|
||||
NSAssert([self.delegate respondsToSelector:@selector(onSetDestinationPathHttpFileTranSportDestination:)], @"Need to add delegate name onSetDestinationPathHttpFileTranSportDestination to add a destinationPath");
|
||||
|
||||
if ([self.delegate respondsToSelector:@selector(onSetDestinationPathHttpFileTranSportDestination:)]) {
|
||||
self.destinationPath = [self.delegate onSetDestinationPathHttpFileTranSportDestination:self];
|
||||
NSAssert(self.destinationPath.length > 0 || self.destinationPath, @"destinationPath can not be nil");
|
||||
}
|
||||
|
||||
//拼接文件名或者预计文件路径
|
||||
NSString* filePath = [self.destinationPath stringByAppendingPathComponent: filename];
|
||||
self.filePath = filePath;
|
||||
//需要判断是否存在重复文件
|
||||
if ([self.delegate respondsToSelector:@selector(onHttpFileDataEstimateDuplicateCanPassTranSportServer:withPath:andFileName:)]) {
|
||||
if (![self.delegate onHttpFileDataEstimateDuplicateCanPassTranSportServer:self withPath:self.filePath andFileName:filename]) {
|
||||
return;
|
||||
};
|
||||
}
|
||||
|
||||
HTTPLogVerbose(@"Saving file to %@", filePath);
|
||||
if(![[NSFileManager defaultManager] createDirectoryAtPath:self.destinationPath withIntermediateDirectories:true attributes:nil error:nil]) {
|
||||
HTTPLogError(@"Could not create directory at path: %@", filePath);
|
||||
}
|
||||
if(![[NSFileManager defaultManager] createFileAtPath:filePath contents:nil attributes:nil]) {
|
||||
HTTPLogError(@"Could not create file at path: %@", filePath);
|
||||
}
|
||||
storeFile = [NSFileHandle fileHandleForWritingAtPath:filePath];
|
||||
[uploadedFiles addObject: [NSString stringWithFormat:@"/upload/%@", filename]];
|
||||
}
|
||||
|
||||
|
||||
- (void) processContent:(NSData*) data WithHeader:(MultipartMessageHeader*) header {
|
||||
// here we just write the output from parser to the file.
|
||||
if( storeFile ) {
|
||||
[storeFile writeData:data];
|
||||
}
|
||||
}
|
||||
|
||||
- (void) processEndOfPartWithHeader:(MultipartMessageHeader*) header {
|
||||
// as the file part is over, we close the file.
|
||||
[storeFile closeFile];
|
||||
storeFile = nil;
|
||||
}
|
||||
|
||||
- (void) processPreambleData:(NSData*) data {
|
||||
// if we are interested in preamble data, we could process it here.
|
||||
|
||||
}
|
||||
|
||||
- (void) processEpilogueData:(NSData*) data {
|
||||
// if we are interested in epilogue data, we could process it here.
|
||||
//用户更新歌曲数量
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:@"processEpilogueData" object:nil];
|
||||
if ([self.delegate respondsToSelector:@selector(onHttpFileTranSportServer:successWithPath:)]) {
|
||||
[self.delegate onHttpFileTranSportServer:self successWithPath:self.filePath];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
19
xplan-ios/Base/Tool/CocoaHttpServer/SJXCSMIPHelper.h
Executable file
19
xplan-ios/Base/Tool/CocoaHttpServer/SJXCSMIPHelper.h
Executable file
@@ -0,0 +1,19 @@
|
||||
//
|
||||
// SJXCSMIPHelper.h
|
||||
// LocalReader
|
||||
//
|
||||
// Created by shapp on 2017/7/24.
|
||||
// Copyright © 2017年 sjx. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@interface SJXCSMIPHelper : NSObject
|
||||
|
||||
/** 获取ip地址 */
|
||||
+ (NSString *)deviceIPAdress;
|
||||
|
||||
#pragma mark - 获取设备当前网络IP地址
|
||||
+ (NSString *)getIPAddress:(BOOL)preferIPv4;
|
||||
|
||||
@end
|
155
xplan-ios/Base/Tool/CocoaHttpServer/SJXCSMIPHelper.m
Executable file
155
xplan-ios/Base/Tool/CocoaHttpServer/SJXCSMIPHelper.m
Executable file
@@ -0,0 +1,155 @@
|
||||
//
|
||||
// SJXCSMIPHelper.m
|
||||
// LocalReader
|
||||
//
|
||||
// Created by shapp on 2017/7/24.
|
||||
// Copyright © 2017年 sjx. All rights reserved.
|
||||
//
|
||||
|
||||
#import "SJXCSMIPHelper.h"
|
||||
#import <ifaddrs.h>
|
||||
#import <arpa/inet.h>
|
||||
#import <net/if.h>
|
||||
|
||||
@implementation SJXCSMIPHelper
|
||||
|
||||
+ (NSString *)deviceIPAdress {
|
||||
|
||||
NSString *address = @"an error occurred when obtaining ip address";
|
||||
|
||||
struct ifaddrs *interfaces = NULL;
|
||||
|
||||
struct ifaddrs *temp_addr = NULL;
|
||||
|
||||
int success = 0;
|
||||
|
||||
success = getifaddrs(&interfaces);
|
||||
|
||||
if (success == 0) { // 0 表示获取成功
|
||||
|
||||
temp_addr = interfaces;
|
||||
|
||||
while (temp_addr != NULL) {
|
||||
|
||||
if( temp_addr->ifa_addr->sa_family == AF_INET) {
|
||||
|
||||
// Check if interface is en0 which is the wifi connection on the iPhone
|
||||
|
||||
if ([[NSString stringWithUTF8String:temp_addr->ifa_name] isEqualToString:@"en0"]) {
|
||||
|
||||
// Get NSString from C String
|
||||
|
||||
address = [NSString stringWithUTF8String:inet_ntoa(((struct sockaddr_in *)temp_addr->ifa_addr)->sin_addr)];
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
temp_addr = temp_addr->ifa_next;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
freeifaddrs(interfaces);
|
||||
|
||||
return address;
|
||||
|
||||
}
|
||||
|
||||
|
||||
#define IOS_CELLULAR @"pdp_ip0"
|
||||
#define IOS_WIFI @"en0"
|
||||
#define IOS_VPN @"utun0"
|
||||
#define IP_ADDR_IPv4 @"ipv4"
|
||||
#define IP_ADDR_IPv6 @"ipv6"
|
||||
|
||||
#pragma mark - 获取设备当前网络IP地址
|
||||
+ (NSString *)getIPAddress:(BOOL)preferIPv4
|
||||
{
|
||||
NSArray *searchArray = preferIPv4 ?
|
||||
@[ IOS_VPN @"/" IP_ADDR_IPv4, IOS_VPN @"/" IP_ADDR_IPv6, IOS_WIFI @"/" IP_ADDR_IPv4, IOS_WIFI @"/" IP_ADDR_IPv6, IOS_CELLULAR @"/" IP_ADDR_IPv4, IOS_CELLULAR @"/" IP_ADDR_IPv6 ] :
|
||||
@[ IOS_VPN @"/" IP_ADDR_IPv6, IOS_VPN @"/" IP_ADDR_IPv4, IOS_WIFI @"/" IP_ADDR_IPv6, IOS_WIFI @"/" IP_ADDR_IPv4, IOS_CELLULAR @"/" IP_ADDR_IPv6, IOS_CELLULAR @"/" IP_ADDR_IPv4 ] ;
|
||||
|
||||
NSDictionary *addresses = [self getIPAddresses];
|
||||
NSLog(@"addresses: %@", addresses);
|
||||
|
||||
__block NSString *address;
|
||||
[searchArray enumerateObjectsUsingBlock:^(NSString *key, NSUInteger idx, BOOL *stop)
|
||||
{
|
||||
address = addresses[key];
|
||||
//筛选出IP地址格式
|
||||
if([self isValidatIP:address]) *stop = YES;
|
||||
} ];
|
||||
return address ? address : @"0.0.0.0";
|
||||
}
|
||||
|
||||
+ (BOOL)isValidatIP:(NSString *)ipAddress {
|
||||
if (ipAddress.length == 0) {
|
||||
return NO;
|
||||
}
|
||||
NSString *urlRegEx = @"^([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\."
|
||||
"([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\."
|
||||
"([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\."
|
||||
"([01]?\\d\\d?|2[0-4]\\d|25[0-5])$";
|
||||
|
||||
NSError *error;
|
||||
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:urlRegEx options:0 error:&error];
|
||||
|
||||
if (regex != nil) {
|
||||
NSTextCheckingResult *firstMatch=[regex firstMatchInString:ipAddress options:0 range:NSMakeRange(0, [ipAddress length])];
|
||||
|
||||
if (firstMatch) {
|
||||
NSRange resultRange = [firstMatch rangeAtIndex:0];
|
||||
NSString *result=[ipAddress substringWithRange:resultRange];
|
||||
//输出结果
|
||||
NSLog(@"%@",result);
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
+ (NSDictionary *)getIPAddresses
|
||||
{
|
||||
NSMutableDictionary *addresses = [NSMutableDictionary dictionaryWithCapacity:8];
|
||||
|
||||
// retrieve the current interfaces - returns 0 on success
|
||||
struct ifaddrs *interfaces;
|
||||
if(!getifaddrs(&interfaces)) {
|
||||
// Loop through linked list of interfaces
|
||||
struct ifaddrs *interface;
|
||||
for(interface=interfaces; interface; interface=interface->ifa_next) {
|
||||
if(!(interface->ifa_flags & IFF_UP) /* || (interface->ifa_flags & IFF_LOOPBACK) */ ) {
|
||||
continue; // deeply nested code harder to read
|
||||
}
|
||||
const struct sockaddr_in *addr = (const struct sockaddr_in*)interface->ifa_addr;
|
||||
char addrBuf[ MAX(INET_ADDRSTRLEN, INET6_ADDRSTRLEN) ];
|
||||
if(addr && (addr->sin_family==AF_INET || addr->sin_family==AF_INET6)) {
|
||||
NSString *name = [NSString stringWithUTF8String:interface->ifa_name];
|
||||
NSString *type;
|
||||
if(addr->sin_family == AF_INET) {
|
||||
if(inet_ntop(AF_INET, &addr->sin_addr, addrBuf, INET_ADDRSTRLEN)) {
|
||||
type = IP_ADDR_IPv4;
|
||||
}
|
||||
} else {
|
||||
const struct sockaddr_in6 *addr6 = (const struct sockaddr_in6*)interface->ifa_addr;
|
||||
if(inet_ntop(AF_INET6, &addr6->sin6_addr, addrBuf, INET6_ADDRSTRLEN)) {
|
||||
type = IP_ADDR_IPv6;
|
||||
}
|
||||
}
|
||||
if(type) {
|
||||
NSString *key = [NSString stringWithFormat:@"%@/%@", name, type];
|
||||
addresses[key] = [NSString stringWithUTF8String:addrBuf];
|
||||
}
|
||||
}
|
||||
}
|
||||
// Free memory
|
||||
freeifaddrs(interfaces);
|
||||
}
|
||||
return [addresses count] ? addresses : nil;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@end
|
41
xplan-ios/Base/Tool/CocoaHttpServer/Vendor/CocoaLumberjack/DDASLLogger.h
vendored
Executable file
41
xplan-ios/Base/Tool/CocoaHttpServer/Vendor/CocoaLumberjack/DDASLLogger.h
vendored
Executable file
@@ -0,0 +1,41 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <asl.h>
|
||||
|
||||
#import "DDLog.h"
|
||||
|
||||
/**
|
||||
* Welcome to Cocoa Lumberjack!
|
||||
*
|
||||
* The project page has a wealth of documentation if you have any questions.
|
||||
* https://github.com/robbiehanson/CocoaLumberjack
|
||||
*
|
||||
* If you're new to the project you may wish to read the "Getting Started" wiki.
|
||||
* https://github.com/robbiehanson/CocoaLumberjack/wiki/GettingStarted
|
||||
*
|
||||
*
|
||||
* This class provides a logger for the Apple System Log facility.
|
||||
*
|
||||
* As described in the "Getting Started" page,
|
||||
* the traditional NSLog() function directs it's output to two places:
|
||||
*
|
||||
* - Apple System Log
|
||||
* - StdErr (if stderr is a TTY) so log statements show up in Xcode console
|
||||
*
|
||||
* To duplicate NSLog() functionality you can simply add this logger and a tty logger.
|
||||
* However, if you instead choose to use file logging (for faster performance),
|
||||
* you may choose to use a file logger and a tty logger.
|
||||
**/
|
||||
|
||||
@interface DDASLLogger : DDAbstractLogger <DDLogger>
|
||||
{
|
||||
aslclient client;
|
||||
}
|
||||
|
||||
+ (DDASLLogger *)sharedInstance;
|
||||
|
||||
// Inherited from DDAbstractLogger
|
||||
|
||||
// - (id <DDLogFormatter>)logFormatter;
|
||||
// - (void)setLogFormatter:(id <DDLogFormatter>)formatter;
|
||||
|
||||
@end
|
99
xplan-ios/Base/Tool/CocoaHttpServer/Vendor/CocoaLumberjack/DDASLLogger.m
vendored
Executable file
99
xplan-ios/Base/Tool/CocoaHttpServer/Vendor/CocoaLumberjack/DDASLLogger.m
vendored
Executable file
@@ -0,0 +1,99 @@
|
||||
#import "DDASLLogger.h"
|
||||
|
||||
#import <libkern/OSAtomic.h>
|
||||
|
||||
/**
|
||||
* Welcome to Cocoa Lumberjack!
|
||||
*
|
||||
* The project page has a wealth of documentation if you have any questions.
|
||||
* https://github.com/robbiehanson/CocoaLumberjack
|
||||
*
|
||||
* If you're new to the project you may wish to read the "Getting Started" wiki.
|
||||
* https://github.com/robbiehanson/CocoaLumberjack/wiki/GettingStarted
|
||||
**/
|
||||
|
||||
#if ! __has_feature(objc_arc)
|
||||
#warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
|
||||
#endif
|
||||
|
||||
|
||||
@implementation DDASLLogger
|
||||
|
||||
static DDASLLogger *sharedInstance;
|
||||
|
||||
/**
|
||||
* The runtime sends initialize to each class in a program exactly one time just before the class,
|
||||
* or any class that inherits from it, is sent its first message from within the program. (Thus the
|
||||
* method may never be invoked if the class is not used.) The runtime sends the initialize message to
|
||||
* classes in a thread-safe manner. Superclasses receive this message before their subclasses.
|
||||
*
|
||||
* This method may also be called directly (assumably by accident), hence the safety mechanism.
|
||||
**/
|
||||
+ (void)initialize
|
||||
{
|
||||
static BOOL initialized = NO;
|
||||
if (!initialized)
|
||||
{
|
||||
initialized = YES;
|
||||
|
||||
sharedInstance = [[DDASLLogger alloc] init];
|
||||
}
|
||||
}
|
||||
|
||||
+ (DDASLLogger *)sharedInstance
|
||||
{
|
||||
return sharedInstance;
|
||||
}
|
||||
|
||||
- (id)init
|
||||
{
|
||||
if (sharedInstance != nil)
|
||||
{
|
||||
return nil;
|
||||
}
|
||||
|
||||
if ((self = [super init]))
|
||||
{
|
||||
// A default asl client is provided for the main thread,
|
||||
// but background threads need to create their own client.
|
||||
|
||||
client = asl_open(NULL, "com.apple.console", 0);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)logMessage:(DDLogMessage *)logMessage
|
||||
{
|
||||
NSString *logMsg = logMessage->logMsg;
|
||||
|
||||
if (formatter)
|
||||
{
|
||||
logMsg = [formatter formatLogMessage:logMessage];
|
||||
}
|
||||
|
||||
if (logMsg)
|
||||
{
|
||||
const char *msg = [logMsg UTF8String];
|
||||
|
||||
int aslLogLevel;
|
||||
switch (logMessage->logFlag)
|
||||
{
|
||||
// Note: By default ASL will filter anything above level 5 (Notice).
|
||||
// So our mappings shouldn't go above that level.
|
||||
|
||||
case LOG_FLAG_ERROR : aslLogLevel = ASL_LEVEL_CRIT; break;
|
||||
case LOG_FLAG_WARN : aslLogLevel = ASL_LEVEL_ERR; break;
|
||||
case LOG_FLAG_INFO : aslLogLevel = ASL_LEVEL_WARNING; break;
|
||||
default : aslLogLevel = ASL_LEVEL_NOTICE; break;
|
||||
}
|
||||
|
||||
asl_log(client, NULL, aslLogLevel, "%s", msg);
|
||||
}
|
||||
}
|
||||
|
||||
- (NSString *)loggerName
|
||||
{
|
||||
return @"cocoa.lumberjack.aslLogger";
|
||||
}
|
||||
|
||||
@end
|
102
xplan-ios/Base/Tool/CocoaHttpServer/Vendor/CocoaLumberjack/DDAbstractDatabaseLogger.h
vendored
Executable file
102
xplan-ios/Base/Tool/CocoaHttpServer/Vendor/CocoaLumberjack/DDAbstractDatabaseLogger.h
vendored
Executable file
@@ -0,0 +1,102 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import "DDLog.h"
|
||||
|
||||
/**
|
||||
* Welcome to Cocoa Lumberjack!
|
||||
*
|
||||
* The project page has a wealth of documentation if you have any questions.
|
||||
* https://github.com/robbiehanson/CocoaLumberjack
|
||||
*
|
||||
* If you're new to the project you may wish to read the "Getting Started" wiki.
|
||||
* https://github.com/robbiehanson/CocoaLumberjack/wiki/GettingStarted
|
||||
*
|
||||
*
|
||||
* This class provides an abstract implementation of a database logger.
|
||||
*
|
||||
* That is, it provides the base implementation for a database logger to build atop of.
|
||||
* All that is needed for a concrete database logger is to extend this class
|
||||
* and override the methods in the implementation file that are prefixed with "db_".
|
||||
**/
|
||||
|
||||
@interface DDAbstractDatabaseLogger : DDAbstractLogger {
|
||||
@protected
|
||||
NSUInteger saveThreshold;
|
||||
NSTimeInterval saveInterval;
|
||||
NSTimeInterval maxAge;
|
||||
NSTimeInterval deleteInterval;
|
||||
BOOL deleteOnEverySave;
|
||||
|
||||
BOOL saveTimerSuspended;
|
||||
NSUInteger unsavedCount;
|
||||
dispatch_time_t unsavedTime;
|
||||
dispatch_source_t saveTimer;
|
||||
dispatch_time_t lastDeleteTime;
|
||||
dispatch_source_t deleteTimer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies how often to save the data to disk.
|
||||
* Since saving is an expensive operation (disk io) it is not done after every log statement.
|
||||
* These properties allow you to configure how/when the logger saves to disk.
|
||||
*
|
||||
* A save is done when either (whichever happens first):
|
||||
*
|
||||
* - The number of unsaved log entries reaches saveThreshold
|
||||
* - The amount of time since the oldest unsaved log entry was created reaches saveInterval
|
||||
*
|
||||
* You can optionally disable the saveThreshold by setting it to zero.
|
||||
* If you disable the saveThreshold you are entirely dependent on the saveInterval.
|
||||
*
|
||||
* You can optionally disable the saveInterval by setting it to zero (or a negative value).
|
||||
* If you disable the saveInterval you are entirely dependent on the saveThreshold.
|
||||
*
|
||||
* It's not wise to disable both saveThreshold and saveInterval.
|
||||
*
|
||||
* The default saveThreshold is 500.
|
||||
* The default saveInterval is 60 seconds.
|
||||
**/
|
||||
@property (assign, readwrite) NSUInteger saveThreshold;
|
||||
@property (assign, readwrite) NSTimeInterval saveInterval;
|
||||
|
||||
/**
|
||||
* It is likely you don't want the log entries to persist forever.
|
||||
* Doing so would allow the database to grow infinitely large over time.
|
||||
*
|
||||
* The maxAge property provides a way to specify how old a log statement can get
|
||||
* before it should get deleted from the database.
|
||||
*
|
||||
* The deleteInterval specifies how often to sweep for old log entries.
|
||||
* Since deleting is an expensive operation (disk io) is is done on a fixed interval.
|
||||
*
|
||||
* An alternative to the deleteInterval is the deleteOnEverySave option.
|
||||
* This specifies that old log entries should be deleted during every save operation.
|
||||
*
|
||||
* You can optionally disable the maxAge by setting it to zero (or a negative value).
|
||||
* If you disable the maxAge then old log statements are not deleted.
|
||||
*
|
||||
* You can optionally disable the deleteInterval by setting it to zero (or a negative value).
|
||||
*
|
||||
* If you disable both deleteInterval and deleteOnEverySave then old log statements are not deleted.
|
||||
*
|
||||
* It's not wise to enable both deleteInterval and deleteOnEverySave.
|
||||
*
|
||||
* The default maxAge is 7 days.
|
||||
* The default deleteInterval is 5 minutes.
|
||||
* The default deleteOnEverySave is NO.
|
||||
**/
|
||||
@property (assign, readwrite) NSTimeInterval maxAge;
|
||||
@property (assign, readwrite) NSTimeInterval deleteInterval;
|
||||
@property (assign, readwrite) BOOL deleteOnEverySave;
|
||||
|
||||
/**
|
||||
* Forces a save of any pending log entries (flushes log entries to disk).
|
||||
**/
|
||||
- (void)savePendingLogEntries;
|
||||
|
||||
/**
|
||||
* Removes any log entries that are older than maxAge.
|
||||
**/
|
||||
- (void)deleteOldLogEntries;
|
||||
|
||||
@end
|
727
xplan-ios/Base/Tool/CocoaHttpServer/Vendor/CocoaLumberjack/DDAbstractDatabaseLogger.m
vendored
Executable file
727
xplan-ios/Base/Tool/CocoaHttpServer/Vendor/CocoaLumberjack/DDAbstractDatabaseLogger.m
vendored
Executable file
@@ -0,0 +1,727 @@
|
||||
#import "DDAbstractDatabaseLogger.h"
|
||||
#import <math.h>
|
||||
|
||||
/**
|
||||
* Welcome to Cocoa Lumberjack!
|
||||
*
|
||||
* The project page has a wealth of documentation if you have any questions.
|
||||
* https://github.com/robbiehanson/CocoaLumberjack
|
||||
*
|
||||
* If you're new to the project you may wish to read the "Getting Started" wiki.
|
||||
* https://github.com/robbiehanson/CocoaLumberjack/wiki/GettingStarted
|
||||
**/
|
||||
|
||||
#if ! __has_feature(objc_arc)
|
||||
#warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
|
||||
#endif
|
||||
|
||||
@interface DDAbstractDatabaseLogger ()
|
||||
- (void)destroySaveTimer;
|
||||
- (void)destroyDeleteTimer;
|
||||
@end
|
||||
|
||||
#pragma mark -
|
||||
|
||||
@implementation DDAbstractDatabaseLogger
|
||||
|
||||
- (id)init
|
||||
{
|
||||
if ((self = [super init]))
|
||||
{
|
||||
saveThreshold = 500;
|
||||
saveInterval = 60; // 60 seconds
|
||||
maxAge = (60 * 60 * 24 * 7); // 7 days
|
||||
deleteInterval = (60 * 5); // 5 minutes
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
[self destroySaveTimer];
|
||||
[self destroyDeleteTimer];
|
||||
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark Override Me
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
- (BOOL)db_log:(DDLogMessage *)logMessage
|
||||
{
|
||||
// Override me and add your implementation.
|
||||
//
|
||||
// Return YES if an item was added to the buffer.
|
||||
// Return NO if the logMessage was ignored.
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (void)db_save
|
||||
{
|
||||
// Override me and add your implementation.
|
||||
}
|
||||
|
||||
- (void)db_delete
|
||||
{
|
||||
// Override me and add your implementation.
|
||||
}
|
||||
|
||||
- (void)db_saveAndDelete
|
||||
{
|
||||
// Override me and add your implementation.
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark Private API
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
- (void)performSaveAndSuspendSaveTimer
|
||||
{
|
||||
if (unsavedCount > 0)
|
||||
{
|
||||
if (deleteOnEverySave)
|
||||
[self db_saveAndDelete];
|
||||
else
|
||||
[self db_save];
|
||||
}
|
||||
|
||||
unsavedCount = 0;
|
||||
unsavedTime = 0;
|
||||
|
||||
if (saveTimer && !saveTimerSuspended)
|
||||
{
|
||||
dispatch_suspend(saveTimer);
|
||||
saveTimerSuspended = YES;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)performDelete
|
||||
{
|
||||
if (maxAge > 0.0)
|
||||
{
|
||||
[self db_delete];
|
||||
|
||||
lastDeleteTime = dispatch_time(DISPATCH_TIME_NOW, 0);
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark Timers
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
- (void)destroySaveTimer
|
||||
{
|
||||
if (saveTimer)
|
||||
{
|
||||
dispatch_source_cancel(saveTimer);
|
||||
if (saveTimerSuspended)
|
||||
{
|
||||
// Must resume a timer before releasing it (or it will crash)
|
||||
dispatch_resume(saveTimer);
|
||||
saveTimerSuspended = NO;
|
||||
}
|
||||
#if !OS_OBJECT_USE_OBJC
|
||||
dispatch_release(saveTimer);
|
||||
#endif
|
||||
saveTimer = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)updateAndResumeSaveTimer
|
||||
{
|
||||
if ((saveTimer != NULL) && (saveInterval > 0.0) && (unsavedTime > 0.0))
|
||||
{
|
||||
uint64_t interval = (uint64_t)(saveInterval * NSEC_PER_SEC);
|
||||
dispatch_time_t startTime = dispatch_time(unsavedTime, interval);
|
||||
|
||||
dispatch_source_set_timer(saveTimer, startTime, interval, 1.0);
|
||||
|
||||
if (saveTimerSuspended)
|
||||
{
|
||||
dispatch_resume(saveTimer);
|
||||
saveTimerSuspended = NO;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)createSuspendedSaveTimer
|
||||
{
|
||||
if ((saveTimer == NULL) && (saveInterval > 0.0))
|
||||
{
|
||||
saveTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, loggerQueue);
|
||||
|
||||
dispatch_source_set_event_handler(saveTimer, ^{ @autoreleasepool {
|
||||
|
||||
[self performSaveAndSuspendSaveTimer];
|
||||
|
||||
}});
|
||||
|
||||
saveTimerSuspended = YES;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)destroyDeleteTimer
|
||||
{
|
||||
if (deleteTimer)
|
||||
{
|
||||
dispatch_source_cancel(deleteTimer);
|
||||
#if !OS_OBJECT_USE_OBJC
|
||||
dispatch_release(deleteTimer);
|
||||
#endif
|
||||
deleteTimer = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)updateDeleteTimer
|
||||
{
|
||||
if ((deleteTimer != NULL) && (deleteInterval > 0.0) && (maxAge > 0.0))
|
||||
{
|
||||
uint64_t interval = (uint64_t)(deleteInterval * NSEC_PER_SEC);
|
||||
dispatch_time_t startTime;
|
||||
|
||||
if (lastDeleteTime > 0)
|
||||
startTime = dispatch_time(lastDeleteTime, interval);
|
||||
else
|
||||
startTime = dispatch_time(DISPATCH_TIME_NOW, interval);
|
||||
|
||||
dispatch_source_set_timer(deleteTimer, startTime, interval, 1.0);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)createAndStartDeleteTimer
|
||||
{
|
||||
if ((deleteTimer == NULL) && (deleteInterval > 0.0) && (maxAge > 0.0))
|
||||
{
|
||||
deleteTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, loggerQueue);
|
||||
|
||||
if (deleteTimer != NULL) {
|
||||
dispatch_source_set_event_handler(deleteTimer, ^{ @autoreleasepool {
|
||||
|
||||
[self performDelete];
|
||||
|
||||
}});
|
||||
|
||||
[self updateDeleteTimer];
|
||||
|
||||
dispatch_resume(deleteTimer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark Configuration
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
- (NSUInteger)saveThreshold
|
||||
{
|
||||
// The design of this method is taken from the DDAbstractLogger implementation.
|
||||
// For extensive documentation please refer to the DDAbstractLogger implementation.
|
||||
|
||||
// Note: The internal implementation MUST access the colorsEnabled variable directly,
|
||||
// This method is designed explicitly for external access.
|
||||
//
|
||||
// Using "self." syntax to go through this method will cause immediate deadlock.
|
||||
// This is the intended result. Fix it by accessing the ivar directly.
|
||||
// Great strides have been take to ensure this is safe to do. Plus it's MUCH faster.
|
||||
|
||||
NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure");
|
||||
NSAssert(![self isOnInternalLoggerQueue], @"MUST access ivar directly, NOT via self.* syntax.");
|
||||
|
||||
dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue];
|
||||
|
||||
__block NSUInteger result;
|
||||
|
||||
dispatch_sync(globalLoggingQueue, ^{
|
||||
dispatch_sync(loggerQueue, ^{
|
||||
result = saveThreshold;
|
||||
});
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
- (void)setSaveThreshold:(NSUInteger)threshold
|
||||
{
|
||||
dispatch_block_t block = ^{ @autoreleasepool {
|
||||
|
||||
if (saveThreshold != threshold)
|
||||
{
|
||||
saveThreshold = threshold;
|
||||
|
||||
// Since the saveThreshold has changed,
|
||||
// we check to see if the current unsavedCount has surpassed the new threshold.
|
||||
//
|
||||
// If it has, we immediately save the log.
|
||||
|
||||
if ((unsavedCount >= saveThreshold) && (saveThreshold > 0))
|
||||
{
|
||||
[self performSaveAndSuspendSaveTimer];
|
||||
}
|
||||
}
|
||||
}};
|
||||
|
||||
// The design of the setter logic below is taken from the DDAbstractLogger implementation.
|
||||
// For documentation please refer to the DDAbstractLogger implementation.
|
||||
|
||||
if ([self isOnInternalLoggerQueue])
|
||||
{
|
||||
block();
|
||||
}
|
||||
else
|
||||
{
|
||||
dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue];
|
||||
NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure");
|
||||
|
||||
dispatch_async(globalLoggingQueue, ^{
|
||||
dispatch_async(loggerQueue, block);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
- (NSTimeInterval)saveInterval
|
||||
{
|
||||
// The design of this method is taken from the DDAbstractLogger implementation.
|
||||
// For extensive documentation please refer to the DDAbstractLogger implementation.
|
||||
|
||||
// Note: The internal implementation MUST access the colorsEnabled variable directly,
|
||||
// This method is designed explicitly for external access.
|
||||
//
|
||||
// Using "self." syntax to go through this method will cause immediate deadlock.
|
||||
// This is the intended result. Fix it by accessing the ivar directly.
|
||||
// Great strides have been take to ensure this is safe to do. Plus it's MUCH faster.
|
||||
|
||||
NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure");
|
||||
NSAssert(![self isOnInternalLoggerQueue], @"MUST access ivar directly, NOT via self.* syntax.");
|
||||
|
||||
dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue];
|
||||
|
||||
__block NSTimeInterval result;
|
||||
|
||||
dispatch_sync(globalLoggingQueue, ^{
|
||||
dispatch_sync(loggerQueue, ^{
|
||||
result = saveInterval;
|
||||
});
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
- (void)setSaveInterval:(NSTimeInterval)interval
|
||||
{
|
||||
dispatch_block_t block = ^{ @autoreleasepool {
|
||||
|
||||
// C99 recommended floating point comparison macro
|
||||
// Read: isLessThanOrGreaterThan(floatA, floatB)
|
||||
|
||||
if (/* saveInterval != interval */ islessgreater(saveInterval, interval))
|
||||
{
|
||||
saveInterval = interval;
|
||||
|
||||
// There are several cases we need to handle here.
|
||||
//
|
||||
// 1. If the saveInterval was previously enabled and it just got disabled,
|
||||
// then we need to stop the saveTimer. (And we might as well release it.)
|
||||
//
|
||||
// 2. If the saveInterval was previously disabled and it just got enabled,
|
||||
// then we need to setup the saveTimer. (Plus we might need to do an immediate save.)
|
||||
//
|
||||
// 3. If the saveInterval increased, then we need to reset the timer so that it fires at the later date.
|
||||
//
|
||||
// 4. If the saveInterval decreased, then we need to reset the timer so that it fires at an earlier date.
|
||||
// (Plus we might need to do an immediate save.)
|
||||
|
||||
if (saveInterval > 0.0)
|
||||
{
|
||||
if (saveTimer == NULL)
|
||||
{
|
||||
// Handles #2
|
||||
//
|
||||
// Since the saveTimer uses the unsavedTime to calculate it's first fireDate,
|
||||
// if a save is needed the timer will fire immediately.
|
||||
|
||||
[self createSuspendedSaveTimer];
|
||||
[self updateAndResumeSaveTimer];
|
||||
}
|
||||
else
|
||||
{
|
||||
// Handles #3
|
||||
// Handles #4
|
||||
//
|
||||
// Since the saveTimer uses the unsavedTime to calculate it's first fireDate,
|
||||
// if a save is needed the timer will fire immediately.
|
||||
|
||||
[self updateAndResumeSaveTimer];
|
||||
}
|
||||
}
|
||||
else if (saveTimer)
|
||||
{
|
||||
// Handles #1
|
||||
|
||||
[self destroySaveTimer];
|
||||
}
|
||||
}
|
||||
}};
|
||||
|
||||
// The design of the setter logic below is taken from the DDAbstractLogger implementation.
|
||||
// For documentation please refer to the DDAbstractLogger implementation.
|
||||
|
||||
if ([self isOnInternalLoggerQueue])
|
||||
{
|
||||
block();
|
||||
}
|
||||
else
|
||||
{
|
||||
dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue];
|
||||
NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure");
|
||||
|
||||
dispatch_async(globalLoggingQueue, ^{
|
||||
dispatch_async(loggerQueue, block);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
- (NSTimeInterval)maxAge
|
||||
{
|
||||
// The design of this method is taken from the DDAbstractLogger implementation.
|
||||
// For extensive documentation please refer to the DDAbstractLogger implementation.
|
||||
|
||||
// Note: The internal implementation MUST access the colorsEnabled variable directly,
|
||||
// This method is designed explicitly for external access.
|
||||
//
|
||||
// Using "self." syntax to go through this method will cause immediate deadlock.
|
||||
// This is the intended result. Fix it by accessing the ivar directly.
|
||||
// Great strides have been take to ensure this is safe to do. Plus it's MUCH faster.
|
||||
|
||||
NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure");
|
||||
NSAssert(![self isOnInternalLoggerQueue], @"MUST access ivar directly, NOT via self.* syntax.");
|
||||
|
||||
dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue];
|
||||
|
||||
__block NSTimeInterval result;
|
||||
|
||||
dispatch_sync(globalLoggingQueue, ^{
|
||||
dispatch_sync(loggerQueue, ^{
|
||||
result = maxAge;
|
||||
});
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
- (void)setMaxAge:(NSTimeInterval)interval
|
||||
{
|
||||
dispatch_block_t block = ^{ @autoreleasepool {
|
||||
|
||||
// C99 recommended floating point comparison macro
|
||||
// Read: isLessThanOrGreaterThan(floatA, floatB)
|
||||
|
||||
if (/* maxAge != interval */ islessgreater(maxAge, interval))
|
||||
{
|
||||
NSTimeInterval oldMaxAge = maxAge;
|
||||
NSTimeInterval newMaxAge = interval;
|
||||
|
||||
maxAge = interval;
|
||||
|
||||
// There are several cases we need to handle here.
|
||||
//
|
||||
// 1. If the maxAge was previously enabled and it just got disabled,
|
||||
// then we need to stop the deleteTimer. (And we might as well release it.)
|
||||
//
|
||||
// 2. If the maxAge was previously disabled and it just got enabled,
|
||||
// then we need to setup the deleteTimer. (Plus we might need to do an immediate delete.)
|
||||
//
|
||||
// 3. If the maxAge was increased,
|
||||
// then we don't need to do anything.
|
||||
//
|
||||
// 4. If the maxAge was decreased,
|
||||
// then we should do an immediate delete.
|
||||
|
||||
BOOL shouldDeleteNow = NO;
|
||||
|
||||
if (oldMaxAge > 0.0)
|
||||
{
|
||||
if (newMaxAge <= 0.0)
|
||||
{
|
||||
// Handles #1
|
||||
|
||||
[self destroyDeleteTimer];
|
||||
}
|
||||
else if (oldMaxAge > newMaxAge)
|
||||
{
|
||||
// Handles #4
|
||||
shouldDeleteNow = YES;
|
||||
}
|
||||
}
|
||||
else if (newMaxAge > 0.0)
|
||||
{
|
||||
// Handles #2
|
||||
shouldDeleteNow = YES;
|
||||
}
|
||||
|
||||
if (shouldDeleteNow)
|
||||
{
|
||||
[self performDelete];
|
||||
|
||||
if (deleteTimer)
|
||||
[self updateDeleteTimer];
|
||||
else
|
||||
[self createAndStartDeleteTimer];
|
||||
}
|
||||
}
|
||||
}};
|
||||
|
||||
// The design of the setter logic below is taken from the DDAbstractLogger implementation.
|
||||
// For documentation please refer to the DDAbstractLogger implementation.
|
||||
|
||||
if ([self isOnInternalLoggerQueue])
|
||||
{
|
||||
block();
|
||||
}
|
||||
else
|
||||
{
|
||||
dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue];
|
||||
NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure");
|
||||
|
||||
dispatch_async(globalLoggingQueue, ^{
|
||||
dispatch_async(loggerQueue, block);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
- (NSTimeInterval)deleteInterval
|
||||
{
|
||||
// The design of this method is taken from the DDAbstractLogger implementation.
|
||||
// For extensive documentation please refer to the DDAbstractLogger implementation.
|
||||
|
||||
// Note: The internal implementation MUST access the colorsEnabled variable directly,
|
||||
// This method is designed explicitly for external access.
|
||||
//
|
||||
// Using "self." syntax to go through this method will cause immediate deadlock.
|
||||
// This is the intended result. Fix it by accessing the ivar directly.
|
||||
// Great strides have been take to ensure this is safe to do. Plus it's MUCH faster.
|
||||
|
||||
NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure");
|
||||
NSAssert(![self isOnInternalLoggerQueue], @"MUST access ivar directly, NOT via self.* syntax.");
|
||||
|
||||
dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue];
|
||||
|
||||
__block NSTimeInterval result;
|
||||
|
||||
dispatch_sync(globalLoggingQueue, ^{
|
||||
dispatch_sync(loggerQueue, ^{
|
||||
result = deleteInterval;
|
||||
});
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
- (void)setDeleteInterval:(NSTimeInterval)interval
|
||||
{
|
||||
dispatch_block_t block = ^{ @autoreleasepool {
|
||||
|
||||
// C99 recommended floating point comparison macro
|
||||
// Read: isLessThanOrGreaterThan(floatA, floatB)
|
||||
|
||||
if (/* deleteInterval != interval */ islessgreater(deleteInterval, interval))
|
||||
{
|
||||
deleteInterval = interval;
|
||||
|
||||
// There are several cases we need to handle here.
|
||||
//
|
||||
// 1. If the deleteInterval was previously enabled and it just got disabled,
|
||||
// then we need to stop the deleteTimer. (And we might as well release it.)
|
||||
//
|
||||
// 2. If the deleteInterval was previously disabled and it just got enabled,
|
||||
// then we need to setup the deleteTimer. (Plus we might need to do an immediate delete.)
|
||||
//
|
||||
// 3. If the deleteInterval increased, then we need to reset the timer so that it fires at the later date.
|
||||
//
|
||||
// 4. If the deleteInterval decreased, then we need to reset the timer so that it fires at an earlier date.
|
||||
// (Plus we might need to do an immediate delete.)
|
||||
|
||||
if (deleteInterval > 0.0)
|
||||
{
|
||||
if (deleteTimer == NULL)
|
||||
{
|
||||
// Handles #2
|
||||
//
|
||||
// Since the deleteTimer uses the lastDeleteTime to calculate it's first fireDate,
|
||||
// if a delete is needed the timer will fire immediately.
|
||||
|
||||
[self createAndStartDeleteTimer];
|
||||
}
|
||||
else
|
||||
{
|
||||
// Handles #3
|
||||
// Handles #4
|
||||
//
|
||||
// Since the deleteTimer uses the lastDeleteTime to calculate it's first fireDate,
|
||||
// if a save is needed the timer will fire immediately.
|
||||
|
||||
[self updateDeleteTimer];
|
||||
}
|
||||
}
|
||||
else if (deleteTimer)
|
||||
{
|
||||
// Handles #1
|
||||
|
||||
[self destroyDeleteTimer];
|
||||
}
|
||||
}
|
||||
}};
|
||||
|
||||
// The design of the setter logic below is taken from the DDAbstractLogger implementation.
|
||||
// For documentation please refer to the DDAbstractLogger implementation.
|
||||
|
||||
if ([self isOnInternalLoggerQueue])
|
||||
{
|
||||
block();
|
||||
}
|
||||
else
|
||||
{
|
||||
dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue];
|
||||
NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure");
|
||||
|
||||
dispatch_async(globalLoggingQueue, ^{
|
||||
dispatch_async(loggerQueue, block);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)deleteOnEverySave
|
||||
{
|
||||
// The design of this method is taken from the DDAbstractLogger implementation.
|
||||
// For extensive documentation please refer to the DDAbstractLogger implementation.
|
||||
|
||||
// Note: The internal implementation MUST access the colorsEnabled variable directly,
|
||||
// This method is designed explicitly for external access.
|
||||
//
|
||||
// Using "self." syntax to go through this method will cause immediate deadlock.
|
||||
// This is the intended result. Fix it by accessing the ivar directly.
|
||||
// Great strides have been take to ensure this is safe to do. Plus it's MUCH faster.
|
||||
|
||||
NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure");
|
||||
NSAssert(![self isOnInternalLoggerQueue], @"MUST access ivar directly, NOT via self.* syntax.");
|
||||
|
||||
dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue];
|
||||
|
||||
__block BOOL result;
|
||||
|
||||
dispatch_sync(globalLoggingQueue, ^{
|
||||
dispatch_sync(loggerQueue, ^{
|
||||
result = deleteOnEverySave;
|
||||
});
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
- (void)setDeleteOnEverySave:(BOOL)flag
|
||||
{
|
||||
dispatch_block_t block = ^{
|
||||
|
||||
deleteOnEverySave = flag;
|
||||
};
|
||||
|
||||
// The design of the setter logic below is taken from the DDAbstractLogger implementation.
|
||||
// For documentation please refer to the DDAbstractLogger implementation.
|
||||
|
||||
if ([self isOnInternalLoggerQueue])
|
||||
{
|
||||
block();
|
||||
}
|
||||
else
|
||||
{
|
||||
dispatch_queue_t globalLoggingQueue = [DDLog loggingQueue];
|
||||
NSAssert(![self isOnGlobalLoggingQueue], @"Core architecture requirement failure");
|
||||
|
||||
dispatch_async(globalLoggingQueue, ^{
|
||||
dispatch_async(loggerQueue, block);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark Public API
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
- (void)savePendingLogEntries
|
||||
{
|
||||
dispatch_block_t block = ^{ @autoreleasepool {
|
||||
|
||||
[self performSaveAndSuspendSaveTimer];
|
||||
}};
|
||||
|
||||
if ([self isOnInternalLoggerQueue])
|
||||
block();
|
||||
else
|
||||
dispatch_async(loggerQueue, block);
|
||||
}
|
||||
|
||||
- (void)deleteOldLogEntries
|
||||
{
|
||||
dispatch_block_t block = ^{ @autoreleasepool {
|
||||
|
||||
[self performDelete];
|
||||
}};
|
||||
|
||||
if ([self isOnInternalLoggerQueue])
|
||||
block();
|
||||
else
|
||||
dispatch_async(loggerQueue, block);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark DDLogger
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
- (void)didAddLogger
|
||||
{
|
||||
// If you override me be sure to invoke [super didAddLogger];
|
||||
|
||||
[self createSuspendedSaveTimer];
|
||||
|
||||
[self createAndStartDeleteTimer];
|
||||
}
|
||||
|
||||
- (void)willRemoveLogger
|
||||
{
|
||||
// If you override me be sure to invoke [super willRemoveLogger];
|
||||
|
||||
[self performSaveAndSuspendSaveTimer];
|
||||
|
||||
[self destroySaveTimer];
|
||||
[self destroyDeleteTimer];
|
||||
}
|
||||
|
||||
- (void)logMessage:(DDLogMessage *)logMessage
|
||||
{
|
||||
if ([self db_log:logMessage])
|
||||
{
|
||||
BOOL firstUnsavedEntry = (++unsavedCount == 1);
|
||||
|
||||
if ((unsavedCount >= saveThreshold) && (saveThreshold > 0))
|
||||
{
|
||||
[self performSaveAndSuspendSaveTimer];
|
||||
}
|
||||
else if (firstUnsavedEntry)
|
||||
{
|
||||
unsavedTime = dispatch_time(DISPATCH_TIME_NOW, 0);
|
||||
[self updateAndResumeSaveTimer];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)flush
|
||||
{
|
||||
// This method is invoked by DDLog's flushLog method.
|
||||
//
|
||||
// It is called automatically when the application quits,
|
||||
// or if the developer invokes DDLog's flushLog method prior to crashing or something.
|
||||
|
||||
[self performSaveAndSuspendSaveTimer];
|
||||
}
|
||||
|
||||
@end
|
334
xplan-ios/Base/Tool/CocoaHttpServer/Vendor/CocoaLumberjack/DDFileLogger.h
vendored
Executable file
334
xplan-ios/Base/Tool/CocoaHttpServer/Vendor/CocoaLumberjack/DDFileLogger.h
vendored
Executable file
@@ -0,0 +1,334 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "DDLog.h"
|
||||
|
||||
@class DDLogFileInfo;
|
||||
|
||||
/**
|
||||
* Welcome to Cocoa Lumberjack!
|
||||
*
|
||||
* The project page has a wealth of documentation if you have any questions.
|
||||
* https://github.com/robbiehanson/CocoaLumberjack
|
||||
*
|
||||
* If you're new to the project you may wish to read the "Getting Started" wiki.
|
||||
* https://github.com/robbiehanson/CocoaLumberjack/wiki/GettingStarted
|
||||
*
|
||||
*
|
||||
* This class provides a logger to write log statements to a file.
|
||||
**/
|
||||
|
||||
|
||||
// Default configuration and safety/sanity values.
|
||||
//
|
||||
// maximumFileSize -> DEFAULT_LOG_MAX_FILE_SIZE
|
||||
// rollingFrequency -> DEFAULT_LOG_ROLLING_FREQUENCY
|
||||
// maximumNumberOfLogFiles -> DEFAULT_LOG_MAX_NUM_LOG_FILES
|
||||
//
|
||||
// You should carefully consider the proper configuration values for your application.
|
||||
|
||||
#define DEFAULT_LOG_MAX_FILE_SIZE (1024 * 1024) // 1 MB
|
||||
#define DEFAULT_LOG_ROLLING_FREQUENCY (60 * 60 * 24) // 24 Hours
|
||||
#define DEFAULT_LOG_MAX_NUM_LOG_FILES (5) // 5 Files
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark -
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// The LogFileManager protocol is designed to allow you to control all aspects of your log files.
|
||||
//
|
||||
// The primary purpose of this is to allow you to do something with the log files after they have been rolled.
|
||||
// Perhaps you want to compress them to save disk space.
|
||||
// Perhaps you want to upload them to an FTP server.
|
||||
// Perhaps you want to run some analytics on the file.
|
||||
//
|
||||
// A default LogFileManager is, of course, provided.
|
||||
// The default LogFileManager simply deletes old log files according to the maximumNumberOfLogFiles property.
|
||||
//
|
||||
// This protocol provides various methods to fetch the list of log files.
|
||||
//
|
||||
// There are two variants: sorted and unsorted.
|
||||
// If sorting is not necessary, the unsorted variant is obviously faster.
|
||||
// The sorted variant will return an array sorted by when the log files were created,
|
||||
// with the most recently created log file at index 0, and the oldest log file at the end of the array.
|
||||
//
|
||||
// You can fetch only the log file paths (full path including name), log file names (name only),
|
||||
// or an array of DDLogFileInfo objects.
|
||||
// The DDLogFileInfo class is documented below, and provides a handy wrapper that
|
||||
// gives you easy access to various file attributes such as the creation date or the file size.
|
||||
|
||||
@protocol DDLogFileManager <NSObject>
|
||||
@required
|
||||
|
||||
// Public properties
|
||||
|
||||
/**
|
||||
* The maximum number of archived log files to keep on disk.
|
||||
* For example, if this property is set to 3,
|
||||
* then the LogFileManager will only keep 3 archived log files (plus the current active log file) on disk.
|
||||
* Once the active log file is rolled/archived, then the oldest of the existing 3 rolled/archived log files is deleted.
|
||||
*
|
||||
* You may optionally disable deleting old/rolled/archived log files by setting this property to zero.
|
||||
**/
|
||||
@property (readwrite, assign) NSUInteger maximumNumberOfLogFiles;
|
||||
|
||||
// Public methods
|
||||
|
||||
- (NSString *)logsDirectory;
|
||||
|
||||
- (NSArray *)unsortedLogFilePaths;
|
||||
- (NSArray *)unsortedLogFileNames;
|
||||
- (NSArray *)unsortedLogFileInfos;
|
||||
|
||||
- (NSArray *)sortedLogFilePaths;
|
||||
- (NSArray *)sortedLogFileNames;
|
||||
- (NSArray *)sortedLogFileInfos;
|
||||
|
||||
// Private methods (only to be used by DDFileLogger)
|
||||
|
||||
- (NSString *)createNewLogFile;
|
||||
|
||||
@optional
|
||||
|
||||
// Notifications from DDFileLogger
|
||||
|
||||
- (void)didArchiveLogFile:(NSString *)logFilePath;
|
||||
- (void)didRollAndArchiveLogFile:(NSString *)logFilePath;
|
||||
|
||||
@end
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark -
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Default log file manager.
|
||||
*
|
||||
* All log files are placed inside the logsDirectory.
|
||||
* If a specific logsDirectory isn't specified, the default directory is used.
|
||||
* On Mac, this is in ~/Library/Logs/<Application Name>.
|
||||
* On iPhone, this is in ~/Library/Caches/Logs.
|
||||
*
|
||||
* Log files are named "log-<uuid>.txt",
|
||||
* where uuid is a 6 character hexadecimal consisting of the set [0123456789ABCDEF].
|
||||
*
|
||||
* Archived log files are automatically deleted according to the maximumNumberOfLogFiles property.
|
||||
**/
|
||||
@interface DDLogFileManagerDefault : NSObject <DDLogFileManager>
|
||||
{
|
||||
NSUInteger maximumNumberOfLogFiles;
|
||||
NSString *_logsDirectory;
|
||||
}
|
||||
|
||||
- (id)init;
|
||||
- (id)initWithLogsDirectory:(NSString *)logsDirectory;
|
||||
|
||||
/* Inherited from DDLogFileManager protocol:
|
||||
|
||||
@property (readwrite, assign) NSUInteger maximumNumberOfLogFiles;
|
||||
|
||||
- (NSString *)logsDirectory;
|
||||
|
||||
- (NSArray *)unsortedLogFilePaths;
|
||||
- (NSArray *)unsortedLogFileNames;
|
||||
- (NSArray *)unsortedLogFileInfos;
|
||||
|
||||
- (NSArray *)sortedLogFilePaths;
|
||||
- (NSArray *)sortedLogFileNames;
|
||||
- (NSArray *)sortedLogFileInfos;
|
||||
|
||||
*/
|
||||
|
||||
@end
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark -
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Most users will want file log messages to be prepended with the date and time.
|
||||
* Rather than forcing the majority of users to write their own formatter,
|
||||
* we will supply a logical default formatter.
|
||||
* Users can easily replace this formatter with their own by invoking the setLogFormatter method.
|
||||
* It can also be removed by calling setLogFormatter, and passing a nil parameter.
|
||||
*
|
||||
* In addition to the convenience of having a logical default formatter,
|
||||
* it will also provide a template that makes it easy for developers to copy and change.
|
||||
**/
|
||||
@interface DDLogFileFormatterDefault : NSObject <DDLogFormatter>
|
||||
{
|
||||
NSDateFormatter *dateFormatter;
|
||||
}
|
||||
|
||||
- (id)init;
|
||||
- (id)initWithDateFormatter:(NSDateFormatter *)dateFormatter;
|
||||
|
||||
@end
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark -
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@interface DDFileLogger : DDAbstractLogger <DDLogger>
|
||||
{
|
||||
__strong id <DDLogFileManager> logFileManager;
|
||||
|
||||
DDLogFileInfo *currentLogFileInfo;
|
||||
NSFileHandle *currentLogFileHandle;
|
||||
|
||||
dispatch_source_t rollingTimer;
|
||||
|
||||
unsigned long long maximumFileSize;
|
||||
NSTimeInterval rollingFrequency;
|
||||
}
|
||||
|
||||
- (id)init;
|
||||
- (id)initWithLogFileManager:(id <DDLogFileManager>)logFileManager;
|
||||
|
||||
/**
|
||||
* Log File Rolling:
|
||||
*
|
||||
* maximumFileSize:
|
||||
* The approximate maximum size to allow log files to grow.
|
||||
* If a log file is larger than this value after a log statement is appended,
|
||||
* then the log file is rolled.
|
||||
*
|
||||
* rollingFrequency
|
||||
* How often to roll the log file.
|
||||
* The frequency is given as an NSTimeInterval, which is a double that specifies the interval in seconds.
|
||||
* Once the log file gets to be this old, it is rolled.
|
||||
*
|
||||
* Both the maximumFileSize and the rollingFrequency are used to manage rolling.
|
||||
* Whichever occurs first will cause the log file to be rolled.
|
||||
*
|
||||
* For example:
|
||||
* The rollingFrequency is 24 hours,
|
||||
* but the log file surpasses the maximumFileSize after only 20 hours.
|
||||
* The log file will be rolled at that 20 hour mark.
|
||||
* A new log file will be created, and the 24 hour timer will be restarted.
|
||||
*
|
||||
* You may optionally disable rolling due to filesize by setting maximumFileSize to zero.
|
||||
* If you do so, rolling is based solely on rollingFrequency.
|
||||
*
|
||||
* You may optionally disable rolling due to time by setting rollingFrequency to zero (or any non-positive number).
|
||||
* If you do so, rolling is based solely on maximumFileSize.
|
||||
*
|
||||
* If you disable both maximumFileSize and rollingFrequency, then the log file won't ever be rolled.
|
||||
* This is strongly discouraged.
|
||||
**/
|
||||
@property (readwrite, assign) unsigned long long maximumFileSize;
|
||||
@property (readwrite, assign) NSTimeInterval rollingFrequency;
|
||||
|
||||
/**
|
||||
* The DDLogFileManager instance can be used to retrieve the list of log files,
|
||||
* and configure the maximum number of archived log files to keep.
|
||||
*
|
||||
* @see DDLogFileManager.maximumNumberOfLogFiles
|
||||
**/
|
||||
@property (strong, nonatomic, readonly) id <DDLogFileManager> logFileManager;
|
||||
|
||||
|
||||
// You can optionally force the current log file to be rolled with this method.
|
||||
|
||||
- (void)rollLogFile;
|
||||
|
||||
// Inherited from DDAbstractLogger
|
||||
|
||||
// - (id <DDLogFormatter>)logFormatter;
|
||||
// - (void)setLogFormatter:(id <DDLogFormatter>)formatter;
|
||||
|
||||
@end
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark -
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* DDLogFileInfo is a simple class that provides access to various file attributes.
|
||||
* It provides good performance as it only fetches the information if requested,
|
||||
* and it caches the information to prevent duplicate fetches.
|
||||
*
|
||||
* It was designed to provide quick snapshots of the current state of log files,
|
||||
* and to help sort log files in an array.
|
||||
*
|
||||
* This class does not monitor the files, or update it's cached attribute values if the file changes on disk.
|
||||
* This is not what the class was designed for.
|
||||
*
|
||||
* If you absolutely must get updated values,
|
||||
* you can invoke the reset method which will clear the cache.
|
||||
**/
|
||||
@interface DDLogFileInfo : NSObject
|
||||
{
|
||||
__strong NSString *filePath;
|
||||
__strong NSString *fileName;
|
||||
|
||||
__strong NSDictionary *fileAttributes;
|
||||
|
||||
__strong NSDate *creationDate;
|
||||
__strong NSDate *modificationDate;
|
||||
|
||||
unsigned long long fileSize;
|
||||
}
|
||||
|
||||
@property (strong, nonatomic, readonly) NSString *filePath;
|
||||
@property (strong, nonatomic, readonly) NSString *fileName;
|
||||
|
||||
@property (strong, nonatomic, readonly) NSDictionary *fileAttributes;
|
||||
|
||||
@property (strong, nonatomic, readonly) NSDate *creationDate;
|
||||
@property (strong, nonatomic, readonly) NSDate *modificationDate;
|
||||
|
||||
@property (nonatomic, readonly) unsigned long long fileSize;
|
||||
|
||||
@property (nonatomic, readonly) NSTimeInterval age;
|
||||
|
||||
@property (nonatomic, readwrite) BOOL isArchived;
|
||||
|
||||
+ (id)logFileWithPath:(NSString *)filePath;
|
||||
|
||||
- (id)initWithFilePath:(NSString *)filePath;
|
||||
|
||||
- (void)reset;
|
||||
- (void)renameFile:(NSString *)newFileName;
|
||||
|
||||
#if TARGET_IPHONE_SIMULATOR
|
||||
|
||||
// So here's the situation.
|
||||
// Extended attributes are perfect for what we're trying to do here (marking files as archived).
|
||||
// This is exactly what extended attributes were designed for.
|
||||
//
|
||||
// But Apple screws us over on the simulator.
|
||||
// Everytime you build-and-go, they copy the application into a new folder on the hard drive,
|
||||
// and as part of the process they strip extended attributes from our log files.
|
||||
// Normally, a copy of a file preserves extended attributes.
|
||||
// So obviously Apple has gone to great lengths to piss us off.
|
||||
//
|
||||
// Thus we use a slightly different tactic for marking log files as archived in the simulator.
|
||||
// That way it "just works" and there's no confusion when testing.
|
||||
//
|
||||
// The difference in method names is indicative of the difference in functionality.
|
||||
// On the simulator we add an attribute by appending a filename extension.
|
||||
//
|
||||
// For example:
|
||||
// log-ABC123.txt -> log-ABC123.archived.txt
|
||||
|
||||
- (BOOL)hasExtensionAttributeWithName:(NSString *)attrName;
|
||||
|
||||
- (void)addExtensionAttributeWithName:(NSString *)attrName;
|
||||
- (void)removeExtensionAttributeWithName:(NSString *)attrName;
|
||||
|
||||
#else
|
||||
|
||||
// Normal use of extended attributes used everywhere else,
|
||||
// such as on Macs and on iPhone devices.
|
||||
|
||||
- (BOOL)hasExtendedAttributeWithName:(NSString *)attrName;
|
||||
|
||||
- (void)addExtendedAttributeWithName:(NSString *)attrName;
|
||||
- (void)removeExtendedAttributeWithName:(NSString *)attrName;
|
||||
|
||||
#endif
|
||||
|
||||
- (NSComparisonResult)reverseCompareByCreationDate:(DDLogFileInfo *)another;
|
||||
- (NSComparisonResult)reverseCompareByModificationDate:(DDLogFileInfo *)another;
|
||||
|
||||
@end
|
1356
xplan-ios/Base/Tool/CocoaHttpServer/Vendor/CocoaLumberjack/DDFileLogger.m
vendored
Executable file
1356
xplan-ios/Base/Tool/CocoaHttpServer/Vendor/CocoaLumberjack/DDFileLogger.m
vendored
Executable file
File diff suppressed because it is too large
Load Diff
601
xplan-ios/Base/Tool/CocoaHttpServer/Vendor/CocoaLumberjack/DDLog.h
vendored
Executable file
601
xplan-ios/Base/Tool/CocoaHttpServer/Vendor/CocoaLumberjack/DDLog.h
vendored
Executable file
@@ -0,0 +1,601 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
/**
|
||||
* Welcome to Cocoa Lumberjack!
|
||||
*
|
||||
* The project page has a wealth of documentation if you have any questions.
|
||||
* https://github.com/robbiehanson/CocoaLumberjack
|
||||
*
|
||||
* If you're new to the project you may wish to read the "Getting Started" wiki.
|
||||
* https://github.com/robbiehanson/CocoaLumberjack/wiki/GettingStarted
|
||||
*
|
||||
* Otherwise, here is a quick refresher.
|
||||
* There are three steps to using the macros:
|
||||
*
|
||||
* Step 1:
|
||||
* Import the header in your implementation file:
|
||||
*
|
||||
* #import "DDLog.h"
|
||||
*
|
||||
* Step 2:
|
||||
* Define your logging level in your implementation file:
|
||||
*
|
||||
* // Log levels: off, error, warn, info, verbose
|
||||
* static const int ddLogLevel = LOG_LEVEL_VERBOSE;
|
||||
*
|
||||
* Step 3:
|
||||
* Replace your NSLog statements with DDLog statements according to the severity of the message.
|
||||
*
|
||||
* NSLog(@"Fatal error, no dohickey found!"); -> DDLogError(@"Fatal error, no dohickey found!");
|
||||
*
|
||||
* DDLog works exactly the same as NSLog.
|
||||
* This means you can pass it multiple variables just like NSLog.
|
||||
**/
|
||||
|
||||
|
||||
@class DDLogMessage;
|
||||
|
||||
@protocol DDLogger;
|
||||
@protocol DDLogFormatter;
|
||||
|
||||
/**
|
||||
* This is the single macro that all other macros below compile into.
|
||||
* This big multiline macro makes all the other macros easier to read.
|
||||
**/
|
||||
|
||||
#define LOG_MACRO(isAsynchronous, lvl, flg, ctx, atag, fnct, frmt, ...) \
|
||||
[DDLog log:isAsynchronous \
|
||||
level:lvl \
|
||||
flag:flg \
|
||||
context:ctx \
|
||||
file:__FILE__ \
|
||||
function:fnct \
|
||||
line:__LINE__ \
|
||||
tag:atag \
|
||||
format:(frmt), ##__VA_ARGS__]
|
||||
|
||||
/**
|
||||
* Define the Objective-C and C versions of the macro.
|
||||
* These automatically inject the proper function name for either an objective-c method or c function.
|
||||
*
|
||||
* We also define shorthand versions for asynchronous and synchronous logging.
|
||||
**/
|
||||
|
||||
#define LOG_OBJC_MACRO(async, lvl, flg, ctx, frmt, ...) \
|
||||
LOG_MACRO(async, lvl, flg, ctx, nil, sel_getName(_cmd), frmt, ##__VA_ARGS__)
|
||||
|
||||
#define LOG_C_MACRO(async, lvl, flg, ctx, frmt, ...) \
|
||||
LOG_MACRO(async, lvl, flg, ctx, nil, __FUNCTION__, frmt, ##__VA_ARGS__)
|
||||
|
||||
#define SYNC_LOG_OBJC_MACRO(lvl, flg, ctx, frmt, ...) \
|
||||
LOG_OBJC_MACRO( NO, lvl, flg, ctx, frmt, ##__VA_ARGS__)
|
||||
|
||||
#define ASYNC_LOG_OBJC_MACRO(lvl, flg, ctx, frmt, ...) \
|
||||
LOG_OBJC_MACRO(YES, lvl, flg, ctx, frmt, ##__VA_ARGS__)
|
||||
|
||||
#define SYNC_LOG_C_MACRO(lvl, flg, ctx, frmt, ...) \
|
||||
LOG_C_MACRO( NO, lvl, flg, ctx, frmt, ##__VA_ARGS__)
|
||||
|
||||
#define ASYNC_LOG_C_MACRO(lvl, flg, ctx, frmt, ...) \
|
||||
LOG_C_MACRO(YES, lvl, flg, ctx, frmt, ##__VA_ARGS__)
|
||||
|
||||
/**
|
||||
* Define version of the macro that only execute if the logLevel is above the threshold.
|
||||
* The compiled versions essentially look like this:
|
||||
*
|
||||
* if (logFlagForThisLogMsg & ddLogLevel) { execute log message }
|
||||
*
|
||||
* As shown further below, Lumberjack actually uses a bitmask as opposed to primitive log levels.
|
||||
* This allows for a great amount of flexibility and some pretty advanced fine grained logging techniques.
|
||||
*
|
||||
* Note that when compiler optimizations are enabled (as they are for your release builds),
|
||||
* the log messages above your logging threshold will automatically be compiled out.
|
||||
*
|
||||
* (If the compiler sees ddLogLevel declared as a constant, the compiler simply checks to see if the 'if' statement
|
||||
* would execute, and if not it strips it from the binary.)
|
||||
*
|
||||
* We also define shorthand versions for asynchronous and synchronous logging.
|
||||
**/
|
||||
|
||||
#define LOG_MAYBE(async, lvl, flg, ctx, fnct, frmt, ...) \
|
||||
do { if(lvl & flg) LOG_MACRO(async, lvl, flg, ctx, nil, fnct, frmt, ##__VA_ARGS__); } while(0)
|
||||
|
||||
#define LOG_OBJC_MAYBE(async, lvl, flg, ctx, frmt, ...) \
|
||||
LOG_MAYBE(async, lvl, flg, ctx, sel_getName(_cmd), frmt, ##__VA_ARGS__)
|
||||
|
||||
#define LOG_C_MAYBE(async, lvl, flg, ctx, frmt, ...) \
|
||||
LOG_MAYBE(async, lvl, flg, ctx, __FUNCTION__, frmt, ##__VA_ARGS__)
|
||||
|
||||
#define SYNC_LOG_OBJC_MAYBE(lvl, flg, ctx, frmt, ...) \
|
||||
LOG_OBJC_MAYBE( NO, lvl, flg, ctx, frmt, ##__VA_ARGS__)
|
||||
|
||||
#define ASYNC_LOG_OBJC_MAYBE(lvl, flg, ctx, frmt, ...) \
|
||||
LOG_OBJC_MAYBE(YES, lvl, flg, ctx, frmt, ##__VA_ARGS__)
|
||||
|
||||
#define SYNC_LOG_C_MAYBE(lvl, flg, ctx, frmt, ...) \
|
||||
LOG_C_MAYBE( NO, lvl, flg, ctx, frmt, ##__VA_ARGS__)
|
||||
|
||||
#define ASYNC_LOG_C_MAYBE(lvl, flg, ctx, frmt, ...) \
|
||||
LOG_C_MAYBE(YES, lvl, flg, ctx, frmt, ##__VA_ARGS__)
|
||||
|
||||
/**
|
||||
* Define versions of the macros that also accept tags.
|
||||
*
|
||||
* The DDLogMessage object includes a 'tag' ivar that may be used for a variety of purposes.
|
||||
* It may be used to pass custom information to loggers or formatters.
|
||||
* Or it may be used by 3rd party extensions to the framework.
|
||||
*
|
||||
* Thes macros just make it a little easier to extend logging functionality.
|
||||
**/
|
||||
|
||||
#define LOG_OBJC_TAG_MACRO(async, lvl, flg, ctx, tag, frmt, ...) \
|
||||
LOG_MACRO(async, lvl, flg, ctx, tag, sel_getName(_cmd), frmt, ##__VA_ARGS__)
|
||||
|
||||
#define LOG_C_TAG_MACRO(async, lvl, flg, ctx, tag, frmt, ...) \
|
||||
LOG_MACRO(async, lvl, flg, ctx, tag, __FUNCTION__, frmt, ##__VA_ARGS__)
|
||||
|
||||
#define LOG_TAG_MAYBE(async, lvl, flg, ctx, tag, fnct, frmt, ...) \
|
||||
do { if(lvl & flg) LOG_MACRO(async, lvl, flg, ctx, tag, fnct, frmt, ##__VA_ARGS__); } while(0)
|
||||
|
||||
#define LOG_OBJC_TAG_MAYBE(async, lvl, flg, ctx, tag, frmt, ...) \
|
||||
LOG_TAG_MAYBE(async, lvl, flg, ctx, tag, sel_getName(_cmd), frmt, ##__VA_ARGS__)
|
||||
|
||||
#define LOG_C_TAG_MAYBE(async, lvl, flg, ctx, tag, frmt, ...) \
|
||||
LOG_TAG_MAYBE(async, lvl, flg, ctx, tag, __FUNCTION__, frmt, ##__VA_ARGS__)
|
||||
|
||||
/**
|
||||
* Define the standard options.
|
||||
*
|
||||
* We default to only 4 levels because it makes it easier for beginners
|
||||
* to make the transition to a logging framework.
|
||||
*
|
||||
* More advanced users may choose to completely customize the levels (and level names) to suite their needs.
|
||||
* For more information on this see the "Custom Log Levels" page:
|
||||
* https://github.com/robbiehanson/CocoaLumberjack/wiki/CustomLogLevels
|
||||
*
|
||||
* Advanced users may also notice that we're using a bitmask.
|
||||
* This is to allow for custom fine grained logging:
|
||||
* https://github.com/robbiehanson/CocoaLumberjack/wiki/FineGrainedLogging
|
||||
*
|
||||
* -- Flags --
|
||||
*
|
||||
* Typically you will use the LOG_LEVELS (see below), but the flags may be used directly in certain situations.
|
||||
* For example, say you have a lot of warning log messages, and you wanted to disable them.
|
||||
* However, you still needed to see your error and info log messages.
|
||||
* You could accomplish that with the following:
|
||||
*
|
||||
* static const int ddLogLevel = LOG_FLAG_ERROR | LOG_FLAG_INFO;
|
||||
*
|
||||
* Flags may also be consulted when writing custom log formatters,
|
||||
* as the DDLogMessage class captures the individual flag that caused the log message to fire.
|
||||
*
|
||||
* -- Levels --
|
||||
*
|
||||
* Log levels are simply the proper bitmask of the flags.
|
||||
*
|
||||
* -- Booleans --
|
||||
*
|
||||
* The booleans may be used when your logging code involves more than one line.
|
||||
* For example:
|
||||
*
|
||||
* if (LOG_VERBOSE) {
|
||||
* for (id sprocket in sprockets)
|
||||
* DDLogVerbose(@"sprocket: %@", [sprocket description])
|
||||
* }
|
||||
*
|
||||
* -- Async --
|
||||
*
|
||||
* Defines the default asynchronous options.
|
||||
* The default philosophy for asynchronous logging is very simple:
|
||||
*
|
||||
* Log messages with errors should be executed synchronously.
|
||||
* After all, an error just occurred. The application could be unstable.
|
||||
*
|
||||
* All other log messages, such as debug output, are executed asynchronously.
|
||||
* After all, if it wasn't an error, then it was just informational output,
|
||||
* or something the application was easily able to recover from.
|
||||
*
|
||||
* -- Changes --
|
||||
*
|
||||
* You are strongly discouraged from modifying this file.
|
||||
* If you do, you make it more difficult on yourself to merge future bug fixes and improvements from the project.
|
||||
* Instead, create your own MyLogging.h or ApplicationNameLogging.h or CompanyLogging.h
|
||||
*
|
||||
* For an example of customizing your logging experience, see the "Custom Log Levels" page:
|
||||
* https://github.com/robbiehanson/CocoaLumberjack/wiki/CustomLogLevels
|
||||
**/
|
||||
|
||||
#define LOG_FLAG_ERROR (1 << 0) // 0...0001
|
||||
#define LOG_FLAG_WARN (1 << 1) // 0...0010
|
||||
#define LOG_FLAG_INFO (1 << 2) // 0...0100
|
||||
#define LOG_FLAG_VERBOSE (1 << 3) // 0...1000
|
||||
|
||||
#define LOG_LEVEL_OFF 0
|
||||
#define LOG_LEVEL_ERROR (LOG_FLAG_ERROR) // 0...0001
|
||||
#define LOG_LEVEL_WARN (LOG_FLAG_ERROR | LOG_FLAG_WARN) // 0...0011
|
||||
#define LOG_LEVEL_INFO (LOG_FLAG_ERROR | LOG_FLAG_WARN | LOG_FLAG_INFO) // 0...0111
|
||||
#define LOG_LEVEL_VERBOSE (LOG_FLAG_ERROR | LOG_FLAG_WARN | LOG_FLAG_INFO | LOG_FLAG_VERBOSE) // 0...1111
|
||||
|
||||
#define LOG_ERROR (ddLogLevel & LOG_FLAG_ERROR)
|
||||
#define LOG_WARN (ddLogLevel & LOG_FLAG_WARN)
|
||||
#define LOG_INFO (ddLogLevel & LOG_FLAG_INFO)
|
||||
#define LOG_VERBOSE (ddLogLevel & LOG_FLAG_VERBOSE)
|
||||
|
||||
#define LOG_ASYNC_ENABLED YES
|
||||
|
||||
#define LOG_ASYNC_ERROR ( NO && LOG_ASYNC_ENABLED)
|
||||
#define LOG_ASYNC_WARN (YES && LOG_ASYNC_ENABLED)
|
||||
#define LOG_ASYNC_INFO (YES && LOG_ASYNC_ENABLED)
|
||||
#define LOG_ASYNC_VERBOSE (YES && LOG_ASYNC_ENABLED)
|
||||
|
||||
#define DDLogError(frmt, ...) LOG_OBJC_MAYBE(LOG_ASYNC_ERROR, ddLogLevel, LOG_FLAG_ERROR, 0, frmt, ##__VA_ARGS__)
|
||||
#define DDLogWarn(frmt, ...) LOG_OBJC_MAYBE(LOG_ASYNC_WARN, ddLogLevel, LOG_FLAG_WARN, 0, frmt, ##__VA_ARGS__)
|
||||
#define DDLogInfo(frmt, ...) LOG_OBJC_MAYBE(LOG_ASYNC_INFO, ddLogLevel, LOG_FLAG_INFO, 0, frmt, ##__VA_ARGS__)
|
||||
#define DDLogVerbose(frmt, ...) LOG_OBJC_MAYBE(LOG_ASYNC_VERBOSE, ddLogLevel, LOG_FLAG_VERBOSE, 0, frmt, ##__VA_ARGS__)
|
||||
|
||||
#define DDLogCError(frmt, ...) LOG_C_MAYBE(LOG_ASYNC_ERROR, ddLogLevel, LOG_FLAG_ERROR, 0, frmt, ##__VA_ARGS__)
|
||||
#define DDLogCWarn(frmt, ...) LOG_C_MAYBE(LOG_ASYNC_WARN, ddLogLevel, LOG_FLAG_WARN, 0, frmt, ##__VA_ARGS__)
|
||||
#define DDLogCInfo(frmt, ...) LOG_C_MAYBE(LOG_ASYNC_INFO, ddLogLevel, LOG_FLAG_INFO, 0, frmt, ##__VA_ARGS__)
|
||||
#define DDLogCVerbose(frmt, ...) LOG_C_MAYBE(LOG_ASYNC_VERBOSE, ddLogLevel, LOG_FLAG_VERBOSE, 0, frmt, ##__VA_ARGS__)
|
||||
|
||||
/**
|
||||
* The THIS_FILE macro gives you an NSString of the file name.
|
||||
* For simplicity and clarity, the file name does not include the full path or file extension.
|
||||
*
|
||||
* For example: DDLogWarn(@"%@: Unable to find thingy", THIS_FILE) -> @"MyViewController: Unable to find thingy"
|
||||
**/
|
||||
|
||||
NSString *DDExtractFileNameWithoutExtension(const char *filePath, BOOL copy);
|
||||
|
||||
#define THIS_FILE (DDExtractFileNameWithoutExtension(__FILE__, NO))
|
||||
|
||||
/**
|
||||
* The THIS_METHOD macro gives you the name of the current objective-c method.
|
||||
*
|
||||
* For example: DDLogWarn(@"%@ - Requires non-nil strings", THIS_METHOD) -> @"setMake:model: requires non-nil strings"
|
||||
*
|
||||
* Note: This does NOT work in straight C functions (non objective-c).
|
||||
* Instead you should use the predefined __FUNCTION__ macro.
|
||||
**/
|
||||
|
||||
#define THIS_METHOD NSStringFromSelector(_cmd)
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark -
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@interface DDLog : NSObject
|
||||
|
||||
/**
|
||||
* Provides access to the underlying logging queue.
|
||||
* This may be helpful to Logger classes for things like thread synchronization.
|
||||
**/
|
||||
|
||||
+ (dispatch_queue_t)loggingQueue;
|
||||
|
||||
/**
|
||||
* Logging Primitive.
|
||||
*
|
||||
* This method is used by the macros above.
|
||||
* It is suggested you stick with the macros as they're easier to use.
|
||||
**/
|
||||
|
||||
+ (void)log:(BOOL)synchronous
|
||||
level:(int)level
|
||||
flag:(int)flag
|
||||
context:(int)context
|
||||
file:(const char *)file
|
||||
function:(const char *)function
|
||||
line:(int)line
|
||||
tag:(id)tag
|
||||
format:(NSString *)format, ... __attribute__ ((format (__NSString__, 9, 10)));
|
||||
|
||||
/**
|
||||
* Logging Primitive.
|
||||
*
|
||||
* This method can be used if you have a prepared va_list.
|
||||
**/
|
||||
|
||||
+ (void)log:(BOOL)asynchronous
|
||||
level:(int)level
|
||||
flag:(int)flag
|
||||
context:(int)context
|
||||
file:(const char *)file
|
||||
function:(const char *)function
|
||||
line:(int)line
|
||||
tag:(id)tag
|
||||
format:(NSString *)format
|
||||
args:(va_list)argList;
|
||||
|
||||
|
||||
/**
|
||||
* Since logging can be asynchronous, there may be times when you want to flush the logs.
|
||||
* The framework invokes this automatically when the application quits.
|
||||
**/
|
||||
|
||||
+ (void)flushLog;
|
||||
|
||||
/**
|
||||
* Loggers
|
||||
*
|
||||
* If you want your log statements to go somewhere,
|
||||
* you should create and add a logger.
|
||||
**/
|
||||
|
||||
+ (void)addLogger:(id <DDLogger>)logger;
|
||||
+ (void)removeLogger:(id <DDLogger>)logger;
|
||||
|
||||
+ (void)removeAllLoggers;
|
||||
|
||||
/**
|
||||
* Registered Dynamic Logging
|
||||
*
|
||||
* These methods allow you to obtain a list of classes that are using registered dynamic logging,
|
||||
* and also provides methods to get and set their log level during run time.
|
||||
**/
|
||||
|
||||
+ (NSArray *)registeredClasses;
|
||||
+ (NSArray *)registeredClassNames;
|
||||
|
||||
+ (int)logLevelForClass:(Class)aClass;
|
||||
+ (int)logLevelForClassWithName:(NSString *)aClassName;
|
||||
|
||||
+ (void)setLogLevel:(int)logLevel forClass:(Class)aClass;
|
||||
+ (void)setLogLevel:(int)logLevel forClassWithName:(NSString *)aClassName;
|
||||
|
||||
@end
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark -
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@protocol DDLogger <NSObject>
|
||||
@required
|
||||
|
||||
- (void)logMessage:(DDLogMessage *)logMessage;
|
||||
|
||||
/**
|
||||
* Formatters may optionally be added to any logger.
|
||||
*
|
||||
* If no formatter is set, the logger simply logs the message as it is given in logMessage,
|
||||
* or it may use its own built in formatting style.
|
||||
**/
|
||||
- (id <DDLogFormatter>)logFormatter;
|
||||
- (void)setLogFormatter:(id <DDLogFormatter>)formatter;
|
||||
|
||||
@optional
|
||||
|
||||
/**
|
||||
* Since logging is asynchronous, adding and removing loggers is also asynchronous.
|
||||
* In other words, the loggers are added and removed at appropriate times with regards to log messages.
|
||||
*
|
||||
* - Loggers will not receive log messages that were executed prior to when they were added.
|
||||
* - Loggers will not receive log messages that were executed after they were removed.
|
||||
*
|
||||
* These methods are executed in the logging thread/queue.
|
||||
* This is the same thread/queue that will execute every logMessage: invocation.
|
||||
* Loggers may use these methods for thread synchronization or other setup/teardown tasks.
|
||||
**/
|
||||
- (void)didAddLogger;
|
||||
- (void)willRemoveLogger;
|
||||
|
||||
/**
|
||||
* Some loggers may buffer IO for optimization purposes.
|
||||
* For example, a database logger may only save occasionaly as the disk IO is slow.
|
||||
* In such loggers, this method should be implemented to flush any pending IO.
|
||||
*
|
||||
* This allows invocations of DDLog's flushLog method to be propogated to loggers that need it.
|
||||
*
|
||||
* Note that DDLog's flushLog method is invoked automatically when the application quits,
|
||||
* and it may be also invoked manually by the developer prior to application crashes, or other such reasons.
|
||||
**/
|
||||
- (void)flush;
|
||||
|
||||
/**
|
||||
* Each logger is executed concurrently with respect to the other loggers.
|
||||
* Thus, a dedicated dispatch queue is used for each logger.
|
||||
* Logger implementations may optionally choose to provide their own dispatch queue.
|
||||
**/
|
||||
- (dispatch_queue_t)loggerQueue;
|
||||
|
||||
/**
|
||||
* If the logger implementation does not choose to provide its own queue,
|
||||
* one will automatically be created for it.
|
||||
* The created queue will receive its name from this method.
|
||||
* This may be helpful for debugging or profiling reasons.
|
||||
**/
|
||||
- (NSString *)loggerName;
|
||||
|
||||
@end
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark -
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@protocol DDLogFormatter <NSObject>
|
||||
@required
|
||||
|
||||
/**
|
||||
* Formatters may optionally be added to any logger.
|
||||
* This allows for increased flexibility in the logging environment.
|
||||
* For example, log messages for log files may be formatted differently than log messages for the console.
|
||||
*
|
||||
* For more information about formatters, see the "Custom Formatters" page:
|
||||
* https://github.com/robbiehanson/CocoaLumberjack/wiki/CustomFormatters
|
||||
*
|
||||
* The formatter may also optionally filter the log message by returning nil,
|
||||
* in which case the logger will not log the message.
|
||||
**/
|
||||
- (NSString *)formatLogMessage:(DDLogMessage *)logMessage;
|
||||
|
||||
@optional
|
||||
|
||||
/**
|
||||
* A single formatter instance can be added to multiple loggers.
|
||||
* These methods provides hooks to notify the formatter of when it's added/removed.
|
||||
*
|
||||
* This is primarily for thread-safety.
|
||||
* If a formatter is explicitly not thread-safe, it may wish to throw an exception if added to multiple loggers.
|
||||
* Or if a formatter has potentially thread-unsafe code (e.g. NSDateFormatter),
|
||||
* it could possibly use these hooks to switch to thread-safe versions of the code.
|
||||
**/
|
||||
- (void)didAddToLogger:(id <DDLogger>)logger;
|
||||
- (void)willRemoveFromLogger:(id <DDLogger>)logger;
|
||||
|
||||
@end
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark -
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@protocol DDRegisteredDynamicLogging
|
||||
|
||||
/**
|
||||
* Implement these methods to allow a file's log level to be managed from a central location.
|
||||
*
|
||||
* This is useful if you'd like to be able to change log levels for various parts
|
||||
* of your code from within the running application.
|
||||
*
|
||||
* Imagine pulling up the settings for your application,
|
||||
* and being able to configure the logging level on a per file basis.
|
||||
*
|
||||
* The implementation can be very straight-forward:
|
||||
*
|
||||
* + (int)ddLogLevel
|
||||
* {
|
||||
* return ddLogLevel;
|
||||
* }
|
||||
*
|
||||
* + (void)ddSetLogLevel:(int)logLevel
|
||||
* {
|
||||
* ddLogLevel = logLevel;
|
||||
* }
|
||||
**/
|
||||
|
||||
+ (int)ddLogLevel;
|
||||
+ (void)ddSetLogLevel:(int)logLevel;
|
||||
|
||||
@end
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark -
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* The DDLogMessage class encapsulates information about the log message.
|
||||
* If you write custom loggers or formatters, you will be dealing with objects of this class.
|
||||
**/
|
||||
|
||||
enum {
|
||||
DDLogMessageCopyFile = 1 << 0,
|
||||
DDLogMessageCopyFunction = 1 << 1
|
||||
};
|
||||
typedef int DDLogMessageOptions;
|
||||
|
||||
@interface DDLogMessage : NSObject
|
||||
{
|
||||
|
||||
// The public variables below can be accessed directly (for speed).
|
||||
// For example: logMessage->logLevel
|
||||
|
||||
@public
|
||||
int logLevel;
|
||||
int logFlag;
|
||||
int logContext;
|
||||
NSString *logMsg;
|
||||
NSDate *timestamp;
|
||||
char *file;
|
||||
char *function;
|
||||
int lineNumber;
|
||||
mach_port_t machThreadID;
|
||||
char *queueLabel;
|
||||
NSString *threadName;
|
||||
|
||||
// For 3rd party extensions to the framework, where flags and contexts aren't enough.
|
||||
id tag;
|
||||
|
||||
// For 3rd party extensions that manually create DDLogMessage instances.
|
||||
DDLogMessageOptions options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Standard init method for a log message object.
|
||||
* Used by the logging primitives. (And the macros use the logging primitives.)
|
||||
*
|
||||
* If you find need to manually create logMessage objects, there is one thing you should be aware of:
|
||||
*
|
||||
* If no flags are passed, the method expects the file and function parameters to be string literals.
|
||||
* That is, it expects the given strings to exist for the duration of the object's lifetime,
|
||||
* and it expects the given strings to be immutable.
|
||||
* In other words, it does not copy these strings, it simply points to them.
|
||||
* This is due to the fact that __FILE__ and __FUNCTION__ are usually used to specify these parameters,
|
||||
* so it makes sense to optimize and skip the unnecessary allocations.
|
||||
* However, if you need them to be copied you may use the options parameter to specify this.
|
||||
* Options is a bitmask which supports DDLogMessageCopyFile and DDLogMessageCopyFunction.
|
||||
**/
|
||||
- (id)initWithLogMsg:(NSString *)logMsg
|
||||
level:(int)logLevel
|
||||
flag:(int)logFlag
|
||||
context:(int)logContext
|
||||
file:(const char *)file
|
||||
function:(const char *)function
|
||||
line:(int)line
|
||||
tag:(id)tag
|
||||
options:(DDLogMessageOptions)optionsMask;
|
||||
|
||||
/**
|
||||
* Returns the threadID as it appears in NSLog.
|
||||
* That is, it is a hexadecimal value which is calculated from the machThreadID.
|
||||
**/
|
||||
- (NSString *)threadID;
|
||||
|
||||
/**
|
||||
* Convenience property to get just the file name, as the file variable is generally the full file path.
|
||||
* This method does not include the file extension, which is generally unwanted for logging purposes.
|
||||
**/
|
||||
- (NSString *)fileName;
|
||||
|
||||
/**
|
||||
* Returns the function variable in NSString form.
|
||||
**/
|
||||
- (NSString *)methodName;
|
||||
|
||||
@end
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark -
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* The DDLogger protocol specifies that an optional formatter can be added to a logger.
|
||||
* Most (but not all) loggers will want to support formatters.
|
||||
*
|
||||
* However, writting getters and setters in a thread safe manner,
|
||||
* while still maintaining maximum speed for the logging process, is a difficult task.
|
||||
*
|
||||
* To do it right, the implementation of the getter/setter has strict requiremenets:
|
||||
* - Must NOT require the logMessage method to acquire a lock.
|
||||
* - Must NOT require the logMessage method to access an atomic property (also a lock of sorts).
|
||||
*
|
||||
* To simplify things, an abstract logger is provided that implements the getter and setter.
|
||||
*
|
||||
* Logger implementations may simply extend this class,
|
||||
* and they can ACCESS THE FORMATTER VARIABLE DIRECTLY from within their logMessage method!
|
||||
**/
|
||||
|
||||
@interface DDAbstractLogger : NSObject <DDLogger>
|
||||
{
|
||||
id <DDLogFormatter> formatter;
|
||||
|
||||
dispatch_queue_t loggerQueue;
|
||||
}
|
||||
|
||||
- (id <DDLogFormatter>)logFormatter;
|
||||
- (void)setLogFormatter:(id <DDLogFormatter>)formatter;
|
||||
|
||||
// For thread-safety assertions
|
||||
- (BOOL)isOnGlobalLoggingQueue;
|
||||
- (BOOL)isOnInternalLoggerQueue;
|
||||
|
||||
@end
|
1083
xplan-ios/Base/Tool/CocoaHttpServer/Vendor/CocoaLumberjack/DDLog.m
vendored
Executable file
1083
xplan-ios/Base/Tool/CocoaHttpServer/Vendor/CocoaLumberjack/DDLog.m
vendored
Executable file
File diff suppressed because it is too large
Load Diff
167
xplan-ios/Base/Tool/CocoaHttpServer/Vendor/CocoaLumberjack/DDTTYLogger.h
vendored
Executable file
167
xplan-ios/Base/Tool/CocoaHttpServer/Vendor/CocoaLumberjack/DDTTYLogger.h
vendored
Executable file
@@ -0,0 +1,167 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
#if TARGET_OS_IPHONE
|
||||
#import <UIKit/UIColor.h>
|
||||
#else
|
||||
#import <AppKit/NSColor.h>
|
||||
#endif
|
||||
|
||||
#import "DDLog.h"
|
||||
|
||||
/**
|
||||
* Welcome to Cocoa Lumberjack!
|
||||
*
|
||||
* The project page has a wealth of documentation if you have any questions.
|
||||
* https://github.com/robbiehanson/CocoaLumberjack
|
||||
*
|
||||
* If you're new to the project you may wish to read the "Getting Started" wiki.
|
||||
* https://github.com/robbiehanson/CocoaLumberjack/wiki/GettingStarted
|
||||
*
|
||||
*
|
||||
* This class provides a logger for Terminal output or Xcode console output,
|
||||
* depending on where you are running your code.
|
||||
*
|
||||
* As described in the "Getting Started" page,
|
||||
* the traditional NSLog() function directs it's output to two places:
|
||||
*
|
||||
* - Apple System Log (so it shows up in Console.app)
|
||||
* - StdErr (if stderr is a TTY, so log statements show up in Xcode console)
|
||||
*
|
||||
* To duplicate NSLog() functionality you can simply add this logger and an asl logger.
|
||||
* However, if you instead choose to use file logging (for faster performance),
|
||||
* you may choose to use only a file logger and a tty logger.
|
||||
**/
|
||||
|
||||
@interface DDTTYLogger : DDAbstractLogger <DDLogger>
|
||||
{
|
||||
NSCalendar *calendar;
|
||||
NSUInteger calendarUnitFlags;
|
||||
|
||||
NSString *appName;
|
||||
char *app;
|
||||
size_t appLen;
|
||||
|
||||
NSString *processID;
|
||||
char *pid;
|
||||
size_t pidLen;
|
||||
|
||||
BOOL colorsEnabled;
|
||||
NSMutableArray *colorProfilesArray;
|
||||
NSMutableDictionary *colorProfilesDict;
|
||||
}
|
||||
|
||||
+ (DDTTYLogger *)sharedInstance;
|
||||
|
||||
/* Inherited from the DDLogger protocol:
|
||||
*
|
||||
* Formatters may optionally be added to any logger.
|
||||
*
|
||||
* If no formatter is set, the logger simply logs the message as it is given in logMessage,
|
||||
* or it may use its own built in formatting style.
|
||||
*
|
||||
* More information about formatters can be found here:
|
||||
* https://github.com/robbiehanson/CocoaLumberjack/wiki/CustomFormatters
|
||||
*
|
||||
* The actual implementation of these methods is inherited from DDAbstractLogger.
|
||||
|
||||
- (id <DDLogFormatter>)logFormatter;
|
||||
- (void)setLogFormatter:(id <DDLogFormatter>)formatter;
|
||||
|
||||
*/
|
||||
|
||||
/**
|
||||
* Want to use different colors for different log levels?
|
||||
* Enable this property.
|
||||
*
|
||||
* If you run the application via the Terminal (not Xcode),
|
||||
* the logger will map colors to xterm-256color or xterm-color (if available).
|
||||
*
|
||||
* Xcode does NOT natively support colors in the Xcode debugging console.
|
||||
* You'll need to install the XcodeColors plugin to see colors in the Xcode console.
|
||||
* https://github.com/robbiehanson/XcodeColors
|
||||
*
|
||||
* The default value if NO.
|
||||
**/
|
||||
@property (readwrite, assign) BOOL colorsEnabled;
|
||||
|
||||
/**
|
||||
* The default color set (foregroundColor, backgroundColor) is:
|
||||
*
|
||||
* - LOG_FLAG_ERROR = (red, nil)
|
||||
* - LOG_FLAG_WARN = (orange, nil)
|
||||
*
|
||||
* You can customize the colors however you see fit.
|
||||
* Please note that you are passing a flag, NOT a level.
|
||||
*
|
||||
* GOOD : [ttyLogger setForegroundColor:pink backgroundColor:nil forFlag:LOG_FLAG_INFO]; // <- Good :)
|
||||
* BAD : [ttyLogger setForegroundColor:pink backgroundColor:nil forFlag:LOG_LEVEL_INFO]; // <- BAD! :(
|
||||
*
|
||||
* LOG_FLAG_INFO = 0...00100
|
||||
* LOG_LEVEL_INFO = 0...00111 <- Would match LOG_FLAG_INFO and LOG_FLAG_WARN and LOG_FLAG_ERROR
|
||||
*
|
||||
* If you run the application within Xcode, then the XcodeColors plugin is required.
|
||||
*
|
||||
* If you run the application from a shell, then DDTTYLogger will automatically map the given color to
|
||||
* the closest available color. (xterm-256color or xterm-color which have 256 and 16 supported colors respectively.)
|
||||
*
|
||||
* This method invokes setForegroundColor:backgroundColor:forFlag:context: and passes the default context (0).
|
||||
**/
|
||||
#if TARGET_OS_IPHONE
|
||||
- (void)setForegroundColor:(UIColor *)txtColor backgroundColor:(UIColor *)bgColor forFlag:(int)mask;
|
||||
#else
|
||||
- (void)setForegroundColor:(NSColor *)txtColor backgroundColor:(NSColor *)bgColor forFlag:(int)mask;
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Just like setForegroundColor:backgroundColor:flag, but allows you to specify a particular logging context.
|
||||
*
|
||||
* A logging context is often used to identify log messages coming from a 3rd party framework,
|
||||
* although logging context's can be used for many different functions.
|
||||
*
|
||||
* Logging context's are explained in further detail here:
|
||||
* https://github.com/robbiehanson/CocoaLumberjack/wiki/CustomContext
|
||||
**/
|
||||
#if TARGET_OS_IPHONE
|
||||
- (void)setForegroundColor:(UIColor *)txtColor backgroundColor:(UIColor *)bgColor forFlag:(int)mask context:(int)ctxt;
|
||||
#else
|
||||
- (void)setForegroundColor:(NSColor *)txtColor backgroundColor:(NSColor *)bgColor forFlag:(int)mask context:(int)ctxt;
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Similar to the methods above, but allows you to map DDLogMessage->tag to a particular color profile.
|
||||
* For example, you could do something like this:
|
||||
*
|
||||
* static NSString *const PurpleTag = @"PurpleTag";
|
||||
*
|
||||
* #define DDLogPurple(frmt, ...) LOG_OBJC_TAG_MACRO(NO, 0, 0, 0, PurpleTag, frmt, ##__VA_ARGS__)
|
||||
*
|
||||
* And then in your applicationDidFinishLaunching, or wherever you configure Lumberjack:
|
||||
*
|
||||
* #if TARGET_OS_IPHONE
|
||||
* UIColor *purple = [UIColor colorWithRed:(64/255.0) green:(0/255.0) blue:(128/255.0) alpha:1.0];
|
||||
* #else
|
||||
* NSColor *purple = [NSColor colorWithCalibratedRed:(64/255.0) green:(0/255.0) blue:(128/255.0) alpha:1.0];
|
||||
*
|
||||
* [[DDTTYLogger sharedInstance] setForegroundColor:purple backgroundColor:nil forTag:PurpleTag];
|
||||
* [DDLog addLogger:[DDTTYLogger sharedInstance]];
|
||||
*
|
||||
* This would essentially give you a straight NSLog replacement that prints in purple:
|
||||
*
|
||||
* DDLogPurple(@"I'm a purple log message!");
|
||||
**/
|
||||
#if TARGET_OS_IPHONE
|
||||
- (void)setForegroundColor:(UIColor *)txtColor backgroundColor:(UIColor *)bgColor forTag:(id <NSCopying>)tag;
|
||||
#else
|
||||
- (void)setForegroundColor:(NSColor *)txtColor backgroundColor:(NSColor *)bgColor forTag:(id <NSCopying>)tag;
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Clearing color profiles.
|
||||
**/
|
||||
- (void)clearColorsForFlag:(int)mask;
|
||||
- (void)clearColorsForFlag:(int)mask context:(int)context;
|
||||
- (void)clearColorsForTag:(id <NSCopying>)tag;
|
||||
- (void)clearColorsForAllFlags;
|
||||
- (void)clearColorsForAllTags;
|
||||
- (void)clearAllColors;
|
||||
|
||||
@end
|
1479
xplan-ios/Base/Tool/CocoaHttpServer/Vendor/CocoaLumberjack/DDTTYLogger.m
vendored
Executable file
1479
xplan-ios/Base/Tool/CocoaHttpServer/Vendor/CocoaLumberjack/DDTTYLogger.m
vendored
Executable file
File diff suppressed because it is too large
Load Diff
65
xplan-ios/Base/Tool/CocoaHttpServer/Vendor/CocoaLumberjack/Extensions/ContextFilterLogFormatter.h
vendored
Executable file
65
xplan-ios/Base/Tool/CocoaHttpServer/Vendor/CocoaLumberjack/Extensions/ContextFilterLogFormatter.h
vendored
Executable file
@@ -0,0 +1,65 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "DDLog.h"
|
||||
|
||||
@class ContextFilterLogFormatter;
|
||||
|
||||
/**
|
||||
* Welcome to Cocoa Lumberjack!
|
||||
*
|
||||
* The project page has a wealth of documentation if you have any questions.
|
||||
* https://github.com/robbiehanson/CocoaLumberjack
|
||||
*
|
||||
* If you're new to the project you may wish to read the "Getting Started" page.
|
||||
* https://github.com/robbiehanson/CocoaLumberjack/wiki/GettingStarted
|
||||
*
|
||||
*
|
||||
* This class provides a log formatter that filters log statements from a logging context not on the whitelist.
|
||||
*
|
||||
* A log formatter can be added to any logger to format and/or filter its output.
|
||||
* You can learn more about log formatters here:
|
||||
* https://github.com/robbiehanson/CocoaLumberjack/wiki/CustomFormatters
|
||||
*
|
||||
* You can learn more about logging context's here:
|
||||
* https://github.com/robbiehanson/CocoaLumberjack/wiki/CustomContext
|
||||
*
|
||||
* But here's a quick overview / refresher:
|
||||
*
|
||||
* Every log statement has a logging context.
|
||||
* These come from the underlying logging macros defined in DDLog.h.
|
||||
* The default logging context is zero.
|
||||
* You can define multiple logging context's for use in your application.
|
||||
* For example, logically separate parts of your app each have a different logging context.
|
||||
* Also 3rd party frameworks that make use of Lumberjack generally use their own dedicated logging context.
|
||||
**/
|
||||
@interface ContextWhitelistFilterLogFormatter : NSObject <DDLogFormatter>
|
||||
|
||||
- (id)init;
|
||||
|
||||
- (void)addToWhitelist:(int)loggingContext;
|
||||
- (void)removeFromWhitelist:(int)loggingContext;
|
||||
|
||||
- (NSArray *)whitelist;
|
||||
|
||||
- (BOOL)isOnWhitelist:(int)loggingContext;
|
||||
|
||||
@end
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark -
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* This class provides a log formatter that filters log statements from a logging context on the blacklist.
|
||||
**/
|
||||
@interface ContextBlacklistFilterLogFormatter : NSObject <DDLogFormatter>
|
||||
|
||||
- (id)init;
|
||||
|
||||
- (void)addToBlacklist:(int)loggingContext;
|
||||
- (void)removeFromBlacklist:(int)loggingContext;
|
||||
|
||||
- (NSArray *)blacklist;
|
||||
|
||||
- (BOOL)isOnBlacklist:(int)loggingContext;
|
||||
|
||||
@end
|
191
xplan-ios/Base/Tool/CocoaHttpServer/Vendor/CocoaLumberjack/Extensions/ContextFilterLogFormatter.m
vendored
Executable file
191
xplan-ios/Base/Tool/CocoaHttpServer/Vendor/CocoaLumberjack/Extensions/ContextFilterLogFormatter.m
vendored
Executable file
@@ -0,0 +1,191 @@
|
||||
#import "ContextFilterLogFormatter.h"
|
||||
#import <libkern/OSAtomic.h>
|
||||
|
||||
/**
|
||||
* Welcome to Cocoa Lumberjack!
|
||||
*
|
||||
* The project page has a wealth of documentation if you have any questions.
|
||||
* https://github.com/robbiehanson/CocoaLumberjack
|
||||
*
|
||||
* If you're new to the project you may wish to read the "Getting Started" wiki.
|
||||
* https://github.com/robbiehanson/CocoaLumberjack/wiki/GettingStarted
|
||||
**/
|
||||
|
||||
#if ! __has_feature(objc_arc)
|
||||
#warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
|
||||
#endif
|
||||
|
||||
@interface LoggingContextSet : NSObject
|
||||
|
||||
- (void)addToSet:(int)loggingContext;
|
||||
- (void)removeFromSet:(int)loggingContext;
|
||||
|
||||
- (NSArray *)currentSet;
|
||||
|
||||
- (BOOL)isInSet:(int)loggingContext;
|
||||
|
||||
@end
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark -
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@implementation ContextWhitelistFilterLogFormatter
|
||||
{
|
||||
LoggingContextSet *contextSet;
|
||||
}
|
||||
|
||||
- (id)init
|
||||
{
|
||||
if ((self = [super init]))
|
||||
{
|
||||
contextSet = [[LoggingContextSet alloc] init];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
|
||||
- (void)addToWhitelist:(int)loggingContext
|
||||
{
|
||||
[contextSet addToSet:loggingContext];
|
||||
}
|
||||
|
||||
- (void)removeFromWhitelist:(int)loggingContext
|
||||
{
|
||||
[contextSet removeFromSet:loggingContext];
|
||||
}
|
||||
|
||||
- (NSArray *)whitelist
|
||||
{
|
||||
return [contextSet currentSet];
|
||||
}
|
||||
|
||||
- (BOOL)isOnWhitelist:(int)loggingContext
|
||||
{
|
||||
return [contextSet isInSet:loggingContext];
|
||||
}
|
||||
|
||||
- (NSString *)formatLogMessage:(DDLogMessage *)logMessage
|
||||
{
|
||||
if ([self isOnWhitelist:logMessage->logContext])
|
||||
return logMessage->logMsg;
|
||||
else
|
||||
return nil;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark -
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@implementation ContextBlacklistFilterLogFormatter
|
||||
{
|
||||
LoggingContextSet *contextSet;
|
||||
}
|
||||
|
||||
- (id)init
|
||||
{
|
||||
if ((self = [super init]))
|
||||
{
|
||||
contextSet = [[LoggingContextSet alloc] init];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
|
||||
- (void)addToBlacklist:(int)loggingContext
|
||||
{
|
||||
[contextSet addToSet:loggingContext];
|
||||
}
|
||||
|
||||
- (void)removeFromBlacklist:(int)loggingContext
|
||||
{
|
||||
[contextSet removeFromSet:loggingContext];
|
||||
}
|
||||
|
||||
- (NSArray *)blacklist
|
||||
{
|
||||
return [contextSet currentSet];
|
||||
}
|
||||
|
||||
- (BOOL)isOnBlacklist:(int)loggingContext
|
||||
{
|
||||
return [contextSet isInSet:loggingContext];
|
||||
}
|
||||
|
||||
- (NSString *)formatLogMessage:(DDLogMessage *)logMessage
|
||||
{
|
||||
if ([self isOnBlacklist:logMessage->logContext])
|
||||
return nil;
|
||||
else
|
||||
return logMessage->logMsg;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark -
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@implementation LoggingContextSet
|
||||
{
|
||||
OSSpinLock lock;
|
||||
NSMutableSet *set;
|
||||
}
|
||||
|
||||
- (id)init
|
||||
{
|
||||
if ((self = [super init]))
|
||||
{
|
||||
set = [[NSMutableSet alloc] init];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
|
||||
- (void)addToSet:(int)loggingContext
|
||||
{
|
||||
OSSpinLockLock(&lock);
|
||||
{
|
||||
[set addObject:@(loggingContext)];
|
||||
}
|
||||
OSSpinLockUnlock(&lock);
|
||||
}
|
||||
|
||||
- (void)removeFromSet:(int)loggingContext
|
||||
{
|
||||
OSSpinLockLock(&lock);
|
||||
{
|
||||
[set removeObject:@(loggingContext)];
|
||||
}
|
||||
OSSpinLockUnlock(&lock);
|
||||
}
|
||||
|
||||
- (NSArray *)currentSet
|
||||
{
|
||||
NSArray *result = nil;
|
||||
|
||||
OSSpinLockLock(&lock);
|
||||
{
|
||||
result = [set allObjects];
|
||||
}
|
||||
OSSpinLockUnlock(&lock);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
- (BOOL)isInSet:(int)loggingContext
|
||||
{
|
||||
BOOL result = NO;
|
||||
|
||||
OSSpinLockLock(&lock);
|
||||
{
|
||||
result = [set containsObject:@(loggingContext)];
|
||||
}
|
||||
OSSpinLockUnlock(&lock);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@end
|
116
xplan-ios/Base/Tool/CocoaHttpServer/Vendor/CocoaLumberjack/Extensions/DispatchQueueLogFormatter.h
vendored
Executable file
116
xplan-ios/Base/Tool/CocoaHttpServer/Vendor/CocoaLumberjack/Extensions/DispatchQueueLogFormatter.h
vendored
Executable file
@@ -0,0 +1,116 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <libkern/OSAtomic.h>
|
||||
#import "DDLog.h"
|
||||
|
||||
|
||||
/**
|
||||
* Welcome to Cocoa Lumberjack!
|
||||
*
|
||||
* The project page has a wealth of documentation if you have any questions.
|
||||
* https://github.com/robbiehanson/CocoaLumberjack
|
||||
*
|
||||
* If you're new to the project you may wish to read the "Getting Started" page.
|
||||
* https://github.com/robbiehanson/CocoaLumberjack/wiki/GettingStarted
|
||||
*
|
||||
*
|
||||
* This class provides a log formatter that prints the dispatch_queue label instead of the mach_thread_id.
|
||||
*
|
||||
* A log formatter can be added to any logger to format and/or filter its output.
|
||||
* You can learn more about log formatters here:
|
||||
* https://github.com/robbiehanson/CocoaLumberjack/wiki/CustomFormatters
|
||||
*
|
||||
* A typical NSLog (or DDTTYLogger) prints detailed info as [<process_id>:<thread_id>].
|
||||
* For example:
|
||||
*
|
||||
* 2011-10-17 20:21:45.435 AppName[19928:5207] Your log message here
|
||||
*
|
||||
* Where:
|
||||
* - 19928 = process id
|
||||
* - 5207 = thread id (mach_thread_id printed in hex)
|
||||
*
|
||||
* When using grand central dispatch (GCD), this information is less useful.
|
||||
* This is because a single serial dispatch queue may be run on any thread from an internally managed thread pool.
|
||||
* For example:
|
||||
*
|
||||
* 2011-10-17 20:32:31.111 AppName[19954:4d07] Message from my_serial_dispatch_queue
|
||||
* 2011-10-17 20:32:31.112 AppName[19954:5207] Message from my_serial_dispatch_queue
|
||||
* 2011-10-17 20:32:31.113 AppName[19954:2c55] Message from my_serial_dispatch_queue
|
||||
*
|
||||
* This formatter allows you to replace the standard [box:info] with the dispatch_queue name.
|
||||
* For example:
|
||||
*
|
||||
* 2011-10-17 20:32:31.111 AppName[img-scaling] Message from my_serial_dispatch_queue
|
||||
* 2011-10-17 20:32:31.112 AppName[img-scaling] Message from my_serial_dispatch_queue
|
||||
* 2011-10-17 20:32:31.113 AppName[img-scaling] Message from my_serial_dispatch_queue
|
||||
*
|
||||
* If the dispatch_queue doesn't have a set name, then it falls back to the thread name.
|
||||
* If the current thread doesn't have a set name, then it falls back to the mach_thread_id in hex (like normal).
|
||||
*
|
||||
* Note: If manually creating your own background threads (via NSThread/alloc/init or NSThread/detachNeThread),
|
||||
* you can use [[NSThread currentThread] setName:(NSString *)].
|
||||
**/
|
||||
@interface DispatchQueueLogFormatter : NSObject <DDLogFormatter> {
|
||||
@protected
|
||||
|
||||
NSString *dateFormatString;
|
||||
}
|
||||
|
||||
/**
|
||||
* Standard init method.
|
||||
* Configure using properties as desired.
|
||||
**/
|
||||
- (id)init;
|
||||
|
||||
/**
|
||||
* The minQueueLength restricts the minimum size of the [detail box].
|
||||
* If the minQueueLength is set to 0, there is no restriction.
|
||||
*
|
||||
* For example, say a dispatch_queue has a label of "diskIO":
|
||||
*
|
||||
* If the minQueueLength is 0: [diskIO]
|
||||
* If the minQueueLength is 4: [diskIO]
|
||||
* If the minQueueLength is 5: [diskIO]
|
||||
* If the minQueueLength is 6: [diskIO]
|
||||
* If the minQueueLength is 7: [diskIO ]
|
||||
* If the minQueueLength is 8: [diskIO ]
|
||||
*
|
||||
* The default minQueueLength is 0 (no minimum, so [detail box] won't be padded).
|
||||
*
|
||||
* If you want every [detail box] to have the exact same width,
|
||||
* set both minQueueLength and maxQueueLength to the same value.
|
||||
**/
|
||||
@property (assign) NSUInteger minQueueLength;
|
||||
|
||||
/**
|
||||
* The maxQueueLength restricts the number of characters that will be inside the [detail box].
|
||||
* If the maxQueueLength is 0, there is no restriction.
|
||||
*
|
||||
* For example, say a dispatch_queue has a label of "diskIO":
|
||||
*
|
||||
* If the maxQueueLength is 0: [diskIO]
|
||||
* If the maxQueueLength is 4: [disk]
|
||||
* If the maxQueueLength is 5: [diskI]
|
||||
* If the maxQueueLength is 6: [diskIO]
|
||||
* If the maxQueueLength is 7: [diskIO]
|
||||
* If the maxQueueLength is 8: [diskIO]
|
||||
*
|
||||
* The default maxQueueLength is 0 (no maximum, so [detail box] won't be truncated).
|
||||
*
|
||||
* If you want every [detail box] to have the exact same width,
|
||||
* set both minQueueLength and maxQueueLength to the same value.
|
||||
**/
|
||||
@property (assign) NSUInteger maxQueueLength;
|
||||
|
||||
/**
|
||||
* Sometimes queue labels have long names like "com.apple.main-queue",
|
||||
* but you'd prefer something shorter like simply "main".
|
||||
*
|
||||
* This method allows you to set such preferred replacements.
|
||||
* The above example is set by default.
|
||||
*
|
||||
* To remove/undo a previous replacement, invoke this method with nil for the 'shortLabel' parameter.
|
||||
**/
|
||||
- (NSString *)replacementStringForQueueLabel:(NSString *)longLabel;
|
||||
- (void)setReplacementString:(NSString *)shortLabel forQueueLabel:(NSString *)longLabel;
|
||||
|
||||
@end
|
251
xplan-ios/Base/Tool/CocoaHttpServer/Vendor/CocoaLumberjack/Extensions/DispatchQueueLogFormatter.m
vendored
Executable file
251
xplan-ios/Base/Tool/CocoaHttpServer/Vendor/CocoaLumberjack/Extensions/DispatchQueueLogFormatter.m
vendored
Executable file
@@ -0,0 +1,251 @@
|
||||
#import "DispatchQueueLogFormatter.h"
|
||||
#import <libkern/OSAtomic.h>
|
||||
|
||||
/**
|
||||
* Welcome to Cocoa Lumberjack!
|
||||
*
|
||||
* The project page has a wealth of documentation if you have any questions.
|
||||
* https://github.com/robbiehanson/CocoaLumberjack
|
||||
*
|
||||
* If you're new to the project you may wish to read the "Getting Started" wiki.
|
||||
* https://github.com/robbiehanson/CocoaLumberjack/wiki/GettingStarted
|
||||
**/
|
||||
|
||||
#if ! __has_feature(objc_arc)
|
||||
#warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
|
||||
#endif
|
||||
|
||||
|
||||
@implementation DispatchQueueLogFormatter
|
||||
{
|
||||
int32_t atomicLoggerCount;
|
||||
NSDateFormatter *threadUnsafeDateFormatter; // Use [self stringFromDate]
|
||||
|
||||
OSSpinLock lock;
|
||||
|
||||
NSUInteger _minQueueLength; // _prefix == Only access via atomic property
|
||||
NSUInteger _maxQueueLength; // _prefix == Only access via atomic property
|
||||
NSMutableDictionary *_replacements; // _prefix == Only access from within spinlock
|
||||
}
|
||||
|
||||
- (id)init
|
||||
{
|
||||
if ((self = [super init]))
|
||||
{
|
||||
dateFormatString = @"yyyy-MM-dd HH:mm:ss:SSS";
|
||||
|
||||
atomicLoggerCount = 0;
|
||||
threadUnsafeDateFormatter = nil;
|
||||
|
||||
_minQueueLength = 0;
|
||||
_maxQueueLength = 0;
|
||||
_replacements = [[NSMutableDictionary alloc] init];
|
||||
|
||||
// Set default replacements:
|
||||
|
||||
[_replacements setObject:@"main" forKey:@"com.apple.main-thread"];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark Configuration
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@synthesize minQueueLength = _minQueueLength;
|
||||
@synthesize maxQueueLength = _maxQueueLength;
|
||||
|
||||
- (NSString *)replacementStringForQueueLabel:(NSString *)longLabel
|
||||
{
|
||||
NSString *result = nil;
|
||||
|
||||
OSSpinLockLock(&lock);
|
||||
{
|
||||
result = [_replacements objectForKey:longLabel];
|
||||
}
|
||||
OSSpinLockUnlock(&lock);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
- (void)setReplacementString:(NSString *)shortLabel forQueueLabel:(NSString *)longLabel
|
||||
{
|
||||
OSSpinLockLock(&lock);
|
||||
{
|
||||
if (shortLabel)
|
||||
[_replacements setObject:shortLabel forKey:longLabel];
|
||||
else
|
||||
[_replacements removeObjectForKey:longLabel];
|
||||
}
|
||||
OSSpinLockUnlock(&lock);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#pragma mark DDLogFormatter
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
- (NSString *)stringFromDate:(NSDate *)date
|
||||
{
|
||||
int32_t loggerCount = OSAtomicAdd32(0, &atomicLoggerCount);
|
||||
|
||||
if (loggerCount <= 1)
|
||||
{
|
||||
// Single-threaded mode.
|
||||
|
||||
if (threadUnsafeDateFormatter == nil)
|
||||
{
|
||||
threadUnsafeDateFormatter = [[NSDateFormatter alloc] init];
|
||||
[threadUnsafeDateFormatter setFormatterBehavior:NSDateFormatterBehavior10_4];
|
||||
[threadUnsafeDateFormatter setDateFormat:dateFormatString];
|
||||
}
|
||||
|
||||
return [threadUnsafeDateFormatter stringFromDate:date];
|
||||
}
|
||||
else
|
||||
{
|
||||
// Multi-threaded mode.
|
||||
// NSDateFormatter is NOT thread-safe.
|
||||
|
||||
NSString *key = @"DispatchQueueLogFormatter_NSDateFormatter";
|
||||
|
||||
NSMutableDictionary *threadDictionary = [[NSThread currentThread] threadDictionary];
|
||||
NSDateFormatter *dateFormatter = [threadDictionary objectForKey:key];
|
||||
|
||||
if (dateFormatter == nil)
|
||||
{
|
||||
dateFormatter = [[NSDateFormatter alloc] init];
|
||||
[dateFormatter setFormatterBehavior:NSDateFormatterBehavior10_4];
|
||||
[dateFormatter setDateFormat:dateFormatString];
|
||||
|
||||
[threadDictionary setObject:dateFormatter forKey:key];
|
||||
}
|
||||
|
||||
return [dateFormatter stringFromDate:date];
|
||||
}
|
||||
}
|
||||
|
||||
- (NSString *)queueThreadLabelForLogMessage:(DDLogMessage *)logMessage
|
||||
{
|
||||
// As per the DDLogFormatter contract, this method is always invoked on the same thread/dispatch_queue
|
||||
|
||||
NSUInteger minQueueLength = self.minQueueLength;
|
||||
NSUInteger maxQueueLength = self.maxQueueLength;
|
||||
|
||||
// Get the name of the queue, thread, or machID (whichever we are to use).
|
||||
|
||||
NSString *queueThreadLabel = nil;
|
||||
|
||||
BOOL useQueueLabel = YES;
|
||||
BOOL useThreadName = NO;
|
||||
|
||||
if (logMessage->queueLabel)
|
||||
{
|
||||
// If you manually create a thread, it's dispatch_queue will have one of the thread names below.
|
||||
// Since all such threads have the same name, we'd prefer to use the threadName or the machThreadID.
|
||||
|
||||
char *names[] = { "com.apple.root.low-priority",
|
||||
"com.apple.root.default-priority",
|
||||
"com.apple.root.high-priority",
|
||||
"com.apple.root.low-overcommit-priority",
|
||||
"com.apple.root.default-overcommit-priority",
|
||||
"com.apple.root.high-overcommit-priority" };
|
||||
|
||||
int length = sizeof(names) / sizeof(char *);
|
||||
|
||||
int i;
|
||||
for (i = 0; i < length; i++)
|
||||
{
|
||||
if (strcmp(logMessage->queueLabel, names[i]) == 0)
|
||||
{
|
||||
useQueueLabel = NO;
|
||||
useThreadName = [logMessage->threadName length] > 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
useQueueLabel = NO;
|
||||
useThreadName = [logMessage->threadName length] > 0;
|
||||
}
|
||||
|
||||
if (useQueueLabel || useThreadName)
|
||||
{
|
||||
NSString *fullLabel;
|
||||
NSString *abrvLabel;
|
||||
|
||||
if (useQueueLabel)
|
||||
fullLabel = @(logMessage->queueLabel);
|
||||
else
|
||||
fullLabel = logMessage->threadName;
|
||||
|
||||
OSSpinLockLock(&lock);
|
||||
{
|
||||
abrvLabel = [_replacements objectForKey:fullLabel];
|
||||
}
|
||||
OSSpinLockUnlock(&lock);
|
||||
|
||||
if (abrvLabel)
|
||||
queueThreadLabel = abrvLabel;
|
||||
else
|
||||
queueThreadLabel = fullLabel;
|
||||
}
|
||||
else
|
||||
{
|
||||
queueThreadLabel = [NSString stringWithFormat:@"%x", logMessage->machThreadID];
|
||||
}
|
||||
|
||||
// Now use the thread label in the output
|
||||
|
||||
NSUInteger labelLength = [queueThreadLabel length];
|
||||
|
||||
// labelLength > maxQueueLength : truncate
|
||||
// labelLength < minQueueLength : padding
|
||||
// : exact
|
||||
|
||||
if ((maxQueueLength > 0) && (labelLength > maxQueueLength))
|
||||
{
|
||||
// Truncate
|
||||
|
||||
return [queueThreadLabel substringToIndex:maxQueueLength];
|
||||
}
|
||||
else if (labelLength < minQueueLength)
|
||||
{
|
||||
// Padding
|
||||
|
||||
NSUInteger numSpaces = minQueueLength - labelLength;
|
||||
|
||||
char spaces[numSpaces + 1];
|
||||
memset(spaces, ' ', numSpaces);
|
||||
spaces[numSpaces] = '\0';
|
||||
|
||||
return [NSString stringWithFormat:@"%@%s", queueThreadLabel, spaces];
|
||||
}
|
||||
else
|
||||
{
|
||||
// Exact
|
||||
|
||||
return queueThreadLabel;
|
||||
}
|
||||
}
|
||||
|
||||
- (NSString *)formatLogMessage:(DDLogMessage *)logMessage
|
||||
{
|
||||
NSString *timestamp = [self stringFromDate:(logMessage->timestamp)];
|
||||
NSString *queueThreadLabel = [self queueThreadLabelForLogMessage:logMessage];
|
||||
|
||||
return [NSString stringWithFormat:@"%@ [%@] %@", timestamp, queueThreadLabel, logMessage->logMsg];
|
||||
}
|
||||
|
||||
- (void)didAddToLogger:(id <DDLogger>)logger
|
||||
{
|
||||
OSAtomicIncrement32(&atomicLoggerCount);
|
||||
}
|
||||
|
||||
- (void)willRemoveFromLogger:(id <DDLogger>)logger
|
||||
{
|
||||
OSAtomicDecrement32(&atomicLoggerCount);
|
||||
}
|
||||
|
||||
@end
|
101
xplan-ios/Base/Tool/CocoaHttpServer/Web/css/index.css
Executable file
101
xplan-ios/Base/Tool/CocoaHttpServer/Web/css/index.css
Executable file
@@ -0,0 +1,101 @@
|
||||
img {
|
||||
width: 100%;
|
||||
vertical-align: top;
|
||||
}
|
||||
html,
|
||||
body {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.container {
|
||||
margin: 0 auto;
|
||||
height: 100%;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
.header {
|
||||
height: 80px;
|
||||
text-align: center;
|
||||
background: #2f2f2f;
|
||||
}
|
||||
.header img {
|
||||
margin: 0 auto;
|
||||
height: 100%;
|
||||
width: 900px;
|
||||
}
|
||||
.section {
|
||||
padding: 20px 24px;
|
||||
width: 800px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.section .uploadBtn {
|
||||
margin: 0 auto 20px;
|
||||
width: 40%;
|
||||
cursor: pointer;
|
||||
}
|
||||
.section #upload,
|
||||
.section #submit {
|
||||
display: none;
|
||||
}
|
||||
.section .songList {
|
||||
height: 600px;
|
||||
background: #fff;
|
||||
}
|
||||
.section .songList .title {
|
||||
padding: 16px 15px;
|
||||
background: #e7e5e5;
|
||||
}
|
||||
.section .songList .title span {
|
||||
border-left: 5px solid #FF894F;
|
||||
padding-left: 10px;
|
||||
line-height: 16px;
|
||||
}
|
||||
.section .songList ul li {
|
||||
height: 60px;
|
||||
display: flex;
|
||||
border-bottom: 1px solid #f5f5f5;
|
||||
align-items: center;
|
||||
padding: 0 10px;
|
||||
}
|
||||
.section .songList ul li .attention {
|
||||
flex: 0 0 20px;
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
background: url('../images/attention.png') no-repeat;
|
||||
background-size: 100% 100%;
|
||||
margin-right: 8px;
|
||||
}
|
||||
.section .songList ul li .progress {
|
||||
flex: 0 0 180px;
|
||||
width: 180px;
|
||||
height: 14px;
|
||||
background: #e7e5e5;
|
||||
border-radius: 7px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.section .songList ul li .progress .bar {
|
||||
width: 0;
|
||||
background: #ffb400;
|
||||
height: 100%;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
.section .songList ul li .download {
|
||||
flex: 0 0 20px;
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
background: url('../images/download.png') no-repeat;
|
||||
background-size: 100% 100%;
|
||||
margin-right: 8px;
|
||||
}
|
||||
.section .songList ul li .delete {
|
||||
flex: 0 0 20px;
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
background: url('../images/delete.png') no-repeat;
|
||||
background-size: 100% 100%;
|
||||
}
|
||||
.section .songList ul li .songName {
|
||||
flex: 4;
|
||||
}
|
||||
.section .songList ul li .songSize {
|
||||
flex: 1;
|
||||
}
|
90
xplan-ios/Base/Tool/CocoaHttpServer/Web/css/reset.css
Executable file
90
xplan-ios/Base/Tool/CocoaHttpServer/Web/css/reset.css
Executable file
@@ -0,0 +1,90 @@
|
||||
/**
|
||||
* Eric Meyer's Reset CSS v2.0 (http://meyerweb.com/eric/tools/css/reset/)
|
||||
* http://cssreset.com
|
||||
*/
|
||||
html, body, div, span, applet, object, iframe,
|
||||
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
|
||||
a, abbr, acronym, address, big, cite, code,
|
||||
del, dfn, em, img, ins, kbd, q, s, samp,
|
||||
small, strike, strong, sub, sup, tt, var,
|
||||
b, u, i, center,
|
||||
dl, dt, dd, ol, ul, li,
|
||||
fieldset, form, label, legend,
|
||||
table, caption, tbody, tfoot, thead, tr, th, td,
|
||||
article, aside, canvas, details, embed,
|
||||
figure, figcaption, footer, header,
|
||||
menu, nav, output, ruby, section, summary,
|
||||
time, mark, audio, video, input {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
font-size: 100%;
|
||||
font-weight: normal;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
/* HTML5 display-role reset for older browsers */
|
||||
article, aside, details, figcaption, figure,
|
||||
footer, header, menu, nav, section {
|
||||
display: block;
|
||||
}
|
||||
|
||||
body {
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
blockquote, q {
|
||||
quotes: none;
|
||||
}
|
||||
|
||||
blockquote:before, blockquote:after,
|
||||
q:before, q:after {
|
||||
content: none;
|
||||
}
|
||||
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
}
|
||||
|
||||
/* custom */
|
||||
a {
|
||||
color: #7e8c8d;
|
||||
text-decoration: none;
|
||||
-webkit-backface-visibility: hidden;
|
||||
}
|
||||
|
||||
li {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar {
|
||||
width: 5px;
|
||||
height: 5px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track-piece {
|
||||
background-color: rgba(0, 0, 0, 0.2);
|
||||
-webkit-border-radius: 6px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:vertical {
|
||||
height: 5px;
|
||||
background-color: rgba(125, 125, 125, 0.7);
|
||||
-webkit-border-radius: 6px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:horizontal {
|
||||
width: 5px;
|
||||
background-color: rgba(125, 125, 125, 0.7);
|
||||
-webkit-border-radius: 6px;
|
||||
}
|
||||
|
||||
html, body {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
-webkit-text-size-adjust: none;
|
||||
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||
}
|
BIN
xplan-ios/Base/Tool/CocoaHttpServer/Web/images/attention.png
Executable file
BIN
xplan-ios/Base/Tool/CocoaHttpServer/Web/images/attention.png
Executable file
Binary file not shown.
After Width: | Height: | Size: 441 B |
BIN
xplan-ios/Base/Tool/CocoaHttpServer/Web/images/button.png
Executable file
BIN
xplan-ios/Base/Tool/CocoaHttpServer/Web/images/button.png
Executable file
Binary file not shown.
After Width: | Height: | Size: 2.8 KiB |
BIN
xplan-ios/Base/Tool/CocoaHttpServer/Web/images/delete.png
Executable file
BIN
xplan-ios/Base/Tool/CocoaHttpServer/Web/images/delete.png
Executable file
Binary file not shown.
After Width: | Height: | Size: 483 B |
BIN
xplan-ios/Base/Tool/CocoaHttpServer/Web/images/download.png
Executable file
BIN
xplan-ios/Base/Tool/CocoaHttpServer/Web/images/download.png
Executable file
Binary file not shown.
After Width: | Height: | Size: 358 B |
BIN
xplan-ios/Base/Tool/CocoaHttpServer/Web/images/header.png
Executable file
BIN
xplan-ios/Base/Tool/CocoaHttpServer/Web/images/header.png
Executable file
Binary file not shown.
After Width: | Height: | Size: 1.3 KiB |
54
xplan-ios/Base/Tool/CocoaHttpServer/Web/index.html
Executable file
54
xplan-ios/Base/Tool/CocoaHttpServer/Web/index.html
Executable file
@@ -0,0 +1,54 @@
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
|
||||
<meta http-equiv=\"content-type\" content=\"text/html; charset=UTF-8\">
|
||||
<title>一键上传歌曲</title>
|
||||
<link rel="stylesheet" href="css/reset.css">
|
||||
<link rel="stylesheet" href="css/index.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<img src="images/header.png" alt="">
|
||||
</div>
|
||||
<div class="section">
|
||||
<form method="post" action="index.html" enctype="multipart/form-data" accept-charset="utf-8">
|
||||
<div class="uploadBtn">
|
||||
<img src="images/button.png" alt="">
|
||||
</div>
|
||||
<input type="file" name="upload" id="upload" accept="audio/mpeg" multiple>
|
||||
<input type="submit" value="Submit" id="submit">
|
||||
</form>
|
||||
<div class="songList">
|
||||
<div class="title"><span>歌曲列表</span></div>
|
||||
<ul id="uploadingList">
|
||||
<!-- <li>
|
||||
<i class="attention"></i>
|
||||
<p class="songName">马博-菊花爆满山.mp3</p>
|
||||
<div class="progress">
|
||||
<div class="bar"></div>
|
||||
</div>
|
||||
</li> -->
|
||||
</ul>
|
||||
<ul id="uploadingDone">
|
||||
<!-- <li>
|
||||
<i class="download"></i>
|
||||
<p class="songName">马博-菊花爆满山.mp3</p>
|
||||
<p class="songSize">4.40MB</p>
|
||||
<i class="delete"></i>
|
||||
</li> -->
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
<script src="js/jquery-3.2.1.min.js"></script>
|
||||
<script src="js/jquery.iframe-transport.js"></script>
|
||||
<script src="js/jquery.ui.widget.js"></script>
|
||||
<script src="js/jquery.fileupload.js"></script>
|
||||
|
||||
<script src="js/index.js"></script>
|
||||
</html>
|
49
xplan-ios/Base/Tool/CocoaHttpServer/Web/js/index.js
Executable file
49
xplan-ios/Base/Tool/CocoaHttpServer/Web/js/index.js
Executable file
@@ -0,0 +1,49 @@
|
||||
$(function(){
|
||||
$('.uploadBtn').on('click',function(){
|
||||
$('#upload').click();
|
||||
})
|
||||
|
||||
$('#upload').fileupload({
|
||||
dropZone: $(document),
|
||||
pasteZone: null,
|
||||
autoUpload: true,
|
||||
sequentialUploads: true,
|
||||
|
||||
dataType: 'json',
|
||||
url:'./upload.html',
|
||||
type: 'POST',
|
||||
add: function(e,data){
|
||||
console.log(data,'添加文件时执行的操作');
|
||||
var $li = '<li><i class="attention"></i><p class="songName">'+data.files[0].name+'</p><div class="progress"><div class="bar"></div></div></li>';
|
||||
data.context = $($li).appendTo('#uploadingList');
|
||||
console.log(data.context);
|
||||
var jqXHR = data.submit();
|
||||
|
||||
},
|
||||
progress: function (e, data) {//上传进度
|
||||
var progress = parseInt(data.loaded / data.total * 100, 10);
|
||||
console.log(progress,data);
|
||||
// $(".progress .bar").css("width", progress + "%");
|
||||
data.context.find(".bar").css("width", progress + "%");
|
||||
},
|
||||
done:function(e,data){
|
||||
console.log('上传完毕');
|
||||
},
|
||||
always: function(e, data) {
|
||||
// 每次传输后(包括成功,失败,被拒执行的回调)
|
||||
data.context.remove();
|
||||
var $li = $('<li />');
|
||||
$li.html('<p class="songName">'+data.files[0].name+'</p><p class="songSize">'+formatFileSize(data.files[0].size)+'</p>');
|
||||
$('#uploadingDone').append($li);
|
||||
}
|
||||
})
|
||||
})
|
||||
function formatFileSize(bytes) {
|
||||
if (bytes >= 1000000000) {
|
||||
return (bytes / 1000000000).toFixed(2) + ' GB';
|
||||
}
|
||||
if (bytes >= 1000000) {
|
||||
return (bytes / 1000000).toFixed(2) + ' MB';
|
||||
}
|
||||
return (bytes / 1000).toFixed(2) + ' KB';
|
||||
}
|
4
xplan-ios/Base/Tool/CocoaHttpServer/Web/js/jquery-3.2.1.min.js
vendored
Executable file
4
xplan-ios/Base/Tool/CocoaHttpServer/Web/js/jquery-3.2.1.min.js
vendored
Executable file
File diff suppressed because one or more lines are too long
1486
xplan-ios/Base/Tool/CocoaHttpServer/Web/js/jquery.fileupload.js
vendored
Executable file
1486
xplan-ios/Base/Tool/CocoaHttpServer/Web/js/jquery.fileupload.js
vendored
Executable file
File diff suppressed because it is too large
Load Diff
224
xplan-ios/Base/Tool/CocoaHttpServer/Web/js/jquery.iframe-transport.js
Executable file
224
xplan-ios/Base/Tool/CocoaHttpServer/Web/js/jquery.iframe-transport.js
Executable file
@@ -0,0 +1,224 @@
|
||||
/*
|
||||
* jQuery Iframe Transport Plugin
|
||||
* https://github.com/blueimp/jQuery-File-Upload
|
||||
*
|
||||
* Copyright 2011, Sebastian Tschan
|
||||
* https://blueimp.net
|
||||
*
|
||||
* Licensed under the MIT license:
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
/* global define, require, window, document, JSON */
|
||||
|
||||
;(function (factory) {
|
||||
'use strict';
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
// Register as an anonymous AMD module:
|
||||
define(['jquery'], factory);
|
||||
} else if (typeof exports === 'object') {
|
||||
// Node/CommonJS:
|
||||
factory(require('jquery'));
|
||||
} else {
|
||||
// Browser globals:
|
||||
factory(window.jQuery);
|
||||
}
|
||||
}(function ($) {
|
||||
'use strict';
|
||||
|
||||
// Helper variable to create unique names for the transport iframes:
|
||||
var counter = 0,
|
||||
jsonAPI = $,
|
||||
jsonParse = 'parseJSON';
|
||||
|
||||
if ('JSON' in window && 'parse' in JSON) {
|
||||
jsonAPI = JSON;
|
||||
jsonParse = 'parse';
|
||||
}
|
||||
|
||||
// The iframe transport accepts four additional options:
|
||||
// options.fileInput: a jQuery collection of file input fields
|
||||
// options.paramName: the parameter name for the file form data,
|
||||
// overrides the name property of the file input field(s),
|
||||
// can be a string or an array of strings.
|
||||
// options.formData: an array of objects with name and value properties,
|
||||
// equivalent to the return data of .serializeArray(), e.g.:
|
||||
// [{name: 'a', value: 1}, {name: 'b', value: 2}]
|
||||
// options.initialIframeSrc: the URL of the initial iframe src,
|
||||
// by default set to "javascript:false;"
|
||||
$.ajaxTransport('iframe', function (options) {
|
||||
if (options.async) {
|
||||
// javascript:false as initial iframe src
|
||||
// prevents warning popups on HTTPS in IE6:
|
||||
/*jshint scripturl: true */
|
||||
var initialIframeSrc = options.initialIframeSrc || 'javascript:false;',
|
||||
/*jshint scripturl: false */
|
||||
form,
|
||||
iframe,
|
||||
addParamChar;
|
||||
return {
|
||||
send: function (_, completeCallback) {
|
||||
form = $('<form style="display:none;"></form>');
|
||||
form.attr('accept-charset', options.formAcceptCharset);
|
||||
addParamChar = /\?/.test(options.url) ? '&' : '?';
|
||||
// XDomainRequest only supports GET and POST:
|
||||
if (options.type === 'DELETE') {
|
||||
options.url = options.url + addParamChar + '_method=DELETE';
|
||||
options.type = 'POST';
|
||||
} else if (options.type === 'PUT') {
|
||||
options.url = options.url + addParamChar + '_method=PUT';
|
||||
options.type = 'POST';
|
||||
} else if (options.type === 'PATCH') {
|
||||
options.url = options.url + addParamChar + '_method=PATCH';
|
||||
options.type = 'POST';
|
||||
}
|
||||
// IE versions below IE8 cannot set the name property of
|
||||
// elements that have already been added to the DOM,
|
||||
// so we set the name along with the iframe HTML markup:
|
||||
counter += 1;
|
||||
iframe = $(
|
||||
'<iframe src="' + initialIframeSrc +
|
||||
'" name="iframe-transport-' + counter + '"></iframe>'
|
||||
).bind('load', function () {
|
||||
var fileInputClones,
|
||||
paramNames = $.isArray(options.paramName) ?
|
||||
options.paramName : [options.paramName];
|
||||
iframe
|
||||
.unbind('load')
|
||||
.bind('load', function () {
|
||||
var response;
|
||||
// Wrap in a try/catch block to catch exceptions thrown
|
||||
// when trying to access cross-domain iframe contents:
|
||||
try {
|
||||
response = iframe.contents();
|
||||
// Google Chrome and Firefox do not throw an
|
||||
// exception when calling iframe.contents() on
|
||||
// cross-domain requests, so we unify the response:
|
||||
if (!response.length || !response[0].firstChild) {
|
||||
throw new Error();
|
||||
}
|
||||
} catch (e) {
|
||||
response = undefined;
|
||||
}
|
||||
// The complete callback returns the
|
||||
// iframe content document as response object:
|
||||
completeCallback(
|
||||
200,
|
||||
'success',
|
||||
{'iframe': response}
|
||||
);
|
||||
// Fix for IE endless progress bar activity bug
|
||||
// (happens on form submits to iframe targets):
|
||||
$('<iframe src="' + initialIframeSrc + '"></iframe>')
|
||||
.appendTo(form);
|
||||
window.setTimeout(function () {
|
||||
// Removing the form in a setTimeout call
|
||||
// allows Chrome's developer tools to display
|
||||
// the response result
|
||||
form.remove();
|
||||
}, 0);
|
||||
});
|
||||
form
|
||||
.prop('target', iframe.prop('name'))
|
||||
.prop('action', options.url)
|
||||
.prop('method', options.type);
|
||||
if (options.formData) {
|
||||
$.each(options.formData, function (index, field) {
|
||||
$('<input type="hidden"/>')
|
||||
.prop('name', field.name)
|
||||
.val(field.value)
|
||||
.appendTo(form);
|
||||
});
|
||||
}
|
||||
if (options.fileInput && options.fileInput.length &&
|
||||
options.type === 'POST') {
|
||||
fileInputClones = options.fileInput.clone();
|
||||
// Insert a clone for each file input field:
|
||||
options.fileInput.after(function (index) {
|
||||
return fileInputClones[index];
|
||||
});
|
||||
if (options.paramName) {
|
||||
options.fileInput.each(function (index) {
|
||||
$(this).prop(
|
||||
'name',
|
||||
paramNames[index] || options.paramName
|
||||
);
|
||||
});
|
||||
}
|
||||
// Appending the file input fields to the hidden form
|
||||
// removes them from their original location:
|
||||
form
|
||||
.append(options.fileInput)
|
||||
.prop('enctype', 'multipart/form-data')
|
||||
// enctype must be set as encoding for IE:
|
||||
.prop('encoding', 'multipart/form-data');
|
||||
// Remove the HTML5 form attribute from the input(s):
|
||||
options.fileInput.removeAttr('form');
|
||||
}
|
||||
form.submit();
|
||||
// Insert the file input fields at their original location
|
||||
// by replacing the clones with the originals:
|
||||
if (fileInputClones && fileInputClones.length) {
|
||||
options.fileInput.each(function (index, input) {
|
||||
var clone = $(fileInputClones[index]);
|
||||
// Restore the original name and form properties:
|
||||
$(input)
|
||||
.prop('name', clone.prop('name'))
|
||||
.attr('form', clone.attr('form'));
|
||||
clone.replaceWith(input);
|
||||
});
|
||||
}
|
||||
});
|
||||
form.append(iframe).appendTo(document.body);
|
||||
},
|
||||
abort: function () {
|
||||
if (iframe) {
|
||||
// javascript:false as iframe src aborts the request
|
||||
// and prevents warning popups on HTTPS in IE6.
|
||||
// concat is used to avoid the "Script URL" JSLint error:
|
||||
iframe
|
||||
.unbind('load')
|
||||
.prop('src', initialIframeSrc);
|
||||
}
|
||||
if (form) {
|
||||
form.remove();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
// The iframe transport returns the iframe content document as response.
|
||||
// The following adds converters from iframe to text, json, html, xml
|
||||
// and script.
|
||||
// Please note that the Content-Type for JSON responses has to be text/plain
|
||||
// or text/html, if the browser doesn't include application/json in the
|
||||
// Accept header, else IE will show a download dialog.
|
||||
// The Content-Type for XML responses on the other hand has to be always
|
||||
// application/xml or text/xml, so IE properly parses the XML response.
|
||||
// See also
|
||||
// https://github.com/blueimp/jQuery-File-Upload/wiki/Setup#content-type-negotiation
|
||||
$.ajaxSetup({
|
||||
converters: {
|
||||
'iframe text': function (iframe) {
|
||||
return iframe && $(iframe[0].body).text();
|
||||
},
|
||||
'iframe json': function (iframe) {
|
||||
return iframe && jsonAPI[jsonParse]($(iframe[0].body).text());
|
||||
},
|
||||
'iframe html': function (iframe) {
|
||||
return iframe && $(iframe[0].body).html();
|
||||
},
|
||||
'iframe xml': function (iframe) {
|
||||
var xmlDoc = iframe && iframe[0];
|
||||
return xmlDoc && $.isXMLDoc(xmlDoc) ? xmlDoc :
|
||||
$.parseXML((xmlDoc.XMLDocument && xmlDoc.XMLDocument.xml) ||
|
||||
$(xmlDoc.body).html());
|
||||
},
|
||||
'iframe script': function (iframe) {
|
||||
return iframe && $.globalEval($(iframe[0].body).text());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
}));
|
572
xplan-ios/Base/Tool/CocoaHttpServer/Web/js/jquery.ui.widget.js
vendored
Executable file
572
xplan-ios/Base/Tool/CocoaHttpServer/Web/js/jquery.ui.widget.js
vendored
Executable file
@@ -0,0 +1,572 @@
|
||||
/*! jQuery UI - v1.11.4+CommonJS - 2015-08-28
|
||||
* http://jqueryui.com
|
||||
* Includes: widget.js
|
||||
* Copyright 2015 jQuery Foundation and other contributors; Licensed MIT */
|
||||
|
||||
(function( factory ) {
|
||||
if ( typeof define === "function" && define.amd ) {
|
||||
|
||||
// AMD. Register as an anonymous module.
|
||||
define([ "jquery" ], factory );
|
||||
|
||||
} else if ( typeof exports === "object" ) {
|
||||
|
||||
// Node/CommonJS
|
||||
factory( require( "jquery" ) );
|
||||
|
||||
} else {
|
||||
|
||||
// Browser globals
|
||||
factory( jQuery );
|
||||
}
|
||||
}(function( $ ) {
|
||||
/*!
|
||||
* jQuery UI Widget 1.11.4
|
||||
* http://jqueryui.com
|
||||
*
|
||||
* Copyright jQuery Foundation and other contributors
|
||||
* Released under the MIT license.
|
||||
* http://jquery.org/license
|
||||
*
|
||||
* http://api.jqueryui.com/jQuery.widget/
|
||||
*/
|
||||
|
||||
|
||||
var widget_uuid = 0,
|
||||
widget_slice = Array.prototype.slice;
|
||||
|
||||
$.cleanData = (function( orig ) {
|
||||
return function( elems ) {
|
||||
var events, elem, i;
|
||||
for ( i = 0; (elem = elems[i]) != null; i++ ) {
|
||||
try {
|
||||
|
||||
// Only trigger remove when necessary to save time
|
||||
events = $._data( elem, "events" );
|
||||
if ( events && events.remove ) {
|
||||
$( elem ).triggerHandler( "remove" );
|
||||
}
|
||||
|
||||
// http://bugs.jquery.com/ticket/8235
|
||||
} catch ( e ) {}
|
||||
}
|
||||
orig( elems );
|
||||
};
|
||||
})( $.cleanData );
|
||||
|
||||
$.widget = function( name, base, prototype ) {
|
||||
var fullName, existingConstructor, constructor, basePrototype,
|
||||
// proxiedPrototype allows the provided prototype to remain unmodified
|
||||
// so that it can be used as a mixin for multiple widgets (#8876)
|
||||
proxiedPrototype = {},
|
||||
namespace = name.split( "." )[ 0 ];
|
||||
|
||||
name = name.split( "." )[ 1 ];
|
||||
fullName = namespace + "-" + name;
|
||||
|
||||
if ( !prototype ) {
|
||||
prototype = base;
|
||||
base = $.Widget;
|
||||
}
|
||||
|
||||
// create selector for plugin
|
||||
$.expr[ ":" ][ fullName.toLowerCase() ] = function( elem ) {
|
||||
return !!$.data( elem, fullName );
|
||||
};
|
||||
|
||||
$[ namespace ] = $[ namespace ] || {};
|
||||
existingConstructor = $[ namespace ][ name ];
|
||||
constructor = $[ namespace ][ name ] = function( options, element ) {
|
||||
// allow instantiation without "new" keyword
|
||||
if ( !this._createWidget ) {
|
||||
return new constructor( options, element );
|
||||
}
|
||||
|
||||
// allow instantiation without initializing for simple inheritance
|
||||
// must use "new" keyword (the code above always passes args)
|
||||
if ( arguments.length ) {
|
||||
this._createWidget( options, element );
|
||||
}
|
||||
};
|
||||
// extend with the existing constructor to carry over any static properties
|
||||
$.extend( constructor, existingConstructor, {
|
||||
version: prototype.version,
|
||||
// copy the object used to create the prototype in case we need to
|
||||
// redefine the widget later
|
||||
_proto: $.extend( {}, prototype ),
|
||||
// track widgets that inherit from this widget in case this widget is
|
||||
// redefined after a widget inherits from it
|
||||
_childConstructors: []
|
||||
});
|
||||
|
||||
basePrototype = new base();
|
||||
// we need to make the options hash a property directly on the new instance
|
||||
// otherwise we'll modify the options hash on the prototype that we're
|
||||
// inheriting from
|
||||
basePrototype.options = $.widget.extend( {}, basePrototype.options );
|
||||
$.each( prototype, function( prop, value ) {
|
||||
if ( !$.isFunction( value ) ) {
|
||||
proxiedPrototype[ prop ] = value;
|
||||
return;
|
||||
}
|
||||
proxiedPrototype[ prop ] = (function() {
|
||||
var _super = function() {
|
||||
return base.prototype[ prop ].apply( this, arguments );
|
||||
},
|
||||
_superApply = function( args ) {
|
||||
return base.prototype[ prop ].apply( this, args );
|
||||
};
|
||||
return function() {
|
||||
var __super = this._super,
|
||||
__superApply = this._superApply,
|
||||
returnValue;
|
||||
|
||||
this._super = _super;
|
||||
this._superApply = _superApply;
|
||||
|
||||
returnValue = value.apply( this, arguments );
|
||||
|
||||
this._super = __super;
|
||||
this._superApply = __superApply;
|
||||
|
||||
return returnValue;
|
||||
};
|
||||
})();
|
||||
});
|
||||
constructor.prototype = $.widget.extend( basePrototype, {
|
||||
// TODO: remove support for widgetEventPrefix
|
||||
// always use the name + a colon as the prefix, e.g., draggable:start
|
||||
// don't prefix for widgets that aren't DOM-based
|
||||
widgetEventPrefix: existingConstructor ? (basePrototype.widgetEventPrefix || name) : name
|
||||
}, proxiedPrototype, {
|
||||
constructor: constructor,
|
||||
namespace: namespace,
|
||||
widgetName: name,
|
||||
widgetFullName: fullName
|
||||
});
|
||||
|
||||
// If this widget is being redefined then we need to find all widgets that
|
||||
// are inheriting from it and redefine all of them so that they inherit from
|
||||
// the new version of this widget. We're essentially trying to replace one
|
||||
// level in the prototype chain.
|
||||
if ( existingConstructor ) {
|
||||
$.each( existingConstructor._childConstructors, function( i, child ) {
|
||||
var childPrototype = child.prototype;
|
||||
|
||||
// redefine the child widget using the same prototype that was
|
||||
// originally used, but inherit from the new version of the base
|
||||
$.widget( childPrototype.namespace + "." + childPrototype.widgetName, constructor, child._proto );
|
||||
});
|
||||
// remove the list of existing child constructors from the old constructor
|
||||
// so the old child constructors can be garbage collected
|
||||
delete existingConstructor._childConstructors;
|
||||
} else {
|
||||
base._childConstructors.push( constructor );
|
||||
}
|
||||
|
||||
$.widget.bridge( name, constructor );
|
||||
|
||||
return constructor;
|
||||
};
|
||||
|
||||
$.widget.extend = function( target ) {
|
||||
var input = widget_slice.call( arguments, 1 ),
|
||||
inputIndex = 0,
|
||||
inputLength = input.length,
|
||||
key,
|
||||
value;
|
||||
for ( ; inputIndex < inputLength; inputIndex++ ) {
|
||||
for ( key in input[ inputIndex ] ) {
|
||||
value = input[ inputIndex ][ key ];
|
||||
if ( input[ inputIndex ].hasOwnProperty( key ) && value !== undefined ) {
|
||||
// Clone objects
|
||||
if ( $.isPlainObject( value ) ) {
|
||||
target[ key ] = $.isPlainObject( target[ key ] ) ?
|
||||
$.widget.extend( {}, target[ key ], value ) :
|
||||
// Don't extend strings, arrays, etc. with objects
|
||||
$.widget.extend( {}, value );
|
||||
// Copy everything else by reference
|
||||
} else {
|
||||
target[ key ] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return target;
|
||||
};
|
||||
|
||||
$.widget.bridge = function( name, object ) {
|
||||
var fullName = object.prototype.widgetFullName || name;
|
||||
$.fn[ name ] = function( options ) {
|
||||
var isMethodCall = typeof options === "string",
|
||||
args = widget_slice.call( arguments, 1 ),
|
||||
returnValue = this;
|
||||
|
||||
if ( isMethodCall ) {
|
||||
this.each(function() {
|
||||
var methodValue,
|
||||
instance = $.data( this, fullName );
|
||||
if ( options === "instance" ) {
|
||||
returnValue = instance;
|
||||
return false;
|
||||
}
|
||||
if ( !instance ) {
|
||||
return $.error( "cannot call methods on " + name + " prior to initialization; " +
|
||||
"attempted to call method '" + options + "'" );
|
||||
}
|
||||
if ( !$.isFunction( instance[options] ) || options.charAt( 0 ) === "_" ) {
|
||||
return $.error( "no such method '" + options + "' for " + name + " widget instance" );
|
||||
}
|
||||
methodValue = instance[ options ].apply( instance, args );
|
||||
if ( methodValue !== instance && methodValue !== undefined ) {
|
||||
returnValue = methodValue && methodValue.jquery ?
|
||||
returnValue.pushStack( methodValue.get() ) :
|
||||
methodValue;
|
||||
return false;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
|
||||
// Allow multiple hashes to be passed on init
|
||||
if ( args.length ) {
|
||||
options = $.widget.extend.apply( null, [ options ].concat(args) );
|
||||
}
|
||||
|
||||
this.each(function() {
|
||||
var instance = $.data( this, fullName );
|
||||
if ( instance ) {
|
||||
instance.option( options || {} );
|
||||
if ( instance._init ) {
|
||||
instance._init();
|
||||
}
|
||||
} else {
|
||||
$.data( this, fullName, new object( options, this ) );
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return returnValue;
|
||||
};
|
||||
};
|
||||
|
||||
$.Widget = function( /* options, element */ ) {};
|
||||
$.Widget._childConstructors = [];
|
||||
|
||||
$.Widget.prototype = {
|
||||
widgetName: "widget",
|
||||
widgetEventPrefix: "",
|
||||
defaultElement: "<div>",
|
||||
options: {
|
||||
disabled: false,
|
||||
|
||||
// callbacks
|
||||
create: null
|
||||
},
|
||||
_createWidget: function( options, element ) {
|
||||
element = $( element || this.defaultElement || this )[ 0 ];
|
||||
this.element = $( element );
|
||||
this.uuid = widget_uuid++;
|
||||
this.eventNamespace = "." + this.widgetName + this.uuid;
|
||||
|
||||
this.bindings = $();
|
||||
this.hoverable = $();
|
||||
this.focusable = $();
|
||||
|
||||
if ( element !== this ) {
|
||||
$.data( element, this.widgetFullName, this );
|
||||
this._on( true, this.element, {
|
||||
remove: function( event ) {
|
||||
if ( event.target === element ) {
|
||||
this.destroy();
|
||||
}
|
||||
}
|
||||
});
|
||||
this.document = $( element.style ?
|
||||
// element within the document
|
||||
element.ownerDocument :
|
||||
// element is window or document
|
||||
element.document || element );
|
||||
this.window = $( this.document[0].defaultView || this.document[0].parentWindow );
|
||||
}
|
||||
|
||||
this.options = $.widget.extend( {},
|
||||
this.options,
|
||||
this._getCreateOptions(),
|
||||
options );
|
||||
|
||||
this._create();
|
||||
this._trigger( "create", null, this._getCreateEventData() );
|
||||
this._init();
|
||||
},
|
||||
_getCreateOptions: $.noop,
|
||||
_getCreateEventData: $.noop,
|
||||
_create: $.noop,
|
||||
_init: $.noop,
|
||||
|
||||
destroy: function() {
|
||||
this._destroy();
|
||||
// we can probably remove the unbind calls in 2.0
|
||||
// all event bindings should go through this._on()
|
||||
this.element
|
||||
.unbind( this.eventNamespace )
|
||||
.removeData( this.widgetFullName )
|
||||
// support: jquery <1.6.3
|
||||
// http://bugs.jquery.com/ticket/9413
|
||||
.removeData( $.camelCase( this.widgetFullName ) );
|
||||
this.widget()
|
||||
.unbind( this.eventNamespace )
|
||||
.removeAttr( "aria-disabled" )
|
||||
.removeClass(
|
||||
this.widgetFullName + "-disabled " +
|
||||
"ui-state-disabled" );
|
||||
|
||||
// clean up events and states
|
||||
this.bindings.unbind( this.eventNamespace );
|
||||
this.hoverable.removeClass( "ui-state-hover" );
|
||||
this.focusable.removeClass( "ui-state-focus" );
|
||||
},
|
||||
_destroy: $.noop,
|
||||
|
||||
widget: function() {
|
||||
return this.element;
|
||||
},
|
||||
|
||||
option: function( key, value ) {
|
||||
var options = key,
|
||||
parts,
|
||||
curOption,
|
||||
i;
|
||||
|
||||
if ( arguments.length === 0 ) {
|
||||
// don't return a reference to the internal hash
|
||||
return $.widget.extend( {}, this.options );
|
||||
}
|
||||
|
||||
if ( typeof key === "string" ) {
|
||||
// handle nested keys, e.g., "foo.bar" => { foo: { bar: ___ } }
|
||||
options = {};
|
||||
parts = key.split( "." );
|
||||
key = parts.shift();
|
||||
if ( parts.length ) {
|
||||
curOption = options[ key ] = $.widget.extend( {}, this.options[ key ] );
|
||||
for ( i = 0; i < parts.length - 1; i++ ) {
|
||||
curOption[ parts[ i ] ] = curOption[ parts[ i ] ] || {};
|
||||
curOption = curOption[ parts[ i ] ];
|
||||
}
|
||||
key = parts.pop();
|
||||
if ( arguments.length === 1 ) {
|
||||
return curOption[ key ] === undefined ? null : curOption[ key ];
|
||||
}
|
||||
curOption[ key ] = value;
|
||||
} else {
|
||||
if ( arguments.length === 1 ) {
|
||||
return this.options[ key ] === undefined ? null : this.options[ key ];
|
||||
}
|
||||
options[ key ] = value;
|
||||
}
|
||||
}
|
||||
|
||||
this._setOptions( options );
|
||||
|
||||
return this;
|
||||
},
|
||||
_setOptions: function( options ) {
|
||||
var key;
|
||||
|
||||
for ( key in options ) {
|
||||
this._setOption( key, options[ key ] );
|
||||
}
|
||||
|
||||
return this;
|
||||
},
|
||||
_setOption: function( key, value ) {
|
||||
this.options[ key ] = value;
|
||||
|
||||
if ( key === "disabled" ) {
|
||||
this.widget()
|
||||
.toggleClass( this.widgetFullName + "-disabled", !!value );
|
||||
|
||||
// If the widget is becoming disabled, then nothing is interactive
|
||||
if ( value ) {
|
||||
this.hoverable.removeClass( "ui-state-hover" );
|
||||
this.focusable.removeClass( "ui-state-focus" );
|
||||
}
|
||||
}
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
enable: function() {
|
||||
return this._setOptions({ disabled: false });
|
||||
},
|
||||
disable: function() {
|
||||
return this._setOptions({ disabled: true });
|
||||
},
|
||||
|
||||
_on: function( suppressDisabledCheck, element, handlers ) {
|
||||
var delegateElement,
|
||||
instance = this;
|
||||
|
||||
// no suppressDisabledCheck flag, shuffle arguments
|
||||
if ( typeof suppressDisabledCheck !== "boolean" ) {
|
||||
handlers = element;
|
||||
element = suppressDisabledCheck;
|
||||
suppressDisabledCheck = false;
|
||||
}
|
||||
|
||||
// no element argument, shuffle and use this.element
|
||||
if ( !handlers ) {
|
||||
handlers = element;
|
||||
element = this.element;
|
||||
delegateElement = this.widget();
|
||||
} else {
|
||||
element = delegateElement = $( element );
|
||||
this.bindings = this.bindings.add( element );
|
||||
}
|
||||
|
||||
$.each( handlers, function( event, handler ) {
|
||||
function handlerProxy() {
|
||||
// allow widgets to customize the disabled handling
|
||||
// - disabled as an array instead of boolean
|
||||
// - disabled class as method for disabling individual parts
|
||||
if ( !suppressDisabledCheck &&
|
||||
( instance.options.disabled === true ||
|
||||
$( this ).hasClass( "ui-state-disabled" ) ) ) {
|
||||
return;
|
||||
}
|
||||
return ( typeof handler === "string" ? instance[ handler ] : handler )
|
||||
.apply( instance, arguments );
|
||||
}
|
||||
|
||||
// copy the guid so direct unbinding works
|
||||
if ( typeof handler !== "string" ) {
|
||||
handlerProxy.guid = handler.guid =
|
||||
handler.guid || handlerProxy.guid || $.guid++;
|
||||
}
|
||||
|
||||
var match = event.match( /^([\w:-]*)\s*(.*)$/ ),
|
||||
eventName = match[1] + instance.eventNamespace,
|
||||
selector = match[2];
|
||||
if ( selector ) {
|
||||
delegateElement.delegate( selector, eventName, handlerProxy );
|
||||
} else {
|
||||
element.bind( eventName, handlerProxy );
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
_off: function( element, eventName ) {
|
||||
eventName = (eventName || "").split( " " ).join( this.eventNamespace + " " ) +
|
||||
this.eventNamespace;
|
||||
element.unbind( eventName ).undelegate( eventName );
|
||||
|
||||
// Clear the stack to avoid memory leaks (#10056)
|
||||
this.bindings = $( this.bindings.not( element ).get() );
|
||||
this.focusable = $( this.focusable.not( element ).get() );
|
||||
this.hoverable = $( this.hoverable.not( element ).get() );
|
||||
},
|
||||
|
||||
_delay: function( handler, delay ) {
|
||||
function handlerProxy() {
|
||||
return ( typeof handler === "string" ? instance[ handler ] : handler )
|
||||
.apply( instance, arguments );
|
||||
}
|
||||
var instance = this;
|
||||
return setTimeout( handlerProxy, delay || 0 );
|
||||
},
|
||||
|
||||
_hoverable: function( element ) {
|
||||
this.hoverable = this.hoverable.add( element );
|
||||
this._on( element, {
|
||||
mouseenter: function( event ) {
|
||||
$( event.currentTarget ).addClass( "ui-state-hover" );
|
||||
},
|
||||
mouseleave: function( event ) {
|
||||
$( event.currentTarget ).removeClass( "ui-state-hover" );
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
_focusable: function( element ) {
|
||||
this.focusable = this.focusable.add( element );
|
||||
this._on( element, {
|
||||
focusin: function( event ) {
|
||||
$( event.currentTarget ).addClass( "ui-state-focus" );
|
||||
},
|
||||
focusout: function( event ) {
|
||||
$( event.currentTarget ).removeClass( "ui-state-focus" );
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
_trigger: function( type, event, data ) {
|
||||
var prop, orig,
|
||||
callback = this.options[ type ];
|
||||
|
||||
data = data || {};
|
||||
event = $.Event( event );
|
||||
event.type = ( type === this.widgetEventPrefix ?
|
||||
type :
|
||||
this.widgetEventPrefix + type ).toLowerCase();
|
||||
// the original event may come from any element
|
||||
// so we need to reset the target on the new event
|
||||
event.target = this.element[ 0 ];
|
||||
|
||||
// copy original event properties over to the new event
|
||||
orig = event.originalEvent;
|
||||
if ( orig ) {
|
||||
for ( prop in orig ) {
|
||||
if ( !( prop in event ) ) {
|
||||
event[ prop ] = orig[ prop ];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.element.trigger( event, data );
|
||||
return !( $.isFunction( callback ) &&
|
||||
callback.apply( this.element[0], [ event ].concat( data ) ) === false ||
|
||||
event.isDefaultPrevented() );
|
||||
}
|
||||
};
|
||||
|
||||
$.each( { show: "fadeIn", hide: "fadeOut" }, function( method, defaultEffect ) {
|
||||
$.Widget.prototype[ "_" + method ] = function( element, options, callback ) {
|
||||
if ( typeof options === "string" ) {
|
||||
options = { effect: options };
|
||||
}
|
||||
var hasOptions,
|
||||
effectName = !options ?
|
||||
method :
|
||||
options === true || typeof options === "number" ?
|
||||
defaultEffect :
|
||||
options.effect || defaultEffect;
|
||||
options = options || {};
|
||||
if ( typeof options === "number" ) {
|
||||
options = { duration: options };
|
||||
}
|
||||
hasOptions = !$.isEmptyObject( options );
|
||||
options.complete = callback;
|
||||
if ( options.delay ) {
|
||||
element.delay( options.delay );
|
||||
}
|
||||
if ( hasOptions && $.effects && $.effects.effect[ effectName ] ) {
|
||||
element[ method ]( options );
|
||||
} else if ( effectName !== method && element[ effectName ] ) {
|
||||
element[ effectName ]( options.duration, options.easing, callback );
|
||||
} else {
|
||||
element.queue(function( next ) {
|
||||
$( this )[ method ]();
|
||||
if ( callback ) {
|
||||
callback.call( element[ 0 ] );
|
||||
}
|
||||
next();
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
var widget = $.widget;
|
||||
|
||||
|
||||
|
||||
}));
|
12
xplan-ios/Base/Tool/CocoaHttpServer/Web/js/vconsole.min.js
vendored
Executable file
12
xplan-ios/Base/Tool/CocoaHttpServer/Web/js/vconsole.min.js
vendored
Executable file
File diff suppressed because one or more lines are too long
9
xplan-ios/Base/Tool/CocoaHttpServer/Web/upload.html
Executable file
9
xplan-ios/Base/Tool/CocoaHttpServer/Web/upload.html
Executable file
@@ -0,0 +1,9 @@
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv=\"content-type\" content=\"text/html; charset=UTF-8\">
|
||||
</head>
|
||||
<body>
|
||||
%MyFiles%
|
||||
</body>
|
||||
</html>
|
Reference in New Issue
Block a user