Skip to content

Instantly share code, notes, and snippets.

@mAu888
Forked from Nub/HTTPRouter.h
Created July 13, 2012 12:45
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mAu888/3104696 to your computer and use it in GitHub Desktop.
Save mAu888/3104696 to your computer and use it in GitHub Desktop.
HTTPRouter for CocoaHTTPServer
//
// HTTPRouter.h
// DynamicServer
//
// Created by Zachry Thayer on 5/9/12.
// Copyright (c) 2012 Zachry Thayer. All rights reserved.
//
#import "HTTPConnection.h"
#import "HTTPMessage.h"
@interface HTTPMessage (HTTPRouterMessage)
@property (nonatomic, copy) NSMutableDictionary* params;
@end
typedef id (^HTTPRouterBlock)(HTTPMessage* message);
@interface HTTPRouter : HTTPConnection
// :var = keyed variable
+ (void)handleRoute:(NSString*)route withBlock:(HTTPRouterBlock)routeBlock;
+ (void)handleRoute:(NSString*)route withRouter:(HTTPRouter*)router;
@end
//
// HTTPRouter.m
// CocoaHTTPServer
//
// Created by Zachry Thayer on 5/9/12.
// Copyright (c) 2012 Zachry Thayer. All rights reserved.
//
#import "HTTPRouter.h"
#import "HTTPDataResponse.h"
#import "HTTPLogging.h"
#import <objc/runtime.h>
// Patch HTTPMessage to have params for the query and route parameters
static char * const HTTPRouterMessageKey = "HTTPRouterMessageKey";
@implementation HTTPMessage (HTTPRouterMessage)
- (void)setParams:(NSMutableDictionary *)newParams
{
objc_setAssociatedObject(self, HTTPRouterMessageKey, newParams, OBJC_ASSOCIATION_COPY);
}
- (NSMutableDictionary*)params
{
return objc_getAssociatedObject(self, HTTPRouterMessageKey);
}
@end
//Eeek! Globals!
NSMutableDictionary* HTTPRouterRoutes;
//BOOL HTTPRouterInitialized = NO;
@interface HTTPRouter ()
//Magic alias!
@property (nonatomic, strong)NSMutableDictionary* routes;
//Helpers
+ (void)prepareRoute:(NSString*)route;
- (HTTPDataResponse*)processResponse:(id)response;
@end
@implementation HTTPRouter
@synthesize routes;
#pragma mark method overides
- (NSObject<HTTPResponse> *)httpResponseForMethod:(NSString *)method URI:(NSString *)path
{
// Find best matched route to request
NSString* basePath = [[path componentsSeparatedByString:@"?"] objectAtIndex:0];
//NSLog(@"Searching for path match to %@", basePath);
for (NSString* key in self.routes)
{
NSString* predicatePath = [key stringByAppendingString:@".predicate"];
NSPredicate *uriMatcher = [NSPredicate predicateWithFormat:[self.routes valueForKeyPath:predicatePath]];
BOOL uriMatch = [uriMatcher evaluateWithObject:basePath];
if (uriMatch) // Handle Match
{
// NSLog(@"Matched URI %@ with route %@", basePath, key);
NSString* blockPath = [key stringByAppendingString:@".block"];
HTTPRouterBlock block = [self.routes valueForKeyPath:blockPath];
request.params = [self parseRouteParams:key withURI:basePath];
id response = block(request);
//NSLog(@"Response:%@", response);
return [self processResponse:response];
}
}
// Default response
return [super httpResponseForMethod:method URI:path];
}
- (NSMutableDictionary *)parseRouteParams:(NSString*)route withURI:(NSString*)path
{
NSMutableDictionary* paramDictionary = [NSMutableDictionary dictionaryWithDictionary:[self parseGetParams]];
NSMutableArray* components = [NSMutableArray arrayWithArray:[[path substringFromIndex:1] componentsSeparatedByString:@"/"]];
NSMutableDictionary* routeDictionary = [self.routes valueForKey:route];
NSDictionary* paramIndices = [routeDictionary valueForKey:@"paramIndices"];
NSArray *keys = [self.routes keysSortedByValueUsingComparator:^(id obj1, id obj2) {
int n1 = [[(NSDictionary *)obj1 valueForKey:@"specifity"] intValue];
int n2 = [[(NSDictionary *)obj2 valueForKey:@"specifity"] intValue];
if (n1 == n2) {
return NSOrderedSame;
}
else if (n1 < n2) {
return NSOrderedAscending;
}
else {
return NSOrderedDescending;
}
}];
for (NSString* key in keys)
{
NSString* value = [components objectAtIndex:[[paramIndices valueForKey:key] integerValue]];
[paramDictionary setValue:value forKey:key];
}
return paramDictionary;
}
#pragma mark route management
+ (void)handleRoute:(NSString*)route withBlock:(HTTPRouterBlock)routeBlock
{
if (!HTTPRouterRoutes)
{
HTTPRouterRoutes = [NSMutableDictionary dictionary];
}
//Add route
NSMutableDictionary* routeDictionary = [NSMutableDictionary dictionary];
//[routeDictionary setValue:(__bridge_transfer id)Block_copy((__bridge void *)routeBlock) forKey:@"block"];
[routeDictionary setValue:[NSNumber numberWithInt:[HTTPRouterRoutes count]] forKey:@"specifity"];
[routeDictionary setValue:[routeBlock copy] forKey:@"block"];
[HTTPRouterRoutes setValue:routeDictionary forKey:route];
[self prepareRoute:route];
}
+ (void)handleRoute:(NSString*)route withRouter:(HTTPRouter*)router
{
if (!HTTPRouterRoutes)
{
HTTPRouterRoutes = [NSMutableDictionary dictionary];
}
//Add route
NSMutableDictionary* routeDictionary = [NSMutableDictionary dictionary];
[routeDictionary setValue:[NSNumber numberWithInt:[HTTPRouterRoutes count]] forKey:@"specifity"];
[routeDictionary setValue:router forKey:@"router"];
[HTTPRouterRoutes setValue:routeDictionary forKey:route];
[self prepareRoute:route];
}
#pragma mark Accessor
- (NSMutableDictionary*)routes
{
if (!HTTPRouterRoutes)
{
HTTPRouterRoutes = [NSMutableDictionary dictionary];
}
return HTTPRouterRoutes;
}
#pragma mark Helpers
+ (void)prepareRoute:(NSString*)route
{
NSMutableDictionary* routeDictionary = [HTTPRouterRoutes valueForKey:route];
NSMutableArray* components = [NSMutableArray arrayWithArray:[route componentsSeparatedByString:@"/"]];
NSMutableString *regex = [NSMutableString stringWithFormat:@""];
NSInteger index = 0;
for (NSString *component in components)
{
if ([component characterAtIndex:0] == ':')//Found parameter
{
//Prepare components to become a regex string
//[components replaceObjectAtIndex:index withObject:@".*?\/"];
[regex appendFormat:@".*\/?"];
//Get parameter dictionary or create and add
NSMutableDictionary* parameters = [routeDictionary valueForKey:@"paramIndices"];
if (!parameters)
{
parameters = [NSMutableDictionary dictionary];
[routeDictionary setValue:parameters forKey:@"paramIndices"];
}
//Insert the route component index for later easy fetching
NSNumber* paramIndex = [NSNumber numberWithInteger:index];
NSString* paramKey = [component substringFromIndex:1];//Skip ':'
[parameters setValue:paramIndex forKey:paramKey];
}
else
{
[regex appendFormat:@"\/%@", component];
}
index++;
}
//NSLog(@"Predicate Regex: %@", regex);
NSString* predicate = [NSString stringWithFormat:@"SELF MATCHES '%@\/?'", regex];//skip first /
[routeDictionary setValue:predicate forKey:@"predicate"];
}
- (HTTPDataResponse*)processResponse:(id)response
{
HTTPDataResponse *dataResponse = nil;
if ([response isKindOfClass:[NSString class]])
{
dataResponse = [[HTTPDataResponse alloc] initWithData:[response dataUsingEncoding:NSUTF8StringEncoding]];
}
if ([response isKindOfClass:[NSData class]])
{
dataResponse = [[HTTPDataResponse alloc] initWithData:response];
}
if ([response isKindOfClass:[NSDictionary class]] || [response isKindOfClass:[NSArray class]])
{
dataResponse = [[HTTPDataResponse alloc] initWithData:[NSJSONSerialization dataWithJSONObject:response options:0 error:nil]];
}
return dataResponse;
}
@end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment