From 6007687e46567f2311676e3739adac2ab7d26d3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roger=20Jo=CC=88nsson?= Date: Tue, 27 Apr 2021 21:06:15 +0200 Subject: [PATCH 1/7] Self references causing leak and/or eternal loop. --- EventSource/EventSource.m | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/EventSource/EventSource.m b/EventSource/EventSource.m index cd9883e..076d644 100644 --- a/EventSource/EventSource.m +++ b/EventSource/EventSource.m @@ -204,10 +204,13 @@ - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didComp [self _dispatchEvent:e type:ReadyStateEvent]; [self _dispatchEvent:e type:ErrorEvent]; - dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(_retryInterval * NSEC_PER_SEC)); - dispatch_after(popTime, connectionQueue, ^(void){ - [self _open]; - }); + __weak __typeof(self) const weakSelf = self; + dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(_retryInterval * NSEC_PER_SEC)); + dispatch_after(popTime, connectionQueue, ^(void){ + __typeof(self) const strongSelf = weakSelf; + if(strongSelf && !strongSelf->wasClosed) + [strongSelf _open]; + }); } // ------------------------------------------------------------------------------------------------------------------------------------- @@ -228,6 +231,8 @@ - (void)_open self.eventSourceTask = [session dataTaskWithRequest:request]; [self.eventSourceTask resume]; + + [session finishTasksAndInvalidate]; Event *e = [Event new]; e.readyState = kEventStateConnecting; From 382155d616e174234f2bef59a0c53430d499b9fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roger=20Jo=CC=88nsson?= Date: Mon, 3 May 2021 08:54:25 +0200 Subject: [PATCH 2/7] Longer intervals. --- EventSource/EventSource.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/EventSource/EventSource.m b/EventSource/EventSource.m index 076d644..c1ea79e 100644 --- a/EventSource/EventSource.m +++ b/EventSource/EventSource.m @@ -9,8 +9,8 @@ #import "EventSource.h" #import -static CGFloat const ES_RETRY_INTERVAL = 1.0; -static CGFloat const ES_DEFAULT_TIMEOUT = 300.0; +static CGFloat const ES_RETRY_INTERVAL = 40.0; +static CGFloat const ES_DEFAULT_TIMEOUT = 600.0; static NSString *const ESKeyValueDelimiter = @":"; static NSString *const ESEventSeparatorLFLF = @"\n\n"; From ed420fff864f27913eaf4855d1958adf67856500 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roger=20Jo=CC=88nsson?= Date: Wed, 12 May 2021 12:15:59 +0200 Subject: [PATCH 3/7] Connect right away --- EventSource/EventSource.m | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/EventSource/EventSource.m b/EventSource/EventSource.m index c1ea79e..bafe9b9 100644 --- a/EventSource/EventSource.m +++ b/EventSource/EventSource.m @@ -70,10 +70,9 @@ - (instancetype)initWithURL:(NSURL *)URL timeoutInterval:(NSTimeInterval)timeout messageQueue = dispatch_queue_create("co.cwbrn.eventsource-queue", DISPATCH_QUEUE_SERIAL); connectionQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0); - dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(_retryInterval * NSEC_PER_SEC)); - dispatch_after(popTime, connectionQueue, ^(void){ - [self _open]; - }); + dispatch_async(connectionQueue, ^{ + [self _open]; + }); } return self; } From b424c2c468e639483576c0dec4e9645f8c02631d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roger=20Jo=CC=88nsson?= Date: Sun, 6 Jun 2021 14:49:27 +0200 Subject: [PATCH 4/7] Added authorization --- .../xcschemes/EventSource-watchOS.xcscheme | 10 +- EventSource/EventSource.h | 6 +- EventSource/EventSource.m | 338 +++++++++--------- 3 files changed, 170 insertions(+), 184 deletions(-) diff --git a/EventSource.xcodeproj/xcshareddata/xcschemes/EventSource-watchOS.xcscheme b/EventSource.xcodeproj/xcshareddata/xcschemes/EventSource-watchOS.xcscheme index 1b4607d..ae87957 100644 --- a/EventSource.xcodeproj/xcshareddata/xcschemes/EventSource-watchOS.xcscheme +++ b/EventSource.xcodeproj/xcshareddata/xcschemes/EventSource-watchOS.xcscheme @@ -15,7 +15,7 @@ @@ -29,8 +29,6 @@ shouldUseLaunchSchemeArgsEnv = "YES"> - - - - diff --git a/EventSource/EventSource.h b/EventSource/EventSource.h index 7752b64..8cb9fc0 100644 --- a/EventSource/EventSource.h +++ b/EventSource/EventSource.h @@ -9,9 +9,9 @@ #import typedef enum { - kEventStateConnecting = 0, - kEventStateOpen = 1, - kEventStateClosed = 2, + kEventStateConnecting = 0, + kEventStateOpen = 1, + kEventStateClosed = 2, } EventState; // --------------------------------------------------------------------------------------------------------------------- diff --git a/EventSource/EventSource.m b/EventSource/EventSource.m index bafe9b9..e337abb 100644 --- a/EventSource/EventSource.m +++ b/EventSource/EventSource.m @@ -10,7 +10,6 @@ #import static CGFloat const ES_RETRY_INTERVAL = 40.0; -static CGFloat const ES_DEFAULT_TIMEOUT = 600.0; static NSString *const ESKeyValueDelimiter = @":"; static NSString *const ESEventSeparatorLFLF = @"\n\n"; @@ -24,12 +23,13 @@ static NSString *const ESEventRetryKey = @"retry"; @interface EventSource () { - BOOL wasClosed; - dispatch_queue_t messageQueue; - dispatch_queue_t connectionQueue; + BOOL wasClosed; + dispatch_queue_t messageQueue; + dispatch_queue_t connectionQueue; } @property (nonatomic, strong) NSURL *eventURL; +@property (nonatomic, strong) NSString *authorization; @property (nonatomic, strong) NSURLSessionDataTask *eventSourceTask; @property (nonatomic, strong) NSMutableDictionary *listeners; @property (nonatomic, assign) NSTimeInterval timeoutInterval; @@ -43,73 +43,59 @@ - (void)_dispatchEvent:(Event *)e; @implementation EventSource -+ (instancetype)eventSourceWithURL:(NSURL *)URL +- (instancetype)initWithURL:(NSURL *)URL authorization:(NSString *)authorization timeoutInterval:(NSTimeInterval)timeoutInterval { - return [[EventSource alloc] initWithURL:URL]; -} - -+ (instancetype)eventSourceWithURL:(NSURL *)URL timeoutInterval:(NSTimeInterval)timeoutInterval -{ - return [[EventSource alloc] initWithURL:URL timeoutInterval:timeoutInterval]; -} - -- (instancetype)initWithURL:(NSURL *)URL -{ - return [self initWithURL:URL timeoutInterval:ES_DEFAULT_TIMEOUT]; -} - -- (instancetype)initWithURL:(NSURL *)URL timeoutInterval:(NSTimeInterval)timeoutInterval -{ - self = [super init]; - if (self) { - _listeners = [NSMutableDictionary dictionary]; - _eventURL = URL; - _timeoutInterval = timeoutInterval; - _retryInterval = ES_RETRY_INTERVAL; - - messageQueue = dispatch_queue_create("co.cwbrn.eventsource-queue", DISPATCH_QUEUE_SERIAL); - connectionQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0); - + self = [super init]; + if (self) { + _listeners = [NSMutableDictionary dictionary]; + _eventURL = URL; + _authorization = authorization; + _timeoutInterval = timeoutInterval; + _retryInterval = ES_RETRY_INTERVAL; + + messageQueue = dispatch_queue_create("co.cwbrn.eventsource-queue", DISPATCH_QUEUE_SERIAL); + connectionQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0); + dispatch_async(connectionQueue, ^{ [self _open]; }); - } - return self; + } + return self; } - (void)addEventListener:(NSString *)eventName handler:(EventSourceEventHandler)handler { - if (self.listeners[eventName] == nil) { - [self.listeners setObject:[NSMutableArray array] forKey:eventName]; - } - - [self.listeners[eventName] addObject:handler]; + if (self.listeners[eventName] == nil) { + [self.listeners setObject:[NSMutableArray array] forKey:eventName]; + } + + [self.listeners[eventName] addObject:handler]; } - (void)onMessage:(EventSourceEventHandler)handler { - [self addEventListener:MessageEvent handler:handler]; + [self addEventListener:MessageEvent handler:handler]; } - (void)onError:(EventSourceEventHandler)handler { - [self addEventListener:ErrorEvent handler:handler]; + [self addEventListener:ErrorEvent handler:handler]; } - (void)onOpen:(EventSourceEventHandler)handler { - [self addEventListener:OpenEvent handler:handler]; + [self addEventListener:OpenEvent handler:handler]; } - (void)onReadyStateChanged:(EventSourceEventHandler)handler { - [self addEventListener:ReadyStateEvent handler:handler]; + [self addEventListener:ReadyStateEvent handler:handler]; } - (void)close { - wasClosed = YES; - [self.eventSourceTask cancel]; + wasClosed = YES; + [self.eventSourceTask cancel]; } // ----------------------------------------------------------------------------------------------------------------------------------------- @@ -117,92 +103,92 @@ - (void)close - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler { - NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response; - if (httpResponse.statusCode == 200) { - // Opened - Event *e = [Event new]; - e.readyState = kEventStateOpen; - - [self _dispatchEvent:e type:ReadyStateEvent]; - [self _dispatchEvent:e type:OpenEvent]; - } - - if (completionHandler) { - completionHandler(NSURLSessionResponseAllow); - } + NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response; + if (httpResponse.statusCode == 200) { + // Opened + Event *e = [Event new]; + e.readyState = kEventStateOpen; + + [self _dispatchEvent:e type:ReadyStateEvent]; + [self _dispatchEvent:e type:OpenEvent]; + } + + if (completionHandler) { + completionHandler(NSURLSessionResponseAllow); + } } - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data { - NSString *eventString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; - NSArray *lines = [eventString componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]]; - - Event *event = [Event new]; - event.readyState = kEventStateOpen; - - for (NSString *line in lines) { - if ([line hasPrefix:ESKeyValueDelimiter]) { - continue; - } - - if (!line || line.length == 0) { - if (event.data != nil) { - dispatch_async(messageQueue, ^{ - [self _dispatchEvent:event]; - }); - - event = [Event new]; - event.readyState = kEventStateOpen; - } - continue; - } - - @autoreleasepool { - NSScanner *scanner = [NSScanner scannerWithString:line]; - scanner.charactersToBeSkipped = [NSCharacterSet whitespaceCharacterSet]; - - NSString *key, *value; - [scanner scanUpToString:ESKeyValueDelimiter intoString:&key]; - [scanner scanString:ESKeyValueDelimiter intoString:nil]; - [scanner scanUpToCharactersFromSet:[NSCharacterSet newlineCharacterSet] intoString:&value]; - - if (key && value) { - if ([key isEqualToString:ESEventEventKey]) { - event.event = value; - } else if ([key isEqualToString:ESEventDataKey]) { - if (event.data != nil) { - event.data = [event.data stringByAppendingFormat:@"\n%@", value]; - } else { - event.data = value; - } - } else if ([key isEqualToString:ESEventIDKey]) { - event.id = value; - self.lastEventID = event.id; - } else if ([key isEqualToString:ESEventRetryKey]) { - self.retryInterval = [value doubleValue]; - } - } - } - } + NSString *eventString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; + NSArray *lines = [eventString componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]]; + + Event *event = [Event new]; + event.readyState = kEventStateOpen; + + for (NSString *line in lines) { + if ([line hasPrefix:ESKeyValueDelimiter]) { + continue; + } + + if (!line || line.length == 0) { + if (event.data != nil) { + dispatch_async(messageQueue, ^{ + [self _dispatchEvent:event]; + }); + + event = [Event new]; + event.readyState = kEventStateOpen; + } + continue; + } + + @autoreleasepool { + NSScanner *scanner = [NSScanner scannerWithString:line]; + scanner.charactersToBeSkipped = [NSCharacterSet whitespaceCharacterSet]; + + NSString *key, *value; + [scanner scanUpToString:ESKeyValueDelimiter intoString:&key]; + [scanner scanString:ESKeyValueDelimiter intoString:nil]; + [scanner scanUpToCharactersFromSet:[NSCharacterSet newlineCharacterSet] intoString:&value]; + + if (key && value) { + if ([key isEqualToString:ESEventEventKey]) { + event.event = value; + } else if ([key isEqualToString:ESEventDataKey]) { + if (event.data != nil) { + event.data = [event.data stringByAppendingFormat:@"\n%@", value]; + } else { + event.data = value; + } + } else if ([key isEqualToString:ESEventIDKey]) { + event.id = value; + self.lastEventID = event.id; + } else if ([key isEqualToString:ESEventRetryKey]) { + self.retryInterval = [value doubleValue]; + } + } + } + } } - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(nullable NSError *)error { - self.eventSourceTask = nil; - - if (wasClosed) { - return; - } - - Event *e = [Event new]; - e.readyState = kEventStateClosed; - e.error = error ?: [NSError errorWithDomain:@"" - code:e.readyState - userInfo:@{ NSLocalizedDescriptionKey: @"Connection with the event source was closed." }]; - - [self _dispatchEvent:e type:ReadyStateEvent]; - [self _dispatchEvent:e type:ErrorEvent]; - + self.eventSourceTask = nil; + + if (wasClosed) { + return; + } + + Event *e = [Event new]; + e.readyState = kEventStateClosed; + e.error = error ?: [NSError errorWithDomain:@"" + code:e.readyState + userInfo:@{ NSLocalizedDescriptionKey: @"Connection with the event source was closed." }]; + + [self _dispatchEvent:e type:ReadyStateEvent]; + [self _dispatchEvent:e type:ErrorEvent]; + __weak __typeof(self) const weakSelf = self; dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(_retryInterval * NSEC_PER_SEC)); dispatch_after(popTime, connectionQueue, ^(void){ @@ -216,50 +202,54 @@ - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didComp - (void)_open { - wasClosed = NO; - NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:self.eventURL - cachePolicy:NSURLRequestReloadIgnoringCacheData - timeoutInterval:self.timeoutInterval]; - if (self.lastEventID) { - [request setValue:self.lastEventID forHTTPHeaderField:@"Last-Event-ID"]; - } - - NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] - delegate:self - delegateQueue:[NSOperationQueue currentQueue]]; - - self.eventSourceTask = [session dataTaskWithRequest:request]; - [self.eventSourceTask resume]; + wasClosed = NO; + NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:self.eventURL + cachePolicy:NSURLRequestReloadIgnoringCacheData + timeoutInterval:self.timeoutInterval]; + if (self.lastEventID) { + [request setValue:self.lastEventID forHTTPHeaderField:@"Last-Event-ID"]; + } + + NSURLSessionConfiguration * const sessionContiguration = NSURLSessionConfiguration.defaultSessionConfiguration; + if(_authorization) + sessionContiguration.HTTPAdditionalHeaders = @{ @"Authorization": _authorization }; + + NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionContiguration + delegate:self + delegateQueue:[NSOperationQueue currentQueue]]; + + self.eventSourceTask = [session dataTaskWithRequest:request]; + [self.eventSourceTask resume]; [session finishTasksAndInvalidate]; - - Event *e = [Event new]; - e.readyState = kEventStateConnecting; - - [self _dispatchEvent:e type:ReadyStateEvent]; - - if (![NSThread isMainThread]) { - CFRunLoopRun(); - } + + Event *e = [Event new]; + e.readyState = kEventStateConnecting; + + [self _dispatchEvent:e type:ReadyStateEvent]; + + if (![NSThread isMainThread]) { + CFRunLoopRun(); + } } - (void)_dispatchEvent:(Event *)event type:(NSString * const)type { - NSArray *errorHandlers = self.listeners[type]; - for (EventSourceEventHandler handler in errorHandlers) { - dispatch_async(connectionQueue, ^{ - handler(event); - }); - } + NSArray *errorHandlers = self.listeners[type]; + for (EventSourceEventHandler handler in errorHandlers) { + dispatch_async(connectionQueue, ^{ + handler(event); + }); + } } - (void)_dispatchEvent:(Event *)event { - [self _dispatchEvent:event type:MessageEvent]; - - if (event.event != nil) { - [self _dispatchEvent:event type:event.event]; - } + [self _dispatchEvent:event type:MessageEvent]; + + if (event.event != nil) { + [self _dispatchEvent:event type:event.event]; + } } @end @@ -270,25 +260,25 @@ @implementation Event - (NSString *)description { - NSString *state = nil; - switch (self.readyState) { - case kEventStateConnecting: - state = @"CONNECTING"; - break; - case kEventStateOpen: - state = @"OPEN"; - break; - case kEventStateClosed: - state = @"CLOSED"; - break; - } - - return [NSString stringWithFormat:@"<%@: readyState: %@, id: %@; event: %@; data: %@>", - [self class], - state, - self.id, - self.event, - self.data]; + NSString *state = nil; + switch (self.readyState) { + case kEventStateConnecting: + state = @"CONNECTING"; + break; + case kEventStateOpen: + state = @"OPEN"; + break; + case kEventStateClosed: + state = @"CLOSED"; + break; + } + + return [NSString stringWithFormat:@"<%@: readyState: %@, id: %@; event: %@; data: %@>", + [self class], + state, + self.id, + self.event, + self.data]; } @end From 8fad4fb23721d918e2ad3ca2b954eed74ce8212f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roger=20Jo=CC=88nsson?= Date: Sun, 6 Jun 2021 15:28:26 +0200 Subject: [PATCH 5/7] Header too... --- EventSource/EventSource.h | 19 ++----------------- 1 file changed, 2 insertions(+), 17 deletions(-) diff --git a/EventSource/EventSource.h b/EventSource/EventSource.h index 8cb9fc0..3692f3f 100644 --- a/EventSource/EventSource.h +++ b/EventSource/EventSource.h @@ -42,27 +42,12 @@ typedef void (^EventSourceEventHandler)(Event *event); /// Connect to and receive Server-Sent Events (SSEs). @interface EventSource : NSObject -/// Returns a new instance of EventSource with the specified URL. -/// -/// @param URL The URL of the EventSource. -+ (instancetype)eventSourceWithURL:(NSURL *)URL; - -/// Returns a new instance of EventSource with the specified URL. -/// -/// @param URL The URL of the EventSource. -/// @param timeoutInterval The request timeout interval in seconds. See NSURLRequest for more details. Default: 5 minutes. -+ (instancetype)eventSourceWithURL:(NSURL *)URL timeoutInterval:(NSTimeInterval)timeoutInterval; - -/// Creates a new instance of EventSource with the specified URL. -/// -/// @param URL The URL of the EventSource. -- (instancetype)initWithURL:(NSURL *)URL; - /// Creates a new instance of EventSource with the specified URL. /// /// @param URL The URL of the EventSource. +/// @param authorization HTTP authorization header parameter. /// @param timeoutInterval The request timeout interval in seconds. See NSURLRequest for more details. Default: 5 minutes. -- (instancetype)initWithURL:(NSURL *)URL timeoutInterval:(NSTimeInterval)timeoutInterval; +- (instancetype)initWithURL:(NSURL *)URL authorization:(NSString *)authorization timeoutInterval:(NSTimeInterval)timeoutInterval; /// Registers an event handler for the Message event. /// From c762ad256036805cb449e07bbeede2e99cdf8714 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roger=20Jo=CC=88nsson?= Date: Mon, 16 Aug 2021 13:21:22 +0200 Subject: [PATCH 6/7] Partial data might be received. --- EventSource/EventSource.m | 94 +++++++++++++++++++++------------------ 1 file changed, 51 insertions(+), 43 deletions(-) diff --git a/EventSource/EventSource.m b/EventSource/EventSource.m index e337abb..27ce3c4 100644 --- a/EventSource/EventSource.m +++ b/EventSource/EventSource.m @@ -36,6 +36,8 @@ @interface EventSource () { @property (nonatomic, assign) NSTimeInterval retryInterval; @property (nonatomic, strong) id lastEventID; +@property (nonatomic) NSMutableString *buffer; + - (void)_open; - (void)_dispatchEvent:(Event *)e; @@ -52,7 +54,7 @@ - (instancetype)initWithURL:(NSURL *)URL authorization:(NSString *)authorization _authorization = authorization; _timeoutInterval = timeoutInterval; _retryInterval = ES_RETRY_INTERVAL; - + messageQueue = dispatch_queue_create("co.cwbrn.eventsource-queue", DISPATCH_QUEUE_SERIAL); connectionQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0); @@ -120,56 +122,59 @@ - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)data - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data { - NSString *eventString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; - NSArray *lines = [eventString componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]]; + [_buffer appendString:[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]]; - Event *event = [Event new]; + while(1) + { + NSRange range; + if((range = [_buffer rangeOfString:ESEventSeparatorLFLF]).location != NSNotFound || + (range = [_buffer rangeOfString:ESEventSeparatorCRCR]).location != NSNotFound || + (range = [_buffer rangeOfString:ESEventSeparatorCRLFCRLF]).location != NSNotFound) + { + [self didReceiveString:[_buffer substringToIndex:range.location]]; + + [_buffer deleteCharactersInRange:NSMakeRange(0, NSMaxRange(range))]; + } + else + break; + } +} + +- (void)didReceiveString:(NSString *)string +{ + Event * const event = [Event new]; event.readyState = kEventStateOpen; - for (NSString *line in lines) { - if ([line hasPrefix:ESKeyValueDelimiter]) { - continue; - } - - if (!line || line.length == 0) { - if (event.data != nil) { - dispatch_async(messageQueue, ^{ - [self _dispatchEvent:event]; - }); - - event = [Event new]; - event.readyState = kEventStateOpen; - } - continue; - } - - @autoreleasepool { - NSScanner *scanner = [NSScanner scannerWithString:line]; - scanner.charactersToBeSkipped = [NSCharacterSet whitespaceCharacterSet]; + for(NSString * const line in [string componentsSeparatedByCharactersInSet:NSCharacterSet.newlineCharacterSet]) + { + @autoreleasepool + { + NSScanner * const scanner = [NSScanner scannerWithString:line]; + scanner.charactersToBeSkipped = NSCharacterSet.whitespaceCharacterSet; - NSString *key, *value; - [scanner scanUpToString:ESKeyValueDelimiter intoString:&key]; - [scanner scanString:ESKeyValueDelimiter intoString:nil]; - [scanner scanUpToCharactersFromSet:[NSCharacterSet newlineCharacterSet] intoString:&value]; - - if (key && value) { - if ([key isEqualToString:ESEventEventKey]) { - event.event = value; - } else if ([key isEqualToString:ESEventDataKey]) { - if (event.data != nil) { - event.data = [event.data stringByAppendingFormat:@"\n%@", value]; - } else { - event.data = value; - } - } else if ([key isEqualToString:ESEventIDKey]) { - event.id = value; - self.lastEventID = event.id; - } else if ([key isEqualToString:ESEventRetryKey]) { - self.retryInterval = [value doubleValue]; + NSString *key; + if([scanner scanUpToString:ESKeyValueDelimiter intoString:&key]) + { + [scanner scanString:ESKeyValueDelimiter intoString:nil]; + + NSString *value; + if([scanner scanUpToCharactersFromSet:NSCharacterSet.newlineCharacterSet intoString:&value]) + { + if([key isEqualToString:ESEventEventKey]) + event.event = value; + else if([key isEqualToString:ESEventDataKey]) + event.data = (event.data ? [event.data stringByAppendingFormat:@"\n%@", value] : value); + else if([key isEqualToString:ESEventIDKey]) + _lastEventID = event.id = value; + else if([key isEqualToString:ESEventRetryKey]) + _retryInterval = value.doubleValue; } } } } + + if(event.data) + dispatch_async(messageQueue, ^{ [self _dispatchEvent:event]; }); } - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(nullable NSError *)error @@ -203,6 +208,9 @@ - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didComp - (void)_open { wasClosed = NO; + + _buffer = [NSMutableString new]; + NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:self.eventURL cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:self.timeoutInterval]; From 6c68ce2c1380b38f58e54d3200f33c85eeb62a73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roger=20Jo=CC=88nsson?= Date: Mon, 16 Aug 2021 15:18:35 +0200 Subject: [PATCH 7/7] Safer --- EventSource/EventSource.m | 32 ++++++++++++++++++-------------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/EventSource/EventSource.m b/EventSource/EventSource.m index 27ce3c4..0908c4c 100644 --- a/EventSource/EventSource.m +++ b/EventSource/EventSource.m @@ -54,7 +54,7 @@ - (instancetype)initWithURL:(NSURL *)URL authorization:(NSString *)authorization _authorization = authorization; _timeoutInterval = timeoutInterval; _retryInterval = ES_RETRY_INTERVAL; - + messageQueue = dispatch_queue_create("co.cwbrn.eventsource-queue", DISPATCH_QUEUE_SERIAL); connectionQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0); @@ -122,21 +122,25 @@ - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)data - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data { - [_buffer appendString:[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]]; - - while(1) + NSString * const string = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; + if(string) { - NSRange range; - if((range = [_buffer rangeOfString:ESEventSeparatorLFLF]).location != NSNotFound || - (range = [_buffer rangeOfString:ESEventSeparatorCRCR]).location != NSNotFound || - (range = [_buffer rangeOfString:ESEventSeparatorCRLFCRLF]).location != NSNotFound) + [_buffer appendString:string]; + + while(1) { - [self didReceiveString:[_buffer substringToIndex:range.location]]; - - [_buffer deleteCharactersInRange:NSMakeRange(0, NSMaxRange(range))]; + NSRange range; + if((range = [_buffer rangeOfString:ESEventSeparatorLFLF]).location != NSNotFound || + (range = [_buffer rangeOfString:ESEventSeparatorCRCR]).location != NSNotFound || + (range = [_buffer rangeOfString:ESEventSeparatorCRLFCRLF]).location != NSNotFound) + { + [self didReceiveString:[_buffer substringToIndex:range.location]]; + + [_buffer deleteCharactersInRange:NSMakeRange(0, NSMaxRange(range))]; + } + else + break; } - else - break; } } @@ -210,7 +214,7 @@ - (void)_open wasClosed = NO; _buffer = [NSMutableString new]; - + NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:self.eventURL cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:self.timeoutInterval];