├── README
├── WebServer.xcodeproj
├── project.pbxproj
├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcuserdata
│ │ └── willonboy.xcuserdatad
│ │ └── UserInterfaceState.xcuserstate
└── xcuserdata
│ └── willonboy.xcuserdatad
│ ├── xcdebugger
│ └── Breakpoints.xcbkptlist
│ └── xcschemes
│ ├── WebServer.xcscheme
│ └── xcschememanagement.plist
└── WebServer
├── DDASLLogger.h
├── DDASLLogger.m
├── DDData.h
├── DDData.m
├── DDFileLogger.h
├── DDFileLogger.m
├── DDLog.h
├── DDLog.m
├── DDNumber.h
├── DDNumber.m
├── DDRange.h
├── DDRange.m
├── DDTTYLogger.h
├── DDTTYLogger.m
├── GCDAsyncSocket.h
├── GCDAsyncSocket.m
├── HTTPAsyncFileResponse.h
├── HTTPAsyncFileResponse.m
├── HTTPAuthenticationRequest.h
├── HTTPAuthenticationRequest.m
├── HTTPConnection.h
├── HTTPConnection.m
├── HTTPDataResponse.h
├── HTTPDataResponse.m
├── HTTPDynamicFileResponse.h
├── HTTPDynamicFileResponse.m
├── HTTPFileResponse.h
├── HTTPFileResponse.m
├── HTTPLogging.h
├── HTTPMessage.h
├── HTTPMessage.m
├── HTTPRedirectResponse.h
├── HTTPRedirectResponse.m
├── HTTPResponse.h
├── HTTPServer.h
├── HTTPServer.m
├── MainViewController.h
├── MainViewController.m
├── WTZAppDelegate.h
├── WTZAppDelegate.m
├── WTZHTTPConnection.h
├── WTZHTTPConnection.m
├── WebServer-Info.plist
├── WebServer-Prefix.pch
├── WebSocket.h
├── WebSocket.m
├── en.lproj
└── InfoPlist.strings
├── localhostAdresses.h
├── localhostAdresses.m
├── main.m
└── web
├── .svn
└── entries
├── file
├── .svn
│ ├── entries
│ └── props
│ │ └── swfupload.swf.svn-work
├── api.js
├── default.css
├── fileprogress.js
├── handlers.js
├── jquery.min.js
├── swfupload.js
├── swfupload.queue.js
└── swfupload.swf
├── image
├── .svn
│ ├── entries
│ └── props
│ │ ├── TestImageNoText_65x29.png.svn-work
│ │ ├── bg-interior-tile-drk.jpg.svn-work
│ │ ├── cancelbutton.gif.svn-work
│ │ ├── cloud.png.svn-work
│ │ └── header-bg.jpg.svn-work
├── TestImageNoText_65x29.png
├── bg-interior-tile-drk.jpg
├── cancelbutton.gif
├── cloud.png
└── header-bg.jpg
└── index.html
/README:
--------------------------------------------------------------------------------
1 | CocoaHTTPServer---multipart-form-data 是基于CocoaHTTPServer第三方库实现multipart/form-data协议来上传form表单文件(主要是上传文件,该项目并没有处理表单值域)
2 | site:www.willonboy.duapp.com
3 | QQ:962286684
4 |
--------------------------------------------------------------------------------
/WebServer.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/WebServer.xcodeproj/project.xcworkspace/xcuserdata/willonboy.xcuserdatad/UserInterfaceState.xcuserstate:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/willonboy/CocoaHTTPServer---multipart-form-data/4a9f7e1d0a3b683f9f7e898c05b2b4a7eb2137b9/WebServer.xcodeproj/project.xcworkspace/xcuserdata/willonboy.xcuserdatad/UserInterfaceState.xcuserstate
--------------------------------------------------------------------------------
/WebServer.xcodeproj/xcuserdata/willonboy.xcuserdatad/xcdebugger/Breakpoints.xcbkptlist:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/WebServer.xcodeproj/xcuserdata/willonboy.xcuserdatad/xcschemes/WebServer.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
4 |
7 |
8 |
14 |
20 |
21 |
22 |
23 |
24 |
29 |
30 |
31 |
32 |
38 |
39 |
40 |
41 |
49 |
50 |
56 |
57 |
58 |
59 |
60 |
61 |
67 |
68 |
74 |
75 |
76 |
77 |
79 |
80 |
83 |
84 |
85 |
--------------------------------------------------------------------------------
/WebServer.xcodeproj/xcuserdata/willonboy.xcuserdatad/xcschemes/xcschememanagement.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SchemeUserState
6 |
7 | WebServer.xcscheme
8 |
9 | orderHint
10 | 0
11 |
12 |
13 | SuppressBuildableAutocreation
14 |
15 | 6C07AA5714BA76C9006E84B4
16 |
17 | primary
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/WebServer/DDASLLogger.h:
--------------------------------------------------------------------------------
1 | #import
2 | #import
3 |
4 | #import "DDLog.h"
5 |
6 | /**
7 | * Welcome to Cocoa Lumberjack!
8 | *
9 | * The Google Code page has a wealth of documentation if you have any questions.
10 | * http://code.google.com/p/cocoalumberjack/
11 | *
12 | * If you're new to the project you may wish to read the "Getting Started" page.
13 | * http://code.google.com/p/cocoalumberjack/wiki/GettingStarted
14 | *
15 | *
16 | * This class provides a logger for the Apple System Log facility.
17 | *
18 | * As described in the "Getting Started" page,
19 | * the traditional NSLog() function directs it's output to two places:
20 | *
21 | * - Apple System Log
22 | * - StdErr (if stderr is a TTY) so log statements show up in Xcode console
23 | *
24 | * To duplicate NSLog() functionality you can simply add this logger and a tty logger.
25 | * However, if you instead choose to use file logging (for faster performance),
26 | * you may choose to use a file logger and a tty logger.
27 | **/
28 |
29 | @interface DDASLLogger : DDAbstractLogger
30 | {
31 | aslclient client;
32 | }
33 |
34 | + (DDASLLogger *)sharedInstance;
35 |
36 | // Inherited from DDAbstractLogger
37 |
38 | // - (id )logFormatter;
39 | // - (void)setLogFormatter:(id )formatter;
40 |
41 | @end
42 |
--------------------------------------------------------------------------------
/WebServer/DDASLLogger.m:
--------------------------------------------------------------------------------
1 | #import "DDASLLogger.h"
2 |
3 | #import
4 |
5 |
6 | @implementation DDASLLogger
7 |
8 | static DDASLLogger *sharedInstance;
9 |
10 | /**
11 | * The runtime sends initialize to each class in a program exactly one time just before the class,
12 | * or any class that inherits from it, is sent its first message from within the program. (Thus the
13 | * method may never be invoked if the class is not used.) The runtime sends the initialize message to
14 | * classes in a thread-safe manner. Superclasses receive this message before their subclasses.
15 | *
16 | * This method may also be called directly (assumably by accident), hence the safety mechanism.
17 | **/
18 | + (void)initialize
19 | {
20 | static BOOL initialized = NO;
21 | if (!initialized)
22 | {
23 | initialized = YES;
24 |
25 | sharedInstance = [[DDASLLogger alloc] init];
26 | }
27 | }
28 |
29 | + (DDASLLogger *)sharedInstance
30 | {
31 | return sharedInstance;
32 | }
33 |
34 | - (id)init
35 | {
36 | if (sharedInstance != nil)
37 | {
38 | [self release];
39 | return nil;
40 | }
41 |
42 | if ((self = [super init]))
43 | {
44 | // A default asl client is provided for the main thread,
45 | // but background threads need to create their own client.
46 |
47 | client = asl_open(NULL, "com.apple.console", 0);
48 | }
49 | return self;
50 | }
51 |
52 | - (void)logMessage:(DDLogMessage *)logMessage
53 | {
54 | NSString *logMsg = logMessage->logMsg;
55 |
56 | if (formatter)
57 | {
58 | logMsg = [formatter formatLogMessage:logMessage];
59 | }
60 |
61 | if (logMsg)
62 | {
63 | const char *msg = [logMsg UTF8String];
64 |
65 | int aslLogLevel;
66 | switch (logMessage->logLevel)
67 | {
68 | // Note: By default ASL will filter anything above level 5 (Notice).
69 | // So our mappings shouldn't go above that level.
70 |
71 | case 1 : aslLogLevel = ASL_LEVEL_CRIT; break;
72 | case 2 : aslLogLevel = ASL_LEVEL_ERR; break;
73 | case 3 : aslLogLevel = ASL_LEVEL_WARNING; break;
74 | default : aslLogLevel = ASL_LEVEL_NOTICE; break;
75 | }
76 |
77 | asl_log(client, NULL, aslLogLevel, "%s", msg);
78 | }
79 | }
80 |
81 | - (NSString *)loggerName
82 | {
83 | return @"cocoa.lumberjack.aslLogger";
84 | }
85 |
86 | @end
87 |
--------------------------------------------------------------------------------
/WebServer/DDData.h:
--------------------------------------------------------------------------------
1 | #import
2 |
3 | @interface NSData (DDData)
4 |
5 | - (NSData *)md5Digest;
6 |
7 | - (NSData *)sha1Digest;
8 |
9 | - (NSString *)hexStringValue;
10 |
11 | - (NSString *)base64Encoded;
12 | - (NSData *)base64Decoded;
13 |
14 | @end
15 |
--------------------------------------------------------------------------------
/WebServer/DDData.m:
--------------------------------------------------------------------------------
1 | #import "DDData.h"
2 | #import
3 |
4 |
5 | @implementation NSData (DDData)
6 |
7 | static char encodingTable[64] = {
8 | 'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P',
9 | 'Q','R','S','T','U','V','W','X','Y','Z','a','b','c','d','e','f',
10 | 'g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v',
11 | 'w','x','y','z','0','1','2','3','4','5','6','7','8','9','+','/' };
12 |
13 | - (NSData *)md5Digest
14 | {
15 | unsigned char result[CC_MD5_DIGEST_LENGTH];
16 |
17 | CC_MD5([self bytes], (CC_LONG)[self length], result);
18 | return [NSData dataWithBytes:result length:CC_MD5_DIGEST_LENGTH];
19 | }
20 |
21 | - (NSData *)sha1Digest
22 | {
23 | unsigned char result[CC_SHA1_DIGEST_LENGTH];
24 |
25 | CC_SHA1([self bytes], (CC_LONG)[self length], result);
26 | return [NSData dataWithBytes:result length:CC_SHA1_DIGEST_LENGTH];
27 | }
28 |
29 | - (NSString *)hexStringValue
30 | {
31 | NSMutableString *stringBuffer = [NSMutableString stringWithCapacity:([self length] * 2)];
32 |
33 | const unsigned char *dataBuffer = [self bytes];
34 | int i;
35 |
36 | for (i = 0; i < [self length]; ++i)
37 | {
38 | [stringBuffer appendFormat:@"%02x", (unsigned long)dataBuffer[i]];
39 | }
40 |
41 | return [[stringBuffer copy] autorelease];
42 | }
43 |
44 | - (NSString *)base64Encoded
45 | {
46 | const unsigned char *bytes = [self bytes];
47 | NSMutableString *result = [NSMutableString stringWithCapacity:[self length]];
48 | unsigned long ixtext = 0;
49 | unsigned long lentext = [self length];
50 | long ctremaining = 0;
51 | unsigned char inbuf[3], outbuf[4];
52 | unsigned short i = 0;
53 | unsigned short charsonline = 0, ctcopy = 0;
54 | unsigned long ix = 0;
55 |
56 | while( YES )
57 | {
58 | ctremaining = lentext - ixtext;
59 | if( ctremaining <= 0 ) break;
60 |
61 | for( i = 0; i < 3; i++ ) {
62 | ix = ixtext + i;
63 | if( ix < lentext ) inbuf[i] = bytes[ix];
64 | else inbuf [i] = 0;
65 | }
66 |
67 | outbuf [0] = (inbuf [0] & 0xFC) >> 2;
68 | outbuf [1] = ((inbuf [0] & 0x03) << 4) | ((inbuf [1] & 0xF0) >> 4);
69 | outbuf [2] = ((inbuf [1] & 0x0F) << 2) | ((inbuf [2] & 0xC0) >> 6);
70 | outbuf [3] = inbuf [2] & 0x3F;
71 | ctcopy = 4;
72 |
73 | switch( ctremaining )
74 | {
75 | case 1:
76 | ctcopy = 2;
77 | break;
78 | case 2:
79 | ctcopy = 3;
80 | break;
81 | }
82 |
83 | for( i = 0; i < ctcopy; i++ )
84 | [result appendFormat:@"%c", encodingTable[outbuf[i]]];
85 |
86 | for( i = ctcopy; i < 4; i++ )
87 | [result appendString:@"="];
88 |
89 | ixtext += 3;
90 | charsonline += 4;
91 | }
92 |
93 | return [NSString stringWithString:result];
94 | }
95 |
96 | - (NSData *)base64Decoded
97 | {
98 | const unsigned char *bytes = [self bytes];
99 | NSMutableData *result = [NSMutableData dataWithCapacity:[self length]];
100 |
101 | unsigned long ixtext = 0;
102 | unsigned long lentext = [self length];
103 | unsigned char ch = 0;
104 | unsigned char inbuf[4], outbuf[3];
105 | short i = 0, ixinbuf = 0;
106 | BOOL flignore = NO;
107 | BOOL flendtext = NO;
108 |
109 | while( YES )
110 | {
111 | if( ixtext >= lentext ) break;
112 | ch = bytes[ixtext++];
113 | flignore = NO;
114 |
115 | if( ( ch >= 'A' ) && ( ch <= 'Z' ) ) ch = ch - 'A';
116 | else if( ( ch >= 'a' ) && ( ch <= 'z' ) ) ch = ch - 'a' + 26;
117 | else if( ( ch >= '0' ) && ( ch <= '9' ) ) ch = ch - '0' + 52;
118 | else if( ch == '+' ) ch = 62;
119 | else if( ch == '=' ) flendtext = YES;
120 | else if( ch == '/' ) ch = 63;
121 | else flignore = YES;
122 |
123 | if( ! flignore )
124 | {
125 | short ctcharsinbuf = 3;
126 | BOOL flbreak = NO;
127 |
128 | if( flendtext )
129 | {
130 | if( ! ixinbuf ) break;
131 | if( ( ixinbuf == 1 ) || ( ixinbuf == 2 ) ) ctcharsinbuf = 1;
132 | else ctcharsinbuf = 2;
133 | ixinbuf = 3;
134 | flbreak = YES;
135 | }
136 |
137 | inbuf [ixinbuf++] = ch;
138 |
139 | if( ixinbuf == 4 )
140 | {
141 | ixinbuf = 0;
142 | outbuf [0] = ( inbuf[0] << 2 ) | ( ( inbuf[1] & 0x30) >> 4 );
143 | outbuf [1] = ( ( inbuf[1] & 0x0F ) << 4 ) | ( ( inbuf[2] & 0x3C ) >> 2 );
144 | outbuf [2] = ( ( inbuf[2] & 0x03 ) << 6 ) | ( inbuf[3] & 0x3F );
145 |
146 | for( i = 0; i < ctcharsinbuf; i++ )
147 | [result appendBytes:&outbuf[i] length:1];
148 | }
149 |
150 | if( flbreak ) break;
151 | }
152 | }
153 |
154 | return [NSData dataWithData:result];
155 | }
156 |
157 | @end
158 |
--------------------------------------------------------------------------------
/WebServer/DDFileLogger.h:
--------------------------------------------------------------------------------
1 | #import
2 | #import "DDLog.h"
3 |
4 | @class DDLogFileInfo;
5 |
6 |
7 | // Default configuration and safety/sanity values.
8 | //
9 | // maximumFileSize -> DEFAULT_LOG_MAX_FILE_SIZE
10 | // rollingFrequency -> DEFAULT_LOG_ROLLING_FREQUENCY
11 | // maximumNumberOfLogFiles -> DEFAULT_LOG_MAX_NUM_LOG_FILES
12 | //
13 | // You should carefully consider the proper configuration values for your application.
14 |
15 | #define DEFAULT_LOG_MAX_FILE_SIZE (1024 * 1024) // 1 MB
16 | #define DEFAULT_LOG_ROLLING_FREQUENCY (60 * 60 * 24) // 24 Hours
17 | #define DEFAULT_LOG_MAX_NUM_LOG_FILES (5) // 5 Files
18 |
19 |
20 | ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
21 | #pragma mark -
22 | ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
23 |
24 | // The LogFileManager protocol is designed to allow you to control all aspects of your log files.
25 | //
26 | // The primary purpose of this is to allow you to do something with the log files after they have been rolled.
27 | // Perhaps you want to compress them to save disk space.
28 | // Perhaps you want to upload them to an FTP server.
29 | // Perhaps you want to run some analytics on the file.
30 | //
31 | // A default LogFileManager is, of course, provided.
32 | // The default LogFileManager simply deletes old log files according to the maximumNumberOfLogFiles property.
33 | //
34 | // This protocol provides various methods to fetch the list of log files.
35 | //
36 | // There are two variants: sorted and unsorted.
37 | // If sorting is not necessary, the unsorted variant is obviously faster.
38 | // The sorted variant will return an array sorted by when the log files were created,
39 | // with the most recently created log file at index 0, and the oldest log file at the end of the array.
40 | //
41 | // You can fetch only the log file paths (full path including name), log file names (name only),
42 | // or an array of DDLogFileInfo objects.
43 | // The DDLogFileInfo class is documented below, and provides a handy wrapper that
44 | // gives you easy access to various file attributes such as the creation date or the file size.
45 |
46 | @protocol DDLogFileManager
47 | @required
48 |
49 | // Public properties
50 |
51 | @property (readwrite, assign) NSUInteger maximumNumberOfLogFiles;
52 |
53 | // Public methods
54 |
55 | - (NSString *)logsDirectory;
56 |
57 | - (NSArray *)unsortedLogFilePaths;
58 | - (NSArray *)unsortedLogFileNames;
59 | - (NSArray *)unsortedLogFileInfos;
60 |
61 | - (NSArray *)sortedLogFilePaths;
62 | - (NSArray *)sortedLogFileNames;
63 | - (NSArray *)sortedLogFileInfos;
64 |
65 | // Private methods (only to be used by DDFileLogger)
66 |
67 | - (NSString *)createNewLogFile;
68 |
69 | @optional
70 |
71 | // Notifications from DDFileLogger
72 |
73 | - (void)didArchiveLogFile:(NSString *)logFilePath;
74 | - (void)didRollAndArchiveLogFile:(NSString *)logFilePath;
75 |
76 | @end
77 |
78 | ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
79 | #pragma mark -
80 | ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
81 |
82 | // Default log file manager.
83 | //
84 | // All log files are placed inside the logsDirectory.
85 | // On Mac, this is in ~/Library/Application Support//Logs.
86 | // On iPhone, this is in ~/Documents/Logs.
87 | //
88 | // Log files are named "log-.txt",
89 | // where uuid is a 6 character hexadecimal consisting of the set [0123456789ABCDEF].
90 | //
91 | // Archived log files are automatically deleted according to the maximumNumberOfLogFiles property.
92 |
93 | @interface DDLogFileManagerDefault : NSObject
94 | {
95 | NSUInteger maximumNumberOfLogFiles;
96 | }
97 |
98 | @end
99 |
100 | ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
101 | #pragma mark -
102 | ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
103 |
104 | // Most users will want file log messages to be prepended with the date and time.
105 | // Rather than forcing the majority of users to write their own formatter,
106 | // we will supply a logical default formatter.
107 | // Users can easily replace this formatter with their own by invoking the setLogFormatter method.
108 | // It can also be removed by calling setLogFormatter, and passing a nil parameter.
109 | //
110 | // In addition to the convenience of having a logical default formatter,
111 | // it will also provide a template that makes it easy for developers to copy and change.
112 |
113 | @interface DDLogFileFormatterDefault : NSObject
114 | {
115 | NSDateFormatter *dateFormatter;
116 | }
117 |
118 | @end
119 |
120 | ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
121 | #pragma mark -
122 | ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
123 |
124 | @interface DDFileLogger : DDAbstractLogger
125 | {
126 | id logFileManager;
127 |
128 | DDLogFileInfo *currentLogFileInfo;
129 | NSFileHandle *currentLogFileHandle;
130 |
131 | NSTimer *rollingTimer;
132 |
133 | unsigned long long maximumFileSize;
134 | NSTimeInterval rollingFrequency;
135 | }
136 |
137 | - (id)init;
138 | - (id)initWithLogFileManager:(id )logFileManager;
139 |
140 | // Configuration
141 | //
142 | // maximumFileSize:
143 | // The approximate maximum size to allow log files to grow.
144 | // If a log file is larger than this value after a write,
145 | // then the log file is rolled.
146 | //
147 | // rollingFrequency
148 | // How often to roll the log file.
149 | // The frequency is given as an NSTimeInterval, which is a double that specifies the interval in seconds.
150 | // Once the log file gets to be this old, it is rolled.
151 | //
152 | // Both the maximumFileSize and the rollingFrequency are used to manage rolling.
153 | // Whichever occurs first will cause the log file to be rolled.
154 | //
155 | // For example:
156 | // The rollingFrequency is 24 hours,
157 | // but the log file surpasses the maximumFileSize after only 20 hours.
158 | // The log file will be rolled at that 20 hour mark.
159 | // A new log file will be created, and the 24 hour timer will be restarted.
160 | //
161 | // logFileManager
162 | // Allows you to retrieve the list of log files,
163 | // and configure the maximum number of archived log files to keep.
164 |
165 | @property (readwrite, assign) unsigned long long maximumFileSize;
166 |
167 | @property (readwrite, assign) NSTimeInterval rollingFrequency;
168 |
169 | @property (nonatomic, readonly) id logFileManager;
170 |
171 |
172 | // You can optionally force the current log file to be rolled with this method.
173 |
174 | - (void)rollLogFile;
175 |
176 | // Inherited from DDAbstractLogger
177 |
178 | // - (id )logFormatter;
179 | // - (void)setLogFormatter:(id )formatter;
180 |
181 | @end
182 |
183 | ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
184 | #pragma mark -
185 | ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
186 |
187 | // DDLogFileInfo is a simple class that provides access to various file attributes.
188 | // It provides good performance as it only fetches the information if requested,
189 | // and it caches the information to prevent duplicate fetches.
190 | //
191 | // It was designed to provide quick snapshots of the current state of log files,
192 | // and to help sort log files in an array.
193 | //
194 | // This class does not monitor the files, or update it's cached attribute values if the file changes on disk.
195 | // This is not what the class was designed for.
196 | //
197 | // If you absolutely must get updated values,
198 | // you can invoke the reset method which will clear the cache.
199 |
200 | @interface DDLogFileInfo : NSObject
201 | {
202 | NSString *filePath;
203 | NSString *fileName;
204 |
205 | NSDictionary *fileAttributes;
206 |
207 | NSDate *creationDate;
208 | NSDate *modificationDate;
209 |
210 | unsigned long long fileSize;
211 | }
212 |
213 | @property (nonatomic, readonly) NSString *filePath;
214 | @property (nonatomic, readonly) NSString *fileName;
215 |
216 | @property (nonatomic, readonly) NSDictionary *fileAttributes;
217 |
218 | @property (nonatomic, readonly) NSDate *creationDate;
219 | @property (nonatomic, readonly) NSDate *modificationDate;
220 |
221 | @property (nonatomic, readonly) unsigned long long fileSize;
222 |
223 | @property (nonatomic, readonly) NSTimeInterval age;
224 |
225 | @property (nonatomic, readwrite) BOOL isArchived;
226 |
227 | + (id)logFileWithPath:(NSString *)filePath;
228 |
229 | - (id)initWithFilePath:(NSString *)filePath;
230 |
231 | - (void)reset;
232 | - (void)renameFile:(NSString *)newFileName;
233 |
234 | #if TARGET_IPHONE_SIMULATOR
235 |
236 | // So here's the situation.
237 | // Extended attributes are perfect for what we're trying to do here (marking files as archived).
238 | // This is exactly what extended attributes were designed for.
239 | //
240 | // But Apple screws us over on the simulator.
241 | // Everytime you build-and-go, they copy the application into a new folder on the hard drive,
242 | // and as part of the process they strip extended attributes from our log files.
243 | // Normally, a copy of a file preserves extended attributes.
244 | // So obviously Apple has gone to great lengths to piss us off.
245 | //
246 | // Thus we use a slightly different tactic for marking log files as archived in the simulator.
247 | // That way it "just works" and there's no confusion when testing.
248 | //
249 | // The difference in method names is indicative of the difference in functionality.
250 | // On the simulator we add an attribute by appending a filename extension.
251 | //
252 | // For example:
253 | // log-ABC123.txt -> log-ABC123.archived.txt
254 |
255 | - (BOOL)hasExtensionAttributeWithName:(NSString *)attrName;
256 |
257 | - (void)addExtensionAttributeWithName:(NSString *)attrName;
258 | - (void)removeExtensionAttributeWithName:(NSString *)attrName;
259 |
260 | #else
261 |
262 | // Normal use of extended attributes used everywhere else,
263 | // such as on Macs and on iPhone devices.
264 |
265 | - (BOOL)hasExtendedAttributeWithName:(NSString *)attrName;
266 |
267 | - (void)addExtendedAttributeWithName:(NSString *)attrName;
268 | - (void)removeExtendedAttributeWithName:(NSString *)attrName;
269 |
270 | #endif
271 |
272 | - (NSComparisonResult)reverseCompareByCreationDate:(DDLogFileInfo *)another;
273 | - (NSComparisonResult)reverseCompareByModificationDate:(DDLogFileInfo *)another;
274 |
275 | @end
276 |
--------------------------------------------------------------------------------
/WebServer/DDLog.h:
--------------------------------------------------------------------------------
1 | #import
2 |
3 | /**
4 | * Welcome to Cocoa Lumberjack!
5 | *
6 | * The Google Code page has a wealth of documentation if you have any questions.
7 | * http://code.google.com/p/cocoalumberjack/
8 | *
9 | * If you're new to the project you may wish to read the "Getting Started" page.
10 | * http://code.google.com/p/cocoalumberjack/wiki/GettingStarted
11 | *
12 | * Otherwise, here is a quick refresher.
13 | * There are three steps to using the macros:
14 | *
15 | * Step 1:
16 | * Import the header in your implementation file:
17 | *
18 | * #import "DDLog.h"
19 | *
20 | * Step 2:
21 | * Define your logging level in your implementation file:
22 | *
23 | * // Log levels: off, error, warn, info, verbose
24 | * static const int ddLogLevel = LOG_LEVEL_VERBOSE;
25 | *
26 | * Step 3:
27 | * Replace your NSLog statements with DDLog statements according to the severity of the message.
28 | *
29 | * NSLog(@"Fatal error, no dohickey found!"); -> DDLogError(@"Fatal error, no dohickey found!");
30 | *
31 | * DDLog works exactly the same as NSLog.
32 | * This means you can pass it multiple variables just like NSLog.
33 | **/
34 |
35 |
36 | // Can we use Grand Central Dispatch?
37 | //
38 | // This question is actually composed of two parts:
39 | // 1. Is it available to the compiler?
40 | // 2. Is it available to the runtime?
41 | //
42 | // For example, if we are building a universal iPad/iPhone app,
43 | // our base SDK may be iOS 4, but our deployment target would be iOS 3.2.
44 | // In this case we can compile against the GCD libraries (which are available starting with iOS 4),
45 | // but we can only use them at runtime if running on iOS 4 or later.
46 | // If running on an iPad using iOS 3.2, we need to use runtime checks for backwards compatibility.
47 | //
48 | // The solution is to use a combination of compile-time and run-time macros.
49 | //
50 | // Note that when the minimum supported SDK supports GCD
51 | // the run-time checks will be compiled out during optimization.
52 |
53 | #if TARGET_OS_IPHONE
54 |
55 | // Compiling for iPod/iPhone/iPad
56 |
57 | #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 40000 // 4.0 supported
58 |
59 | #if __IPHONE_OS_VERSION_MIN_REQUIRED >= 40000 // 4.0 supported and required
60 |
61 | #define IS_GCD_AVAILABLE YES
62 | #define GCD_MAYBE_AVAILABLE 1
63 | #define GCD_MAYBE_UNAVAILABLE 0
64 |
65 | #else // 4.0 supported but not required
66 |
67 | #ifndef NSFoundationVersionNumber_iPhoneOS_4_0
68 | #define NSFoundationVersionNumber_iPhoneOS_4_0 751.32
69 | #endif
70 |
71 | #define IS_GCD_AVAILABLE (NSFoundationVersionNumber >= NSFoundationVersionNumber_iPhoneOS_4_0)
72 | #define GCD_MAYBE_AVAILABLE 1
73 | #define GCD_MAYBE_UNAVAILABLE 1
74 |
75 | #endif
76 |
77 | #else // 4.0 not supported
78 |
79 | #define IS_GCD_AVAILABLE NO
80 | #define GCD_MAYBE_AVAILABLE 0
81 | #define GCD_MAYBE_UNAVAILABLE 1
82 |
83 | #endif
84 |
85 | #else
86 |
87 | // Compiling for Mac OS X
88 |
89 | #if MAC_OS_X_VERSION_MAX_ALLOWED >= 1060 // 10.6 supported
90 |
91 | #if MAC_OS_X_VERSION_MIN_REQUIRED >= 1060 // 10.6 supported and required
92 |
93 | #define IS_GCD_AVAILABLE YES
94 | #define GCD_MAYBE_AVAILABLE 1
95 | #define GCD_MAYBE_UNAVAILABLE 0
96 |
97 | #else // 10.6 supported but not required
98 |
99 | #ifndef NSFoundationVersionNumber10_6
100 | #define NSFoundationVersionNumber10_6 751.00
101 | #endif
102 |
103 | #define IS_GCD_AVAILABLE (NSFoundationVersionNumber >= NSFoundationVersionNumber10_6)
104 | #define GCD_MAYBE_AVAILABLE 1
105 | #define GCD_MAYBE_UNAVAILABLE 1
106 |
107 | #endif
108 |
109 | #else // 10.6 not supported
110 |
111 | #define IS_GCD_AVAILABLE NO
112 | #define GCD_MAYBE_AVAILABLE 0
113 | #define GCD_MAYBE_UNAVAILABLE 1
114 |
115 | #endif
116 |
117 | #endif
118 |
119 | /*
120 | // Uncomment for quick temporary test to see if it builds for older OS targets
121 | #undef IS_GCD_AVAILABLE
122 | #undef GCD_MAYBE_AVAILABLE
123 | #undef GCD_MAYBE_UNAVAILABLE
124 |
125 | #define IS_GCD_AVAILABLE NO
126 | #define GCD_MAYBE_AVAILABLE 0
127 | #define GCD_MAYBE_UNAVAILABLE 1
128 | */
129 |
130 | @class DDLogMessage;
131 |
132 | @protocol DDLogger;
133 | @protocol DDLogFormatter;
134 |
135 | /**
136 | * Define our big multiline macros so all the other macros will be easy to read.
137 | **/
138 |
139 | #define LOG_MACRO(isSynchronous, lvl, flg, ctx, fnct, frmt, ...) \
140 | [DDLog log:isSynchronous \
141 | level:lvl \
142 | flag:flg \
143 | context:ctx \
144 | file:__FILE__ \
145 | function:fnct \
146 | line:__LINE__ \
147 | format:(frmt), ##__VA_ARGS__]
148 |
149 |
150 | #define LOG_OBJC_MACRO(sync, lvl, flg, ctx, frmt, ...) \
151 | LOG_MACRO(sync, lvl, flg, ctx, sel_getName(_cmd), frmt, ##__VA_ARGS__)
152 |
153 | #define LOG_C_MACRO(sync, lvl, flag, ctx, frmt, ...) \
154 | LOG_MACRO(sync, lvl, flg, ctx, __FUNCTION__, frmt, ##__VA_ARGS__)
155 |
156 | #define SYNC_LOG_OBJC_MACRO(lvl, flg, ctx, frmt, ...) \
157 | LOG_OBJC_MACRO(YES, lvl, flg, ctx, frmt, ##__VA_ARGS__)
158 |
159 | #define ASYNC_LOG_OBJC_MACRO(lvl, flg, ctx, frmt, ...) \
160 | LOG_OBJC_MACRO( NO, lvl, flg, ctx, frmt, ##__VA_ARGS__)
161 |
162 | #define SYNC_LOG_C_MACRO(lvl, flg, ctx, frmt, ...) \
163 | LOG_C_MACRO(YES, lvl, flg, ctx, frmt, ##__VA_ARGS__)
164 |
165 | #define ASYNC_LOG_C_MACRO(lvl, flg, ctx, frmt, ...) \
166 | LOG_C_MACRO( NO, lvl, flg, ctx, frmt, ##__VA_ARGS__)
167 |
168 |
169 | #define LOG_MAYBE(sync, lvl, flg, ctx, fnct, frmt, ...) \
170 | do { if(lvl & flg) LOG_MACRO(sync, lvl, flg, ctx, fnct, frmt, ##__VA_ARGS__); } while(0)
171 |
172 | #define LOG_OBJC_MAYBE(sync, lvl, flg, ctx, frmt, ...) \
173 | LOG_MAYBE(sync, lvl, flg, ctx, sel_getName(_cmd), frmt, ##__VA_ARGS__)
174 |
175 | #define LOG_C_MAYBE(sync, lvl, flg, ctx, frmt, ...) \
176 | LOG_MAYBE(sync, lvl, flg, ctx, __FUNCTION__, frmt, ##__VA_ARGS__)
177 |
178 | #define SYNC_LOG_OBJC_MAYBE(lvl, flg, ctx, frmt, ...) \
179 | LOG_OBJC_MAYBE(YES, lvl, flg, ctx, frmt, ##__VA_ARGS__)
180 |
181 | #define ASYNC_LOG_OBJC_MAYBE(lvl, flg, ctx, frmt, ...) \
182 | LOG_OBJC_MAYBE( NO, lvl, flg, ctx, frmt, ##__VA_ARGS__)
183 |
184 | #define SYNC_LOG_C_MAYBE(lvl, flg, ctx, frmt, ...) \
185 | LOG_C_MAYBE(YES, lvl, flg, ctx, frmt, ##__VA_ARGS__)
186 |
187 | #define ASYNC_LOG_C_MAYBE(lvl, flg, ctx, frmt, ...) \
188 | LOG_C_MAYBE( NO, lvl, flg, ctx, frmt, ##__VA_ARGS__)
189 |
190 | /**
191 | * Define our standard log levels.
192 | *
193 | * We default to only 4 levels because it makes it easier for beginners
194 | * to make the transition to a logging framework.
195 | *
196 | * More advanced users may choose to completely customize the levels (and level names) to suite their needs.
197 | * For more information on this see the "Custom Log Levels" page:
198 | * http://code.google.com/p/cocoalumberjack/wiki/CustomLogLevels
199 | *
200 | * Advanced users may also notice that we're using a bitmask.
201 | * This is to allow for custom fine grained logging:
202 | * http://code.google.com/p/cocoalumberjack/wiki/FineGrainedLogging
203 | **/
204 |
205 | #define LOG_FLAG_ERROR (1 << 0) // 0...0001
206 | #define LOG_FLAG_WARN (1 << 1) // 0...0010
207 | #define LOG_FLAG_INFO (1 << 2) // 0...0100
208 | #define LOG_FLAG_VERBOSE (1 << 3) // 0...1000
209 |
210 | #define LOG_LEVEL_OFF 0
211 | #define LOG_LEVEL_ERROR (LOG_FLAG_ERROR) // 0...0001
212 | #define LOG_LEVEL_WARN (LOG_FLAG_ERROR | LOG_FLAG_WARN) // 0...0011
213 | #define LOG_LEVEL_INFO (LOG_FLAG_ERROR | LOG_FLAG_WARN | LOG_FLAG_INFO) // 0...0111
214 | #define LOG_LEVEL_VERBOSE (LOG_FLAG_ERROR | LOG_FLAG_WARN | LOG_FLAG_INFO | LOG_FLAG_VERBOSE) // 0...1111
215 |
216 | #define LOG_ERROR (ddLogLevel & LOG_FLAG_ERROR)
217 | #define LOG_WARN (ddLogLevel & LOG_FLAG_WARN)
218 | #define LOG_INFO (ddLogLevel & LOG_FLAG_INFO)
219 | #define LOG_VERBOSE (ddLogLevel & LOG_FLAG_VERBOSE)
220 |
221 | #define DDLogError(frmt, ...) SYNC_LOG_OBJC_MAYBE(ddLogLevel, LOG_FLAG_ERROR, 0, frmt, ##__VA_ARGS__)
222 | #define DDLogWarn(frmt, ...) ASYNC_LOG_OBJC_MAYBE(ddLogLevel, LOG_FLAG_WARN, 0, frmt, ##__VA_ARGS__)
223 | #define DDLogInfo(frmt, ...) ASYNC_LOG_OBJC_MAYBE(ddLogLevel, LOG_FLAG_INFO, 0, frmt, ##__VA_ARGS__)
224 | #define DDLogVerbose(frmt, ...) ASYNC_LOG_OBJC_MAYBE(ddLogLevel, LOG_FLAG_VERBOSE, 0, frmt, ##__VA_ARGS__)
225 |
226 | #define DDLogCError(frmt, ...) SYNC_LOG_C_MAYBE(ddLogLevel, LOG_FLAG_ERROR, 0, frmt, ##__VA_ARGS__)
227 | #define DDLogCWarn(frmt, ...) ASYNC_LOG_C_MAYBE(ddLogLevel, LOG_FLAG_WARN, 0, frmt, ##__VA_ARGS__)
228 | #define DDLogCInfo(frmt, ...) ASYNC_LOG_C_MAYBE(ddLogLevel, LOG_FLAG_INFO, 0, frmt, ##__VA_ARGS__)
229 | #define DDLogCVerbose(frmt, ...) ASYNC_LOG_C_MAYBE(ddLogLevel, LOG_FLAG_VERBOSE, 0, frmt, ##__VA_ARGS__)
230 |
231 | /**
232 | * The THIS_FILE macro gives you an NSString of the file name.
233 | * For simplicity and clarity, the file name does not include the full path or file extension.
234 | *
235 | * For example: DDLogWarn(@"%@: Unable to find thingy", THIS_FILE) -> @"MyViewController: Unable to find thingy"
236 | **/
237 |
238 | NSString *ExtractFileNameWithoutExtension(const char *filePath, BOOL copy);
239 |
240 | #define THIS_FILE (ExtractFileNameWithoutExtension(__FILE__, NO))
241 |
242 | /**
243 | * The THIS_METHOD macro gives you the name of the current objective-c method.
244 | *
245 | * For example: DDLogWarn(@"%@ - Requires non-nil strings") -> @"setMake:model: requires non-nil strings"
246 | *
247 | * Note: This does NOT work in straight C functions (non objective-c).
248 | * Instead you should use the predefined __FUNCTION__ macro.
249 | **/
250 |
251 | #define THIS_METHOD NSStringFromSelector(_cmd)
252 |
253 |
254 | ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
255 | #pragma mark -
256 | ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
257 |
258 | @interface DDLog : NSObject
259 |
260 | #if GCD_MAYBE_AVAILABLE
261 |
262 | /**
263 | * Provides access to the underlying logging queue.
264 | * This may be helpful to Logger classes for things like thread synchronization.
265 | **/
266 |
267 | + (dispatch_queue_t)loggingQueue;
268 |
269 | #endif
270 |
271 | #if GCD_MAYBE_UNAVAILABLE
272 |
273 | /**
274 | * Provides access to the underlying logging thread.
275 | * This may be helpful to Logger classes for things like thread synchronization.
276 | **/
277 |
278 | + (NSThread *)loggingThread;
279 |
280 | #endif
281 |
282 | /**
283 | * Logging Primitive.
284 | *
285 | * This method is used by the macros above.
286 | * It is suggested you stick with the macros as they're easier to use.
287 | **/
288 |
289 | + (void)log:(BOOL)synchronous
290 | level:(int)level
291 | flag:(int)flag
292 | context:(int)context
293 | file:(const char *)file
294 | function:(const char *)function
295 | line:(int)line
296 | format:(NSString *)format, ...;
297 |
298 | /**
299 | * Since logging can be asynchronous, there may be times when you want to flush the logs.
300 | * The framework invokes this automatically when the application quits.
301 | **/
302 |
303 | + (void)flushLog;
304 |
305 | /**
306 | * Loggers
307 | *
308 | * If you want your log statements to go somewhere,
309 | * you should create and add a logger.
310 | **/
311 |
312 | + (void)addLogger:(id )logger;
313 | + (void)removeLogger:(id )logger;
314 |
315 | + (void)removeAllLoggers;
316 |
317 | /**
318 | * Registered Dynamic Logging
319 | *
320 | * These methods allow you to obtain a list of classes that are using registered dynamic logging,
321 | * and also provides methods to get and set their log level during run time.
322 | **/
323 |
324 | + (NSArray *)registeredClasses;
325 | + (NSArray *)registeredClassNames;
326 |
327 | + (int)logLevelForClass:(Class)aClass;
328 | + (int)logLevelForClassWithName:(NSString *)aClassName;
329 |
330 | + (void)setLogLevel:(int)logLevel forClass:(Class)aClass;
331 | + (void)setLogLevel:(int)logLevel forClassWithName:(NSString *)aClassName;
332 |
333 | @end
334 |
335 | ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
336 | #pragma mark -
337 | ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
338 |
339 | @protocol DDLogger
340 | @required
341 |
342 | - (void)logMessage:(DDLogMessage *)logMessage;
343 |
344 | /**
345 | * Formatters may optionally be added to any logger.
346 | * If no formatter is set, the logger simply logs the message as it is given in logMessage.
347 | * Or it may use its own built in formatting style.
348 | **/
349 | - (id )logFormatter;
350 | - (void)setLogFormatter:(id )formatter;
351 |
352 | @optional
353 |
354 | /**
355 | * Since logging is asynchronous, adding and removing loggers is also asynchronous.
356 | * In other words, the loggers are added and removed at appropriate times with regards to log messages.
357 | *
358 | * - Loggers will not receive log messages that were executed prior to when they were added.
359 | * - Loggers will not receive log messages that were executed after they were removed.
360 | *
361 | * These methods are executed in the logging thread/queue.
362 | * This is the same thread/queue that will execute every logMessage: invocation.
363 | * Loggers may use these methods for thread synchronization or other setup/teardown tasks.
364 | **/
365 |
366 | - (void)didAddLogger;
367 | - (void)willRemoveLogger;
368 |
369 | #if GCD_MAYBE_AVAILABLE
370 |
371 | /**
372 | * When Grand Central Dispatch is available
373 | * each logger is executed concurrently with respect to the other loggers.
374 | * Thus, a dedicated dispatch queue is used for each logger.
375 | * Logger implementations may optionally choose to provide their own dispatch queue.
376 | **/
377 | - (dispatch_queue_t)loggerQueue;
378 |
379 | /**
380 | * If the logger implementation does not choose to provide its own queue,
381 | * one will automatically be created for it.
382 | * The created queue will receive its name from this method.
383 | * This may be helpful for debugging or profiling reasons.
384 | **/
385 | - (NSString *)loggerName;
386 |
387 | #endif
388 |
389 | @end
390 |
391 | ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
392 | #pragma mark -
393 | ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
394 |
395 | @protocol DDLogFormatter
396 | @required
397 |
398 | /**
399 | * Formatters may optionally be added to any logger.
400 | * This allows for increased flexibility in the logging environment.
401 | * For example, log messages for log files may be formatted differently than log messages for the console.
402 | *
403 | * For more information about formatters, see the "Custom Formatters" page:
404 | * http://code.google.com/p/cocoalumberjack/wiki/CustomFormatters
405 | *
406 | * The formatter may also optionally filter the log message by returning nil,
407 | * in which case the logger will not log the message.
408 | **/
409 |
410 | - (NSString *)formatLogMessage:(DDLogMessage *)logMessage;
411 |
412 | @end
413 |
414 | ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
415 | #pragma mark -
416 | ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
417 |
418 | @protocol DDRegisteredDynamicLogging
419 |
420 | /**
421 | * Implement these methods to allow a file's log level to be managed from a central location.
422 | *
423 | * This is useful if you'd like to be able to change log levels for various parts
424 | * of your code from within the running application.
425 | *
426 | * Imagine pulling up the settings for your application,
427 | * and being able to configure the logging level on a per file basis.
428 | *
429 | * The implementation can be very straight-forward:
430 | *
431 | * + (int)ddLogLevel
432 | * {
433 | * return ddLogLevel;
434 | * }
435 | *
436 | * + (void)ddSetLogLevel:(int)logLevel
437 | * {
438 | * ddLogLevel = logLevel;
439 | * }
440 | **/
441 |
442 | + (int)ddLogLevel;
443 | + (void)ddSetLogLevel:(int)logLevel;
444 |
445 | @end
446 |
447 | ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
448 | #pragma mark -
449 | ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
450 |
451 | /**
452 | * The DDLogMessage class encapsulates information about the log message.
453 | * If you write custom loggers or formatters, you will be dealing with objects of this class.
454 | **/
455 |
456 | @interface DDLogMessage : NSObject
457 | {
458 |
459 | // The public variables below can be accessed directly (for speed).
460 | // For example: logMessage->logLevel
461 |
462 | @public
463 | int logLevel;
464 | int logFlag;
465 | int logContext;
466 | NSString *logMsg;
467 | NSDate *timestamp;
468 | const char *file;
469 | const char *function;
470 | int lineNumber;
471 | mach_port_t machThreadID;
472 |
473 | // The private variables below are only calculated if needed.
474 | // You should use the public methods to access this information.
475 |
476 | @private
477 | NSString *threadID;
478 | NSString *fileName;
479 | NSString *methodName;
480 | }
481 |
482 | // The initializer is somewhat reserved for internal use.
483 | // However, if you find need to manually create logMessage objects,
484 | // there is one thing you should be aware of.
485 | // The initializer expects the file and function parameters to be string literals.
486 | // That is, it expects the given strings to exist for the duration of the object's lifetime,
487 | // and it expects the given strings to be immutable.
488 | // In other words, it does not copy these strings, it simply points to them.
489 |
490 | - (id)initWithLogMsg:(NSString *)logMsg
491 | level:(int)logLevel
492 | flag:(int)logFlag
493 | context:(int)logContext
494 | file:(const char *)file
495 | function:(const char *)function
496 | line:(int)line;
497 |
498 | /**
499 | * Returns the threadID as it appears in NSLog.
500 | * That is, it is a hexadecimal value which is calculated from the machThreadID.
501 | **/
502 | - (NSString *)threadID;
503 |
504 | /**
505 | * Convenience method to get just the file name, as the file variable is generally the full file path.
506 | * This method does not include the file extension, which is generally unwanted for logging purposes.
507 | **/
508 | - (NSString *)fileName;
509 |
510 | /**
511 | * Returns the function variable in NSString form.
512 | **/
513 | - (NSString *)methodName;
514 |
515 | @end
516 |
517 | ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
518 | #pragma mark -
519 | ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
520 |
521 | /**
522 | * The DDLogger protocol specifies that an optional formatter can be added to a logger.
523 | * Most (but not all) loggers will want to support formatters.
524 | *
525 | * However, writting getters and setters in a thread safe manner,
526 | * while still maintaining maximum speed for the logging process, is a difficult task.
527 | *
528 | * To do it right, the implementation of the getter/setter has strict requiremenets:
529 | * - Must NOT require the logMessage method to acquire a lock.
530 | * - Must NOT require the logMessage method to access an atomic property (also a lock of sorts).
531 | *
532 | * To simplify things, an abstract logger is provided that implements the getter and setter.
533 | *
534 | * Logger implementations may simply extend this class,
535 | * and they can ACCESS THE FORMATTER VARIABLE DIRECTLY from within their logMessage method!
536 | **/
537 |
538 | @interface DDAbstractLogger : NSObject
539 | {
540 | id formatter;
541 |
542 | #if GCD_MAYBE_AVAILABLE
543 | dispatch_queue_t loggerQueue;
544 | #endif
545 | }
546 |
547 | - (id )logFormatter;
548 | - (void)setLogFormatter:(id )formatter;
549 |
550 | @end
551 |
--------------------------------------------------------------------------------
/WebServer/DDNumber.h:
--------------------------------------------------------------------------------
1 | #import
2 |
3 |
4 | @interface NSNumber (DDNumber)
5 |
6 | + (BOOL)parseString:(NSString *)str intoSInt64:(SInt64 *)pNum;
7 | + (BOOL)parseString:(NSString *)str intoUInt64:(UInt64 *)pNum;
8 |
9 | + (BOOL)parseString:(NSString *)str intoNSInteger:(NSInteger *)pNum;
10 | + (BOOL)parseString:(NSString *)str intoNSUInteger:(NSUInteger *)pNum;
11 |
12 | @end
13 |
--------------------------------------------------------------------------------
/WebServer/DDNumber.m:
--------------------------------------------------------------------------------
1 | #import "DDNumber.h"
2 |
3 |
4 | @implementation NSNumber (DDNumber)
5 |
6 | + (BOOL)parseString:(NSString *)str intoSInt64:(SInt64 *)pNum
7 | {
8 | if(str == nil)
9 | {
10 | *pNum = 0;
11 | return NO;
12 | }
13 |
14 | errno = 0;
15 |
16 | // On both 32-bit and 64-bit machines, long long = 64 bit
17 |
18 | *pNum = strtoll([str UTF8String], NULL, 10);
19 |
20 | if(errno != 0)
21 | return NO;
22 | else
23 | return YES;
24 | }
25 |
26 | + (BOOL)parseString:(NSString *)str intoUInt64:(UInt64 *)pNum
27 | {
28 | if(str == nil)
29 | {
30 | *pNum = 0;
31 | return NO;
32 | }
33 |
34 | errno = 0;
35 |
36 | // On both 32-bit and 64-bit machines, unsigned long long = 64 bit
37 |
38 | *pNum = strtoull([str UTF8String], NULL, 10);
39 |
40 | if(errno != 0)
41 | return NO;
42 | else
43 | return YES;
44 | }
45 |
46 | + (BOOL)parseString:(NSString *)str intoNSInteger:(NSInteger *)pNum
47 | {
48 | if(str == nil)
49 | {
50 | *pNum = 0;
51 | return NO;
52 | }
53 |
54 | errno = 0;
55 |
56 | // On LP64, NSInteger = long = 64 bit
57 | // Otherwise, NSInteger = int = long = 32 bit
58 |
59 | *pNum = strtol([str UTF8String], NULL, 10);
60 |
61 | if(errno != 0)
62 | return NO;
63 | else
64 | return YES;
65 | }
66 |
67 | + (BOOL)parseString:(NSString *)str intoNSUInteger:(NSUInteger *)pNum
68 | {
69 | if(str == nil)
70 | {
71 | *pNum = 0;
72 | return NO;
73 | }
74 |
75 | errno = 0;
76 |
77 | // On LP64, NSUInteger = unsigned long = 64 bit
78 | // Otherwise, NSUInteger = unsigned int = unsigned long = 32 bit
79 |
80 | *pNum = strtoul([str UTF8String], NULL, 10);
81 |
82 | if(errno != 0)
83 | return NO;
84 | else
85 | return YES;
86 | }
87 |
88 | @end
89 |
--------------------------------------------------------------------------------
/WebServer/DDRange.h:
--------------------------------------------------------------------------------
1 | /**
2 | * DDRange is the functional equivalent of a 64 bit NSRange.
3 | * The HTTP Server is designed to support very large files.
4 | * On 32 bit architectures (ppc, i386) NSRange uses unsigned 32 bit integers.
5 | * This only supports a range of up to 4 gigabytes.
6 | * By defining our own variant, we can support a range up to 16 exabytes.
7 | *
8 | * All effort is given such that DDRange functions EXACTLY the same as NSRange.
9 | **/
10 |
11 | #import
12 | #import
13 |
14 | @class NSString;
15 |
16 | typedef struct _DDRange {
17 | UInt64 location;
18 | UInt64 length;
19 | } DDRange;
20 |
21 | typedef DDRange *DDRangePointer;
22 |
23 | NS_INLINE DDRange DDMakeRange(UInt64 loc, UInt64 len) {
24 | DDRange r;
25 | r.location = loc;
26 | r.length = len;
27 | return r;
28 | }
29 |
30 | NS_INLINE UInt64 DDMaxRange(DDRange range) {
31 | return (range.location + range.length);
32 | }
33 |
34 | NS_INLINE BOOL DDLocationInRange(UInt64 loc, DDRange range) {
35 | return (loc - range.location < range.length);
36 | }
37 |
38 | NS_INLINE BOOL DDEqualRanges(DDRange range1, DDRange range2) {
39 | return ((range1.location == range2.location) && (range1.length == range2.length));
40 | }
41 |
42 | FOUNDATION_EXPORT DDRange DDUnionRange(DDRange range1, DDRange range2);
43 | FOUNDATION_EXPORT DDRange DDIntersectionRange(DDRange range1, DDRange range2);
44 | FOUNDATION_EXPORT NSString *DDStringFromRange(DDRange range);
45 | FOUNDATION_EXPORT DDRange DDRangeFromString(NSString *aString);
46 |
47 | NSInteger DDRangeCompare(DDRangePointer pDDRange1, DDRangePointer pDDRange2);
48 |
49 | @interface NSValue (NSValueDDRangeExtensions)
50 |
51 | + (NSValue *)valueWithDDRange:(DDRange)range;
52 | - (DDRange)ddrangeValue;
53 |
54 | - (NSInteger)ddrangeCompare:(NSValue *)ddrangeValue;
55 |
56 | @end
57 |
--------------------------------------------------------------------------------
/WebServer/DDRange.m:
--------------------------------------------------------------------------------
1 | #import "DDRange.h"
2 | #import "DDNumber.h"
3 |
4 | DDRange DDUnionRange(DDRange range1, DDRange range2)
5 | {
6 | DDRange result;
7 |
8 | result.location = MIN(range1.location, range2.location);
9 | result.length = MAX(DDMaxRange(range1), DDMaxRange(range2)) - result.location;
10 |
11 | return result;
12 | }
13 |
14 | DDRange DDIntersectionRange(DDRange range1, DDRange range2)
15 | {
16 | DDRange result;
17 |
18 | if((DDMaxRange(range1) < range2.location) || (DDMaxRange(range2) < range1.location))
19 | {
20 | return DDMakeRange(0, 0);
21 | }
22 |
23 | result.location = MAX(range1.location, range2.location);
24 | result.length = MIN(DDMaxRange(range1), DDMaxRange(range2)) - result.location;
25 |
26 | return result;
27 | }
28 |
29 | NSString *DDStringFromRange(DDRange range)
30 | {
31 | return [NSString stringWithFormat:@"{%qu, %qu}", range.location, range.length];
32 | }
33 |
34 | DDRange DDRangeFromString(NSString *aString)
35 | {
36 | DDRange result = DDMakeRange(0, 0);
37 |
38 | // NSRange will ignore '-' characters, but not '+' characters
39 | NSCharacterSet *cset = [NSCharacterSet characterSetWithCharactersInString:@"+0123456789"];
40 |
41 | NSScanner *scanner = [NSScanner scannerWithString:aString];
42 | [scanner setCharactersToBeSkipped:[cset invertedSet]];
43 |
44 | NSString *str1 = nil;
45 | NSString *str2 = nil;
46 |
47 | BOOL found1 = [scanner scanCharactersFromSet:cset intoString:&str1];
48 | BOOL found2 = [scanner scanCharactersFromSet:cset intoString:&str2];
49 |
50 | if(found1) [NSNumber parseString:str1 intoUInt64:&result.location];
51 | if(found2) [NSNumber parseString:str2 intoUInt64:&result.length];
52 |
53 | return result;
54 | }
55 |
56 | NSInteger DDRangeCompare(DDRangePointer pDDRange1, DDRangePointer pDDRange2)
57 | {
58 | // Comparison basis:
59 | // Which range would you encouter first if you started at zero, and began walking towards infinity.
60 | // If you encouter both ranges at the same time, which range would end first.
61 |
62 | if(pDDRange1->location < pDDRange2->location)
63 | {
64 | return NSOrderedAscending;
65 | }
66 | if(pDDRange1->location > pDDRange2->location)
67 | {
68 | return NSOrderedDescending;
69 | }
70 | if(pDDRange1->length < pDDRange2->length)
71 | {
72 | return NSOrderedAscending;
73 | }
74 | if(pDDRange1->length > pDDRange2->length)
75 | {
76 | return NSOrderedDescending;
77 | }
78 |
79 | return NSOrderedSame;
80 | }
81 |
82 | @implementation NSValue (NSValueDDRangeExtensions)
83 |
84 | + (NSValue *)valueWithDDRange:(DDRange)range
85 | {
86 | return [NSValue valueWithBytes:&range objCType:@encode(DDRange)];
87 | }
88 |
89 | - (DDRange)ddrangeValue
90 | {
91 | DDRange result;
92 | [self getValue:&result];
93 | return result;
94 | }
95 |
96 | - (NSInteger)ddrangeCompare:(NSValue *)other
97 | {
98 | DDRange r1 = [self ddrangeValue];
99 | DDRange r2 = [other ddrangeValue];
100 |
101 | return DDRangeCompare(&r1, &r2);
102 | }
103 |
104 | @end
105 |
--------------------------------------------------------------------------------
/WebServer/DDTTYLogger.h:
--------------------------------------------------------------------------------
1 | #import
2 |
3 | #import "DDLog.h"
4 |
5 | /**
6 | * Welcome to Cocoa Lumberjack!
7 | *
8 | * The Google Code page has a wealth of documentation if you have any questions.
9 | * http://code.google.com/p/cocoalumberjack/
10 | *
11 | * If you're new to the project you may wish to read the "Getting Started" page.
12 | * http://code.google.com/p/cocoalumberjack/wiki/GettingStarted
13 | *
14 | *
15 | * This class provides a logger for Terminal output or Xcode console output,
16 | * depending on where you are running your code.
17 | *
18 | * As described in the "Getting Started" page,
19 | * the traditional NSLog() function directs it's output to two places:
20 | *
21 | * - Apple System Log (so it shows up in Console.app)
22 | * - StdErr (if stderr is a TTY, so log statements show up in Xcode console)
23 | *
24 | * To duplicate NSLog() functionality you can simply add this logger and an asl logger.
25 | * However, if you instead choose to use file logging (for faster performance),
26 | * you may choose to use only a file logger and a tty logger.
27 | **/
28 |
29 | @interface DDTTYLogger : DDAbstractLogger
30 | {
31 | BOOL isaTTY;
32 |
33 | NSDateFormatter *dateFormatter;
34 |
35 | char *app; // Not null terminated
36 | char *pid; // Not null terminated
37 |
38 | size_t appLen;
39 | size_t pidLen;
40 | }
41 |
42 | + (DDTTYLogger *)sharedInstance;
43 |
44 | // Inherited from DDAbstractLogger
45 |
46 | // - (id )logFormatter;
47 | // - (void)setLogFormatter:(id )formatter;
48 |
49 | @end
50 |
--------------------------------------------------------------------------------
/WebServer/DDTTYLogger.m:
--------------------------------------------------------------------------------
1 | #import "DDTTYLogger.h"
2 |
3 | #import
4 | #import
5 |
6 |
7 | @implementation DDTTYLogger
8 |
9 | static DDTTYLogger *sharedInstance;
10 |
11 | /**
12 | * The runtime sends initialize to each class in a program exactly one time just before the class,
13 | * or any class that inherits from it, is sent its first message from within the program. (Thus the
14 | * method may never be invoked if the class is not used.) The runtime sends the initialize message to
15 | * classes in a thread-safe manner. Superclasses receive this message before their subclasses.
16 | *
17 | * This method may also be called directly (assumably by accident), hence the safety mechanism.
18 | **/
19 | + (void)initialize
20 | {
21 | static BOOL initialized = NO;
22 | if (!initialized)
23 | {
24 | initialized = YES;
25 |
26 | sharedInstance = [[DDTTYLogger alloc] init];
27 | }
28 | }
29 |
30 | + (DDTTYLogger *)sharedInstance
31 | {
32 | return sharedInstance;
33 | }
34 |
35 | - (id)init
36 | {
37 | if (sharedInstance != nil)
38 | {
39 | [self release];
40 | return nil;
41 | }
42 |
43 | if ((self = [super init]))
44 | {
45 | isaTTY = isatty(STDERR_FILENO);
46 |
47 | if (isaTTY)
48 | {
49 | dateFormatter = [[NSDateFormatter alloc] init];
50 | [dateFormatter setFormatterBehavior:NSDateFormatterBehavior10_4];
51 | [dateFormatter setDateFormat:@"yyyy-MM-dd HH:mm:ss:SSS"];
52 |
53 | // Initialze 'app' variable (char *)
54 |
55 | NSString *appNStr = [[NSProcessInfo processInfo] processName];
56 | const char *appCStr = [appNStr UTF8String];
57 |
58 | appLen = strlen(appCStr);
59 |
60 | app = (char *)malloc(appLen);
61 | strncpy(app, appCStr, appLen); // Not null terminated
62 |
63 | // Initialize 'pid' variable (char *)
64 |
65 | NSString *pidNStr = [NSString stringWithFormat:@"%i", (int)getpid()];
66 | const char *pidCStr = [pidNStr UTF8String];
67 |
68 | pidLen = strlen(pidCStr);
69 |
70 | pid = (char *)malloc(pidLen);
71 | strncpy(pid, pidCStr, pidLen); // Not null terminated
72 | }
73 | }
74 | return self;
75 | }
76 |
77 | - (void)logMessage:(DDLogMessage *)logMessage
78 | {
79 | if (!isaTTY) return;
80 |
81 | NSString *logMsg = logMessage->logMsg;
82 | BOOL isFormatted = NO;
83 |
84 | if (formatter)
85 | {
86 | logMsg = [formatter formatLogMessage:logMessage];
87 | isFormatted = logMsg != logMessage->logMsg;
88 | }
89 |
90 | if (logMsg)
91 | {
92 | const char *msg = [logMsg UTF8String];
93 | size_t msgLen = strlen(msg);
94 |
95 | if (isFormatted)
96 | {
97 | struct iovec v[2];
98 |
99 | v[0].iov_base = (char *)msg;
100 | v[0].iov_len = msgLen;
101 |
102 | v[1].iov_base = "\n";
103 | v[1].iov_len = (msg[msgLen] == '\n') ? 0 : 1;
104 |
105 | writev(STDERR_FILENO, v, 2);
106 | }
107 | else
108 | {
109 | // The following is a highly optimized verion of file output to std err.
110 |
111 | // ts = timestamp
112 |
113 | NSString *tsNStr = [dateFormatter stringFromDate:(logMessage->timestamp)];
114 |
115 | const char *tsCStr = [tsNStr UTF8String];
116 | size_t tsLen = strlen(tsCStr);
117 |
118 | // tid = thread id
119 | //
120 | // How many characters do we need for the thread id?
121 | // logMessage->machThreadID is of type mach_port_t, which is an unsigned int.
122 | //
123 | // 1 hex char = 4 bits
124 | // 8 hex chars for 32 bit, plus ending '\0' = 9
125 |
126 | char tidCStr[9];
127 | int tidLen = snprintf(tidCStr, 9, "%x", logMessage->machThreadID);
128 |
129 | // Here is our format: "%s %s[%i:%s] %s", timestamp, appName, processID, threadID, logMsg
130 |
131 | struct iovec v[10];
132 |
133 | v[0].iov_base = (char *)tsCStr;
134 | v[0].iov_len = tsLen;
135 |
136 | v[1].iov_base = " ";
137 | v[1].iov_len = 1;
138 |
139 | v[2].iov_base = app;
140 | v[2].iov_len = appLen;
141 |
142 | v[3].iov_base = "[";
143 | v[3].iov_len = 1;
144 |
145 | v[4].iov_base = pid;
146 | v[4].iov_len = pidLen;
147 |
148 | v[5].iov_base = ":";
149 | v[5].iov_len = 1;
150 |
151 | v[6].iov_base = tidCStr;
152 | v[6].iov_len = MIN((size_t)8, tidLen); // snprintf doesn't return what you might think
153 |
154 | v[7].iov_base = "] ";
155 | v[7].iov_len = 2;
156 |
157 | v[8].iov_base = (char *)msg;
158 | v[8].iov_len = msgLen;
159 |
160 | v[9].iov_base = "\n";
161 | v[9].iov_len = (msg[msgLen] == '\n') ? 0 : 1;
162 |
163 | writev(STDERR_FILENO, v, 10);
164 | }
165 | }
166 | }
167 |
168 | - (NSString *)loggerName
169 | {
170 | return @"cocoa.lumberjack.ttyLogger";
171 | }
172 |
173 | @end
174 |
--------------------------------------------------------------------------------
/WebServer/HTTPAsyncFileResponse.h:
--------------------------------------------------------------------------------
1 | #import
2 | #import "HTTPResponse.h"
3 |
4 | @class HTTPConnection;
5 |
6 | /**
7 | * This is an asynchronous version of HTTPFileResponse.
8 | * It reads data from the given file asynchronously via GCD.
9 | *
10 | * It may be overriden to allow custom post-processing of the data that has been read from the file.
11 | * An example of this is the HTTPDynamicFileResponse class.
12 | **/
13 |
14 | @interface HTTPAsyncFileResponse : NSObject
15 | {
16 | HTTPConnection *connection;
17 |
18 | NSString *filePath;
19 | UInt64 fileLength;
20 | UInt64 fileOffset; // File offset as pertains to data given to connection
21 | UInt64 readOffset; // File offset as pertains to data read from file (but maybe not returned to connection)
22 |
23 | BOOL aborted;
24 |
25 | NSData *data;
26 |
27 | int fileFD;
28 | void *readBuffer;
29 | NSUInteger readBufferSize; // Malloced size of readBuffer
30 | NSUInteger readBufferOffset; // Offset within readBuffer where the end of existing data is
31 | NSUInteger readRequestLength;
32 | dispatch_queue_t readQueue;
33 | dispatch_source_t readSource;
34 | BOOL readSourceSuspended;
35 | }
36 |
37 | - (id)initWithFilePath:(NSString *)filePath forConnection:(HTTPConnection *)connection;
38 | - (NSString *)filePath;
39 |
40 | @end
41 |
42 | /**
43 | * Explanation of Variables (excluding those that are obvious)
44 | *
45 | * fileOffset
46 | * This is the number of bytes that have been returned to the connection via the readDataOfLength method.
47 | * If 1KB of data has been read from the file, but none of that data has yet been returned to the connection,
48 | * then the fileOffset variable remains at zero.
49 | * This variable is used in the calculation of the isDone method.
50 | * Only after all data has been returned to the connection are we actually done.
51 | *
52 | * readOffset
53 | * Represents the offset of the file descriptor.
54 | * In other words, the file position indidcator for our read stream.
55 | * It might be easy to think of it as the total number of bytes that have been read from the file.
56 | * However, this isn't entirely accurate, as the setOffset: method may have caused us to
57 | * jump ahead in the file (lseek).
58 | *
59 | * readBuffer
60 | * Malloc'd buffer to hold data read from the file.
61 | *
62 | * readBufferSize
63 | * Total allocation size of malloc'd buffer.
64 | *
65 | * readBufferOffset
66 | * Represents the position in the readBuffer where we should store new bytes.
67 | *
68 | * readRequestLength
69 | * The total number of bytes that were requested from the connection.
70 | * It's OK if we return a lesser number of bytes to the connection.
71 | * It's NOT OK if we return a greater number of bytes to the connection.
72 | * Doing so would disrupt proper support for range requests.
73 | * If, however, the response is chunked then we don't need to worry about this.
74 | * Chunked responses inheritly don't support range requests.
75 | **/
--------------------------------------------------------------------------------
/WebServer/HTTPAsyncFileResponse.m:
--------------------------------------------------------------------------------
1 | #import "HTTPAsyncFileResponse.h"
2 | #import "HTTPConnection.h"
3 | #import "HTTPLogging.h"
4 |
5 | #import
6 | #import
7 |
8 | // Log levels : off, error, warn, info, verbose
9 | // Other flags: trace
10 | static const int httpLogLevel = HTTP_LOG_LEVEL_WARN; // | HTTP_LOG_FLAG_TRACE;
11 |
12 | #define NULL_FD -1
13 |
14 | /**
15 | * Architecure overview:
16 | *
17 | * HTTPConnection will invoke our readDataOfLength: method to fetch data.
18 | * We will return nil, and then proceed to read the data via our readSource on our readQueue.
19 | * Once the requested amount of data has been read, we then pause our readSource,
20 | * and inform the connection of the available data.
21 | *
22 | * While our read is in progress, we don't have to worry about the connection calling any other methods,
23 | * except the connectionDidClose method, which would be invoked if the remote end closed the socket connection.
24 | * To safely handle this, we do a synchronous dispatch on the readQueue,
25 | * and nilify the connection as well as cancel our readSource.
26 | *
27 | * In order to minimize resource consumption during a HEAD request,
28 | * we don't open the file until we have to (until the connection starts requesting data).
29 | **/
30 |
31 | @implementation HTTPAsyncFileResponse
32 |
33 | - (id)initWithFilePath:(NSString *)fpath forConnection:(HTTPConnection *)parent
34 | {
35 | if ((self = [super init]))
36 | {
37 | HTTPLogTrace();
38 |
39 | connection = parent; // Parents retain children, children do NOT retain parents
40 |
41 | filePath = [fpath copy];
42 | if (filePath == nil)
43 | {
44 | HTTPLogWarn(@"%@: Init failed - Nil filePath", THIS_FILE);
45 |
46 | [self release];
47 | return nil;
48 | }
49 |
50 | NSDictionary *fileAttributes = [[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:NULL];
51 | if (fileAttributes == nil)
52 | {
53 | HTTPLogWarn(@"%@: Init failed - Unable to get file attributes. filePath: %@", THIS_FILE, filePath);
54 |
55 | [self release];
56 | return nil;
57 | }
58 |
59 | fileLength = (UInt64)[[fileAttributes objectForKey:NSFileSize] unsignedLongLongValue];
60 | fileOffset = 0;
61 |
62 | aborted = NO;
63 |
64 | // We don't bother opening the file here.
65 | // If this is a HEAD request we only need to know the fileLength.
66 | fileFD = NULL_FD;
67 | }
68 | return self;
69 | }
70 |
71 | - (void)abort
72 | {
73 | HTTPLogTrace();
74 |
75 | [connection responseDidAbort:self];
76 | aborted = YES;
77 | }
78 |
79 | - (void)processReadBuffer
80 | {
81 | // This method is here to allow superclasses to perform post-processing of the data.
82 | // For an example, see the HTTPDynamicFileResponse class.
83 | //
84 | // At this point, the readBuffer has readBufferOffset bytes available.
85 | // This method is in charge of updating the readBufferOffset.
86 | // Failure to do so will cause the readBuffer to grow to fileLength. (Imagine a 1 GB file...)
87 |
88 | // Copy the data out of the temporary readBuffer.
89 | data = [[NSData alloc] initWithBytes:readBuffer length:readBufferOffset];
90 |
91 | // Reset the read buffer.
92 | readBufferOffset = 0;
93 |
94 | // Notify the connection that we have data available for it.
95 | [connection responseHasAvailableData:self];
96 | }
97 |
98 | - (void)pauseReadSource
99 | {
100 | if (!readSourceSuspended)
101 | {
102 | HTTPLogVerbose(@"%@[%p]: Suspending readSource", THIS_FILE, self);
103 |
104 | readSourceSuspended = YES;
105 | dispatch_suspend(readSource);
106 | }
107 | }
108 |
109 | - (void)resumeReadSource
110 | {
111 | if (readSourceSuspended)
112 | {
113 | HTTPLogVerbose(@"%@[%p]: Resuming readSource", THIS_FILE, self);
114 |
115 | readSourceSuspended = NO;
116 | dispatch_resume(readSource);
117 | }
118 | }
119 |
120 | - (void)cancelReadSource
121 | {
122 | HTTPLogVerbose(@"%@[%p]: Canceling readSource", THIS_FILE, self);
123 |
124 | dispatch_source_cancel(readSource);
125 |
126 | // Cancelling a dispatch source doesn't
127 | // invoke the cancel handler if the dispatch source is paused.
128 |
129 | if (readSourceSuspended)
130 | {
131 | readSourceSuspended = NO;
132 | dispatch_resume(readSource);
133 | }
134 | }
135 |
136 | - (BOOL)openFileAndSetupReadSource
137 | {
138 | HTTPLogTrace();
139 |
140 | fileFD = open([filePath UTF8String], (O_RDONLY | O_NONBLOCK));
141 | if (fileFD == NULL_FD)
142 | {
143 | HTTPLogError(@"%@: Unable to open file. filePath: %@", THIS_FILE, filePath);
144 |
145 | return NO;
146 | }
147 |
148 | HTTPLogVerbose(@"%@[%p]: Open fd[%i] -> %@", THIS_FILE, self, fileFD, filePath);
149 |
150 | readQueue = dispatch_queue_create("HTTPAsyncFileResponse", NULL);
151 | readSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, fileFD, 0, readQueue);
152 |
153 |
154 | dispatch_source_set_event_handler(readSource, ^{
155 |
156 | HTTPLogTrace2(@"%@: eventBlock - fd[%i]", THIS_FILE, fileFD);
157 |
158 | // Determine how much data we should read.
159 | //
160 | // It is OK if we ask to read more bytes than exist in the file.
161 | // It is NOT OK to over-allocate the buffer.
162 |
163 | unsigned long long _bytesAvailableOnFD = dispatch_source_get_data(readSource);
164 |
165 | UInt64 _bytesLeftInFile = fileLength - readOffset;
166 |
167 | NSUInteger bytesAvailableOnFD;
168 | NSUInteger bytesLeftInFile;
169 |
170 | bytesAvailableOnFD = (_bytesAvailableOnFD > NSUIntegerMax) ? NSUIntegerMax : (NSUInteger)_bytesAvailableOnFD;
171 | bytesLeftInFile = (_bytesLeftInFile > NSUIntegerMax) ? NSUIntegerMax : (NSUInteger)_bytesLeftInFile;
172 |
173 | NSUInteger bytesLeftInRequest = readRequestLength - readBufferOffset;
174 |
175 | NSUInteger bytesLeft = MIN(bytesLeftInRequest, bytesLeftInFile);
176 |
177 | NSUInteger bytesToRead = MIN(bytesAvailableOnFD, bytesLeft);
178 |
179 | // Make sure buffer is big enough for read request.
180 | // Do not over-allocate.
181 |
182 | if (readBuffer == NULL || bytesToRead > (readBufferSize - readBufferOffset))
183 | {
184 | readBufferSize = bytesToRead;
185 | readBuffer = reallocf(readBuffer, (size_t)bytesToRead);
186 |
187 | if (readBuffer == NULL)
188 | {
189 | HTTPLogError(@"%@[%p]: Unable to allocate buffer", THIS_FILE, self);
190 |
191 | [self pauseReadSource];
192 | [self abort];
193 |
194 | return;
195 | }
196 | }
197 |
198 | // Perform the read
199 |
200 | HTTPLogVerbose(@"%@[%p]: Attempting to read %lu bytes from file", THIS_FILE, self, bytesToRead);
201 |
202 | ssize_t result = read(fileFD, readBuffer + readBufferOffset, (size_t)bytesToRead);
203 |
204 | // Check the results
205 | if (result < 0)
206 | {
207 | HTTPLogError(@"%@: Error(%i) reading file(%@)", THIS_FILE, errno, filePath);
208 |
209 | [self pauseReadSource];
210 | [self abort];
211 | }
212 | else if (result == 0)
213 | {
214 | HTTPLogError(@"%@: Read EOF on file(%@)", THIS_FILE, filePath);
215 |
216 | [self pauseReadSource];
217 | [self abort];
218 | }
219 | else // (result > 0)
220 | {
221 | HTTPLogVerbose(@"%@[%p]: Read %d bytes from file", THIS_FILE, self, result);
222 |
223 | readOffset += result;
224 | readBufferOffset += result;
225 |
226 | [self pauseReadSource];
227 | [self processReadBuffer];
228 | }
229 |
230 | });
231 |
232 | int theFileFD = fileFD;
233 | dispatch_source_t theReadSource = readSource;
234 |
235 | dispatch_source_set_cancel_handler(readSource, ^{
236 |
237 | // Do not access self from within this block in any way, shape or form.
238 | //
239 | // Note: You access self if you reference an iVar.
240 |
241 | HTTPLogTrace2(@"%@: cancelBlock - Close fd[%i]", THIS_FILE, theFileFD);
242 |
243 | dispatch_release(theReadSource);
244 | close(theFileFD);
245 | });
246 |
247 | readSourceSuspended = YES;
248 |
249 | return YES;
250 | }
251 |
252 | - (BOOL)openFileIfNeeded
253 | {
254 | if (aborted)
255 | {
256 | // The file operation has been aborted.
257 | // This could be because we failed to open the file,
258 | // or the reading process failed.
259 | return NO;
260 | }
261 |
262 | if (fileFD != NULL_FD)
263 | {
264 | // File has already been opened.
265 | return YES;
266 | }
267 |
268 | return [self openFileAndSetupReadSource];
269 | }
270 |
271 | - (UInt64)contentLength
272 | {
273 | HTTPLogTrace2(@"%@[%p]: contentLength - %llu", THIS_FILE, self, fileLength);
274 |
275 | return fileLength;
276 | }
277 |
278 | - (UInt64)offset
279 | {
280 | HTTPLogTrace();
281 |
282 | return fileOffset;
283 | }
284 |
285 | - (void)setOffset:(UInt64)offset
286 | {
287 | HTTPLogTrace2(@"%@[%p]: setOffset:%llu", THIS_FILE, self, offset);
288 |
289 | if (![self openFileIfNeeded])
290 | {
291 | // File opening failed,
292 | // or response has been aborted due to another error.
293 | return;
294 | }
295 |
296 | fileOffset = offset;
297 | readOffset = offset;
298 |
299 | off_t result = lseek(fileFD, (off_t)offset, SEEK_SET);
300 | if (result == -1)
301 | {
302 | HTTPLogError(@"%@[%p]: lseek failed - errno(%i) filePath(%@)", THIS_FILE, self, errno, filePath);
303 |
304 | [self abort];
305 | }
306 | }
307 |
308 | - (NSData *)readDataOfLength:(NSUInteger)length
309 | {
310 | HTTPLogTrace2(@"%@[%p]: readDataOfLength:%lu", THIS_FILE, self, (unsigned long)length);
311 |
312 | if (data)
313 | {
314 | NSUInteger dataLength = [data length];
315 |
316 | HTTPLogVerbose(@"%@[%p]: Returning data of length %lu", THIS_FILE, self, dataLength);
317 |
318 | fileOffset += dataLength;
319 |
320 | NSData *result = data;
321 | data = nil;
322 |
323 | return [result autorelease];
324 | }
325 | else
326 | {
327 | if (![self openFileIfNeeded])
328 | {
329 | // File opening failed,
330 | // or response has been aborted due to another error.
331 | return nil;
332 | }
333 |
334 | dispatch_sync(readQueue, ^{
335 |
336 | NSAssert(readSourceSuspended, @"Invalid logic - perhaps HTTPConnection has changed.");
337 |
338 | readRequestLength = length;
339 | [self resumeReadSource];
340 | });
341 |
342 | return nil;
343 | }
344 | }
345 |
346 | - (BOOL)isDone
347 | {
348 | BOOL result = (fileOffset == fileLength);
349 |
350 | HTTPLogTrace2(@"%@[%p]: isDone - %@", THIS_FILE, self, (result ? @"YES" : @"NO"));
351 |
352 | return result;
353 | }
354 |
355 | - (NSString *)filePath
356 | {
357 | return filePath;
358 | }
359 |
360 | - (BOOL)isAsynchronous
361 | {
362 | HTTPLogTrace();
363 |
364 | return YES;
365 | }
366 |
367 | - (void)connectionDidClose
368 | {
369 | HTTPLogTrace();
370 |
371 | if (fileFD != NULL_FD)
372 | {
373 | dispatch_sync(readQueue, ^{
374 |
375 | // Prevent any further calls to the connection
376 | connection = nil;
377 |
378 | // Cancel the readSource.
379 | // We do this here because the readSource's eventBlock has retained self.
380 | // In other words, if we don't cancel the readSource, we will never get deallocated.
381 |
382 | [self cancelReadSource];
383 | });
384 | }
385 | }
386 |
387 | - (void)dealloc
388 | {
389 | HTTPLogTrace();
390 |
391 | if (readQueue)
392 | dispatch_release(readQueue);
393 |
394 | if (readBuffer)
395 | free(readBuffer);
396 |
397 | [filePath release];
398 | [data release];
399 |
400 | [super dealloc];
401 | }
402 |
403 | @end
404 |
--------------------------------------------------------------------------------
/WebServer/HTTPAuthenticationRequest.h:
--------------------------------------------------------------------------------
1 | #import
2 |
3 | #if TARGET_OS_IPHONE
4 | // Note: You may need to add the CFNetwork Framework to your project
5 | #import
6 | #endif
7 |
8 | @class HTTPMessage;
9 |
10 |
11 | @interface HTTPAuthenticationRequest : NSObject
12 | {
13 | BOOL isBasic;
14 | BOOL isDigest;
15 |
16 | NSString *base64Credentials;
17 |
18 | NSString *username;
19 | NSString *realm;
20 | NSString *nonce;
21 | NSString *uri;
22 | NSString *qop;
23 | NSString *nc;
24 | NSString *cnonce;
25 | NSString *response;
26 | }
27 | - (id)initWithRequest:(HTTPMessage *)request;
28 |
29 | - (BOOL)isBasic;
30 | - (BOOL)isDigest;
31 |
32 | // Basic
33 | - (NSString *)base64Credentials;
34 |
35 | // Digest
36 | - (NSString *)username;
37 | - (NSString *)realm;
38 | - (NSString *)nonce;
39 | - (NSString *)uri;
40 | - (NSString *)qop;
41 | - (NSString *)nc;
42 | - (NSString *)cnonce;
43 | - (NSString *)response;
44 |
45 | @end
46 |
--------------------------------------------------------------------------------
/WebServer/HTTPAuthenticationRequest.m:
--------------------------------------------------------------------------------
1 | #import "HTTPAuthenticationRequest.h"
2 | #import "HTTPMessage.h"
3 |
4 | @interface HTTPAuthenticationRequest (PrivateAPI)
5 | - (NSString *)quotedSubHeaderFieldValue:(NSString *)param fromHeaderFieldValue:(NSString *)header;
6 | - (NSString *)nonquotedSubHeaderFieldValue:(NSString *)param fromHeaderFieldValue:(NSString *)header;
7 | @end
8 |
9 |
10 | @implementation HTTPAuthenticationRequest
11 |
12 | - (id)initWithRequest:(HTTPMessage *)request
13 | {
14 | if ((self = [super init]))
15 | {
16 | NSString *authInfo = [request headerField:@"Authorization"];
17 |
18 | isBasic = NO;
19 | if ([authInfo length] >= 6)
20 | {
21 | isBasic = [[authInfo substringToIndex:6] caseInsensitiveCompare:@"Basic "] == NSOrderedSame;
22 | }
23 |
24 | isDigest = NO;
25 | if ([authInfo length] >= 7)
26 | {
27 | isDigest = [[authInfo substringToIndex:7] caseInsensitiveCompare:@"Digest "] == NSOrderedSame;
28 | }
29 |
30 | if (isBasic)
31 | {
32 | NSMutableString *temp = [[[authInfo substringFromIndex:6] mutableCopy] autorelease];
33 | CFStringTrimWhitespace((CFMutableStringRef)temp);
34 |
35 | base64Credentials = [temp copy];
36 | }
37 |
38 | if (isDigest)
39 | {
40 | username = [[self quotedSubHeaderFieldValue:@"username" fromHeaderFieldValue:authInfo] retain];
41 | realm = [[self quotedSubHeaderFieldValue:@"realm" fromHeaderFieldValue:authInfo] retain];
42 | nonce = [[self quotedSubHeaderFieldValue:@"nonce" fromHeaderFieldValue:authInfo] retain];
43 | uri = [[self quotedSubHeaderFieldValue:@"uri" fromHeaderFieldValue:authInfo] retain];
44 |
45 | // It appears from RFC 2617 that the qop is to be given unquoted
46 | // Tests show that Firefox performs this way, but Safari does not
47 | // Thus we'll attempt to retrieve the value as nonquoted, but we'll verify it doesn't start with a quote
48 | qop = [self nonquotedSubHeaderFieldValue:@"qop" fromHeaderFieldValue:authInfo];
49 | if(qop && ([qop characterAtIndex:0] == '"'))
50 | {
51 | qop = [self quotedSubHeaderFieldValue:@"qop" fromHeaderFieldValue:authInfo];
52 | }
53 | [qop retain];
54 |
55 | nc = [[self nonquotedSubHeaderFieldValue:@"nc" fromHeaderFieldValue:authInfo] retain];
56 | cnonce = [[self quotedSubHeaderFieldValue:@"cnonce" fromHeaderFieldValue:authInfo] retain];
57 | response = [[self quotedSubHeaderFieldValue:@"response" fromHeaderFieldValue:authInfo] retain];
58 | }
59 | }
60 | return self;
61 | }
62 |
63 | - (void)dealloc
64 | {
65 | [base64Credentials release];
66 | [username release];
67 | [realm release];
68 | [nonce release];
69 | [uri release];
70 | [qop release];
71 | [nc release];
72 | [cnonce release];
73 | [response release];
74 | [super dealloc];
75 | }
76 |
77 | ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
78 | #pragma mark Accessors:
79 | ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
80 |
81 | - (BOOL)isBasic {
82 | return isBasic;
83 | }
84 |
85 | - (BOOL)isDigest {
86 | return isDigest;
87 | }
88 |
89 | - (NSString *)base64Credentials {
90 | return base64Credentials;
91 | }
92 |
93 | - (NSString *)username {
94 | return username;
95 | }
96 |
97 | - (NSString *)realm {
98 | return realm;
99 | }
100 |
101 | - (NSString *)nonce {
102 | return nonce;
103 | }
104 |
105 | - (NSString *)uri {
106 | return uri;
107 | }
108 |
109 | - (NSString *)qop {
110 | return qop;
111 | }
112 |
113 | - (NSString *)nc {
114 | return nc;
115 | }
116 |
117 | - (NSString *)cnonce {
118 | return cnonce;
119 | }
120 |
121 | - (NSString *)response {
122 | return response;
123 | }
124 |
125 | ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
126 | #pragma mark Private API:
127 | ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
128 |
129 | /**
130 | * Retrieves a "Sub Header Field Value" from a given header field value.
131 | * The sub header field is expected to be quoted.
132 | *
133 | * In the following header field:
134 | * Authorization: Digest username="Mufasa", qop=auth, response="6629fae4939"
135 | * The sub header field titled 'username' is quoted, and this method would return the value @"Mufasa".
136 | **/
137 | - (NSString *)quotedSubHeaderFieldValue:(NSString *)param fromHeaderFieldValue:(NSString *)header
138 | {
139 | NSRange startRange = [header rangeOfString:[NSString stringWithFormat:@"%@=\"", param]];
140 | if(startRange.location == NSNotFound)
141 | {
142 | // The param was not found anywhere in the header
143 | return nil;
144 | }
145 |
146 | NSUInteger postStartRangeLocation = startRange.location + startRange.length;
147 | NSUInteger postStartRangeLength = [header length] - postStartRangeLocation;
148 | NSRange postStartRange = NSMakeRange(postStartRangeLocation, postStartRangeLength);
149 |
150 | NSRange endRange = [header rangeOfString:@"\"" options:0 range:postStartRange];
151 | if(endRange.location == NSNotFound)
152 | {
153 | // The ending double-quote was not found anywhere in the header
154 | return nil;
155 | }
156 |
157 | NSRange subHeaderRange = NSMakeRange(postStartRangeLocation, endRange.location - postStartRangeLocation);
158 | return [header substringWithRange:subHeaderRange];
159 | }
160 |
161 | /**
162 | * Retrieves a "Sub Header Field Value" from a given header field value.
163 | * The sub header field is expected to not be quoted.
164 | *
165 | * In the following header field:
166 | * Authorization: Digest username="Mufasa", qop=auth, response="6629fae4939"
167 | * The sub header field titled 'qop' is nonquoted, and this method would return the value @"auth".
168 | **/
169 | - (NSString *)nonquotedSubHeaderFieldValue:(NSString *)param fromHeaderFieldValue:(NSString *)header
170 | {
171 | NSRange startRange = [header rangeOfString:[NSString stringWithFormat:@"%@=", param]];
172 | if(startRange.location == NSNotFound)
173 | {
174 | // The param was not found anywhere in the header
175 | return nil;
176 | }
177 |
178 | NSUInteger postStartRangeLocation = startRange.location + startRange.length;
179 | NSUInteger postStartRangeLength = [header length] - postStartRangeLocation;
180 | NSRange postStartRange = NSMakeRange(postStartRangeLocation, postStartRangeLength);
181 |
182 | NSRange endRange = [header rangeOfString:@"," options:0 range:postStartRange];
183 | if(endRange.location == NSNotFound)
184 | {
185 | // The ending comma was not found anywhere in the header
186 | // However, if the nonquoted param is at the end of the string, there would be no comma
187 | // This is only possible if there are no spaces anywhere
188 | NSRange endRange2 = [header rangeOfString:@" " options:0 range:postStartRange];
189 | if(endRange2.location != NSNotFound)
190 | {
191 | return nil;
192 | }
193 | else
194 | {
195 | return [header substringWithRange:postStartRange];
196 | }
197 | }
198 | else
199 | {
200 | NSRange subHeaderRange = NSMakeRange(postStartRangeLocation, endRange.location - postStartRangeLocation);
201 | return [header substringWithRange:subHeaderRange];
202 | }
203 | }
204 |
205 | @end
206 |
--------------------------------------------------------------------------------
/WebServer/HTTPConnection.h:
--------------------------------------------------------------------------------
1 | #import
2 |
3 | @class GCDAsyncSocket;
4 | @class HTTPMessage;
5 | @class HTTPServer;
6 | @class WebSocket;
7 | @protocol HTTPResponse;
8 |
9 |
10 | #define HTTPConnectionDidDieNotification @"HTTPConnectionDidDie"
11 |
12 | ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
13 | #pragma mark -
14 | ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
15 |
16 | @interface HTTPConfig : NSObject
17 | {
18 | HTTPServer *server;
19 | NSString *documentRoot;
20 | dispatch_queue_t queue;
21 | }
22 |
23 | - (id)initWithServer:(HTTPServer *)server documentRoot:(NSString *)documentRoot;
24 | - (id)initWithServer:(HTTPServer *)server documentRoot:(NSString *)documentRoot queue:(dispatch_queue_t)q;
25 |
26 | @property (nonatomic, readonly) HTTPServer *server;
27 | @property (nonatomic, readonly) NSString *documentRoot;
28 | @property (nonatomic, readonly) dispatch_queue_t queue;
29 |
30 | @end
31 |
32 | ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
33 | #pragma mark -
34 | ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
35 |
36 | @interface HTTPConnection : NSObject
37 | {
38 | dispatch_queue_t connectionQueue;
39 | GCDAsyncSocket *asyncSocket;
40 | HTTPConfig *config;
41 |
42 | BOOL started;
43 |
44 | HTTPMessage *request;
45 | unsigned int numHeaderLines;
46 |
47 | BOOL sentResponseHeaders;
48 |
49 | NSString *nonce;
50 | long lastNC;
51 |
52 | NSObject *httpResponse;
53 |
54 | NSMutableArray *ranges;
55 | NSMutableArray *ranges_headers;
56 | NSString *ranges_boundry;
57 | int rangeIndex;
58 |
59 | UInt64 requestContentLength;
60 | UInt64 requestContentLengthReceived;
61 |
62 | NSMutableArray *responseDataSizes;
63 | }
64 |
65 | - (id)initWithAsyncSocket:(GCDAsyncSocket *)newSocket configuration:(HTTPConfig *)aConfig;
66 |
67 | - (void)start;
68 | - (void)stop;
69 |
70 | - (void)startConnection;
71 |
72 | - (BOOL)supportsMethod:(NSString *)method atPath:(NSString *)path;
73 | - (BOOL)expectsRequestBodyFromMethod:(NSString *)method atPath:(NSString *)path;
74 |
75 | - (BOOL)isSecureServer;
76 | - (NSArray *)sslIdentityAndCertificates;
77 |
78 | - (BOOL)isPasswordProtected:(NSString *)path;
79 | - (BOOL)useDigestAccessAuthentication;
80 | - (NSString *)realm;
81 | - (NSString *)passwordForUser:(NSString *)username;
82 |
83 | - (NSDictionary *)parseParams:(NSString *)query;
84 | - (NSDictionary *)parseGetParams;
85 |
86 | - (NSString *)requestURI;
87 |
88 | - (NSArray *)directoryIndexFileNames;
89 | - (NSString *)filePathForURI:(NSString *)path;
90 | - (NSObject *)httpResponseForMethod:(NSString *)method URI:(NSString *)path;
91 | - (WebSocket *)webSocketForURI:(NSString *)path;
92 |
93 | - (void)prepareForBodyWithSize:(UInt64)contentLength;
94 | - (void)processDataChunk:(NSData *)postDataChunk;
95 |
96 | - (void)handleVersionNotSupported:(NSString *)version;
97 | - (void)handleAuthenticationFailed;
98 | - (void)handleResourceNotFound;
99 | - (void)handleInvalidRequest:(NSData *)data;
100 | - (void)handleUnknownMethod:(NSString *)method;
101 |
102 | - (NSData *)preprocessResponse:(HTTPMessage *)response;
103 | - (NSData *)preprocessErrorResponse:(HTTPMessage *)response;
104 |
105 | - (BOOL)shouldDie;
106 | - (void)die;
107 |
108 | @end
109 |
110 | @interface HTTPConnection (AsynchronousHTTPResponse)
111 | - (void)responseHasAvailableData:(NSObject *)sender;
112 | - (void)responseDidAbort:(NSObject *)sender;
113 | @end
114 |
--------------------------------------------------------------------------------
/WebServer/HTTPDataResponse.h:
--------------------------------------------------------------------------------
1 | #import
2 | #import "HTTPResponse.h"
3 |
4 |
5 | @interface HTTPDataResponse : NSObject
6 | {
7 | NSUInteger offset;
8 | NSData *data;
9 | }
10 |
11 | - (id)initWithData:(NSData *)data;
12 |
13 | @end
14 |
--------------------------------------------------------------------------------
/WebServer/HTTPDataResponse.m:
--------------------------------------------------------------------------------
1 | #import "HTTPDataResponse.h"
2 | #import "HTTPLogging.h"
3 |
4 | // Log levels : off, error, warn, info, verbose
5 | // Other flags: trace
6 | static const int httpLogLevel = HTTP_LOG_LEVEL_OFF; // | HTTP_LOG_FLAG_TRACE;
7 |
8 |
9 | @implementation HTTPDataResponse
10 |
11 | - (id)initWithData:(NSData *)dataParam
12 | {
13 | if((self = [super init]))
14 | {
15 | HTTPLogTrace();
16 |
17 | offset = 0;
18 | data = [dataParam retain];
19 | }
20 | return self;
21 | }
22 |
23 | - (void)dealloc
24 | {
25 | HTTPLogTrace();
26 |
27 | [data release];
28 | [super dealloc];
29 | }
30 |
31 | - (UInt64)contentLength
32 | {
33 | UInt64 result = (UInt64)[data length];
34 |
35 | HTTPLogTrace2(@"%@[%p]: contentLength - %llu", THIS_FILE, self, result);
36 |
37 | return result;
38 | }
39 |
40 | - (UInt64)offset
41 | {
42 | HTTPLogTrace();
43 |
44 | return offset;
45 | }
46 |
47 | - (void)setOffset:(UInt64)offsetParam
48 | {
49 | HTTPLogTrace2(@"%@[%p]: setOffset:%llu", THIS_FILE, self, offset);
50 |
51 | offset = (NSUInteger)offsetParam;
52 | }
53 |
54 | - (NSData *)readDataOfLength:(NSUInteger)lengthParameter
55 | {
56 | HTTPLogTrace2(@"%@[%p]: readDataOfLength:%lu", THIS_FILE, self, (unsigned long)lengthParameter);
57 |
58 | NSUInteger remaining = [data length] - offset;
59 | NSUInteger length = lengthParameter < remaining ? lengthParameter : remaining;
60 |
61 | void *bytes = (void *)([data bytes] + offset);
62 |
63 | offset += length;
64 |
65 | return [NSData dataWithBytesNoCopy:bytes length:length freeWhenDone:NO];
66 | }
67 |
68 | - (BOOL)isDone
69 | {
70 | BOOL result = (offset == [data length]);
71 |
72 | HTTPLogTrace2(@"%@[%p]: isDone - %@", THIS_FILE, self, (result ? @"YES" : @"NO"));
73 |
74 | return result;
75 | }
76 |
77 | @end
78 |
--------------------------------------------------------------------------------
/WebServer/HTTPDynamicFileResponse.h:
--------------------------------------------------------------------------------
1 | #import
2 | #import "HTTPResponse.h"
3 | #import "HTTPAsyncFileResponse.h"
4 |
5 | /**
6 | * This class is designed to assist with dynamic content.
7 | * Imagine you have a file that you want to make dynamic:
8 | *
9 | *
10 | *
11 | *
ComputerName Control Panel
12 | * ...
13 | *
System Time: SysTime
14 | *
15 | *
16 | *
17 | * Now you could generate the entire file in Objective-C,
18 | * but this would be a horribly tedious process.
19 | * Beside, you want to design the file with professional tools to make it look pretty.
20 | *
21 | * So all you have to do is escape your dynamic content like this:
22 | *
23 | * ...
24 | *
%%ComputerName%% Control Panel
25 | * ...
26 | *
System Time: %%SysTime%%
27 | *
28 | * And then you create an instance of this class with:
29 | *
30 | * - separator = @"%%"
31 | * - replacementDictionary = { "ComputerName"="Black MacBook", "SysTime"="2010-04-30 03:18:24" }
32 | *
33 | * This class will then perform the replacements for you, on the fly, as it reads the file data.
34 | * This class is also asynchronous, so it will perform the file IO using its own GCD queue.
35 | **/
36 |
37 | @interface HTTPDynamicFileResponse : HTTPAsyncFileResponse
38 | {
39 | NSData *separator;
40 | NSDictionary *replacementDict;
41 | }
42 |
43 | - (id)initWithFilePath:(NSString *)filePath
44 | forConnection:(HTTPConnection *)connection
45 | separator:(NSString *)separatorStr
46 | replacementDictionary:(NSDictionary *)dictionary;
47 |
48 | @end
49 |
--------------------------------------------------------------------------------
/WebServer/HTTPDynamicFileResponse.m:
--------------------------------------------------------------------------------
1 | #import "HTTPDynamicFileResponse.h"
2 | #import "HTTPConnection.h"
3 | #import "HTTPLogging.h"
4 |
5 | // Log levels : off, error, warn, info, verbose
6 | // Other flags: trace
7 | static const int httpLogLevel = HTTP_LOG_LEVEL_WARN; // | HTTP_LOG_FLAG_TRACE;
8 |
9 | #define NULL_FD -1
10 |
11 |
12 | @implementation HTTPDynamicFileResponse
13 |
14 | - (id)initWithFilePath:(NSString *)fpath
15 | forConnection:(HTTPConnection *)parent
16 | separator:(NSString *)separatorStr
17 | replacementDictionary:(NSDictionary *)dict
18 | {
19 | if ((self = [super initWithFilePath:fpath forConnection:parent]))
20 | {
21 | HTTPLogTrace();
22 |
23 | separator = [[separatorStr dataUsingEncoding:NSUTF8StringEncoding] retain];
24 | replacementDict = [dict retain];
25 | }
26 | return self;
27 | }
28 |
29 | - (BOOL)isChunked
30 | {
31 | HTTPLogTrace();
32 |
33 | return YES;
34 | }
35 |
36 | - (UInt64)contentLength
37 | {
38 | // This method shouldn't be called since we're using a chunked response.
39 | // We override it just to be safe.
40 |
41 | HTTPLogTrace();
42 |
43 | return 0;
44 | }
45 |
46 | - (void)setOffset:(UInt64)offset
47 | {
48 | // This method shouldn't be called since we're using a chunked response.
49 | // We override it just to be safe.
50 |
51 | HTTPLogTrace();
52 | }
53 |
54 | - (BOOL)isDone
55 | {
56 | BOOL result = (readOffset == fileLength) && (readBufferOffset == 0);
57 |
58 | HTTPLogTrace2(@"%@[%p]: isDone - %@", THIS_FILE, self, (result ? @"YES" : @"NO"));
59 |
60 | return result;
61 | }
62 |
63 | - (void)processReadBuffer
64 | {
65 | HTTPLogTrace();
66 |
67 | // At this point, the readBuffer has readBufferOffset bytes available.
68 | // This method is in charge of updating the readBufferOffset.
69 |
70 | NSUInteger bufLen = readBufferOffset;
71 | NSUInteger sepLen = [separator length];
72 |
73 | // We're going to start looking for the separator at the beginning of the buffer,
74 | // and stop when we get to the point where the separator would no longer fit in the buffer.
75 |
76 | NSUInteger offset = 0;
77 | NSUInteger stopOffset = (bufLen > sepLen) ? bufLen - sepLen + 1 : 0;
78 |
79 | // In order to do the replacement, we need to find the starting and ending separator.
80 | // For example:
81 | //
82 | // %%USER_NAME%%
83 | //
84 | // Where "%%" is the separator.
85 |
86 | BOOL found1 = NO;
87 | BOOL found2 = NO;
88 |
89 | NSUInteger s1 = 0;
90 | NSUInteger s2 = 0;
91 |
92 | const void *sep = [separator bytes];
93 |
94 | while (offset < stopOffset)
95 | {
96 | const void *subBuffer = readBuffer + offset;
97 |
98 | if (memcmp(subBuffer, sep, sepLen) == 0)
99 | {
100 | if (!found1)
101 | {
102 | // Found the first separator
103 |
104 | found1 = YES;
105 | s1 = offset;
106 | offset += sepLen;
107 |
108 | HTTPLogVerbose(@"%@[%p]: Found s1 at %lu", THIS_FILE, self, (unsigned long)s1);
109 | }
110 | else
111 | {
112 | // Found the second separator
113 |
114 | found2 = YES;
115 | s2 = offset;
116 | offset += sepLen;
117 |
118 | HTTPLogVerbose(@"%@[%p]: Found s2 at %lu", THIS_FILE, self, (unsigned long)s2);
119 | }
120 |
121 | if (found1 && found2)
122 | {
123 | // We found our separators.
124 | // Now extract the string between the two separators.
125 |
126 | NSRange fullRange = NSMakeRange(s1, (s2 - s1 + sepLen));
127 | NSRange strRange = NSMakeRange(s1 + sepLen, (s2 - s1 - sepLen));
128 |
129 | // Wish we could use the simple subdataWithRange method.
130 | // But that method copies the bytes...
131 | // So for performance reasons, we need to use the methods that don't copy the bytes.
132 |
133 | void *strBuf = readBuffer + strRange.location;
134 | NSUInteger strLen = strRange.length;
135 |
136 | NSString *key = [[NSString alloc] initWithBytes:strBuf length:strLen encoding:NSUTF8StringEncoding];
137 | if (key)
138 | {
139 | // Is there a given replacement for this key?
140 |
141 | NSString *value = [replacementDict objectForKey:key];
142 | if (value)
143 | {
144 | // Found the replacement value.
145 | // Now perform the replacement in the buffer.
146 |
147 | HTTPLogVerbose(@"%@[%p]: key(%@) -> value(%@)", THIS_FILE, self, key, value);
148 |
149 | NSData *v = [value dataUsingEncoding:NSUTF8StringEncoding];
150 | NSUInteger vLength = [v length];
151 |
152 | if (fullRange.length == vLength)
153 | {
154 | // Replacement is exactly the same size as what it is replacing
155 |
156 | // memcpy(void *restrict dst, const void *restrict src, size_t n);
157 |
158 | memcpy(readBuffer + fullRange.location, [v bytes], vLength);
159 | }
160 | else // (fullRange.length != vLength)
161 | {
162 | NSInteger diff = (NSInteger)vLength - (NSInteger)fullRange.length;
163 |
164 | if (diff > 0)
165 | {
166 | // Replacement is bigger than what it is replacing.
167 | // Make sure there is room in the buffer for the replacement.
168 |
169 | if (diff > (readBufferSize - bufLen))
170 | {
171 | NSUInteger inc = MAX(diff, 256);
172 |
173 | readBufferSize += inc;
174 | readBuffer = reallocf(readBuffer, readBufferSize);
175 | }
176 | }
177 |
178 | // Move the data that comes after the replacement.
179 | //
180 | // If replacement is smaller than what it is replacing,
181 | // then we are shifting the data toward the beginning of the buffer.
182 | //
183 | // If replacement is bigger than what it is replacing,
184 | // then we are shifting the data toward the end of the buffer.
185 | //
186 | // memmove(void *dst, const void *src, size_t n);
187 | //
188 | // The memmove() function copies n bytes from src to dst.
189 | // The two areas may overlap; the copy is always done in a non-destructive manner.
190 |
191 | void *src = readBuffer + fullRange.location + fullRange.length;
192 | void *dst = readBuffer + fullRange.location + vLength;
193 |
194 | NSUInteger remaining = bufLen - (fullRange.location + fullRange.length);
195 |
196 | memmove(dst, src, remaining);
197 |
198 | // Now copy the replacement into its location.
199 | //
200 | // memcpy(void *restrict dst, const void *restrict src, size_t n)
201 | //
202 | // The memcpy() function copies n bytes from src to dst.
203 | // If the two areas overlap, behavior is undefined.
204 |
205 | memcpy(readBuffer + fullRange.location, [v bytes], vLength);
206 |
207 | // And don't forget to update our indices.
208 |
209 | bufLen += diff;
210 | offset += diff;
211 | stopOffset += diff;
212 | }
213 | }
214 |
215 | [key release];
216 | }
217 |
218 | found1 = found2 = NO;
219 | }
220 | }
221 | else
222 | {
223 | offset++;
224 | }
225 | }
226 |
227 | // We've gone through our buffer now, and performed all the replacements that we could.
228 | // It's now time to update the amount of available data we have.
229 |
230 | if (readOffset == fileLength)
231 | {
232 | // We've read in the entire file.
233 | // So there can be no more replacements.
234 |
235 | data = [[NSData alloc] initWithBytes:readBuffer length:bufLen];
236 | readBufferOffset = 0;
237 | }
238 | else
239 | {
240 | // There are a couple different situations that we need to take into account here.
241 | //
242 | // Imagine the following file:
243 | // My name is %%USER_NAME%%
244 | //
245 | // Situation 1:
246 | // The first chunk of data we read was "My name is %%".
247 | // So we found the first separator, but not the second.
248 | // In this case we can only return the data that precedes the first separator.
249 | //
250 | // Situation 2:
251 | // The first chunk of data we read was "My name is %".
252 | // So we didn't find any separators, but part of a separator may be included in our buffer.
253 |
254 | NSUInteger available;
255 | if (found1)
256 | {
257 | // Situation 1
258 | available = s1;
259 | }
260 | else
261 | {
262 | // Situation 2
263 | available = stopOffset;
264 | }
265 |
266 | // Copy available data
267 |
268 | data = [[NSData alloc] initWithBytes:readBuffer length:available];
269 |
270 | // Remove the copied data from the buffer.
271 | // We do this by shifting the remaining data toward the beginning of the buffer.
272 |
273 | NSUInteger remaining = bufLen - available;
274 |
275 | memmove(readBuffer, readBuffer + available, remaining);
276 | readBufferOffset = remaining;
277 | }
278 |
279 | [connection responseHasAvailableData:self];
280 | }
281 |
282 | - (void)dealloc
283 | {
284 | HTTPLogTrace();
285 |
286 | [separator release];
287 | [replacementDict release];
288 |
289 | [super dealloc];
290 | }
291 |
292 | @end
293 |
--------------------------------------------------------------------------------
/WebServer/HTTPFileResponse.h:
--------------------------------------------------------------------------------
1 | #import
2 | #import "HTTPResponse.h"
3 |
4 | @class HTTPConnection;
5 |
6 |
7 | @interface HTTPFileResponse : NSObject
8 | {
9 | HTTPConnection *connection;
10 |
11 | NSString *filePath;
12 | UInt64 fileLength;
13 | UInt64 fileOffset;
14 |
15 | BOOL aborted;
16 |
17 | int fileFD;
18 | void *buffer;
19 | NSUInteger bufferSize;
20 | }
21 |
22 | - (id)initWithFilePath:(NSString *)filePath forConnection:(HTTPConnection *)connection;
23 | - (NSString *)filePath;
24 |
25 | @end
26 |
--------------------------------------------------------------------------------
/WebServer/HTTPFileResponse.m:
--------------------------------------------------------------------------------
1 | #import "HTTPFileResponse.h"
2 | #import "HTTPConnection.h"
3 | #import "HTTPLogging.h"
4 |
5 | #import
6 | #import
7 |
8 | // Log levels : off, error, warn, info, verbose
9 | // Other flags: trace
10 | static const int httpLogLevel = HTTP_LOG_LEVEL_WARN; // | HTTP_LOG_FLAG_TRACE;
11 |
12 | #define NULL_FD -1
13 |
14 |
15 | @implementation HTTPFileResponse
16 |
17 | - (id)initWithFilePath:(NSString *)fpath forConnection:(HTTPConnection *)parent
18 | {
19 | if((self = [super init]))
20 | {
21 | HTTPLogTrace();
22 |
23 | connection = parent; // Parents retain children, children do NOT retain parents
24 |
25 | filePath = [fpath copy];
26 | if (filePath == nil)
27 | {
28 | HTTPLogWarn(@"%@: Init failed - Nil filePath", THIS_FILE);
29 |
30 | [self release];
31 | return nil;
32 | }
33 |
34 | NSDictionary *fileAttributes = [[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:nil];
35 | if (fileAttributes == nil)
36 | {
37 | HTTPLogWarn(@"%@: Init failed - Unable to get file attributes. filePath: %@", THIS_FILE, filePath);
38 |
39 | [self release];
40 | return nil;
41 | }
42 |
43 | fileLength = (UInt64)[[fileAttributes objectForKey:NSFileSize] unsignedLongLongValue];
44 | fileOffset = 0;
45 |
46 | aborted = NO;
47 |
48 | // We don't bother opening the file here.
49 | // If this is a HEAD request we only need to know the fileLength.
50 | fileFD = NULL_FD;
51 | }
52 | return self;
53 | }
54 |
55 | - (void)abort
56 | {
57 | HTTPLogTrace();
58 |
59 | [connection responseDidAbort:self];
60 | aborted = YES;
61 | }
62 |
63 | - (BOOL)openFile
64 | {
65 | HTTPLogTrace();
66 |
67 | fileFD = open([filePath UTF8String], O_RDONLY);
68 | if (fileFD == NULL_FD)
69 | {
70 | HTTPLogError(@"%@[%p]: Unable to open file. filePath: %@", THIS_FILE, self, filePath);
71 |
72 | [self abort];
73 | return NO;
74 | }
75 |
76 | HTTPLogVerbose(@"%@[%p]: Open fd[%i] -> %@", THIS_FILE, self, fileFD, filePath);
77 |
78 | return YES;
79 | }
80 |
81 | - (BOOL)openFileIfNeeded
82 | {
83 | if (aborted)
84 | {
85 | // The file operation has been aborted.
86 | // This could be because we failed to open the file,
87 | // or the reading process failed.
88 | return NO;
89 | }
90 |
91 | if (fileFD != NULL_FD)
92 | {
93 | // File has already been opened.
94 | return YES;
95 | }
96 |
97 | return [self openFile];
98 | }
99 |
100 | - (UInt64)contentLength
101 | {
102 | HTTPLogTrace();
103 |
104 | return fileLength;
105 | }
106 |
107 | - (UInt64)offset
108 | {
109 | HTTPLogTrace();
110 |
111 | return fileOffset;
112 | }
113 |
114 | - (void)setOffset:(UInt64)offset
115 | {
116 | HTTPLogTrace2(@"%@[%p]: setOffset:%llu", THIS_FILE, self, offset);
117 |
118 | if (![self openFileIfNeeded])
119 | {
120 | // File opening failed,
121 | // or response has been aborted due to another error.
122 | return;
123 | }
124 |
125 | fileOffset = offset;
126 |
127 | off_t result = lseek(fileFD, (off_t)offset, SEEK_SET);
128 | if (result == -1)
129 | {
130 | HTTPLogError(@"%@[%p]: lseek failed - errno(%i) filePath(%@)", THIS_FILE, self, errno, filePath);
131 |
132 | [self abort];
133 | }
134 | }
135 |
136 | - (NSData *)readDataOfLength:(NSUInteger)length
137 | {
138 | HTTPLogTrace2(@"%@[%p]: readDataOfLength:%lu", THIS_FILE, self, (unsigned long)length);
139 |
140 | if (![self openFileIfNeeded])
141 | {
142 | // File opening failed,
143 | // or response has been aborted due to another error.
144 | return nil;
145 | }
146 |
147 | // Determine how much data we should read.
148 | //
149 | // It is OK if we ask to read more bytes than exist in the file.
150 | // It is NOT OK to over-allocate the buffer.
151 |
152 | UInt64 bytesLeftInFile = fileLength - fileOffset;
153 |
154 | NSUInteger bytesToRead = (NSUInteger)MIN(length, bytesLeftInFile);
155 |
156 | // Make sure buffer is big enough for read request.
157 | // Do not over-allocate.
158 |
159 | if (buffer == NULL || bufferSize < bytesToRead)
160 | {
161 | bufferSize = bytesToRead;
162 | buffer = reallocf(buffer, (size_t)bufferSize);
163 |
164 | if (buffer == NULL)
165 | {
166 | HTTPLogError(@"%@[%p]: Unable to allocate buffer", THIS_FILE, self);
167 |
168 | [self abort];
169 | return nil;
170 | }
171 | }
172 |
173 | // Perform the read
174 |
175 | HTTPLogVerbose(@"%@[%p]: Attempting to read %lu bytes from file", THIS_FILE, self, bytesToRead);
176 |
177 | ssize_t result = read(fileFD, buffer, bytesToRead);
178 |
179 | // Check the results
180 |
181 | if (result < 0)
182 | {
183 | HTTPLogError(@"%@: Error(%i) reading file(%@)", THIS_FILE, errno, filePath);
184 |
185 | [self abort];
186 | return nil;
187 | }
188 | else if (result == 0)
189 | {
190 | HTTPLogError(@"%@: Read EOF on file(%@)", THIS_FILE, filePath);
191 |
192 | [self abort];
193 | return nil;
194 | }
195 | else // (result > 0)
196 | {
197 | HTTPLogVerbose(@"%@[%p]: Read %d bytes from file", THIS_FILE, self, result);
198 |
199 | fileOffset += result;
200 |
201 | return [NSData dataWithBytes:buffer length:result];
202 | }
203 | }
204 |
205 | - (BOOL)isDone
206 | {
207 | BOOL result = (fileOffset == fileLength);
208 |
209 | HTTPLogTrace2(@"%@[%p]: isDone - %@", THIS_FILE, self, (result ? @"YES" : @"NO"));
210 |
211 | return result;
212 | }
213 |
214 | - (NSString *)filePath
215 | {
216 | return filePath;
217 | }
218 |
219 | - (void)dealloc
220 | {
221 | HTTPLogTrace();
222 |
223 | if (fileFD != NULL_FD)
224 | {
225 | HTTPLogVerbose(@"%@[%p]: Close fd[%i]", THIS_FILE, self, fileFD);
226 |
227 | close(fileFD);
228 | }
229 |
230 | if (buffer)
231 | free(buffer);
232 |
233 | [filePath release];
234 | [super dealloc];
235 | }
236 |
237 | @end
238 |
--------------------------------------------------------------------------------
/WebServer/HTTPLogging.h:
--------------------------------------------------------------------------------
1 | /**
2 | * In order to provide fast and flexible logging, this project uses Cocoa Lumberjack.
3 | *
4 | * The Google Code page has a wealth of documentation if you have any questions.
5 | * http://code.google.com/p/cocoalumberjack/
6 | *
7 | * Here's what you need to know concerning how logging is setup for CocoaHTTPServer:
8 | *
9 | * There are 4 log levels:
10 | * - Error
11 | * - Warning
12 | * - Info
13 | * - Verbose
14 | *
15 | * In addition to this, there is a Trace flag that can be enabled.
16 | * When tracing is enabled, it spits out the methods that are being called.
17 | *
18 | * Please note that tracing is separate from the log levels.
19 | * For example, one could set the log level to warning, and enable tracing.
20 | *
21 | * All logging is asynchronous, except errors.
22 | * To use logging within your own custom files, follow the steps below.
23 | *
24 | * Step 1:
25 | * Import this header in your implementation file:
26 | *
27 | * #import "HTTPLogging.h"
28 | *
29 | * Step 2:
30 | * Define your logging level in your implementation file:
31 | *
32 | * // Log levels: off, error, warn, info, verbose
33 | * static const int httpLogLevel = HTTP_LOG_LEVEL_VERBOSE;
34 | *
35 | * If you wish to enable tracing, you could do something like this:
36 | *
37 | * // Debug levels: off, error, warn, info, verbose
38 | * static const int httpLogLevel = HTTP_LOG_LEVEL_INFO | HTTP_LOG_FLAG_TRACE;
39 | *
40 | * Step 3:
41 | * Replace your NSLog statements with HTTPLog statements according to the severity of the message.
42 | *
43 | * NSLog(@"Fatal error, no dohickey found!"); -> HTTPLogError(@"Fatal error, no dohickey found!");
44 | *
45 | * HTTPLog works exactly the same as NSLog.
46 | * This means you can pass it multiple variables just like NSLog.
47 | **/
48 |
49 | #import "DDLog.h"
50 |
51 | // Define logging context for every log message coming from the HTTP server.
52 | // The logging context can be extracted from the DDLogMessage from within the logging framework,
53 | // which gives loggers, formatters, and filters the ability to optionally process them differently.
54 |
55 | #define HTTP_LOG_CONTEXT 80
56 |
57 | // Configure log levels.
58 |
59 | #define HTTP_LOG_FLAG_ERROR (1 << 0) // 0...00001
60 | #define HTTP_LOG_FLAG_WARN (1 << 1) // 0...00010
61 | #define HTTP_LOG_FLAG_INFO (1 << 2) // 0...00100
62 | #define HTTP_LOG_FLAG_VERBOSE (1 << 3) // 0...01000
63 |
64 | #define HTTP_LOG_LEVEL_OFF 0 // 0...00000
65 | #define HTTP_LOG_LEVEL_ERROR (HTTP_LOG_LEVEL_OFF | HTTP_LOG_FLAG_ERROR) // 0...00001
66 | #define HTTP_LOG_LEVEL_WARN (HTTP_LOG_LEVEL_ERROR | HTTP_LOG_FLAG_WARN) // 0...00011
67 | #define HTTP_LOG_LEVEL_INFO (HTTP_LOG_LEVEL_WARN | HTTP_LOG_FLAG_INFO) // 0...00111
68 | #define HTTP_LOG_LEVEL_VERBOSE (HTTP_LOG_LEVEL_INFO | HTTP_LOG_FLAG_VERBOSE) // 0...01111
69 |
70 | // Setup fine grained logging.
71 | // The first 4 bits are being used by the standard log levels (0 - 3)
72 | //
73 | // We're going to add tracing, but NOT as a log level.
74 | // Tracing can be turned on and off independently of log level.
75 |
76 | #define HTTP_LOG_FLAG_TRACE (1 << 4) // 0...10000
77 |
78 | // Setup the usual boolean macros.
79 |
80 | #define HTTP_LOG_ERROR (httpLogLevel & HTTP_LOG_FLAG_ERROR)
81 | #define HTTP_LOG_WARN (httpLogLevel & HTTP_LOG_FLAG_WARN)
82 | #define HTTP_LOG_INFO (httpLogLevel & HTTP_LOG_FLAG_INFO)
83 | #define HTTP_LOG_VERBOSE (httpLogLevel & HTTP_LOG_FLAG_VERBOSE)
84 | #define HTTP_LOG_TRACE (httpLogLevel & HTTP_LOG_FLAG_TRACE)
85 |
86 | // Configure asynchronous logging.
87 | // We follow the default configuration,
88 | // but we reserve a special macro to easily disable asynchronous logging for debugging purposes.
89 |
90 | #define HTTP_LOG_ASYNC_ENABLED YES
91 |
92 | #define HTTP_LOG_ASYNC_ERROR ( NO && HTTP_LOG_ASYNC_ENABLED)
93 | #define HTTP_LOG_ASYNC_WARN (YES && HTTP_LOG_ASYNC_ENABLED)
94 | #define HTTP_LOG_ASYNC_INFO (YES && HTTP_LOG_ASYNC_ENABLED)
95 | #define HTTP_LOG_ASYNC_VERBOSE (YES && HTTP_LOG_ASYNC_ENABLED)
96 | #define HTTP_LOG_ASYNC_TRACE (YES && HTTP_LOG_ASYNC_ENABLED)
97 |
98 | // Define logging primitives.
99 |
100 | #define HTTPLogError(frmt, ...) LOG_OBJC_MAYBE(HTTP_LOG_ASYNC_ERROR, httpLogLevel, HTTP_LOG_FLAG_ERROR, \
101 | HTTP_LOG_CONTEXT, frmt, ##__VA_ARGS__)
102 |
103 | #define HTTPLogWarn(frmt, ...) LOG_OBJC_MAYBE(HTTP_LOG_ASYNC_WARN, httpLogLevel, HTTP_LOG_FLAG_WARN, \
104 | HTTP_LOG_CONTEXT, frmt, ##__VA_ARGS__)
105 |
106 | #define HTTPLogInfo(frmt, ...) LOG_OBJC_MAYBE(HTTP_LOG_ASYNC_INFO, httpLogLevel, HTTP_LOG_FLAG_INFO, \
107 | HTTP_LOG_CONTEXT, frmt, ##__VA_ARGS__)
108 |
109 | #define HTTPLogVerbose(frmt, ...) LOG_OBJC_MAYBE(HTTP_LOG_ASYNC_VERBOSE, httpLogLevel, HTTP_LOG_FLAG_VERBOSE, \
110 | HTTP_LOG_CONTEXT, frmt, ##__VA_ARGS__)
111 |
112 | #define HTTPLogTrace() LOG_OBJC_MAYBE(HTTP_LOG_ASYNC_TRACE, httpLogLevel, HTTP_LOG_FLAG_TRACE, \
113 | HTTP_LOG_CONTEXT, @"%@[%p]: %@", THIS_FILE, self, THIS_METHOD)
114 |
115 | #define HTTPLogTrace2(frmt, ...) LOG_OBJC_MAYBE(HTTP_LOG_ASYNC_TRACE, httpLogLevel, HTTP_LOG_FLAG_TRACE, \
116 | HTTP_LOG_CONTEXT, frmt, ##__VA_ARGS__)
117 |
118 |
119 | #define HTTPLogCError(frmt, ...) LOG_C_MAYBE(HTTP_LOG_ASYNC_ERROR, httpLogLevel, HTTP_LOG_FLAG_ERROR, \
120 | HTTP_LOG_CONTEXT, frmt, ##__VA_ARGS__)
121 |
122 | #define HTTPLogCWarn(frmt, ...) LOG_C_MAYBE(HTTP_LOG_ASYNC_WARN, httpLogLevel, HTTP_LOG_FLAG_WARN, \
123 | HTTP_LOG_CONTEXT, frmt, ##__VA_ARGS__)
124 |
125 | #define HTTPLogCInfo(frmt, ...) LOG_C_MAYBE(HTTP_LOG_ASYNC_INFO, httpLogLevel, HTTP_LOG_FLAG_INFO, \
126 | HTTP_LOG_CONTEXT, frmt, ##__VA_ARGS__)
127 |
128 | #define HTTPLogCVerbose(frmt, ...) LOG_C_MAYBE(HTTP_LOG_ASYNC_VERBOSE, httpLogLevel, HTTP_LOG_FLAG_VERBOSE, \
129 | HTTP_LOG_CONTEXT, frmt, ##__VA_ARGS__)
130 |
131 | #define HTTPLogCTrace() LOG_C_MAYBE(HTTP_LOG_ASYNC_TRACE, httpLogLevel, HTTP_LOG_FLAG_TRACE, \
132 | HTTP_LOG_CONTEXT, @"%@[%p]: %@", THIS_FILE, self, __FUNCTION__)
133 |
134 | #define HTTPLogCTrace2(frmt, ...) LOG_C_MAYBE(HTTP_LOG_ASYNC_TRACE, httpLogLevel, HTTP_LOG_FLAG_TRACE, \
135 | HTTP_LOG_CONTEXT, frmt, ##__VA_ARGS__)
136 |
137 |
--------------------------------------------------------------------------------
/WebServer/HTTPMessage.h:
--------------------------------------------------------------------------------
1 | /**
2 | * The HTTPMessage class is a simple Objective-C wrapper around Apple's CFHTTPMessage class.
3 | **/
4 |
5 | #import
6 |
7 | #if TARGET_OS_IPHONE
8 | // Note: You may need to add the CFNetwork Framework to your project
9 | #import
10 | #endif
11 |
12 | #define HTTPVersion1_0 ((NSString *)kCFHTTPVersion1_0)
13 | #define HTTPVersion1_1 ((NSString *)kCFHTTPVersion1_1)
14 |
15 |
16 | @interface HTTPMessage : NSObject
17 | {
18 | CFHTTPMessageRef message;
19 | }
20 |
21 | - (id)initEmptyRequest;
22 |
23 | - (id)initRequestWithMethod:(NSString *)method URL:(NSURL *)url version:(NSString *)version;
24 |
25 | - (id)initResponseWithStatusCode:(NSInteger)code description:(NSString *)description version:(NSString *)version;
26 |
27 | - (BOOL)appendData:(NSData *)data;
28 |
29 | - (BOOL)isHeaderComplete;
30 |
31 | - (NSString *)version;
32 |
33 | - (NSString *)method;
34 | - (NSURL *)url;
35 |
36 | - (NSInteger)statusCode;
37 |
38 | - (NSDictionary *)allHeaderFields;
39 | - (NSString *)headerField:(NSString *)headerField;
40 |
41 | - (void)setHeaderField:(NSString *)headerField value:(NSString *)headerFieldValue;
42 |
43 | - (NSData *)messageData;
44 |
45 | - (NSData *)body;
46 | - (void)setBody:(NSData *)body;
47 |
48 | @end
49 |
--------------------------------------------------------------------------------
/WebServer/HTTPMessage.m:
--------------------------------------------------------------------------------
1 | #import "HTTPMessage.h"
2 |
3 |
4 | @implementation HTTPMessage
5 |
6 | - (id)initEmptyRequest
7 | {
8 | if ((self = [super init]))
9 | {
10 | message = CFHTTPMessageCreateEmpty(NULL, YES);
11 | }
12 | return self;
13 | }
14 |
15 | - (id)initRequestWithMethod:(NSString *)method URL:(NSURL *)url version:(NSString *)version
16 | {
17 | if ((self = [super init]))
18 | {
19 | message = CFHTTPMessageCreateRequest(NULL, (CFStringRef)method, (CFURLRef)url, (CFStringRef)version);
20 | }
21 | return self;
22 | }
23 |
24 | - (id)initResponseWithStatusCode:(NSInteger)code description:(NSString *)description version:(NSString *)version
25 | {
26 | if ((self = [super init]))
27 | {
28 | message = CFHTTPMessageCreateResponse(NULL, (CFIndex)code, (CFStringRef)description, (CFStringRef)version);
29 | }
30 | return self;
31 | }
32 |
33 | - (void)dealloc
34 | {
35 | if (message)
36 | {
37 | CFRelease(message);
38 | }
39 | [super dealloc];
40 | }
41 |
42 | - (BOOL)appendData:(NSData *)data
43 | {
44 | return CFHTTPMessageAppendBytes(message, [data bytes], [data length]);
45 | }
46 |
47 | - (BOOL)isHeaderComplete
48 | {
49 | return CFHTTPMessageIsHeaderComplete(message);
50 | }
51 |
52 | - (NSString *)version
53 | {
54 | return [NSMakeCollectable(CFHTTPMessageCopyVersion(message)) autorelease];
55 | }
56 |
57 | - (NSString *)method
58 | {
59 | return [NSMakeCollectable(CFHTTPMessageCopyRequestMethod(message)) autorelease];
60 | }
61 |
62 | - (NSURL *)url
63 | {
64 | return [NSMakeCollectable(CFHTTPMessageCopyRequestURL(message)) autorelease];
65 | }
66 |
67 | - (NSInteger)statusCode
68 | {
69 | return (NSInteger)CFHTTPMessageGetResponseStatusCode(message);
70 | }
71 |
72 | - (NSDictionary *)allHeaderFields
73 | {
74 | return [NSMakeCollectable(CFHTTPMessageCopyAllHeaderFields(message)) autorelease];
75 | }
76 |
77 | - (NSString *)headerField:(NSString *)headerField
78 | {
79 | return [NSMakeCollectable(CFHTTPMessageCopyHeaderFieldValue(message, (CFStringRef)headerField)) autorelease];
80 | }
81 |
82 | - (void)setHeaderField:(NSString *)headerField value:(NSString *)headerFieldValue
83 | {
84 | CFHTTPMessageSetHeaderFieldValue(message, (CFStringRef)headerField, (CFStringRef)headerFieldValue);
85 | }
86 |
87 | - (NSData *)messageData
88 | {
89 | return [NSMakeCollectable(CFHTTPMessageCopySerializedMessage(message)) autorelease];
90 | }
91 |
92 | - (NSData *)body
93 | {
94 | return [NSMakeCollectable(CFHTTPMessageCopyBody(message)) autorelease];
95 | }
96 |
97 | - (void)setBody:(NSData *)body
98 | {
99 | CFHTTPMessageSetBody(message, (CFDataRef)body);
100 | }
101 |
102 | @end
103 |
--------------------------------------------------------------------------------
/WebServer/HTTPRedirectResponse.h:
--------------------------------------------------------------------------------
1 | #import
2 | #import "HTTPResponse.h"
3 |
4 |
5 | @interface HTTPRedirectResponse : NSObject
6 | {
7 | NSString *redirectPath;
8 | }
9 |
10 | - (id)initWithPath:(NSString *)redirectPath;
11 |
12 | @end
13 |
--------------------------------------------------------------------------------
/WebServer/HTTPRedirectResponse.m:
--------------------------------------------------------------------------------
1 | #import "HTTPRedirectResponse.h"
2 | #import "HTTPLogging.h"
3 |
4 | // Log levels : off, error, warn, info, verbose
5 | // Other flags: trace
6 | static const int httpLogLevel = HTTP_LOG_LEVEL_OFF; // | HTTP_LOG_FLAG_TRACE;
7 |
8 |
9 | @implementation HTTPRedirectResponse
10 |
11 | - (id)initWithPath:(NSString *)path
12 | {
13 | if ((self = [super init]))
14 | {
15 | HTTPLogTrace();
16 |
17 | redirectPath = [path copy];
18 | }
19 | return self;
20 | }
21 |
22 | - (UInt64)contentLength
23 | {
24 | return 0;
25 | }
26 |
27 | - (UInt64)offset
28 | {
29 | return 0;
30 | }
31 |
32 | - (void)setOffset:(UInt64)offset
33 | {
34 | // Nothing to do
35 | }
36 |
37 | - (NSData *)readDataOfLength:(NSUInteger)length
38 | {
39 | HTTPLogTrace();
40 |
41 | return nil;
42 | }
43 |
44 | - (BOOL)isDone
45 | {
46 | return YES;
47 | }
48 |
49 | - (NSDictionary *)httpHeaders
50 | {
51 | HTTPLogTrace();
52 |
53 | return [NSDictionary dictionaryWithObject:redirectPath forKey:@"Location"];
54 | }
55 |
56 | - (NSInteger)status
57 | {
58 | HTTPLogTrace();
59 |
60 | return 302;
61 | }
62 |
63 | - (void)dealloc
64 | {
65 | HTTPLogTrace();
66 |
67 | [redirectPath release];
68 | [super dealloc];
69 | }
70 |
71 | @end
72 |
--------------------------------------------------------------------------------
/WebServer/HTTPResponse.h:
--------------------------------------------------------------------------------
1 | #import
2 |
3 |
4 | @protocol HTTPResponse
5 |
6 | /**
7 | * Returns the length of the data in bytes.
8 | * If you don't know the length in advance, implement the isChunked method and have it return YES.
9 | **/
10 | - (UInt64)contentLength;
11 |
12 | /**
13 | * The HTTP server supports range requests in order to allow things like
14 | * file download resumption and optimized streaming on mobile devices.
15 | **/
16 | - (UInt64)offset;
17 | - (void)setOffset:(UInt64)offset;
18 |
19 | /**
20 | * Returns the data for the response.
21 | * You do not have to return data of the exact length that is given.
22 | * You may optionally return data of a lesser length.
23 | * However, you must never return data of a greater length than requested.
24 | * Doing so could disrupt proper support for range requests.
25 | *
26 | * To support asynchronous responses, read the discussion at the bottom of this header.
27 | **/
28 | - (NSData *)readDataOfLength:(NSUInteger)length;
29 |
30 | /**
31 | * Should only return YES after the HTTPConnection has read all available data.
32 | * That is, all data for the response has been returned to the HTTPConnection via the readDataOfLength method.
33 | **/
34 | - (BOOL)isDone;
35 |
36 | @optional
37 |
38 | /**
39 | * If you need time to calculate any part of the HTTP response headers (status code or header fields),
40 | * this method allows you to delay sending the headers so that you may asynchronously execute the calculations.
41 | * Simply implement this method and return YES until you have everything you need concerning the headers.
42 | *
43 | * This method ties into the asynchronous response architecture of the HTTPConnection.
44 | * You should read the full discussion at the bottom of this header.
45 | *
46 | * If you return YES from this method,
47 | * the HTTPConnection will wait for you to invoke the responseHasAvailableData method.
48 | * After you do, the HTTPConnection will again invoke this method to see if the response is ready to send the headers.
49 | *
50 | * You should only delay sending the headers until you have everything you need concerning just the headers.
51 | * Asynchronously generating the body of the response is not an excuse to delay sending the headers.
52 | * Instead you should tie into the asynchronous response architecture, and use techniques such as the isChunked method.
53 | *
54 | * Important: You should read the discussion at the bottom of this header.
55 | **/
56 | - (BOOL)delayResponeHeaders;
57 |
58 | /**
59 | * Status code for response.
60 | * Allows for responses such as redirect (301), etc.
61 | **/
62 | - (NSInteger)status;
63 |
64 | /**
65 | * If you want to add any extra HTTP headers to the response,
66 | * simply return them in a dictionary in this method.
67 | **/
68 | - (NSDictionary *)httpHeaders;
69 |
70 | /**
71 | * If you don't know the content-length in advance,
72 | * implement this method in your custom response class and return YES.
73 | *
74 | * Important: You should read the discussion at the bottom of this header.
75 | **/
76 | - (BOOL)isChunked;
77 |
78 | /**
79 | * This method is called from the HTTPConnection class when the connection is closed,
80 | * or when the connection is finished with the response.
81 | * If your response is asynchronous, you should implement this method so you know not to
82 | * invoke any methods on the HTTPConnection after this method is called (as the connection may be deallocated).
83 | **/
84 | - (void)connectionDidClose;
85 |
86 | @end
87 |
88 |
89 | /**
90 | * Important notice to those implementing custom asynchronous and/or chunked responses:
91 | *
92 | * HTTPConnection supports asynchronous responses. All you have to do in your custom response class is
93 | * asynchronously generate the response, and invoke HTTPConnection's responseHasAvailableData method.
94 | * You don't have to wait until you have all of the response ready to invoke this method. For example, if you
95 | * generate the response in incremental chunks, you could call responseHasAvailableData after generating
96 | * each chunk. Please see the HTTPAsyncFileResponse class for an example of how to do this.
97 | *
98 | * The normal flow of events for an HTTPConnection while responding to a request is like this:
99 | * - Send http resopnse headers
100 | * - Get data from response via readDataOfLength method.
101 | * - Add data to asyncSocket's write queue.
102 | * - Wait for asyncSocket to notify it that the data has been sent.
103 | * - Get more data from response via readDataOfLength method.
104 | * - ... continue this cycle until the entire response has been sent.
105 | *
106 | * With an asynchronous response, the flow is a little different.
107 | *
108 | * First the HTTPResponse is given the opportunity to postpone sending the HTTP response headers.
109 | * This allows the response to asynchronously execute any code needed to calculate a part of the header.
110 | * An example might be the response needs to generate some custom header fields,
111 | * or perhaps the response needs to look for a resource on network-attached storage.
112 | * Since the network-attached storage may be slow, the response doesn't know whether to send a 200 or 404 yet.
113 | * In situations such as this, the HTTPResponse simply implements the delayResponseHeaders method and returns YES.
114 | * After returning YES from this method, the HTTPConnection will wait until the response invokes its
115 | * responseHasAvailableData method. After this occurs, the HTTPConnection will again query the delayResponseHeaders
116 | * method to see if the response is ready to send the headers.
117 | * This cycle will continue until the delayResponseHeaders method returns NO.
118 | *
119 | * You should only delay sending the response headers until you have everything you need concerning just the headers.
120 | * Asynchronously generating the body of the response is not an excuse to delay sending the headers.
121 | *
122 | * After the response headers have been sent, the HTTPConnection calls your readDataOfLength method.
123 | * You may or may not have any available data at this point. If you don't, then simply return nil.
124 | * You should later invoke HTTPConnection's responseHasAvailableData when you have data to send.
125 | *
126 | * You don't have to keep track of when you return nil in the readDataOfLength method, or how many times you've invoked
127 | * responseHasAvailableData. Just simply call responseHasAvailableData whenever you've generated new data, and
128 | * return nil in your readDataOfLength whenever you don't have any available data in the requested range.
129 | * HTTPConnection will automatically detect when it should be requesting new data and will act appropriately.
130 | *
131 | * It's important that you also keep in mind that the HTTP server supports range requests.
132 | * The setOffset method is mandatory, and should not be ignored.
133 | * Make sure you take into account the offset within the readDataOfLength method.
134 | * You should also be aware that the HTTPConnection automatically sorts any range requests.
135 | * So if your setOffset method is called with a value of 100, then you can safely release bytes 0-99.
136 | *
137 | * HTTPConnection can also help you keep your memory footprint small.
138 | * Imagine you're dynamically generating a 10 MB response. You probably don't want to load all this data into
139 | * RAM, and sit around waiting for HTTPConnection to slowly send it out over the network. All you need to do
140 | * is pay attention to when HTTPConnection requests more data via readDataOfLength. This is because HTTPConnection
141 | * will never allow asyncSocket's write queue to get much bigger than READ_CHUNKSIZE bytes. You should
142 | * consider how you might be able to take advantage of this fact to generate your asynchronous response on demand,
143 | * while at the same time keeping your memory footprint small, and your application lightning fast.
144 | *
145 | * If you don't know the content-length in advanced, you should also implement the isChunked method.
146 | * This means the response will not include a Content-Length header, and will instead use "Transfer-Encoding: chunked".
147 | * There's a good chance that if your response is asynchronous and dynamic, it's also chunked.
148 | * If your response is chunked, you don't need to worry about range requests.
149 | **/
150 |
--------------------------------------------------------------------------------
/WebServer/HTTPServer.h:
--------------------------------------------------------------------------------
1 | #import
2 |
3 | @class GCDAsyncSocket;
4 | @class WebSocket;
5 |
6 | #if TARGET_OS_IPHONE
7 | #if __IPHONE_OS_VERSION_MIN_REQUIRED >= 40000 // iPhone 4.0
8 | #define IMPLEMENTED_PROTOCOLS
9 | #else
10 | #define IMPLEMENTED_PROTOCOLS
11 | #endif
12 | #else
13 | #if MAC_OS_X_VERSION_MIN_REQUIRED >= 1060 // Mac OS X 10.6
14 | #define IMPLEMENTED_PROTOCOLS
15 | #else
16 | #define IMPLEMENTED_PROTOCOLS
17 | #endif
18 | #endif
19 |
20 |
21 | @interface HTTPServer : NSObject IMPLEMENTED_PROTOCOLS
22 | {
23 | // Underlying asynchronous TCP/IP socket
24 | dispatch_queue_t serverQueue;
25 | dispatch_queue_t connectionQueue;
26 | GCDAsyncSocket *asyncSocket;
27 |
28 | // HTTP server configuration
29 | NSString *documentRoot;
30 | Class connectionClass;
31 | NSString *interface;
32 | UInt16 port;
33 |
34 | // NSNetService and related variables
35 | NSNetService *netService;
36 | NSString *domain;
37 | NSString *type;
38 | NSString *name;
39 | NSString *publishedName;
40 | NSDictionary *txtRecordDictionary;
41 |
42 | // Connection management
43 | NSMutableArray *connections;
44 | NSMutableArray *webSockets;
45 | NSLock *connectionsLock;
46 | NSLock *webSocketsLock;
47 |
48 | BOOL isRunning;
49 | }
50 |
51 | /**
52 | * Specifies the document root to serve files from.
53 | * For example, if you set this to "/Users//Sites",
54 | * then it will serve files out of the local Sites directory (including subdirectories).
55 | *
56 | * The default value is nil.
57 | * The default server configuration will not serve any files until this is set.
58 | *
59 | * If you change the documentRoot while the server is running,
60 | * the change will affect future incoming http connections.
61 | **/
62 | - (NSString *)documentRoot;
63 | - (void)setDocumentRoot:(NSString *)value;
64 |
65 | /**
66 | * The connection class is the class used to handle incoming HTTP connections.
67 | *
68 | * The default value is [HTTPConnection class].
69 | * You can override HTTPConnection, and then set this to [MyHTTPConnection class].
70 | *
71 | * If you change the connectionClass while the server is running,
72 | * the change will affect future incoming http connections.
73 | **/
74 | - (Class)connectionClass;
75 | - (void)setConnectionClass:(Class)value;
76 |
77 | /**
78 | * Set what interface you'd like the server to listen on.
79 | * By default this is nil, which causes the server to listen on all available interfaces like en1, wifi etc.
80 | *
81 | * The interface may be specified by name (e.g. "en1" or "lo0") or by IP address (e.g. "192.168.4.34").
82 | * You may also use the special strings "localhost" or "loopback" to specify that
83 | * the socket only accept connections from the local machine.
84 | **/
85 | - (NSString *)interface;
86 | - (void)setInterface:(NSString *)value;
87 |
88 | /**
89 | * The port number to run the HTTP server on.
90 | *
91 | * The default port number is zero, meaning the server will automatically use any available port.
92 | * This is the recommended port value, as it avoids possible port conflicts with other applications.
93 | * Technologies such as Bonjour can be used to allow other applications to automatically discover the port number.
94 | *
95 | * Note: As is common on most OS's, you need root privledges to bind to port numbers below 1024.
96 | *
97 | * You can change the port property while the server is running, but it won't affect the running server.
98 | * To actually change the port the server is listening for connections on you'll need to restart the server.
99 | *
100 | * The listeningPort method will always return the port number the running server is listening for connections on.
101 | * If the server is not running this method returns 0.
102 | **/
103 | - (UInt16)port;
104 | - (UInt16)listeningPort;
105 | - (void)setPort:(UInt16)value;
106 |
107 | /**
108 | * Bonjour domain for publishing the service.
109 | * The default value is "local.".
110 | *
111 | * Note: Bonjour publishing requires you set a type.
112 | *
113 | * If you change the domain property after the bonjour service has already been published (server already started),
114 | * you'll need to invoke the republishBonjour method to update the broadcasted bonjour service.
115 | **/
116 | - (NSString *)domain;
117 | - (void)setDomain:(NSString *)value;
118 |
119 | /**
120 | * Bonjour name for publishing the service.
121 | * The default value is "".
122 | *
123 | * If using an empty string ("") for the service name when registering,
124 | * the system will automatically use the "Computer Name".
125 | * Using an empty string will also handle name conflicts
126 | * by automatically appending a digit to the end of the name.
127 | *
128 | * Note: Bonjour publishing requires you set a type.
129 | *
130 | * If you change the name after the bonjour service has already been published (server already started),
131 | * you'll need to invoke the republishBonjour method to update the broadcasted bonjour service.
132 | *
133 | * The publishedName method will always return the actual name that was published via the bonjour service.
134 | * If the service is not running this method returns nil.
135 | **/
136 | - (NSString *)name;
137 | - (NSString *)publishedName;
138 | - (void)setName:(NSString *)value;
139 |
140 | /**
141 | * Bonjour type for publishing the service.
142 | * The default value is nil.
143 | * The service will not be published via bonjour unless the type is set.
144 | *
145 | * If you wish to publish the service as a traditional HTTP server, you should set the type to be "_http._tcp.".
146 | *
147 | * If you change the type after the bonjour service has already been published (server already started),
148 | * you'll need to invoke the republishBonjour method to update the broadcasted bonjour service.
149 | **/
150 | - (NSString *)type;
151 | - (void)setType:(NSString *)value;
152 |
153 | /**
154 | * Republishes the service via bonjour if the server is running.
155 | * If the service was not previously published, this method will publish it (if the server is running).
156 | **/
157 | - (void)republishBonjour;
158 |
159 | /**
160 | *
161 | **/
162 | - (NSDictionary *)TXTRecordDictionary;
163 | - (void)setTXTRecordDictionary:(NSDictionary *)dict;
164 |
165 | - (BOOL)start:(NSError **)errPtr;
166 | - (BOOL)stop;
167 | - (BOOL)isRunning;
168 |
169 | - (void)addWebSocket:(WebSocket *)ws;
170 |
171 | - (NSUInteger)numberOfHTTPConnections;
172 | - (NSUInteger)numberOfWebSocketConnections;
173 |
174 | @end
175 |
--------------------------------------------------------------------------------
/WebServer/MainViewController.h:
--------------------------------------------------------------------------------
1 | //
2 | // MainViewController.h
3 | // WebServer
4 | //
5 | // Created by willonboy on 12-1-9.
6 | // Copyright (c) 2012年 __MyCompanyName__. All rights reserved.
7 | //
8 |
9 | #define HTTP_SERVER_PORT (8080)
10 |
11 | #import
12 | #import "HTTPServer.h"
13 | #import "WTZHTTPConnection.h"
14 | #import "localhostAdresses.h"
15 |
16 | @interface MainViewController : UIViewController
17 | {
18 | UISwitch *switchBar;
19 | UIProgressView *progressView;
20 | HTTPServer *webServer;
21 | NSDictionary *ips;
22 | UILabel *en0IpLabel;
23 | UILabel *wwwIpLabel;
24 | }
25 |
26 | - (void)swithBarChangedValue;
27 | - (void)handleUploadProgressNotification:(NSNotification *) notification;
28 | - (void)changeProgressViewValue:(NSNumber *) value;
29 |
30 | #pragma mark - get ips
31 |
32 | - (void)bindIps:(NSNotification *) notification;
33 | - (void)displayIps;
34 | - (void)hideIps;
35 |
36 | #pragma mark - HTTPSERVER
37 |
38 | - (void)initHttpServer;
39 | - (void)startHttpServer;
40 | - (void)stopHttpServer;
41 |
42 | - (void)connectionClosed:(NSNotification *) notification;
43 | @end
44 |
45 |
--------------------------------------------------------------------------------
/WebServer/MainViewController.m:
--------------------------------------------------------------------------------
1 | //
2 | // MainViewController.m
3 | // WebServer
4 | //
5 | // Created by willonboy on 12-1-9.
6 | // Copyright (c) 2012年 __MyCompanyName__. All rights reserved.
7 | //
8 |
9 | #import "MainViewController.h"
10 |
11 | @implementation MainViewController
12 |
13 | - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
14 | {
15 | self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
16 | if (self) {
17 | // Custom initialization
18 | }
19 | return self;
20 | }
21 |
22 | - (void)dealloc {
23 | if (switchBar != nil)
24 | {
25 | [switchBar release];
26 | switchBar = nil;
27 | }
28 |
29 | if (progressView != nil)
30 | {
31 | [progressView release];
32 | progressView = nil;
33 | }
34 | if (webServer != nil) {
35 | [webServer release];
36 | webServer = nil;
37 | }
38 | if (ips != nil) {
39 | [ips release];
40 | ips = nil;
41 | }
42 | en0IpLabel = nil;
43 | wwwIpLabel = nil;
44 | [super dealloc];
45 | }
46 |
47 | #pragma mark - View lifecycle
48 |
49 | // Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
50 | - (void)viewDidLoad
51 | {
52 | [super viewDidLoad];
53 | UILabel *switchLabel = [[UILabel alloc] initWithFrame:CGRectMake(40, 50, 110, 35)];
54 | switchLabel.text = @"Start Or Stop:";
55 | [self.view addSubview:switchLabel];
56 | [switchLabel release];
57 |
58 | switchBar = [[UISwitch alloc] initWithFrame:CGRectMake(170, 50, 100, 35)];
59 | [switchBar addTarget:self action:@selector(swithBarChangedValue) forControlEvents:UIControlEventValueChanged];
60 | [self.view addSubview:switchBar];
61 |
62 | UILabel *progressLabel = [[UILabel alloc] initWithFrame:CGRectMake(40, 160, 110, 35)];
63 | progressLabel.text = @"Progress...";
64 | [self.view addSubview:progressLabel];
65 | [progressLabel release];
66 |
67 | progressView = [[UIProgressView alloc] initWithFrame:CGRectMake(40, 195, 240, 25)];
68 | progressView.progress = 0.0f;
69 | [self.view addSubview:progressView];
70 |
71 | }
72 |
73 |
74 | - (void)viewWillAppear:(BOOL)animated
75 | {
76 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(connectionClosed:) name:HTTPConnectionDidDieNotification object:nil];
77 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(bindIps:) name:NOTIFICATION_RESOLVED_LOCALHOST_IP_ADDRS object:nil];
78 | [self initHttpServer];
79 | [localhostAdresses list];
80 |
81 | [super viewWillAppear:animated];
82 | }
83 |
84 |
85 | - (void)viewWillDisappear:(BOOL)animated
86 | {
87 | [[NSNotificationCenter defaultCenter] removeObserver:self name:NOTIFICATION_RESOLVED_LOCALHOST_IP_ADDRS object:nil];
88 | [super viewWillDisappear:animated];
89 | }
90 |
91 | - (void)viewDidUnload
92 | {
93 | [super viewDidUnload];
94 |
95 | if (switchBar != nil)
96 | {
97 | [switchBar release];
98 | switchBar = nil;
99 | }
100 |
101 | if (progressView != nil)
102 | {
103 | [progressView release];
104 | progressView = nil;
105 | }
106 | }
107 |
108 |
109 |
110 | #pragma mark - 控件事件/函数
111 |
112 | - (void)swithBarChangedValue;
113 | {
114 | if (switchBar.on) {
115 | //
116 | NSLog(@"switch on");
117 | [self startHttpServer];
118 | }
119 | else
120 | {
121 | NSLog(@"switch off");
122 | [self stopHttpServer];
123 | }
124 | }
125 |
126 | //注意这里并不能直接改变progressView.progress的值 因为NSNotification也是运行在非主线程中的!
127 | - (void)handleUploadProgressNotification:(NSNotification *) notification
128 | {
129 | NSNumber *uploadProgress = (NSNumber *)[notification object];
130 | [self performSelectorOnMainThread:@selector(changeProgressViewValue:) withObject:uploadProgress waitUntilDone:NO];
131 | }
132 |
133 |
134 | - (void)changeProgressViewValue:(NSNumber *) value;
135 | {
136 | progressView.progress = [value floatValue];
137 | [progressView setNeedsDisplay];
138 | NSLog(@"current progress value is %f", [value floatValue]);
139 | }
140 |
141 |
142 |
143 | #pragma mark - get ips
144 |
145 | - (void)bindIps:(NSNotification *) notification
146 | {
147 | ips = [(NSDictionary *)[notification object] copy];
148 | NSLog(@"IPs addresses: %@", ips);
149 | }
150 |
151 | - (void)displayIps
152 | {
153 | NSString *en0Ip = nil;
154 | NSString *wwwIp = nil;
155 |
156 | if (ips != nil)
157 | {
158 | if ([[ips allKeys] containsObject:@"en0"]) {
159 | en0Ip = (NSString *)[ips objectForKey:@"en0"];
160 | en0IpLabel = [[UILabel alloc] initWithFrame:CGRectMake(40, 100, 260, 25)];
161 | en0IpLabel.text = [NSString stringWithFormat:@"局域网IP: %@:%d", en0Ip, HTTP_SERVER_PORT];
162 | [self.view addSubview:en0IpLabel];
163 | [en0IpLabel release];
164 | }
165 |
166 | if ([[ips allKeys] containsObject:@"www"]) {
167 | wwwIp = (NSString *)[ips objectForKey:@"www"];
168 | wwwIpLabel = [[UILabel alloc] initWithFrame:CGRectMake(40, 130, 260, 25)];
169 | wwwIpLabel.text = [NSString stringWithFormat:@"万维网IP: %@:%d", wwwIp, HTTP_SERVER_PORT];
170 | [self.view addSubview:wwwIpLabel];
171 | [wwwIpLabel release];
172 | }
173 | }
174 |
175 | NSLog(@"\n en0Ip:%@ \n wwwIp:%@", en0Ip, wwwIp);
176 | }
177 |
178 | - (void)hideIps
179 | {
180 | if (en0IpLabel != nil) {
181 | [en0IpLabel removeFromSuperview];
182 | en0IpLabel = nil;
183 | }
184 |
185 | if (wwwIpLabel != nil) {
186 | [wwwIpLabel removeFromSuperview];
187 | wwwIpLabel = nil;
188 | }
189 | }
190 |
191 |
192 | #pragma mark - HTTPSERVER
193 |
194 | - (void)initHttpServer
195 | {
196 | if (webServer == nil) {
197 | webServer = [[HTTPServer alloc] init];
198 | webServer.port = HTTP_SERVER_PORT;
199 | webServer.documentRoot = [[NSBundle mainBundle] pathForResource:@"web" ofType:nil];
200 | webServer.type = @"_http._tcp.";
201 | webServer.connectionClass = [WTZHTTPConnection class];
202 | }
203 | }
204 |
205 | - (void)startHttpServer
206 | {
207 | if (webServer != nil && ![webServer isRunning]) {
208 | progressView.progress = 0.0f;
209 | [self displayIps];
210 | [webServer start:nil];
211 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleUploadProgressNotification:) name:UPLOAD_FILE_PROGRESS object:nil];
212 | }
213 | }
214 |
215 | - (void)stopHttpServer
216 | {
217 | if (webServer != nil && [webServer isRunning]) {
218 | [self hideIps];
219 | [webServer stop];
220 | [[NSNotificationCenter defaultCenter] removeObserver:self name:UPLOAD_FILE_PROGRESS object:nil];
221 | }
222 | }
223 |
224 |
225 | - (void)connectionClosed:(NSNotification *) notification
226 | {
227 | if (progressView.progress < 1) {
228 | NSLog(@"上传中止了!");
229 | }
230 |
231 | NSLog(@"connectionClosed");
232 | }
233 |
234 | @end
235 |
--------------------------------------------------------------------------------
/WebServer/WTZAppDelegate.h:
--------------------------------------------------------------------------------
1 | //
2 | // WTZAppDelegate.h
3 | // WebServer
4 | //
5 | // Created by willonboy on 12-1-9.
6 | // Copyright (c) 2012年 __MyCompanyName__. All rights reserved.
7 | //
8 |
9 | #import
10 | #import "MainViewController.h"
11 |
12 |
13 | @interface WTZAppDelegate : UIResponder
14 |
15 | @property (retain, nonatomic) UIWindow *window;
16 |
17 | @end
18 |
--------------------------------------------------------------------------------
/WebServer/WTZAppDelegate.m:
--------------------------------------------------------------------------------
1 | //
2 | // WTZAppDelegate.m
3 | // WebServer
4 | //
5 | // Created by willonboy on 12-1-9.
6 | // Copyright (c) 2012年 __MyCompanyName__. All rights reserved.
7 | //
8 |
9 | #import "WTZAppDelegate.h"
10 |
11 | @implementation WTZAppDelegate
12 |
13 | @synthesize window = _window;
14 |
15 | - (void)dealloc
16 | {
17 | [_window release];
18 | [super dealloc];
19 | }
20 |
21 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
22 | {
23 | self.window = [[[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]] autorelease];
24 | // Override point for customization after application launch.
25 | self.window.backgroundColor = [UIColor whiteColor];
26 |
27 | MainViewController *mainVC = [[MainViewController alloc] init];
28 | mainVC.title = @"COCOAHTTPSERVER MULTIPART/FORM-DATA DEMO";
29 | self.window.rootViewController = mainVC;
30 | [mainVC release];
31 |
32 | [self.window makeKeyAndVisible];
33 | return YES;
34 | }
35 |
36 | - (void)applicationWillResignActive:(UIApplication *)application
37 | {
38 | /*
39 | Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
40 | Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
41 | */
42 | }
43 |
44 | - (void)applicationDidEnterBackground:(UIApplication *)application
45 | {
46 | /*
47 | Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
48 | If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
49 | */
50 | }
51 |
52 | - (void)applicationWillEnterForeground:(UIApplication *)application
53 | {
54 | /*
55 | Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
56 | */
57 | }
58 |
59 | - (void)applicationDidBecomeActive:(UIApplication *)application
60 | {
61 | /*
62 | Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
63 | */
64 | }
65 |
66 | - (void)applicationWillTerminate:(UIApplication *)application
67 | {
68 | /*
69 | Called when the application is about to terminate.
70 | Save data if appropriate.
71 | See also applicationDidEnterBackground:.
72 | */
73 | }
74 |
75 | @end
76 |
--------------------------------------------------------------------------------
/WebServer/WTZHTTPConnection.h:
--------------------------------------------------------------------------------
1 | //
2 | // WTZHTTPConnection.h
3 | // FileServer
4 | //
5 | // Created by willonboy on 11-12-7.
6 | // Copyright (c) 2011年 __MyCompanyName__. All rights reserved.
7 | //
8 |
9 | #define UPLOAD_FILE_PROGRESS @"uploadfileprogress"
10 |
11 | #import
12 | #import "HTTPConnection.h"
13 |
14 | @interface WTZHTTPConnection : HTTPConnection
15 | {
16 | int dataStartIndex;
17 | NSMutableArray *multipartData;
18 | BOOL postHeaderOK;
19 | }
20 |
21 |
22 | - (BOOL) isBeginOfOctetStream;
23 |
24 | @end
25 |
--------------------------------------------------------------------------------
/WebServer/WTZHTTPConnection.m:
--------------------------------------------------------------------------------
1 | //
2 | // WTZHTTPConnection.m
3 | // FileServer
4 | //
5 | // Created by willonboy on 11-12-7.(qq:962286684)
6 | // Copyright (c) 2011年 __MyCompanyName__. All rights reserved.
7 | //
8 |
9 | #import "WTZHTTPConnection.h"
10 |
11 |
12 |
13 | @implementation WTZHTTPConnection
14 |
15 | - (void)dealloc {
16 | [multipartData release];
17 | [super dealloc];
18 | }
19 |
20 |
21 |
22 |
23 | //扩展HTTPServer支持的请求类型,默认支持GET,HEAD,不支持POST
24 | - (BOOL)supportsMethod:(NSString *)method atPath:(NSString *)relativePath
25 | {
26 | if ([@"POST" isEqualToString:method])
27 | {
28 | return YES;
29 | }
30 | return [super supportsMethod:method atPath:relativePath];
31 | }
32 |
33 | //处量返回的response数据
34 | - (NSObject *)httpResponseForMethod:(NSString *)method URI:(NSString *)path
35 | {
36 | return [super httpResponseForMethod:method URI:path];
37 | }
38 |
39 |
40 | //处理POST请求提交的数据流(下面方法是改自 Andrew Davidson的类)
41 | - (void)processDataChunk:(NSData *)postDataChunk
42 | {
43 | NSLog(@"processDataChunk function called");
44 | //multipartData初始化不放在init函数中, 当前类似乎不经init函数初始化
45 | if (multipartData == nil) {
46 | multipartData = [[NSMutableArray alloc] init];
47 | }
48 |
49 | //处理multipart/form-data的POST请求中Body数据集中的表单值域并创建文件
50 | if (!postHeaderOK)
51 | {
52 | //0x0A0D: 换行符
53 | UInt16 separatorBytes = 0x0A0D;
54 | NSData* separatorData = [NSData dataWithBytes:&separatorBytes length:2];
55 |
56 | int l = [separatorData length];
57 | for (int i = 0; i < [postDataChunk length] - l; i++)
58 | {
59 | //每次取两个字节 比对下看看是否是换行
60 | NSRange searchRange = {i, l};
61 | //如果是换行符则进行如下处理
62 | if ([[postDataChunk subdataWithRange:searchRange] isEqualToData:separatorData])
63 | {
64 | //获取dataStartIndex标识的上一个换行位置到当前换行符之间的数据的Range
65 | NSRange newDataRange = {dataStartIndex, i - dataStartIndex};
66 | //dataStartIndex标识的上一个换行位置到移到当前换行符位置
67 | dataStartIndex = i + l;
68 | i += l - 1;
69 | //获取dataStartIndex标识的上一个换行位置到当前换行符之间的数据
70 | NSData *newData = [postDataChunk subdataWithRange:newDataRange];
71 | //如果newData不为空或还没有处理完multipart/form-data中表单变量值域则继续处理剩下的表单值域数据
72 | if ([newData length] || ![self isBeginOfOctetStream])
73 | {
74 | if ([newData length]) {
75 | [multipartData addObject:newData];
76 | }
77 | }
78 | else
79 | {
80 | //将标识处理完multipart/form-data中表单变量值域的postHeaderOK变量设置为TRUE;
81 | postHeaderOK = TRUE;
82 | //这里暂时写成硬编码 弊端:每次增加表单变量都要改这里的数值
83 | //获取Content-Disposition: form-data; name="xxx"; filename="xxx"
84 | NSString* postInfo = [[NSString alloc] initWithBytes:[[multipartData objectAtIndex:4] bytes]
85 | length:[[multipartData objectAtIndex:4] length]
86 | encoding:NSUTF8StringEncoding];
87 | NSLog(@"postInfo is:%@", postInfo);
88 | NSArray* postInfoComponents = [postInfo componentsSeparatedByString:@"; filename="];
89 | postInfoComponents = [[postInfoComponents lastObject] componentsSeparatedByString:@"\""];
90 | NSLog(@"postInfoComponents0 :%@",postInfoComponents);
91 | if ([postInfoComponents count]<2)
92 | {
93 | return;
94 | }
95 |
96 | postInfoComponents = [[postInfoComponents objectAtIndex:1] componentsSeparatedByString:@"\\"];
97 | NSString *documentPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
98 | NSString* filename = [documentPath stringByAppendingPathComponent:[postInfoComponents lastObject]];
99 | NSLog(@"filename :%@",filename);
100 | NSRange fileDataRange = {dataStartIndex, [postDataChunk length] - dataStartIndex};
101 | [[NSFileManager defaultManager] createFileAtPath:filename contents:[postDataChunk subdataWithRange:fileDataRange] attributes:nil];
102 | NSFileHandle *file = [[NSFileHandle fileHandleForUpdatingAtPath:filename] retain];
103 | if (file)
104 | {
105 | [file seekToEndOfFile];
106 | [multipartData addObject:file];
107 | }
108 |
109 | [postInfo release];
110 | break;
111 | }
112 | }
113 | }
114 | }
115 | else //表单值域已经处理过了 这之后的数据全是文件数据流
116 | {
117 | [(NSFileHandle*)[multipartData lastObject] writeData:postDataChunk];
118 | }
119 |
120 | float uploadProgress = (double)requestContentLengthReceived / requestContentLength;
121 | //实际应用时 当前类的实例是相当于单例一样被引用(因为只被实例化一次)
122 | if (uploadProgress >= 1.0) {
123 | postHeaderOK = NO;
124 | [multipartData release];
125 | multipartData = nil;
126 | }
127 | [[NSNotificationCenter defaultCenter] postNotificationName:UPLOAD_FILE_PROGRESS object:[NSNumber numberWithFloat:uploadProgress] userInfo:nil];
128 | }
129 |
130 |
131 | //检查是否已经处理完了multipart/form-data表单中的表单变量
132 | - (BOOL) isBeginOfOctetStream
133 | {
134 | NSString *octetStreamFlag = @"Content-Type: application/octet-stream";
135 | NSString *findData = [[NSString alloc] initWithData:(NSData *)[multipartData lastObject] encoding:NSUTF8StringEncoding];
136 |
137 | for (NSData *d in multipartData) {
138 | NSString *temp = [[[NSString alloc] initWithData:d encoding:NSUTF8StringEncoding] autorelease] ;
139 | NSLog(@"multipartData items: %@", temp);
140 | }
141 | //如果已经处理完了multipart/form-data表单中的表单变量
142 | if ( findData != nil && [findData length] > 0 )
143 | {
144 | NSLog(@"findData is :%@\n octetStreamFlag is :%@", findData, octetStreamFlag);
145 | if ([octetStreamFlag isEqualToString:findData]) {
146 | NSLog(@"multipart/form-data 变量值域数据处理完毕");
147 | [findData release];
148 | return YES;
149 | }
150 | [findData release];
151 | return NO;
152 | }
153 | return NO;
154 |
155 | }
156 |
157 |
158 | @end
159 |
--------------------------------------------------------------------------------
/WebServer/WebServer-Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleDisplayName
8 | ${PRODUCT_NAME}
9 | CFBundleExecutable
10 | ${EXECUTABLE_NAME}
11 | CFBundleIconFiles
12 |
13 | CFBundleIdentifier
14 | com.willonboy.${PRODUCT_NAME:rfc1034identifier}
15 | CFBundleInfoDictionaryVersion
16 | 6.0
17 | CFBundleName
18 | ${PRODUCT_NAME}
19 | CFBundlePackageType
20 | APPL
21 | CFBundleShortVersionString
22 | 1.0
23 | CFBundleSignature
24 | ????
25 | CFBundleVersion
26 | 1.0
27 | LSRequiresIPhoneOS
28 |
29 | UIRequiredDeviceCapabilities
30 |
31 | armv7
32 |
33 | UISupportedInterfaceOrientations
34 |
35 | UIInterfaceOrientationPortrait
36 | UIInterfaceOrientationLandscapeLeft
37 | UIInterfaceOrientationLandscapeRight
38 |
39 | UISupportedInterfaceOrientations~ipad
40 |
41 | UIInterfaceOrientationPortrait
42 | UIInterfaceOrientationPortraitUpsideDown
43 | UIInterfaceOrientationLandscapeLeft
44 | UIInterfaceOrientationLandscapeRight
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/WebServer/WebServer-Prefix.pch:
--------------------------------------------------------------------------------
1 | //
2 | // Prefix header for all source files of the 'WebServer' target in the 'WebServer' project
3 | //
4 |
5 | #import
6 |
7 | #ifndef __IPHONE_3_0
8 | #warning "This project uses features only available in iOS SDK 3.0 and later."
9 | #endif
10 |
11 | #ifdef __OBJC__
12 | #import
13 | #import
14 | #endif
15 |
--------------------------------------------------------------------------------
/WebServer/WebSocket.h:
--------------------------------------------------------------------------------
1 | #import
2 |
3 | @class HTTPMessage;
4 | @class GCDAsyncSocket;
5 |
6 |
7 | #define WebSocketDidDieNotification @"WebSocketDidDie"
8 |
9 | @interface WebSocket : NSObject
10 | {
11 | dispatch_queue_t websocketQueue;
12 |
13 | HTTPMessage *request;
14 | GCDAsyncSocket *asyncSocket;
15 |
16 | NSData *term;
17 |
18 | BOOL isStarted;
19 | BOOL isOpen;
20 | BOOL isVersion76;
21 | }
22 |
23 | + (BOOL)isWebSocketRequest:(HTTPMessage *)request;
24 |
25 | - (id)initWithRequest:(HTTPMessage *)request socket:(GCDAsyncSocket *)socket;
26 |
27 | /**
28 | * Delegate option.
29 | *
30 | * In most cases it will be easier to subclass WebSocket,
31 | * but some circumstances may lead one to prefer standard delegate callbacks instead.
32 | **/
33 | @property (/* atomic */ assign) id delegate;
34 |
35 | /**
36 | * The WebSocket class is thread-safe, generally via it's GCD queue.
37 | * All public API methods are thread-safe,
38 | * and the subclass API methods are thread-safe as they are all invoked on the same GCD queue.
39 | **/
40 | @property (nonatomic, readonly) dispatch_queue_t websocketQueue;
41 |
42 | /**
43 | * Public API
44 | *
45 | * These methods are automatically called by the HTTPServer.
46 | * You may invoke the stop method yourself to close the WebSocket manually.
47 | **/
48 | - (void)start;
49 | - (void)stop;
50 |
51 | /**
52 | * Public API
53 | *
54 | * Sends a message over the WebSocket.
55 | * This method is thread-safe.
56 | **/
57 | - (void)sendMessage:(NSString *)msg;
58 |
59 | /**
60 | * Subclass API
61 | *
62 | * These methods are designed to be overriden by subclasses.
63 | **/
64 | - (void)didOpen;
65 | - (void)didReceiveMessage:(NSString *)msg;
66 | - (void)didClose;
67 |
68 | @end
69 |
70 | ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
71 | #pragma mark -
72 | ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
73 |
74 | /**
75 | * There are two ways to create your own custom WebSocket:
76 | *
77 | * - Subclass it and override the methods you're interested in.
78 | * - Use traditional delegate paradigm along with your own custom class.
79 | *
80 | * They both exist to allow for maximum flexibility.
81 | * In most cases it will be easier to subclass WebSocket.
82 | * However some circumstances may lead one to prefer standard delegate callbacks instead.
83 | * One such example, you're already subclassing another class, so subclassing WebSocket isn't an option.
84 | **/
85 |
86 | @protocol WebSocketDelegate
87 | @optional
88 |
89 | - (void)webSocketDidOpen:(WebSocket *)ws;
90 |
91 | - (void)webSocket:(WebSocket *)ws didReceiveMessage:(NSString *)msg;
92 |
93 | - (void)webSocketDidClose:(WebSocket *)ws;
94 |
95 | @end
--------------------------------------------------------------------------------
/WebServer/WebSocket.m:
--------------------------------------------------------------------------------
1 | #import "WebSocket.h"
2 | #import "HTTPMessage.h"
3 | #import "GCDAsyncSocket.h"
4 | #import "DDNumber.h"
5 | #import "DDData.h"
6 | #import "HTTPLogging.h"
7 |
8 | // Log levels: off, error, warn, info, verbose
9 | // Other flags : trace
10 | static const int httpLogLevel = HTTP_LOG_LEVEL_WARN; // | HTTP_LOG_FLAG_TRACE;
11 |
12 | #define TIMEOUT_NONE -1
13 | #define TIMEOUT_REQUEST_BODY 10
14 |
15 | #define TAG_HTTP_REQUEST_BODY 100
16 | #define TAG_HTTP_RESPONSE_HEADERS 200
17 | #define TAG_HTTP_RESPONSE_BODY 201
18 |
19 | #define TAG_PREFIX 300
20 | #define TAG_MSG_PLUS_SUFFIX 301
21 |
22 |
23 | @interface WebSocket (PrivateAPI)
24 |
25 | - (void)readRequestBody;
26 | - (void)sendResponseBody;
27 | - (void)sendResponseHeaders;
28 |
29 | @end
30 |
31 | ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
32 | #pragma mark -
33 | ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
34 |
35 | @implementation WebSocket
36 |
37 | + (BOOL)isWebSocketRequest:(HTTPMessage *)request
38 | {
39 | // Request (Draft 75):
40 | //
41 | // GET /demo HTTP/1.1
42 | // Upgrade: WebSocket
43 | // Connection: Upgrade
44 | // Host: example.com
45 | // Origin: http://example.com
46 | // WebSocket-Protocol: sample
47 | //
48 | //
49 | // Request (Draft 76):
50 | //
51 | // GET /demo HTTP/1.1
52 | // Upgrade: WebSocket
53 | // Connection: Upgrade
54 | // Host: example.com
55 | // Origin: http://example.com
56 | // Sec-WebSocket-Protocol: sample
57 | // Sec-WebSocket-Key1: 4 @1 46546xW%0l 1 5
58 | // Sec-WebSocket-Key2: 12998 5 Y3 1 .P00
59 | //
60 | // ^n:ds[4U
61 |
62 | // Look for Upgrade: and Connection: headers.
63 | // If we find them, and they have the proper value,
64 | // we can safely assume this is a websocket request.
65 |
66 | NSString *upgradeHeaderValue = [request headerField:@"Upgrade"];
67 | NSString *connectionHeaderValue = [request headerField:@"Connection"];
68 |
69 | BOOL isWebSocket = YES;
70 |
71 | if (!upgradeHeaderValue || !connectionHeaderValue) {
72 | isWebSocket = NO;
73 | }
74 | else if (![upgradeHeaderValue caseInsensitiveCompare:@"WebSocket"] == NSOrderedSame) {
75 | isWebSocket = NO;
76 | }
77 | else if (![connectionHeaderValue caseInsensitiveCompare:@"Upgrade"] == NSOrderedSame) {
78 | isWebSocket = NO;
79 | }
80 |
81 | HTTPLogTrace2(@"%@: %@ - %@", THIS_FILE, THIS_METHOD, (isWebSocket ? @"YES" : @"NO"));
82 |
83 | return isWebSocket;
84 | }
85 |
86 | + (BOOL)isVersion76Request:(HTTPMessage *)request
87 | {
88 | NSString *key1 = [request headerField:@"Sec-WebSocket-Key1"];
89 | NSString *key2 = [request headerField:@"Sec-WebSocket-Key2"];
90 |
91 | BOOL isVersion76;
92 |
93 | if (!key1 || !key2) {
94 | isVersion76 = NO;
95 | }
96 | else {
97 | isVersion76 = YES;
98 | }
99 |
100 | HTTPLogTrace2(@"%@: %@ - %@", THIS_FILE, THIS_METHOD, (isVersion76 ? @"YES" : @"NO"));
101 |
102 | return isVersion76;
103 | }
104 |
105 | ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
106 | #pragma mark Setup and Teardown
107 | ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
108 |
109 | @synthesize delegate;
110 | @synthesize websocketQueue;
111 |
112 | - (id)initWithRequest:(HTTPMessage *)aRequest socket:(GCDAsyncSocket *)socket
113 | {
114 | HTTPLogTrace();
115 |
116 | if (aRequest == nil)
117 | {
118 | [self release];
119 | return nil;
120 | }
121 |
122 | if ((self = [super init]))
123 | {
124 | if (HTTP_LOG_VERBOSE)
125 | {
126 | NSData *requestHeaders = [aRequest messageData];
127 |
128 | NSString *temp = [[NSString alloc] initWithData:requestHeaders encoding:NSUTF8StringEncoding];
129 | HTTPLogVerbose(@"%@[%p] Request Headers:\n%@", THIS_FILE, self, temp);
130 | [temp release];
131 | }
132 |
133 | websocketQueue = dispatch_queue_create("WebSocket", NULL);
134 | request = [aRequest retain];
135 |
136 | asyncSocket = [socket retain];
137 | [asyncSocket setDelegate:self delegateQueue:websocketQueue];
138 |
139 | isOpen = NO;
140 | isVersion76 = [[self class] isVersion76Request:request];
141 |
142 | term = [[NSData alloc] initWithBytes:"\xFF" length:1];
143 | }
144 | return self;
145 | }
146 |
147 | - (void)dealloc
148 | {
149 | HTTPLogTrace();
150 |
151 | dispatch_release(websocketQueue);
152 |
153 | [request release];
154 |
155 | [asyncSocket setDelegate:nil delegateQueue:NULL];
156 | [asyncSocket disconnect];
157 | [asyncSocket release];
158 |
159 | [super dealloc];
160 | }
161 |
162 | - (id)delegate
163 | {
164 | __block id result = nil;
165 |
166 | dispatch_sync(websocketQueue, ^{
167 | result = delegate;
168 | });
169 |
170 | return result;
171 | }
172 |
173 | - (void)setDelegate:(id)newDelegate
174 | {
175 | dispatch_async(websocketQueue, ^{
176 | delegate = newDelegate;
177 | });
178 | }
179 |
180 | ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
181 | #pragma mark Start and Stop
182 | ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
183 |
184 | /**
185 | * Starting point for the WebSocket after it has been fully initialized (including subclasses).
186 | * This method is called by the HTTPConnection it is spawned from.
187 | **/
188 | - (void)start
189 | {
190 | // This method is not exactly designed to be overriden.
191 | // Subclasses are encouraged to override the didOpen method instead.
192 |
193 | dispatch_async(websocketQueue, ^{
194 | NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
195 |
196 | if (isStarted) return;
197 | isStarted = YES;
198 |
199 | if (isVersion76)
200 | {
201 | [self readRequestBody];
202 | }
203 | else
204 | {
205 | [self sendResponseHeaders];
206 | [self didOpen];
207 | }
208 |
209 | [pool release];
210 | });
211 | }
212 |
213 | /**
214 | * This method is called by the HTTPServer if it is asked to stop.
215 | * The server, in turn, invokes stop on each WebSocket instance.
216 | **/
217 | - (void)stop
218 | {
219 | // This method is not exactly designed to be overriden.
220 | // Subclasses are encouraged to override the didClose method instead.
221 |
222 | dispatch_async(websocketQueue, ^{
223 | NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
224 |
225 | [asyncSocket disconnect];
226 |
227 | [pool release];
228 | });
229 | }
230 |
231 | ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
232 | #pragma mark HTTP Response
233 | ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
234 |
235 | - (void)readRequestBody
236 | {
237 | HTTPLogTrace();
238 |
239 | NSAssert(isVersion76, @"WebSocket version 75 doesn't contain a request body");
240 |
241 | [asyncSocket readDataToLength:8 withTimeout:TIMEOUT_NONE tag:TAG_HTTP_REQUEST_BODY];
242 | }
243 |
244 | - (NSString *)originResponseHeaderValue
245 | {
246 | HTTPLogTrace();
247 |
248 | NSString *origin = [request headerField:@"Origin"];
249 |
250 | if (origin == nil)
251 | {
252 | NSString *port = [NSString stringWithFormat:@"%hu", [asyncSocket localPort]];
253 |
254 | return [NSString stringWithFormat:@"http://localhost:%@", port];
255 | }
256 | else
257 | {
258 | return origin;
259 | }
260 | }
261 |
262 | - (NSString *)locationResponseHeaderValue
263 | {
264 | HTTPLogTrace();
265 |
266 | NSString *location;
267 | NSString *host = [request headerField:@"Host"];
268 |
269 | NSString *requestUri = [[request url] relativeString];
270 |
271 | if (host == nil)
272 | {
273 | NSString *port = [NSString stringWithFormat:@"%hu", [asyncSocket localPort]];
274 |
275 | location = [NSString stringWithFormat:@"ws://localhost:%@%@", port, requestUri];
276 | }
277 | else
278 | {
279 | location = [NSString stringWithFormat:@"ws://%@%@", host, requestUri];
280 | }
281 |
282 | return location;
283 | }
284 |
285 | - (void)sendResponseHeaders
286 | {
287 | HTTPLogTrace();
288 |
289 | // Request (Draft 75):
290 | //
291 | // GET /demo HTTP/1.1
292 | // Upgrade: WebSocket
293 | // Connection: Upgrade
294 | // Host: example.com
295 | // Origin: http://example.com
296 | // WebSocket-Protocol: sample
297 | //
298 | //
299 | // Request (Draft 76):
300 | //
301 | // GET /demo HTTP/1.1
302 | // Upgrade: WebSocket
303 | // Connection: Upgrade
304 | // Host: example.com
305 | // Origin: http://example.com
306 | // Sec-WebSocket-Protocol: sample
307 | // Sec-WebSocket-Key2: 12998 5 Y3 1 .P00
308 | // Sec-WebSocket-Key1: 4 @1 46546xW%0l 1 5
309 | //
310 | // ^n:ds[4U
311 |
312 |
313 | // Response (Draft 75):
314 | //
315 | // HTTP/1.1 101 Web Socket Protocol Handshake
316 | // Upgrade: WebSocket
317 | // Connection: Upgrade
318 | // WebSocket-Origin: http://example.com
319 | // WebSocket-Location: ws://example.com/demo
320 | // WebSocket-Protocol: sample
321 | //
322 | //
323 | // Response (Draft 76):
324 | //
325 | // HTTP/1.1 101 WebSocket Protocol Handshake
326 | // Upgrade: WebSocket
327 | // Connection: Upgrade
328 | // Sec-WebSocket-Origin: http://example.com
329 | // Sec-WebSocket-Location: ws://example.com/demo
330 | // Sec-WebSocket-Protocol: sample
331 | //
332 | // 8jKS'y:G*Co,Wxa-
333 |
334 |
335 | HTTPMessage *wsResponse = [[HTTPMessage alloc] initResponseWithStatusCode:101
336 | description:@"Web Socket Protocol Handshake"
337 | version:HTTPVersion1_1];
338 |
339 | [wsResponse setHeaderField:@"Upgrade" value:@"WebSocket"];
340 | [wsResponse setHeaderField:@"Connection" value:@"Upgrade"];
341 |
342 | // Note: It appears that WebSocket-Origin and WebSocket-Location
343 | // are required for Google's Chrome implementation to work properly.
344 | //
345 | // If we don't send either header, Chrome will never report the WebSocket as open.
346 | // If we only send one of the two, Chrome will immediately close the WebSocket.
347 | //
348 | // In addition to this it appears that Chrome's implementation is very picky of the values of the headers.
349 | // They have to match exactly with what Chrome sent us or it will close the WebSocket.
350 |
351 | NSString *originValue = [self originResponseHeaderValue];
352 | NSString *locationValue = [self locationResponseHeaderValue];
353 |
354 | NSString *originField = isVersion76 ? @"Sec-WebSocket-Origin" : @"WebSocket-Origin";
355 | NSString *locationField = isVersion76 ? @"Sec-WebSocket-Location" : @"WebSocket-Location";
356 |
357 | [wsResponse setHeaderField:originField value:originValue];
358 | [wsResponse setHeaderField:locationField value:locationValue];
359 |
360 | NSData *responseHeaders = [wsResponse messageData];
361 |
362 | [wsResponse release];
363 |
364 | if (HTTP_LOG_VERBOSE)
365 | {
366 | NSString *temp = [[NSString alloc] initWithData:responseHeaders encoding:NSUTF8StringEncoding];
367 | HTTPLogVerbose(@"%@[%p] Response Headers:\n%@", THIS_FILE, self, temp);
368 | [temp release];
369 | }
370 |
371 | [asyncSocket writeData:responseHeaders withTimeout:TIMEOUT_NONE tag:TAG_HTTP_RESPONSE_HEADERS];
372 | }
373 |
374 | - (NSData *)processKey:(NSString *)key
375 | {
376 | HTTPLogTrace();
377 |
378 | unichar c;
379 | NSUInteger i;
380 | NSUInteger length = [key length];
381 |
382 | // Concatenate the digits into a string,
383 | // and count the number of spaces.
384 |
385 | NSMutableString *numStr = [NSMutableString stringWithCapacity:10];
386 | long long numSpaces = 0;
387 |
388 | for (i = 0; i < length; i++)
389 | {
390 | c = [key characterAtIndex:i];
391 |
392 | if (c >= '0' && c <= '9')
393 | {
394 | [numStr appendFormat:@"%C", c];
395 | }
396 | else if (c == ' ')
397 | {
398 | numSpaces++;
399 | }
400 | }
401 |
402 | long long num = strtoll([numStr UTF8String], NULL, 10);
403 |
404 | long long resultHostNum;
405 |
406 | if (numSpaces == 0)
407 | resultHostNum = 0;
408 | else
409 | resultHostNum = num / numSpaces;
410 |
411 | HTTPLogVerbose(@"key(%@) -> %qi / %qi = %qi", key, num, numSpaces, resultHostNum);
412 |
413 | // Convert result to 4 byte big-endian (network byte order)
414 | // and then convert to raw data.
415 |
416 | UInt32 result = OSSwapHostToBigInt32((uint32_t)resultHostNum);
417 |
418 | return [NSData dataWithBytes:&result length:4];
419 | }
420 |
421 | - (void)sendResponseBody:(NSData *)d3
422 | {
423 | HTTPLogTrace();
424 |
425 | NSAssert(isVersion76, @"WebSocket version 75 doesn't contain a response body");
426 | NSAssert([d3 length] == 8, @"Invalid requestBody length");
427 |
428 | NSString *key1 = [request headerField:@"Sec-WebSocket-Key1"];
429 | NSString *key2 = [request headerField:@"Sec-WebSocket-Key2"];
430 |
431 | NSData *d1 = [self processKey:key1];
432 | NSData *d2 = [self processKey:key2];
433 |
434 | // Concatenated d1, d2 & d3
435 |
436 | NSMutableData *d0 = [NSMutableData dataWithCapacity:(4+4+8)];
437 | [d0 appendData:d1];
438 | [d0 appendData:d2];
439 | [d0 appendData:d3];
440 |
441 | // Hash the data using MD5
442 |
443 | NSData *responseBody = [d0 md5Digest];
444 |
445 | [asyncSocket writeData:responseBody withTimeout:TIMEOUT_NONE tag:TAG_HTTP_RESPONSE_BODY];
446 |
447 | if (HTTP_LOG_VERBOSE)
448 | {
449 | NSString *s1 = [[NSString alloc] initWithData:d1 encoding:NSASCIIStringEncoding];
450 | NSString *s2 = [[NSString alloc] initWithData:d2 encoding:NSASCIIStringEncoding];
451 | NSString *s3 = [[NSString alloc] initWithData:d3 encoding:NSASCIIStringEncoding];
452 |
453 | NSString *s0 = [[NSString alloc] initWithData:d0 encoding:NSASCIIStringEncoding];
454 |
455 | NSString *sH = [[NSString alloc] initWithData:responseBody encoding:NSASCIIStringEncoding];
456 |
457 | HTTPLogVerbose(@"key1 result : raw(%@) str(%@)", d1, s1);
458 | HTTPLogVerbose(@"key2 result : raw(%@) str(%@)", d2, s2);
459 | HTTPLogVerbose(@"key3 passed : raw(%@) str(%@)", d3, s3);
460 | HTTPLogVerbose(@"key0 concat : raw(%@) str(%@)", d0, s0);
461 | HTTPLogVerbose(@"responseBody: raw(%@) str(%@)", responseBody, sH);
462 |
463 | [s1 release];
464 | [s2 release];
465 | [s3 release];
466 | [s0 release];
467 | [sH release];
468 | }
469 | }
470 |
471 | ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
472 | #pragma mark Core Functionality
473 | ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
474 |
475 | - (void)didOpen
476 | {
477 | HTTPLogTrace();
478 |
479 | // Override me to perform any custom actions once the WebSocket has been opened.
480 | // This method is invoked on the websocketQueue.
481 | //
482 | // Don't forget to invoke [super didOpen] in your method.
483 |
484 | // Start reading for messages
485 | [asyncSocket readDataToLength:1 withTimeout:TIMEOUT_NONE tag:TAG_PREFIX];
486 |
487 | // Notify delegate
488 | if ([delegate respondsToSelector:@selector(webSocketDidOpen:)])
489 | {
490 | [delegate webSocketDidOpen:self];
491 | }
492 | }
493 |
494 | - (void)sendMessage:(NSString *)msg
495 | {
496 | HTTPLogTrace();
497 |
498 | NSData *msgData = [msg dataUsingEncoding:NSUTF8StringEncoding];
499 |
500 | NSMutableData *data = [NSMutableData dataWithCapacity:([msgData length] + 2)];
501 |
502 | [data appendBytes:"\x00" length:1];
503 | [data appendData:msgData];
504 | [data appendBytes:"\xFF" length:1];
505 |
506 | // Remember: GCDAsyncSocket is thread-safe
507 |
508 | [asyncSocket writeData:data withTimeout:TIMEOUT_NONE tag:0];
509 | }
510 |
511 | - (void)didReceiveMessage:(NSString *)msg
512 | {
513 | HTTPLogTrace();
514 |
515 | // Override me to process incoming messages.
516 | // This method is invoked on the websocketQueue.
517 | //
518 | // For completeness, you should invoke [super didReceiveMessage:msg] in your method.
519 |
520 | // Notify delegate
521 | if ([delegate respondsToSelector:@selector(webSocket:didReceiveMessage:)])
522 | {
523 | [delegate webSocket:self didReceiveMessage:msg];
524 | }
525 | }
526 |
527 | - (void)didClose
528 | {
529 | HTTPLogTrace();
530 |
531 | // Override me to perform any cleanup when the socket is closed
532 | // This method is invoked on the websocketQueue.
533 | //
534 | // Don't forget to invoke [super didClose] at the end of your method.
535 |
536 | // Notify delegate
537 | if ([delegate respondsToSelector:@selector(webSocketDidClose:)])
538 | {
539 | [delegate webSocketDidClose:self];
540 | }
541 |
542 | // Notify HTTPServer
543 | [[NSNotificationCenter defaultCenter] postNotificationName:WebSocketDidDieNotification object:self];
544 | }
545 |
546 | ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
547 | #pragma mark AsyncSocket Delegate
548 | ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
549 |
550 | - (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag
551 | {
552 | HTTPLogTrace();
553 |
554 | if (tag == TAG_HTTP_REQUEST_BODY)
555 | {
556 | [self sendResponseHeaders];
557 | [self sendResponseBody:data];
558 | [self didOpen];
559 | }
560 | else if (tag == TAG_PREFIX)
561 | {
562 | UInt8 *pFrame = (UInt8 *)[data bytes];
563 | UInt8 frame = *pFrame;
564 |
565 | if (frame <= 0x7F)
566 | {
567 | [asyncSocket readDataToData:term withTimeout:TIMEOUT_NONE tag:TAG_MSG_PLUS_SUFFIX];
568 | }
569 | else
570 | {
571 | // Unsupported frame type
572 | [self didClose];
573 | }
574 | }
575 | else
576 | {
577 | NSUInteger msgLength = [data length] - 1; // Excluding ending 0xFF frame
578 |
579 | NSString *msg = [[NSString alloc] initWithBytes:[data bytes] length:msgLength encoding:NSUTF8StringEncoding];
580 |
581 | [self didReceiveMessage:msg];
582 |
583 | [msg release];
584 |
585 | // Read next message
586 | [asyncSocket readDataToLength:1 withTimeout:TIMEOUT_NONE tag:TAG_PREFIX];
587 | }
588 | }
589 |
590 | - (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)error
591 | {
592 | HTTPLogTrace2(@"%@[%p]: socketDidDisconnect:withError: %@", THIS_FILE, self, error);
593 |
594 | [self didClose];
595 | }
596 |
597 | @end
598 |
--------------------------------------------------------------------------------
/WebServer/en.lproj/InfoPlist.strings:
--------------------------------------------------------------------------------
1 | /* Localized versions of Info.plist keys */
2 |
3 |
--------------------------------------------------------------------------------
/WebServer/localhostAdresses.h:
--------------------------------------------------------------------------------
1 | //
2 | // MainViewController.h
3 | // WebServer
4 | //
5 | // Created by willonboy on 12-1-9.
6 | // Copyright (c) 2012年 __MyCompanyName__. All rights reserved.
7 | //
8 |
9 | #define NOTIFICATION_RESOLVED_LOCALHOST_IP_ADDRS @"ResolvedLocalhostAdressesNotification"
10 |
11 | #import
12 |
13 |
14 | @interface localhostAdresses : NSObject {
15 |
16 | }
17 |
18 | + (void)list;
19 |
20 | @end
21 |
--------------------------------------------------------------------------------
/WebServer/localhostAdresses.m:
--------------------------------------------------------------------------------
1 | //
2 | // MainViewController.h
3 | // WebServer
4 | //
5 | // Created by willonboy on 12-1-9.
6 | // Copyright (c) 2012年 __MyCompanyName__. All rights reserved.
7 | //
8 | #import "localhostAdresses.h"
9 |
10 | #import
11 | #import
12 | #import
13 |
14 | @implementation localhostAdresses
15 |
16 | + (void)list
17 | {
18 | NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
19 |
20 | NSMutableDictionary* result = [NSMutableDictionary dictionary];
21 | struct ifaddrs* addrs;
22 | BOOL success = (getifaddrs(&addrs) == 0);
23 |
24 | if (success)
25 | {
26 | const struct ifaddrs* cursor = addrs;
27 | while (cursor != NULL)
28 | {
29 | NSMutableString* ip;
30 | if (cursor->ifa_addr->sa_family == AF_INET)
31 | {
32 | const struct sockaddr_in* dlAddr = (const struct sockaddr_in*)cursor->ifa_addr;
33 | const uint8_t* base = (const uint8_t*)&dlAddr->sin_addr;
34 | ip = [[NSMutableString new] autorelease];
35 | for (int i = 0; i < 4; i++)
36 | {
37 | if (i != 0)
38 | [ip appendFormat:@"."];
39 | [ip appendFormat:@"%d", base[i]];
40 | }
41 | [result setObject:(NSString*)ip forKey:[NSString stringWithFormat:@"%s", cursor->ifa_name]];
42 | }
43 | cursor = cursor->ifa_next;
44 | }
45 | freeifaddrs(addrs);
46 | }
47 |
48 | //获取外网IP
49 | NSURL *netIPURL = [NSURL URLWithString:@"http://whatismyip.org"];//http://whatismyip.org/ipimg.php (image)
50 | NSString *netIP = [NSString stringWithContentsOfURL:netIPURL encoding:NSUTF8StringEncoding error:nil];
51 |
52 | if (netIP)
53 | {
54 | [result setObject:netIP forKey:@"www"];
55 | }
56 |
57 | NSLog(@"IP addresses: %@", result);
58 | [[NSNotificationCenter defaultCenter] postNotificationName:NOTIFICATION_RESOLVED_LOCALHOST_IP_ADDRS object:result];
59 |
60 | [pool release];
61 | }
62 |
63 | @end
64 |
--------------------------------------------------------------------------------
/WebServer/main.m:
--------------------------------------------------------------------------------
1 | //
2 | // main.m
3 | // WebServer
4 | //
5 | // Created by willonboy on 12-1-9.
6 | // Copyright (c) 2012年 __MyCompanyName__. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | #import "WTZAppDelegate.h"
12 |
13 | int main(int argc, char *argv[])
14 | {
15 | NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
16 | int result = UIApplicationMain(argc, argv, nil, NSStringFromClass([WTZAppDelegate class]));
17 | [pool release];
18 | return result;
19 | }
20 |
--------------------------------------------------------------------------------
/WebServer/web/.svn/entries:
--------------------------------------------------------------------------------
1 | 10
2 |
3 | dir
4 | 0
5 | http://svn.archermind.com/huawei-osc-client/iphone/trunk/code/demo%20code/FileServer/web
6 | http://svn.archermind.com/huawei-osc-client
7 | add
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 | 39b44eff-e321-4711-b32a-822dfffbe932
28 |
29 | file
30 | dir
31 |
32 |
33 |
34 | add
35 |
36 | image
37 | dir
38 |
39 |
40 |
41 | add
42 |
43 | index.html
44 | file
45 |
46 |
47 |
48 | add
49 |
50 |
--------------------------------------------------------------------------------
/WebServer/web/file/.svn/entries:
--------------------------------------------------------------------------------
1 | 10
2 |
3 | dir
4 | 0
5 | http://svn.archermind.com/huawei-osc-client/iphone/trunk/code/demo%20code/FileServer/web/file
6 | http://svn.archermind.com/huawei-osc-client
7 | add
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 | 39b44eff-e321-4711-b32a-822dfffbe932
28 |
29 | fileprogress.js
30 | file
31 |
32 |
33 |
34 | add
35 |
36 | swfupload.js
37 | file
38 |
39 |
40 |
41 | add
42 |
43 | swfupload.queue.js
44 | file
45 |
46 |
47 |
48 | add
49 |
50 | swfupload.swf
51 | file
52 |
53 |
54 |
55 | add
56 |
57 |
58 |
59 |
60 |
61 | has-props
62 | has-prop-mods
63 |
64 | api.js
65 | file
66 |
67 |
68 |
69 | add
70 |
71 | default.css
72 | file
73 |
74 |
75 |
76 | add
77 |
78 | handlers.js
79 | file
80 |
81 |
82 |
83 | add
84 |
85 | jquery.min.js
86 | file
87 |
88 |
89 |
90 | add
91 |
92 |
--------------------------------------------------------------------------------
/WebServer/web/file/.svn/props/swfupload.swf.svn-work:
--------------------------------------------------------------------------------
1 | K 13
2 | svn:mime-type
3 | V 24
4 | application/octet-stream
5 | END
6 |
--------------------------------------------------------------------------------
/WebServer/web/file/api.js:
--------------------------------------------------------------------------------
1 | var serviceApi;
2 | if (serviceApi == undefined) {
3 | serviceApi = {};
4 | }
5 |
6 | serviceApi.getUploadedFileList = function(callback){
7 | $.getJSON('service/api', {method:"getUploadedFileList"}, callback);
8 | }
9 |
10 | serviceApi.sendCancelUploadedAction = function(_id)
11 | {
12 | $.getJSON('service/api', {method:"cancelUploadedAction", id:_id}, callback);
13 | }
14 |
15 | serviceApi.sendCancelAllUploadedAction = function(callback)
16 | {
17 | $.getJSON('service/api', {method:"cancelAllUploadedAction"}, callback);
18 | }
--------------------------------------------------------------------------------
/WebServer/web/file/default.css:
--------------------------------------------------------------------------------
1 | /* -----------------------------------------------
2 | www.swfupload.org
3 | Description: Common Screen Stylesheet for SWFUpload Demos
4 | Updated on: May 1, 2008
5 | ----------------------------------------------- */
6 |
7 |
8 | /* -----------------------------------------------
9 | GLOBAL RESET
10 | ----------------------------------------------- */
11 |
12 | html, body, div, span, applet, object, iframe,
13 | h1, h2, h3, h4, h5, h6, p, blockquote, pre,
14 | a, abbr, acronym, address, big, cite, code,
15 | del, dfn, font, img, ins, kbd, q, s, samp,
16 | small, strike, strong, sub, sup, tt, var,
17 | dl, dt, dd, ol, ul, li,
18 | fieldset, form, label, legend,
19 | table, caption, tbody, tfoot, thead, tr, th, td {
20 | margin: 0;
21 | padding: 0;
22 | border: 0;
23 | outline: 0;
24 | font-weight: inherit;
25 | font-style: inherit;
26 | font-size: 100%;
27 | font-family: inherit;
28 | vertical-align: baseline;
29 | }
30 |
31 | /* remember to define focus styles! */
32 | :focus { outline: 0; }
33 | body {
34 | line-height: 1;
35 | color: black;
36 | background: #ffffff;
37 | }
38 | ol, ul {
39 | list-style: none;
40 | }
41 | /* tables still need 'cellspacing="0"' in the markup */
42 | table {
43 | border-collapse: separate;
44 | border-spacing: 0;
45 | }
46 | caption, th, td {
47 | text-align: left;
48 | font-weight: normal;
49 | }
50 | blockquote:before, blockquote:after,
51 | q:before, q:after {
52 | content: "";
53 | }
54 | blockquote, q {
55 | quotes: "" "";
56 | }
57 |
58 |
59 | /* -----------------------------------------------
60 | BASIC ELEMENTS
61 | ----------------------------------------------- */
62 |
63 |
64 | /* -- Text Styles ------------------------------- */
65 | html,
66 | body {
67 | margin: 0;
68 | padding: 0;
69 | width: 100%;
70 | font: 12px/1.4em Helvetica, Arial, sans-serif;
71 | }
72 |
73 | a {
74 | color: #385ea2;
75 | text-decoration: none;
76 | }
77 | a:hover { text-decoration: underline; }
78 |
79 | strong { font-weight: 700; }
80 |
81 | h1 {
82 | font: 28px/1em Arial, Helvetica, sans-serif;
83 | padding: 60px 20px 20px;
84 | margin-bottom: 15px;
85 | color: #333;
86 | text-decoration: none;
87 | }
88 |
89 | h1 a{
90 | color: #fff;
91 | text-decoration: none;
92 | }
93 |
94 | h2 {
95 | font-size: 22px;
96 | font-weight: 300;
97 | padding-top: 1em;
98 | padding-bottom: .25em;
99 | }
100 |
101 |
102 | p {
103 | margin-top: .25em;
104 | margin-bottom: .5em;
105 | }
106 |
107 | ul { padding: 4px 5px; }
108 | ul li {
109 | padding: 4px 5px;
110 | margin: 0 20px;
111 | list-style:square;
112 | }
113 |
114 | code {
115 | display: block;
116 | background:#edffb8 none repeat scroll 0%;
117 | border-color:#b2da3a;
118 | border-style:solid;
119 | border-width:1px 0;
120 | font-size: 1em;
121 | margin: 1em 0pt;
122 | overflow:auto;
123 | padding: 0.3em 0.4em;
124 | white-space:pre;
125 | }
126 |
127 | /* -- Layout ------------------------------- */
128 |
129 | .theFont{
130 | font-size:11px;
131 | }
132 | #header {
133 | background: #313131 url(../image/header-bg.jpg) repeat-x top left;
134 | height: 85px;
135 | position: relative;
136 | }
137 | #logo {
138 | padding: 0;
139 | margin: 0;
140 | background: url(../image/cloud.png) no-repeat;
141 | height: 76px;
142 | width: 272px;
143 | text-indent: -5000px;
144 | overflow: hidden;
145 | }
146 | /* hide link text */
147 | #logo a {
148 | display: block;
149 | color: #fff;
150 | text-indent: -5000px;
151 | overflow: hidden;
152 | height: 106px;
153 | width: 272px;
154 | }
155 |
156 | #version {
157 | color: #fff;
158 | position: absolute;
159 | right: 20px;
160 | top: 85px;
161 | }
162 |
163 |
164 | #content { width: 680px;}
165 | #content { margin: 20px 90px; }
166 |
167 |
168 |
169 |
170 | /* -- Form Styles ------------------------------- */
171 | form {
172 | margin: 0;
173 | padding: 0;
174 | }
175 |
176 |
177 |
178 | div.fieldset {
179 | border: 1px solid #afe14c;
180 | margin: 10px 0;
181 | padding: 20px 10px;
182 | }
183 | div.fieldset span.legend {
184 | position: relative;
185 | background-color: #FFF;
186 | padding: 3px;
187 | top: -30px;
188 | font: 700 14px Arial, Helvetica, sans-serif;
189 | color: #73b304;
190 | }
191 |
192 | div.flash {
193 | width: 375px;
194 | margin: 10px 5px;
195 | border-color: #D9E4FF;
196 |
197 | -moz-border-radius-topleft : 5px;
198 | -webkit-border-top-left-radius : 5px;
199 | -moz-border-radius-topright : 5px;
200 | -webkit-border-top-right-radius : 5px;
201 | -moz-border-radius-bottomleft : 5px;
202 | -webkit-border-bottom-left-radius : 5px;
203 | -moz-border-radius-bottomright : 5px;
204 | -webkit-border-bottom-right-radius : 5px;
205 |
206 | }
207 |
208 | button,
209 | input,
210 | select,
211 | textarea {
212 | border-width: 1px;
213 | margin-bottom: 10px;
214 | padding: 2px 3px;
215 | }
216 |
217 |
218 |
219 | input[disabled]{ border: 1px solid #ccc } /* FF 2 Fix */
220 |
221 |
222 | label {
223 | width: 150px;
224 | text-align: right;
225 | display:block;
226 | margin-right: 5px;
227 | }
228 |
229 | #btnSubmit { margin: 0 0 0 155px ; }
230 |
231 | /* -- Table Styles ------------------------------- */
232 | td {
233 | font: 10pt Helvetica, Arial, sans-serif;
234 | vertical-align: top;
235 | }
236 |
237 | .progressWrapper {
238 | width: 357px;
239 | overflow: hidden;
240 | }
241 |
242 | .progressContainer {
243 | margin: 5px;
244 | padding: 4px;
245 | border: solid 1px #E8E8E8;
246 | background-color: #F7F7F7;
247 | overflow: hidden;
248 | }
249 | /* Message */
250 | .message {
251 | margin: 1em 0;
252 | padding: 10px 20px;
253 | border: solid 1px #FFDD99;
254 | background-color: #FFFFCC;
255 | overflow: hidden;
256 | }
257 | /* Error */
258 | .red {
259 | border: solid 1px #B50000;
260 | background-color: #FFEBEB;
261 | }
262 |
263 | /* Current */
264 | .green {
265 | border: solid 1px #DDF0DD;
266 | background-color: #EBFFEB;
267 | }
268 |
269 | /* Complete */
270 | .blue {
271 | border: solid 1px #CEE2F2;
272 | background-color: #F0F5FF;
273 | }
274 |
275 | .progressName {
276 | font-size: 8pt;
277 | font-weight: 700;
278 | color: #555;
279 | width: 323px;
280 | height: 14px;
281 | text-align: left;
282 | white-space: nowrap;
283 | overflow: hidden;
284 | }
285 |
286 | .progressBarInProgress,
287 | .progressBarComplete,
288 | .progressBarError {
289 | font-size: 0;
290 | width: 0%;
291 | height: 2px;
292 | background-color: blue;
293 | margin-top: 2px;
294 | }
295 |
296 | .progressBarComplete {
297 | width: 100%;
298 | background-color: green;
299 | visibility: hidden;
300 | }
301 |
302 | .progressBarError {
303 | width: 100%;
304 | background-color: red;
305 | visibility: hidden;
306 | }
307 |
308 | .progressBarStatus {
309 | margin-top: 2px;
310 | width: 337px;
311 | font-size: 7pt;
312 | font-family: Arial;
313 | text-align: left;
314 | white-space: nowrap;
315 | }
316 |
317 | a.progressCancel {
318 | font-size: 0;
319 | display: block;
320 | height: 14px;
321 | width: 14px;
322 | background-image: url(../image/cancelbutton.gif);
323 | background-repeat: no-repeat;
324 | background-position: -14px 0px;
325 | float: right;
326 | }
327 |
328 | a.progressCancel:hover {
329 | background-position: 0px 0px;
330 | }
331 |
332 |
333 | /* -- SWFUpload Object Styles ------------------------------- */
334 | .swfupload {
335 | vertical-align: top;
336 | }
337 | /*************---------------------index.html -----------------***************/
338 | .addnewfile
339 | {
340 | float:right;
341 | color:#ffffff;
342 | }
343 | #uploadFileList{
344 | margin:20px;
345 | }
346 | #btn{
347 | text-align:center;
348 | margin-top:20px;
349 | }
350 |
--------------------------------------------------------------------------------
/WebServer/web/file/fileprogress.js:
--------------------------------------------------------------------------------
1 | /*
2 | A simple class for displaying file information and progress
3 | Note: This is a demonstration only and not part of SWFUpload.
4 | Note: Some have had problems adapting this class in IE7. It may not be suitable for your application.
5 | */
6 |
7 | // Constructor
8 | // file is a SWFUpload file object
9 | // targetID is the HTML element id attribute that the FileProgress HTML structure will be added to.
10 | // Instantiating a new FileProgress object with an existing file will reuse/update the existing DOM elements
11 | function FileProgress(file, targetID) {
12 | this.fileProgressID = file.id;
13 |
14 | this.opacity = 100;
15 | this.height = 0;
16 |
17 |
18 | this.fileProgressWrapper = document.getElementById(this.fileProgressID);
19 | if (!this.fileProgressWrapper) {
20 | this.fileProgressWrapper = document.createElement("div");
21 | this.fileProgressWrapper.className = "progressWrapper";
22 | this.fileProgressWrapper.id = this.fileProgressID;
23 |
24 | this.fileProgressElement = document.createElement("div");
25 | this.fileProgressElement.className = "progressContainer";
26 |
27 | var progressCancel = document.createElement("a");
28 | progressCancel.className = "progressCancel";
29 | progressCancel.href = "#";
30 | progressCancel.style.visibility = "hidden";
31 | progressCancel.appendChild(document.createTextNode(" "));
32 |
33 | var progressText = document.createElement("div");
34 | progressText.className = "progressName";
35 | progressText.appendChild(document.createTextNode(file.name));
36 |
37 | var progressBar = document.createElement("div");
38 | progressBar.className = "progressBarInProgress";
39 |
40 | var progressStatus = document.createElement("div");
41 | progressStatus.className = "progressBarStatus";
42 | progressStatus.innerHTML = " ";
43 |
44 | this.fileProgressElement.appendChild(progressCancel);
45 | this.fileProgressElement.appendChild(progressText);
46 | this.fileProgressElement.appendChild(progressStatus);
47 | this.fileProgressElement.appendChild(progressBar);
48 |
49 | this.fileProgressWrapper.appendChild(this.fileProgressElement);
50 |
51 | document.getElementById(targetID).appendChild(this.fileProgressWrapper);
52 | } else {
53 | this.fileProgressElement = this.fileProgressWrapper.firstChild;
54 | this.reset();
55 | }
56 |
57 | this.height = this.fileProgressWrapper.offsetHeight;
58 | this.setTimer(null);
59 |
60 |
61 | }
62 |
63 | FileProgress.prototype.setTimer = function (timer) {
64 | this.fileProgressElement["FP_TIMER"] = timer;
65 | };
66 | FileProgress.prototype.getTimer = function (timer) {
67 | return this.fileProgressElement["FP_TIMER"] || null;
68 | };
69 |
70 | FileProgress.prototype.reset = function () {
71 | this.fileProgressElement.className = "progressContainer";
72 |
73 | this.fileProgressElement.childNodes[2].innerHTML = " ";
74 | this.fileProgressElement.childNodes[2].className = "progressBarStatus";
75 |
76 | this.fileProgressElement.childNodes[3].className = "progressBarInProgress";
77 | this.fileProgressElement.childNodes[3].style.width = "0%";
78 |
79 | this.appear();
80 | };
81 |
82 | FileProgress.prototype.setProgress = function (percentage) {
83 | this.fileProgressElement.className = "progressContainer green";
84 | this.fileProgressElement.childNodes[3].className = "progressBarInProgress";
85 | this.fileProgressElement.childNodes[3].style.width = percentage + "%";
86 |
87 | this.appear();
88 | };
89 | FileProgress.prototype.setComplete = function () {
90 | this.fileProgressElement.className = "progressContainer blue";
91 | this.fileProgressElement.childNodes[3].className = "progressBarComplete";
92 | this.fileProgressElement.childNodes[3].style.width = "";
93 |
94 | var oSelf = this;
95 | this.setTimer(setTimeout(function () {
96 | oSelf.disappear();
97 | }, 10000));
98 | };
99 | FileProgress.prototype.setError = function () {
100 | this.fileProgressElement.className = "progressContainer red";
101 | this.fileProgressElement.childNodes[3].className = "progressBarError";
102 | this.fileProgressElement.childNodes[3].style.width = "";
103 |
104 | var oSelf = this;
105 | this.setTimer(setTimeout(function () {
106 | oSelf.disappear();
107 | }, 5000));
108 | };
109 | FileProgress.prototype.setCancelled = function () {
110 | this.fileProgressElement.className = "progressContainer";
111 | this.fileProgressElement.childNodes[3].className = "progressBarError";
112 | this.fileProgressElement.childNodes[3].style.width = "";
113 |
114 | var oSelf = this;
115 | this.setTimer(setTimeout(function () {
116 | oSelf.disappear();
117 | }, 2000));
118 | };
119 | FileProgress.prototype.setStatus = function (status) {
120 | this.fileProgressElement.childNodes[2].innerHTML = status;
121 | };
122 |
123 | // Show/Hide the cancel button
124 | FileProgress.prototype.toggleCancel = function (show, swfUploadInstance) {
125 | this.fileProgressElement.childNodes[0].style.visibility = show ? "visible" : "hidden";
126 | if (swfUploadInstance) {
127 | var fileID = this.fileProgressID;
128 | this.fileProgressElement.childNodes[0].onclick = function () {
129 | swfUploadInstance.cancelUpload(fileID);
130 | return false;
131 | };
132 | }
133 | };
134 |
135 | FileProgress.prototype.appear = function () {
136 | if (this.getTimer() !== null) {
137 | clearTimeout(this.getTimer());
138 | this.setTimer(null);
139 | }
140 |
141 | if (this.fileProgressWrapper.filters) {
142 | try {
143 | this.fileProgressWrapper.filters.item("DXImageTransform.Microsoft.Alpha").opacity = 100;
144 | } catch (e) {
145 | // If it is not set initially, the browser will throw an error. This will set it if it is not set yet.
146 | this.fileProgressWrapper.style.filter = "progid:DXImageTransform.Microsoft.Alpha(opacity=100)";
147 | }
148 | } else {
149 | this.fileProgressWrapper.style.opacity = 1;
150 | }
151 |
152 | this.fileProgressWrapper.style.height = "";
153 |
154 | this.height = this.fileProgressWrapper.offsetHeight;
155 | this.opacity = 100;
156 | this.fileProgressWrapper.style.display = "";
157 |
158 | };
159 |
160 | // Fades out and clips away the FileProgress box.
161 | FileProgress.prototype.disappear = function () {
162 |
163 | var reduceOpacityBy = 15;
164 | var reduceHeightBy = 4;
165 | var rate = 30; // 15 fps
166 |
167 | if (this.opacity > 0) {
168 | this.opacity -= reduceOpacityBy;
169 | if (this.opacity < 0) {
170 | this.opacity = 0;
171 | }
172 |
173 | if (this.fileProgressWrapper.filters) {
174 | try {
175 | this.fileProgressWrapper.filters.item("DXImageTransform.Microsoft.Alpha").opacity = this.opacity;
176 | } catch (e) {
177 | // If it is not set initially, the browser will throw an error. This will set it if it is not set yet.
178 | this.fileProgressWrapper.style.filter = "progid:DXImageTransform.Microsoft.Alpha(opacity=" + this.opacity + ")";
179 | }
180 | } else {
181 | this.fileProgressWrapper.style.opacity = this.opacity / 100;
182 | }
183 | }
184 |
185 | if (this.height > 0) {
186 | this.height -= reduceHeightBy;
187 | if (this.height < 0) {
188 | this.height = 0;
189 | }
190 |
191 | this.fileProgressWrapper.style.height = this.height + "px";
192 | }
193 |
194 | if (this.height > 0 || this.opacity > 0) {
195 | var oSelf = this;
196 | this.setTimer(setTimeout(function () {
197 | oSelf.disappear();
198 | }, rate));
199 | } else {
200 | this.fileProgressWrapper.style.display = "none";
201 | this.setTimer(null);
202 | }
203 | };
--------------------------------------------------------------------------------
/WebServer/web/file/handlers.js:
--------------------------------------------------------------------------------
1 | /* Demo Note: This demo uses a FileProgress class that handles the UI for displaying the file name and percent complete.
2 | The FileProgress class is not part of SWFUpload.
3 | */
4 |
5 |
6 | /* **********************
7 | Event Handlers
8 | These are my custom event handlers to make my
9 | web application behave the way I went when SWFUpload
10 | completes different tasks. These aren't part of the SWFUpload
11 | package. They are part of my application. Without these none
12 | of the actions SWFUpload makes will show up in my application.
13 | ********************** */
14 | function fileQueued(file) {
15 | try {
16 | var progress = new FileProgress(file, this.customSettings.progressTarget);
17 | progress.setStatus("等待中...");
18 | progress.toggleCancel(true, this);
19 |
20 | } catch (ex) {
21 | this.debug(ex);
22 | }
23 |
24 | }
25 |
26 | function fileQueueError(file, errorCode, message) {
27 | try {
28 | if (errorCode === SWFUpload.QUEUE_ERROR.QUEUE_LIMIT_EXCEEDED) {
29 | alert("You have attempted to queue too many files.\n" + (message === 0 ? "You have reached the upload limit." : "You may select " + (message > 1 ? "up to " + message + " files." : "one file.")));
30 | return;
31 | }
32 |
33 | var progress = new FileProgress(file, this.customSettings.progressTarget);
34 | progress.setError();
35 | progress.toggleCancel(false);
36 |
37 | switch (errorCode) {
38 | case SWFUpload.QUEUE_ERROR.FILE_EXCEEDS_SIZE_LIMIT:
39 | progress.setStatus("File is too big.");
40 | this.debug("Error Code: File too big, File name: " + file.name + ", File size: " + file.size + ", Message: " + message);
41 | break;
42 | case SWFUpload.QUEUE_ERROR.ZERO_BYTE_FILE:
43 | progress.setStatus("Cannot upload Zero Byte files.");
44 | this.debug("Error Code: Zero byte file, File name: " + file.name + ", File size: " + file.size + ", Message: " + message);
45 | break;
46 | case SWFUpload.QUEUE_ERROR.INVALID_FILETYPE:
47 | progress.setStatus("Invalid File Type.");
48 | this.debug("Error Code: Invalid File Type, File name: " + file.name + ", File size: " + file.size + ", Message: " + message);
49 | break;
50 | default:
51 | if (file !== null) {
52 | progress.setStatus("Unhandled Error");
53 | }
54 | this.debug("Error Code: " + errorCode + ", File name: " + file.name + ", File size: " + file.size + ", Message: " + message);
55 | break;
56 | }
57 | } catch (ex) {
58 | this.debug(ex);
59 | }
60 | }
61 |
62 | function fileDialogComplete(numFilesSelected, numFilesQueued) {
63 | try {
64 | if (numFilesSelected > 0) {
65 | document.getElementById(this.customSettings.cancelButtonId).disabled = false;
66 | }
67 |
68 | /* I want auto start the upload and I can do that here */
69 | this.startUpload();
70 | } catch (ex) {
71 | this.debug(ex);
72 | }
73 | }
74 |
75 | function uploadStart(file) {
76 | try {
77 | /* I don't want to do any file validation or anything, I'll just update the UI and
78 | return true to indicate that the upload should start.
79 | It's important to update the UI here because in Linux no uploadProgress events are called. The best
80 | we can do is say we are uploading.
81 | */
82 | var progress = new FileProgress(file, this.customSettings.progressTarget);
83 | progress.setStatus("开始上传...");
84 | progress.toggleCancel(true, this);
85 | }
86 | catch (ex) {}
87 |
88 | return true;
89 | }
90 |
91 | function uploadProgress(file, bytesLoaded, bytesTotal) {
92 | try {
93 | var percent = Math.ceil((bytesLoaded / bytesTotal) * 100);
94 |
95 | var progress = new FileProgress(file, this.customSettings.progressTarget);
96 | progress.setProgress(percent);
97 | progress.setStatus("正在上传("+percent+"%)");
98 | } catch (ex) {
99 | this.debug(ex);
100 | }
101 | }
102 |
103 | function uploadSuccess(file, serverData) {
104 | try {
105 | var progress = new FileProgress(file, this.customSettings.progressTarget);
106 | progress.setComplete();
107 | progress.setStatus("已完成。");
108 | progress.toggleCancel(false);
109 |
110 | } catch (ex) {
111 | this.debug(ex);
112 | }
113 | }
114 |
115 | function uploadError(file, errorCode, message) {
116 | try {
117 | var progress = new FileProgress(file, this.customSettings.progressTarget);
118 | progress.setError();
119 | progress.toggleCancel(false);
120 |
121 | switch (errorCode) {
122 | case SWFUpload.UPLOAD_ERROR.HTTP_ERROR:
123 | progress.setStatus("Upload Error: " + message);
124 | this.debug("Error Code: HTTP Error, File name: " + file.name + ", Message: " + message);
125 | break;
126 | case SWFUpload.UPLOAD_ERROR.UPLOAD_FAILED:
127 | progress.setStatus("Upload Failed.");
128 | this.debug("Error Code: Upload Failed, File name: " + file.name + ", File size: " + file.size + ", Message: " + message);
129 | break;
130 | case SWFUpload.UPLOAD_ERROR.IO_ERROR:
131 | progress.setStatus("Server (IO) Error");
132 | this.debug("Error Code: IO Error, File name: " + file.name + ", Message: " + message);
133 | break;
134 | case SWFUpload.UPLOAD_ERROR.SECURITY_ERROR:
135 | progress.setStatus("Security Error");
136 | this.debug("Error Code: Security Error, File name: " + file.name + ", Message: " + message);
137 | break;
138 | case SWFUpload.UPLOAD_ERROR.UPLOAD_LIMIT_EXCEEDED:
139 | progress.setStatus("Upload limit exceeded.");
140 | this.debug("Error Code: Upload Limit Exceeded, File name: " + file.name + ", File size: " + file.size + ", Message: " + message);
141 | break;
142 | case SWFUpload.UPLOAD_ERROR.FILE_VALIDATION_FAILED:
143 | progress.setStatus("Failed Validation. Upload skipped.");
144 | this.debug("Error Code: File Validation Failed, File name: " + file.name + ", File size: " + file.size + ", Message: " + message);
145 | break;
146 | case SWFUpload.UPLOAD_ERROR.FILE_CANCELLED:
147 | // If there aren't any files left (they were all cancelled) disable the cancel button
148 | if (this.getStats().files_queued === 0) {
149 | document.getElementById(this.customSettings.cancelButtonId).disabled = true;
150 | }
151 | progress.setStatus("已取消。");
152 | progress.setCancelled();
153 | break;
154 | case SWFUpload.UPLOAD_ERROR.UPLOAD_STOPPED:
155 | progress.setStatus("已停止。");
156 | break;
157 | default:
158 | progress.setStatus("Unhandled Error: " + errorCode);
159 | this.debug("Error Code: " + errorCode + ", File name: " + file.name + ", File size: " + file.size + ", Message: " + message);
160 | break;
161 | }
162 | } catch (ex) {
163 | this.debug(ex);
164 | }
165 | }
166 |
167 | function uploadComplete(file) {
168 | if (this.getStats().files_queued === 0) {
169 | document.getElementById(this.customSettings.cancelButtonId).disabled = true;
170 | }
171 | }
172 |
173 | // This event comes from the Queue Plugin
174 | function queueComplete(numFilesUploaded) {
175 | var status = document.getElementById("divStatus");
176 | // status.innerHTML = numFilesUploaded + " file" + (numFilesUploaded === 1 ? "" : "s") + " uploaded.";
177 | status.innerHTML = numFilesUploaded + " 个文件已经上传。";
178 | }
179 |
--------------------------------------------------------------------------------
/WebServer/web/file/swfupload.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/willonboy/CocoaHTTPServer---multipart-form-data/4a9f7e1d0a3b683f9f7e898c05b2b4a7eb2137b9/WebServer/web/file/swfupload.js
--------------------------------------------------------------------------------
/WebServer/web/file/swfupload.queue.js:
--------------------------------------------------------------------------------
1 | /*
2 | Queue Plug-in
3 |
4 | Features:
5 | *Adds a cancelQueue() method for cancelling the entire queue.
6 | *All queued files are uploaded when startUpload() is called.
7 | *If false is returned from uploadComplete then the queue upload is stopped.
8 | If false is not returned (strict comparison) then the queue upload is continued.
9 | *Adds a QueueComplete event that is fired when all the queued files have finished uploading.
10 | Set the event handler with the queue_complete_handler setting.
11 |
12 | */
13 |
14 | var SWFUpload;
15 | if (typeof(SWFUpload) === "function") {
16 | SWFUpload.queue = {};
17 |
18 | SWFUpload.prototype.initSettings = (function (oldInitSettings) {
19 | return function () {
20 | if (typeof(oldInitSettings) === "function") {
21 | oldInitSettings.call(this);
22 | }
23 |
24 | this.queueSettings = {};
25 |
26 | this.queueSettings.queue_cancelled_flag = false;
27 | this.queueSettings.queue_upload_count = 0;
28 |
29 | this.queueSettings.user_upload_complete_handler = this.settings.upload_complete_handler;
30 | this.queueSettings.user_upload_start_handler = this.settings.upload_start_handler;
31 | this.settings.upload_complete_handler = SWFUpload.queue.uploadCompleteHandler;
32 | this.settings.upload_start_handler = SWFUpload.queue.uploadStartHandler;
33 |
34 | this.settings.queue_complete_handler = this.settings.queue_complete_handler || null;
35 | };
36 | })(SWFUpload.prototype.initSettings);
37 |
38 | SWFUpload.prototype.startUpload = function (fileID) {
39 | this.queueSettings.queue_cancelled_flag = false;
40 | this.callFlash("StartUpload", [fileID]);
41 | };
42 |
43 | SWFUpload.prototype.cancelQueue = function () {
44 | this.queueSettings.queue_cancelled_flag = true;
45 | this.stopUpload();
46 |
47 | var stats = this.getStats();
48 | while (stats.files_queued > 0) {
49 | this.cancelUpload();
50 | stats = this.getStats();
51 | }
52 | };
53 |
54 | SWFUpload.queue.uploadStartHandler = function (file) {
55 | var returnValue;
56 | if (typeof(this.queueSettings.user_upload_start_handler) === "function") {
57 | returnValue = this.queueSettings.user_upload_start_handler.call(this, file);
58 | }
59 |
60 | // To prevent upload a real "FALSE" value must be returned, otherwise default to a real "TRUE" value.
61 | returnValue = (returnValue === false) ? false : true;
62 |
63 | this.queueSettings.queue_cancelled_flag = !returnValue;
64 |
65 | return returnValue;
66 | };
67 |
68 | SWFUpload.queue.uploadCompleteHandler = function (file) {
69 | var user_upload_complete_handler = this.queueSettings.user_upload_complete_handler;
70 | var continueUpload;
71 |
72 | if (file.filestatus === SWFUpload.FILE_STATUS.COMPLETE) {
73 | this.queueSettings.queue_upload_count++;
74 | }
75 |
76 | if (typeof(user_upload_complete_handler) === "function") {
77 | continueUpload = (user_upload_complete_handler.call(this, file) === false) ? false : true;
78 | } else if (file.filestatus === SWFUpload.FILE_STATUS.QUEUED) {
79 | // If the file was stopped and re-queued don't restart the upload
80 | continueUpload = false;
81 | } else {
82 | continueUpload = true;
83 | }
84 |
85 | if (continueUpload) {
86 | var stats = this.getStats();
87 | if (stats.files_queued > 0 && this.queueSettings.queue_cancelled_flag === false) {
88 | this.startUpload();
89 | } else if (this.queueSettings.queue_cancelled_flag === false) {
90 | this.queueEvent("queue_complete_handler", [this.queueSettings.queue_upload_count]);
91 | this.queueSettings.queue_upload_count = 0;
92 | } else {
93 | this.queueSettings.queue_cancelled_flag = false;
94 | this.queueSettings.queue_upload_count = 0;
95 | }
96 | }
97 | };
98 | }
--------------------------------------------------------------------------------
/WebServer/web/file/swfupload.swf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/willonboy/CocoaHTTPServer---multipart-form-data/4a9f7e1d0a3b683f9f7e898c05b2b4a7eb2137b9/WebServer/web/file/swfupload.swf
--------------------------------------------------------------------------------
/WebServer/web/image/.svn/entries:
--------------------------------------------------------------------------------
1 | 10
2 |
3 | dir
4 | 0
5 | http://svn.archermind.com/huawei-osc-client/iphone/trunk/code/demo%20code/FileServer/web/image
6 | http://svn.archermind.com/huawei-osc-client
7 | add
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 | 39b44eff-e321-4711-b32a-822dfffbe932
28 |
29 | TestImageNoText_65x29.png
30 | file
31 |
32 |
33 |
34 | add
35 |
36 |
37 |
38 |
39 |
40 | has-props
41 | has-prop-mods
42 |
43 | cancelbutton.gif
44 | file
45 |
46 |
47 |
48 | add
49 |
50 |
51 |
52 |
53 |
54 | has-props
55 | has-prop-mods
56 |
57 | bg-interior-tile-drk.jpg
58 | file
59 |
60 |
61 |
62 | add
63 |
64 |
65 |
66 |
67 |
68 | has-props
69 | has-prop-mods
70 |
71 | cloud.png
72 | file
73 |
74 |
75 |
76 | add
77 |
78 |
79 |
80 |
81 |
82 | has-props
83 | has-prop-mods
84 |
85 | header-bg.jpg
86 | file
87 |
88 |
89 |
90 | add
91 |
92 |
93 |
94 |
95 |
96 | has-props
97 | has-prop-mods
98 |
99 |
--------------------------------------------------------------------------------
/WebServer/web/image/.svn/props/TestImageNoText_65x29.png.svn-work:
--------------------------------------------------------------------------------
1 | K 13
2 | svn:mime-type
3 | V 24
4 | application/octet-stream
5 | END
6 |
--------------------------------------------------------------------------------
/WebServer/web/image/.svn/props/bg-interior-tile-drk.jpg.svn-work:
--------------------------------------------------------------------------------
1 | K 13
2 | svn:mime-type
3 | V 24
4 | application/octet-stream
5 | END
6 |
--------------------------------------------------------------------------------
/WebServer/web/image/.svn/props/cancelbutton.gif.svn-work:
--------------------------------------------------------------------------------
1 | K 13
2 | svn:mime-type
3 | V 24
4 | application/octet-stream
5 | END
6 |
--------------------------------------------------------------------------------
/WebServer/web/image/.svn/props/cloud.png.svn-work:
--------------------------------------------------------------------------------
1 | K 13
2 | svn:mime-type
3 | V 24
4 | application/octet-stream
5 | END
6 |
--------------------------------------------------------------------------------
/WebServer/web/image/.svn/props/header-bg.jpg.svn-work:
--------------------------------------------------------------------------------
1 | K 13
2 | svn:mime-type
3 | V 24
4 | application/octet-stream
5 | END
6 |
--------------------------------------------------------------------------------
/WebServer/web/image/TestImageNoText_65x29.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/willonboy/CocoaHTTPServer---multipart-form-data/4a9f7e1d0a3b683f9f7e898c05b2b4a7eb2137b9/WebServer/web/image/TestImageNoText_65x29.png
--------------------------------------------------------------------------------
/WebServer/web/image/bg-interior-tile-drk.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/willonboy/CocoaHTTPServer---multipart-form-data/4a9f7e1d0a3b683f9f7e898c05b2b4a7eb2137b9/WebServer/web/image/bg-interior-tile-drk.jpg
--------------------------------------------------------------------------------
/WebServer/web/image/cancelbutton.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/willonboy/CocoaHTTPServer---multipart-form-data/4a9f7e1d0a3b683f9f7e898c05b2b4a7eb2137b9/WebServer/web/image/cancelbutton.gif
--------------------------------------------------------------------------------
/WebServer/web/image/cloud.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/willonboy/CocoaHTTPServer---multipart-form-data/4a9f7e1d0a3b683f9f7e898c05b2b4a7eb2137b9/WebServer/web/image/cloud.png
--------------------------------------------------------------------------------
/WebServer/web/image/header-bg.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/willonboy/CocoaHTTPServer---multipart-form-data/4a9f7e1d0a3b683f9f7e898c05b2b4a7eb2137b9/WebServer/web/image/header-bg.jpg
--------------------------------------------------------------------------------
/WebServer/web/index.html:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 | 文件中转
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
81 |
82 |
83 |
84 |