初始化提交

This commit is contained in:
2026-02-03 16:52:44 +08:00
commit d2f9806384
512 changed files with 65167 additions and 0 deletions

View File

@@ -0,0 +1,12 @@
#import <Foundation/Foundation.h>
@interface NSNumber (DDNumber)
+ (BOOL)parseString:(NSString *)str intoSInt64:(SInt64 *)pNum;
+ (BOOL)parseString:(NSString *)str intoUInt64:(UInt64 *)pNum;
+ (BOOL)parseString:(NSString *)str intoNSInteger:(NSInteger *)pNum;
+ (BOOL)parseString:(NSString *)str intoNSUInteger:(NSUInteger *)pNum;
@end

View File

@@ -0,0 +1,88 @@
#import "DDNumber.h"
@implementation NSNumber (DDNumber)
+ (BOOL)parseString:(NSString *)str intoSInt64:(SInt64 *)pNum
{
if(str == nil)
{
*pNum = 0;
return NO;
}
errno = 0;
// On both 32-bit and 64-bit machines, long long = 64 bit
*pNum = strtoll([str UTF8String], NULL, 10);
if(errno != 0)
return NO;
else
return YES;
}
+ (BOOL)parseString:(NSString *)str intoUInt64:(UInt64 *)pNum
{
if(str == nil)
{
*pNum = 0;
return NO;
}
errno = 0;
// On both 32-bit and 64-bit machines, unsigned long long = 64 bit
*pNum = strtoull([str UTF8String], NULL, 10);
if(errno != 0)
return NO;
else
return YES;
}
+ (BOOL)parseString:(NSString *)str intoNSInteger:(NSInteger *)pNum
{
if(str == nil)
{
*pNum = 0;
return NO;
}
errno = 0;
// On LP64, NSInteger = long = 64 bit
// Otherwise, NSInteger = int = long = 32 bit
*pNum = strtol([str UTF8String], NULL, 10);
if(errno != 0)
return NO;
else
return YES;
}
+ (BOOL)parseString:(NSString *)str intoNSUInteger:(NSUInteger *)pNum
{
if(str == nil)
{
*pNum = 0;
return NO;
}
errno = 0;
// On LP64, NSUInteger = unsigned long = 64 bit
// Otherwise, NSUInteger = unsigned int = unsigned long = 32 bit
*pNum = strtoul([str UTF8String], NULL, 10);
if(errno != 0)
return NO;
else
return YES;
}
@end

View File

@@ -0,0 +1,56 @@
/**
* DDRange is the functional equivalent of a 64 bit NSRange.
* The HTTP Server is designed to support very large files.
* On 32 bit architectures (ppc, i386) NSRange uses unsigned 32 bit integers.
* This only supports a range of up to 4 gigabytes.
* By defining our own variant, we can support a range up to 16 exabytes.
*
* All effort is given such that DDRange functions EXACTLY the same as NSRange.
**/
#import <Foundation/NSValue.h>
#import <Foundation/NSObjCRuntime.h>
@class NSString;
typedef struct _DDRange {
UInt64 location;
UInt64 length;
} DDRange;
typedef DDRange *DDRangePointer;
NS_INLINE DDRange DDMakeRange(UInt64 loc, UInt64 len) {
DDRange r;
r.location = loc;
r.length = len;
return r;
}
NS_INLINE UInt64 DDMaxRange(DDRange range) {
return (range.location + range.length);
}
NS_INLINE BOOL DDLocationInRange(UInt64 loc, DDRange range) {
return (loc - range.location < range.length);
}
NS_INLINE BOOL DDEqualRanges(DDRange range1, DDRange range2) {
return ((range1.location == range2.location) && (range1.length == range2.length));
}
FOUNDATION_EXPORT DDRange DDUnionRange(DDRange range1, DDRange range2);
FOUNDATION_EXPORT DDRange DDIntersectionRange(DDRange range1, DDRange range2);
FOUNDATION_EXPORT NSString *DDStringFromRange(DDRange range);
FOUNDATION_EXPORT DDRange DDRangeFromString(NSString *aString);
NSInteger DDRangeCompare(DDRangePointer pDDRange1, DDRangePointer pDDRange2);
@interface NSValue (NSValueDDRangeExtensions)
+ (NSValue *)valueWithDDRange:(DDRange)range;
- (DDRange)ddrangeValue;
- (NSInteger)ddrangeCompare:(NSValue *)ddrangeValue;
@end

View File

