HTTPServer.m 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372
  1. #import "HTTPServer.h"
  2. #import "HTTPConnection.h"
  3. #import "HTTPLogging.h"
  4. #import "GCDAsyncSocket.h"
  5. #if ! __has_feature(objc_arc)
  6. #warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
  7. #endif
  8. #pragma clang diagnostic ignored "-Wdirect-ivar-access"
  9. #pragma clang diagnostic ignored "-Wimplicit-retain-self"
  10. #pragma clang diagnostic ignored "-Wnullable-to-nonnull-conversion"
  11. #pragma clang diagnostic ignored "-Wunused"
  12. // Log levels: off, error, warn, info, verbose
  13. // Other flags: trace
  14. static const int httpLogLevel = HTTP_LOG_LEVEL_INFO; // | HTTP_LOG_FLAG_TRACE;
  15. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  16. #pragma mark -
  17. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  18. @implementation HTTPServer
  19. /**
  20. * Standard Constructor.
  21. * Instantiates an HTTP server, but does not start it.
  22. **/
  23. - (id)init
  24. {
  25. if ((self = [super init]))
  26. {
  27. HTTPLogTrace();
  28. // Setup underlying dispatch queues
  29. serverQueue = dispatch_queue_create("HTTPServer", NULL);
  30. connectionQueue = dispatch_queue_create("HTTPConnection", NULL);
  31. IsOnServerQueueKey = &IsOnServerQueueKey;
  32. IsOnConnectionQueueKey = &IsOnConnectionQueueKey;
  33. void *nonNullUnusedPointer = (__bridge void *)self; // Whatever, just not null
  34. dispatch_queue_set_specific(serverQueue, IsOnServerQueueKey, nonNullUnusedPointer, NULL);
  35. dispatch_queue_set_specific(connectionQueue, IsOnConnectionQueueKey, nonNullUnusedPointer, NULL);
  36. // Initialize underlying GCD based tcp socket
  37. asyncSocket = [[GCDAsyncSocket alloc] initWithDelegate:(id<GCDAsyncSocketDelegate>)self delegateQueue:serverQueue];
  38. // Use default connection class of HTTPConnection
  39. connectionClass = [HTTPConnection self];
  40. // By default bind on all available interfaces, en1, wifi etc
  41. interface = nil;
  42. // Use a default port of 0
  43. // This will allow the kernel to automatically pick an open port for us
  44. port = 0;
  45. // Initialize arrays to hold all the HTTP connections
  46. connections = [[NSMutableArray alloc] init];
  47. connectionsLock = [[NSLock alloc] init];
  48. // Register for notifications of closed connections
  49. [[NSNotificationCenter defaultCenter] addObserver:self
  50. selector:@selector(connectionDidDie:)
  51. name:HTTPConnectionDidDieNotification
  52. object:nil];
  53. isRunning = NO;
  54. }
  55. return self;
  56. }
  57. /**
  58. * Standard Deconstructor.
  59. * Stops the server, and clients, and releases any resources connected with this instance.
  60. **/
  61. - (void)dealloc
  62. {
  63. HTTPLogTrace();
  64. // Remove notification observer
  65. [[NSNotificationCenter defaultCenter] removeObserver:self];
  66. // Stop the server if it's running
  67. [self stop];
  68. // Release all instance variables
  69. #if !OS_OBJECT_USE_OBJC
  70. dispatch_release(serverQueue);
  71. dispatch_release(connectionQueue);
  72. #endif
  73. [asyncSocket setDelegate:nil delegateQueue:NULL];
  74. }
  75. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  76. #pragma mark Server Configuration
  77. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  78. /**
  79. * The document root is filesystem root for the webserver.
  80. * Thus requests for /index.html will be referencing the index.html file within the document root directory.
  81. * All file requests are relative to this document root.
  82. **/
  83. - (NSString *)documentRoot
  84. {
  85. __block NSString *result;
  86. dispatch_sync(serverQueue, ^{
  87. result = documentRoot;
  88. });
  89. return result;
  90. }
  91. - (void)setDocumentRoot:(NSString *)value
  92. {
  93. HTTPLogTrace();
  94. // Document root used to be of type NSURL.
  95. // Add type checking for early warning to developers upgrading from older versions.
  96. if (value && ![value isKindOfClass:[NSString class]])
  97. {
  98. HTTPLogWarn(@"%@: %@ - Expecting NSString parameter, received %@ parameter",
  99. THIS_FILE, THIS_METHOD, NSStringFromClass([value class]));
  100. return;
  101. }
  102. NSString *valueCopy = [value copy];
  103. dispatch_async(serverQueue, ^{
  104. documentRoot = valueCopy;
  105. });
  106. }
  107. /**
  108. * The connection class is the class that will be used to handle connections.
  109. * That is, when a new connection is created, an instance of this class will be intialized.
  110. * The default connection class is HTTPConnection.
  111. * If you use a different connection class, it is assumed that the class extends HTTPConnection
  112. **/
  113. - (Class)connectionClass
  114. {
  115. __block Class result;
  116. dispatch_sync(serverQueue, ^{
  117. result = connectionClass;
  118. });
  119. return result;
  120. }
  121. - (void)setConnectionClass:(Class)value
  122. {
  123. HTTPLogTrace();
  124. dispatch_async(serverQueue, ^{
  125. connectionClass = value;
  126. });
  127. }
  128. /**
  129. * What interface to bind the listening socket to.
  130. **/
  131. - (NSString *)interface
  132. {
  133. __block NSString *result;
  134. dispatch_sync(serverQueue, ^{
  135. result = interface;
  136. });
  137. return result;
  138. }
  139. - (void)setInterface:(NSString *)value
  140. {
  141. NSString *valueCopy = [value copy];
  142. dispatch_async(serverQueue, ^{
  143. interface = valueCopy;
  144. });
  145. }
  146. /**
  147. * The port to listen for connections on.
  148. * By default this port is initially set to zero, which allows the kernel to pick an available port for us.
  149. * After the HTTP server has started, the port being used may be obtained by this method.
  150. **/
  151. - (UInt16)port
  152. {
  153. __block UInt16 result;
  154. dispatch_sync(serverQueue, ^{
  155. result = port;
  156. });
  157. return result;
  158. }
  159. - (UInt16)listeningPort
  160. {
  161. __block UInt16 result;
  162. dispatch_sync(serverQueue, ^{
  163. if (isRunning)
  164. result = [asyncSocket localPort];
  165. else
  166. result = 0;
  167. });
  168. return result;
  169. }
  170. - (void)setPort:(UInt16)value
  171. {
  172. HTTPLogTrace();
  173. dispatch_async(serverQueue, ^{
  174. port = value;
  175. });
  176. }
  177. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  178. #pragma mark Server Control
  179. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  180. - (BOOL)start:(NSError **)errPtr
  181. {
  182. HTTPLogTrace();
  183. __block BOOL success = YES;
  184. __block NSError *err = nil;
  185. dispatch_sync(serverQueue, ^{ @autoreleasepool {
  186. success = [asyncSocket acceptOnInterface:interface port:port error:&err];
  187. if (success)
  188. {
  189. HTTPLogInfo(@"%@: Started HTTP server on port %hu", THIS_FILE, [asyncSocket localPort]);
  190. isRunning = YES;
  191. }
  192. else
  193. {
  194. HTTPLogError(@"%@: Failed to start HTTP Server: %@", THIS_FILE, err);
  195. }
  196. }});
  197. if (errPtr)
  198. *errPtr = err;
  199. return success;
  200. }
  201. - (void)stop
  202. {
  203. [self stop:NO];
  204. }
  205. - (void)stop:(BOOL)keepExistingConnections
  206. {
  207. HTTPLogTrace();
  208. dispatch_sync(serverQueue, ^{ @autoreleasepool {
  209. // Stop listening / accepting incoming connections
  210. [asyncSocket disconnect];
  211. isRunning = NO;
  212. if (!keepExistingConnections)
  213. {
  214. // Stop all HTTP connections the server owns
  215. [connectionsLock lock];
  216. for (HTTPConnection *connection in connections)
  217. {
  218. [connection stop];
  219. }
  220. [connections removeAllObjects];
  221. [connectionsLock unlock];
  222. }
  223. }});
  224. }
  225. - (BOOL)isRunning
  226. {
  227. __block BOOL result;
  228. dispatch_sync(serverQueue, ^{
  229. result = isRunning;
  230. });
  231. return result;
  232. }
  233. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  234. #pragma mark Server Status
  235. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  236. /**
  237. * Returns the number of http client connections that are currently connected to the server.
  238. **/
  239. - (NSUInteger)numberOfHTTPConnections
  240. {
  241. NSUInteger result = 0;
  242. [connectionsLock lock];
  243. result = [connections count];
  244. [connectionsLock unlock];
  245. return result;
  246. }
  247. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  248. #pragma mark Incoming Connections
  249. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  250. - (HTTPConfig *)config
  251. {
  252. // Override me if you want to provide a custom config to the new connection.
  253. //
  254. // Generally this involves overriding the HTTPConfig class to include any custom settings,
  255. // and then having this method return an instance of 'MyHTTPConfig'.
  256. // Note: Think you can make the server faster by putting each connection on its own queue?
  257. // Then benchmark it before and after and discover for yourself the shocking truth!
  258. //
  259. // Try the apache benchmark tool (already installed on your Mac):
  260. // $ ab -n 1000 -c 1 http://localhost:<port>/some_path.html
  261. return [[HTTPConfig alloc] initWithServer:self documentRoot:documentRoot queue:connectionQueue];
  262. }
  263. - (void)socket:(GCDAsyncSocket *)sock didAcceptNewSocket:(GCDAsyncSocket *)newSocket
  264. {
  265. HTTPConnection *newConnection = (HTTPConnection *)[[connectionClass alloc] initWithAsyncSocket:newSocket
  266. configuration:[self config]];
  267. [connectionsLock lock];
  268. [connections addObject:newConnection];
  269. [connectionsLock unlock];
  270. [newConnection start];
  271. }
  272. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  273. #pragma mark Notifications
  274. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  275. /**
  276. * This method is automatically called when a notification of type HTTPConnectionDidDieNotification is posted.
  277. * It allows us to remove the connection from our array.
  278. **/
  279. - (void)connectionDidDie:(NSNotification *)notification
  280. {
  281. // Note: This method is called on the connection queue that posted the notification
  282. [connectionsLock lock];
  283. HTTPLogTrace();
  284. [connections removeObject:[notification object]];
  285. [connectionsLock unlock];
  286. }
  287. @end