I'm using a custom NSURLProtocol class to implement IP direct connection. Now the received data format from our server cloud be gzip, so I will inflate the data when the encoding foramt is "gzip", below is the key code:
//encoding format
...
NSString *contentEncoding = headerFields[@"Content-Encoding"];
self.isGzipType = contentEncoding.length > 0 && [contentEncoding isEqualToString:@"gzip"];
...
// callback
- (void)handleStreamDataDidReceive:(NSInputStream *)aInputStream statusCode:(long)statusCode {
uint8_t buffer[16 * 1024];
uint8_t *buf = NULL;
NSUInteger length = 0;
if (![aInputStream getBuffer:&buf length:&length]) {
NSInteger amount = [self.inputStream read:buffer maxLength:sizeof(buffer)];
buf = buffer;
length = amount;
}
// Assuming the data is complete and ready to be processed
if (length > 0) {
NSData *data = [[NSData alloc] initWithBytes:buf length:length];
if (self.isGzipType) {
NSData *gzipData = [data gzipInflate];
if (gzipData != nil && gzipData.length > 0) {
data = gzipData;
}
}
[self.client URLProtocol:self didLoadData:data];
}
and when the data is not that big, that is to say, the delegate method of case 'NSStreamEventHasBytesAvailable' only called once, all is well.
- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode {
if (eventCode == NSStreamEventHasBytesAvailable) {
if (![aStream isKindOfClass:[NSInputStream class]]) {
return;
}
[self onStreamHasBytesAvailable:aStream];
}
...
Now if the delegate method of case 'NSStreamEventHasBytesAvailable' called more than once,
I will get a nil data from NSData *gzipData = [data gzipInflate];
and therefore the 'self.client' would got a error response.
I think this is because the fragment of 'gzip' data could not be inflated. Have you guys ever met this before and how you solved this?
I handle this in the below method:
- (void)handleStreamDataDidReceive:(NSInputStream *)aInputStream statusCode:(long)statusCode {
uint8_t buffer[16 * 1024];
uint8_t *buf = NULL;
NSUInteger length = 0;
if (![aInputStream getBuffer:&buf length:&length]) {
NSInteger amount = [self.inputStream read:buffer maxLength:sizeof(buffer)];
buf = buffer;
length = amount;
}
// Assuming the data is complete and ready to be processed
if (length > 0) {
NSData *data = [[NSData alloc] initWithBytes:buf length:length];
if (!self.receivedData) {
self.receivedData = [NSMutableData data];
}
[self.receivedData appendData:data];
}
}
else if (eventCode == NSStreamEventEndEncountered) {
if (self.isGzipType) {
self.receivedData = [self.receivedData gzipInflate];
}
[self.client URLProtocol:self didLoadData:self.receivedData];
[self closeStream:aStream];
[self.client URLProtocolDidFinishLoading:self];
}
As you can see, I append each fragment data in my own 'receivedData' and inflate it if possible when 'eventCode == NSStreamEventEndEncountered', this solved my problem, but I'm not sure is there any risk in my solution. I'd appreciate it if someone could help me out.
Is there any other way to handle my problem, or is my solution is safety enough to put into service.
Is there a reason you can't just use WebSockets and NSURLSessionWebSocketTask? It seems like you're reinventing the wheel here, and using a standard protocol and built-in API is going to be a lot easier than rolling your own custom solution, not to mention probably faster and more reliable.
Anyway, the short answer is that the structure of gzipped data starts with compression tables, and the compression tables change over time as the encoder receives more data. By using a compressor that is tied to the data object (which changes every time), it is trying to use the standard baseline decoding table, and that won't work after the first chunk or two, if at all.
The only way to decode something like that would be with a single instance of a decoder that is allocated and stored in an object that hangs off of the actual NSURLSessionTask object — either with associated objects (assuming you aren't using background sessions) or by using a combination of the task identifier and the session object as a key in an NSDictionary. That way you are feeding the subsequent chunks to the same decoder instance, and it can decode it using the correct symbol table.
You can probably start with this library: https://www.zlib.net/
Or, as I said, just use the OS-provided functionality for doing this (and change the server side if needed to use that standard protocol).