241 lines
7.6 KiB
Mathematica
241 lines
7.6 KiB
Mathematica
|
|
/**
|
||
|
|
* Copyright (c) 2015-present, Facebook, Inc.
|
||
|
|
* All rights reserved.
|
||
|
|
*
|
||
|
|
* This source code is licensed under the BSD-style license found in the
|
||
|
|
* LICENSE file in the root directory of this source tree.
|
||
|
|
*/
|
||
|
|
|
||
|
|
#import "FBWebServer.h"
|
||
|
|
|
||
|
|
#import "RoutingConnection.h"
|
||
|
|
#import "RoutingHTTPServer.h"
|
||
|
|
|
||
|
|
#import "FBCommandHandler.h"
|
||
|
|
#import "FBErrorBuilder.h"
|
||
|
|
#import "FBExceptionHandler.h"
|
||
|
|
#import "FBMjpegServer.h"
|
||
|
|
#import "FBRouteRequest.h"
|
||
|
|
#import "FBRuntimeUtils.h"
|
||
|
|
#import "FBSession.h"
|
||
|
|
#import "FBTCPSocket.h"
|
||
|
|
#import "FBUnknownCommands.h"
|
||
|
|
#import "FBConfiguration.h"
|
||
|
|
#import "FBLogger.h"
|
||
|
|
|
||
|
|
#import "XCUIDevice+FBHelpers.h"
|
||
|
|
|
||
|
|
static NSString *const FBServerURLBeginMarker = @"ServerURLHere->";
|
||
|
|
static NSString *const FBServerURLEndMarker = @"<-ServerURLHere";
|
||
|
|
|
||
|
|
@interface FBHTTPConnection : RoutingConnection
|
||
|
|
@end
|
||
|
|
|
||
|
|
@implementation FBHTTPConnection
|
||
|
|
|
||
|
|
- (void)handleResourceNotFound
|
||
|
|
{
|
||
|
|
[FBLogger logFmt:@"Received request for %@ which we do not handle", self.requestURI];
|
||
|
|
[super handleResourceNotFound];
|
||
|
|
}
|
||
|
|
|
||
|
|
@end
|
||
|
|
|
||
|
|
|
||
|
|
@interface FBWebServer ()
|
||
|
|
@property (nonatomic, strong) FBExceptionHandler *exceptionHandler;
|
||
|
|
@property (nonatomic, strong) RoutingHTTPServer *server;
|
||
|
|
@property (atomic, assign) BOOL keepAlive;
|
||
|
|
@property (nonatomic, nullable) FBTCPSocket *screenshotsBroadcaster;
|
||
|
|
@end
|
||
|
|
|
||
|
|
@implementation FBWebServer
|
||
|
|
|
||
|
|
+ (NSArray<Class<FBCommandHandler>> *)collectCommandHandlerClasses
|
||
|
|
{
|
||
|
|
NSArray *handlersClasses = FBClassesThatConformsToProtocol(@protocol(FBCommandHandler));
|
||
|
|
NSMutableArray *handlers = [NSMutableArray array];
|
||
|
|
for (Class aClass in handlersClasses) {
|
||
|
|
if ([aClass respondsToSelector:@selector(shouldRegisterAutomatically)]) {
|
||
|
|
if (![aClass shouldRegisterAutomatically]) {
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
[handlers addObject:aClass];
|
||
|
|
}
|
||
|
|
return handlers.copy;
|
||
|
|
}
|
||
|
|
|
||
|
|
- (void)startServing
|
||
|
|
{
|
||
|
|
[FBLogger logFmt:@"Built at %s %s", __DATE__, __TIME__];
|
||
|
|
self.exceptionHandler = [FBExceptionHandler new];
|
||
|
|
[self startHTTPServer];
|
||
|
|
[self initScreenshotsBroadcaster];
|
||
|
|
|
||
|
|
self.keepAlive = YES;
|
||
|
|
NSRunLoop *runLoop = [NSRunLoop mainRunLoop];
|
||
|
|
while (self.keepAlive &&
|
||
|
|
[runLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]);
|
||
|
|
}
|
||
|
|
|
||
|
|
- (void)startHTTPServer
|
||
|
|
{
|
||
|
|
self.server = [[RoutingHTTPServer alloc] init];
|
||
|
|
[self.server setRouteQueue:dispatch_get_main_queue()];
|
||
|
|
[self.server setDefaultHeader:@"Server" value:@"WebDriverAgent/1.0"];
|
||
|
|
[self.server setDefaultHeader:@"Access-Control-Allow-Origin" value:@"*"];
|
||
|
|
[self.server setDefaultHeader:@"Access-Control-Allow-Headers" value:@"Content-Type, X-Requested-With"];
|
||
|
|
[self.server setConnectionClass:[FBHTTPConnection self]];
|
||
|
|
|
||
|
|
[self registerRouteHandlers:[self.class collectCommandHandlerClasses]];
|
||
|
|
[self registerServerKeyRouteHandlers];
|
||
|
|
|
||
|
|
NSRange serverPortRange = FBConfiguration.bindingPortRange;
|
||
|
|
NSError *error;
|
||
|
|
BOOL serverStarted = NO;
|
||
|
|
|
||
|
|
for (NSUInteger index = 0; index < serverPortRange.length; index++) {
|
||
|
|
NSInteger port = serverPortRange.location + index;
|
||
|
|
[self.server setPort:(UInt16)port];
|
||
|
|
|
||
|
|
serverStarted = [self attemptToStartServer:self.server onPort:port withError:&error];
|
||
|
|
if (serverStarted) {
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
|
||
|
|
[FBLogger logFmt:@"Failed to start web server on port %ld with error %@", (long)port, [error description]];
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!serverStarted) {
|
||
|
|
[FBLogger logFmt:@"Last attempt to start web server failed with error %@", [error description]];
|
||
|
|
abort();
|
||
|
|
}
|
||
|
|
[FBLogger logFmt:@"%@http://%@:%d%@", FBServerURLBeginMarker, [XCUIDevice sharedDevice].fb_wifiIPAddress ?: @"localhost", [self.server port], FBServerURLEndMarker];
|
||
|
|
}
|
||
|
|
|
||
|
|
- (void)initScreenshotsBroadcaster
|
||
|
|
{
|
||
|
|
[self readMjpegSettingsFromEnv];
|
||
|
|
self.screenshotsBroadcaster = [[FBTCPSocket alloc]
|
||
|
|
initWithPort:(uint16_t)FBConfiguration.mjpegServerPort];
|
||
|
|
self.screenshotsBroadcaster.delegate = [[FBMjpegServer alloc] init];
|
||
|
|
NSError *error;
|
||
|
|
if (![self.screenshotsBroadcaster startWithError:&error]) {
|
||
|
|
[FBLogger logFmt:@"Cannot init screenshots broadcaster service on port %@. Original error: %@", @(FBConfiguration.mjpegServerPort), error.description];
|
||
|
|
self.screenshotsBroadcaster = nil;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
- (void)stopScreenshotsBroadcaster
|
||
|
|
{
|
||
|
|
if (nil == self.screenshotsBroadcaster) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
[self.screenshotsBroadcaster stop];
|
||
|
|
}
|
||
|
|
|
||
|
|
- (void)readMjpegSettingsFromEnv
|
||
|
|
{
|
||
|
|
NSDictionary *env = NSProcessInfo.processInfo.environment;
|
||
|
|
NSString *scalingFactor = [env objectForKey:@"MJPEG_SCALING_FACTOR"];
|
||
|
|
if (scalingFactor != nil && [scalingFactor length] > 0) {
|
||
|
|
[FBConfiguration setMjpegScalingFactor:[scalingFactor floatValue]];
|
||
|
|
}
|
||
|
|
NSString *screenshotQuality = [env objectForKey:@"MJPEG_SERVER_SCREENSHOT_QUALITY"];
|
||
|
|
if (screenshotQuality != nil && [screenshotQuality length] > 0) {
|
||
|
|
[FBConfiguration setMjpegServerScreenshotQuality:[screenshotQuality integerValue]];
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
- (void)stopServing
|
||
|
|
{
|
||
|
|
[FBSession.activeSession kill];
|
||
|
|
[self stopScreenshotsBroadcaster];
|
||
|
|
if (self.server.isRunning) {
|
||
|
|
[self.server stop:NO];
|
||
|
|
}
|
||
|
|
self.keepAlive = NO;
|
||
|
|
}
|
||
|
|
|
||
|
|
- (BOOL)attemptToStartServer:(RoutingHTTPServer *)server onPort:(NSInteger)port withError:(NSError **)error
|
||
|
|
{
|
||
|
|
server.port = (UInt16)port;
|
||
|
|
NSError *innerError = nil;
|
||
|
|
BOOL started = [server start:&innerError];
|
||
|
|
if (!started) {
|
||
|
|
if (!error) {
|
||
|
|
return NO;
|
||
|
|
}
|
||
|
|
|
||
|
|
NSString *description = @"Unknown Error when Starting server";
|
||
|
|
if ([innerError.domain isEqualToString:NSPOSIXErrorDomain] && innerError.code == EADDRINUSE) {
|
||
|
|
description = [NSString stringWithFormat:@"Unable to start web server on port %ld", (long)port];
|
||
|
|
}
|
||
|
|
return
|
||
|
|
[[[[FBErrorBuilder builder]
|
||
|
|
withDescription:description]
|
||
|
|
withInnerError:innerError]
|
||
|
|
buildError:error];
|
||
|
|
}
|
||
|
|
return YES;
|
||
|
|
}
|
||
|
|
|
||
|
|
- (void)registerRouteHandlers:(NSArray *)commandHandlerClasses
|
||
|
|
{
|
||
|
|
for (Class<FBCommandHandler> commandHandler in commandHandlerClasses) {
|
||
|
|
NSArray *routes = [commandHandler routes];
|
||
|
|
for (FBRoute *route in routes) {
|
||
|
|
[self.server handleMethod:route.verb withPath:route.path block:^(RouteRequest *request, RouteResponse *response) {
|
||
|
|
NSDictionary *arguments = [NSJSONSerialization JSONObjectWithData:request.body options:NSJSONReadingMutableContainers error:NULL];
|
||
|
|
FBRouteRequest *routeParams = [FBRouteRequest
|
||
|
|
routeRequestWithURL:request.url
|
||
|
|
parameters:request.params
|
||
|
|
arguments:arguments ?: @{}
|
||
|
|
];
|
||
|
|
|
||
|
|
[FBLogger verboseLog:routeParams.description];
|
||
|
|
|
||
|
|
@try {
|
||
|
|
[route mountRequest:routeParams intoResponse:response];
|
||
|
|
}
|
||
|
|
@catch (NSException *exception) {
|
||
|
|
[self handleException:exception forResponse:response];
|
||
|
|
}
|
||
|
|
}];
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
- (void)handleException:(NSException *)exception forResponse:(RouteResponse *)response
|
||
|
|
{
|
||
|
|
[self.exceptionHandler handleException:exception forResponse:response];
|
||
|
|
}
|
||
|
|
|
||
|
|
- (void)registerServerKeyRouteHandlers
|
||
|
|
{
|
||
|
|
[self.server get:@"/health" withBlock:^(RouteRequest *request, RouteResponse *response) {
|
||
|
|
[response respondWithString:@"<!DOCTYPE html><html><title>Health Check</title><body><p>I-AM-ALIVE</p></body></html>"];
|
||
|
|
}];
|
||
|
|
|
||
|
|
NSString *calibrationPage = @"<html>"
|
||
|
|
"<title>{\"x\":null,\"y\":null}</title>"
|
||
|
|
"<header>"
|
||
|
|
"<script>document.addEventListener(\"click\",function(e){document.title=JSON.stringify({x:e.clientX,y:e.clientY})})</script>"
|
||
|
|
"</header>"
|
||
|
|
"</html>";
|
||
|
|
[self.server get:@"/calibrate" withBlock:^(RouteRequest *request, RouteResponse *response) {
|
||
|
|
[response respondWithString:calibrationPage];
|
||
|
|
}];
|
||
|
|
|
||
|
|
[self.server get:@"/wda/shutdown" withBlock:^(RouteRequest *request, RouteResponse *response) {
|
||
|
|
[response respondWithString:@"Shutting down"];
|
||
|
|
[self.delegate webServerDidRequestShutdown:self];
|
||
|
|
}];
|
||
|
|
|
||
|
|
[self registerRouteHandlers:@[FBUnknownCommands.class]];
|
||
|
|
}
|
||
|
|
|
||
|
|
@end
|