HTTPServer.m 19 KB

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