Skip to content

Commit

Permalink
Merge pull request #4 from launchdarkly/dz/streamingBackoff
Browse files Browse the repository at this point in the history
Streaming backoff
  • Loading branch information
arun251 authored Mar 17, 2017
2 parents 4b671be + 053d2a5 commit 476b972
Show file tree
Hide file tree
Showing 2 changed files with 42 additions and 38 deletions.
4 changes: 2 additions & 2 deletions DarklyEventSource.podspec
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
Pod::Spec.new do |s|
s.name = "DarklyEventSource"
s.version = "1.3.0"
s.version = "1.3.1"
s.summary = "HTML5 Server-Sent Events in your Cocoa app."
s.homepage = "https://github.com/launchdarkly/ios-eventsource"
s.license = 'MIT (see LICENSE.txt)'
s.author = { "Neil Cowburn" => "[email protected]" }
s.source = { :git => "https://github.com/launchdarkly/ios-eventsource.git", :tag => "1.3.0" }
s.source = { :git => "https://github.com/launchdarkly/ios-eventsource.git", :tag => "1.3.1" }
s.source_files = 'EventSource', 'EventSource/EventSource.{h,m}'
s.ios.xcconfig = { 'FRAMEWORK_SEARCH_PATHS' => '$(inherited) "$(SDKROOT)/Developer/Library/Frameworks" "$(DEVELOPER_LIBRARY_DIR)/Frameworks"' }
s.osx.xcconfig = { 'FRAMEWORK_SEARCH_PATHS' => '$(inherited) "$(DEVELOPER_LIBRARY_DIR)/Frameworks"' }
Expand Down
76 changes: 40 additions & 36 deletions EventSource/EventSource.m
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

static CGFloat const ES_RETRY_INTERVAL = 1.0;
static CGFloat const ES_DEFAULT_TIMEOUT = 300.0;
static CGFloat const ES_MAX_RECONNECT_TIME = 180.0;

static NSString *const ESKeyValueDelimiter = @":";
static NSString *const ESEventSeparatorLFLF = @"\n\n";
Expand All @@ -34,6 +35,7 @@ @interface EventSource () <NSURLSessionDataDelegate> {
@property (nonatomic, strong) NSMutableDictionary *listeners;
@property (nonatomic, assign) NSTimeInterval timeoutInterval;
@property (nonatomic, assign) NSTimeInterval retryInterval;
@property (nonatomic, assign) NSInteger retryAttempt;
@property (nonatomic, strong) NSString *mobileKey;
@property (nonatomic, strong) id lastEventID;

Expand Down Expand Up @@ -67,10 +69,11 @@ - (instancetype)initWithURL:(NSURL *)URL mobileKey:(NSString *)mobileKey timeout
_eventURL = URL;
_timeoutInterval = timeoutInterval;
_retryInterval = ES_RETRY_INTERVAL;
_retryAttempt = 0;
_mobileKey = mobileKey;
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];
Expand Down Expand Up @@ -114,8 +117,6 @@ - (void)close
[self.eventSourceTask cancel];
}

// -----------------------------------------------------------------------------------------------------------------------------------------

- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask
didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler
{
Expand All @@ -124,11 +125,12 @@ - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)data
// Opened
Event *e = [Event new];
e.readyState = kEventStateOpen;


_retryAttempt = 0;
[self _dispatchEvent:e type:ReadyStateEvent];
[self _dispatchEvent:e type:OpenEvent];
}

if (completionHandler) {
completionHandler(NSURLSessionResponseAllow);
}
Expand All @@ -138,34 +140,34 @@ - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)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) {
dispatch_async(messageQueue, ^{
[self _dispatchEvent:event];
});

event = [Event new];
event.readyState = kEventStateOpen;
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;
Expand All @@ -189,28 +191,26 @@ - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)data
- (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." }];

code:e.readyState
userInfo:@{ NSLocalizedDescriptionKey: @"Connection with the event source was closed." }];
[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_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)([self increaseIntervalWithBackoff] * NSEC_PER_SEC));
dispatch_after(popTime, connectionQueue, ^(void){
[self _open];
});
}

// -------------------------------------------------------------------------------------------------------------------------------------

- (void)_open
{
wasClosed = NO;
Expand All @@ -219,19 +219,19 @@ - (void)_open
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];

Event *e = [Event new];
e.readyState = kEventStateConnecting;

[self _dispatchEvent:e type:ReadyStateEvent];

if (![NSThread isMainThread]) {
CFRunLoopRun();
}
Expand All @@ -250,29 +250,33 @@ - (void)_dispatchEvent:(Event *)event type:(NSString * const)type
- (void)_dispatchEvent:(Event *)event
{
[self _dispatchEvent:event type:MessageEvent];

if (event.event != nil) {
[self _dispatchEvent:event type:event.event];
}
}

- (CGFloat)increaseIntervalWithBackoff {
_retryAttempt++;
return arc4random_uniform(MIN(ES_MAX_RECONNECT_TIME, _retryInterval * pow(2, _retryAttempt)));
}

@end

// ---------------------------------------------------------------------------------------------------------------------

@implementation Event

- (NSString *)description
{
NSString *state = nil;
switch (self.readyState) {
case kEventStateConnecting:
case kEventStateConnecting:
state = @"CONNECTING";
break;
case kEventStateOpen:
case kEventStateOpen:
state = @"OPEN";
break;
case kEventStateClosed:
case kEventStateClosed:
state = @"CLOSED";
break;
}
Expand Down

0 comments on commit 476b972

Please sign in to comment.