@@ -0,0 +1,106 @@
#import "DDRange.h"
#import "DDNumber.h"
#pragma clang diagnostic ignored "-Wformat-non-iso"
DDRange DDUnionRange(DDRange range1, DDRange range2)
{
DDRange result;
result.location = MIN(range1.location, range2.location);
result.length = MAX(DDMaxRange(range1), DDMaxRange(range2)) - result.location;
return result;
}
DDRange DDIntersectionRange(DDRange range1, DDRange range2)
{
DDRange result;
if((DDMaxRange(range1) < range2.location) || (DDMaxRange(range2) < range1.location))
{
return DDMakeRange(0, 0);
}
result.location = MAX(range1.location, range2.location);
result.length = MIN(DDMaxRange(range1), DDMaxRange(range2)) - result.location;
return result;
}
NSString *DDStringFromRange(DDRange range)
{
return [NSString stringWithFormat:@"{%qu, %qu}", range.location, range.length];
}
DDRange DDRangeFromString(NSString *aString)
{
DDRange result = DDMakeRange(0, 0);
// NSRange will ignore '-' characters, but not '+' characters
NSCharacterSet *cset = [NSCharacterSet characterSetWithCharactersInString:@"+0123456789"];
NSScanner *scanner = [NSScanner scannerWithString:aString];
[scanner setCharactersToBeSkipped:[cset invertedSet]];
NSString *str1 = nil;
NSString *str2 = nil;
BOOL found1 = [scanner scanCharactersFromSet:cset intoString:&str1];
BOOL found2 = [scanner scanCharactersFromSet:cset intoString:&str2];
if(found1) [NSNumber parseString:str1 intoUInt64:&result.location];
if(found2) [NSNumber parseString:str2 intoUInt64:&result.length];
return result;
}
NSInteger DDRangeCompare(DDRangePointer pDDRange1, DDRangePointer pDDRange2)
{
// Comparison basis:
// Which range would you encouter first if you started at zero, and began walking towards infinity.
// If you encouter both ranges at the same time, which range would end first.
if(pDDRange1->location < pDDRange2->location)
{
return NSOrderedAscending;
}
if(pDDRange1->location > pDDRange2->location)
{
return NSOrderedDescending;
}
if(pDDRange1->length < pDDRange2->length)
{
return NSOrderedAscending;
}
if(pDDRange1->length > pDDRange2->length)
{
return NSOrderedDescending;
}
return NSOrderedSame;
}
@implementation NSValue (NSValueDDRangeExtensions)
+ (NSValue *)valueWithDDRange:(DDRange)range
{
return [NSValue valueWithBytes:&range objCType:@encode(DDRange)];
}
- (DDRange)ddrangeValue
{
DDRange result;
[self getValue:&result];
return result;
}
- (NSInteger)ddrangeCompare:(NSValue *)other
{
DDRange r1 = [self ddrangeValue];
DDRange r2 = [other ddrangeValue];
return DDRangeCompare(&r1, &r2);
}
@end

View File

