MyHTTPConnection.m 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254
  1. //
  2. // MyHTTPConnection.m
  3. // WebServerIPhone
  4. //
  5. // CocoaLumberjack Demos
  6. //
  7. #import "MyHTTPConnection.h"
  8. #import "WebServerIPhoneAppDelegate.h"
  9. #import "HTTPLogging.h"
  10. #import "HTTPMessage.h"
  11. #import "HTTPDataResponse.h"
  12. #import "HTTPDynamicFileResponse.h"
  13. #import "GCDAsyncSocket.h"
  14. #import <CocoaLumberjack/CocoaLumberjack.h>
  15. #import "WebSocket.h"
  16. #import "WebSocketLogger.h"
  17. @implementation MyHTTPConnection
  18. static NSMutableSet *webSocketLoggers;
  19. /**
  20. * The runtime sends initialize to each class in a program exactly one time just before the class,
  21. * or any class that inherits from it, is sent its first message from within the program. (Thus the
  22. * method may never be invoked if the class is not used.) The runtime sends the initialize message to
  23. * classes in a thread-safe manner. Superclasses receive this message before their subclasses.
  24. *
  25. * This method may also be called directly (assumably by accident), hence the safety mechanism.
  26. **/
  27. + (void)initialize
  28. {
  29. static dispatch_once_t onceToken;
  30. dispatch_once(&onceToken, ^{
  31. // We need some place to store the webSocketLogger instances.
  32. // So we'll store them here, in a class variable.
  33. //
  34. // We'll also use a simple notification system to release them when they die.
  35. webSocketLoggers = [[NSMutableSet alloc] init];
  36. [[NSNotificationCenter defaultCenter] addObserver:self
  37. selector:@selector(webSocketLoggerDidDie:)
  38. name:WebSocketLoggerDidDieNotification
  39. object:nil];
  40. });
  41. }
  42. + (void)addWebSocketLogger:(WebSocketLogger *)webSocketLogger
  43. {
  44. @synchronized(webSocketLoggers)
  45. {
  46. [webSocketLoggers addObject:webSocketLogger];
  47. }
  48. }
  49. + (void)webSocketLoggerDidDie:(NSNotification *)notification
  50. {
  51. @synchronized(webSocketLoggers)
  52. {
  53. [webSocketLoggers removeObject:[notification object]];
  54. }
  55. }
  56. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  57. #pragma mark Utilities
  58. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  59. /**
  60. * Returns the logFileManager, which is a part of the DDFileLogger system.
  61. * The DDLogFileManager is the subsystem which manages the location and creation of log files.
  62. **/
  63. - (id <DDLogFileManager>)logFileManager
  64. {
  65. WebServerIPhoneAppDelegate *appDelegate;
  66. appDelegate = (WebServerIPhoneAppDelegate *)[[UIApplication sharedApplication] delegate];
  67. return appDelegate.fileLogger.logFileManager;
  68. }
  69. /**
  70. * Dynamic discovery of proper websocket href.
  71. **/
  72. - (NSString *)wsLocation
  73. {
  74. NSString *port = [NSString stringWithFormat:@"%hu", [asyncSocket localPort]];
  75. NSString *wsLocation;
  76. NSString *wsHost = [request headerField:@"Host"];
  77. if (wsHost == nil)
  78. {
  79. wsLocation = [NSString stringWithFormat:@"ws://localhost:%@/livelog", port];
  80. }
  81. else
  82. {
  83. wsLocation = [NSString stringWithFormat:@"ws://%@/livelog", wsHost];
  84. }
  85. return wsLocation;
  86. }
  87. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  88. #pragma mark /logs.html
  89. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  90. /**
  91. * Returns the response body for requests to "/logs/index.html".
  92. *
  93. * The response is generated dynamically.
  94. * It returns the list of log files currently on the system, along with their creation date and file size.
  95. **/
  96. - (NSData *)generateIndexData
  97. {
  98. NSArray *sortedLogFileInfos = [[self logFileManager] sortedLogFileInfos];
  99. NSDateFormatter *df = [[NSDateFormatter alloc] init];
  100. [df setFormatterBehavior:NSDateFormatterBehavior10_4];
  101. [df setDateFormat:@"yyyy/MM/dd HH:mm:ss"];
  102. NSNumberFormatter *nf = [[NSNumberFormatter alloc] init];
  103. [nf setFormatterBehavior:NSNumberFormatterBehavior10_4];
  104. [nf setNumberStyle:NSNumberFormatterDecimalStyle];
  105. [nf setMinimumFractionDigits:2];
  106. [nf setMaximumFractionDigits:2];
  107. NSMutableString *response = [NSMutableString stringWithCapacity:1000];
  108. [response appendString:@"<html><head>"];
  109. [response appendString:@"<style type='text/css'>@import url('styles.css');</style>"];
  110. [response appendString:@"</head><body>"];
  111. [response appendString:@"<h1>Device Log Files</h1>"];
  112. [response appendString:@"<table cellspacing='2'>"];
  113. for (DDLogFileInfo *logFileInfo in sortedLogFileInfos)
  114. {
  115. NSString *fileName = logFileInfo.fileName;
  116. NSString *fileDate = [df stringFromDate:[logFileInfo creationDate]];
  117. NSString *fileSize;
  118. unsigned long long sizeInBytes = logFileInfo.fileSize;
  119. double GBs = (double)(sizeInBytes) / (double)(1024 * 1024 * 1024);
  120. double MBs = (double)(sizeInBytes) / (double)(1024 * 1024);
  121. double KBs = (double)(sizeInBytes) / (double)(1024);
  122. if(GBs >= 1.0)
  123. {
  124. NSString *temp = [nf stringFromNumber:[NSNumber numberWithDouble:GBs]];
  125. fileSize = [NSString stringWithFormat:@"%@ GB", temp];
  126. }
  127. else if(MBs >= 1.0)
  128. {
  129. NSString *temp = [nf stringFromNumber:[NSNumber numberWithDouble:MBs]];
  130. fileSize = [NSString stringWithFormat:@"%@ MB", temp];
  131. }
  132. else
  133. {
  134. NSString *temp = [nf stringFromNumber:[NSNumber numberWithDouble:KBs]];
  135. fileSize = [NSString stringWithFormat:@"%@ KB", temp];
  136. }
  137. NSString *fileLink = [NSString stringWithFormat:@"<a href='/logs/%@'>%@</a>", fileName, fileName];
  138. [response appendFormat:@"<tr><td>%@</td><td>%@</td><td align='right'>%@</td>", fileLink, fileDate, fileSize];
  139. }
  140. [response appendString:@"</table></body></html>"];
  141. return [response dataUsingEncoding:NSUTF8StringEncoding];
  142. }
  143. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  144. #pragma mark HTTPConnection
  145. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  146. /**
  147. * Overrides method in HTTPConnection.
  148. *
  149. * This method is invoked to retrieve the filePath for a given URI.
  150. * We override it to provide proper mapping for log file paths.
  151. **/
  152. - (NSString *)filePathForURI:(NSString *)path allowDirectory:(BOOL)allowDirectory
  153. {
  154. if ([path hasPrefix:@"/logs/"])
  155. {
  156. NSString *logsDir = [[self logFileManager] logsDirectory];
  157. return [logsDir stringByAppendingPathComponent:[path lastPathComponent]];
  158. }
  159. // Fall through
  160. return [super filePathForURI:path allowDirectory:allowDirectory];
  161. }
  162. /**
  163. * Overrides method in HTTPConnection.
  164. **/
  165. - (NSObject<HTTPResponse> *)httpResponseForMethod:(NSString *)method URI:(NSString *)path
  166. {
  167. if ([path isEqualToString:@"/logs.html"])
  168. {
  169. // Dynamically generate html response with list of available log files
  170. NSData *indexData = [self generateIndexData];
  171. return [[HTTPDataResponse alloc] initWithData:indexData];
  172. }
  173. else if ([path isEqualToString:@"/socket.html"])
  174. {
  175. // The socket.html file contains a URL template that needs to be completed:
  176. //
  177. // ws = new WebSocket("%%WEBSOCKET_URL%%");
  178. //
  179. // We need to replace "%%WEBSOCKET_URL%%" with whatever URL the server is running on.
  180. // We can accomplish this easily with the HTTPDynamicFileResponse class,
  181. // which takes a dictionary of replacement key-value pairs,
  182. // and performs replacements on the fly as it uploads the file.
  183. NSString *loc = [self wsLocation];
  184. NSDictionary *replacementDict = [NSDictionary dictionaryWithObject:loc forKey:@"WEBSOCKET_URL"];
  185. return [[HTTPDynamicFileResponse alloc] initWithFilePath:[self filePathForURI:path]
  186. forConnection:self
  187. separator:@"%%"
  188. replacementDictionary:replacementDict];
  189. }
  190. // Fall through
  191. return [super httpResponseForMethod:method URI:path];
  192. }
  193. /**
  194. * Overrides method in HTTPConnection.
  195. **/
  196. - (WebSocket *)webSocketForURI:(NSString *)path
  197. {
  198. if ([path isEqualToString:@"/livelog"])
  199. {
  200. // Create the WebSocket
  201. WebSocket *webSocket = [[WebSocket alloc] initWithRequest:request socket:asyncSocket];
  202. // Create the WebSocketLogger
  203. WebSocketLogger *webSocketLogger = [[WebSocketLogger alloc] initWithWebSocket:webSocket];
  204. [[self class] addWebSocketLogger:webSocketLogger];
  205. return webSocket;
  206. }
  207. return [super webSocketForURI:path];
  208. }
  209. @end