- Redo HTTP buffering mechanism
- Handle HTTP errors better - Fix handling ShoutCAST servers responding ICY headersCQTexperiment
parent
b37a6f0f0d
commit
a43ae5fc81
|
@ -20,10 +20,14 @@
|
||||||
NSURLSession * session;
|
NSURLSession * session;
|
||||||
NSURLSessionDataTask * task;
|
NSURLSessionDataTask * task;
|
||||||
|
|
||||||
|
Boolean didReceiveResponse;
|
||||||
|
Boolean didReceiveRandomData;
|
||||||
|
Boolean didComplete;
|
||||||
|
|
||||||
Boolean redirected;
|
Boolean redirected;
|
||||||
NSMutableArray * redirectURLs;
|
NSMutableArray * redirectURLs;
|
||||||
|
|
||||||
NSMutableArray * bufferedData;
|
NSMutableData * bufferedData;
|
||||||
|
|
||||||
long _bytesBuffered;
|
long _bytesBuffered;
|
||||||
long _byteCount;
|
long _byteCount;
|
||||||
|
|
|
@ -11,6 +11,9 @@
|
||||||
|
|
||||||
#import "Logging.h"
|
#import "Logging.h"
|
||||||
|
|
||||||
|
#import <stdlib.h>
|
||||||
|
#import <string.h>
|
||||||
|
|
||||||
#define BUFFER_SIZE 131072
|
#define BUFFER_SIZE 131072
|
||||||
|
|
||||||
@implementation HTTPSource
|
@implementation HTTPSource
|
||||||
|
@ -18,7 +21,6 @@
|
||||||
- (NSURLSession *)createSession
|
- (NSURLSession *)createSession
|
||||||
{
|
{
|
||||||
queue = [[NSOperationQueue alloc] init];
|
queue = [[NSOperationQueue alloc] init];
|
||||||
[queue setMaxConcurrentOperationCount:1];
|
|
||||||
|
|
||||||
NSURLSession *session = nil;
|
NSURLSession *session = nil;
|
||||||
session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]
|
session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]
|
||||||
|
@ -32,16 +34,102 @@
|
||||||
didReceiveData:(NSData *)data{
|
didReceiveData:(NSData *)data{
|
||||||
long bytesBuffered = 0;
|
long bytesBuffered = 0;
|
||||||
if (!task) return;
|
if (!task) return;
|
||||||
|
if (didReceiveRandomData) {
|
||||||
|
// Parse ICY header here?
|
||||||
|
// XXX
|
||||||
|
didReceiveRandomData = NO;
|
||||||
|
|
||||||
|
const char * header = "ICY 200 OK\r\n";
|
||||||
|
size_t length = [data length];
|
||||||
|
if (length >= strlen(header)) {
|
||||||
|
const char * dataBytes = (const char *) [data bytes];
|
||||||
|
const char * dataStart = dataBytes;
|
||||||
|
if (memcmp(dataBytes, header, strlen(header)) == 0) {
|
||||||
|
const char * dataEnd = dataBytes + length;
|
||||||
|
Boolean endFound = NO;
|
||||||
|
while (dataBytes + 4 <= dataEnd) {
|
||||||
|
if (memcmp(dataBytes, "\r\n\r\n", 4) == 0) {
|
||||||
|
endFound = YES;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
dataBytes++;
|
||||||
|
}
|
||||||
|
if (!endFound) {
|
||||||
|
@synchronized(task) {
|
||||||
|
didComplete = YES;
|
||||||
|
[task cancel];
|
||||||
|
task = nil;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dataEnd = dataBytes + 4;
|
||||||
|
NSUInteger dataLeft = length - (dataEnd - dataStart);
|
||||||
|
dataBytes = dataStart;
|
||||||
|
dataBytes += strlen("ICY 200 OK\r\n");
|
||||||
|
char headerBuffer[80 * 1024 + 1];
|
||||||
|
while (dataBytes < dataEnd - 2) {
|
||||||
|
const char * string = dataBytes;
|
||||||
|
while (dataBytes < dataEnd - 2) {
|
||||||
|
if (memcmp(dataBytes, "\r\n", 2) == 0) break;
|
||||||
|
dataBytes++;
|
||||||
|
}
|
||||||
|
if (dataBytes - string > 80 * 1024)
|
||||||
|
dataBytes = string + 80 * 1024;
|
||||||
|
strncpy(headerBuffer, string, dataBytes - string);
|
||||||
|
headerBuffer[dataBytes - string] = '\0';
|
||||||
|
|
||||||
|
char * colon = strchr(headerBuffer, ':');
|
||||||
|
if (colon) {
|
||||||
|
*colon = '\0';
|
||||||
|
colon++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strcasecmp(headerBuffer, "content-type") == 0) {
|
||||||
|
_mimeType = [NSString stringWithUTF8String:colon];
|
||||||
|
}
|
||||||
|
|
||||||
|
dataBytes += 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
data = [NSData dataWithBytes:dataEnd length:dataLeft];
|
||||||
|
|
||||||
|
didReceiveResponse = YES;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@synchronized(bufferedData) {
|
@synchronized(bufferedData) {
|
||||||
[bufferedData addObject:data];
|
[bufferedData appendData:data];
|
||||||
_bytesBuffered += [data length];
|
_bytesBuffered += [data length];
|
||||||
bytesBuffered = _bytesBuffered;
|
bytesBuffered = _bytesBuffered;
|
||||||
}
|
}
|
||||||
if (bytesBuffered > BUFFER_SIZE) {
|
if (bytesBuffered >= BUFFER_SIZE) {
|
||||||
[task suspend];
|
[task suspend];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (void)URLSession:(NSURLSession *)session
|
||||||
|
dataTask:(NSURLSessionDataTask *)dataTask
|
||||||
|
didReceiveResponse:(NSURLResponse *)response
|
||||||
|
completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler {
|
||||||
|
if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
|
||||||
|
NSInteger statusCode = [(NSHTTPURLResponse *)response statusCode];
|
||||||
|
if (statusCode != 200) {
|
||||||
|
completionHandler(NSURLSessionResponseCancel);
|
||||||
|
@synchronized (task) {
|
||||||
|
task = nil;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_mimeType = [response MIMEType];
|
||||||
|
if ([_mimeType isEqualToString:@"application/octet-stream"])
|
||||||
|
didReceiveRandomData = YES;
|
||||||
|
else
|
||||||
|
didReceiveResponse = YES;
|
||||||
|
|
||||||
|
completionHandler(NSURLSessionResponseAllow);
|
||||||
|
}
|
||||||
|
|
||||||
- (void)URLSession:(NSURLSession *)session
|
- (void)URLSession:(NSURLSession *)session
|
||||||
task:(NSURLSessionTask *)task
|
task:(NSURLSessionTask *)task
|
||||||
willPerformHTTPRedirection:(NSHTTPURLResponse *)response
|
willPerformHTTPRedirection:(NSHTTPURLResponse *)response
|
||||||
|
@ -49,6 +137,7 @@ willPerformHTTPRedirection:(NSHTTPURLResponse *)response
|
||||||
completionHandler:(void (^)(NSURLRequest *))completionHandler {
|
completionHandler:(void (^)(NSURLRequest *))completionHandler {
|
||||||
NSURL * url = [request URL];
|
NSURL * url = [request URL];
|
||||||
if ([redirectURLs containsObject:url]) {
|
if ([redirectURLs containsObject:url]) {
|
||||||
|
completionHandler(NULL);
|
||||||
@synchronized(self->task) {
|
@synchronized(self->task) {
|
||||||
self->task = nil;
|
self->task = nil;
|
||||||
}
|
}
|
||||||
|
@ -56,14 +145,13 @@ willPerformHTTPRedirection:(NSHTTPURLResponse *)response
|
||||||
else {
|
else {
|
||||||
[redirectURLs addObject:url];
|
[redirectURLs addObject:url];
|
||||||
redirected = YES;
|
redirected = YES;
|
||||||
|
didReceiveResponse = NO;
|
||||||
|
didComplete = NO;
|
||||||
@synchronized(bufferedData) {
|
@synchronized(bufferedData) {
|
||||||
[bufferedData removeAllObjects];
|
[bufferedData setLength:0];
|
||||||
_bytesBuffered = 0;
|
_bytesBuffered = 0;
|
||||||
}
|
}
|
||||||
@synchronized(self->task) {
|
completionHandler(request);
|
||||||
self->task = [session dataTaskWithRequest:request];
|
|
||||||
[self->task resume];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -77,6 +165,7 @@ didBecomeInvalidWithError:(NSError *)error {
|
||||||
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask
|
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask
|
||||||
willCacheResponse:(NSCachedURLResponse *)proposedResponse
|
willCacheResponse:(NSCachedURLResponse *)proposedResponse
|
||||||
completionHandler:(void (^)(NSCachedURLResponse *cachedResponse))completionHandler{
|
completionHandler:(void (^)(NSCachedURLResponse *cachedResponse))completionHandler{
|
||||||
|
didComplete = YES;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)URLSession:(NSURLSession *)session
|
- (void)URLSession:(NSURLSession *)session
|
||||||
|
@ -89,10 +178,12 @@ didCompleteWithError:(NSError *)error{
|
||||||
|
|
||||||
- (BOOL)open:(NSURL *)url
|
- (BOOL)open:(NSURL *)url
|
||||||
{
|
{
|
||||||
|
didReceiveResponse = NO;
|
||||||
|
didReceiveRandomData = NO;
|
||||||
redirected = NO;
|
redirected = NO;
|
||||||
|
|
||||||
redirectURLs = [[NSMutableArray alloc] init];
|
redirectURLs = [[NSMutableArray alloc] init];
|
||||||
bufferedData = [[NSMutableArray alloc] init];
|
bufferedData = [[NSMutableData alloc] init];
|
||||||
|
|
||||||
URL = url;
|
URL = url;
|
||||||
[redirectURLs addObject:URL];
|
[redirectURLs addObject:URL];
|
||||||
|
@ -102,25 +193,10 @@ didCompleteWithError:(NSError *)error{
|
||||||
task = [session dataTaskWithRequest:request];
|
task = [session dataTaskWithRequest:request];
|
||||||
[task resume];
|
[task resume];
|
||||||
|
|
||||||
NSURLResponse * response = nil;
|
while (task && !didReceiveResponse)
|
||||||
while (!response) {
|
usleep(1000);
|
||||||
@synchronized(task) {
|
|
||||||
if (!task) return NO;
|
if (!task) return NO;
|
||||||
redirected = NO;
|
|
||||||
response = [task response];
|
|
||||||
}
|
|
||||||
if (response && !redirected) break;
|
|
||||||
if (redirected) continue;
|
|
||||||
usleep(100);
|
|
||||||
}
|
|
||||||
if (response) {
|
|
||||||
if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
|
|
||||||
NSInteger statusCode = [(NSHTTPURLResponse *)response statusCode];
|
|
||||||
if (statusCode != 200)
|
|
||||||
return NO;
|
|
||||||
}
|
|
||||||
_mimeType = [response MIMEType];
|
|
||||||
}
|
|
||||||
|
|
||||||
return YES;
|
return YES;
|
||||||
}
|
}
|
||||||
|
@ -148,18 +224,25 @@ didCompleteWithError:(NSError *)error{
|
||||||
|
|
||||||
- (long)read:(void *)buffer amount:(long)amount
|
- (long)read:(void *)buffer amount:(long)amount
|
||||||
{
|
{
|
||||||
|
if (didComplete)
|
||||||
|
return 0;
|
||||||
|
|
||||||
long totalRead = 0;
|
long totalRead = 0;
|
||||||
long bytesBuffered = 0;
|
long bytesBuffered = 0;
|
||||||
|
|
||||||
while (totalRead < amount) {
|
while (totalRead < amount) {
|
||||||
NSData * dataBlock = nil;
|
NSData * dataBlock = nil;
|
||||||
|
NSUInteger copySize = amount - totalRead;
|
||||||
@synchronized(bufferedData) {
|
@synchronized(bufferedData) {
|
||||||
if ([bufferedData count])
|
if ([bufferedData length]) {
|
||||||
dataBlock = [bufferedData objectAtIndex:0];
|
if (copySize > [bufferedData length])
|
||||||
|
copySize = [bufferedData length];
|
||||||
|
dataBlock = [bufferedData subdataWithRange:NSMakeRange(0, copySize)];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (!dataBlock) {
|
if (!dataBlock) {
|
||||||
@synchronized(task) {
|
@synchronized(task) {
|
||||||
if (!task) return totalRead;
|
if (!task || didComplete) return totalRead;
|
||||||
}
|
}
|
||||||
usleep(1000);
|
usleep(1000);
|
||||||
continue;
|
continue;
|
||||||
|
@ -169,36 +252,20 @@ didCompleteWithError:(NSError *)error{
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
NSInteger amountUsed = amountReceived;
|
|
||||||
|
|
||||||
if (amountUsed > (amount - totalRead) )
|
|
||||||
amountUsed = amount - totalRead;
|
|
||||||
|
|
||||||
const void * dataBytes = [dataBlock bytes];
|
const void * dataBytes = [dataBlock bytes];
|
||||||
memcpy(((uint8_t *)buffer) + totalRead, dataBytes, amountUsed);
|
memcpy(((uint8_t *)buffer) + totalRead, dataBytes, amountReceived);
|
||||||
|
|
||||||
if (amountUsed < amountReceived) {
|
|
||||||
NSData * dataOut = [NSData dataWithBytes:(((uint8_t *)dataBytes) + amountUsed) length:(amountReceived - amountUsed)];
|
|
||||||
@synchronized(bufferedData) {
|
@synchronized(bufferedData) {
|
||||||
[bufferedData removeObjectAtIndex:0];
|
[bufferedData replaceBytesInRange:NSMakeRange(0, amountReceived) withBytes:NULL length:0];
|
||||||
[bufferedData insertObject:dataOut atIndex:0];
|
_bytesBuffered -= amountReceived;
|
||||||
_bytesBuffered -= amountUsed;
|
|
||||||
bytesBuffered = _bytesBuffered;
|
bytesBuffered = _bytesBuffered;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
else {
|
|
||||||
@synchronized(bufferedData) {
|
|
||||||
[bufferedData removeObjectAtIndex:0];
|
|
||||||
_bytesBuffered -= amountUsed;
|
|
||||||
bytesBuffered = _bytesBuffered;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (bytesBuffered <= (BUFFER_SIZE * 3 / 4)) {
|
if (!didComplete && bytesBuffered <= (BUFFER_SIZE * 3 / 4)) {
|
||||||
[task resume];
|
[task resume];
|
||||||
}
|
}
|
||||||
|
|
||||||
totalRead += amountUsed;
|
totalRead += amountReceived;
|
||||||
}
|
}
|
||||||
|
|
||||||
_byteCount += totalRead;
|
_byteCount += totalRead;
|
||||||
|
|
Loading…
Reference in New Issue