| #import "MyHTTPConnection.h" |
| #import "WebServerIPhoneAppDelegate.h" |
| #import "HTTPLogging.h" |
| #import "HTTPMessage.h" |
| #import "HTTPDataResponse.h" |
| #import "HTTPDynamicFileResponse.h" |
| #import "GCDAsyncSocket.h" |
| #import <CocoaLumberjack/CocoaLumberjack.h> |
| #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 <DDLogFileManager>)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:@"<html><head>"]; |
| [response appendString:@"<style type='text/css'>@import url('styles.css');</style>"]; |
| [response appendString:@"</head><body>"]; |
| |
| [response appendString:@"<h1>Device Log Files</h1>"]; |
| |
| [response appendString:@"<table cellspacing='2'>"]; |
| |
| 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:@"<a href='/logs/%@'>%@</a>", fileName, fileName]; |
| |
| [response appendFormat:@"<tr><td>%@</td><td>%@</td><td align='right'>%@</td>", fileLink, fileDate, fileSize]; |
| } |
| |
| [response appendString:@"</table></body></html>"]; |
| |
| 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<HTTPResponse> *)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 |