- Redo HTTP buffering mechanism

- Handle HTTP errors better
- Fix handling ShoutCAST servers responding ICY headers
CQTexperiment
Christopher Snowhill 2020-03-10 01:56:59 -07:00
parent b37a6f0f0d
commit a43ae5fc81
2 changed files with 126 additions and 55 deletions

View File

@ -20,10 +20,14 @@
NSURLSession * session;
NSURLSessionDataTask * task;
Boolean didReceiveResponse;
Boolean didReceiveRandomData;
Boolean didComplete;
Boolean redirected;
NSMutableArray * redirectURLs;
NSMutableArray * bufferedData;
NSMutableData * bufferedData;
long _bytesBuffered;
long _byteCount;

View File

@ -11,6 +11,9 @@
#import "Logging.h"
#import <stdlib.h>
#import <string.h>
#define BUFFER_SIZE 131072
@implementation HTTPSource
@ -18,7 +21,6 @@
- (NSURLSession *)createSession
{
queue = [[NSOperationQueue alloc] init];
[queue setMaxConcurrentOperationCount:1];
NSURLSession *session = nil;
session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]
@ -32,16 +34,102 @@
didReceiveData:(NSData *)data{
long bytesBuffered = 0;
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) {
[bufferedData addObject:data];
[bufferedData appendData:data];
_bytesBuffered += [data length];
bytesBuffered = _bytesBuffered;
}
if (bytesBuffered > BUFFER_SIZE) {
if (bytesBuffered >= BUFFER_SIZE) {
[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
task:(NSURLSessionTask *)task
willPerformHTTPRedirection:(NSHTTPURLResponse *)response
@ -49,6 +137,7 @@ willPerformHTTPRedirection:(NSHTTPURLResponse *)response
completionHandler:(void (^)(NSURLRequest *))completionHandler {
NSURL * url = [request URL];
if ([redirectURLs containsObject:url]) {
completionHandler(NULL);
@synchronized(self->task) {
self->task = nil;
}
@ -56,14 +145,13 @@ willPerformHTTPRedirection:(NSHTTPURLResponse *)response
else {
[redirectURLs addObject:url];
redirected = YES;
didReceiveResponse = NO;
didComplete = NO;
@synchronized(bufferedData) {
[bufferedData removeAllObjects];
[bufferedData setLength:0];
_bytesBuffered = 0;
}
@synchronized(self->task) {
self->task = [session dataTaskWithRequest:request];
[self->task resume];
}
completionHandler(request);
}
}
@ -77,6 +165,7 @@ didBecomeInvalidWithError:(NSError *)error {
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask
willCacheResponse:(NSCachedURLResponse *)proposedResponse
completionHandler:(void (^)(NSCachedURLResponse *cachedResponse))completionHandler{
didComplete = YES;
}
- (void)URLSession:(NSURLSession *)session
@ -89,10 +178,12 @@ didCompleteWithError:(NSError *)error{
- (BOOL)open:(NSURL *)url
{
didReceiveResponse = NO;
didReceiveRandomData = NO;
redirected = NO;
redirectURLs = [[NSMutableArray alloc] init];
bufferedData = [[NSMutableArray alloc] init];
bufferedData = [[NSMutableData alloc] init];
URL = url;
[redirectURLs addObject:URL];
@ -102,25 +193,10 @@ didCompleteWithError:(NSError *)error{
task = [session dataTaskWithRequest:request];
[task resume];
NSURLResponse * response = nil;
while (!response) {
@synchronized(task) {
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];
}
while (task && !didReceiveResponse)
usleep(1000);
if (!task) return NO;
return YES;
}
@ -148,18 +224,25 @@ didCompleteWithError:(NSError *)error{
- (long)read:(void *)buffer amount:(long)amount
{
if (didComplete)
return 0;
long totalRead = 0;
long bytesBuffered = 0;
while (totalRead < amount) {
NSData * dataBlock = nil;
NSUInteger copySize = amount - totalRead;
@synchronized(bufferedData) {
if ([bufferedData count])
dataBlock = [bufferedData objectAtIndex:0];
if ([bufferedData length]) {
if (copySize > [bufferedData length])
copySize = [bufferedData length];
dataBlock = [bufferedData subdataWithRange:NSMakeRange(0, copySize)];
}
}
if (!dataBlock) {
@synchronized(task) {
if (!task) return totalRead;
if (!task || didComplete) return totalRead;
}
usleep(1000);
continue;
@ -169,36 +252,20 @@ didCompleteWithError:(NSError *)error{
break;
}
NSInteger amountUsed = amountReceived;
if (amountUsed > (amount - totalRead) )
amountUsed = amount - totalRead;
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) {
[bufferedData removeObjectAtIndex:0];
[bufferedData insertObject:dataOut atIndex:0];
_bytesBuffered -= amountUsed;
bytesBuffered = _bytesBuffered;
}
}
else {
@synchronized(bufferedData) {
[bufferedData removeObjectAtIndex:0];
_bytesBuffered -= amountUsed;
bytesBuffered = _bytesBuffered;
}
@synchronized(bufferedData) {
[bufferedData replaceBytesInRange:NSMakeRange(0, amountReceived) withBytes:NULL length:0];
_bytesBuffered -= amountReceived;
bytesBuffered = _bytesBuffered;
}
if (bytesBuffered <= (BUFFER_SIZE * 3 / 4)) {
if (!didComplete && bytesBuffered <= (BUFFER_SIZE * 3 / 4)) {
[task resume];
}
totalRead += amountUsed;
totalRead += amountReceived;
}
_byteCount += totalRead;