| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303 |
- #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
|