Hello Robbiehanson,
To start, I am really really new to IOS / Objective C.
I am given a project which already uses your server. The enhancement needed to decrypt a progressive download video. In my research a couple of people have asked you and implemented this, but I could not find any code.
So I went on and mucked HTTPAsyncFileResponse myself. Now while waiting for my hairs to grow back, the code works fine. At this state (my knowledge about the language and framework is almost nil) I am satisfied with this code.
This code is for anyone who don't want to start clean. and over the top of your head if anyone see any big issues/leaks please comment.
HTTPConnection.m
- (void)sendResponseHeadersAndBody
{
...
if ([ranges count] == 1)
{
if(remote){
HTTPProxyResponse * proxyResponse = (HTTPProxyResponse *)httpResponse;
response = [proxyResponse remoteResponse];
}
else
{
response = [self newUniRangeResponse:contentLength];
}
}
else
{
response = [self newMultiRangeResponse:contentLength];
}
...
}
HTTPProxyResponse.h
#import <Foundation/Foundation.h>
#import "HTTPConnection.h"
#import "HTTPResponse.h"
#import "HTTPMessage.h"
#import "GCDAsyncSocket.h"
@class HTTPConnection;
@class GCDAsyncSocket;
@interface HTTPProxyResponse : NSObject <HTTPResponse>
{
dispatch_queue_t socketQueue;
GCDAsyncSocket *asyncSocket;
HTTPMessage *localRequest;
HTTPMessage __strong *remoteResponse;
unsigned int numHeaderLines;
HTTPConnection *connection;
UInt64 fileLength; // Actual lwngth of the file
UInt64 fileOffset; // File offset as pertains to data given to connection
UInt64 readOffset; // File offset as pertains to data read from file (but maybe not returned to connection)
BOOL connected;
BOOL aborted;
NSMutableData __strong *buffer;
NSData __strong *data;
void *readBuffer;
NSUInteger readBufferSize; // Malloced size of readBuffer
NSUInteger readBufferOffset; // Offset within readBuffer where the end of existing data is
NSUInteger readRequestLength;
}
@property (nonatomic, strong) HTTPMessage *remoteResponse;
- (id)initWithLocalRequest:(HTTPMessage *)request forConnection:(HTTPConnection *)connection;
@end
HTTPProxyResponse.m
#import "HTTPProxyResponse.h"
#import "MyAppDelegate.h"
#import "HTTPLogging.h"
#import <unistd.h>
#import <fcntl.h>
#import <CommonCrypto/CommonCryptor.h>
#import <CommonCrypto/CommonDigest.h>
// Log levels : off, error, warn, info, verbose
// Other flags: trace
static const int httpLogLevel = HTTP_LOG_FLAG_TRACE;
// Define chunk size used to read in data for responses
// This is how much data will be read from disk into RAM at a time
#if TARGET_OS_IPHONE
#define READ_CHUNKSIZE (1024 * 128)
#else
#define READ_CHUNKSIZE (1024 * 512)
#endif
// Define the various timeouts (in seconds) for various parts of the HTTP process
#define TIMEOUT_READ_FIRST_HEADER_LINE 30
#define TIMEOUT_READ_SUBSEQUENT_HEADER_LINE 30
#define TIMEOUT_READ_BODY -1
#define TIMEOUT_WRITE_HEAD 30
#define TIMEOUT_WRITE_ERROR 30
// Define the various limits
// LIMIT_MAX_HEADER_LINE_LENGTH: Max length (in bytes) of any single line in a header (including \r\n)
// LIMIT_MAX_HEADER_LINES : Max number of lines in a single header (including first GET line)
#define LIMIT_MAX_HEADER_LINE_LENGTH 8190
#define LIMIT_MAX_HEADER_LINES 100
// Define the various tags we'll use to differentiate what it is we're currently doing
#define HTTP_REQUEST_HEADER 10
#define HTTP_RESPONSE_HEADER 92
#define HTTP_RESPONSE_BODY 93
#define HTTP_FINAL_RESPONSE 91
@implementation HTTPProxyResponse
@synthesize remoteResponse;
- (NSURL *)remoteRequestURI
{
return [[NSURL alloc] initWithString:@"http://www.example.com/path/to/my/video.mp4"];
}
- (id)initWithLocalRequest:(HTTPMessage *)request forConnection:(HTTPConnection *)parent
{
if ((self = [super init]))
{
HTTPLogTrace();
connection = parent; // Parents retain children, children do NOT retain parents
localRequest = request;
NSURL *remoteURI = [self remoteRequestURI];
MyAppDelegate * appDelegate = (MyAppDelegate *)[[UIApplication sharedApplication] delegate];
fileLength = [appDelegate currentVideoLength];
fileOffset = 0;
socketQueue = dispatch_queue_create("AsyncSocket", NULL);
asyncSocket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:socketQueue];
uint16_t port = [[remoteURI port] unsignedLongLongValue];
if(port==0) port = 80;
NSError *error = nil;
bool res = [asyncSocket connectToHost:[remoteURI host] onPort:port error:&error];
if(!res || error) return nil;
connected = NO;
aborted = NO;
}
return self;
}
- (void)abort
{
HTTPLogTrace();
[asyncSocket disconnect];
[connection responseDidAbort:self];
aborted = YES;
}
- (BOOL)delayResponseHeaders
{
return !(connected && [remoteResponse isHeaderComplete]);
}
- (void)processReadBuffer:(int)read
{
// This method is here to allow superclasses to perform post-processing of the data.
// For an example, see the HTTPDynamicFileResponse class.
//
// At this point, the readBuffer has readBufferOffset bytes available.
// This method is in charge of updating the readBufferOffset.
// Failure to do so will cause the readBuffer to grow to fileLength. (Imagine a 1 GB file...)
// Copy the data out of the temporary readBuffer.
data = [[NSData alloc] initWithBytes:readBuffer length:readBufferOffset];
// Reset the read buffer.
readBufferOffset = 0;
// Notify the connection that we have data available for it.
[connection responseHasAvailableData:self];
}
- (BOOL)canContinue
{
if (aborted || asyncSocket == nil || [remoteResponse statusCode] == 304)
{
return NO;
}
return [asyncSocket isConnected];
}
- (UInt64)contentLength
{
HTTPLogTrace2(@"%@[%p]: contentLength - %llu", THIS_FILE, self, fileLength);
return fileLength;
}
- (UInt64)offset
{
HTTPLogTrace();
return fileOffset;
}
- (void)setOffset:(UInt64)offset
{
HTTPLogTrace2(@"%@[%p]: setOffset:%llu", THIS_FILE, self, offset);
fileOffset = offset;
readOffset = offset;
}
- (void)startHeadRequest
{
HTTPLogTrace();
NSURL *remoteUrl = [self remoteRequestURI];
HTTPMessage *remoteRequest = [[HTTPMessage alloc] initRequestWithMethod:@"HEAD" URL:remoteUrl version:HTTPVersion1_1];
// Add HOST headers¸
[remoteRequest setHeaderField:@"HOST" value:[remoteUrl host]];
[remoteRequest setHeaderField:@"User-Agent" value:[localRequest headerField:@"User-Agent"]];
[remoteRequest setHeaderField:@"Range" value:[NSString stringWithFormat:@"%lld-",fileOffset]];
[remoteRequest setHeaderField:@"Connection" value:@"Keep-Alive"];
NSData *requestData = [remoteRequest messageData];
[asyncSocket writeData:requestData withTimeout:TIMEOUT_WRITE_HEAD tag:HTTP_REQUEST_HEADER];
}
- (void)startContentRequest
{
HTTPLogTrace();
NSURL *remoteUrl = [self remoteRequestURI];
HTTPMessage *remoteRequest = [[HTTPMessage alloc] initRequestWithMethod:[localRequest method] URL:remoteUrl version:HTTPVersion1_1];
// Add HOST headers¸
[remoteRequest setHeaderField:@"HOST" value:[remoteUrl host]];
// Add standart headers from orignal request
NSMutableDictionary *requestHeaders = [[localRequest allHeaderFields] mutableCopy];
[requestHeaders removeObjectForKey:@"HOST"];
NSEnumerator *keyEnumerator = [requestHeaders keyEnumerator];
NSString *key;
while ((key = [keyEnumerator nextObject]))
{
NSString *value = [requestHeaders objectForKey:key];
[remoteRequest setHeaderField:key value:value];
}
NSData *requestData = [remoteRequest messageData];
[asyncSocket writeData:requestData withTimeout:TIMEOUT_WRITE_HEAD tag:HTTP_REQUEST_HEADER];
}
- (NSData *)readDataOfLength:(NSUInteger)length
{
if (data)
{
NSUInteger dataLength = [data length];
fileOffset += dataLength;
NSData *result = data;
data = nil;
return result;
}
else
{
if(!connected){
return nil;
}
if (![self canContinue])
{
[self abort];
return nil;
}
HTTPLogVerbose(@"%@[%p]: Reading data of length %d", THIS_FILE, self, length);
[asyncSocket readDataToLength:length withTimeout:TIMEOUT_READ_BODY buffer:buffer bufferOffset:readBufferOffset tag:HTTP_RESPONSE_BODY];
return nil;
}
}
- (BOOL)isDone
{
BOOL result = (fileOffset == fileLength);
HTTPLogTrace2(@"%@[%p]: isDone - %@", THIS_FILE, self, (result ? @"YES" : @"NO"));
return result;
}
- (BOOL)isAsynchronous
{
HTTPLogTrace();
return YES;
}
- (void)connectionDidClose
{
HTTPLogTrace();
[asyncSocket disconnect];
}
- (void)dealloc
{
HTTPLogTrace();
//if (readBuffer)
// free(readBuffer);
if(socketQueue)
dispatch_release(socketQueue);
//[super dealloc];
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark GCDAsyncSocket Delegate
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(UInt16)port
{
connected = true;
if(fileLength >0)
{
[self startContentRequest];
} else {
[self startHeadRequest];
}
}
- (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err
{
connected = false;
if(err) {
HTTPLogTrace2(@"%@[%p]: socketDidDisconnect:\n %@", THIS_FILE, self, err);
}
}
/**
* This method is called after the socket has successfully written data to the stream.
**/
- (void)socket:(GCDAsyncSocket *)sock didWriteDataWithTag:(long)tag
{
if (tag == HTTP_REQUEST_HEADER)
{
remoteResponse = [[HTTPMessage alloc] initEmptyResponse];
[asyncSocket readDataToData:[GCDAsyncSocket CRLFData]
withTimeout:TIMEOUT_READ_FIRST_HEADER_LINE
maxLength:LIMIT_MAX_HEADER_LINE_LENGTH
tag:HTTP_RESPONSE_HEADER];
}
}
/**
* This method is called after the socket has successfully read data from the stream.
* Remember that this method will only be called after the socket reaches a CRLF, or after it's read the proper length.
**/
- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData*)dat withTag:(long)tag
{
if (tag == HTTP_RESPONSE_HEADER)
{
NSString *log = [[NSString alloc]initWithData:dat encoding:NSUTF8StringEncoding];
HTTPLogTrace2(@"%@[%p]: didReadData\n %@", THIS_FILE, self, log);
// Append the header line to the http message
BOOL result = [remoteResponse appendData:dat];
if (!result)
{
HTTPLogWarn(@"%@[%p]: Malformed request", THIS_FILE, self);
[self abort];
}
else if (![remoteResponse isHeaderComplete])
{
// We don't have a complete header yet
// That is, we haven't yet received a CRLF on a line by itself, indicating the end of the header
if (++numHeaderLines > LIMIT_MAX_HEADER_LINES)
{
// Reached the maximum amount of header lines in a single HTTP request
// This could be an attempted DOS attack
[self abort];
// Explictly return to ensure we don't do anything after the socket disconnect
return;
}
else
{
[asyncSocket readDataToData:[GCDAsyncSocket CRLFData]
withTimeout:TIMEOUT_READ_SUBSEQUENT_HEADER_LINE
maxLength:LIMIT_MAX_HEADER_LINE_LENGTH
tag:HTTP_RESPONSE_HEADER];
}
}
else
{
// We have an entire HTTP request header from the client
//NSRange isHeadRequest = [[remoteResponse method] rangeOfString:@"HEAD" options:NSCaseInsensitiveSearch];
//if(isHeadRequest.location == NSNotFound)
if(fileLength > 0)
{
// Now we need to reply to the local request
// Not able to pass my buffer dont know why
//buffer = [[NSMutableData alloc] initWithCapacity:READ_CHUNKSIZE];
//Kick start reading the body from the connections getDataOfLength
[connection responseHasAvailableData:self];
}
else
{
NSString *fLength = [remoteResponse headerField:@"Content-Length"];
fileLength = [fLength longLongValue];
if(fileLength > 0)
{
MyAppDelegate * appDelegate = (MyAppDelegate *)[[UIApplication sharedApplication] delegate];
appDelegate.currentVideoLength = fileLength;
[self startContentRequest];
}
else
{
[self abort];
}
}
}
}
else if (tag == HTTP_RESPONSE_BODY)
{
// Handle a chunk of data from the remote response body
NSUInteger read = [dat length];
readBuffer = (void *)[dat bytes];
readOffset += read;
readBufferOffset += read;
[self processReadBuffer:read];
}
}
@end