You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

157 lines
3.4 KiB

  1. /*!
  2. * Connect - compress
  3. * Copyright(c) 2010 Sencha Inc.
  4. * Copyright(c) 2011 TJ Holowaychuk
  5. * MIT Licensed
  6. */
  7. /**
  8. * Module dependencies.
  9. */
  10. var zlib = require('zlib');
  11. /**
  12. * Supported content-encoding methods.
  13. */
  14. exports.methods = {
  15. gzip: zlib.createGzip
  16. , deflate: zlib.createDeflate
  17. };
  18. /**
  19. * Default filter function.
  20. */
  21. exports.filter = function(req, res){
  22. return /json|text|javascript/.test(res.getHeader('Content-Type'));
  23. };
  24. /**
  25. * Compress:
  26. *
  27. * Compress response data with gzip/deflate.
  28. *
  29. * Filter:
  30. *
  31. * A `filter` callback function may be passed to
  32. * replace the default logic of:
  33. *
  34. * exports.filter = function(req, res){
  35. * return /json|text|javascript/.test(res.getHeader('Content-Type'));
  36. * };
  37. *
  38. * Options:
  39. *
  40. * All remaining options are passed to the gzip/deflate
  41. * creation functions. Consult node's docs for additional details.
  42. *
  43. * - `chunkSize` (default: 16*1024)
  44. * - `windowBits`
  45. * - `level`: 0-9 where 0 is no compression, and 9 is slow but best compression
  46. * - `memLevel`: 1-9 low is slower but uses less memory, high is fast but uses more
  47. * - `strategy`: compression strategy
  48. *
  49. * @param {Object} options
  50. * @return {Function}
  51. * @api public
  52. */
  53. module.exports = function compress(options) {
  54. options = options || {};
  55. var names = Object.keys(exports.methods)
  56. , filter = options.filter || exports.filter;
  57. return function compress(req, res, next){
  58. var accept = req.headers['accept-encoding']
  59. , vary = res.getHeader('Vary')
  60. , write = res.write
  61. , end = res.end
  62. , stream
  63. , method;
  64. // vary
  65. if (!vary) {
  66. res.setHeader('Vary', 'Accept-Encoding');
  67. } else if (!~vary.indexOf('Accept-Encoding')) {
  68. res.setHeader('Vary', vary + ', Accept-Encoding');
  69. }
  70. // see #724
  71. req.on('close', function(){
  72. res.write = res.end = function(){};
  73. });
  74. // proxy
  75. res.write = function(chunk, encoding){
  76. if (!this.headerSent) this._implicitHeader();
  77. return stream
  78. ? stream.write(new Buffer(chunk, encoding))
  79. : write.call(res, chunk, encoding);
  80. };
  81. res.end = function(chunk, encoding){
  82. if (chunk) this.write(chunk, encoding);
  83. return stream
  84. ? stream.end()
  85. : end.call(res);
  86. };
  87. res.on('header', function(){
  88. var encoding = res.getHeader('Content-Encoding') || 'identity';
  89. // already encoded
  90. if ('identity' != encoding) return;
  91. // default request filter
  92. if (!filter(req, res)) return;
  93. // SHOULD use identity
  94. if (!accept) return;
  95. // head
  96. if ('HEAD' == req.method) return;
  97. // default to gzip
  98. if ('*' == accept.trim()) method = 'gzip';
  99. // compression method
  100. if (!method) {
  101. for (var i = 0, len = names.length; i < len; ++i) {
  102. if (~accept.indexOf(names[i])) {
  103. method = names[i];
  104. break;
  105. }
  106. }
  107. }
  108. // compression method
  109. if (!method) return;
  110. // compression stream
  111. stream = exports.methods[method](options);
  112. // header fields
  113. res.setHeader('Content-Encoding', method);
  114. res.removeHeader('Content-Length');
  115. // compression
  116. stream.on('data', function(chunk){
  117. write.call(res, chunk);
  118. });
  119. stream.on('end', function(){
  120. end.call(res);
  121. });
  122. stream.on('drain', function() {
  123. res.emit('drain');
  124. });
  125. });
  126. next();
  127. };
  128. };