初始化提交
This commit is contained in:
13
WebDriverAgentLib/Vendor/RoutingHTTPServer/HTTPResponseProxy.h
vendored
Normal file
13
WebDriverAgentLib/Vendor/RoutingHTTPServer/HTTPResponseProxy.h
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "HTTPResponse.h"
|
||||
|
||||
// Wraps an HTTPResponse object to allow setting a custom status code
|
||||
// without needing to create subclasses of every response.
|
||||
@interface HTTPResponseProxy : NSObject <HTTPResponse>
|
||||
|
||||
@property (nonatomic) NSObject<HTTPResponse> *response;
|
||||
@property (nonatomic) NSInteger status;
|
||||
|
||||
- (NSInteger)customStatus;
|
||||
|
||||
@end
|
||||
84
WebDriverAgentLib/Vendor/RoutingHTTPServer/HTTPResponseProxy.m
vendored
Normal file
84
WebDriverAgentLib/Vendor/RoutingHTTPServer/HTTPResponseProxy.m
vendored
Normal file
@@ -0,0 +1,84 @@
|
||||
#import "HTTPResponseProxy.h"
|
||||
|
||||
#pragma clang diagnostic ignored "-Wdirect-ivar-access"
|
||||
|
||||
@implementation HTTPResponseProxy
|
||||
|
||||
@synthesize response;
|
||||
@synthesize status;
|
||||
|
||||
- (NSInteger)status {
|
||||
if (status != 0) {
|
||||
return status;
|
||||
} else if ([response respondsToSelector:@selector(status)]) {
|
||||
return [response status];
|
||||
}
|
||||
|
||||
return 200;
|
||||
}
|
||||
|
||||
- (void)setStatus:(NSInteger)statusCode {
|
||||
status = statusCode;
|
||||
}
|
||||
|
||||
- (NSInteger)customStatus {
|
||||
return status;
|
||||
}
|
||||
|
||||
// Implement the required HTTPResponse methods
|
||||
- (UInt64)contentLength {
|
||||
if (response) {
|
||||
return [response contentLength];
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
- (UInt64)offset {
|
||||
if (response) {
|
||||
return [response offset];
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setOffset:(UInt64)offset {
|
||||
if (response) {
|
||||
[response setOffset:offset];
|
||||
}
|
||||
}
|
||||
|
||||
- (NSData *)readDataOfLength:(NSUInteger)length {
|
||||
if (response) {
|
||||
return [response readDataOfLength:length];
|
||||
} else {
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)isDone {
|
||||
if (response) {
|
||||
return [response isDone];
|
||||
} else {
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
|
||||
// Forward all other invocations to the actual response object
|
||||
- (void)forwardInvocation:(NSInvocation *)invocation {
|
||||
if ([response respondsToSelector:[invocation selector]]) {
|
||||
[invocation invokeWithTarget:response];
|
||||
} else {
|
||||
[super forwardInvocation:invocation];
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)respondsToSelector:(SEL)selector {
|
||||
if ([super respondsToSelector:selector])
|
||||
return YES;
|
||||
|
||||
return [response respondsToSelector:selector];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
19
WebDriverAgentLib/Vendor/RoutingHTTPServer/LICENSE
vendored
Normal file
19
WebDriverAgentLib/Vendor/RoutingHTTPServer/LICENSE
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
Copyright (c) 2011 Matt Stevens
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
18
WebDriverAgentLib/Vendor/RoutingHTTPServer/Route.h
vendored
Normal file
18
WebDriverAgentLib/Vendor/RoutingHTTPServer/Route.h
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "RoutingHTTPServer.h"
|
||||
|
||||
@interface Route : NSObject
|
||||
|
||||
@property (nonatomic) NSRegularExpression *regex;
|
||||
@property (nonatomic, copy) RequestHandler handler;
|
||||
|
||||
#if __has_feature(objc_arc_weak)
|
||||
@property (nonatomic, weak) id target;
|
||||
#else
|
||||
@property (nonatomic, assign) id target;
|
||||
#endif
|
||||
|
||||
@property (nonatomic, assign) SEL selector;
|
||||
@property (nonatomic) NSArray *keys;
|
||||
|
||||
@end
|
||||
11
WebDriverAgentLib/Vendor/RoutingHTTPServer/Route.m
vendored
Normal file
11
WebDriverAgentLib/Vendor/RoutingHTTPServer/Route.m
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
#import "Route.h"
|
||||
|
||||
@implementation Route
|
||||
|
||||
@synthesize regex;
|
||||
@synthesize handler;
|
||||
@synthesize target;
|
||||
@synthesize selector;
|
||||
@synthesize keys;
|
||||
|
||||
@end
|
||||
16
WebDriverAgentLib/Vendor/RoutingHTTPServer/RouteRequest.h
vendored
Normal file
16
WebDriverAgentLib/Vendor/RoutingHTTPServer/RouteRequest.h
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
@class HTTPMessage;
|
||||
|
||||
@interface RouteRequest : NSObject
|
||||
|
||||
@property (nonatomic, readonly) NSDictionary *headers;
|
||||
@property (nonatomic, readonly) NSDictionary *params;
|
||||
|
||||
- (id)initWithHTTPMessage:(HTTPMessage *)msg parameters:(NSDictionary *)params;
|
||||
- (NSString *)header:(NSString *)field;
|
||||
- (id)param:(NSString *)name;
|
||||
- (NSString *)method;
|
||||
- (NSURL *)url;
|
||||
- (NSData *)body;
|
||||
|
||||
@end
|
||||
50
WebDriverAgentLib/Vendor/RoutingHTTPServer/RouteRequest.m
vendored
Normal file
50
WebDriverAgentLib/Vendor/RoutingHTTPServer/RouteRequest.m
vendored
Normal file
@@ -0,0 +1,50 @@
|
||||
#import "RouteRequest.h"
|
||||
#import "HTTPMessage.h"
|
||||
|
||||
#pragma clang diagnostic ignored "-Wdirect-ivar-access"
|
||||
#pragma clang diagnostic ignored "-Widiomatic-parentheses"
|
||||
|
||||
@implementation RouteRequest {
|
||||
HTTPMessage *message;
|
||||
}
|
||||
|
||||
@synthesize params;
|
||||
|
||||
- (id)initWithHTTPMessage:(HTTPMessage *)msg parameters:(NSDictionary *)parameters {
|
||||
if (self = [super init]) {
|
||||
params = parameters;
|
||||
message = msg;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSDictionary *)headers {
|
||||
return [message allHeaderFields];
|
||||
}
|
||||
|
||||
- (NSString *)header:(NSString *)field {
|
||||
return [message headerField:field];
|
||||
}
|
||||
|
||||
- (id)param:(NSString *)name {
|
||||
return [params objectForKey:name];
|
||||
}
|
||||
|
||||
- (NSString *)method {
|
||||
return [message method];
|
||||
}
|
||||
|
||||
- (NSURL *)url {
|
||||
return [message url];
|
||||
}
|
||||
|
||||
- (NSData *)body {
|
||||
return [message body];
|
||||
}
|
||||
|
||||
- (NSString *)description {
|
||||
NSData *data = [message messageData];
|
||||
return [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding];
|
||||
}
|
||||
|
||||
@end
|
||||
20
WebDriverAgentLib/Vendor/RoutingHTTPServer/RouteResponse.h
vendored
Normal file
20
WebDriverAgentLib/Vendor/RoutingHTTPServer/RouteResponse.h
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "HTTPResponse.h"
|
||||
@class HTTPConnection;
|
||||
@class HTTPResponseProxy;
|
||||
|
||||
@interface RouteResponse : NSObject
|
||||
|
||||
@property (nonatomic, unsafe_unretained, readonly) HTTPConnection *connection;
|
||||
@property (nonatomic, readonly) NSDictionary *headers;
|
||||
@property (nonatomic, strong) NSObject<HTTPResponse> *response;
|
||||
@property (nonatomic, readonly) NSObject<HTTPResponse> *proxiedResponse;
|
||||
@property (nonatomic) NSInteger statusCode;
|
||||
|
||||
- (id)initWithConnection:(HTTPConnection *)theConnection;
|
||||
- (void)setHeader:(NSString *)field value:(NSString *)value;
|
||||
- (void)respondWithString:(NSString *)string;
|
||||
- (void)respondWithString:(NSString *)string encoding:(NSStringEncoding)encoding;
|
||||
- (void)respondWithData:(NSData *)data;
|
||||
|
||||
@end
|
||||
66
WebDriverAgentLib/Vendor/RoutingHTTPServer/RouteResponse.m
vendored
Normal file
66
WebDriverAgentLib/Vendor/RoutingHTTPServer/RouteResponse.m
vendored
Normal file
@@ -0,0 +1,66 @@
|
||||
#import "RouteResponse.h"
|
||||
#import "HTTPConnection.h"
|
||||
#import "HTTPDataResponse.h"
|
||||
#import "HTTPResponseProxy.h"
|
||||
|
||||
#pragma clang diagnostic ignored "-Wdirect-ivar-access"
|
||||
#pragma clang diagnostic ignored "-Widiomatic-parentheses"
|
||||
|
||||
@implementation RouteResponse {
|
||||
NSMutableDictionary *headers;
|
||||
HTTPResponseProxy *proxy;
|
||||
}
|
||||
|
||||
@synthesize connection;
|
||||
@synthesize headers;
|
||||
|
||||
- (id)initWithConnection:(HTTPConnection *)theConnection {
|
||||
if (self = [super init]) {
|
||||
connection = theConnection;
|
||||
headers = [[NSMutableDictionary alloc] init];
|
||||
proxy = [[HTTPResponseProxy alloc] init];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSObject <HTTPResponse>*)response {
|
||||
return proxy.response;
|
||||
}
|
||||
|
||||
- (void)setResponse:(NSObject <HTTPResponse>*)response {
|
||||
proxy.response = response;
|
||||
}
|
||||
|
||||
- (NSObject <HTTPResponse>*)proxiedResponse {
|
||||
if (proxy.response != nil || proxy.customStatus != 0 || [headers count] > 0) {
|
||||
return proxy;
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (NSInteger)statusCode {
|
||||
return proxy.status;
|
||||
}
|
||||
|
||||
- (void)setStatusCode:(NSInteger)status {
|
||||
proxy.status = status;
|
||||
}
|
||||
|
||||
- (void)setHeader:(NSString *)field value:(NSString *)value {
|
||||
[headers setObject:value forKey:field];
|
||||
}
|
||||
|
||||
- (void)respondWithString:(NSString *)string {
|
||||
[self respondWithString:string encoding:NSUTF8StringEncoding];
|
||||
}
|
||||
|
||||
- (void)respondWithString:(NSString *)string encoding:(NSStringEncoding)encoding {
|
||||
[self respondWithData:[string dataUsingEncoding:encoding]];
|
||||
}
|
||||
|
||||
- (void)respondWithData:(NSData *)data {
|
||||
self.response = [[HTTPDataResponse alloc] initWithData:data];
|
||||
}
|
||||
|
||||
@end
|
||||
5
WebDriverAgentLib/Vendor/RoutingHTTPServer/RoutingConnection.h
vendored
Normal file
5
WebDriverAgentLib/Vendor/RoutingHTTPServer/RoutingConnection.h
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "HTTPConnection.h"
|
||||
|
||||
@interface RoutingConnection : HTTPConnection
|
||||
@end
|
||||
142
WebDriverAgentLib/Vendor/RoutingHTTPServer/RoutingConnection.m
vendored
Normal file
142
WebDriverAgentLib/Vendor/RoutingHTTPServer/RoutingConnection.m
vendored
Normal file
@@ -0,0 +1,142 @@
|
||||
#import "RoutingConnection.h"
|
||||
#import "RoutingHTTPServer.h"
|
||||
#import "HTTPMessage.h"
|
||||
#import "HTTPResponseProxy.h"
|
||||
|
||||
#pragma clang diagnostic ignored "-Wdirect-ivar-access"
|
||||
#pragma clang diagnostic ignored "-Widiomatic-parentheses"
|
||||
#pragma clang diagnostic ignored "-Wundeclared-selector"
|
||||
|
||||
@implementation RoutingConnection {
|
||||
__unsafe_unretained RoutingHTTPServer *http;
|
||||
NSDictionary *headers;
|
||||
}
|
||||
|
||||
- (id)initWithAsyncSocket:(GCDAsyncSocket *)newSocket configuration:(HTTPConfig *)aConfig {
|
||||
if (self = [super initWithAsyncSocket:newSocket configuration:aConfig]) {
|
||||
NSAssert([config.server isKindOfClass:[RoutingHTTPServer class]],
|
||||
@"A RoutingConnection is being used with a server that is not a %@",
|
||||
NSStringFromClass([RoutingHTTPServer class]));
|
||||
|
||||
http = (RoutingHTTPServer *)config.server;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (BOOL)supportsMethod:(NSString *)method atPath:(NSString *)path {
|
||||
|
||||
if ([http supportsMethod:method])
|
||||
return YES;
|
||||
|
||||
return [super supportsMethod:method atPath:path];
|
||||
}
|
||||
|
||||
- (BOOL)shouldHandleRequestForMethod:(NSString *)method atPath:(NSString *)path {
|
||||
// The default implementation is strict about the use of Content-Length. Either
|
||||
// a given method + path combination must *always* include data or *never*
|
||||
// include data. The routing connection is lenient, a POST that sometimes does
|
||||
// not include data or a GET that sometimes does is fine. It is up to the route
|
||||
// implementations to decide how to handle these situations.
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)processBodyData:(NSData *)postDataChunk {
|
||||
BOOL result = [request appendData:postDataChunk];
|
||||
if (!result) {
|
||||
// TODO: Log
|
||||
}
|
||||
}
|
||||
|
||||
- (NSObject<HTTPResponse> *)httpResponseForMethod:(NSString *)method URI:(NSString *)path {
|
||||
NSURL *url = [request url];
|
||||
NSString *query = nil;
|
||||
NSDictionary *params = [NSDictionary dictionary];
|
||||
headers = nil;
|
||||
|
||||
if (url) {
|
||||
path = [url path]; // Strip the query string from the path
|
||||
query = [url query];
|
||||
if (query) {
|
||||
params = [self parseParams:query];
|
||||
}
|
||||
}
|
||||
|
||||
RouteResponse *response = [http routeMethod:method withPath:path parameters:params request:request connection:self];
|
||||
if (response != nil) {
|
||||
headers = response.headers;
|
||||
return response.proxiedResponse;
|
||||
}
|
||||
|
||||
// Set a MIME type for static files if possible
|
||||
NSObject<HTTPResponse> *staticResponse = [super httpResponseForMethod:method URI:path];
|
||||
if (staticResponse && [staticResponse respondsToSelector:@selector(filePath)]) {
|
||||
NSString *mimeType = [http mimeTypeForPath:[staticResponse performSelector:@selector(filePath)]];
|
||||
if (mimeType) {
|
||||
headers = [NSDictionary dictionaryWithObject:mimeType forKey:@"Content-Type"];
|
||||
}
|
||||
}
|
||||
return staticResponse;
|
||||
}
|
||||
|
||||
- (void)responseHasAvailableData:(NSObject<HTTPResponse> *)sender {
|
||||
HTTPResponseProxy *proxy = (HTTPResponseProxy *)httpResponse;
|
||||
if (proxy.response == sender) {
|
||||
[super responseHasAvailableData:httpResponse];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)responseDidAbort:(NSObject<HTTPResponse> *)sender {
|
||||
HTTPResponseProxy *proxy = (HTTPResponseProxy *)httpResponse;
|
||||
if (proxy.response == sender) {
|
||||
[super responseDidAbort:httpResponse];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setHeadersForResponse:(HTTPMessage *)response isError:(BOOL)isError {
|
||||
[http.defaultHeaders enumerateKeysAndObjectsUsingBlock:^(id field, id value, BOOL *stop) {
|
||||
[response setHeaderField:field value:value];
|
||||
}];
|
||||
|
||||
if (headers && !isError) {
|
||||
[headers enumerateKeysAndObjectsUsingBlock:^(id field, id value, BOOL *stop) {
|
||||
[response setHeaderField:field value:value];
|
||||
}];
|
||||
}
|
||||
|
||||
// Set the connection header if not already specified
|
||||
NSString *connection = [response headerField:@"Connection"];
|
||||
if (!connection) {
|
||||
connection = [self shouldDie] ? @"close" : @"keep-alive";
|
||||
[response setHeaderField:@"Connection" value:connection];
|
||||
}
|
||||
}
|
||||
|
||||
- (NSData *)preprocessResponse:(HTTPMessage *)response {
|
||||
[self setHeadersForResponse:response isError:NO];
|
||||
return [super preprocessResponse:response];
|
||||
}
|
||||
|
||||
- (NSData *)preprocessErrorResponse:(HTTPMessage *)response {
|
||||
[self setHeadersForResponse:response isError:YES];
|
||||
return [super preprocessErrorResponse:response];
|
||||
}
|
||||
|
||||
- (BOOL)shouldDie {
|
||||
__block BOOL shouldDie = [super shouldDie];
|
||||
|
||||
// Allow custom headers to determine if the connection should be closed
|
||||
if (!shouldDie && headers) {
|
||||
[headers enumerateKeysAndObjectsUsingBlock:^(id field, id value, BOOL *stop) {
|
||||
if ([field caseInsensitiveCompare:@"connection"] == NSOrderedSame) {
|
||||
if ([value caseInsensitiveCompare:@"close"] == NSOrderedSame) {
|
||||
shouldDie = YES;
|
||||
}
|
||||
*stop = YES;
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
return shouldDie;
|
||||
}
|
||||
|
||||
@end
|
||||
55
WebDriverAgentLib/Vendor/RoutingHTTPServer/RoutingHTTPServer.h
vendored
Normal file
55
WebDriverAgentLib/Vendor/RoutingHTTPServer/RoutingHTTPServer.h
vendored
Normal file
@@ -0,0 +1,55 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
//! Project version number for Peertalk.
|
||||
FOUNDATION_EXPORT double RoutingHTTPServerVersionNumber;
|
||||
|
||||
//! Project version string for Peertalk.
|
||||
FOUNDATION_EXPORT const unsigned char RoutingHTTPServerVersionString[];
|
||||
|
||||
#import "HTTPServer.h"
|
||||
#import "HTTPConnection.h"
|
||||
#import "HTTPResponse.h"
|
||||
#import "RouteResponse.h"
|
||||
#import "RouteRequest.h"
|
||||
#import "RoutingConnection.h"
|
||||
|
||||
#import "GCDAsyncSocket.h"
|
||||
|
||||
typedef void (^RequestHandler)(RouteRequest *request, RouteResponse *response);
|
||||
|
||||
@interface RoutingHTTPServer : HTTPServer
|
||||
|
||||
@property (nonatomic, readonly) NSDictionary *defaultHeaders;
|
||||
|
||||
// Specifies headers that will be set on every response.
|
||||
// These headers can be overridden by RouteResponses.
|
||||
- (void)setDefaultHeaders:(NSDictionary *)headers;
|
||||
- (void)setDefaultHeader:(NSString *)field value:(NSString *)value;
|
||||
|
||||
// Returns the dispatch queue on which routes are processed.
|
||||
// By default this is NULL and routes are processed on CocoaHTTPServer's
|
||||
// connection queue. You can specify a queue to process routes on, such as
|
||||
// dispatch_get_main_queue() to process all routes on the main thread.
|
||||
- (dispatch_queue_t)routeQueue;
|
||||
- (void)setRouteQueue:(dispatch_queue_t)queue;
|
||||
|
||||
- (NSDictionary *)mimeTypes;
|
||||
- (void)setMIMETypes:(NSDictionary *)types;
|
||||
- (void)setMIMEType:(NSString *)type forExtension:(NSString *)ext;
|
||||
- (NSString *)mimeTypeForPath:(NSString *)path;
|
||||
|
||||
// Convenience methods. Yes I know, this is Cocoa and we don't use convenience
|
||||
// methods because typing lengthy primitives over and over and over again is
|
||||
// elegant with the beauty and the poetry. These are just, you know, here.
|
||||
- (void)get:(NSString *)path withBlock:(RequestHandler)block;
|
||||
- (void)post:(NSString *)path withBlock:(RequestHandler)block;
|
||||
- (void)put:(NSString *)path withBlock:(RequestHandler)block;
|
||||
- (void)delete:(NSString *)path withBlock:(RequestHandler)block;
|
||||
|
||||
- (void)handleMethod:(NSString *)method withPath:(NSString *)path block:(RequestHandler)block;
|
||||
- (void)handleMethod:(NSString *)method withPath:(NSString *)path target:(id)target selector:(SEL)selector;
|
||||
|
||||
- (BOOL)supportsMethod:(NSString *)method;
|
||||
- (RouteResponse *)routeMethod:(NSString *)method withPath:(NSString *)path parameters:(NSDictionary *)params request:(HTTPMessage *)request connection:(HTTPConnection *)connection;
|
||||
|
||||
@end
|
||||
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