mirror of
https://github.com/swisspol/GCDWebServer.git
synced 2026-02-11 00:00:07 +08:00
Compare commits
24 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
47b8ea5f7c | ||
|
|
dc7fe87878 | ||
|
|
cedec20673 | ||
|
|
ed0f3ac68e | ||
|
|
0a48f42ccb | ||
|
|
6c1439405d | ||
|
|
79e041eae5 | ||
|
|
fb02b9f9d1 | ||
|
|
1b163b1b8b | ||
|
|
eb6589e27a | ||
|
|
0a34a0b205 | ||
|
|
1e99e91407 | ||
|
|
096b07a201 | ||
|
|
e65b569ddc | ||
|
|
08e58e4a5a | ||
|
|
111027f413 | ||
|
|
d7e9386272 | ||
|
|
040515aff4 | ||
|
|
bb56e1e808 | ||
|
|
ff7a5c8e0a | ||
|
|
7c24996be3 | ||
|
|
feacf7601e | ||
|
|
36658278f8 | ||
|
|
0f2f22a1b0 |
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright (c) 2012-2013, Pierre-Olivier Latour
|
Copyright (c) 2012-2014, Pierre-Olivier Latour
|
||||||
All rights reserved.
|
All rights reserved.
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
Redistribution and use in source and binary forms, with or without
|
||||||
@@ -31,14 +31,19 @@
|
|||||||
typedef GCDWebServerRequest* (^GCDWebServerMatchBlock)(NSString* requestMethod, NSURL* requestURL, NSDictionary* requestHeaders, NSString* urlPath, NSDictionary* urlQuery);
|
typedef GCDWebServerRequest* (^GCDWebServerMatchBlock)(NSString* requestMethod, NSURL* requestURL, NSDictionary* requestHeaders, NSString* urlPath, NSDictionary* urlQuery);
|
||||||
typedef GCDWebServerResponse* (^GCDWebServerProcessBlock)(GCDWebServerRequest* request);
|
typedef GCDWebServerResponse* (^GCDWebServerProcessBlock)(GCDWebServerRequest* request);
|
||||||
|
|
||||||
@interface GCDWebServer : NSObject {
|
#ifdef __cplusplus
|
||||||
@private
|
extern "C" {
|
||||||
NSMutableArray* _handlers;
|
#endif
|
||||||
|
|
||||||
NSUInteger _port;
|
NSString* GCDWebServerGetMimeTypeForExtension(NSString* extension);
|
||||||
dispatch_source_t _source;
|
NSString* GCDWebServerUnescapeURLString(NSString* string);
|
||||||
CFNetServiceRef _service;
|
NSDictionary* GCDWebServerParseURLEncodedForm(NSString* form);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
@interface GCDWebServer : NSObject
|
||||||
@property(nonatomic, readonly, getter=isRunning) BOOL running;
|
@property(nonatomic, readonly, getter=isRunning) BOOL running;
|
||||||
@property(nonatomic, readonly) NSUInteger port;
|
@property(nonatomic, readonly) NSUInteger port;
|
||||||
@property(nonatomic, readonly) NSString* bonjourName; // Only non-nil if Bonjour registration is active
|
@property(nonatomic, readonly) NSString* bonjourName; // Only non-nil if Bonjour registration is active
|
||||||
@@ -60,8 +65,13 @@ typedef GCDWebServerResponse* (^GCDWebServerProcessBlock)(GCDWebServerRequest* r
|
|||||||
@end
|
@end
|
||||||
|
|
||||||
@interface GCDWebServer (Handlers)
|
@interface GCDWebServer (Handlers)
|
||||||
- (void)addDefaultHandlerForMethod:(NSString*)method requestClass:(Class)class processBlock:(GCDWebServerProcessBlock)block;
|
- (void)addDefaultHandlerForMethod:(NSString*)method requestClass:(Class)aClass processBlock:(GCDWebServerProcessBlock)block;
|
||||||
- (void)addHandlerForBasePath:(NSString*)basePath localPath:(NSString*)localPath indexFilename:(NSString*)indexFilename cacheAge:(NSUInteger)cacheAge; // Base path is recursive and case-sensitive
|
- (void)addHandlerForMethod:(NSString*)method path:(NSString*)path requestClass:(Class)aClass processBlock:(GCDWebServerProcessBlock)block; // Path is case-insensitive
|
||||||
- (void)addHandlerForMethod:(NSString*)method path:(NSString*)path requestClass:(Class)class processBlock:(GCDWebServerProcessBlock)block; // Path is case-insensitive
|
- (void)addHandlerForMethod:(NSString*)method pathRegex:(NSString*)regex requestClass:(Class)aClass processBlock:(GCDWebServerProcessBlock)block; // Regular expression is case-insensitive
|
||||||
- (void)addHandlerForMethod:(NSString*)method pathRegex:(NSString*)regex requestClass:(Class)class processBlock:(GCDWebServerProcessBlock)block; // Regular expression is case-insensitive
|
@end
|
||||||
|
|
||||||
|
@interface GCDWebServer (GETHandlers)
|
||||||
|
- (void)addGETHandlerForPath:(NSString*)path staticData:(NSData*)staticData contentType:(NSString*)contentType cacheAge:(NSUInteger)cacheAge; // Path is case-insensitive
|
||||||
|
- (void)addGETHandlerForPath:(NSString*)path filePath:(NSString*)filePath isAttachment:(BOOL)isAttachment cacheAge:(NSUInteger)cacheAge allowRangeRequests:(BOOL)allowRangeRequests; // Path is case-insensitive
|
||||||
|
- (void)addGETHandlerForBasePath:(NSString*)basePath directoryPath:(NSString*)directoryPath indexFilename:(NSString*)indexFilename cacheAge:(NSUInteger)cacheAge allowRangeRequests:(BOOL)allowRangeRequests; // Base path is recursive and case-sensitive
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright (c) 2012-2013, Pierre-Olivier Latour
|
Copyright (c) 2012-2014, Pierre-Olivier Latour
|
||||||
All rights reserved.
|
All rights reserved.
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
Redistribution and use in source and binary forms, with or without
|
||||||
@@ -36,6 +36,23 @@
|
|||||||
|
|
||||||
#define kMaxPendingConnections 16
|
#define kMaxPendingConnections 16
|
||||||
|
|
||||||
|
@interface GCDWebServer () {
|
||||||
|
@private
|
||||||
|
NSMutableArray* _handlers;
|
||||||
|
|
||||||
|
NSUInteger _port;
|
||||||
|
dispatch_source_t _source;
|
||||||
|
CFNetServiceRef _service;
|
||||||
|
}
|
||||||
|
@end
|
||||||
|
|
||||||
|
@interface GCDWebServerHandler () {
|
||||||
|
@private
|
||||||
|
GCDWebServerMatchBlock _matchBlock;
|
||||||
|
GCDWebServerProcessBlock _processBlock;
|
||||||
|
}
|
||||||
|
@end
|
||||||
|
|
||||||
static BOOL _run;
|
static BOOL _run;
|
||||||
|
|
||||||
NSString* GCDWebServerGetMimeTypeForExtension(NSString* extension) {
|
NSString* GCDWebServerGetMimeTypeForExtension(NSString* extension) {
|
||||||
@@ -62,7 +79,7 @@ NSString* GCDWebServerGetMimeTypeForExtension(NSString* extension) {
|
|||||||
|
|
||||||
NSString* GCDWebServerUnescapeURLString(NSString* string) {
|
NSString* GCDWebServerUnescapeURLString(NSString* string) {
|
||||||
return ARC_BRIDGE_RELEASE(CFURLCreateStringByReplacingPercentEscapesUsingEncoding(kCFAllocatorDefault, (CFStringRef)string, CFSTR(""),
|
return ARC_BRIDGE_RELEASE(CFURLCreateStringByReplacingPercentEscapesUsingEncoding(kCFAllocatorDefault, (CFStringRef)string, CFSTR(""),
|
||||||
kCFStringEncodingUTF8));
|
kCFStringEncodingUTF8));
|
||||||
}
|
}
|
||||||
|
|
||||||
NSDictionary* GCDWebServerParseURLEncodedForm(NSString* form) {
|
NSDictionary* GCDWebServerParseURLEncodedForm(NSString* form) {
|
||||||
@@ -83,7 +100,11 @@ NSDictionary* GCDWebServerParseURLEncodedForm(NSString* form) {
|
|||||||
|
|
||||||
key = [key stringByReplacingOccurrencesOfString:@"+" withString:@" "];
|
key = [key stringByReplacingOccurrencesOfString:@"+" withString:@" "];
|
||||||
value = [value stringByReplacingOccurrencesOfString:@"+" withString:@" "];
|
value = [value stringByReplacingOccurrencesOfString:@"+" withString:@" "];
|
||||||
[parameters setObject:GCDWebServerUnescapeURLString(value) forKey:GCDWebServerUnescapeURLString(key)];
|
if (key && value) {
|
||||||
|
[parameters setObject:GCDWebServerUnescapeURLString(value) forKey:GCDWebServerUnescapeURLString(key)];
|
||||||
|
} else {
|
||||||
|
DNOT_REACHED();
|
||||||
|
}
|
||||||
|
|
||||||
if ([scanner isAtEnd]) {
|
if ([scanner isAtEnd]) {
|
||||||
break;
|
break;
|
||||||
@@ -244,7 +265,7 @@ static void _NetServiceClientCallBack(CFNetServiceRef service, CFStreamError* er
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (name) {
|
if (name) {
|
||||||
_service = CFNetServiceCreate(kCFAllocatorDefault, CFSTR("local."), CFSTR("_http._tcp"), (ARC_BRIDGE CFStringRef)name, _port);
|
_service = CFNetServiceCreate(kCFAllocatorDefault, CFSTR("local."), CFSTR("_http._tcp"), (ARC_BRIDGE CFStringRef)name, (SInt32)_port);
|
||||||
if (_service) {
|
if (_service) {
|
||||||
CFNetServiceClientContext context = {0, (ARC_BRIDGE void*)self, NULL, NULL, NULL};
|
CFNetServiceClientContext context = {0, (ARC_BRIDGE void*)self, NULL, NULL, NULL};
|
||||||
CFNetServiceSetClient(_service, _NetServiceClientCallBack, &context);
|
CFNetServiceSetClient(_service, _NetServiceClientCallBack, &context);
|
||||||
@@ -314,7 +335,7 @@ static void _NetServiceClientCallBack(CFNetServiceRef service, CFStreamError* er
|
|||||||
- (BOOL)runWithPort:(NSUInteger)port {
|
- (BOOL)runWithPort:(NSUInteger)port {
|
||||||
BOOL success = NO;
|
BOOL success = NO;
|
||||||
_run = YES;
|
_run = YES;
|
||||||
void* handler = signal(SIGINT, _SignalHandler);
|
void (*handler)(int) = signal(SIGINT, _SignalHandler);
|
||||||
if (handler != SIG_ERR) {
|
if (handler != SIG_ERR) {
|
||||||
if ([self startWithPort:port bonjourName:@""]) {
|
if ([self startWithPort:port bonjourName:@""]) {
|
||||||
while (_run) {
|
while (_run) {
|
||||||
@@ -332,16 +353,79 @@ static void _NetServiceClientCallBack(CFNetServiceRef service, CFStreamError* er
|
|||||||
|
|
||||||
@implementation GCDWebServer (Handlers)
|
@implementation GCDWebServer (Handlers)
|
||||||
|
|
||||||
- (void)addDefaultHandlerForMethod:(NSString*)method requestClass:(Class)class processBlock:(GCDWebServerProcessBlock)block {
|
- (void)addDefaultHandlerForMethod:(NSString*)method requestClass:(Class)aClass processBlock:(GCDWebServerProcessBlock)block {
|
||||||
[self addHandlerWithMatchBlock:^GCDWebServerRequest *(NSString* requestMethod, NSURL* requestURL, NSDictionary* requestHeaders, NSString* urlPath, NSDictionary* urlQuery) {
|
[self addHandlerWithMatchBlock:^GCDWebServerRequest *(NSString* requestMethod, NSURL* requestURL, NSDictionary* requestHeaders, NSString* urlPath, NSDictionary* urlQuery) {
|
||||||
|
|
||||||
return ARC_AUTORELEASE([[class alloc] initWithMethod:requestMethod url:requestURL headers:requestHeaders path:urlPath query:urlQuery]);
|
return ARC_AUTORELEASE([[aClass alloc] initWithMethod:requestMethod url:requestURL headers:requestHeaders path:urlPath query:urlQuery]);
|
||||||
|
|
||||||
} processBlock:block];
|
} processBlock:block];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (GCDWebServerResponse*)_responseWithContentsOfFile:(NSString*)path {
|
- (void)addHandlerForMethod:(NSString*)method path:(NSString*)path requestClass:(Class)aClass processBlock:(GCDWebServerProcessBlock)block {
|
||||||
return [GCDWebServerFileResponse responseWithFile:path];
|
if ([path hasPrefix:@"/"] && [aClass isSubclassOfClass:[GCDWebServerRequest class]]) {
|
||||||
|
[self addHandlerWithMatchBlock:^GCDWebServerRequest *(NSString* requestMethod, NSURL* requestURL, NSDictionary* requestHeaders, NSString* urlPath, NSDictionary* urlQuery) {
|
||||||
|
|
||||||
|
if (![requestMethod isEqualToString:method]) {
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
if ([urlPath caseInsensitiveCompare:path] != NSOrderedSame) {
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
return ARC_AUTORELEASE([[aClass alloc] initWithMethod:requestMethod url:requestURL headers:requestHeaders path:urlPath query:urlQuery]);
|
||||||
|
|
||||||
|
} processBlock:block];
|
||||||
|
} else {
|
||||||
|
DNOT_REACHED();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)addHandlerForMethod:(NSString*)method pathRegex:(NSString*)regex requestClass:(Class)aClass processBlock:(GCDWebServerProcessBlock)block {
|
||||||
|
NSRegularExpression* expression = [NSRegularExpression regularExpressionWithPattern:regex options:NSRegularExpressionCaseInsensitive error:NULL];
|
||||||
|
if (expression && [aClass isSubclassOfClass:[GCDWebServerRequest class]]) {
|
||||||
|
[self addHandlerWithMatchBlock:^GCDWebServerRequest *(NSString* requestMethod, NSURL* requestURL, NSDictionary* requestHeaders, NSString* urlPath, NSDictionary* urlQuery) {
|
||||||
|
|
||||||
|
if (![requestMethod isEqualToString:method]) {
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
if ([expression firstMatchInString:urlPath options:0 range:NSMakeRange(0, urlPath.length)] == nil) {
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
return ARC_AUTORELEASE([[aClass alloc] initWithMethod:requestMethod url:requestURL headers:requestHeaders path:urlPath query:urlQuery]);
|
||||||
|
|
||||||
|
} processBlock:block];
|
||||||
|
} else {
|
||||||
|
DNOT_REACHED();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
@implementation GCDWebServer (GETHandlers)
|
||||||
|
|
||||||
|
- (void)addGETHandlerForPath:(NSString*)path staticData:(NSData*)staticData contentType:(NSString*)contentType cacheAge:(NSUInteger)cacheAge {
|
||||||
|
GCDWebServerResponse* response = [GCDWebServerDataResponse responseWithData:staticData contentType:contentType];
|
||||||
|
response.cacheControlMaxAge = cacheAge;
|
||||||
|
[self addHandlerForMethod:@"GET" path:path requestClass:[GCDWebServerRequest class] processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {
|
||||||
|
|
||||||
|
return response;
|
||||||
|
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)addGETHandlerForPath:(NSString*)path filePath:(NSString*)filePath isAttachment:(BOOL)isAttachment cacheAge:(NSUInteger)cacheAge allowRangeRequests:(BOOL)allowRangeRequests {
|
||||||
|
[self addHandlerForMethod:@"GET" path:path requestClass:[GCDWebServerRequest class] processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {
|
||||||
|
|
||||||
|
GCDWebServerResponse* response = nil;
|
||||||
|
if (allowRangeRequests) {
|
||||||
|
response = [GCDWebServerFileResponse responseWithFile:filePath byteRange:request.byteRange isAttachment:isAttachment];
|
||||||
|
[response setValue:@"bytes" forAdditionalHeader:@"Accept-Ranges"];
|
||||||
|
} else {
|
||||||
|
response = [GCDWebServerFileResponse responseWithFile:filePath isAttachment:isAttachment];
|
||||||
|
}
|
||||||
|
response.cacheControlMaxAge = cacheAge;
|
||||||
|
return response;
|
||||||
|
|
||||||
|
}];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (GCDWebServerResponse*)_responseWithContentsOfDirectory:(NSString*)path {
|
- (GCDWebServerResponse*)_responseWithContentsOfDirectory:(NSString*)path {
|
||||||
@@ -350,7 +434,8 @@ static void _NetServiceClientCallBack(CFNetServiceRef service, CFStreamError* er
|
|||||||
return nil;
|
return nil;
|
||||||
}
|
}
|
||||||
NSMutableString* html = [NSMutableString string];
|
NSMutableString* html = [NSMutableString string];
|
||||||
[html appendString:@"<html><body>\n"];
|
[html appendString:@"<!DOCTYPE html>\n"];
|
||||||
|
[html appendString:@"<html><head><meta charset=\"utf-8\"></head><body>\n"];
|
||||||
[html appendString:@"<ul>\n"];
|
[html appendString:@"<ul>\n"];
|
||||||
for (NSString* file in enumerator) {
|
for (NSString* file in enumerator) {
|
||||||
if (![file hasPrefix:@"."]) {
|
if (![file hasPrefix:@"."]) {
|
||||||
@@ -370,7 +455,7 @@ static void _NetServiceClientCallBack(CFNetServiceRef service, CFStreamError* er
|
|||||||
return [GCDWebServerDataResponse responseWithHTML:html];
|
return [GCDWebServerDataResponse responseWithHTML:html];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)addHandlerForBasePath:(NSString*)basePath localPath:(NSString*)localPath indexFilename:(NSString*)indexFilename cacheAge:(NSUInteger)cacheAge {
|
- (void)addGETHandlerForBasePath:(NSString*)basePath directoryPath:(NSString*)directoryPath indexFilename:(NSString*)indexFilename cacheAge:(NSUInteger)cacheAge allowRangeRequests:(BOOL)allowRangeRequests {
|
||||||
if ([basePath hasPrefix:@"/"] && [basePath hasSuffix:@"/"]) {
|
if ([basePath hasPrefix:@"/"] && [basePath hasSuffix:@"/"]) {
|
||||||
#if __has_feature(objc_arc)
|
#if __has_feature(objc_arc)
|
||||||
__unsafe_unretained GCDWebServer* server = self;
|
__unsafe_unretained GCDWebServer* server = self;
|
||||||
@@ -390,19 +475,24 @@ static void _NetServiceClientCallBack(CFNetServiceRef service, CFStreamError* er
|
|||||||
} processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {
|
} processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {
|
||||||
|
|
||||||
GCDWebServerResponse* response = nil;
|
GCDWebServerResponse* response = nil;
|
||||||
NSString* filePath = [localPath stringByAppendingPathComponent:[request.path substringFromIndex:basePath.length]];
|
NSString* filePath = [directoryPath stringByAppendingPathComponent:[request.path substringFromIndex:basePath.length]];
|
||||||
BOOL isDirectory;
|
BOOL isDirectory;
|
||||||
if ([[NSFileManager defaultManager] fileExistsAtPath:filePath isDirectory:&isDirectory]) {
|
if ([[NSFileManager defaultManager] fileExistsAtPath:filePath isDirectory:&isDirectory]) {
|
||||||
if (isDirectory) {
|
if (isDirectory) {
|
||||||
if (indexFilename) {
|
if (indexFilename) {
|
||||||
NSString* indexPath = [filePath stringByAppendingPathComponent:indexFilename];
|
NSString* indexPath = [filePath stringByAppendingPathComponent:indexFilename];
|
||||||
if ([[NSFileManager defaultManager] fileExistsAtPath:indexPath isDirectory:&isDirectory] && !isDirectory) {
|
if ([[NSFileManager defaultManager] fileExistsAtPath:indexPath isDirectory:&isDirectory] && !isDirectory) {
|
||||||
return [server _responseWithContentsOfFile:indexPath];
|
return [GCDWebServerFileResponse responseWithFile:indexPath];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
response = [server _responseWithContentsOfDirectory:filePath];
|
response = [server _responseWithContentsOfDirectory:filePath];
|
||||||
} else {
|
} else {
|
||||||
response = [server _responseWithContentsOfFile:filePath];
|
if (allowRangeRequests) {
|
||||||
|
response = [GCDWebServerFileResponse responseWithFile:filePath byteRange:request.byteRange];
|
||||||
|
[response setValue:@"bytes" forAdditionalHeader:@"Accept-Ranges"];
|
||||||
|
} else {
|
||||||
|
response = [GCDWebServerFileResponse responseWithFile:filePath];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (response) {
|
if (response) {
|
||||||
@@ -418,41 +508,4 @@ static void _NetServiceClientCallBack(CFNetServiceRef service, CFStreamError* er
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)addHandlerForMethod:(NSString*)method path:(NSString*)path requestClass:(Class)class processBlock:(GCDWebServerProcessBlock)block {
|
|
||||||
if ([path hasPrefix:@"/"] && [class isSubclassOfClass:[GCDWebServerRequest class]]) {
|
|
||||||
[self addHandlerWithMatchBlock:^GCDWebServerRequest *(NSString* requestMethod, NSURL* requestURL, NSDictionary* requestHeaders, NSString* urlPath, NSDictionary* urlQuery) {
|
|
||||||
|
|
||||||
if (![requestMethod isEqualToString:method]) {
|
|
||||||
return nil;
|
|
||||||
}
|
|
||||||
if ([urlPath caseInsensitiveCompare:path] != NSOrderedSame) {
|
|
||||||
return nil;
|
|
||||||
}
|
|
||||||
return ARC_AUTORELEASE([[class alloc] initWithMethod:requestMethod url:requestURL headers:requestHeaders path:urlPath query:urlQuery]);
|
|
||||||
|
|
||||||
} processBlock:block];
|
|
||||||
} else {
|
|
||||||
DNOT_REACHED();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)addHandlerForMethod:(NSString*)method pathRegex:(NSString*)regex requestClass:(Class)class processBlock:(GCDWebServerProcessBlock)block {
|
|
||||||
NSRegularExpression* expression = [NSRegularExpression regularExpressionWithPattern:regex options:NSRegularExpressionCaseInsensitive error:NULL];
|
|
||||||
if (expression && [class isSubclassOfClass:[GCDWebServerRequest class]]) {
|
|
||||||
[self addHandlerWithMatchBlock:^GCDWebServerRequest *(NSString* requestMethod, NSURL* requestURL, NSDictionary* requestHeaders, NSString* urlPath, NSDictionary* urlQuery) {
|
|
||||||
|
|
||||||
if (![requestMethod isEqualToString:method]) {
|
|
||||||
return nil;
|
|
||||||
}
|
|
||||||
if ([expression firstMatchInString:urlPath options:0 range:NSMakeRange(0, urlPath.length)] == nil) {
|
|
||||||
return nil;
|
|
||||||
}
|
|
||||||
return ARC_AUTORELEASE([[class alloc] initWithMethod:requestMethod url:requestURL headers:requestHeaders path:urlPath query:urlQuery]);
|
|
||||||
|
|
||||||
} processBlock:block];
|
|
||||||
} else {
|
|
||||||
DNOT_REACHED();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright (c) 2012-2013, Pierre-Olivier Latour
|
Copyright (c) 2012-2014, Pierre-Olivier Latour
|
||||||
All rights reserved.
|
All rights reserved.
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
Redistribution and use in source and binary forms, with or without
|
||||||
@@ -29,20 +29,7 @@
|
|||||||
|
|
||||||
@class GCDWebServerHandler;
|
@class GCDWebServerHandler;
|
||||||
|
|
||||||
@interface GCDWebServerConnection : NSObject {
|
@interface GCDWebServerConnection : NSObject
|
||||||
@private
|
|
||||||
GCDWebServer* _server;
|
|
||||||
NSData* _address;
|
|
||||||
CFSocketNativeHandle _socket;
|
|
||||||
NSUInteger _bytesRead;
|
|
||||||
NSUInteger _bytesWritten;
|
|
||||||
|
|
||||||
CFHTTPMessageRef _requestMessage;
|
|
||||||
GCDWebServerRequest* _request;
|
|
||||||
GCDWebServerHandler* _handler;
|
|
||||||
CFHTTPMessageRef _responseMessage;
|
|
||||||
GCDWebServerResponse* _response;
|
|
||||||
}
|
|
||||||
@property(nonatomic, readonly) GCDWebServer* server;
|
@property(nonatomic, readonly) GCDWebServer* server;
|
||||||
@property(nonatomic, readonly) NSData* address; // struct sockaddr
|
@property(nonatomic, readonly) NSData* address; // struct sockaddr
|
||||||
@property(nonatomic, readonly) NSUInteger totalBytesRead;
|
@property(nonatomic, readonly) NSUInteger totalBytesRead;
|
||||||
@@ -51,6 +38,8 @@
|
|||||||
|
|
||||||
@interface GCDWebServerConnection (Subclassing)
|
@interface GCDWebServerConnection (Subclassing)
|
||||||
- (void)open;
|
- (void)open;
|
||||||
|
- (void)didUpdateBytesRead; // Called from arbitrary thread after @totalBytesRead is updated - Default implementation does nothing
|
||||||
|
- (void)didUpdateBytesWritten; // Called from arbitrary thread after @totalBytesWritten is updated - Default implementation does nothing
|
||||||
- (GCDWebServerResponse*)processRequest:(GCDWebServerRequest*)request withBlock:(GCDWebServerProcessBlock)block;
|
- (GCDWebServerResponse*)processRequest:(GCDWebServerRequest*)request withBlock:(GCDWebServerProcessBlock)block;
|
||||||
- (void)close;
|
- (void)close;
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright (c) 2012-2013, Pierre-Olivier Latour
|
Copyright (c) 2012-2014, Pierre-Olivier Latour
|
||||||
All rights reserved.
|
All rights reserved.
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
Redistribution and use in source and binary forms, with or without
|
||||||
@@ -45,6 +45,22 @@ static NSData* _continueData = nil;
|
|||||||
static NSDateFormatter* _dateFormatter = nil;
|
static NSDateFormatter* _dateFormatter = nil;
|
||||||
static dispatch_queue_t _formatterQueue = NULL;
|
static dispatch_queue_t _formatterQueue = NULL;
|
||||||
|
|
||||||
|
@interface GCDWebServerConnection () {
|
||||||
|
@private
|
||||||
|
GCDWebServer* _server;
|
||||||
|
NSData* _address;
|
||||||
|
CFSocketNativeHandle _socket;
|
||||||
|
NSUInteger _bytesRead;
|
||||||
|
NSUInteger _bytesWritten;
|
||||||
|
|
||||||
|
CFHTTPMessageRef _requestMessage;
|
||||||
|
GCDWebServerRequest* _request;
|
||||||
|
GCDWebServerHandler* _handler;
|
||||||
|
CFHTTPMessageRef _responseMessage;
|
||||||
|
GCDWebServerResponse* _response;
|
||||||
|
}
|
||||||
|
@end
|
||||||
|
|
||||||
@implementation GCDWebServerConnection (Read)
|
@implementation GCDWebServerConnection (Read)
|
||||||
|
|
||||||
- (void)_readBufferWithLength:(NSUInteger)length completionBlock:(ReadBufferCompletionBlock)block {
|
- (void)_readBufferWithLength:(NSUInteger)length completionBlock:(ReadBufferCompletionBlock)block {
|
||||||
@@ -56,6 +72,7 @@ static dispatch_queue_t _formatterQueue = NULL;
|
|||||||
if (size > 0) {
|
if (size > 0) {
|
||||||
LOG_DEBUG(@"Connection received %i bytes on socket %i", size, _socket);
|
LOG_DEBUG(@"Connection received %i bytes on socket %i", size, _socket);
|
||||||
_bytesRead += size;
|
_bytesRead += size;
|
||||||
|
[self didUpdateBytesRead];
|
||||||
block(buffer);
|
block(buffer);
|
||||||
} else {
|
} else {
|
||||||
if (_bytesRead > 0) {
|
if (_bytesRead > 0) {
|
||||||
@@ -140,7 +157,7 @@ static dispatch_queue_t _formatterQueue = NULL;
|
|||||||
if (remainingLength >= 0) {
|
if (remainingLength >= 0) {
|
||||||
bool success = dispatch_data_apply(buffer, ^bool(dispatch_data_t region, size_t offset, const void* buffer, size_t size) {
|
bool success = dispatch_data_apply(buffer, ^bool(dispatch_data_t region, size_t offset, const void* buffer, size_t size) {
|
||||||
NSInteger result = [_request write:buffer maxLength:size];
|
NSInteger result = [_request write:buffer maxLength:size];
|
||||||
if (result != size) {
|
if (result != (NSInteger)size) {
|
||||||
LOG_ERROR(@"Failed writing request body on socket %i (error %i)", _socket, (int)result);
|
LOG_ERROR(@"Failed writing request body on socket %i (error %i)", _socket, (int)result);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -156,8 +173,8 @@ static dispatch_queue_t _formatterQueue = NULL;
|
|||||||
block(NO);
|
block(NO);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
DNOT_REACHED();
|
|
||||||
block(NO);
|
block(NO);
|
||||||
|
DNOT_REACHED();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
block(NO);
|
block(NO);
|
||||||
@@ -179,6 +196,7 @@ static dispatch_queue_t _formatterQueue = NULL;
|
|||||||
DCHECK(data == NULL);
|
DCHECK(data == NULL);
|
||||||
LOG_DEBUG(@"Connection sent %i bytes on socket %i", size, _socket);
|
LOG_DEBUG(@"Connection sent %i bytes on socket %i", size, _socket);
|
||||||
_bytesWritten += size;
|
_bytesWritten += size;
|
||||||
|
[self didUpdateBytesWritten];
|
||||||
block(YES);
|
block(YES);
|
||||||
} else {
|
} else {
|
||||||
LOG_ERROR(@"Error while writing to socket %i: %s (%i)", _socket, strerror(error), error);
|
LOG_ERROR(@"Error while writing to socket %i: %s (%i)", _socket, strerror(error), error);
|
||||||
@@ -342,7 +360,7 @@ static dispatch_queue_t _formatterQueue = NULL;
|
|||||||
NSInteger length = _request.contentLength;
|
NSInteger length = _request.contentLength;
|
||||||
if (initialData.length) {
|
if (initialData.length) {
|
||||||
NSInteger result = [_request write:initialData.bytes maxLength:initialData.length];
|
NSInteger result = [_request write:initialData.bytes maxLength:initialData.length];
|
||||||
if (result == initialData.length) {
|
if (result == (NSInteger)initialData.length) {
|
||||||
length -= initialData.length;
|
length -= initialData.length;
|
||||||
DCHECK(length >= 0);
|
DCHECK(length >= 0);
|
||||||
} else {
|
} else {
|
||||||
@@ -479,6 +497,14 @@ static dispatch_queue_t _formatterQueue = NULL;
|
|||||||
[self _readRequestHeaders];
|
[self _readRequestHeaders];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (void)didUpdateBytesRead {
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)didUpdateBytesWritten {
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
- (GCDWebServerResponse*)processRequest:(GCDWebServerRequest*)request withBlock:(GCDWebServerProcessBlock)block {
|
- (GCDWebServerResponse*)processRequest:(GCDWebServerRequest*)request withBlock:(GCDWebServerProcessBlock)block {
|
||||||
LOG_DEBUG(@"Connection on socket %i processing %@ request for \"%@\" (%i bytes body)", _socket, _request.method, _request.path, _request.contentLength);
|
LOG_DEBUG(@"Connection on socket %i processing %@ request for \"%@\" (%i bytes body)", _socket, _request.method, _request.path, _request.contentLength);
|
||||||
GCDWebServerResponse* response = nil;
|
GCDWebServerResponse* response = nil;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright (c) 2012-2013, Pierre-Olivier Latour
|
Copyright (c) 2012-2014, Pierre-Olivier Latour
|
||||||
All rights reserved.
|
All rights reserved.
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
Redistribution and use in source and binary forms, with or without
|
||||||
@@ -26,6 +26,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#import <TargetConditionals.h>
|
#import <TargetConditionals.h>
|
||||||
|
#import <AvailabilityMacros.h>
|
||||||
|
|
||||||
#if __has_feature(objc_arc)
|
#if __has_feature(objc_arc)
|
||||||
#define ARC_BRIDGE __bridge
|
#define ARC_BRIDGE __bridge
|
||||||
@@ -105,18 +106,6 @@ static inline void __LogMessage(long level, NSString* format, ...) {
|
|||||||
#define kGCDWebServerDefaultMimeType @"application/octet-stream"
|
#define kGCDWebServerDefaultMimeType @"application/octet-stream"
|
||||||
#define kGCDWebServerGCDQueue dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
|
#define kGCDWebServerGCDQueue dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
extern "C" {
|
|
||||||
#endif
|
|
||||||
|
|
||||||
NSString* GCDWebServerGetMimeTypeForExtension(NSString* extension);
|
|
||||||
NSString* GCDWebServerUnescapeURLString(NSString* string);
|
|
||||||
NSDictionary* GCDWebServerParseURLEncodedForm(NSString* form);
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
@interface GCDWebServerConnection ()
|
@interface GCDWebServerConnection ()
|
||||||
- (id)initWithServer:(GCDWebServer*)server address:(NSData*)address socket:(CFSocketNativeHandle)socket;
|
- (id)initWithServer:(GCDWebServer*)server address:(NSData*)address socket:(CFSocketNativeHandle)socket;
|
||||||
@end
|
@end
|
||||||
@@ -125,11 +114,7 @@ NSDictionary* GCDWebServerParseURLEncodedForm(NSString* form);
|
|||||||
@property(nonatomic, readonly) NSArray* handlers;
|
@property(nonatomic, readonly) NSArray* handlers;
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@interface GCDWebServerHandler : NSObject {
|
@interface GCDWebServerHandler : NSObject
|
||||||
@private
|
|
||||||
GCDWebServerMatchBlock _matchBlock;
|
|
||||||
GCDWebServerProcessBlock _processBlock;
|
|
||||||
}
|
|
||||||
@property(nonatomic, readonly) GCDWebServerMatchBlock matchBlock;
|
@property(nonatomic, readonly) GCDWebServerMatchBlock matchBlock;
|
||||||
@property(nonatomic, readonly) GCDWebServerProcessBlock processBlock;
|
@property(nonatomic, readonly) GCDWebServerProcessBlock processBlock;
|
||||||
- (id)initWithMatchBlock:(GCDWebServerMatchBlock)matchBlock processBlock:(GCDWebServerProcessBlock)processBlock;
|
- (id)initWithMatchBlock:(GCDWebServerMatchBlock)matchBlock processBlock:(GCDWebServerProcessBlock)processBlock;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright (c) 2012-2013, Pierre-Olivier Latour
|
Copyright (c) 2012-2014, Pierre-Olivier Latour
|
||||||
All rights reserved.
|
All rights reserved.
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
Redistribution and use in source and binary forms, with or without
|
||||||
@@ -27,16 +27,7 @@
|
|||||||
|
|
||||||
#import <Foundation/Foundation.h>
|
#import <Foundation/Foundation.h>
|
||||||
|
|
||||||
@interface GCDWebServerRequest : NSObject {
|
@interface GCDWebServerRequest : NSObject
|
||||||
@private
|
|
||||||
NSString* _method;
|
|
||||||
NSURL* _url;
|
|
||||||
NSDictionary* _headers;
|
|
||||||
NSString* _path;
|
|
||||||
NSDictionary* _query;
|
|
||||||
NSString* _type;
|
|
||||||
NSUInteger _length;
|
|
||||||
}
|
|
||||||
@property(nonatomic, readonly) NSString* method;
|
@property(nonatomic, readonly) NSString* method;
|
||||||
@property(nonatomic, readonly) NSURL* URL;
|
@property(nonatomic, readonly) NSURL* URL;
|
||||||
@property(nonatomic, readonly) NSDictionary* headers;
|
@property(nonatomic, readonly) NSDictionary* headers;
|
||||||
@@ -44,6 +35,7 @@
|
|||||||
@property(nonatomic, readonly) NSDictionary* query; // May be nil
|
@property(nonatomic, readonly) NSDictionary* query; // May be nil
|
||||||
@property(nonatomic, readonly) NSString* contentType; // Automatically parsed from headers (nil if request has no body)
|
@property(nonatomic, readonly) NSString* contentType; // Automatically parsed from headers (nil if request has no body)
|
||||||
@property(nonatomic, readonly) NSUInteger contentLength; // Automatically parsed from headers
|
@property(nonatomic, readonly) NSUInteger contentLength; // Automatically parsed from headers
|
||||||
|
@property(nonatomic, readonly) NSRange byteRange; // Automatically parsed from headers ([NSNotFound, 0] if request has no "Range" header, [offset, length] for byte range from beginning or [NSNotFound, -bytes] from end)
|
||||||
- (id)initWithMethod:(NSString*)method url:(NSURL*)url headers:(NSDictionary*)headers path:(NSString*)path query:(NSDictionary*)query;
|
- (id)initWithMethod:(NSString*)method url:(NSURL*)url headers:(NSDictionary*)headers path:(NSString*)path query:(NSDictionary*)query;
|
||||||
- (BOOL)hasBody; // Convenience method
|
- (BOOL)hasBody; // Convenience method
|
||||||
@end
|
@end
|
||||||
@@ -54,71 +46,35 @@
|
|||||||
- (BOOL)close; // Implementation required
|
- (BOOL)close; // Implementation required
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@interface GCDWebServerDataRequest : GCDWebServerRequest {
|
@interface GCDWebServerDataRequest : GCDWebServerRequest
|
||||||
@private
|
|
||||||
NSMutableData* _data;
|
|
||||||
}
|
|
||||||
@property(nonatomic, readonly) NSData* data; // Only valid after open / write / close sequence
|
@property(nonatomic, readonly) NSData* data; // Only valid after open / write / close sequence
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@interface GCDWebServerFileRequest : GCDWebServerRequest {
|
@interface GCDWebServerFileRequest : GCDWebServerRequest
|
||||||
@private
|
|
||||||
NSString* _filePath;
|
|
||||||
int _file;
|
|
||||||
}
|
|
||||||
@property(nonatomic, readonly) NSString* filePath; // Only valid after open / write / close sequence
|
@property(nonatomic, readonly) NSString* filePath; // Only valid after open / write / close sequence
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@interface GCDWebServerURLEncodedFormRequest : GCDWebServerDataRequest {
|
@interface GCDWebServerURLEncodedFormRequest : GCDWebServerDataRequest
|
||||||
@private
|
|
||||||
NSDictionary* _arguments;
|
|
||||||
}
|
|
||||||
@property(nonatomic, readonly) NSDictionary* arguments; // Only valid after open / write / close sequence
|
@property(nonatomic, readonly) NSDictionary* arguments; // Only valid after open / write / close sequence
|
||||||
+ (NSString*)mimeType;
|
+ (NSString*)mimeType;
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@interface GCDWebServerMultiPart : NSObject {
|
@interface GCDWebServerMultiPart : NSObject
|
||||||
@private
|
|
||||||
NSString* _contentType;
|
|
||||||
NSString* _mimeType;
|
|
||||||
}
|
|
||||||
@property(nonatomic, readonly) NSString* contentType; // May be nil
|
@property(nonatomic, readonly) NSString* contentType; // May be nil
|
||||||
@property(nonatomic, readonly) NSString* mimeType; // Defaults to "text/plain" per specifications if undefined
|
@property(nonatomic, readonly) NSString* mimeType; // Defaults to "text/plain" per specifications if undefined
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@interface GCDWebServerMultiPartArgument : GCDWebServerMultiPart {
|
@interface GCDWebServerMultiPartArgument : GCDWebServerMultiPart
|
||||||
@private
|
|
||||||
NSData* _data;
|
|
||||||
NSString* _string;
|
|
||||||
}
|
|
||||||
@property(nonatomic, readonly) NSData* data;
|
@property(nonatomic, readonly) NSData* data;
|
||||||
@property(nonatomic, readonly) NSString* string; // May be nil (only valid for text mime types
|
@property(nonatomic, readonly) NSString* string; // May be nil (only valid for text mime types
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@interface GCDWebServerMultiPartFile : GCDWebServerMultiPart {
|
@interface GCDWebServerMultiPartFile : GCDWebServerMultiPart
|
||||||
@private
|
|
||||||
NSString* _fileName;
|
|
||||||
NSString* _temporaryPath;
|
|
||||||
}
|
|
||||||
@property(nonatomic, readonly) NSString* fileName; // May be nil
|
@property(nonatomic, readonly) NSString* fileName; // May be nil
|
||||||
@property(nonatomic, readonly) NSString* temporaryPath;
|
@property(nonatomic, readonly) NSString* temporaryPath;
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@interface GCDWebServerMultiPartFormRequest : GCDWebServerRequest {
|
@interface GCDWebServerMultiPartFormRequest : GCDWebServerRequest
|
||||||
@private
|
|
||||||
NSData* _boundary;
|
|
||||||
|
|
||||||
NSUInteger _parserState;
|
|
||||||
NSMutableData* _parserData;
|
|
||||||
NSString* _controlName;
|
|
||||||
NSString* _fileName;
|
|
||||||
NSString* _contentType;
|
|
||||||
NSString* _tmpPath;
|
|
||||||
int _tmpFile;
|
|
||||||
|
|
||||||
NSMutableDictionary* _arguments;
|
|
||||||
NSMutableDictionary* _files;
|
|
||||||
}
|
|
||||||
@property(nonatomic, readonly) NSDictionary* arguments; // Only valid after open / write / close sequence
|
@property(nonatomic, readonly) NSDictionary* arguments; // Only valid after open / write / close sequence
|
||||||
@property(nonatomic, readonly) NSDictionary* files; // Only valid after open / write / close sequence
|
@property(nonatomic, readonly) NSDictionary* files; // Only valid after open / write / close sequence
|
||||||
+ (NSString*)mimeType;
|
+ (NSString*)mimeType;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright (c) 2012-2013, Pierre-Olivier Latour
|
Copyright (c) 2012-2014, Pierre-Olivier Latour
|
||||||
All rights reserved.
|
All rights reserved.
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
Redistribution and use in source and binary forms, with or without
|
||||||
@@ -37,6 +37,76 @@ enum {
|
|||||||
kParserState_End
|
kParserState_End
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@interface GCDWebServerRequest () {
|
||||||
|
@private
|
||||||
|
NSString* _method;
|
||||||
|
NSURL* _url;
|
||||||
|
NSDictionary* _headers;
|
||||||
|
NSString* _path;
|
||||||
|
NSDictionary* _query;
|
||||||
|
NSString* _type;
|
||||||
|
NSUInteger _length;
|
||||||
|
NSRange _range;
|
||||||
|
}
|
||||||
|
@end
|
||||||
|
|
||||||
|
@interface GCDWebServerDataRequest () {
|
||||||
|
@private
|
||||||
|
NSMutableData* _data;
|
||||||
|
}
|
||||||
|
@end
|
||||||
|
|
||||||
|
@interface GCDWebServerFileRequest () {
|
||||||
|
@private
|
||||||
|
NSString* _filePath;
|
||||||
|
int _file;
|
||||||
|
}
|
||||||
|
@end
|
||||||
|
|
||||||
|
@interface GCDWebServerURLEncodedFormRequest () {
|
||||||
|
@private
|
||||||
|
NSDictionary* _arguments;
|
||||||
|
}
|
||||||
|
@end
|
||||||
|
|
||||||
|
@interface GCDWebServerMultiPart () {
|
||||||
|
@private
|
||||||
|
NSString* _contentType;
|
||||||
|
NSString* _mimeType;
|
||||||
|
}
|
||||||
|
@end
|
||||||
|
|
||||||
|
@interface GCDWebServerMultiPartArgument () {
|
||||||
|
@private
|
||||||
|
NSData* _data;
|
||||||
|
NSString* _string;
|
||||||
|
}
|
||||||
|
@end
|
||||||
|
|
||||||
|
@interface GCDWebServerMultiPartFile () {
|
||||||
|
@private
|
||||||
|
NSString* _fileName;
|
||||||
|
NSString* _temporaryPath;
|
||||||
|
}
|
||||||
|
@end
|
||||||
|
|
||||||
|
@interface GCDWebServerMultiPartFormRequest () {
|
||||||
|
@private
|
||||||
|
NSData* _boundary;
|
||||||
|
|
||||||
|
NSUInteger _parserState;
|
||||||
|
NSMutableData* _parserData;
|
||||||
|
NSString* _controlName;
|
||||||
|
NSString* _fileName;
|
||||||
|
NSString* _contentType;
|
||||||
|
NSString* _tmpPath;
|
||||||
|
int _tmpFile;
|
||||||
|
|
||||||
|
NSMutableDictionary* _arguments;
|
||||||
|
NSMutableDictionary* _files;
|
||||||
|
}
|
||||||
|
@end
|
||||||
|
|
||||||
static NSData* _newlineData = nil;
|
static NSData* _newlineData = nil;
|
||||||
static NSData* _newlinesData = nil;
|
static NSData* _newlinesData = nil;
|
||||||
static NSData* _dashNewlineData = nil;
|
static NSData* _dashNewlineData = nil;
|
||||||
@@ -70,7 +140,7 @@ static NSStringEncoding _StringEncodingFromCharset(NSString* charset) {
|
|||||||
|
|
||||||
@implementation GCDWebServerRequest : NSObject
|
@implementation GCDWebServerRequest : NSObject
|
||||||
|
|
||||||
@synthesize method=_method, URL=_url, headers=_headers, path=_path, query=_query, contentType=_type, contentLength=_length;
|
@synthesize method=_method, URL=_url, headers=_headers, path=_path, query=_query, contentType=_type, contentLength=_length, byteRange=_range;
|
||||||
|
|
||||||
- (id)initWithMethod:(NSString*)method url:(NSURL*)url headers:(NSDictionary*)headers path:(NSString*)path query:(NSDictionary*)query {
|
- (id)initWithMethod:(NSString*)method url:(NSURL*)url headers:(NSDictionary*)headers path:(NSString*)path query:(NSDictionary*)query {
|
||||||
if ((self = [super init])) {
|
if ((self = [super init])) {
|
||||||
@@ -88,10 +158,39 @@ static NSStringEncoding _StringEncodingFromCharset(NSString* charset) {
|
|||||||
return nil;
|
return nil;
|
||||||
}
|
}
|
||||||
_length = length;
|
_length = length;
|
||||||
|
|
||||||
if ((_length > 0) && (_type == nil)) {
|
if ((_length > 0) && (_type == nil)) {
|
||||||
_type = [kGCDWebServerDefaultMimeType copy];
|
_type = [kGCDWebServerDefaultMimeType copy];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_range = NSMakeRange(NSNotFound, 0);
|
||||||
|
NSString* rangeHeader = [[_headers objectForKey:@"Range"] lowercaseString];
|
||||||
|
if (rangeHeader) {
|
||||||
|
if ([rangeHeader hasPrefix:@"bytes="]) {
|
||||||
|
NSArray* components = [[rangeHeader substringFromIndex:6] componentsSeparatedByString:@","];
|
||||||
|
if (components.count == 1) {
|
||||||
|
components = [[components firstObject] componentsSeparatedByString:@"-"];
|
||||||
|
if (components.count == 2) {
|
||||||
|
NSString* startString = [components objectAtIndex:0];
|
||||||
|
NSInteger startValue = [startString integerValue];
|
||||||
|
NSString* endString = [components objectAtIndex:1];
|
||||||
|
NSInteger endValue = [endString integerValue];
|
||||||
|
if (startString.length && (startValue >= 0) && endString.length && (endValue >= startValue)) { // The second 500 bytes: "500-999"
|
||||||
|
_range.location = startValue;
|
||||||
|
_range.length = endValue - startValue + 1;
|
||||||
|
} else if (startString.length && (startValue >= 0)) { // The bytes after 9500 bytes: "9500-"
|
||||||
|
_range.location = startValue;
|
||||||
|
_range.length = NSUIntegerMax;
|
||||||
|
} else if (endString.length && (endValue > 0)) { // The final 500 bytes: "-500"
|
||||||
|
_range.location = NSNotFound;
|
||||||
|
_range.length = endValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ((_range.location == NSNotFound) && (_range.length == 0)) { // Ignore "Range" header if syntactically invalid
|
||||||
|
LOG_WARNING(@"Failed to parse 'Range' header \"%@\" for url: %@", rangeHeader, url);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
@@ -429,8 +528,8 @@ static NSStringEncoding _StringEncodingFromCharset(NSString* charset) {
|
|||||||
const void* dataBytes = _parserData.bytes;
|
const void* dataBytes = _parserData.bytes;
|
||||||
NSUInteger dataLength = range.location - 2;
|
NSUInteger dataLength = range.location - 2;
|
||||||
if (_tmpPath) {
|
if (_tmpPath) {
|
||||||
int result = write(_tmpFile, dataBytes, dataLength);
|
ssize_t result = write(_tmpFile, dataBytes, dataLength);
|
||||||
if (result == dataLength) {
|
if (result == (ssize_t)dataLength) {
|
||||||
if (close(_tmpFile) == 0) {
|
if (close(_tmpFile) == 0) {
|
||||||
_tmpFile = 0;
|
_tmpFile = 0;
|
||||||
GCDWebServerMultiPartFile* file = [[GCDWebServerMultiPartFile alloc] initWithContentType:_contentType fileName:_fileName temporaryPath:_tmpPath];
|
GCDWebServerMultiPartFile* file = [[GCDWebServerMultiPartFile alloc] initWithContentType:_contentType fileName:_fileName temporaryPath:_tmpPath];
|
||||||
@@ -467,8 +566,8 @@ static NSStringEncoding _StringEncodingFromCharset(NSString* charset) {
|
|||||||
NSUInteger margin = 2 * _boundary.length;
|
NSUInteger margin = 2 * _boundary.length;
|
||||||
if (_tmpPath && (_parserData.length > margin)) {
|
if (_tmpPath && (_parserData.length > margin)) {
|
||||||
NSUInteger length = _parserData.length - margin;
|
NSUInteger length = _parserData.length - margin;
|
||||||
int result = write(_tmpFile, _parserData.bytes, length);
|
ssize_t result = write(_tmpFile, _parserData.bytes, length);
|
||||||
if (result == length) {
|
if (result == (ssize_t)length) {
|
||||||
[_parserData replaceBytesInRange:NSMakeRange(0, length) withBytes:NULL length:0];
|
[_parserData replaceBytesInRange:NSMakeRange(0, length) withBytes:NULL length:0];
|
||||||
} else {
|
} else {
|
||||||
DNOT_REACHED();
|
DNOT_REACHED();
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright (c) 2012-2013, Pierre-Olivier Latour
|
Copyright (c) 2012-2014, Pierre-Olivier Latour
|
||||||
All rights reserved.
|
All rights reserved.
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
Redistribution and use in source and binary forms, with or without
|
||||||
@@ -27,14 +27,7 @@
|
|||||||
|
|
||||||
#import <Foundation/Foundation.h>
|
#import <Foundation/Foundation.h>
|
||||||
|
|
||||||
@interface GCDWebServerResponse : NSObject {
|
@interface GCDWebServerResponse : NSObject
|
||||||
@private
|
|
||||||
NSString* _type;
|
|
||||||
NSUInteger _length;
|
|
||||||
NSInteger _status;
|
|
||||||
NSUInteger _maxAge;
|
|
||||||
NSMutableDictionary* _headers;
|
|
||||||
}
|
|
||||||
@property(nonatomic, readonly) NSString* contentType;
|
@property(nonatomic, readonly) NSString* contentType;
|
||||||
@property(nonatomic, readonly) NSUInteger contentLength;
|
@property(nonatomic, readonly) NSUInteger contentLength;
|
||||||
@property(nonatomic) NSInteger statusCode; // Default is 200
|
@property(nonatomic) NSInteger statusCode; // Default is 200
|
||||||
@@ -60,11 +53,7 @@
|
|||||||
- (id)initWithRedirect:(NSURL*)location permanent:(BOOL)permanent;
|
- (id)initWithRedirect:(NSURL*)location permanent:(BOOL)permanent;
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@interface GCDWebServerDataResponse : GCDWebServerResponse {
|
@interface GCDWebServerDataResponse : GCDWebServerResponse
|
||||||
@private
|
|
||||||
NSData* _data;
|
|
||||||
NSInteger _offset;
|
|
||||||
}
|
|
||||||
+ (GCDWebServerDataResponse*)responseWithData:(NSData*)data contentType:(NSString*)type;
|
+ (GCDWebServerDataResponse*)responseWithData:(NSData*)data contentType:(NSString*)type;
|
||||||
- (id)initWithData:(NSData*)data contentType:(NSString*)type;
|
- (id)initWithData:(NSData*)data contentType:(NSString*)type;
|
||||||
@end
|
@end
|
||||||
@@ -78,13 +67,13 @@
|
|||||||
- (id)initWithHTMLTemplate:(NSString*)path variables:(NSDictionary*)variables; // Simple template system that replaces all occurences of "%variable%" with corresponding value (encodes using UTF-8)
|
- (id)initWithHTMLTemplate:(NSString*)path variables:(NSDictionary*)variables; // Simple template system that replaces all occurences of "%variable%" with corresponding value (encodes using UTF-8)
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@interface GCDWebServerFileResponse : GCDWebServerResponse {
|
@interface GCDWebServerFileResponse : GCDWebServerResponse
|
||||||
@private
|
|
||||||
NSString* _path;
|
|
||||||
int _file;
|
|
||||||
}
|
|
||||||
+ (GCDWebServerFileResponse*)responseWithFile:(NSString*)path;
|
+ (GCDWebServerFileResponse*)responseWithFile:(NSString*)path;
|
||||||
+ (GCDWebServerFileResponse*)responseWithFile:(NSString*)path isAttachment:(BOOL)attachment;
|
+ (GCDWebServerFileResponse*)responseWithFile:(NSString*)path isAttachment:(BOOL)attachment;
|
||||||
|
+ (GCDWebServerFileResponse*)responseWithFile:(NSString*)path byteRange:(NSRange)range;
|
||||||
|
+ (GCDWebServerFileResponse*)responseWithFile:(NSString*)path byteRange:(NSRange)range isAttachment:(BOOL)attachment;
|
||||||
- (id)initWithFile:(NSString*)path;
|
- (id)initWithFile:(NSString*)path;
|
||||||
- (id)initWithFile:(NSString*)path isAttachment:(BOOL)attachment;
|
- (id)initWithFile:(NSString*)path isAttachment:(BOOL)attachment;
|
||||||
|
- (id)initWithFile:(NSString*)path byteRange:(NSRange)range; // Pass [NSNotFound, 0] to disable byte range entirely, [offset, length] to enable byte range from beginning of file or [NSNotFound, -bytes] from end of file
|
||||||
|
- (id)initWithFile:(NSString*)path byteRange:(NSRange)range isAttachment:(BOOL)attachment;
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright (c) 2012-2013, Pierre-Olivier Latour
|
Copyright (c) 2012-2014, Pierre-Olivier Latour
|
||||||
All rights reserved.
|
All rights reserved.
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
Redistribution and use in source and binary forms, with or without
|
||||||
@@ -29,6 +29,32 @@
|
|||||||
|
|
||||||
#import "GCDWebServerPrivate.h"
|
#import "GCDWebServerPrivate.h"
|
||||||
|
|
||||||
|
@interface GCDWebServerResponse () {
|
||||||
|
@private
|
||||||
|
NSString* _type;
|
||||||
|
NSUInteger _length;
|
||||||
|
NSInteger _status;
|
||||||
|
NSUInteger _maxAge;
|
||||||
|
NSMutableDictionary* _headers;
|
||||||
|
}
|
||||||
|
@end
|
||||||
|
|
||||||
|
@interface GCDWebServerDataResponse () {
|
||||||
|
@private
|
||||||
|
NSData* _data;
|
||||||
|
NSInteger _offset;
|
||||||
|
}
|
||||||
|
@end
|
||||||
|
|
||||||
|
@interface GCDWebServerFileResponse () {
|
||||||
|
@private
|
||||||
|
NSString* _path;
|
||||||
|
NSUInteger _offset;
|
||||||
|
NSUInteger _size;
|
||||||
|
int _file;
|
||||||
|
}
|
||||||
|
@end
|
||||||
|
|
||||||
@implementation GCDWebServerResponse
|
@implementation GCDWebServerResponse
|
||||||
|
|
||||||
@synthesize contentType=_type, contentLength=_length, statusCode=_status, cacheControlMaxAge=_maxAge, additionalHeaders=_headers;
|
@synthesize contentType=_type, contentLength=_length, statusCode=_status, cacheControlMaxAge=_maxAge, additionalHeaders=_headers;
|
||||||
@@ -155,7 +181,7 @@
|
|||||||
- (NSInteger)read:(void*)buffer maxLength:(NSUInteger)length {
|
- (NSInteger)read:(void*)buffer maxLength:(NSUInteger)length {
|
||||||
DCHECK(_offset >= 0);
|
DCHECK(_offset >= 0);
|
||||||
NSInteger size = 0;
|
NSInteger size = 0;
|
||||||
if (_offset < _data.length) {
|
if (_offset < (NSInteger)_data.length) {
|
||||||
size = MIN(_data.length - _offset, length);
|
size = MIN(_data.length - _offset, length);
|
||||||
bcopy((char*)_data.bytes + _offset, buffer, size);
|
bcopy((char*)_data.bytes + _offset, buffer, size);
|
||||||
_offset += size;
|
_offset += size;
|
||||||
@@ -227,24 +253,63 @@
|
|||||||
return ARC_AUTORELEASE([[[self class] alloc] initWithFile:path isAttachment:attachment]);
|
return ARC_AUTORELEASE([[[self class] alloc] initWithFile:path isAttachment:attachment]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
+ (GCDWebServerFileResponse*)responseWithFile:(NSString*)path byteRange:(NSRange)range {
|
||||||
|
return ARC_AUTORELEASE([[[self class] alloc] initWithFile:path byteRange:range]);
|
||||||
|
}
|
||||||
|
|
||||||
|
+ (GCDWebServerFileResponse*)responseWithFile:(NSString*)path byteRange:(NSRange)range isAttachment:(BOOL)attachment {
|
||||||
|
return ARC_AUTORELEASE([[[self class] alloc] initWithFile:path byteRange:range isAttachment:attachment]);
|
||||||
|
}
|
||||||
|
|
||||||
- (id)initWithFile:(NSString*)path {
|
- (id)initWithFile:(NSString*)path {
|
||||||
return [self initWithFile:path isAttachment:NO];
|
return [self initWithFile:path byteRange:NSMakeRange(NSNotFound, 0) isAttachment:NO];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (id)initWithFile:(NSString*)path isAttachment:(BOOL)attachment {
|
- (id)initWithFile:(NSString*)path isAttachment:(BOOL)attachment {
|
||||||
|
return [self initWithFile:path byteRange:NSMakeRange(NSNotFound, 0) isAttachment:attachment];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (id)initWithFile:(NSString*)path byteRange:(NSRange)range {
|
||||||
|
return [self initWithFile:path byteRange:range isAttachment:NO];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (id)initWithFile:(NSString*)path byteRange:(NSRange)range isAttachment:(BOOL)attachment {
|
||||||
struct stat info;
|
struct stat info;
|
||||||
if (lstat([path fileSystemRepresentation], &info) || !(info.st_mode & S_IFREG)) {
|
if (lstat([path fileSystemRepresentation], &info) || !(info.st_mode & S_IFREG)) {
|
||||||
DNOT_REACHED();
|
DNOT_REACHED();
|
||||||
ARC_RELEASE(self);
|
ARC_RELEASE(self);
|
||||||
return nil;
|
return nil;
|
||||||
}
|
}
|
||||||
|
if ((range.location != NSNotFound) || (range.length > 0)) {
|
||||||
|
if (range.location != NSNotFound) {
|
||||||
|
range.location = MIN(range.location, (NSUInteger)info.st_size);
|
||||||
|
range.length = MIN(range.length, info.st_size - range.location);
|
||||||
|
} else {
|
||||||
|
range.length = MIN(range.length, (NSUInteger)info.st_size);
|
||||||
|
range.location = info.st_size - range.length;
|
||||||
|
}
|
||||||
|
if (range.length == 0) {
|
||||||
|
ARC_RELEASE(self);
|
||||||
|
return nil; // TODO: Return 416 status code and "Content-Range: bytes */{file length}" header
|
||||||
|
}
|
||||||
|
}
|
||||||
NSString* type = GCDWebServerGetMimeTypeForExtension([path pathExtension]);
|
NSString* type = GCDWebServerGetMimeTypeForExtension([path pathExtension]);
|
||||||
if (type == nil) {
|
if (type == nil) {
|
||||||
type = kGCDWebServerDefaultMimeType;
|
type = kGCDWebServerDefaultMimeType;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((self = [super initWithContentType:type contentLength:info.st_size])) {
|
if ((self = [super initWithContentType:type contentLength:(range.location != NSNotFound ? range.length : info.st_size)])) {
|
||||||
_path = [path copy];
|
_path = [path copy];
|
||||||
|
if (range.location != NSNotFound) {
|
||||||
|
_offset = range.location;
|
||||||
|
_size = range.length;
|
||||||
|
[self setStatusCode:206];
|
||||||
|
[self setValue:[NSString stringWithFormat:@"bytes %i-%i/%i", (int)range.location, (int)(range.location + range.length - 1), (int)info.st_size] forAdditionalHeader:@"Content-Range"];
|
||||||
|
LOG_DEBUG(@"Using content bytes range [%i-%i] for file \"%@\"", (int)range.location, (int)(range.location + range.length - 1), path);
|
||||||
|
} else {
|
||||||
|
_offset = 0;
|
||||||
|
_size = info.st_size;
|
||||||
|
}
|
||||||
if (attachment) { // TODO: Use http://tools.ietf.org/html/rfc5987 to encode file names with special characters instead of using lossy conversion to ISO 8859-1
|
if (attachment) { // TODO: Use http://tools.ietf.org/html/rfc5987 to encode file names with special characters instead of using lossy conversion to ISO 8859-1
|
||||||
NSData* data = [[path lastPathComponent] dataUsingEncoding:NSISOLatin1StringEncoding allowLossyConversion:YES];
|
NSData* data = [[path lastPathComponent] dataUsingEncoding:NSISOLatin1StringEncoding allowLossyConversion:YES];
|
||||||
NSString* fileName = data ? [[NSString alloc] initWithData:data encoding:NSISOLatin1StringEncoding] : nil;
|
NSString* fileName = data ? [[NSString alloc] initWithData:data encoding:NSISOLatin1StringEncoding] : nil;
|
||||||
@@ -269,12 +334,24 @@
|
|||||||
- (BOOL)open {
|
- (BOOL)open {
|
||||||
DCHECK(_file <= 0);
|
DCHECK(_file <= 0);
|
||||||
_file = open([_path fileSystemRepresentation], O_NOFOLLOW | O_RDONLY);
|
_file = open([_path fileSystemRepresentation], O_NOFOLLOW | O_RDONLY);
|
||||||
return (_file > 0 ? YES : NO);
|
if (_file <= 0) {
|
||||||
|
return NO;
|
||||||
|
}
|
||||||
|
if (lseek(_file, _offset, SEEK_SET) != (off_t)_offset) {
|
||||||
|
close(_file);
|
||||||
|
_file = 0;
|
||||||
|
return NO;
|
||||||
|
}
|
||||||
|
return YES;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSInteger)read:(void*)buffer maxLength:(NSUInteger)length {
|
- (NSInteger)read:(void*)buffer maxLength:(NSUInteger)length {
|
||||||
DCHECK(_file > 0);
|
DCHECK(_file > 0);
|
||||||
return read(_file, buffer, length);
|
ssize_t result = read(_file, buffer, MIN(length, _size));
|
||||||
|
if (result > 0) {
|
||||||
|
_size -= result;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (BOOL)close {
|
- (BOOL)close {
|
||||||
|
|||||||
@@ -1,18 +0,0 @@
|
|||||||
Pod::Spec.new do |s|
|
|
||||||
s.name = 'GCDWebServer'
|
|
||||||
s.version = '1.2'
|
|
||||||
s.author = { 'Pierre-Olivier Latour' => 'info@pol-online.net' }
|
|
||||||
s.license = { :type => 'BSD', :file => 'LICENSE' }
|
|
||||||
s.homepage = 'https://github.com/swisspol/GCDWebServer'
|
|
||||||
s.summary = 'Lightweight GCD based HTTP server for Mac OS X & iOS apps'
|
|
||||||
s.source = { :git => 'https://github.com/swisspol/GCDWebServer.git', :tag => s.version.to_s }
|
|
||||||
|
|
||||||
s.requires_arc = true
|
|
||||||
|
|
||||||
s.source_files = 'CGDWebServer/*.{h,m}'
|
|
||||||
|
|
||||||
s.ios.deployment_target = '5.0'
|
|
||||||
s.osx.deployment_target = '10.7'
|
|
||||||
|
|
||||||
s.ios.framework = 'MobileCoreServices'
|
|
||||||
end
|
|
||||||
@@ -79,6 +79,8 @@
|
|||||||
E22112981690B7AA0048D2B2 /* CFNetwork.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CFNetwork.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS6.0.sdk/System/Library/Frameworks/CFNetwork.framework; sourceTree = DEVELOPER_DIR; };
|
E22112981690B7AA0048D2B2 /* CFNetwork.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CFNetwork.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS6.0.sdk/System/Library/Frameworks/CFNetwork.framework; sourceTree = DEVELOPER_DIR; };
|
||||||
E221129A1690B7B10048D2B2 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS6.0.sdk/System/Library/Frameworks/UIKit.framework; sourceTree = DEVELOPER_DIR; };
|
E221129A1690B7B10048D2B2 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS6.0.sdk/System/Library/Frameworks/UIKit.framework; sourceTree = DEVELOPER_DIR; };
|
||||||
E221129C1690B7BA0048D2B2 /* MobileCoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MobileCoreServices.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS6.0.sdk/System/Library/Frameworks/MobileCoreServices.framework; sourceTree = DEVELOPER_DIR; };
|
E221129C1690B7BA0048D2B2 /* MobileCoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MobileCoreServices.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS6.0.sdk/System/Library/Frameworks/MobileCoreServices.framework; sourceTree = DEVELOPER_DIR; };
|
||||||
|
E263213318DB688E00D9DC0C /* index.html */ = {isa = PBXFileReference; lastKnownFileType = text.html; path = index.html; sourceTree = "<group>"; };
|
||||||
|
E263213418DB7F7900D9DC0C /* filedrop-min.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; path = "filedrop-min.js"; sourceTree = "<group>"; };
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
/* Begin PBXFrameworksBuildPhase section */
|
/* Begin PBXFrameworksBuildPhase section */
|
||||||
@@ -145,6 +147,8 @@
|
|||||||
E221128D1690B6470048D2B2 /* Mac */ = {
|
E221128D1690B6470048D2B2 /* Mac */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
E263213418DB7F7900D9DC0C /* filedrop-min.js */,
|
||||||
|
E263213318DB688E00D9DC0C /* index.html */,
|
||||||
E221128E1690B6470048D2B2 /* main.m */,
|
E221128E1690B6470048D2B2 /* main.m */,
|
||||||
);
|
);
|
||||||
path = Mac;
|
path = Mac;
|
||||||
@@ -222,7 +226,7 @@
|
|||||||
08FB7793FE84155DC02AAC07 /* Project object */ = {
|
08FB7793FE84155DC02AAC07 /* Project object */ = {
|
||||||
isa = PBXProject;
|
isa = PBXProject;
|
||||||
attributes = {
|
attributes = {
|
||||||
LastUpgradeCheck = 0500;
|
LastUpgradeCheck = 0510;
|
||||||
};
|
};
|
||||||
buildConfigurationList = 1DEB928908733DD80010E9CD /* Build configuration list for PBXProject "GCDWebServer" */;
|
buildConfigurationList = 1DEB928908733DD80010E9CD /* Build configuration list for PBXProject "GCDWebServer" */;
|
||||||
compatibilityVersion = "Xcode 3.2";
|
compatibilityVersion = "Xcode 3.2";
|
||||||
@@ -291,7 +295,18 @@
|
|||||||
1DEB928608733DD80010E9CD /* Debug */ = {
|
1DEB928608733DD80010E9CD /* Debug */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
|
ARCHS = "$(ARCHS_STANDARD)";
|
||||||
MACOSX_DEPLOYMENT_TARGET = 10.7;
|
MACOSX_DEPLOYMENT_TARGET = 10.7;
|
||||||
|
OTHER_LDFLAGS = (
|
||||||
|
"-sectcreate",
|
||||||
|
__TEXT,
|
||||||
|
_index_html_,
|
||||||
|
"\"Mac/index.html\"",
|
||||||
|
"-sectcreate",
|
||||||
|
__TEXT,
|
||||||
|
_filedrop_js_,
|
||||||
|
"\"Mac/filedrop-min.js\"",
|
||||||
|
);
|
||||||
PRODUCT_NAME = GCDWebServer;
|
PRODUCT_NAME = GCDWebServer;
|
||||||
SDKROOT = macosx;
|
SDKROOT = macosx;
|
||||||
};
|
};
|
||||||
@@ -300,7 +315,18 @@
|
|||||||
1DEB928708733DD80010E9CD /* Release */ = {
|
1DEB928708733DD80010E9CD /* Release */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
|
ARCHS = "$(ARCHS_STANDARD)";
|
||||||
MACOSX_DEPLOYMENT_TARGET = 10.7;
|
MACOSX_DEPLOYMENT_TARGET = 10.7;
|
||||||
|
OTHER_LDFLAGS = (
|
||||||
|
"-sectcreate",
|
||||||
|
__TEXT,
|
||||||
|
_index_html_,
|
||||||
|
"\"Mac/index.html\"",
|
||||||
|
"-sectcreate",
|
||||||
|
__TEXT,
|
||||||
|
_filedrop_js_,
|
||||||
|
"\"Mac/filedrop-min.js\"",
|
||||||
|
);
|
||||||
PRODUCT_NAME = GCDWebServer;
|
PRODUCT_NAME = GCDWebServer;
|
||||||
SDKROOT = macosx;
|
SDKROOT = macosx;
|
||||||
};
|
};
|
||||||
@@ -312,7 +338,16 @@
|
|||||||
CLANG_ENABLE_OBJC_ARC = YES;
|
CLANG_ENABLE_OBJC_ARC = YES;
|
||||||
GCC_OPTIMIZATION_LEVEL = 0;
|
GCC_OPTIMIZATION_LEVEL = 0;
|
||||||
ONLY_ACTIVE_ARCH = YES;
|
ONLY_ACTIVE_ARCH = YES;
|
||||||
WARNING_CFLAGS = "-Wall";
|
WARNING_CFLAGS = (
|
||||||
|
"-Wall",
|
||||||
|
"-Weverything",
|
||||||
|
"-Wno-gnu-statement-expression",
|
||||||
|
"-Wno-direct-ivar-access",
|
||||||
|
"-Wno-implicit-retain-self",
|
||||||
|
"-Wno-assign-enum",
|
||||||
|
"-Wno-format-nonliteral",
|
||||||
|
"-Wno-cast-align",
|
||||||
|
);
|
||||||
};
|
};
|
||||||
name = Debug;
|
name = Debug;
|
||||||
};
|
};
|
||||||
@@ -321,6 +356,7 @@
|
|||||||
buildSettings = {
|
buildSettings = {
|
||||||
CLANG_ENABLE_OBJC_ARC = YES;
|
CLANG_ENABLE_OBJC_ARC = YES;
|
||||||
GCC_PREPROCESSOR_DEFINITIONS = NDEBUG;
|
GCC_PREPROCESSOR_DEFINITIONS = NDEBUG;
|
||||||
|
GCC_TREAT_WARNINGS_AS_ERRORS = YES;
|
||||||
WARNING_CFLAGS = "-Wall";
|
WARNING_CFLAGS = "-Wall";
|
||||||
};
|
};
|
||||||
name = Release;
|
name = Release;
|
||||||
@@ -328,6 +364,7 @@
|
|||||||
E22112761690B4DF0048D2B2 /* Debug */ = {
|
E22112761690B4DF0048D2B2 /* Debug */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
|
ARCHS = "$(ARCHS_STANDARD_INCLUDING_64_BIT)";
|
||||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||||
INFOPLIST_FILE = iOS/Info.plist;
|
INFOPLIST_FILE = iOS/Info.plist;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 5.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 5.0;
|
||||||
@@ -341,6 +378,7 @@
|
|||||||
E22112771690B4DF0048D2B2 /* Release */ = {
|
E22112771690B4DF0048D2B2 /* Release */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
|
ARCHS = "$(ARCHS_STANDARD_INCLUDING_64_BIT)";
|
||||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||||
INFOPLIST_FILE = iOS/Info.plist;
|
INFOPLIST_FILE = iOS/Info.plist;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 5.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 5.0;
|
||||||
|
|||||||
2
LICENSE
2
LICENSE
@@ -1,4 +1,4 @@
|
|||||||
Copyright (c) 2012-2013, Pierre-Olivier Latour
|
Copyright (c) 2012-2014, Pierre-Olivier Latour
|
||||||
All rights reserved.
|
All rights reserved.
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
|||||||
11
Mac/filedrop-min.js
vendored
Executable file
11
Mac/filedrop-min.js
vendored
Executable file
File diff suppressed because one or more lines are too long
91
Mac/index.html
Executable file
91
Mac/index.html
Executable file
@@ -0,0 +1,91 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Basic FileDrop example</title>
|
||||||
|
|
||||||
|
<script type="text/javascript" src="filedrop-min.js"></script>
|
||||||
|
|
||||||
|
<style type="text/css">
|
||||||
|
|
||||||
|
/* Essential FileDrop zone element configuration: */
|
||||||
|
.fd-zone {
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
/* The following are not required but create a pretty box: */
|
||||||
|
width: 15em;
|
||||||
|
margin: 0 auto;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hides <input type="file"> while simulating "Browse" button: */
|
||||||
|
.fd-file {
|
||||||
|
opacity: 0;
|
||||||
|
font-size: 118px;
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
z-index: 1;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
filter: alpha(opacity=0);
|
||||||
|
font-family: sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Provides visible feedback when use drags a file over the drop zone: */
|
||||||
|
.fd-zone.over { border-color: maroon; background: #eee; }
|
||||||
|
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<noscript style="color: maroon">
|
||||||
|
<h2>JavaScript is disabled in your browser. How do you expect FileDrop to work?</h2>
|
||||||
|
</noscript>
|
||||||
|
|
||||||
|
<h2 style="text-align: center">
|
||||||
|
<a href="http://filedropjs.org">FileDrop</a> basic sample
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<!-- A FileDrop area. Can contain any text or elements, or be empty.
|
||||||
|
Can be of any HTML tag too, not necessary fieldset. -->
|
||||||
|
<fieldset id="zone">
|
||||||
|
<legend>Drop a file inside…</legend>
|
||||||
|
<p>Or click here to <em>Browse</em>..</p>
|
||||||
|
|
||||||
|
<!-- Putting another element on top of file input so it overlays it
|
||||||
|
and user can interact with it freely. -->
|
||||||
|
<p style="z-index: 10; position: relative">
|
||||||
|
<input type="checkbox" id="multiple">
|
||||||
|
<label for="multiple">Allow multiple selection</label>
|
||||||
|
</p>
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
// Attach FileDrop to an area ('zone' is an ID but you can also give a DOM node):
|
||||||
|
var zone = new FileDrop('zone', {});
|
||||||
|
|
||||||
|
// Do something when a user chooses or drops a file:
|
||||||
|
zone.event('send', function (files) {
|
||||||
|
// Depending on browser support files (FileList) might contain multiple items.
|
||||||
|
files.each(function (file) {
|
||||||
|
// React on successful AJAX upload:
|
||||||
|
file.event('done', function (xhr) {
|
||||||
|
// 'this' here points to fd.File instance that has triggered the event.
|
||||||
|
alert('Done uploading ' + this.name + ', response:\n\n' + xhr.responseText);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Send the file:
|
||||||
|
file.sendTo('ajax-upload');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// A bit of sugar - toggling multiple selection:
|
||||||
|
fd.addEvent(fd.byID('multiple'), 'change', function (e) {
|
||||||
|
zone.multiple(e.currentTarget || e.srcElement.checked);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
48
Mac/main.m
48
Mac/main.m
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright (c) 2012-2013, Pierre-Olivier Latour
|
Copyright (c) 2012-2014, Pierre-Olivier Latour
|
||||||
All rights reserved.
|
All rights reserved.
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
Redistribution and use in source and binary forms, with or without
|
||||||
@@ -25,19 +25,33 @@
|
|||||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#import <mach-o/getsect.h>
|
||||||
|
|
||||||
#import "GCDWebServer.h"
|
#import "GCDWebServer.h"
|
||||||
|
|
||||||
|
static NSData* _DataFromTEXTSection(const char* name) {
|
||||||
|
unsigned long size = 0;
|
||||||
|
char* ptr = getsectdata("__TEXT", name, &size);
|
||||||
|
if (!ptr || !size) {
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
return [NSData dataWithBytesNoCopy:ptr length:size freeWhenDone:NO];
|
||||||
|
}
|
||||||
|
|
||||||
int main(int argc, const char* argv[]) {
|
int main(int argc, const char* argv[]) {
|
||||||
BOOL success = NO;
|
BOOL success = NO;
|
||||||
|
int mode = (argc == 2 ? MIN(MAX(atoi(argv[1]), 0), 3) : 0);
|
||||||
@autoreleasepool {
|
@autoreleasepool {
|
||||||
GCDWebServer* webServer = [[GCDWebServer alloc] init];
|
GCDWebServer* webServer = [[GCDWebServer alloc] init];
|
||||||
switch (0) {
|
switch (mode) {
|
||||||
|
|
||||||
|
// Simply serve contents of home directory
|
||||||
case 0: {
|
case 0: {
|
||||||
[webServer addHandlerForBasePath:@"/" localPath:NSHomeDirectory() indexFilename:nil cacheAge:0];
|
[webServer addGETHandlerForBasePath:@"/" directoryPath:NSHomeDirectory() indexFilename:nil cacheAge:0 allowRangeRequests:YES];
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Renders a HTML page
|
||||||
case 1: {
|
case 1: {
|
||||||
[webServer addDefaultHandlerForMethod:@"GET"
|
[webServer addDefaultHandlerForMethod:@"GET"
|
||||||
requestClass:[GCDWebServerRequest class]
|
requestClass:[GCDWebServerRequest class]
|
||||||
@@ -49,6 +63,7 @@ int main(int argc, const char* argv[]) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Implements an HTML form
|
||||||
case 2: {
|
case 2: {
|
||||||
[webServer addHandlerForMethod:@"GET"
|
[webServer addHandlerForMethod:@"GET"
|
||||||
path:@"/"
|
path:@"/"
|
||||||
@@ -79,6 +94,33 @@ int main(int argc, const char* argv[]) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Implements drag & drop file upload using http://filedropjs.org (requires Chrome 13+, Firefox 3.6+, IE 10+ or Safari 6+)
|
||||||
|
case 3: {
|
||||||
|
[webServer addGETHandlerForPath:@"/"
|
||||||
|
staticData:_DataFromTEXTSection("_index_html_")
|
||||||
|
contentType:@"text/html; charset=utf-8"
|
||||||
|
cacheAge:0];
|
||||||
|
[webServer addGETHandlerForPath:@"/filedrop-min.js"
|
||||||
|
staticData:_DataFromTEXTSection("_filedrop_js_")
|
||||||
|
contentType:@"application/javascript; charset=utf-8"
|
||||||
|
cacheAge:0];
|
||||||
|
[webServer addHandlerForMethod:@"POST" path:@"/ajax-upload" requestClass:[GCDWebServerFileRequest class] processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) {
|
||||||
|
|
||||||
|
NSString* fileName = GCDWebServerUnescapeURLString([request.headers objectForKey:@"X-File-Name"]);
|
||||||
|
NSString* inPath = [(GCDWebServerFileRequest*)request filePath];
|
||||||
|
NSString* outPath = [@"/tmp" stringByAppendingPathComponent:fileName];
|
||||||
|
[[NSFileManager defaultManager] removeItemAtPath:outPath error:NULL];
|
||||||
|
if ([[NSFileManager defaultManager] moveItemAtPath:inPath toPath:outPath error:NULL]) {
|
||||||
|
NSString* message = [NSString stringWithFormat:@"File uploaded to \"%@\"", outPath];
|
||||||
|
return [GCDWebServerDataResponse responseWithText:message];
|
||||||
|
} else {
|
||||||
|
return [GCDWebServerResponse responseWithStatusCode:500];
|
||||||
|
}
|
||||||
|
|
||||||
|
}];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
success = [webServer runWithPort:8080];
|
success = [webServer runWithPort:8080];
|
||||||
#if !__has_feature(objc_arc)
|
#if !__has_feature(objc_arc)
|
||||||
|
|||||||
10
README.md
10
README.md
@@ -10,7 +10,7 @@ GCDWebServer is a lightweight GCD based HTTP 1.1 server designed to be embedded
|
|||||||
* Available under a friendly [New BSD License](LICENSE)
|
* Available under a friendly [New BSD License](LICENSE)
|
||||||
|
|
||||||
What's not available out of the box but can be implemented on top of the API:
|
What's not available out of the box but can be implemented on top of the API:
|
||||||
* Authentication like Basic Authentication
|
* Authentication like [Basic Authentication](https://en.wikipedia.org/wiki/Basic_access_authentication)
|
||||||
* Web forms submitted using "multipart/mixed"
|
* Web forms submitted using "multipart/mixed"
|
||||||
|
|
||||||
What's not supported (but not really required from an embedded HTTP server):
|
What's not supported (but not really required from an embedded HTTP server):
|
||||||
@@ -18,8 +18,8 @@ What's not supported (but not really required from an embedded HTTP server):
|
|||||||
* HTTPS
|
* HTTPS
|
||||||
|
|
||||||
Requirements:
|
Requirements:
|
||||||
* OS X 10.7 or later
|
* OS X 10.7 or later (x86_64)
|
||||||
* iOS 5.0 or later
|
* iOS 5.0 or later (armv7, armv7s or arm64)
|
||||||
|
|
||||||
Hello World
|
Hello World
|
||||||
===========
|
===========
|
||||||
@@ -67,7 +67,7 @@ int main(int argc, const char* argv[]) {
|
|||||||
@autoreleasepool {
|
@autoreleasepool {
|
||||||
|
|
||||||
GCDWebServer* webServer = [[GCDWebServer alloc] init];
|
GCDWebServer* webServer = [[GCDWebServer alloc] init];
|
||||||
[webServer addHandlerForBasePath:@"/" localPath:NSHomeDirectory() indexFilename:nil cacheAge:3600];
|
[webServer addGETHandlerForBasePath:@"/" directoryPath:NSHomeDirectory() indexFilename:nil cacheAge:3600 allowRangeRequests:YES];
|
||||||
[webServer runWithPort:8080];
|
[webServer runWithPort:8080];
|
||||||
[webServer release];
|
[webServer release];
|
||||||
|
|
||||||
@@ -171,7 +171,7 @@ Assuming you have a website directory in your app containing HTML template files
|
|||||||
NSString* websitePath = [[NSBundle mainBundle] pathForResource:@"Website" ofType:nil];
|
NSString* websitePath = [[NSBundle mainBundle] pathForResource:@"Website" ofType:nil];
|
||||||
|
|
||||||
// Add a default handler to serve static files (i.e. anything other than HTML files)
|
// Add a default handler to serve static files (i.e. anything other than HTML files)
|
||||||
[self addHandlerForBasePath:@"/" localPath:websitePath indexFilename:nil cacheAge:3600];
|
[self addGETHandlerForBasePath:@"/" directoryPath:websitePath indexFilename:nil cacheAge:3600 allowRangeRequests:YES];
|
||||||
|
|
||||||
// Add an override handler for all requests to "*.html" URLs to do the special HTML templatization
|
// Add an override handler for all requests to "*.html" URLs to do the special HTML templatization
|
||||||
[self addHandlerForMethod:@"GET"
|
[self addHandlerForMethod:@"GET"
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright (c) 2012-2013, Pierre-Olivier Latour
|
Copyright (c) 2012-2014, Pierre-Olivier Latour
|
||||||
All rights reserved.
|
All rights reserved.
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
Redistribution and use in source and binary forms, with or without
|
||||||
@@ -29,10 +29,6 @@
|
|||||||
|
|
||||||
#import "GCDWebServer.h"
|
#import "GCDWebServer.h"
|
||||||
|
|
||||||
@interface AppDelegate : UIResponder <UIApplicationDelegate> {
|
@interface AppDelegate : UIResponder <UIApplicationDelegate>
|
||||||
@private
|
|
||||||
UIWindow* _window;
|
|
||||||
GCDWebServer* _webServer;
|
|
||||||
}
|
|
||||||
@property(retain, nonatomic) UIWindow* window;
|
@property(retain, nonatomic) UIWindow* window;
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright (c) 2012-2013, Pierre-Olivier Latour
|
Copyright (c) 2012-2014, Pierre-Olivier Latour
|
||||||
All rights reserved.
|
All rights reserved.
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
Redistribution and use in source and binary forms, with or without
|
||||||
@@ -27,8 +27,17 @@
|
|||||||
|
|
||||||
#import "AppDelegate.h"
|
#import "AppDelegate.h"
|
||||||
|
|
||||||
|
@interface AppDelegate () {
|
||||||
|
@private
|
||||||
|
UIWindow* _window;
|
||||||
|
GCDWebServer* _webServer;
|
||||||
|
}
|
||||||
|
@end
|
||||||
|
|
||||||
@implementation AppDelegate
|
@implementation AppDelegate
|
||||||
|
|
||||||
|
@synthesize window=_window;
|
||||||
|
|
||||||
#if !__has_feature(objc_arc)
|
#if !__has_feature(objc_arc)
|
||||||
|
|
||||||
- (void)dealloc {
|
- (void)dealloc {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright (c) 2012-2013, Pierre-Olivier Latour
|
Copyright (c) 2012-2014, Pierre-Olivier Latour
|
||||||
All rights reserved.
|
All rights reserved.
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
|||||||
Reference in New Issue
Block a user