RoutingHTTPServer.m 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303
  1. #import "RoutingHTTPServer.h"
  2. #import "RoutingConnection.h"
  3. #import "Route.h"
  4. #pragma clang diagnostic ignored "-Wdirect-ivar-access"
  5. #pragma clang diagnostic ignored "-Widiomatic-parentheses"
  6. @implementation RoutingHTTPServer {
  7. NSMutableDictionary *routes;
  8. NSMutableDictionary *defaultHeaders;
  9. NSMutableDictionary *mimeTypes;
  10. dispatch_queue_t routeQueue;
  11. }
  12. @synthesize defaultHeaders;
  13. - (id)init {
  14. if (self = [super init]) {
  15. connectionClass = [RoutingConnection self];
  16. routes = [[NSMutableDictionary alloc] init];
  17. defaultHeaders = [[NSMutableDictionary alloc] init];
  18. [self setupMIMETypes];
  19. }
  20. return self;
  21. }
  22. #if !OS_OBJECT_USE_OBJC_RETAIN_RELEASE
  23. - (void)dealloc {
  24. if (routeQueue)
  25. dispatch_release(routeQueue);
  26. }
  27. #endif
  28. - (void)setDefaultHeaders:(NSDictionary *)headers {
  29. if (headers) {
  30. defaultHeaders = [headers mutableCopy];
  31. } else {
  32. defaultHeaders = [[NSMutableDictionary alloc] init];
  33. }
  34. }
  35. - (void)setDefaultHeader:(NSString *)field value:(NSString *)value {
  36. [defaultHeaders setObject:value forKey:field];
  37. }
  38. - (dispatch_queue_t)routeQueue {
  39. return routeQueue;
  40. }
  41. - (void)setRouteQueue:(dispatch_queue_t)queue {
  42. #if !OS_OBJECT_USE_OBJC_RETAIN_RELEASE
  43. if (queue)
  44. dispatch_retain(queue);
  45. if (routeQueue)
  46. dispatch_release(routeQueue);
  47. #endif
  48. routeQueue = queue;
  49. }
  50. - (NSDictionary *)mimeTypes {
  51. return mimeTypes;
  52. }
  53. - (void)setMIMETypes:(NSDictionary *)types {
  54. NSMutableDictionary *newTypes;
  55. if (types) {
  56. newTypes = [types mutableCopy];
  57. } else {
  58. newTypes = [[NSMutableDictionary alloc] init];
  59. }
  60. mimeTypes = newTypes;
  61. }
  62. - (void)setMIMEType:(NSString *)theType forExtension:(NSString *)ext {
  63. [mimeTypes setObject:theType forKey:ext];
  64. }
  65. - (NSString *)mimeTypeForPath:(NSString *)path {
  66. NSString *ext = [[path pathExtension] lowercaseString];
  67. if (!ext || [ext length] < 1)
  68. return nil;
  69. return [mimeTypes objectForKey:ext];
  70. }
  71. - (void)get:(NSString *)path withBlock:(RequestHandler)block {
  72. [self handleMethod:@"GET" withPath:path block:block];
  73. }
  74. - (void)post:(NSString *)path withBlock:(RequestHandler)block {
  75. [self handleMethod:@"POST" withPath:path block:block];
  76. }
  77. - (void)put:(NSString *)path withBlock:(RequestHandler)block {
  78. [self handleMethod:@"PUT" withPath:path block:block];
  79. }
  80. - (void)delete:(NSString *)path withBlock:(RequestHandler)block {
  81. [self handleMethod:@"DELETE" withPath:path block:block];
  82. }
  83. - (void)handleMethod:(NSString *)method
  84. withPath:(NSString *)path
  85. block:(RequestHandler)block {
  86. Route *route = [self routeWithPath:path];
  87. route.handler = block;
  88. [self addRoute:route forMethod:method];
  89. }
  90. - (void)handleMethod:(NSString *)method
  91. withPath:(NSString *)path
  92. target:(id)target
  93. selector:(SEL)selector {
  94. Route *route = [self routeWithPath:path];
  95. route.target = target;
  96. route.selector = selector;
  97. [self addRoute:route forMethod:method];
  98. }
  99. - (void)addRoute:(Route *)route forMethod:(NSString *)method {
  100. method = [method uppercaseString];
  101. NSMutableArray *methodRoutes = [routes objectForKey:method];
  102. if (methodRoutes == nil) {
  103. methodRoutes = [NSMutableArray array];
  104. [routes setObject:methodRoutes forKey:method];
  105. }
  106. [methodRoutes addObject:route];
  107. // Define a HEAD route for all GET routes
  108. if ([method isEqualToString:@"GET"]) {
  109. [self addRoute:route forMethod:@"HEAD"];
  110. }
  111. }
  112. - (Route *)routeWithPath:(NSString *)path {
  113. Route *route = [[Route alloc] init];
  114. NSMutableArray *keys = [NSMutableArray array];
  115. if ([path length] > 2 && [path characterAtIndex:0] == '{') {
  116. // This is a custom regular expression, just remove the {}
  117. path = [path substringWithRange:NSMakeRange(1, [path length] - 2)];
  118. } else {
  119. NSRegularExpression *regex = nil;
  120. // Escape regex characters
  121. regex = [NSRegularExpression regularExpressionWithPattern:@"[.+()]" options:0 error:nil];
  122. path = [regex stringByReplacingMatchesInString:path options:0 range:NSMakeRange(0, path.length) withTemplate:@"\\\\$0"];
  123. // Parse any :parameters and * in the path
  124. regex = [NSRegularExpression regularExpressionWithPattern:@"(:(\\w+)|\\*)"
  125. options:0
  126. error:nil];
  127. NSMutableString *regexPath = [NSMutableString stringWithString:path];
  128. __block NSInteger diff = 0;
  129. [regex enumerateMatchesInString:path options:0 range:NSMakeRange(0, path.length)
  130. usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) {
  131. NSRange replacementRange = NSMakeRange(diff + result.range.location, result.range.length);
  132. NSString *replacementString;
  133. NSString *capturedString = [path substringWithRange:result.range];
  134. if ([capturedString isEqualToString:@"*"]) {
  135. [keys addObject:@"wildcards"];
  136. replacementString = @"(.*?)";
  137. } else {
  138. NSString *keyString = [path substringWithRange:[result rangeAtIndex:2]];
  139. [keys addObject:keyString];
  140. replacementString = @"([^/]+)";
  141. }
  142. [regexPath replaceCharactersInRange:replacementRange withString:replacementString];
  143. diff += replacementString.length - result.range.length;
  144. }];
  145. path = [NSString stringWithFormat:@"^%@$", regexPath];
  146. }
  147. route.regex = [NSRegularExpression regularExpressionWithPattern:path options:NSRegularExpressionCaseInsensitive error:nil];
  148. if ([keys count] > 0) {
  149. route.keys = keys;
  150. }
  151. return route;
  152. }
  153. - (BOOL)supportsMethod:(NSString *)method {
  154. return ([routes objectForKey:method] != nil);
  155. }
  156. - (void)handleRoute:(Route *)route
  157. withRequest:(RouteRequest *)request
  158. response:(RouteResponse *)response {
  159. if (route.handler) {
  160. route.handler(request, response);
  161. } else {
  162. id target = route.target;
  163. SEL selector = route.selector;
  164. NSMethodSignature *signature = [target methodSignatureForSelector:selector];
  165. NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
  166. [invocation setSelector:selector];
  167. [invocation setArgument:&request atIndex:2];
  168. [invocation setArgument:&response atIndex:3];
  169. [invocation invokeWithTarget:target];
  170. }
  171. }
  172. - (RouteResponse *)routeMethod:(NSString *)method
  173. withPath:(NSString *)path
  174. parameters:(NSDictionary *)params
  175. request:(HTTPMessage *)httpMessage
  176. connection:(HTTPConnection *)connection {
  177. NSMutableArray *methodRoutes = [routes objectForKey:method];
  178. if (methodRoutes == nil)
  179. return nil;
  180. for (Route *route in methodRoutes) {
  181. NSTextCheckingResult *result = [route.regex firstMatchInString:path options:0 range:NSMakeRange(0, path.length)];
  182. if (!result)
  183. continue;
  184. // The first range is all of the text matched by the regex.
  185. NSUInteger captureCount = [result numberOfRanges];
  186. if (route.keys) {
  187. // Add the route's parameters to the parameter dictionary, accounting for
  188. // the first range containing the matched text.
  189. if (captureCount == [route.keys count] + 1) {
  190. NSMutableDictionary *newParams = [params mutableCopy];
  191. NSUInteger index = 1;
  192. BOOL firstWildcard = YES;
  193. for (NSString *key in route.keys) {
  194. NSString *capture = [path substringWithRange:[result rangeAtIndex:index]];
  195. if ([key isEqualToString:@"wildcards"]) {
  196. NSMutableArray *wildcards = [newParams objectForKey:key];
  197. if (firstWildcard) {
  198. // Create a new array and replace any existing object with the same key
  199. wildcards = [NSMutableArray array];
  200. [newParams setObject:wildcards forKey:key];
  201. firstWildcard = NO;
  202. }
  203. [wildcards addObject:capture];
  204. } else {
  205. [newParams setObject:capture forKey:key];
  206. }
  207. index++;
  208. }
  209. params = newParams;
  210. }
  211. } else if (captureCount > 1) {
  212. // For custom regular expressions place the anonymous captures in the captures parameter
  213. NSMutableDictionary *newParams = [params mutableCopy];
  214. NSMutableArray *captures = [NSMutableArray array];
  215. for (NSUInteger i = 1; i < captureCount; i++) {
  216. [captures addObject:[path substringWithRange:[result rangeAtIndex:i]]];
  217. }
  218. [newParams setObject:captures forKey:@"captures"];
  219. params = newParams;
  220. }
  221. RouteRequest *request = [[RouteRequest alloc] initWithHTTPMessage:httpMessage parameters:params];
  222. RouteResponse *response = [[RouteResponse alloc] initWithConnection:connection];
  223. if (!routeQueue) {
  224. [self handleRoute:route withRequest:request response:response];
  225. } else {
  226. // Process the route on the specified queue
  227. dispatch_sync(routeQueue, ^{
  228. @autoreleasepool {
  229. [self handleRoute:route withRequest:request response:response];
  230. }
  231. });
  232. }
  233. return response;
  234. }
  235. return nil;
  236. }
  237. - (void)setupMIMETypes {
  238. mimeTypes = [[NSMutableDictionary alloc] initWithObjectsAndKeys:
  239. @"application/x-javascript", @"js",
  240. @"image/gif", @"gif",
  241. @"image/jpeg", @"jpg",
  242. @"image/jpeg", @"jpeg",
  243. @"image/png", @"png",
  244. @"image/svg+xml", @"svg",
  245. @"image/tiff", @"tif",
  246. @"image/tiff", @"tiff",
  247. @"image/x-icon", @"ico",
  248. @"image/x-ms-bmp", @"bmp",
  249. @"text/css", @"css",
  250. @"text/html", @"html",
  251. @"text/html", @"htm",
  252. @"text/plain", @"txt",
  253. @"text/xml", @"xml",
  254. nil];
  255. }
  256. @end