-
Notifications
You must be signed in to change notification settings - Fork 2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add the callback of send operation. #458
base: main
Are you sure you want to change the base?
Changes from 7 commits
f1e1cee
c7b8f8d
296c151
f599d0c
ac1c632
399e25b
00545e5
b313fe8
706623c
f630b95
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -69,6 +69,31 @@ | |
NSString *const SRWebSocketErrorDomain = @"SRWebSocketErrorDomain"; | ||
NSString *const SRHTTPResponseErrorKey = @"HTTPResponseStatusCode"; | ||
|
||
@interface SRDataCallback : NSObject | ||
@property (nonatomic, assign) NSRange range; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: Remove extra space aka make it look like |
||
@property (nonatomic, copy, readonly) SRSendCompletionBlock completion; | ||
|
||
+ (instancetype)new NS_UNAVAILABLE; | ||
- (instancetype)init NS_UNAVAILABLE; | ||
- (instancetype)initWithRange:(NSRange)range | ||
completion:(SRSendCompletionBlock)completion NS_DESIGNATED_INITIALIZER; | ||
|
||
@end | ||
|
||
@implementation SRDataCallback | ||
|
||
- (instancetype)initWithRange:(NSRange)range completion:(SRSendCompletionBlock)completion | ||
{ | ||
self = [super init]; | ||
|
||
_range = range; | ||
_completion = [completion copy]; | ||
|
||
return self; | ||
} | ||
|
||
@end | ||
|
||
@interface SRWebSocket () <NSStreamDelegate> | ||
|
||
@property (atomic, assign, readwrite) SRReadyState readyState; | ||
|
@@ -138,6 +163,8 @@ @implementation SRWebSocket { | |
|
||
// proxy support | ||
SRProxyConnect *_proxyConnect; | ||
|
||
NSMutableDictionary<NSNumber *, SRDataCallback *> *_sendCallbacks; | ||
} | ||
|
||
@synthesize readyState = _readyState; | ||
|
@@ -179,6 +206,8 @@ - (instancetype)initWithURLRequest:(NSURLRequest *)request protocols:(NSArray<NS | |
_consumerPool = [[SRIOConsumerPool alloc] init]; | ||
|
||
_scheduledRunloops = [[NSMutableSet alloc] init]; | ||
|
||
_sendCallbacks = [NSMutableDictionary dictionary]; | ||
|
||
return self; | ||
} | ||
|
@@ -548,6 +577,13 @@ - (void)_closeWithProtocolError:(NSString *)message; | |
- (void)_failWithError:(NSError *)error; | ||
{ | ||
dispatch_async(_workQueue, ^{ | ||
[_sendCallbacks enumerateKeysAndObjectsUsingBlock:^(NSNumber * _Nonnull key, SRDataCallback * _Nonnull obj, BOOL * _Nonnull stop) { | ||
if (obj.completion) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since |
||
obj.completion(error); | ||
} | ||
}]; | ||
[_sendCallbacks removeAllObjects]; | ||
|
||
if (self.readyState != SR_CLOSED) { | ||
_failed = YES; | ||
[self.delegateController performDelegateBlock:^(id<SRWebSocketDelegate> _Nullable delegate, SRDelegateAvailableMethods availableMethods) { | ||
|
@@ -566,14 +602,31 @@ - (void)_failWithError:(NSError *)error; | |
}); | ||
} | ||
|
||
- (void)_writeData:(NSData *)data; | ||
- (void)_writeData:(NSData *)data | ||
{ | ||
[self assertOnWorkQueue]; | ||
[self _writeData:data completion:nil]; | ||
} | ||
|
||
- (void)_writeData:(NSData *)data completion:(nullable SRSendCompletionBlock)completion | ||
{ | ||
[self assertOnWorkQueue]; | ||
|
||
if (_closeWhenFinishedWriting) { | ||
if (completion) { | ||
completion(SRErrorWithCodeDescription(2134, @"socket is closed")); | ||
} | ||
return; | ||
} | ||
|
||
|
||
if (completion != nil) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: Replace with |
||
NSUInteger location = dispatch_data_get_size(_outputBuffer); | ||
NSRange dataRange = NSMakeRange(location, data.length); | ||
SRDataCallback *record = [[SRDataCallback alloc] initWithRange:dataRange completion:completion]; | ||
|
||
static NSUInteger keyCount = 0; | ||
_sendCallbacks[@(keyCount++)] = record; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure if this is the best way to use a |
||
} | ||
|
||
__block NSData *strongData = data; | ||
dispatch_data_t newData = dispatch_data_create(data.bytes, data.length, nil, ^{ | ||
strongData = nil; | ||
|
@@ -597,18 +650,32 @@ - (void)send:(nullable id)message | |
|
||
- (BOOL)sendString:(NSString *)string error:(NSError **)error | ||
{ | ||
return [self sendString:string error:error completion:nil]; | ||
} | ||
|
||
- (BOOL)sendString:(NSString *)string completion:(nullable SRSendCompletionBlock)completion { | ||
return [self sendString:string error:NULL completion:completion]; | ||
} | ||
|
||
- (BOOL)sendString:(NSString *)string error:(NSError **)error completion:(nullable SRSendCompletionBlock)completion { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What do you think if we would remove |
||
if (self.readyState != SR_OPEN) { | ||
NSString *message = @"Invalid State: Cannot call `sendString:error:` until connection is open."; | ||
NSError *locialError = SRErrorWithCodeDescription(2134, message); | ||
if (error) { | ||
*error = SRErrorWithCodeDescription(2134, message); | ||
*error = locialError; | ||
} | ||
|
||
if (completion) { | ||
completion(locialError); | ||
} | ||
|
||
SRDebugLog(message); | ||
return NO; | ||
} | ||
|
||
string = [string copy]; | ||
dispatch_async(_workQueue, ^{ | ||
[self _sendFrameWithOpcode:SROpCodeTextFrame data:[string dataUsingEncoding:NSUTF8StringEncoding]]; | ||
[self _sendFrameWithOpcode:SROpCodeTextFrame data:[string dataUsingEncoding:NSUTF8StringEncoding] completion:completion]; | ||
}); | ||
return YES; | ||
} | ||
|
@@ -620,21 +687,36 @@ - (BOOL)sendData:(nullable NSData *)data error:(NSError **)error | |
} | ||
|
||
- (BOOL)sendDataNoCopy:(nullable NSData *)data error:(NSError **)error | ||
{ | ||
return [self sendDataNoCopy:data error:error completion:nil]; | ||
} | ||
|
||
- (BOOL)sendDataNoCopy:(nullable NSData *)data completion:(nullable SRSendCompletionBlock)completion | ||
{ | ||
return [self sendDataNoCopy:data error:NULL completion:completion]; | ||
} | ||
|
||
- (BOOL)sendDataNoCopy:(nullable NSData *)data error:(NSError **)error completion:(SRSendCompletionBlock)completion | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same as for |
||
{ | ||
if (self.readyState != SR_OPEN) { | ||
NSString *message = @"Invalid State: Cannot call `sendDataNoCopy:error:` until connection is open."; | ||
NSError *localError = SRErrorWithCodeDescription(2134, message); | ||
if (error) { | ||
*error = SRErrorWithCodeDescription(2134, message); | ||
*error = localError; | ||
} | ||
|
||
if (completion) { | ||
completion(localError); | ||
} | ||
SRDebugLog(message); | ||
return NO; | ||
} | ||
|
||
dispatch_async(_workQueue, ^{ | ||
if (data) { | ||
[self _sendFrameWithOpcode:SROpCodeBinaryFrame data:data]; | ||
[self _sendFrameWithOpcode:SROpCodeBinaryFrame data:data completion:completion]; | ||
} else { | ||
[self _sendFrameWithOpcode:SROpCodeTextFrame data:nil]; | ||
[self _sendFrameWithOpcode:SROpCodeTextFrame data:nil completion:completion]; | ||
} | ||
}); | ||
return YES; | ||
|
@@ -1060,7 +1142,23 @@ - (void)_pumpWriting; | |
|
||
_outputBufferOffset += bytesWritten; | ||
|
||
NSMutableArray<NSNumber *> *removeKeys = [NSMutableArray array]; | ||
[_sendCallbacks enumerateKeysAndObjectsUsingBlock:^(NSNumber * _Nonnull key, SRDataCallback * _Nonnull obj, BOOL * _Nonnull stop) { | ||
if (NSMaxRange(obj.range) <= _outputBufferOffset) { | ||
[removeKeys addObject:key]; | ||
obj.completion(nil); | ||
} | ||
}]; | ||
|
||
[_sendCallbacks removeObjectsForKeys:removeKeys]; | ||
|
||
if (_outputBufferOffset > SRDefaultBufferSize() && _outputBufferOffset > dataLength / 2) { | ||
[_sendCallbacks enumerateKeysAndObjectsUsingBlock:^(NSNumber * _Nonnull key, SRDataCallback * _Nonnull obj, BOOL * _Nonnull stop) { | ||
NSRange range = obj.range; | ||
range.location -= _outputBufferOffset; | ||
obj.range = range; | ||
}]; | ||
|
||
_outputBuffer = dispatch_data_create_subrange(_outputBuffer, _outputBufferOffset, dataLength - _outputBufferOffset); | ||
_outputBufferOffset = 0; | ||
} | ||
|
@@ -1320,19 +1418,26 @@ -(void)_pumpScanner; | |
|
||
static const size_t SRFrameHeaderOverhead = 32; | ||
|
||
- (void)_sendFrameWithOpcode:(SROpCode)opCode data:(NSData *)data | ||
- (void)_sendFrameWithOpcode:(SROpCode)opCode data:(NSData *)data completion:(SRSendCompletionBlock)completion | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
{ | ||
[self assertOnWorkQueue]; | ||
|
||
if (!data) { | ||
if (completion) { | ||
completion(nil); | ||
} | ||
return; | ||
} | ||
|
||
size_t payloadLength = data.length; | ||
|
||
NSMutableData *frameData = [[NSMutableData alloc] initWithLength:payloadLength + SRFrameHeaderOverhead]; | ||
if (!frameData) { | ||
[self closeWithCode:SRStatusCodeMessageTooBig reason:@"Message too big"]; | ||
NSString *reason = @"Message too big"; | ||
[self closeWithCode:SRStatusCodeMessageTooBig reason:reason]; | ||
if (completion) { | ||
completion(SRErrorWithCodeDescription(SRStatusCodeMessageTooBig, reason)); | ||
} | ||
return; | ||
} | ||
uint8_t *frameBuffer = (uint8_t *)frameData.mutableBytes; | ||
|
@@ -1387,7 +1492,12 @@ - (void)_sendFrameWithOpcode:(SROpCode)opCode data:(NSData *)data | |
assert(frameBufferSize <= frameData.length); | ||
frameData.length = frameBufferSize; | ||
|
||
[self _writeData:frameData]; | ||
[self _writeData:frameData completion:completion]; | ||
} | ||
|
||
- (void)_sendFrameWithOpcode:(SROpCode)opCode data:(NSData *)data | ||
{ | ||
[self _sendFrameWithOpcode:opCode data:data completion:nil]; | ||
} | ||
|
||
- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since you are constructing this one time only, let's ensure we can't mutate
completion
later.You would need to replace the code block with this:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The above code block also contains the fixes for minor nits, like whitespace, naming as well as proper method attributes (
NS_UNAVAILABLE
to make sure no one can initialize this class withalloc] init]
ornew
as well as attribute for designated initializer).