@@ -0,0 +1,107 @@
#import <Foundation/Foundation.h>
@class GCDAsyncSocket;
@class HTTPMessage;
@class HTTPServer;
@class WebSocket;
@protocol HTTPResponse;
#define HTTPConnectionDidDieNotification @"HTTPConnectionDidDie"
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@interface HTTPConfig : NSObject
{
HTTPServer __unsafe_unretained *server;
NSString __strong *documentRoot;
dispatch_queue_t queue;
}
- (id)initWithServer:(HTTPServer *)server documentRoot:(NSString *)documentRoot;
- (id)initWithServer:(HTTPServer *)server documentRoot:(NSString *)documentRoot queue:(dispatch_queue_t)q;
@property (nonatomic, unsafe_unretained, readonly) HTTPServer *server;
@property (nonatomic, strong, readonly) NSString *documentRoot;
@property (nonatomic, readonly) dispatch_queue_t queue;
@end
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@interface HTTPConnection : NSObject
{
dispatch_queue_t connectionQueue;
GCDAsyncSocket *asyncSocket;
HTTPConfig *config;
BOOL started;
HTTPMessage *request;
unsigned int numHeaderLines;
BOOL sentResponseHeaders;
NSObject<HTTPResponse> *httpResponse;
NSMutableArray *ranges;
NSMutableArray *ranges_headers;
NSString *ranges_boundry;
int rangeIndex;
UInt64 requestContentLength;
UInt64 requestContentLengthReceived;
UInt64 requestChunkSize;
UInt64 requestChunkSizeReceived;
NSMutableArray *responseDataSizes;
}
- (id)initWithAsyncSocket:(GCDAsyncSocket *)newSocket configuration:(HTTPConfig *)aConfig;
- (void)start;
- (void)stop;
- (void)startConnection;
- (BOOL)supportsMethod:(NSString *)method atPath:(NSString *)path;
- (BOOL)expectsRequestBodyFromMethod:(NSString *)method atPath:(NSString *)path;
- (NSDictionary *)parseParams:(NSString *)query;
- (NSDictionary *)parseGetParams;
- (NSString *)requestURI;
- (NSArray *)directoryIndexFileNames;
- (NSString *)filePathForURI:(NSString *)path;
- (NSString *)filePathForURI:(NSString *)path allowDirectory:(BOOL)allowDirectory;
- (NSObject<HTTPResponse> *)httpResponseForMethod:(NSString *)method URI:(NSString *)path;
- (WebSocket *)webSocketForURI:(NSString *)path;
- (void)prepareForBodyWithSize:(UInt64)contentLength;
- (void)processBodyData:(NSData *)postDataChunk;
- (void)finishBody;
- (void)handleVersionNotSupported:(NSString *)version;
- (void)handleResourceNotFound;
- (void)handleInvalidRequest:(NSData *)data;
- (void)handleUnknownMethod:(NSString *)method;
- (NSData *)preprocessResponse:(HTTPMessage *)response;
- (NSData *)preprocessErrorResponse:(HTTPMessage *)response;
- (void)finishResponse;
- (BOOL)shouldDie;
- (void)die;
@end
@interface HTTPConnection (AsynchronousHTTPResponse)
- (void)responseHasAvailableData:(NSObject<HTTPResponse> *)sender;
- (void)responseDidAbort:(NSObject<HTTPResponse> *)sender;
@end

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,122 @@
/**
* In order to provide fast and flexible logging, this project uses Cocoa Lumberjack.
*
* The Google Code page has a wealth of documentation if you have any questions.
* https://github.com/robbiehanson/CocoaLumberjack
*
* Here's what you need to know concerning how logging is setup for CocoaHTTPServer:
*
* There are 4 log levels:
* - Error
* - Warning
* - Info
* - Verbose
*
* In addition to this, there is a Trace flag that can be enabled.
* When tracing is enabled, it spits out the methods that are being called.
*
* Please note that tracing is separate from the log levels.
* For example, one could set the log level to warning, and enable tracing.
*
* All logging is asynchronous, except errors.
* To use logging within your own custom files, follow the steps below.
*
* Step 1:
* Import this header in your implementation file:
*
* #import "HTTPLogging.h"
*
* Step 2:
* Define your logging level in your implementation file:
*
* // Log levels: off, error, warn, info, verbose
* static const int httpLogLevel = HTTP_LOG_LEVEL_VERBOSE;
*
* If you wish to enable tracing, you could do something like this:
*
* // Debug levels: off, error, warn, info, verbose
* static const int httpLogLevel = HTTP_LOG_LEVEL_INFO | HTTP_LOG_FLAG_TRACE;
*
* Step 3:
* Replace your NSLog statements with HTTPLog statements according to the severity of the message.
*
* NSLog(@"Fatal error, no dohickey found!"); -> HTTPLogError(@"Fatal error, no dohickey found!");
*
* HTTPLog works exactly the same as NSLog.
* This means you can pass it multiple variables just like NSLog.
**/
// Define logging context for every log message coming from the HTTP server.
// The logging context can be extracted from the DDLogMessage from within the logging framework,
// which gives loggers, formatters, and filters the ability to optionally process them differently.
#define HTTP_LOG_CONTEXT 80
// Configure log levels.
#define HTTP_LOG_FLAG_ERROR (1 << 0) // 0...00001
#define HTTP_LOG_FLAG_WARN (1 << 1) // 0...00010
#define HTTP_LOG_FLAG_INFO (1 << 2) // 0...00100
#define HTTP_LOG_FLAG_VERBOSE (1 << 3) // 0...01000
#define HTTP_LOG_LEVEL_OFF 0 // 0...00000
#define HTTP_LOG_LEVEL_ERROR (HTTP_LOG_LEVEL_OFF | HTTP_LOG_FLAG_ERROR) // 0...00001
#define HTTP_LOG_LEVEL_WARN (HTTP_LOG_LEVEL_ERROR | HTTP_LOG_FLAG_WARN) // 0...00011
#define HTTP_LOG_LEVEL_INFO (HTTP_LOG_LEVEL_WARN | HTTP_LOG_FLAG_INFO) // 0...00111
#define HTTP_LOG_LEVEL_VERBOSE (HTTP_LOG_LEVEL_INFO | HTTP_LOG_FLAG_VERBOSE) // 0...01111
// Setup fine grained logging.
// The first 4 bits are being used by the standard log levels (0 - 3)
//
// We're going to add tracing, but NOT as a log level.
// Tracing can be turned on and off independently of log level.
#define HTTP_LOG_FLAG_TRACE (1 << 4) // 0...10000
// Setup the usual boolean macros.
#define HTTP_LOG_ERROR (httpLogLevel & HTTP_LOG_FLAG_ERROR)
#define HTTP_LOG_WARN (httpLogLevel & HTTP_LOG_FLAG_WARN)
#define HTTP_LOG_INFO (httpLogLevel & HTTP_LOG_FLAG_INFO)
#define HTTP_LOG_VERBOSE (httpLogLevel & HTTP_LOG_FLAG_VERBOSE)
#define HTTP_LOG_TRACE (httpLogLevel & HTTP_LOG_FLAG_TRACE)
// Configure asynchronous logging.
// We follow the default configuration,
// but we reserve a special macro to easily disable asynchronous logging for debugging purposes.
#define HTTP_LOG_ASYNC_ENABLED YES
#define HTTP_LOG_ASYNC_ERROR ( NO && HTTP_LOG_ASYNC_ENABLED)
#define HTTP_LOG_ASYNC_WARN (YES && HTTP_LOG_ASYNC_ENABLED)
#define HTTP_LOG_ASYNC_INFO (YES && HTTP_LOG_ASYNC_ENABLED)
#define HTTP_LOG_ASYNC_VERBOSE (YES && HTTP_LOG_ASYNC_ENABLED)
#define HTTP_LOG_ASYNC_TRACE (YES && HTTP_LOG_ASYNC_ENABLED)
// Define logging primitives.
#define HTTPLogError(...) { }
#define HTTPLogWarn(...) { }
#define HTTPLogInfo(...) { }
#define HTTPLogVerbose(...) { }
#define HTTPLogTrace() { }
#define HTTPLogTrace2(...) { }
#define HTTPLogCError(...) { }
#define HTTPLogCWarn(...) { }
#define HTTPLogCInfo(...) { }
#define HTTPLogCVerbose(...) { }
#define HTTPLogCTrace() { }
#define HTTPLogCTrace2(...) { }

