PageRenderTime 50ms CodeModel.GetById 19ms RepoModel.GetById 1ms app.codeStats 0ms

/mio/libraries/CocoaHttpServer/HTTPServer.m

https://gitlab.com/base.io/mio
Objective C | 784 lines | 453 code | 185 blank | 146 comment | 20 complexity | 2724c79ef83c8414d75d16da08a8e73d MD5 | raw file
  1. #import "HTTPServer.h"
  2. #import "GCDAsyncSocket.h"
  3. #import "HTTPConnection.h"
  4. #import "WebSocket.h"
  5. #import "HTTPLogging.h"
  6. #if ! __has_feature(objc_arc)
  7. #warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
  8. #endif
  9. // Does ARC support support GCD objects?
  10. // It does if the minimum deployment target is iOS 6+ or Mac OS X 8+
  11. #if TARGET_OS_IPHONE
  12. // Compiling for iOS
  13. #if __IPHONE_OS_VERSION_MIN_REQUIRED >= 60000 // iOS 6.0 or later
  14. #define NEEDS_DISPATCH_RETAIN_RELEASE 0
  15. #else // iOS 5.X or earlier
  16. #define NEEDS_DISPATCH_RETAIN_RELEASE 1
  17. #endif
  18. #else
  19. // Compiling for Mac OS X
  20. #if MAC_OS_X_VERSION_MIN_REQUIRED >= 1080 // Mac OS X 10.8 or later
  21. #define NEEDS_DISPATCH_RETAIN_RELEASE 0
  22. #else
  23. #define NEEDS_DISPATCH_RETAIN_RELEASE 1 // Mac OS X 10.7 or earlier
  24. #endif
  25. #endif
  26. // Log levels: off, error, warn, info, verbose
  27. // Other flags: trace
  28. static const int httpLogLevel = HTTP_LOG_LEVEL_INFO; // | HTTP_LOG_FLAG_TRACE;
  29. @interface HTTPServer (PrivateAPI)
  30. - (void)unpublishBonjour;
  31. - (void)publishBonjour;
  32. + (void)startBonjourThreadIfNeeded;
  33. + (void)performBonjourBlock:(dispatch_block_t)block;
  34. @end
  35. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  36. #pragma mark -
  37. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  38. @implementation HTTPServer
  39. /**
  40. * Standard Constructor.
  41. * Instantiates an HTTP server, but does not start it.
  42. **/
  43. - (id)init
  44. {
  45. if ((self = [super init]))
  46. {
  47. HTTPLogTrace();
  48. // Initialize underlying dispatch queue and GCD based tcp socket
  49. serverQueue = dispatch_queue_create("HTTPServer", NULL);
  50. asyncSocket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:serverQueue];
  51. // Use default connection class of HTTPConnection
  52. connectionQueue = dispatch_queue_create("HTTPConnection", NULL);
  53. connectionClass = [HTTPConnection self];
  54. // By default bind on all available interfaces, en1, wifi etc
  55. interface = nil;
  56. // Use a default port of 0
  57. // This will allow the kernel to automatically pick an open port for us
  58. port = 0;
  59. // Configure default values for bonjour service
  60. // Bonjour domain. Use the local domain by default
  61. domain = @"local.";
  62. // If using an empty string ("") for the service name when registering,
  63. // the system will automatically use the "Computer Name".
  64. // Passing in an empty string will also handle name conflicts
  65. // by automatically appending a digit to the end of the name.
  66. name = @"";
  67. // Initialize arrays to hold all the HTTP and webSocket connections
  68. connections = [[NSMutableArray alloc] init];
  69. webSockets = [[NSMutableArray alloc] init];
  70. connectionsLock = [[NSLock alloc] init];
  71. webSocketsLock = [[NSLock alloc] init];
  72. // Register for notifications of closed connections
  73. [[NSNotificationCenter defaultCenter] addObserver:self
  74. selector:@selector(connectionDidDie:)
  75. name:HTTPConnectionDidDieNotification
  76. object:nil];
  77. // Register for notifications of closed websocket connections
  78. [[NSNotificationCenter defaultCenter] addObserver:self
  79. selector:@selector(webSocketDidDie:)
  80. name:WebSocketDidDieNotification
  81. object:nil];
  82. isRunning = NO;
  83. }
  84. return self;
  85. }
  86. /**
  87. * Standard Deconstructor.
  88. * Stops the server, and clients, and releases any resources connected with this instance.
  89. **/
  90. - (void)dealloc
  91. {
  92. HTTPLogTrace();
  93. // Remove notification observer
  94. [[NSNotificationCenter defaultCenter] removeObserver:self];
  95. // Stop the server if it's running
  96. [self stop];
  97. // Release all instance variables
  98. #if NEEDS_DISPATCH_RETAIN_RELEASE
  99. dispatch_release(serverQueue);
  100. dispatch_release(connectionQueue);
  101. #endif
  102. [asyncSocket setDelegate:nil delegateQueue:NULL];
  103. }
  104. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  105. #pragma mark Server Configuration
  106. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  107. /**
  108. * The document root is filesystem root for the webserver.
  109. * Thus requests for /index.html will be referencing the index.html file within the document root directory.
  110. * All file requests are relative to this document root.
  111. **/
  112. - (NSString *)documentRoot
  113. {
  114. __block NSString *result;
  115. dispatch_sync(serverQueue, ^{
  116. result = documentRoot;
  117. });
  118. return result;
  119. }
  120. - (void)setDocumentRoot:(NSString *)value
  121. {
  122. HTTPLogTrace();
  123. // Document root used to be of type NSURL.
  124. // Add type checking for early warning to developers upgrading from older versions.
  125. if (value && ![value isKindOfClass:[NSString class]])
  126. {
  127. HTTPLogWarn(@"%@: %@ - Expecting NSString parameter, received %@ parameter",
  128. THIS_FILE, THIS_METHOD, NSStringFromClass([value class]));
  129. return;
  130. }
  131. NSString *valueCopy = [value copy];
  132. dispatch_async(serverQueue, ^{
  133. documentRoot = valueCopy;
  134. });
  135. }
  136. /**
  137. * The connection class is the class that will be used to handle connections.
  138. * That is, when a new connection is created, an instance of this class will be intialized.
  139. * The default connection class is HTTPConnection.
  140. * If you use a different connection class, it is assumed that the class extends HTTPConnection
  141. **/
  142. - (Class)connectionClass
  143. {
  144. __block Class result;
  145. dispatch_sync(serverQueue, ^{
  146. result = connectionClass;
  147. });
  148. return result;
  149. }
  150. - (void)setConnectionClass:(Class)value
  151. {
  152. HTTPLogTrace();
  153. dispatch_async(serverQueue, ^{
  154. connectionClass = value;
  155. });
  156. }
  157. /**
  158. * What interface to bind the listening socket to.
  159. **/
  160. - (NSString *)interface
  161. {
  162. __block NSString *result;
  163. dispatch_sync(serverQueue, ^{
  164. result = interface;
  165. });
  166. return result;
  167. }
  168. - (void)setInterface:(NSString *)value
  169. {
  170. NSString *valueCopy = [value copy];
  171. dispatch_async(serverQueue, ^{
  172. interface = valueCopy;
  173. });
  174. }
  175. /**
  176. * The port to listen for connections on.
  177. * By default this port is initially set to zero, which allows the kernel to pick an available port for us.
  178. * After the HTTP server has started, the port being used may be obtained by this method.
  179. **/
  180. - (UInt16)port
  181. {
  182. __block UInt16 result;
  183. dispatch_sync(serverQueue, ^{
  184. result = port;
  185. });
  186. return result;
  187. }
  188. - (UInt16)listeningPort
  189. {
  190. __block UInt16 result;
  191. dispatch_sync(serverQueue, ^{
  192. if (isRunning)
  193. result = [asyncSocket localPort];
  194. else
  195. result = 0;
  196. });
  197. return result;
  198. }
  199. - (void)setPort:(UInt16)value
  200. {
  201. HTTPLogTrace();
  202. dispatch_async(serverQueue, ^{
  203. port = value;
  204. });
  205. }
  206. /**
  207. * Domain on which to broadcast this service via Bonjour.
  208. * The default domain is @"local".
  209. **/
  210. - (NSString *)domain
  211. {
  212. __block NSString *result;
  213. dispatch_sync(serverQueue, ^{
  214. result = domain;
  215. });
  216. return result;
  217. }
  218. - (void)setDomain:(NSString *)value
  219. {
  220. HTTPLogTrace();
  221. NSString *valueCopy = [value copy];
  222. dispatch_async(serverQueue, ^{
  223. domain = valueCopy;
  224. });
  225. }
  226. /**
  227. * The name to use for this service via Bonjour.
  228. * The default name is an empty string,
  229. * which should result in the published name being the host name of the computer.
  230. **/
  231. - (NSString *)name
  232. {
  233. __block NSString *result;
  234. dispatch_sync(serverQueue, ^{
  235. result = name;
  236. });
  237. return result;
  238. }
  239. - (NSString *)publishedName
  240. {
  241. __block NSString *result;
  242. dispatch_sync(serverQueue, ^{
  243. if (netService == nil)
  244. {
  245. result = nil;
  246. }
  247. else
  248. {
  249. dispatch_block_t bonjourBlock = ^{
  250. result = [[netService name] copy];
  251. };
  252. [[self class] performBonjourBlock:bonjourBlock];
  253. }
  254. });
  255. return result;
  256. }
  257. - (void)setName:(NSString *)value
  258. {
  259. NSString *valueCopy = [value copy];
  260. dispatch_async(serverQueue, ^{
  261. name = valueCopy;
  262. });
  263. }
  264. /**
  265. * The type of service to publish via Bonjour.
  266. * No type is set by default, and one must be set in order for the service to be published.
  267. **/
  268. - (NSString *)type
  269. {
  270. __block NSString *result;
  271. dispatch_sync(serverQueue, ^{
  272. result = type;
  273. });
  274. return result;
  275. }
  276. - (void)setType:(NSString *)value
  277. {
  278. NSString *valueCopy = [value copy];
  279. dispatch_async(serverQueue, ^{
  280. type = valueCopy;
  281. });
  282. }
  283. /**
  284. * The extra data to use for this service via Bonjour.
  285. **/
  286. - (NSDictionary *)TXTRecordDictionary
  287. {
  288. __block NSDictionary *result;
  289. dispatch_sync(serverQueue, ^{
  290. result = txtRecordDictionary;
  291. });
  292. return result;
  293. }
  294. - (void)setTXTRecordDictionary:(NSDictionary *)value
  295. {
  296. HTTPLogTrace();
  297. NSDictionary *valueCopy = [value copy];
  298. dispatch_async(serverQueue, ^{
  299. txtRecordDictionary = valueCopy;
  300. // Update the txtRecord of the netService if it has already been published
  301. if (netService)
  302. {
  303. NSNetService *theNetService = netService;
  304. NSData *txtRecordData = nil;
  305. if (txtRecordDictionary)
  306. txtRecordData = [NSNetService dataFromTXTRecordDictionary:txtRecordDictionary];
  307. dispatch_block_t bonjourBlock = ^{
  308. [theNetService setTXTRecordData:txtRecordData];
  309. };
  310. [[self class] performBonjourBlock:bonjourBlock];
  311. }
  312. });
  313. }
  314. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  315. #pragma mark Server Control
  316. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  317. - (BOOL)start:(NSError **)errPtr
  318. {
  319. HTTPLogTrace();
  320. __block BOOL success = YES;
  321. __block NSError *err = nil;
  322. dispatch_sync(serverQueue, ^{ @autoreleasepool {
  323. success = [asyncSocket acceptOnInterface:interface port:port error:&err];
  324. if (success)
  325. {
  326. HTTPLogInfo(@"%@: Started HTTP server on port %hu", THIS_FILE, [asyncSocket localPort]);
  327. isRunning = YES;
  328. [self publishBonjour];
  329. }
  330. else
  331. {
  332. HTTPLogError(@"%@: Failed to start HTTP Server: %@", THIS_FILE, err);
  333. }
  334. }});
  335. if (errPtr)
  336. *errPtr = err;
  337. return success;
  338. }
  339. - (void)stop
  340. {
  341. [self stop:NO];
  342. }
  343. - (void)stop:(BOOL)keepExistingConnections
  344. {
  345. HTTPLogTrace();
  346. dispatch_sync(serverQueue, ^{ @autoreleasepool {
  347. // First stop publishing the service via bonjour
  348. [self unpublishBonjour];
  349. // Stop listening / accepting incoming connections
  350. [asyncSocket disconnect];
  351. isRunning = NO;
  352. if (!keepExistingConnections)
  353. {
  354. // Stop all HTTP connections the server owns
  355. [connectionsLock lock];
  356. for (HTTPConnection *connection in connections)
  357. {
  358. [connection stop];
  359. }
  360. [connections removeAllObjects];
  361. [connectionsLock unlock];
  362. // Stop all WebSocket connections the server owns
  363. [webSocketsLock lock];
  364. for (WebSocket *webSocket in webSockets)
  365. {
  366. [webSocket stop];
  367. }
  368. [webSockets removeAllObjects];
  369. [webSocketsLock unlock];
  370. }
  371. }});
  372. }
  373. - (BOOL)isRunning
  374. {
  375. __block BOOL result;
  376. dispatch_sync(serverQueue, ^{
  377. result = isRunning;
  378. });
  379. return result;
  380. }
  381. - (void)addWebSocket:(WebSocket *)ws
  382. {
  383. [webSocketsLock lock];
  384. HTTPLogTrace();
  385. [webSockets addObject:ws];
  386. [webSocketsLock unlock];
  387. }
  388. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  389. #pragma mark Server Status
  390. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  391. /**
  392. * Returns the number of http client connections that are currently connected to the server.
  393. **/
  394. - (NSUInteger)numberOfHTTPConnections
  395. {
  396. NSUInteger result = 0;
  397. [connectionsLock lock];
  398. result = [connections count];
  399. [connectionsLock unlock];
  400. return result;
  401. }
  402. /**
  403. * Returns the number of websocket client connections that are currently connected to the server.
  404. **/
  405. - (NSUInteger)numberOfWebSocketConnections
  406. {
  407. NSUInteger result = 0;
  408. [webSocketsLock lock];
  409. result = [webSockets count];
  410. [webSocketsLock unlock];
  411. return result;
  412. }
  413. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  414. #pragma mark Incoming Connections
  415. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  416. - (HTTPConfig *)config
  417. {
  418. // Override me if you want to provide a custom config to the new connection.
  419. //
  420. // Generally this involves overriding the HTTPConfig class to include any custom settings,
  421. // and then having this method return an instance of 'MyHTTPConfig'.
  422. // Note: Think you can make the server faster by putting each connection on its own queue?
  423. // Then benchmark it before and after and discover for yourself the shocking truth!
  424. //
  425. // Try the apache benchmark tool (already installed on your Mac):
  426. // $ ab -n 1000 -c 1 http://localhost:<port>/some_path.html
  427. return [[HTTPConfig alloc] initWithServer:self documentRoot:documentRoot queue:connectionQueue];
  428. }
  429. - (void)socket:(GCDAsyncSocket *)sock didAcceptNewSocket:(GCDAsyncSocket *)newSocket
  430. {
  431. HTTPConnection *newConnection = (HTTPConnection *)[[connectionClass alloc] initWithAsyncSocket:newSocket
  432. configuration:[self config]];
  433. [connectionsLock lock];
  434. [connections addObject:newConnection];
  435. [connectionsLock unlock];
  436. [newConnection start];
  437. }
  438. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  439. #pragma mark Bonjour
  440. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  441. - (void)publishBonjour
  442. {
  443. HTTPLogTrace();
  444. NSAssert(dispatch_get_current_queue() == serverQueue, @"Invalid queue");
  445. if (type)
  446. {
  447. netService = [[NSNetService alloc] initWithDomain:domain type:type name:name port:[asyncSocket localPort]];
  448. [netService setDelegate:self];
  449. NSNetService *theNetService = netService;
  450. NSData *txtRecordData = nil;
  451. if (txtRecordDictionary)
  452. txtRecordData = [NSNetService dataFromTXTRecordDictionary:txtRecordDictionary];
  453. dispatch_block_t bonjourBlock = ^{
  454. [theNetService removeFromRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
  455. [theNetService scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
  456. [theNetService publish];
  457. // Do not set the txtRecordDictionary prior to publishing!!!
  458. // This will cause the OS to crash!!!
  459. if (txtRecordData)
  460. {
  461. [theNetService setTXTRecordData:txtRecordData];
  462. }
  463. };
  464. [[self class] startBonjourThreadIfNeeded];
  465. [[self class] performBonjourBlock:bonjourBlock];
  466. }
  467. }
  468. - (void)unpublishBonjour
  469. {
  470. HTTPLogTrace();
  471. NSAssert(dispatch_get_current_queue() == serverQueue, @"Invalid queue");
  472. if (netService)
  473. {
  474. NSNetService *theNetService = netService;
  475. dispatch_block_t bonjourBlock = ^{
  476. [theNetService stop];
  477. };
  478. [[self class] performBonjourBlock:bonjourBlock];
  479. netService = nil;
  480. }
  481. }
  482. /**
  483. * Republishes the service via bonjour if the server is running.
  484. * If the service was not previously published, this method will publish it (if the server is running).
  485. **/
  486. - (void)republishBonjour
  487. {
  488. HTTPLogTrace();
  489. dispatch_async(serverQueue, ^{
  490. [self unpublishBonjour];
  491. [self publishBonjour];
  492. });
  493. }
  494. /**
  495. * Called when our bonjour service has been successfully published.
  496. * This method does nothing but output a log message telling us about the published service.
  497. **/
  498. - (void)netServiceDidPublish:(NSNetService *)ns
  499. {
  500. // Override me to do something here...
  501. //
  502. // Note: This method is invoked on our bonjour thread.
  503. HTTPLogInfo(@"Bonjour Service Published: domain(%@) type(%@) name(%@)", [ns domain], [ns type], [ns name]);
  504. }
  505. /**
  506. * Called if our bonjour service failed to publish itself.
  507. * This method does nothing but output a log message telling us about the published service.
  508. **/
  509. - (void)netService:(NSNetService *)ns didNotPublish:(NSDictionary *)errorDict
  510. {
  511. // Override me to do something here...
  512. //
  513. // Note: This method in invoked on our bonjour thread.
  514. HTTPLogWarn(@"Failed to Publish Service: domain(%@) type(%@) name(%@) - %@",
  515. [ns domain], [ns type], [ns name], errorDict);
  516. }
  517. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  518. #pragma mark Notifications
  519. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  520. /**
  521. * This method is automatically called when a notification of type HTTPConnectionDidDieNotification is posted.
  522. * It allows us to remove the connection from our array.
  523. **/
  524. - (void)connectionDidDie:(NSNotification *)notification
  525. {
  526. // Note: This method is called on the connection queue that posted the notification
  527. [connectionsLock lock];
  528. HTTPLogTrace();
  529. [connections removeObject:[notification object]];
  530. [connectionsLock unlock];
  531. }
  532. /**
  533. * This method is automatically called when a notification of type WebSocketDidDieNotification is posted.
  534. * It allows us to remove the websocket from our array.
  535. **/
  536. - (void)webSocketDidDie:(NSNotification *)notification
  537. {
  538. // Note: This method is called on the connection queue that posted the notification
  539. [webSocketsLock lock];
  540. HTTPLogTrace();
  541. [webSockets removeObject:[notification object]];
  542. [webSocketsLock unlock];
  543. }
  544. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  545. #pragma mark Bonjour Thread
  546. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  547. /**
  548. * NSNetService is runloop based, so it requires a thread with a runloop.
  549. * This gives us two options:
  550. *
  551. * - Use the main thread
  552. * - Setup our own dedicated thread
  553. *
  554. * Since we have various blocks of code that need to synchronously access the netservice objects,
  555. * using the main thread becomes troublesome and a potential for deadlock.
  556. **/
  557. static NSThread *bonjourThread;
  558. + (void)startBonjourThreadIfNeeded
  559. {
  560. HTTPLogTrace();
  561. static dispatch_once_t predicate;
  562. dispatch_once(&predicate, ^{
  563. HTTPLogVerbose(@"%@: Starting bonjour thread...", THIS_FILE);
  564. bonjourThread = [[NSThread alloc] initWithTarget:self
  565. selector:@selector(bonjourThread)
  566. object:nil];
  567. [bonjourThread start];
  568. });
  569. }
  570. + (void)bonjourThread
  571. {
  572. @autoreleasepool {
  573. HTTPLogVerbose(@"%@: BonjourThread: Started", THIS_FILE);
  574. // We can't run the run loop unless it has an associated input source or a timer.
  575. // So we'll just create a timer that will never fire - unless the server runs for 10,000 years.
  576. [NSTimer scheduledTimerWithTimeInterval:[[NSDate distantFuture] timeIntervalSinceNow]
  577. target:self
  578. selector:@selector(donothingatall:)
  579. userInfo:nil
  580. repeats:YES];
  581. [[NSRunLoop currentRunLoop] run];
  582. HTTPLogVerbose(@"%@: BonjourThread: Aborted", THIS_FILE);
  583. }
  584. }
  585. + (void)executeBonjourBlock:(dispatch_block_t)block
  586. {
  587. HTTPLogTrace();
  588. NSAssert([NSThread currentThread] == bonjourThread, @"Executed on incorrect thread");
  589. block();
  590. }
  591. + (void)performBonjourBlock:(dispatch_block_t)block
  592. {
  593. HTTPLogTrace();
  594. [self performSelector:@selector(executeBonjourBlock:)
  595. onThread:bonjourThread
  596. withObject:block
  597. waitUntilDone:YES];
  598. }
  599. @end