forked from pkyeck/socket.IO-objc
-
Notifications
You must be signed in to change notification settings - Fork 0
/
SocketIOTransportXHR.m
281 lines (232 loc) · 8.68 KB
/
SocketIOTransportXHR.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
//
// SocketIOTransportXHR.m
// v0.5.1
//
// based on
// socketio-cocoa https://github.com/fpotter/socketio-cocoa
// by Fred Potter <fpotter@pieceable.com>
//
// using
// https://github.com/square/SocketRocket
//
// reusing some parts of
// /socket.io/socket.io.js
//
// Created by Philipp Kyeck http://beta-interactive.de
//
// With help from
// https://github.com/pkyeck/socket.IO-objc/blob/master/CONTRIBUTORS.md
//
#import "SocketIOTransportXHR.h"
#import "SocketIO.h"
#define DEBUG_LOGS 0
#if DEBUG_LOGS
#define DEBUGLOG(...) NSLog(__VA_ARGS__)
#else
#define DEBUGLOG(...)
#endif
static NSString* kInsecureXHRURL = @"http://%@/socket.io/1/xhr-polling/%@";
static NSString* kSecureXHRURL = @"https://%@/socket.io/1/xhr-polling/%@";
static NSString* kInsecureXHRPortURL = @"http://%@:%d/socket.io/1/xhr-polling/%@";
static NSString* kSecureXHRPortURL = @"https://%@:%d/socket.io/1/xhr-polling/%@";
@interface SocketIOTransportXHR (Private)
- (void) checkAndStartPoll;
- (void) poll:(NSString *)data;
- (void) poll:(NSString *)data retryNumber:(int)retry;
@end
@implementation SocketIOTransportXHR
@synthesize delegate,
isClosed = _isClosed;
- (id) initWithDelegate:(id<SocketIOTransportDelegate>)delegate_
{
self = [super init];
if (self) {
self.delegate = delegate_;
_data = [[NSMutableData alloc] init];
_polls = [[NSMutableDictionary alloc] init];
}
return self;
}
- (void) open
{
NSString *format;
if (delegate.port) {
format = delegate.useSecure ? kSecureXHRPortURL : kInsecureXHRPortURL;
_url = [NSString stringWithFormat:format, delegate.host, delegate.port, delegate.sid];
}
else {
format = delegate.useSecure ? kSecureXHRURL : kInsecureXHRURL;
_url = [NSString stringWithFormat:format, delegate.host, delegate.sid];
}
DEBUGLOG(@"Opening XHR @ %@", _url);
[self poll:nil];
}
- (void) close
{
NSMutableDictionary *pollData;
NSURLConnection *conn;
for (NSString *key in _polls) {
pollData = [_polls objectForKey:key];
conn = [pollData objectForKey:@"connection"];
[conn cancel];
}
[_polls removeAllObjects];
_isClosed = YES;
}
- (BOOL) isReady
{
return YES;
}
- (void) send:(NSString *)request
{
[self poll:request];
}
#pragma mark -
#pragma mark private methods
- (void) checkAndStartPoll
{
if (_isClosed) {
return;
}
BOOL restart = NO;
// no polls currently running -> start one
if ([_polls count] == 0) {
restart = YES;
}
else {
restart = YES;
// look for polls w/o data -> if there, no need to restart
for (NSString *key in _polls) {
NSMutableDictionary *pollData = [_polls objectForKey:key];
if ([pollData objectForKey:@"data"] == nil) {
restart = NO;
break;
}
}
}
if (restart) {
[self poll:nil];
}
}
- (void) poll:(NSString *)data
{
[self poll:data retryNumber:0];
}
- (void) poll:(NSString *)data retryNumber:(int)retry
{
NSTimeInterval timeStamp = [[NSDate date] timeIntervalSince1970];
double unix = timeStamp * 1000;
NSString *url = [_url stringByAppendingString:[NSString stringWithFormat:@"?t=%.0f", unix]];
DEBUGLOG(@"---------------------------------------------------------------------------------------");
DEBUGLOG(@"poll() %@", url);
NSMutableURLRequest *req = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:url]
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:[delegate heartbeatTimeout]];
if (data != nil) {
DEBUGLOG(@"poll() %@", data);
[req setHTTPMethod:@"POST"];
[req setValue:@"text/plain; charset=UTF-8" forHTTPHeaderField:@"Content-Type"];
[req setHTTPBody:[data dataUsingEncoding:NSUTF8StringEncoding]];
}
[req setValue:@"Keep-Alive" forHTTPHeaderField:@"Connection"];
NSURLConnection *conn = [[NSURLConnection alloc] initWithRequest:req delegate:self];
// add pollData to polls dictionary
NSMutableDictionary *pollData = [[NSMutableDictionary alloc] init];
[pollData setObject:[NSNumber numberWithInt:retry] forKey:@"retries"];
[pollData setObject:conn forKey:@"connection"];
[pollData setValue:data forKey:@"data"];
[_polls setObject:pollData forKey:conn.description];
[conn start];
}
#pragma mark -
#pragma mark NSURLConnection delegate methods
- (void) connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
// check for server status code (http://gigliwood.com/weblog/Cocoa/Q__When_is_an_conne.html)
if ([response respondsToSelector:@selector(statusCode)]) {
NSInteger statusCode = [((NSHTTPURLResponse *)response) statusCode];
DEBUGLOG(@"didReceiveResponse() %i", statusCode);
if (statusCode >= 400) {
// stop connecting; no more delegate messages
[connection cancel];
NSString *error = [NSString stringWithFormat:NSLocalizedString(@"Server returned status code %d", @""), statusCode];
NSDictionary *errorInfo = [NSDictionary dictionaryWithObject:error forKey:NSLocalizedDescriptionKey];
NSError *statusError = [NSError errorWithDomain:SocketIOError
code:statusCode
userInfo:errorInfo];
if ([delegate respondsToSelector:@selector(onError:)]) {
[delegate onError:statusError];
}
}
}
[_data setLength:0];
}
- (void) connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
DEBUGLOG(@"didReceiveData(): %@", data);
[_data appendData:data];
}
- (void) connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
DEBUGLOG(@"didFailWithError: %@", [error localizedDescription]);
// retry 3 times or throw error
NSMutableDictionary *pollData = [_polls objectForKey:connection.description];
NSString *data = [pollData objectForKey:@"data"];
[_polls removeObjectForKey:connection.description];
NSNumber *retries = [pollData objectForKey:@"retries"];
if ([retries intValue] < 2) {
[self poll:data retryNumber:[retries intValue] + 1];
}
else {
NSMutableDictionary *errorInfo = [[NSMutableDictionary alloc] init];
[errorInfo setValue:[error localizedDescription] forKey:@"reason"];
[errorInfo setValue:data forKey:@"data"];
if ([delegate respondsToSelector:@selector(onError:)]) {
[delegate onError:[NSError errorWithDomain:SocketIOError
code:SocketIODataCouldNotBeSend
userInfo:errorInfo]];
}
}
}
// Sometimes Socket.IO "batches" up messages in one packet, so we have to split them.
- (NSArray *)packetsFromPayload:(NSString *)payload
{
// "Batched" format is:
// �[packet_0 length]�[packet_0]�[packet_1 length]�[packet_1]�[packet_n length]�[packet_n]
if ([payload hasPrefix:@"\ufffd"]) {
// Payload has multiple packets, split based on the '�' character
// Skip the first character, then split
NSArray *split = [[payload substringFromIndex:1] componentsSeparatedByString:@"\ufffd"];
// Init array with [split count] / 2 because we only need the odd-numbered
NSMutableArray *packets = [NSMutableArray arrayWithCapacity:[split count]/2];
// Now all of the odd-numbered indices are the packets (1, 3, 5, etc.)
[split enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
if (idx % 2 != 0) {
[packets addObject:obj];
}
}];
NSLog(@"Parsed a payload!");
return packets;
}
else {
// Regular single-packet payload
return @[payload];
}
}
- (void) connectionDidFinishLoading:(NSURLConnection *)connection
{
NSString *message = [[NSString alloc] initWithData:_data encoding:NSUTF8StringEncoding];
DEBUGLOG(@"response: __%@__", message);
if (![message isEqualToString:@"1"]) {
NSArray *messages = [self packetsFromPayload:message];
if([delegate respondsToSelector:@selector(onData:)]) {
[messages enumerateObjectsUsingBlock:^(NSString *message, NSUInteger idx, BOOL *stop) {
[delegate onData:message];
}];
}
}
// remove current connection from pool
[_polls removeObjectForKey:connection.description];
[self checkAndStartPoll];
}
@end