初始化提交
This commit is contained in:
303
WebDriverAgentLib/Vendor/RoutingHTTPServer/RoutingHTTPServer.m
vendored
Normal file
303
WebDriverAgentLib/Vendor/RoutingHTTPServer/RoutingHTTPServer.m
vendored
Normal file
@@ -0,0 +1,303 @@
|
||||
#import "RoutingHTTPServer.h"
|
||||
#import "RoutingConnection.h"
|
||||
#import "Route.h"
|
||||
|
||||
#pragma clang diagnostic ignored "-Wdirect-ivar-access"
|
||||
#pragma clang diagnostic ignored "-Widiomatic-parentheses"
|
||||
|
||||
@implementation RoutingHTTPServer {
|
||||
NSMutableDictionary *routes;
|
||||
NSMutableDictionary *defaultHeaders;
|
||||
NSMutableDictionary *mimeTypes;
|
||||
dispatch_queue_t routeQueue;
|
||||
}
|
||||
|
||||
@synthesize defaultHeaders;
|
||||
|
||||
- (id)init {
|
||||
if (self = [super init]) {
|
||||
connectionClass = [RoutingConnection self];
|
||||
routes = [[NSMutableDictionary alloc] init];
|
||||
defaultHeaders = [[NSMutableDictionary alloc] init];
|
||||
[self setupMIMETypes];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
#if !OS_OBJECT_USE_OBJC_RETAIN_RELEASE
|
||||
- (void)dealloc {
|
||||
if (routeQueue)
|
||||
dispatch_release(routeQueue);
|
||||
}
|
||||
#endif
|
||||
|
||||
- (void)setDefaultHeaders:(NSDictionary *)headers {
|
||||
if (headers) {
|
||||
defaultHeaders = [headers mutableCopy];
|
||||
} else {
|
||||
defaultHeaders = [[NSMutableDictionary alloc] init];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setDefaultHeader:(NSString *)field value:(NSString *)value {
|
||||
[defaultHeaders setObject:value forKey:field];
|
||||
}
|
||||
|
||||
- (dispatch_queue_t)routeQueue {
|
||||
return routeQueue;
|
||||
}
|
||||
|
||||
- (void)setRouteQueue:(dispatch_queue_t)queue {
|
||||
#if !OS_OBJECT_USE_OBJC_RETAIN_RELEASE
|
||||
if (queue)
|
||||
dispatch_retain(queue);
|
||||
|
||||
if (routeQueue)
|
||||
dispatch_release(routeQueue);
|
||||
#endif
|
||||
|
||||
routeQueue = queue;
|
||||
}
|
||||
|
||||
- (NSDictionary *)mimeTypes {
|
||||
return mimeTypes;
|
||||
}
|
||||
|
||||
- (void)setMIMETypes:(NSDictionary *)types {
|
||||
NSMutableDictionary *newTypes;
|
||||
if (types) {
|
||||
newTypes = [types mutableCopy];
|
||||
} else {
|
||||
newTypes = [[NSMutableDictionary alloc] init];
|
||||
}
|
||||
|
||||
mimeTypes = newTypes;
|
||||
}
|
||||
|
||||
- (void)setMIMEType:(NSString *)theType forExtension:(NSString *)ext {
|
||||
[mimeTypes setObject:theType forKey:ext];
|
||||
}
|
||||
|
||||
- (NSString *)mimeTypeForPath:(NSString *)path {
|
||||
NSString *ext = [[path pathExtension] lowercaseString];
|
||||
if (!ext || [ext length] < 1)
|
||||
return nil;
|
||||
|
||||
return [mimeTypes objectForKey:ext];
|
||||
}
|
||||
|
||||
- (void)get:(NSString *)path withBlock:(RequestHandler)block {
|
||||
[self handleMethod:@"GET" withPath:path block:block];
|
||||
}
|
||||
|
||||
- (void)post:(NSString *)path withBlock:(RequestHandler)block {
|
||||
[self handleMethod:@"POST" withPath:path block:block];
|
||||
}
|
||||
|
||||
- (void)put:(NSString *)path withBlock:(RequestHandler)block {
|
||||
[self handleMethod:@"PUT" withPath:path block:block];
|
||||
}
|
||||
|
||||
- (void)delete:(NSString *)path withBlock:(RequestHandler)block {
|
||||
[self handleMethod:@"DELETE" withPath:path block:block];
|
||||
}
|
||||
|
||||
- (void)handleMethod:(NSString *)method
|
||||
withPath:(NSString *)path
|
||||
block:(RequestHandler)block {
|
||||
Route *route = [self routeWithPath:path];
|
||||
route.handler = block;
|
||||
|
||||
[self addRoute:route forMethod:method];
|
||||
}
|
||||
|
||||
- (void)handleMethod:(NSString *)method
|
||||
withPath:(NSString *)path
|
||||
target:(id)target
|
||||
selector:(SEL)selector {
|
||||
Route *route = [self routeWithPath:path];
|
||||
route.target = target;
|
||||
route.selector = selector;
|
||||
|
||||
[self addRoute:route forMethod:method];
|
||||
}
|
||||
|
||||
- (void)addRoute:(Route *)route forMethod:(NSString *)method {
|
||||
method = [method uppercaseString];
|
||||
NSMutableArray *methodRoutes = [routes objectForKey:method];
|
||||
if (methodRoutes == nil) {
|
||||
methodRoutes = [NSMutableArray array];
|
||||
[routes setObject:methodRoutes forKey:method];
|
||||
}
|
||||
|
||||
[methodRoutes addObject:route];
|
||||
|
||||
// Define a HEAD route for all GET routes
|
||||
if ([method isEqualToString:@"GET"]) {
|
||||
[self addRoute:route forMethod:@"HEAD"];
|
||||
}
|
||||
}
|
||||
|
||||
- (Route *)routeWithPath:(NSString *)path {
|
||||
Route *route = [[Route alloc] init];
|
||||
NSMutableArray *keys = [NSMutableArray array];
|
||||
|
||||
if ([path length] > 2 && [path characterAtIndex:0] == '{') {
|
||||
// This is a custom regular expression, just remove the {}
|
||||
path = [path substringWithRange:NSMakeRange(1, [path length] - 2)];
|
||||
} else {
|
||||
NSRegularExpression *regex = nil;
|
||||
|
||||
// Escape regex characters
|
||||
regex = [NSRegularExpression regularExpressionWithPattern:@"[.+()]" options:0 error:nil];
|
||||
path = [regex stringByReplacingMatchesInString:path options:0 range:NSMakeRange(0, path.length) withTemplate:@"\\\\$0"];
|
||||
|
||||
// Parse any :parameters and * in the path
|
||||
regex = [NSRegularExpression regularExpressionWithPattern:@"(:(\\w+)|\\*)"
|
||||
options:0
|
||||
error:nil];
|
||||
NSMutableString *regexPath = [NSMutableString stringWithString:path];
|
||||
__block NSInteger diff = 0;
|
||||
[regex enumerateMatchesInString:path options:0 range:NSMakeRange(0, path.length)
|
||||
usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) {
|
||||
NSRange replacementRange = NSMakeRange(diff + result.range.location, result.range.length);
|
||||
NSString *replacementString;
|
||||
|
||||
NSString *capturedString = [path substringWithRange:result.range];
|
||||
if ([capturedString isEqualToString:@"*"]) {
|
||||
[keys addObject:@"wildcards"];
|
||||
replacementString = @"(.*?)";
|
||||
} else {
|
||||
NSString *keyString = [path substringWithRange:[result rangeAtIndex:2]];
|
||||
[keys addObject:keyString];
|
||||
replacementString = @"([^/]+)";
|
||||
}
|
||||
|
||||
[regexPath replaceCharactersInRange:replacementRange withString:replacementString];
|
||||
diff += replacementString.length - result.range.length;
|
||||
}];
|
||||
|
||||
path = [NSString stringWithFormat:@"^%@$", regexPath];
|
||||
}
|
||||
|
||||
route.regex = [NSRegularExpression regularExpressionWithPattern:path options:NSRegularExpressionCaseInsensitive error:nil];
|
||||
if ([keys count] > 0) {
|
||||
route.keys = keys;
|
||||
}
|
||||
|
||||
return route;
|
||||
}
|
||||
|
||||
- (BOOL)supportsMethod:(NSString *)method {
|
||||
return ([routes objectForKey:method] != nil);
|
||||
}
|
||||
|
||||
- (void)handleRoute:(Route *)route
|
||||
withRequest:(RouteRequest *)request
|
||||
response:(RouteResponse *)response {
|
||||
if (route.handler) {
|
||||
route.handler(request, response);
|
||||
} else {
|
||||
id target = route.target;
|
||||
SEL selector = route.selector;
|
||||
NSMethodSignature *signature = [target methodSignatureForSelector:selector];
|
||||
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
|
||||
[invocation setSelector:selector];
|
||||
[invocation setArgument:&request atIndex:2];
|
||||
[invocation setArgument:&response atIndex:3];
|
||||
[invocation invokeWithTarget:target];
|
||||
}
|
||||
}
|
||||
|
||||
- (RouteResponse *)routeMethod:(NSString *)method
|
||||
withPath:(NSString *)path
|
||||
parameters:(NSDictionary *)params
|
||||
request:(HTTPMessage *)httpMessage
|
||||
connection:(HTTPConnection *)connection {
|
||||
NSMutableArray *methodRoutes = [routes objectForKey:method];
|
||||
if (methodRoutes == nil)
|
||||
return nil;
|
||||
|
||||
for (Route *route in methodRoutes) {
|
||||
NSTextCheckingResult *result = [route.regex firstMatchInString:path options:0 range:NSMakeRange(0, path.length)];
|
||||
if (!result)
|
||||
continue;
|
||||
|
||||
// The first range is all of the text matched by the regex.
|
||||
NSUInteger captureCount = [result numberOfRanges];
|
||||
|
||||
if (route.keys) {
|
||||
// Add the route's parameters to the parameter dictionary, accounting for
|
||||
// the first range containing the matched text.
|
||||
if (captureCount == [route.keys count] + 1) {
|
||||
NSMutableDictionary *newParams = [params mutableCopy];
|
||||
NSUInteger index = 1;
|
||||
BOOL firstWildcard = YES;
|
||||
for (NSString *key in route.keys) {
|
||||
NSString *capture = [path substringWithRange:[result rangeAtIndex:index]];
|
||||
if ([key isEqualToString:@"wildcards"]) {
|
||||
NSMutableArray *wildcards = [newParams objectForKey:key];
|
||||
if (firstWildcard) {
|
||||
// Create a new array and replace any existing object with the same key
|
||||
wildcards = [NSMutableArray array];
|
||||
[newParams setObject:wildcards forKey:key];
|
||||
firstWildcard = NO;
|
||||
}
|
||||
[wildcards addObject:capture];
|
||||
} else {
|
||||
[newParams setObject:capture forKey:key];
|
||||
}
|
||||
index++;
|
||||
}
|
||||
params = newParams;
|
||||
}
|
||||
} else if (captureCount > 1) {
|
||||
// For custom regular expressions place the anonymous captures in the captures parameter
|
||||
NSMutableDictionary *newParams = [params mutableCopy];
|
||||
NSMutableArray *captures = [NSMutableArray array];
|
||||
for (NSUInteger i = 1; i < captureCount; i++) {
|
||||
[captures addObject:[path substringWithRange:[result rangeAtIndex:i]]];
|
||||
}
|
||||
[newParams setObject:captures forKey:@"captures"];
|
||||
params = newParams;
|
||||
}
|
||||
|
||||
RouteRequest *request = [[RouteRequest alloc] initWithHTTPMessage:httpMessage parameters:params];
|
||||
RouteResponse *response = [[RouteResponse alloc] initWithConnection:connection];
|
||||
if (!routeQueue) {
|
||||
[self handleRoute:route withRequest:request response:response];
|
||||
} else {
|
||||
// Process the route on the specified queue
|
||||
dispatch_sync(routeQueue, ^{
|
||||
@autoreleasepool {
|
||||
[self handleRoute:route withRequest:request response:response];
|
||||
}
|
||||
});
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (void)setupMIMETypes {
|
||||
mimeTypes = [[NSMutableDictionary alloc] initWithObjectsAndKeys:
|
||||
@"application/x-javascript", @"js",
|
||||
@"image/gif", @"gif",
|
||||
@"image/jpeg", @"jpg",
|
||||
@"image/jpeg", @"jpeg",
|
||||
@"image/png", @"png",
|
||||
@"image/svg+xml", @"svg",
|
||||
@"image/tiff", @"tif",
|
||||
@"image/tiff", @"tiff",
|
||||
@"image/x-icon", @"ico",
|
||||
@"image/x-ms-bmp", @"bmp",
|
||||
@"text/css", @"css",
|
||||
@"text/html", @"html",
|
||||
@"text/html", @"htm",
|
||||
@"text/plain", @"txt",
|
||||
@"text/xml", @"xml",
|
||||
nil];
|
||||
}
|
||||
|
||||
@end
|
||||
Reference in New Issue
Block a user