HTTPServer.m 22 KB

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