View File

@@ -0,0 +1,48 @@
/**
* The HTTPMessage class is a simple Objective-C wrapper around Apple's CFHTTPMessage class.
**/
#import <Foundation/Foundation.h>
#if TARGET_OS_IPHONE
// Note: You may need to add the CFNetwork Framework to your project
#import <CFNetwork/CFNetwork.h>
#endif
#define HTTPVersion1_0 ((NSString *)kCFHTTPVersion1_0)
#define HTTPVersion1_1 ((NSString *)kCFHTTPVersion1_1)
@interface HTTPMessage : NSObject
{
CFHTTPMessageRef message;
}
- (id)initEmptyRequest;
- (id)initRequestWithMethod:(NSString *)method URL:(NSURL *)url version:(NSString *)version;
- (id)initResponseWithStatusCode:(NSInteger)code description:(NSString *)description version:(NSString *)version;
- (BOOL)appendData:(NSData *)data;
- (BOOL)isHeaderComplete;
- (NSString *)version;
- (NSString *)method;
- (NSURL *)url;
- (NSInteger)statusCode;
- (NSDictionary *)allHeaderFields;
- (NSString *)headerField:(NSString *)headerField;
- (void)setHeaderField:(NSString *)headerField value:(NSString *)headerFieldValue;
- (NSData *)messageData;
- (NSData *)body;
- (void)setBody:(NSData *)body;
@end

View File

@@ -0,0 +1,114 @@
#import "HTTPMessage.h"
#if ! __has_feature(objc_arc)
#warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
#endif
#pragma clang diagnostic ignored "-Wdirect-ivar-access"
@implementation HTTPMessage
- (id)initEmptyRequest
{
if ((self = [super init]))
{
message = CFHTTPMessageCreateEmpty(NULL, YES);
}
return self;
}
- (id)initRequestWithMethod:(NSString *)method URL:(NSURL *)url version:(NSString *)version
{
if ((self = [super init]))
{
message = CFHTTPMessageCreateRequest(NULL,
(__bridge CFStringRef)method,
(__bridge CFURLRef)url,
(__bridge CFStringRef)version);
}
return self;
}
- (id)initResponseWithStatusCode:(NSInteger)code description:(NSString *)description version:(NSString *)version
{
if ((self = [super init]))
{
message = CFHTTPMessageCreateResponse(NULL,
(CFIndex)code,
(__bridge CFStringRef)description,
(__bridge CFStringRef)version);
}
return self;
}
- (void)dealloc
{
if (message)
{
CFRelease(message);
}
}
- (BOOL)appendData:(NSData *)data
{
return CFHTTPMessageAppendBytes(message, [data bytes], [data length]);
}
- (BOOL)isHeaderComplete
{
return CFHTTPMessageIsHeaderComplete(message);
}
- (NSString *)version
{
return (__bridge_transfer NSString *)CFHTTPMessageCopyVersion(message);
}
- (NSString *)method
{
return (__bridge_transfer NSString *)CFHTTPMessageCopyRequestMethod(message);
}
- (NSURL *)url
{
return (__bridge_transfer NSURL *)CFHTTPMessageCopyRequestURL(message);
}
- (NSInteger)statusCode
{
return (NSInteger)CFHTTPMessageGetResponseStatusCode(message);
}
- (NSDictionary *)allHeaderFields
{
return (__bridge_transfer NSDictionary *)CFHTTPMessageCopyAllHeaderFields(message);
}
- (NSString *)headerField:(NSString *)headerField
{
return (__bridge_transfer NSString *)CFHTTPMessageCopyHeaderFieldValue(message, (__bridge CFStringRef)headerField);
}
- (void)setHeaderField:(NSString *)headerField value:(NSString *)headerFieldValue
{
CFHTTPMessageSetHeaderFieldValue(message,
(__bridge CFStringRef)headerField,
(__bridge CFStringRef)headerFieldValue);
}
- (NSData *)messageData
{
return (__bridge_transfer NSData *)CFHTTPMessageCopySerializedMessage(message);
}
- (NSData *)body
{
return (__bridge_transfer NSData *)CFHTTPMessageCopyBody(message);
}
- (void)setBody:(NSData *)body
{
CFHTTPMessageSetBody(message, (__bridge CFDataRef)body);
}
@end

View File

