-
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 1 commit
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 |
---|---|---|
|
@@ -59,6 +59,8 @@ extern NSString *const SRHTTPResponseErrorKey; | |
|
||
@protocol SRWebSocketDelegate; | ||
|
||
typedef void (^SRSendCompleteBlock)(NSError * _Nullable error); | ||
|
||
///-------------------------------------- | ||
#pragma mark - SRWebSocket | ||
///-------------------------------------- | ||
|
@@ -278,6 +280,18 @@ extern NSString *const SRHTTPResponseErrorKey; | |
*/ | ||
- (BOOL)sendString:(NSString *)string error:(NSError **)error NS_SWIFT_NAME(send(string:)); | ||
|
||
/** | ||
Send a UTF-8 String to the server. | ||
|
||
@param string String to send. | ||
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: Please align |
||
@param complete The call back of send result. | ||
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: Double space after |
||
If an error occurs, this block will invoked with an `NSerror` object cantaining information about the error; Otherwise | ||
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: |
||
this block will invoked with `nil`. | ||
|
||
@return `YES` if the string was scheduled to send, otherwise - `NO`. | ||
*/ | ||
- (BOOL)sendString:(NSString *)string complete:(SRSendCompleteBlock)complete; | ||
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. Let's improve the naming here to the following. Also, will improve the Swift imported method. - (BOOL)sendString:(NSString *)string
completion:(nullable SRSendCompletionBlock)completion NS_SWIFT_NAME(send(string: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. The above code block also ensures that everyone can pass |
||
|
||
/** | ||
Send binary data to the server. | ||
|
||
|
@@ -302,6 +316,18 @@ extern NSString *const SRHTTPResponseErrorKey; | |
*/ | ||
- (BOOL)sendDataNoCopy:(nullable NSData *)data error:(NSError **)error NS_SWIFT_NAME(send(dataNoCopy:)); | ||
|
||
/** | ||
Send binary data to the server, without making a defensive copy of it first. | ||
|
||
@param data Data to send. | ||
@param complete The call back of send result. | ||
If an error occurs, this block will invoked with an `NSerror` object cantaining information about the error; Otherwise | ||
this block will invoked with `nil`. | ||
|
||
@return `YES` if the string was scheduled to send, otherwise - `NO`. | ||
*/ | ||
- (BOOL)sendDataNoCopy:(nullable NSData *)data completed:(SRSendCompleteBlock)complete; | ||
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 above method aka rename this to - (BOOL)sendDataNoCopy:(nullable NSData *)data
completion:(nullable SRSendCompletionBlock)completion NS_SWIFT_NAME(send(dataNoCopy: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. The above code block also ensures that everyone can pass nil to the completion argument, since it looks like you are validating it anyway. |
||
|
||
/** | ||
Send Ping message to the server with optional data. | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -69,6 +69,14 @@ | |
NSString *const SRWebSocketErrorDomain = @"SRWebSocketErrorDomain"; | ||
NSString *const SRHTTPResponseErrorKey = @"HTTPResponseStatusCode"; | ||
|
||
@interface SRDataCallback : NSObject | ||
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 you are constructing this one time only, let's ensure we can't mutate @interface SRDataCallback : NSObject
@property (nonatomic, assign) NSRange range;
@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 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. The above code block also contains the fixes for minor nits, like whitespace, naming as well as proper method attributes ( |
||
@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) SRSendCompleteBlock completeBlock; | ||
@end | ||
|
||
@implementation SRDataCallback | ||
@end | ||
|
||
@interface SRWebSocket () <NSStreamDelegate> | ||
|
||
@property (atomic, assign, readwrite) SRReadyState readyState; | ||
|
@@ -138,6 +146,9 @@ @implementation SRWebSocket { | |
|
||
// proxy support | ||
SRProxyConnect *_proxyConnect; | ||
|
||
NSMutableDictionary<NSNumber *, SRDataCallback *> *_sendCallbacks; | ||
|
||
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 unneeded whitespace. |
||
} | ||
|
||
@synthesize readyState = _readyState; | ||
|
@@ -179,6 +190,8 @@ - (instancetype)initWithURLRequest:(NSURLRequest *)request protocols:(NSArray<NS | |
_consumerPool = [[SRIOConsumerPool alloc] init]; | ||
|
||
_scheduledRunloops = [[NSMutableSet alloc] init]; | ||
|
||
_sendCallbacks = [[NSMutableDictionary alloc] init]; | ||
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: Either use |
||
|
||
return self; | ||
} | ||
|
@@ -545,6 +558,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.completeBlock) { | ||
obj.completeBlock(error); | ||
} | ||
}]; | ||
[_sendCallbacks removeAllObjects]; | ||
|
||
if (self.readyState != SR_CLOSED) { | ||
_failed = YES; | ||
[self.delegateController performDelegateBlock:^(id<SRWebSocketDelegate> _Nullable delegate, SRDelegateAvailableMethods availableMethods) { | ||
|
@@ -563,14 +583,27 @@ - (void)_failWithError:(NSError *)error; | |
}); | ||
} | ||
|
||
- (void)_writeData:(NSData *)data; | ||
- (void)_writeData:(NSData *)data | ||
{ | ||
[self assertOnWorkQueue]; | ||
[self _writeData:data complete:NULL]; | ||
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. Please use |
||
} | ||
|
||
- (void)_writeData:(NSData *)data complete:(SRSendCompleteBlock)complete { | ||
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 public API - please rename to - (void)_writeData:(NSData *)data 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. And another nit on top of this: |
||
[self assertOnWorkQueue]; | ||
|
||
if (_closeWhenFinishedWriting) { | ||
if (complete) { | ||
complete(SRErrorWithCodeDescription(2134, @"socket is closed")); | ||
} | ||
return; | ||
} | ||
|
||
|
||
SRDataCallback *record = [[SRDataCallback alloc] init]; | ||
record.completeBlock = complete; | ||
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. This |
||
NSUInteger location = dispatch_data_get_size(_outputBuffer); | ||
record.range = NSMakeRange(location, [data length]); | ||
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: Please use dot-syntax for ObjC properties aka |
||
_sendCallbacks[@([data hash])] = 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. Looks like the most important part of this code review:
You need to change the key here to probably |
||
|
||
__block NSData *strongData = data; | ||
dispatch_data_t newData = dispatch_data_create(data.bytes, data.length, nil, ^{ | ||
strongData = nil; | ||
|
@@ -594,18 +627,32 @@ - (void)send:(nullable id)message | |
|
||
- (BOOL)sendString:(NSString *)string error:(NSError **)error | ||
{ | ||
return [self sendString:string error:error complete:NULL]; | ||
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: Use |
||
} | ||
|
||
- (BOOL)sendString:(NSString *)string complete:(SRSendCompleteBlock)complete { | ||
return [self sendString:string error:NULL complete:complete]; | ||
} | ||
|
||
- (BOOL)sendString:(NSString *)string error:(NSError **)error complete:(SRSendCompleteBlock)complete { | ||
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 (complete) { | ||
complete(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] complete:complete]; | ||
}); | ||
return YES; | ||
} | ||
|
@@ -618,20 +665,33 @@ - (BOOL)sendData:(nullable NSData *)data error:(NSError **)error | |
|
||
- (BOOL)sendDataNoCopy:(nullable NSData *)data error:(NSError **)error | ||
{ | ||
return [self sendDataNoCopy:data error:error completed:NULL]; | ||
} | ||
|
||
- (BOOL)sendDataNoCopy:(nullable NSData *)data completed:(SRSendCompleteBlock)complete { | ||
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: Opening bracket on the next line. |
||
return [self sendDataNoCopy:data error:NULL completed:complete]; | ||
} | ||
|
||
- (BOOL)sendDataNoCopy:(nullable NSData *)data error:(NSError **)error completed:(SRSendCompleteBlock)complete { | ||
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: Opening bracket on the next line. |
||
if (self.readyState != SR_OPEN) { | ||
NSString *message = @"Invalid State: Cannot call `sendDataNoCopy:error:` until connection is open."; | ||
NSError *locialError = SRErrorWithCodeDescription(2134, message); | ||
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: |
||
if (error) { | ||
*error = SRErrorWithCodeDescription(2134, message); | ||
*error = locialError; | ||
} | ||
|
||
if (complete) { | ||
complete(locialError); | ||
} | ||
SRDebugLog(message); | ||
return NO; | ||
} | ||
|
||
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: Revert white space addition. |
||
dispatch_async(_workQueue, ^{ | ||
if (data) { | ||
[self _sendFrameWithOpcode:SROpCodeBinaryFrame data:data]; | ||
[self _sendFrameWithOpcode:SROpCodeBinaryFrame data:data complete:complete]; | ||
} else { | ||
[self _sendFrameWithOpcode:SROpCodeTextFrame data:nil]; | ||
[self _sendFrameWithOpcode:SROpCodeTextFrame data:nil complete:complete]; | ||
} | ||
}); | ||
return YES; | ||
|
@@ -1051,10 +1111,29 @@ - (void)_pumpWriting; | |
[self _failWithError:error]; | ||
return; | ||
} | ||
|
||
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 whitespace addition. |
||
_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]; | ||
if (obj.completeBlock) { | ||
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. This can go away once we can ensure that |
||
obj.completeBlock(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; | ||
} | ||
|
@@ -1314,74 +1393,85 @@ -(void)_pumpScanner; | |
|
||
static const size_t SRFrameHeaderOverhead = 32; | ||
|
||
- (void)_sendFrameWithOpcode:(SROpCode)opCode data:(NSData *)data | ||
{ | ||
- (void)_sendFrameWithOpcode:(SROpCode)opCode data:(NSData *)data complete:(SRSendCompleteBlock)complete { | ||
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: Move the opening bracket to the next line. |
||
[self assertOnWorkQueue]; | ||
|
||
if (!data) { | ||
if (complete) { | ||
complete(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 (complete) { | ||
complete(SRErrorWithCodeDescription(SRStatusCodeMessageTooBig, reason)); | ||
} | ||
return; | ||
} | ||
uint8_t *frameBuffer = (uint8_t *)frameData.mutableBytes; | ||
|
||
// set fin | ||
frameBuffer[0] = SRFinMask | opCode; | ||
|
||
// set the mask and header | ||
frameBuffer[1] |= SRMaskMask; | ||
|
||
size_t frameBufferSize = 2; | ||
|
||
if (payloadLength < 126) { | ||
frameBuffer[1] |= payloadLength; | ||
} else { | ||
uint64_t declaredPayloadLength = 0; | ||
size_t declaredPayloadLengthSize = 0; | ||
|
||
if (payloadLength <= UINT16_MAX) { | ||
frameBuffer[1] |= 126; | ||
|
||
declaredPayloadLength = CFSwapInt16BigToHost((uint16_t)payloadLength); | ||
declaredPayloadLengthSize = sizeof(uint16_t); | ||
} else { | ||
frameBuffer[1] |= 127; | ||
|
||
declaredPayloadLength = CFSwapInt64BigToHost((uint64_t)payloadLength); | ||
declaredPayloadLengthSize = sizeof(uint64_t); | ||
} | ||
|
||
memcpy((frameBuffer + frameBufferSize), &declaredPayloadLength, declaredPayloadLengthSize); | ||
frameBufferSize += declaredPayloadLengthSize; | ||
} | ||
|
||
const uint8_t *unmaskedPayloadBuffer = (uint8_t *)data.bytes; | ||
uint8_t *maskKey = frameBuffer + frameBufferSize; | ||
|
||
size_t randomBytesSize = sizeof(uint32_t); | ||
int result = SecRandomCopyBytes(kSecRandomDefault, randomBytesSize, maskKey); | ||
if (result != 0) { | ||
//TODO: (nlutsenko) Check if there was an error. | ||
} | ||
frameBufferSize += randomBytesSize; | ||
|
||
// Copy and unmask the buffer | ||
uint8_t *frameBufferPayloadPointer = frameBuffer + frameBufferSize; | ||
|
||
memcpy(frameBufferPayloadPointer, unmaskedPayloadBuffer, payloadLength); | ||
SRMaskBytesSIMD(frameBufferPayloadPointer, payloadLength, maskKey); | ||
frameBufferSize += payloadLength; | ||
|
||
assert(frameBufferSize <= frameData.length); | ||
frameData.length = frameBufferSize; | ||
|
||
[self _writeData:frameData complete:complete]; | ||
} | ||
|
||
[self _writeData:frameData]; | ||
- (void)_sendFrameWithOpcode:(SROpCode)opCode data:(NSData *)data | ||
{ | ||
[self _sendFrameWithOpcode:opCode data:data complete:NULL]; | ||
} | ||
|
||
- (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.
Nit: Rename to
SRSendCompletionBlock
.