Added Gzip support

Summary:
Added Gzip function to RCTUtils. This uses dlopen to load the zlib library at runtime so there's no need to link it into your project.

The main reason for this feature is to support gzipping of HTTP request bodies. Now, if you add 'Content-Encoding:gzip' to your request headers when using XMLHttpRequest, your request body will be automatically gzipped on the native side before sending.

(Note: Gzip decoding of *response* bodies is handled automatically by iOS, and was already available).
This commit is contained in:
Nick Lockwood 2015-07-16 09:34:28 -07:00
parent 36f76e5893
commit 81ad713f5f
3 changed files with 65 additions and 4 deletions

View File

@ -249,8 +249,15 @@ RCT_EXPORT_MODULE()
request.HTTPBody = result[@"body"]; request.HTTPBody = result[@"body"];
NSString *contentType = result[@"contentType"]; NSString *contentType = result[@"contentType"];
if (contentType) { if (contentType) {
[request setValue:contentType forHTTPHeaderField:@"content-type"]; [request setValue:contentType forHTTPHeaderField:@"Content-Type"];
} }
// Gzip the request body
if ([request.allHTTPHeaderFields[@"Content-Encoding"] isEqualToString:@"gzip"]) {
request.HTTPBody = RCTGzipData(request.HTTPBody, -1 /* default */);
[request setValue:[@(request.HTTPBody.length) description] forHTTPHeaderField:@"Content-Length"];
}
[self sendRequest:request [self sendRequest:request
incrementalUpdates:incrementalUpdates incrementalUpdates:incrementalUpdates
responseSender:responseSender]; responseSender:responseSender];

View File

@ -15,7 +15,7 @@
#import "RCTAssert.h" #import "RCTAssert.h"
#import "RCTDefines.h" #import "RCTDefines.h"
// Utility functions for JSON object <-> string serialization/deserialization // JSON serialization/deserialization
RCT_EXTERN NSString *RCTJSONStringify(id jsonObject, NSError **error); RCT_EXTERN NSString *RCTJSONStringify(id jsonObject, NSError **error);
RCT_EXTERN id RCTJSONParse(NSString *jsonString, NSError **error); RCT_EXTERN id RCTJSONParse(NSString *jsonString, NSError **error);
RCT_EXTERN id RCTJSONParseMutable(NSString *jsonString, NSError **error); RCT_EXTERN id RCTJSONParseMutable(NSString *jsonString, NSError **error);
@ -24,7 +24,7 @@ RCT_EXTERN id RCTJSONParseWithOptions(NSString *jsonString, NSError **error, NSJ
// Strip non JSON-safe values from an object graph // Strip non JSON-safe values from an object graph
RCT_EXTERN id RCTJSONClean(id object); RCT_EXTERN id RCTJSONClean(id object);
// Get MD5 hash of a string (TODO: currently unused. Remove?) // Get MD5 hash of a string
RCT_EXTERN NSString *RCTMD5Hash(NSString *string); RCT_EXTERN NSString *RCTMD5Hash(NSString *string);
// Get screen metrics in a thread-safe way // Get screen metrics in a thread-safe way
@ -64,3 +64,6 @@ RCT_EXTERN id RCTNullIfNil(id value);
// Convert data to a Base64-encoded data URL // Convert data to a Base64-encoded data URL
RCT_EXTERN NSURL *RCTDataURL(NSString *mimeType, NSData *data); RCT_EXTERN NSURL *RCTDataURL(NSString *mimeType, NSData *data);
// Gzip functionality - compression level in range 0 - 1 (-1 for default)
RCT_EXTERN NSData *RCTGzipData(NSData *data, float level);

View File

@ -10,12 +10,15 @@
#import "RCTUtils.h" #import "RCTUtils.h"
#import <mach/mach_time.h> #import <mach/mach_time.h>
#import <objc/runtime.h> #import <objc/message.h>
#import <UIKit/UIKit.h> #import <UIKit/UIKit.h>
#import <CommonCrypto/CommonCrypto.h> #import <CommonCrypto/CommonCrypto.h>
#import <zlib.h>
#import <dlfcn.h>
#import "RCTLog.h" #import "RCTLog.h"
NSString *RCTJSONStringify(id jsonObject, NSError **error) NSString *RCTJSONStringify(id jsonObject, NSError **error)
@ -305,3 +308,51 @@ NSURL *RCTDataURL(NSString *mimeType, NSData *data)
[data base64EncodedStringWithOptions:(NSDataBase64EncodingOptions)0]]]; [data base64EncodedStringWithOptions:(NSDataBase64EncodingOptions)0]]];
} }
static BOOL RCTIsGzippedData(NSData *data)
{
UInt8 *bytes = (UInt8 *)data.bytes;
return (data.length >= 2 && bytes[0] == 0x1f && bytes[1] == 0x8b);
}
NSData *RCTGzipData(NSData *input, float level)
{
if (input.length == 0 || RCTIsGzippedData(input)) {
return input;
}
void *libz = dlopen("libz.dylib", RTLD_NOW);
int (*deflateInit2_)(z_streamp, int, int, int, int, int, const char *, int) = dlsym(libz, "deflateInit2_");
int (*deflate)(z_streamp, int) = dlsym(libz, "deflate");
int (*deflateEnd)(z_streamp) = dlsym(libz, "deflateEnd");
z_stream stream;
stream.zalloc = Z_NULL;
stream.zfree = Z_NULL;
stream.opaque = Z_NULL;
stream.avail_in = (uint)input.length;
stream.next_in = (Bytef *)input.bytes;
stream.total_out = 0;
stream.avail_out = 0;
static const NSUInteger RCTGZipChunkSize = 16384;
NSMutableData *output = nil;
int compression = (level < 0.0f)? Z_DEFAULT_COMPRESSION: (int)(roundf(level * 9));
if (deflateInit2(&stream, compression, Z_DEFLATED, 31, 8, Z_DEFAULT_STRATEGY) == Z_OK) {
output = [NSMutableData dataWithLength:RCTGZipChunkSize];
while (stream.avail_out == 0) {
if (stream.total_out >= output.length) {
output.length += RCTGZipChunkSize;
}
stream.next_out = (uint8_t *)output.mutableBytes + stream.total_out;
stream.avail_out = (uInt)(output.length - stream.total_out);
deflate(&stream, Z_FINISH);
}
deflateEnd(&stream);
output.length = stream.total_out;
}
dlclose(libz);
return output;
}