@@ -0,0 +1,149 @@
#import <Foundation/Foundation.h>
@protocol HTTPResponse
/**
* Returns the length of the data in bytes.
* If you don't know the length in advance, implement the isChunked method and have it return YES.
**/
- (UInt64)contentLength;
/**
* The HTTP server supports range requests in order to allow things like
* file download resumption and optimized streaming on mobile devices.
**/
- (UInt64)offset;
- (void)setOffset:(UInt64)offset;
/**
* Returns the data for the response.
* You do not have to return data of the exact length that is given.
* You may optionally return data of a lesser length.
* However, you must never return data of a greater length than requested.
* Doing so could disrupt proper support for range requests.
*
* To support asynchronous responses, read the discussion at the bottom of this header.
**/
- (NSData *)readDataOfLength:(NSUInteger)length;
/**
* Should only return YES after the HTTPConnection has read all available data.
* That is, all data for the response has been returned to the HTTPConnection via the readDataOfLength method.
**/
- (BOOL)isDone;
@optional
/**
* If you need time to calculate any part of the HTTP response headers (status code or header fields),
* this method allows you to delay sending the headers so that you may asynchronously execute the calculations.
* Simply implement this method and return YES until you have everything you need concerning the headers.
*
* This method ties into the asynchronous response architecture of the HTTPConnection.
* You should read the full discussion at the bottom of this header.
*
* If you return YES from this method,
* the HTTPConnection will wait for you to invoke the responseHasAvailableData method.
* After you do, the HTTPConnection will again invoke this method to see if the response is ready to send the headers.
*
* You should only delay sending the headers until you have everything you need concerning just the headers.
* Asynchronously generating the body of the response is not an excuse to delay sending the headers.
* Instead you should tie into the asynchronous response architecture, and use techniques such as the isChunked method.
*
* Important: You should read the discussion at the bottom of this header.
**/
- (BOOL)delayResponseHeaders;
/**
* Status code for response.
* Allows for responses such as redirect (301), etc.
**/
- (NSInteger)status;
/**
* If you want to add any extra HTTP headers to the response,
* simply return them in a dictionary in this method.
**/
- (NSDictionary *)httpHeaders;
/**
* If you don't know the content-length in advance,
* implement this method in your custom response class and return YES.
*
* Important: You should read the discussion at the bottom of this header.
**/
- (BOOL)isChunked;
/**
* This method is called from the HTTPConnection class when the connection is closed,
* or when the connection is finished with the response.
* If your response is asynchronous, you should implement this method so you know not to
* invoke any methods on the HTTPConnection after this method is called (as the connection may be deallocated).
**/
- (void)connectionDidClose;
@end
/**
* Important notice to those implementing custom asynchronous and/or chunked responses:
*
* HTTPConnection supports asynchronous responses. All you have to do in your custom response class is
* asynchronously generate the response, and invoke HTTPConnection's responseHasAvailableData method.
* You don't have to wait until you have all of the response ready to invoke this method. For example, if you
* generate the response in incremental chunks, you could call responseHasAvailableData after generating
* each chunk. Please see the HTTPAsyncFileResponse class for an example of how to do this.
*
* The normal flow of events for an HTTPConnection while responding to a request is like this:
* - Send http resopnse headers
* - Get data from response via readDataOfLength method.
* - Add data to asyncSocket's write queue.
* - Wait for asyncSocket to notify it that the data has been sent.
* - Get more data from response via readDataOfLength method.
* - ... continue this cycle until the entire response has been sent.
*
* With an asynchronous response, the flow is a little different.
*
* First the HTTPResponse is given the opportunity to postpone sending the HTTP response headers.
* This allows the response to asynchronously execute any code needed to calculate a part of the header.
* An example might be the response needs to generate some custom header fields,
* or perhaps the response needs to look for a resource on network-attached storage.
* Since the network-attached storage may be slow, the response doesn't know whether to send a 200 or 404 yet.
* In situations such as this, the HTTPResponse simply implements the delayResponseHeaders method and returns YES.
* After returning YES from this method, the HTTPConnection will wait until the response invokes its
* responseHasAvailableData method. After this occurs, the HTTPConnection will again query the delayResponseHeaders
* method to see if the response is ready to send the headers.
* This cycle will continue until the delayResponseHeaders method returns NO.
*
* You should only delay sending the response headers until you have everything you need concerning just the headers.
* Asynchronously generating the body of the response is not an excuse to delay sending the headers.
*
* After the response headers have been sent, the HTTPConnection calls your readDataOfLength method.
* You may or may not have any available data at this point. If you don't, then simply return nil.
* You should later invoke HTTPConnection's responseHasAvailableData when you have data to send.
*
* You don't have to keep track of when you return nil in the readDataOfLength method, or how many times you've invoked
* responseHasAvailableData. Just simply call responseHasAvailableData whenever you've generated new data, and
* return nil in your readDataOfLength whenever you don't have any available data in the requested range.
* HTTPConnection will automatically detect when it should be requesting new data and will act appropriately.
*
* It's important that you also keep in mind that the HTTP server supports range requests.
* The setOffset method is mandatory, and should not be ignored.
* Make sure you take into account the offset within the readDataOfLength method.
* You should also be aware that the HTTPConnection automatically sorts any range requests.
* So if your setOffset method is called with a value of 100, then you can safely release bytes 0-99.
*
* HTTPConnection can also help you keep your memory footprint small.
* Imagine you're dynamically generating a 10 MB response. You probably don't want to load all this data into
* RAM, and sit around waiting for HTTPConnection to slowly send it out over the network. All you need to do
* is pay attention to when HTTPConnection requests more data via readDataOfLength. This is because HTTPConnection
* will never allow asyncSocket's write queue to get much bigger than READ_CHUNKSIZE bytes. You should
* consider how you might be able to take advantage of this fact to generate your asynchronous response on demand,
* while at the same time keeping your memory footprint small, and your application lightning fast.
*
* If you don't know the content-length in advanced, you should also implement the isChunked method.
* This means the response will not include a Content-Length header, and will instead use "Transfer-Encoding: chunked".
* There's a good chance that if your response is asynchronous and dynamic, it's also chunked.
* If your response is chunked, you don't need to worry about range requests.
**/

View File

