Skip to content

Instantly share code, notes, and snippets.

@elsurudo
Last active July 18, 2016 08:59
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save elsurudo/6039065 to your computer and use it in GitHub Desktop.
Save elsurudo/6039065 to your computer and use it in GitHub Desktop.
Class to handle Server Sent Events in a similar way to browser EventSource
#import <Foundation/Foundation.h>
@class EventSource;
@protocol EventSourceDelegate <NSObject>
@optional
- (void)eventSourceDidOpenConnection:(EventSource*)eventSource;
- (void)eventSource:(EventSource*)eventSource didFailWithError:(NSError*)error;
@required
- (void)eventSource:(EventSource *)eventSource didReceiveMessage:(NSString*)message
eventID:(NSString*)eventID
type:(NSString*)type;
@end
@interface EventSource : NSObject
- (id)initWithURL:(NSURL*)url delegate:(NSObject<EventSourceDelegate>*)delegate;
- (void)cancel;
@property (weak, nonatomic) NSObject <EventSourceDelegate> *delegate;
@property (nonatomic, readonly) NSURL *url;
@property (nonatomic, readonly) NSString *lastEventID;
@end
#import "EventSource.h"
#define kErrorCodeBadResponseStatusCode 1
#define kErrorCodeBadResponseContentType 2
@interface EventSource () <NSURLConnectionDelegate, NSURLConnectionDataDelegate>
@property (nonatomic, strong) NSURL *url;
@property (nonatomic, strong) NSURLConnection *connection;
@property (nonatomic, strong) NSMutableString *currentMessage;
@end
@implementation EventSource
- (id)initWithURL:(NSURL*)url delegate:(NSObject<EventSourceDelegate>*)delegate
{
self = [super init];
if (self)
{
_url = url;
_delegate = delegate;
// start connection
_currentMessage = [NSMutableString string];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
_connection = [NSURLConnection connectionWithRequest:request delegate:self];
}
return self;
}
- (void)cancel
{
[_connection cancel];
}
#pragma mark - NSURLConnectionDelegate methods
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
if ([_delegate respondsToSelector:@selector(eventSource:didFailWithError:)]) {
[_delegate eventSource:self didFailWithError:error];
}
}
#pragma mark - NSURLConnectionDataDelegate methods
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse*)response;
// ensure we've got a successful response
if ([[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(200, 100)] containsIndex:[httpResponse statusCode]])
{
// ensure HTTP content-type is text/event-stream
if ([httpResponse.allHeaderFields[@"content-type"] isEqualToString:@"text/event-stream"])
{
if ([_delegate respondsToSelector:@selector(eventSourceDidOpenConnection:)]) {
[_delegate eventSourceDidOpenConnection:self];
}
}
else
{
if ([_delegate respondsToSelector:@selector(eventSource:didFailWithError:)])
{
NSError *error = [NSError errorWithDomain:NSCocoaErrorDomain
code:kErrorCodeBadResponseContentType
userInfo:@{ NSLocalizedDescriptionKey: NSLocalizedString(@"Server must respond with a content-type of 'text/event-stream'.", nil) }];
[_delegate eventSource:self didFailWithError:error];
}
[_connection cancel];
}
}
else
{
if ([_delegate respondsToSelector:@selector(eventSource:didFailWithError:)])
{
NSError *error = [NSError errorWithDomain:NSCocoaErrorDomain
code:kErrorCodeBadResponseStatusCode
userInfo:@{ NSLocalizedDescriptionKey: NSLocalizedString(@"Server must respond with a status code in the 200-299 range.", nil) }];
[_delegate eventSource:self didFailWithError:error];
}
[_connection cancel];
}
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
NSString *receivedString = [[NSString alloc] initWithData:data
encoding:NSUTF8StringEncoding];
[_currentMessage appendString:receivedString];
NSArray *messages = [_currentMessage componentsSeparatedByString:@"\n\n"];
for (int i = 0; i < messages.count - 1; i++)
{
[self processMessage:messages[i]];
}
_currentMessage = [NSMutableString stringWithString:[messages lastObject]];
}
#pragma mark - Privates
- (void)processMessage:(NSString*)message
{
NSMutableString *eventMessage = [NSMutableString string];
NSString *eventType = nil;
NSString *eventID = nil;
NSArray *lines = [message componentsSeparatedByString:@"\n"];
for (NSString *line in lines)
{
if ([line hasPrefix:@":"])
{
// comment; do nothing
}
else if ([line hasPrefix:@"id:"])
{
// id
NSString *value = [line substringFromIndex:3];
value = [value stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
eventID = value;
_lastEventID = value;
}
else if ([line hasPrefix:@"event:"])
{
// event
NSString *value = [line substringFromIndex:6];
value = [value stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
eventType = value;
}
else if ([line hasPrefix:@"data:"])
{
// data
NSString *value = [line substringFromIndex:5];
value = [value stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
[eventMessage appendFormat:@"%@\n", value];
}
}
[_delegate eventSource:self didReceiveMessage:eventMessage eventID:eventType type:eventType];
}
@end
@seandong
Copy link

seandong commented Feb 9, 2014

change [httpResponse.allHeaderFields[@"content-type"] isEqualToString:@"text/event-stream"] to [httpResponse.allHeaderFields[@"content-type"] hasPrefix:@"text/event-stream"] may be better.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment