// // MyHTTPConnection.m // WebServerIPhone // // CocoaLumberjack Demos // #import "MyHTTPConnection.h" #import "WebServerIPhoneAppDelegate.h" #import "HTTPLogging.h" #import "HTTPMessage.h" #import "HTTPDataResponse.h" #import "HTTPDynamicFileResponse.h" #import "GCDAsyncSocket.h" #import #import "WebSocket.h" #import "WebSocketLogger.h" @implementation MyHTTPConnection static NSMutableSet *webSocketLoggers; /** * The runtime sends initialize to each class in a program exactly one time just before the class, * or any class that inherits from it, is sent its first message from within the program. (Thus the * method may never be invoked if the class is not used.) The runtime sends the initialize message to * classes in a thread-safe manner. Superclasses receive this message before their subclasses. * * This method may also be called directly (assumably by accident), hence the safety mechanism. **/ + (void)initialize { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ // We need some place to store the webSocketLogger instances. // So we'll store them here, in a class variable. // // We'll also use a simple notification system to release them when they die. webSocketLoggers = [[NSMutableSet alloc] init]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(webSocketLoggerDidDie:) name:WebSocketLoggerDidDieNotification object:nil]; }); } + (void)addWebSocketLogger:(WebSocketLogger *)webSocketLogger { @synchronized(webSocketLoggers) { [webSocketLoggers addObject:webSocketLogger]; } } + (void)webSocketLoggerDidDie:(NSNotification *)notification { @synchronized(webSocketLoggers) { [webSocketLoggers removeObject:[notification object]]; } } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #pragma mark Utilities //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /** * Returns the logFileManager, which is a part of the DDFileLogger system. * The DDLogFileManager is the subsystem which manages the location and creation of log files. **/ - (id )logFileManager { WebServerIPhoneAppDelegate *appDelegate; appDelegate = (WebServerIPhoneAppDelegate *)[[UIApplication sharedApplication] delegate]; return appDelegate.fileLogger.logFileManager; } /** * Dynamic discovery of proper websocket href. **/ - (NSString *)wsLocation { NSString *port = [NSString stringWithFormat:@"%hu", [asyncSocket localPort]]; NSString *wsLocation; NSString *wsHost = [request headerField:@"Host"]; if (wsHost == nil) { wsLocation = [NSString stringWithFormat:@"ws://localhost:%@/livelog", port]; } else { wsLocation = [NSString stringWithFormat:@"ws://%@/livelog", wsHost]; } return wsLocation; } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #pragma mark /logs.html //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /** * Returns the response body for requests to "/logs/index.html". * * The response is generated dynamically. * It returns the list of log files currently on the system, along with their creation date and file size. **/ - (NSData *)generateIndexData { NSArray *sortedLogFileInfos = [[self logFileManager] sortedLogFileInfos]; NSDateFormatter *df = [[NSDateFormatter alloc] init]; [df setFormatterBehavior:NSDateFormatterBehavior10_4]; [df setDateFormat:@"yyyy/MM/dd HH:mm:ss"]; NSNumberFormatter *nf = [[NSNumberFormatter alloc] init]; [nf setFormatterBehavior:NSNumberFormatterBehavior10_4]; [nf setNumberStyle:NSNumberFormatterDecimalStyle]; [nf setMinimumFractionDigits:2]; [nf setMaximumFractionDigits:2]; NSMutableString *response = [NSMutableString stringWithCapacity:1000]; [response appendString:@""]; [response appendString:@""]; [response appendString:@""]; [response appendString:@"

Device Log Files

"]; [response appendString:@""]; for (DDLogFileInfo *logFileInfo in sortedLogFileInfos) { NSString *fileName = logFileInfo.fileName; NSString *fileDate = [df stringFromDate:[logFileInfo creationDate]]; NSString *fileSize; unsigned long long sizeInBytes = logFileInfo.fileSize; double GBs = (double)(sizeInBytes) / (double)(1024 * 1024 * 1024); double MBs = (double)(sizeInBytes) / (double)(1024 * 1024); double KBs = (double)(sizeInBytes) / (double)(1024); if(GBs >= 1.0) { NSString *temp = [nf stringFromNumber:[NSNumber numberWithDouble:GBs]]; fileSize = [NSString stringWithFormat:@"%@ GB", temp]; } else if(MBs >= 1.0) { NSString *temp = [nf stringFromNumber:[NSNumber numberWithDouble:MBs]]; fileSize = [NSString stringWithFormat:@"%@ MB", temp]; } else { NSString *temp = [nf stringFromNumber:[NSNumber numberWithDouble:KBs]]; fileSize = [NSString stringWithFormat:@"%@ KB", temp]; } NSString *fileLink = [NSString stringWithFormat:@"%@", fileName, fileName]; [response appendFormat:@"", fileLink, fileDate, fileSize]; } [response appendString:@"
%@%@%@
"]; return [response dataUsingEncoding:NSUTF8StringEncoding]; } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #pragma mark HTTPConnection //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /** * Overrides method in HTTPConnection. * * This method is invoked to retrieve the filePath for a given URI. * We override it to provide proper mapping for log file paths. **/ - (NSString *)filePathForURI:(NSString *)path allowDirectory:(BOOL)allowDirectory { if ([path hasPrefix:@"/logs/"]) { NSString *logsDir = [[self logFileManager] logsDirectory]; return [logsDir stringByAppendingPathComponent:[path lastPathComponent]]; } // Fall through return [super filePathForURI:path allowDirectory:allowDirectory]; } /** * Overrides method in HTTPConnection. **/ - (NSObject *)httpResponseForMethod:(NSString *)method URI:(NSString *)path { if ([path isEqualToString:@"/logs.html"]) { // Dynamically generate html response with list of available log files NSData *indexData = [self generateIndexData]; return [[HTTPDataResponse alloc] initWithData:indexData]; } else if ([path isEqualToString:@"/socket.html"]) { // The socket.html file contains a URL template that needs to be completed: // // ws = new WebSocket("%%WEBSOCKET_URL%%"); // // We need to replace "%%WEBSOCKET_URL%%" with whatever URL the server is running on. // We can accomplish this easily with the HTTPDynamicFileResponse class, // which takes a dictionary of replacement key-value pairs, // and performs replacements on the fly as it uploads the file. NSString *loc = [self wsLocation]; NSDictionary *replacementDict = [NSDictionary dictionaryWithObject:loc forKey:@"WEBSOCKET_URL"]; return [[HTTPDynamicFileResponse alloc] initWithFilePath:[self filePathForURI:path] forConnection:self separator:@"%%" replacementDictionary:replacementDict]; } // Fall through return [super httpResponseForMethod:method URI:path]; } /** * Overrides method in HTTPConnection. **/ - (WebSocket *)webSocketForURI:(NSString *)path { if ([path isEqualToString:@"/livelog"]) { // Create the WebSocket WebSocket *webSocket = [[WebSocket alloc] initWithRequest:request socket:asyncSocket]; // Create the WebSocketLogger WebSocketLogger *webSocketLogger = [[WebSocketLogger alloc] initWithWebSocket:webSocket]; [[self class] addWebSocketLogger:webSocketLogger]; return webSocket; } return [super webSocketForURI:path]; } @end