@@ -0,0 +1,126 @@
#import <Foundation/Foundation.h>
@class GCDAsyncSocket;
@class WebSocket;
#if TARGET_OS_IPHONE
#define IMPLEMENTED_PROTOCOLS
#else
#define IMPLEMENTED_PROTOCOLS
#endif
@interface HTTPServer : NSObject IMPLEMENTED_PROTOCOLS
{
// Underlying asynchronous TCP/IP socket
GCDAsyncSocket *asyncSocket;
// Dispatch queues
dispatch_queue_t serverQueue;
dispatch_queue_t connectionQueue;
void *IsOnServerQueueKey;
void *IsOnConnectionQueueKey;
// HTTP server configuration
NSString *documentRoot;
Class connectionClass;
NSString *interface;
UInt16 port;
// Connection management
NSMutableArray *connections;
NSLock *connectionsLock;
BOOL isRunning;
}
/**
* Specifies the document root to serve files from.
* For example, if you set this to "/Users/<your_username>/Sites",
* then it will serve files out of the local Sites directory (including subdirectories).
*
* The default value is nil.
* The default server configuration will not serve any files until this is set.
*
* If you change the documentRoot while the server is running,
* the change will affect future incoming http connections.
**/
- (NSString *)documentRoot;
- (void)setDocumentRoot:(NSString *)value;
/**
* The connection class is the class used to handle incoming HTTP connections.
*
* The default value is [HTTPConnection class].
* You can override HTTPConnection, and then set this to [MyHTTPConnection class].
*
* If you change the connectionClass while the server is running,
* the change will affect future incoming http connections.
**/
- (Class)connectionClass;
- (void)setConnectionClass:(Class)value;
/**
* Set what interface you'd like the server to listen on.
* By default this is nil, which causes the server to listen on all available interfaces like en1, wifi etc.
*
* The interface may be specified by name (e.g. "en1" or "lo0") or by IP address (e.g. "192.168.4.34").
* You may also use the special strings "localhost" or "loopback" to specify that
* the socket only accept connections from the local machine.
**/
- (NSString *)interface;
- (void)setInterface:(NSString *)value;
/**
* The port number to run the HTTP server on.
*
* The default port number is zero, meaning the server will automatically use any available port.
* This is the recommended port value, as it avoids possible port conflicts with other applications.
* Technologies such as Bonjour can be used to allow other applications to automatically discover the port number.
*
* Note: As is common on most OS's, you need root privledges to bind to port numbers below 1024.
*
* You can change the port property while the server is running, but it won't affect the running server.
* To actually change the port the server is listening for connections on you'll need to restart the server.
*
* The listeningPort method will always return the port number the running server is listening for connections on.
* If the server is not running this method returns 0.
**/
- (UInt16)port;
- (UInt16)listeningPort;
- (void)setPort:(UInt16)value;
/**
* Attempts to starts the server on the configured port, interface, etc.
*
* If an error occurs, this method returns NO and sets the errPtr (if given).
* Otherwise returns YES on success.
*
* Some examples of errors that might occur:
* - You specified the server listen on a port which is already in use by another application.
* - You specified the server listen on a port number below 1024, which requires root priviledges.
*
* Code Example:
*
* NSError *err = nil;
* if (![httpServer start:&err])
* {
* NSLog(@"Error starting http server: %@", err);
* }
**/
- (BOOL)start:(NSError **)errPtr;
/**
* Stops the server, preventing it from accepting any new connections.
* You may specify whether or not you want to close the existing client connections.
*
* The default stop method (with no arguments) will close any existing connections. (It invokes [self stop:NO])
**/
- (void)stop;
- (void)stop:(BOOL)keepExistingConnections;
- (BOOL)isRunning;
- (NSUInteger)numberOfHTTPConnections;
@end

View File

