├── LICENSE ├── README ├── compress.cc ├── filetest.js ├── test.js └── wscript /LICENSE: -------------------------------------------------------------------------------- 1 | // Copyright 2009, Acknack Ltd. All rights reserved. 2 | // Permission is hereby granted, free of charge, to any person obtaining a copy 3 | // of this software and associated documentation files (the "Software"), to 4 | // deal in the Software without restriction, including without limitation the 5 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 6 | // sell copies of the Software, and to permit persons to whom the Software is 7 | // furnished to do so, subject to the following conditions: 8 | // 9 | // The above copyright notice and this permission notice shall be included in 10 | // all copies or substantial portions of the Software. 11 | // 12 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 13 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 14 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 15 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 16 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 17 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 18 | // IN THE SOFTWARE. 19 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | node-compress 2 | ============= 3 | 4 | A streaming compression / gzip module for node.js 5 | 6 | To install, ensure that you have libz installed, and run: 7 | 8 | node-waf configure 9 | node-waf build 10 | 11 | This will put the compress.node binary module in build/default. 12 | 13 | 14 | Quick example 15 | ------------- 16 | 17 | var compress=require("./compress"); 18 | var sys=require("sys"); 19 | var posix=require("posix"); 20 | 21 | // Create gzip stream 22 | var gzip=new compress.Gzip; 23 | gzip.init(); 24 | 25 | // Pump data to be compressed 26 | var gzdata1 = gzip.deflate("My data that needs ", "binary"); 27 | sys.puts("Compressed size : "+gzdata1.length); 28 | 29 | var gzdata2 = gzip.deflate("to be compressed. 01234567890.", "binary"); 30 | sys.puts("Compressed size : "+gzdata2.length); 31 | 32 | var gzdata3 = gzip.end(); 33 | sys.puts("Last bit : "+gzdata3.length); 34 | 35 | // Normally stream this out as its generated, but just print here 36 | var gzdata = gzdata1+gzdata2+gzdata3; 37 | sys.puts("Total compressed size : "+gzdata.length); 38 | -------------------------------------------------------------------------------- /compress.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #define CHUNK 16384 9 | 10 | using namespace v8; 11 | using namespace node; 12 | 13 | 14 | class Gzip : public EventEmitter { 15 | public: 16 | static void 17 | Initialize (v8::Handle target) 18 | { 19 | HandleScope scope; 20 | 21 | Local t = FunctionTemplate::New(New); 22 | 23 | t->Inherit(EventEmitter::constructor_template); 24 | t->InstanceTemplate()->SetInternalFieldCount(1); 25 | 26 | NODE_SET_PROTOTYPE_METHOD(t, "init", GzipInit); 27 | NODE_SET_PROTOTYPE_METHOD(t, "deflate", GzipDeflate); 28 | NODE_SET_PROTOTYPE_METHOD(t, "end", GzipEnd); 29 | 30 | target->Set(String::NewSymbol("Gzip"), t->GetFunction()); 31 | } 32 | 33 | int GzipInit(int level) { 34 | int ret; 35 | /* allocate deflate state */ 36 | strm.zalloc = Z_NULL; 37 | strm.zfree = Z_NULL; 38 | strm.opaque = Z_NULL; 39 | ret = deflateInit2(&strm, level, Z_DEFLATED, 16+MAX_WBITS, 8, Z_DEFAULT_STRATEGY); 40 | return ret; 41 | } 42 | 43 | int GzipDeflate(char* data, int data_len, char** out, int* out_len) { 44 | int ret; 45 | char* temp; 46 | int i=1; 47 | 48 | *out = NULL; 49 | *out_len = 0; 50 | ret = 0; 51 | 52 | if (data_len == 0) 53 | return 0; 54 | 55 | while(data_len>0) { 56 | if (data_len>CHUNK) { 57 | strm.avail_in = CHUNK; 58 | } else { 59 | strm.avail_in = data_len; 60 | } 61 | 62 | strm.next_in = (Bytef*)data; 63 | do { 64 | temp = (char *)realloc(*out, CHUNK*i +1); 65 | if (temp == NULL) { 66 | return Z_MEM_ERROR; 67 | } 68 | *out = temp; 69 | strm.avail_out = CHUNK; 70 | strm.next_out = (Bytef*)*out + *out_len; 71 | ret = deflate(&strm, Z_NO_FLUSH); 72 | assert(ret != Z_STREAM_ERROR); /* state not clobbered */ 73 | *out_len += (CHUNK - strm.avail_out); 74 | i++; 75 | } while (strm.avail_out == 0); 76 | 77 | data += CHUNK; 78 | data_len -= CHUNK; 79 | } 80 | return ret; 81 | } 82 | 83 | 84 | int GzipEnd(char** out, int*out_len) { 85 | int ret; 86 | char* temp; 87 | int i = 1; 88 | 89 | *out = NULL; 90 | *out_len = 0; 91 | strm.avail_in = 0; 92 | strm.next_in = NULL; 93 | 94 | do { 95 | temp = (char *)realloc(*out, CHUNK*i); 96 | if (temp == NULL) { 97 | return Z_MEM_ERROR; 98 | } 99 | *out = temp; 100 | strm.avail_out = CHUNK; 101 | strm.next_out = (Bytef*)*out + *out_len; 102 | ret = deflate(&strm, Z_FINISH); 103 | assert(ret != Z_STREAM_ERROR); /* state not clobbered */ 104 | *out_len += (CHUNK - strm.avail_out); 105 | i++; 106 | } while (strm.avail_out == 0); 107 | 108 | 109 | deflateEnd(&strm); 110 | return ret; 111 | } 112 | 113 | 114 | protected: 115 | 116 | static Handle 117 | New (const Arguments& args) 118 | { 119 | HandleScope scope; 120 | 121 | Gzip *gzip = new Gzip(); 122 | gzip->Wrap(args.This()); 123 | 124 | return args.This(); 125 | } 126 | 127 | static Handle 128 | GzipInit (const Arguments& args) 129 | { 130 | Gzip *gzip = ObjectWrap::Unwrap(args.This()); 131 | 132 | HandleScope scope; 133 | 134 | int level=Z_DEFAULT_COMPRESSION; 135 | 136 | int r = gzip->GzipInit(level); 137 | 138 | return scope.Close(Integer::New(r)); 139 | } 140 | 141 | static Handle 142 | GzipDeflate(const Arguments& args) { 143 | Gzip *gzip = ObjectWrap::Unwrap(args.This()); 144 | 145 | HandleScope scope; 146 | 147 | enum encoding enc = ParseEncoding(args[1]); 148 | ssize_t len = DecodeBytes(args[0], enc); 149 | 150 | if (len < 0) { 151 | Local exception = Exception::TypeError(String::New("Bad argument")); 152 | return ThrowException(exception); 153 | } 154 | char* buf = new char[len]; 155 | ssize_t written = DecodeWrite(buf, len, args[0], enc); 156 | assert(written == len); 157 | 158 | char* out; 159 | int out_size; 160 | int r = gzip->GzipDeflate(buf, len, &out, &out_size); 161 | 162 | if (out_size==0) { 163 | return scope.Close(String::New("")); 164 | } 165 | 166 | Local outString = Encode(out, out_size, BINARY); 167 | free(out); 168 | return scope.Close(outString); 169 | } 170 | 171 | static Handle 172 | GzipEnd(const Arguments& args) { 173 | Gzip *gzip = ObjectWrap::Unwrap(args.This()); 174 | 175 | HandleScope scope; 176 | 177 | char* out; 178 | int out_size; 179 | bool hex_format = false; 180 | 181 | if (args.Length() > 0 && args[0]->IsString()) { 182 | String::Utf8Value format_type(args[1]->ToString()); 183 | } 184 | 185 | 186 | int r = gzip->GzipEnd( &out, &out_size); 187 | 188 | if (out_size==0) { 189 | return String::New(""); 190 | } 191 | Local outString = Encode(out, out_size, BINARY); 192 | free(out); 193 | return scope.Close(outString); 194 | 195 | } 196 | 197 | 198 | Gzip () : EventEmitter () 199 | { 200 | } 201 | 202 | ~Gzip () 203 | { 204 | } 205 | 206 | private: 207 | 208 | z_stream strm; 209 | }; 210 | 211 | 212 | class Gunzip : public EventEmitter { 213 | public: 214 | static void 215 | Initialize (v8::Handle target) 216 | { 217 | HandleScope scope; 218 | 219 | Local t = FunctionTemplate::New(New); 220 | 221 | t->Inherit(EventEmitter::constructor_template); 222 | t->InstanceTemplate()->SetInternalFieldCount(1); 223 | 224 | NODE_SET_PROTOTYPE_METHOD(t, "init", GunzipInit); 225 | NODE_SET_PROTOTYPE_METHOD(t, "inflate", GunzipInflate); 226 | NODE_SET_PROTOTYPE_METHOD(t, "end", GunzipEnd); 227 | 228 | target->Set(String::NewSymbol("Gunzip"), t->GetFunction()); 229 | } 230 | 231 | int GunzipInit() { 232 | /* allocate inflate state */ 233 | strm.zalloc = Z_NULL; 234 | strm.zfree = Z_NULL; 235 | strm.opaque = Z_NULL; 236 | strm.avail_in = 0; 237 | strm.next_in = Z_NULL; 238 | int ret = inflateInit2(&strm, 16+MAX_WBITS); 239 | return ret; 240 | } 241 | 242 | int GunzipInflate(const char* data, int data_len, char** out, int* out_len) { 243 | int ret; 244 | char* temp; 245 | int i=1; 246 | 247 | *out = NULL; 248 | *out_len = 0; 249 | 250 | if (data_len == 0) 251 | return 0; 252 | 253 | while(data_len>0) { 254 | if (data_len>CHUNK) { 255 | strm.avail_in = CHUNK; 256 | } else { 257 | strm.avail_in = data_len; 258 | } 259 | 260 | strm.next_in = (Bytef*)data; 261 | 262 | do { 263 | temp = (char *)realloc(*out, CHUNK*i); 264 | if (temp == NULL) { 265 | return Z_MEM_ERROR; 266 | } 267 | *out = temp; 268 | strm.avail_out = CHUNK; 269 | strm.next_out = (Bytef*)*out + *out_len; 270 | ret = inflate(&strm, Z_NO_FLUSH); 271 | assert(ret != Z_STREAM_ERROR); /* state not clobbered */ 272 | switch (ret) { 273 | case Z_NEED_DICT: 274 | ret = Z_DATA_ERROR; /* and fall through */ 275 | case Z_DATA_ERROR: 276 | case Z_MEM_ERROR: 277 | (void)inflateEnd(&strm); 278 | return ret; 279 | } 280 | *out_len += (CHUNK - strm.avail_out); 281 | i++; 282 | } while (strm.avail_out == 0); 283 | data += CHUNK; 284 | data_len -= CHUNK; 285 | } 286 | return ret; 287 | 288 | } 289 | 290 | 291 | void GunzipEnd() { 292 | inflateEnd(&strm); 293 | } 294 | 295 | protected: 296 | 297 | static Handle 298 | New(const Arguments& args) { 299 | HandleScope scope; 300 | 301 | Gunzip *gunzip = new Gunzip(); 302 | gunzip->Wrap(args.This()); 303 | 304 | return args.This(); 305 | } 306 | 307 | static Handle 308 | GunzipInit(const Arguments& args) { 309 | Gunzip *gunzip = ObjectWrap::Unwrap(args.This()); 310 | 311 | HandleScope scope; 312 | 313 | int r = gunzip->GunzipInit(); 314 | 315 | return scope.Close(Integer::New(r)); 316 | } 317 | 318 | 319 | static Handle 320 | GunzipInflate(const Arguments& args) { 321 | Gunzip *gunzip = ObjectWrap::Unwrap(args.This()); 322 | 323 | HandleScope scope; 324 | 325 | enum encoding enc = ParseEncoding(args[1]); 326 | ssize_t len = DecodeBytes(args[0], enc); 327 | 328 | if (len < 0) { 329 | Local exception = Exception::TypeError(String::New("Bad argument")); 330 | return ThrowException(exception); 331 | } 332 | 333 | char* buf = new char[len]; 334 | ssize_t written = DecodeWrite(buf, len, args[0], BINARY); 335 | assert(written == len); 336 | 337 | char* out; 338 | int out_size; 339 | int r = gunzip->GunzipInflate(buf, len, &out, &out_size); 340 | 341 | Local outString = Encode(out, out_size, enc); 342 | free(out); 343 | return scope.Close(outString); 344 | } 345 | 346 | static Handle 347 | GunzipEnd(const Arguments& args) { 348 | Gunzip *gunzip = ObjectWrap::Unwrap(args.This()); 349 | 350 | HandleScope scope; 351 | 352 | gunzip->GunzipEnd(); 353 | 354 | return scope.Close(String::New("")); 355 | } 356 | 357 | Gunzip () : EventEmitter () 358 | { 359 | } 360 | 361 | ~Gunzip () 362 | { 363 | } 364 | 365 | private: 366 | 367 | z_stream strm; 368 | 369 | }; 370 | 371 | extern "C" void 372 | init (Handle target) 373 | { 374 | HandleScope scope; 375 | Gzip::Initialize(target); 376 | Gunzip::Initialize(target); 377 | } 378 | -------------------------------------------------------------------------------- /filetest.js: -------------------------------------------------------------------------------- 1 | var compress=require("./compress"); 2 | var sys=require("sys"); 3 | var posix=require("posix"); 4 | 5 | // Read in our test file 6 | var data=posix.cat("filetest.js", encoding="binary").wait(); 7 | sys.puts("Got : "+data.length); 8 | 9 | // Set output file 10 | var fd = posix.open("filetest.js.gz", process.O_WRONLY | process.O_TRUNC | process.O_CREAT, 0644).wait(); 11 | sys.puts("Openned file"); 12 | 13 | // Create gzip stream 14 | var gzip=new compress.Gzip; 15 | gzip.init(); 16 | 17 | // Pump data to be compressed 18 | gzdata=gzip.deflate(data, "binary"); // Do this as many times as required 19 | sys.puts("Compressed size : "+gzdata.length); 20 | posix.write(fd, gzdata, encoding="binary").wait(); 21 | 22 | // Get the last bit 23 | gzlast=gzip.end(); 24 | sys.puts("Last bit : "+gzlast.length); 25 | posix.write(fd, gzlast, encoding="binary").wait(); 26 | posix.close(fd).wait(); 27 | sys.puts("File closed"); 28 | 29 | // See if we can uncompress it ok 30 | var gunzip=new compress.Gunzip; 31 | gunzip.init(); 32 | var testdata = posix.cat("filetest.js.gz", encoding="binary").wait(); 33 | sys.puts("Test opened : "+testdata.length); 34 | sys.puts(gunzip.inflate(testdata, "binary").length); 35 | gunzip.end(); 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | var compress=require("./compress"); 2 | var sys=require("sys"); 3 | var posix=require("posix"); 4 | 5 | // Create gzip stream 6 | var gzip=new compress.Gzip; 7 | gzip.init(); 8 | 9 | // Pump data to be compressed 10 | var gzdata1 = gzip.deflate("My data that needs ", "binary"); 11 | sys.puts("Compressed size : "+gzdata1.length); 12 | 13 | var gzdata2 = gzip.deflate("to be compressed. 01234567890.", "binary"); 14 | sys.puts("Compressed size : "+gzdata2.length); 15 | 16 | var gzdata3=gzip.end(); 17 | sys.puts("Last bit : "+gzdata3.length); 18 | 19 | // Take the output stream, and chop it up into two 20 | var gzdata = gzdata1+gzdata2+gzdata3; 21 | sys.puts("Total compressed size : "+gzdata.length); 22 | var d1 = gzdata.substr(0, 25); 23 | var d2 = gzdata.substr(25); 24 | 25 | // Create gunzip stream to decode these 26 | var gunzip = new compress.Gunzip; 27 | gunzip.init(); 28 | var data1 = gunzip.inflate(d1, "binary"); 29 | var data2 = gunzip.inflate(d2, "binary"); 30 | var data3 = gunzip.end(); 31 | 32 | sys.puts(data1+data2+data3); 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /wscript: -------------------------------------------------------------------------------- 1 | import Options 2 | from os import unlink, symlink, popen 3 | from os.path import exists 4 | 5 | srcdir = "." 6 | blddir = "build" 7 | VERSION = "0.0.1" 8 | 9 | def set_options(opt): 10 | opt.tool_options("compiler_cxx") 11 | opt.tool_options("compiler_cc") 12 | 13 | def configure(conf): 14 | conf.check_tool("compiler_cxx") 15 | conf.check_tool("compiler_cc") 16 | conf.check_tool("node_addon") 17 | 18 | conf.check(lib='z', libpath=['/usr/lib', '/usr/local/lib'], uselib_store='ZLIB') 19 | 20 | def build(bld): 21 | obj = bld.new_task_gen("cxx", "shlib", "node_addon") 22 | obj.target = "compress" 23 | obj.source = "compress.cc" 24 | obj.uselib = "ZLIB" 25 | 26 | def shutdown(): 27 | # HACK to get compress.node out of build directory. 28 | # better way to do this? 29 | if Options.commands['clean']: 30 | if exists('compress.node'): unlink('compress.node') 31 | else: 32 | if exists('build/default/compress.node') and not exists('compress.node'): 33 | symlink('build/default/compress.node', 'compress.node') 34 | --------------------------------------------------------------------------------