FBWebServer.m 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241
  1. /**
  2. * Copyright (c) 2015-present, Facebook, Inc.
  3. * All rights reserved.
  4. *
  5. * This source code is licensed under the BSD-style license found in the
  6. * LICENSE file in the root directory of this source tree. An additional grant
  7. * of patent rights can be found in the PATENTS file in the same directory.
  8. */
  9. #import "FBWebServer.h"
  10. #import "RoutingConnection.h"
  11. #import "RoutingHTTPServer.h"
  12. #import "FBCommandHandler.h"
  13. #import "FBErrorBuilder.h"
  14. #import "FBExceptionHandler.h"
  15. #import "FBMjpegServer.h"
  16. #import "FBRouteRequest.h"
  17. #import "FBRuntimeUtils.h"
  18. #import "FBSession.h"
  19. #import "FBTCPSocket.h"
  20. #import "FBUnknownCommands.h"
  21. #import "FBConfiguration.h"
  22. #import "FBLogger.h"
  23. #import "XCUIDevice+FBHelpers.h"
  24. static NSString *const FBServerURLBeginMarker = @"ServerURLHere->";
  25. static NSString *const FBServerURLEndMarker = @"<-ServerURLHere";
  26. @interface FBHTTPConnection : RoutingConnection
  27. @end
  28. @implementation FBHTTPConnection
  29. - (void)handleResourceNotFound
  30. {
  31. [FBLogger logFmt:@"Received request for %@ which we do not handle", self.requestURI];
  32. [super handleResourceNotFound];
  33. }
  34. @end
  35. @interface FBWebServer ()
  36. @property (nonatomic, strong) FBExceptionHandler *exceptionHandler;
  37. @property (nonatomic, strong) RoutingHTTPServer *server;
  38. @property (atomic, assign) BOOL keepAlive;
  39. @property (nonatomic, nullable) FBTCPSocket *screenshotsBroadcaster;
  40. @end
  41. @implementation FBWebServer
  42. + (NSArray<Class<FBCommandHandler>> *)collectCommandHandlerClasses
  43. {
  44. NSArray *handlersClasses = FBClassesThatConformsToProtocol(@protocol(FBCommandHandler));
  45. NSMutableArray *handlers = [NSMutableArray array];
  46. for (Class aClass in handlersClasses) {
  47. if ([aClass respondsToSelector:@selector(shouldRegisterAutomatically)]) {
  48. if (![aClass shouldRegisterAutomatically]) {
  49. continue;
  50. }
  51. }
  52. [handlers addObject:aClass];
  53. }
  54. return handlers.copy;
  55. }
  56. - (void)startServing
  57. {
  58. [FBLogger logFmt:@"Built at %s %s", __DATE__, __TIME__];
  59. self.exceptionHandler = [FBExceptionHandler new];
  60. [self startHTTPServer];
  61. [self initScreenshotsBroadcaster];
  62. self.keepAlive = YES;
  63. NSRunLoop *runLoop = [NSRunLoop mainRunLoop];
  64. while (self.keepAlive &&
  65. [runLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]);
  66. }
  67. - (void)startHTTPServer
  68. {
  69. self.server = [[RoutingHTTPServer alloc] init];
  70. [self.server setRouteQueue:dispatch_get_main_queue()];
  71. [self.server setDefaultHeader:@"Server" value:@"WebDriverAgent/1.0"];
  72. [self.server setDefaultHeader:@"Access-Control-Allow-Origin" value:@"*"];
  73. [self.server setDefaultHeader:@"Access-Control-Allow-Headers" value:@"Content-Type, X-Requested-With"];
  74. [self.server setConnectionClass:[FBHTTPConnection self]];
  75. [self registerRouteHandlers:[self.class collectCommandHandlerClasses]];
  76. [self registerServerKeyRouteHandlers];
  77. NSRange serverPortRange = FBConfiguration.bindingPortRange;
  78. NSError *error;
  79. BOOL serverStarted = NO;
  80. for (NSUInteger index = 0; index < serverPortRange.length; index++) {
  81. NSInteger port = serverPortRange.location + index;
  82. [self.server setPort:(UInt16)port];
  83. serverStarted = [self attemptToStartServer:self.server onPort:port withError:&error];
  84. if (serverStarted) {
  85. break;
  86. }
  87. [FBLogger logFmt:@"Failed to start web server on port %ld with error %@", (long)port, [error description]];
  88. }
  89. if (!serverStarted) {
  90. [FBLogger logFmt:@"Last attempt to start web server failed with error %@", [error description]];
  91. abort();
  92. }
  93. [FBLogger logFmt:@"%@http://%@:%d%@", FBServerURLBeginMarker, [XCUIDevice sharedDevice].fb_wifiIPAddress ?: @"localhost", [self.server port], FBServerURLEndMarker];
  94. }
  95. - (void)initScreenshotsBroadcaster
  96. {
  97. [self readMjpegSettingsFromEnv];
  98. self.screenshotsBroadcaster = [[FBTCPSocket alloc]
  99. initWithPort:(uint16_t)FBConfiguration.mjpegServerPort];
  100. self.screenshotsBroadcaster.delegate = [[FBMjpegServer alloc] init];
  101. NSError *error;
  102. if (![self.screenshotsBroadcaster startWithError:&error]) {
  103. [FBLogger logFmt:@"Cannot init screenshots broadcaster service on port %@. Original error: %@", @(FBConfiguration.mjpegServerPort), error.description];
  104. self.screenshotsBroadcaster = nil;
  105. }
  106. }
  107. - (void)stopScreenshotsBroadcaster
  108. {
  109. if (nil == self.screenshotsBroadcaster) {
  110. return;
  111. }
  112. [self.screenshotsBroadcaster stop];
  113. }
  114. - (void)readMjpegSettingsFromEnv
  115. {
  116. NSDictionary *env = NSProcessInfo.processInfo.environment;
  117. NSString *scalingFactor = [env objectForKey:@"MJPEG_SCALING_FACTOR"];
  118. if (scalingFactor != nil && [scalingFactor length] > 0) {
  119. [FBConfiguration setMjpegScalingFactor:[scalingFactor integerValue]];
  120. }
  121. NSString *screenshotQuality = [env objectForKey:@"MJPEG_SERVER_SCREENSHOT_QUALITY"];
  122. if (screenshotQuality != nil && [screenshotQuality length] > 0) {
  123. [FBConfiguration setMjpegServerScreenshotQuality:[screenshotQuality integerValue]];
  124. }
  125. }
  126. - (void)stopServing
  127. {
  128. [FBSession.activeSession kill];
  129. [self stopScreenshotsBroadcaster];
  130. if (self.server.isRunning) {
  131. [self.server stop:NO];
  132. }
  133. self.keepAlive = NO;
  134. }
  135. - (BOOL)attemptToStartServer:(RoutingHTTPServer *)server onPort:(NSInteger)port withError:(NSError **)error
  136. {
  137. server.port = (UInt16)port;
  138. NSError *innerError = nil;
  139. BOOL started = [server start:&innerError];
  140. if (!started) {
  141. if (!error) {
  142. return NO;
  143. }
  144. NSString *description = @"Unknown Error when Starting server";
  145. if ([innerError.domain isEqualToString:NSPOSIXErrorDomain] && innerError.code == EADDRINUSE) {
  146. description = [NSString stringWithFormat:@"Unable to start web server on port %ld", (long)port];
  147. }
  148. return
  149. [[[[FBErrorBuilder builder]
  150. withDescription:description]
  151. withInnerError:innerError]
  152. buildError:error];
  153. }
  154. return YES;
  155. }
  156. - (void)registerRouteHandlers:(NSArray *)commandHandlerClasses
  157. {
  158. for (Class<FBCommandHandler> commandHandler in commandHandlerClasses) {
  159. NSArray *routes = [commandHandler routes];
  160. for (FBRoute *route in routes) {
  161. [self.server handleMethod:route.verb withPath:route.path block:^(RouteRequest *request, RouteResponse *response) {
  162. NSDictionary *arguments = [NSJSONSerialization JSONObjectWithData:request.body options:NSJSONReadingMutableContainers error:NULL];
  163. FBRouteRequest *routeParams = [FBRouteRequest
  164. routeRequestWithURL:request.url
  165. parameters:request.params
  166. arguments:arguments ?: @{}
  167. ];
  168. [FBLogger verboseLog:routeParams.description];
  169. @try {
  170. [route mountRequest:routeParams intoResponse:response];
  171. }
  172. @catch (NSException *exception) {
  173. [self handleException:exception forResponse:response];
  174. }
  175. }];
  176. }
  177. }
  178. }
  179. - (void)handleException:(NSException *)exception forResponse:(RouteResponse *)response
  180. {
  181. [self.exceptionHandler handleException:exception forResponse:response];
  182. }
  183. - (void)registerServerKeyRouteHandlers
  184. {
  185. [self.server get:@"/health" withBlock:^(RouteRequest *request, RouteResponse *response) {
  186. [response respondWithString:@"I-AM-ALIVE"];
  187. }];
  188. NSString *calibrationPage = @"<html>"
  189. "<title>{\"x\":null,\"y\":null}</title>"
  190. "<header>"
  191. "<script>document.addEventListener(\"click\",function(e){document.title=JSON.stringify({x:e.clientX,y:e.clientY})})</script>"
  192. "</header>"
  193. "</html>";
  194. [self.server get:@"/calibrate" withBlock:^(RouteRequest *request, RouteResponse *response) {
  195. [response respondWithString:calibrationPage];
  196. }];
  197. [self.server get:@"/wda/shutdown" withBlock:^(RouteRequest *request, RouteResponse *response) {
  198. [response respondWithString:@"Shutting down"];
  199. [self.delegate webServerDidRequestShutdown:self];
  200. }];
  201. [self registerRouteHandlers:@[FBUnknownCommands.class]];
  202. }
  203. @end