@@ -0,0 +1,372 @@
#import "HTTPServer.h"
#import "HTTPConnection.h"
#import "HTTPLogging.h"
#import "GCDAsyncSocket.h"
#if ! __has_feature(objc_arc)
#warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
#endif
#pragma clang diagnostic ignored "-Wdirect-ivar-access"
#pragma clang diagnostic ignored "-Wimplicit-retain-self"
#pragma clang diagnostic ignored "-Wnullable-to-nonnull-conversion"
#pragma clang diagnostic ignored "-Wunused"
// Log levels: off, error, warn, info, verbose
// Other flags: trace
static const int httpLogLevel = HTTP_LOG_LEVEL_INFO; // | HTTP_LOG_FLAG_TRACE;
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@implementation HTTPServer
/**
* Standard Constructor.
* Instantiates an HTTP server, but does not start it.
**/
- (id)init
{
if ((self = [super init]))
{
HTTPLogTrace();
// Setup underlying dispatch queues
serverQueue = dispatch_queue_create("HTTPServer", NULL);
connectionQueue = dispatch_queue_create("HTTPConnection", NULL);
IsOnServerQueueKey = &IsOnServerQueueKey;
IsOnConnectionQueueKey = &IsOnConnectionQueueKey;
void *nonNullUnusedPointer = (__bridge void *)self; // Whatever, just not null
dispatch_queue_set_specific(serverQueue, IsOnServerQueueKey, nonNullUnusedPointer, NULL);
dispatch_queue_set_specific(connectionQueue, IsOnConnectionQueueKey, nonNullUnusedPointer, NULL);
// Initialize underlying GCD based tcp socket
asyncSocket = [[GCDAsyncSocket alloc] initWithDelegate:(id<GCDAsyncSocketDelegate>)self delegateQueue:serverQueue];
// Use default connection class of HTTPConnection
connectionClass = [HTTPConnection self];
// By default bind on all available interfaces, en1, wifi etc
interface = nil;
// Use a default port of 0
// This will allow the kernel to automatically pick an open port for us
port = 0;
// Initialize arrays to hold all the HTTP connections
connections = [[NSMutableArray alloc] init];
connectionsLock = [[NSLock alloc] init];
// Register for notifications of closed connections
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(connectionDidDie:)
name:HTTPConnectionDidDieNotification
object:nil];
isRunning = NO;
}
return self;
}
/**
* Standard Deconstructor.
* Stops the server, and clients, and releases any resources connected with this instance.
**/
- (void)dealloc
{
HTTPLogTrace();
// Remove notification observer
[[NSNotificationCenter defaultCenter] removeObserver:self];
// Stop the server if it's running
[self stop];
// Release all instance variables
#if !OS_OBJECT_USE_OBJC
dispatch_release(serverQueue);
dispatch_release(connectionQueue);
#endif
[asyncSocket setDelegate:nil delegateQueue:NULL];
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Server Configuration
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**
* The document root is filesystem root for the webserver.
* Thus requests for /index.html will be referencing the index.html file within the document root directory.
* All file requests are relative to this document root.
**/
- (NSString *)documentRoot
{
__block NSString *result;
dispatch_sync(serverQueue, ^{
result = documentRoot;
});
return result;
}
- (void)setDocumentRoot:(NSString *)value
{
HTTPLogTrace();
// Document root used to be of type NSURL.
// Add type checking for early warning to developers upgrading from older versions.
if (value && ![value isKindOfClass:[NSString class]])
{
HTTPLogWarn(@"%@: %@ - Expecting NSString parameter, received %@ parameter",
THIS_FILE, THIS_METHOD, NSStringFromClass([value class]));
return;
}
NSString *valueCopy = [value copy];
dispatch_async(serverQueue, ^{
documentRoot = valueCopy;
});
}
/**
* The connection class is the class that will be used to handle connections.
* That is, when a new connection is created, an instance of this class will be intialized.
* The default connection class is HTTPConnection.
* If you use a different connection class, it is assumed that the class extends HTTPConnection
**/
- (Class)connectionClass
{
__block Class result;
dispatch_sync(serverQueue, ^{
result = connectionClass;
});
return result;
}
- (void)setConnectionClass:(Class)value
{
HTTPLogTrace();
dispatch_async(serverQueue, ^{
connectionClass = value;
});
}
/**
* What interface to bind the listening socket to.
**/
- (NSString *)interface
{
__block NSString *result;
dispatch_sync(serverQueue, ^{
result = interface;
});
return result;
}
- (void)setInterface:(NSString *)value
{
NSString *valueCopy = [value copy];
dispatch_async(serverQueue, ^{
interface = valueCopy;
});
}
/**
* The port to listen for connections on.
* By default this port is initially set to zero, which allows the kernel to pick an available port for us.
* After the HTTP server has started, the port being used may be obtained by this method.
**/
- (UInt16)port
{
__block UInt16 result;
dispatch_sync(serverQueue, ^{
result = port;
});
return result;
}
- (UInt16)listeningPort
{
__block UInt16 result;
dispatch_sync(serverQueue, ^{
if (isRunning)
result = [asyncSocket localPort];
else
result = 0;
});
return result;
}
- (void)setPort:(UInt16)value
{
HTTPLogTrace();
dispatch_async(serverQueue, ^{
port = value;
});
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Server Control
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- (BOOL)start:(NSError **)errPtr
{
HTTPLogTrace();
__block BOOL success = YES;
__block NSError *err = nil;
dispatch_sync(serverQueue, ^{ @autoreleasepool {
success = [asyncSocket acceptOnInterface:interface port:port error:&err];
if (success)
{
HTTPLogInfo(@"%@: Started HTTP server on port %hu", THIS_FILE, [asyncSocket localPort]);
isRunning = YES;
}
else
{
HTTPLogError(@"%@: Failed to start HTTP Server: %@", THIS_FILE, err);
}
}});
if (errPtr)
*errPtr = err;
return success;
}
- (void)stop
{
[self stop:NO];
}
- (void)stop:(BOOL)keepExistingConnections
{
HTTPLogTrace();
dispatch_sync(serverQueue, ^{ @autoreleasepool {
// Stop listening / accepting incoming connections
[asyncSocket disconnect];
isRunning = NO;
if (!keepExistingConnections)
{
// Stop all HTTP connections the server owns
[connectionsLock lock];
for (HTTPConnection *connection in connections)
{
[connection stop];
}
[connections removeAllObjects];
[connectionsLock unlock];
}
}});
}
- (BOOL)isRunning
{
__block BOOL result;
dispatch_sync(serverQueue, ^{
result = isRunning;
});
return result;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Server Status
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**
* Returns the number of http client connections that are currently connected to the server.
**/
- (NSUInteger)numberOfHTTPConnections
{
NSUInteger result = 0;
[connectionsLock lock];
result = [connections count];
[connectionsLock unlock];
return result;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Incoming Connections
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- (HTTPConfig *)config
{
// Override me if you want to provide a custom config to the new connection.
//
// Generally this involves overriding the HTTPConfig class to include any custom settings,
// and then having this method return an instance of 'MyHTTPConfig'.
// Note: Think you can make the server faster by putting each connection on its own queue?
// Then benchmark it before and after and discover for yourself the shocking truth!
//
// Try the apache benchmark tool (already installed on your Mac):
// $ ab -n 1000 -c 1 http://localhost:<port>/some_path.html
return [[HTTPConfig alloc] initWithServer:self documentRoot:documentRoot queue:connectionQueue];
}
- (void)socket:(GCDAsyncSocket *)sock didAcceptNewSocket:(GCDAsyncSocket *)newSocket
{
HTTPConnection *newConnection = (HTTPConnection *)[[connectionClass alloc] initWithAsyncSocket:newSocket
configuration:[self config]];
[connectionsLock lock];
[connections addObject:newConnection];
[connectionsLock unlock];
[newConnection start];
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Notifications
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**
* This method is automatically called when a notification of type HTTPConnectionDidDieNotification is posted.
* It allows us to remove the connection from our array.
**/
- (void)connectionDidDie:(NSNotification *)notification
{
// Note: This method is called on the connection queue that posted the notification
[connectionsLock lock];
HTTPLogTrace();
[connections removeObject:[notification object]];
[connectionsLock unlock];
}
@end

View File

@@ -0,0 +1,18 @@
Software License Agreement (BSD License)
Copyright (c) 2011, Deusty, LLC
All rights reserved.
Redistribution and use of this software in source and binary forms,
with or without modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above
copyright notice, this list of conditions and the
following disclaimer.
* Neither the name of Deusty nor the names of its
contributors may be used to endorse or promote products
derived from this software without specific prior
written permission of Deusty, LLC.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@@ -0,0 +1,13 @@
#import <Foundation/Foundation.h>
#import "HTTPResponse.h"
@interface HTTPDataResponse : NSObject <HTTPResponse>
{
NSUInteger offset;
NSData *data;
}
- (id)initWithData:(NSData *)data;
@end

View File

@@ -0,0 +1,83 @@
#import "HTTPDataResponse.h"
#import "HTTPLogging.h"
#if ! __has_feature(objc_arc)
#warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
#endif
#pragma clang diagnostic ignored "-Wdirect-ivar-access"
#pragma clang diagnostic ignored "-Wcast-qual"
#pragma clang diagnostic ignored "-Wunused-variable"
// Log levels : off, error, warn, info, verbose
// Other flags: trace
static const int httpLogLevel = HTTP_LOG_LEVEL_OFF; // | HTTP_LOG_FLAG_TRACE;
@implementation HTTPDataResponse
- (id)initWithData:(NSData *)dataParam
{
if((self = [super init]))
{
HTTPLogTrace();
offset = 0;
data = dataParam;
}
return self;
}
- (void)dealloc
{
HTTPLogTrace();
}
- (UInt64)contentLength
{
UInt64 result = (UInt64)[data length];
HTTPLogTrace2(@"%@[%p]: contentLength - %llu", THIS_FILE, self, result);
return result;
}
- (UInt64)offset
{
HTTPLogTrace();
return offset;
}
- (void)setOffset:(UInt64)offsetParam
{
HTTPLogTrace2(@"%@[%p]: setOffset:%lu", THIS_FILE, self, (unsigned long)offset);
offset = (NSUInteger)offsetParam;
}
- (NSData *)readDataOfLength:(NSUInteger)lengthParameter
{
HTTPLogTrace2(@"%@[%p]: readDataOfLength:%lu", THIS_FILE, self, (unsigned long)lengthParameter);
NSUInteger remaining = [data length] - offset;
NSUInteger length = lengthParameter < remaining ? lengthParameter : remaining;
void *bytes = (void *)(((char*)[data bytes]) + offset);
offset += length;
return [NSData dataWithBytesNoCopy:bytes length:length freeWhenDone:NO];
}
- (BOOL)isDone
{
BOOL result = (offset == [data length]);
HTTPLogTrace2(@"%@[%p]: isDone - %@", THIS_FILE, self, (result ? @"YES" : @"NO"));
return result;
}
@end

View File

@@ -0,0 +1,9 @@
#import "HTTPResponse.h"
@interface HTTPErrorResponse : NSObject <HTTPResponse> {
NSInteger _status;
}
- (id)initWithErrorCode:(int)httpErrorCode;
@end

View File

@@ -0,0 +1,40 @@
#import "HTTPErrorResponse.h"
#pragma clang diagnostic ignored "-Wdirect-ivar-access"
@implementation HTTPErrorResponse
-(id)initWithErrorCode:(int)httpErrorCode
{
if ((self = [super init]))
{
_status = httpErrorCode;
}
return self;
}
- (UInt64) contentLength {
return 0;
}
- (UInt64) offset {
return 0;
}
- (void)setOffset:(UInt64)offset {
;
}
- (NSData*) readDataOfLength:(NSUInteger)length {
return nil;
}
- (BOOL) isDone {
return YES;
}
- (NSInteger) status {
return _status;
}
@end