Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also compare across forks.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also compare across forks.
base fork: indutny/node-spdy
...
head fork: indutny/node-spdy
Checking mergeability… Don’t worry, you can still create the pull request.
  • 18 commits
  • 17 files changed
  • 2 commit comments
  • 3 contributors
Commits on Apr 23, 2012
@eee-c eee-c Copy version 2 to version 3 as baseline b323223
@eee-c eee-c Advertise v3 support a86227f
Commits on Apr 27, 2012
@eee-c eee-c More or less working spdy/3 implementation 326ed30
@eee-c eee-c Push dictionaries down into the protocol defs
Since they vary between specs
38a77bb
Commits on May 04, 2012
@eee-c eee-c Rudimentary spdy/3 flow control
Support WINDOW_UPDATE (framing and parsing).

Maintain internal data window per-stream.
e51e42f
Commits on May 05, 2012
@eee-c eee-c Better name for the flow control data transfer window properties. df63a3e
Commits on May 07, 2012
@eee-c eee-c Support adjustable data transfer windows via SETTINGS frames a4c9add
Commits on May 08, 2012
@eee-c eee-c Side-by-side support of spdy/2 and spdy/3 6dbe5d9
Commits on May 09, 2012
@eee-c eee-c Fix number of entries in the simple maxStream SETTINGS frame b7f87eb
Commits on May 11, 2012
@eee-c eee-c Patch from Tatsuhiro Tsujikawa fixing flow control.
Transfer every last byte available before the receive window is exhausted (previously would not tranfer if the next chunk would go below the receive window).
2ed581c
@eee-c eee-c Add Tatsuhiro Tsujikawa to the list of authors.
Thanks for the awesome patch!
bd8c4fd
Commits on May 14, 2012
@jcayzac jcayzac Fixed bug in Connection.prototype.setDefaultTransferWindow 9dc7f50
Commits on May 16, 2012
@eee-c eee-c Merge pull request #46 from jcayzac/spdy-v3
Fixed bug in Connection.prototype.setDefaultTransferWindow
838374b
@eee-c eee-c Handle late WINDOW_UPDATE frames
(should also work with other control frames)
31ec620
Commits on May 22, 2012
@eee-c eee-c Need colon headers for spdy/3 push 7c20c03
Commits on Jun 29, 2012
@eee-c eee-c Allow reply headers to be set from upstream
This does not appear to break anything and should help with #51
d4fa632
Commits on Jul 19, 2012
@indutny test: fix tests 76dfea3
@indutny test: more coverage
* zlib-pool: improve APIs
eab837c
View
1  README.md
@@ -107,6 +107,7 @@ will be sent for each additional stream).
* [Fedor Indutny](https://github.com/indutny)
* [Chris Strom](https://github.com/eee-c)
* [François de Metz](https://github.com/francois2metz)
+* [Tatsuhiro Tsujikawa](https://github.com/tatsuhiro-t)
#### LICENSE
View
3  lib/spdy.js
@@ -12,8 +12,9 @@ try {
spdy.protocol.generic = require('./spdy/protocol/generic.js');
}
-// Only SPDY v2 is supported now
+// Supported SPDY versions
spdy.protocol[2] = require('./spdy/protocol/v2');
+spdy.protocol[3] = require('./spdy/protocol/v3');
spdy.parser = require('./spdy/parser');
View
44 lib/spdy/parser.js
@@ -130,6 +130,29 @@ Parser.prototype.end = function end() {
};
//
+// ### function createFramer (version)
+// #### @version {Number} Protocol version, either 2 or 3
+// Sets framer instance on Parser's instance
+//
+Parser.prototype.createFramer = function createFramer(version) {
+ if (spdy.protocol[version]) {
+ this.framer = new spdy.protocol[version].Framer(
+ spdy.utils.zwrap(this.deflate),
+ spdy.utils.zwrap(this.inflate)
+ );
+
+ // Propagate framer to connection
+ this.connection.framer = this.framer;
+ this.emit('framer', this.framer);
+ } else {
+ this.emit(
+ 'error',
+ new Error('Unknown protocol version requested: ' + version)
+ );
+ }
+};
+
+//
// ### function execute (state, data, callback)
// #### @state {Object} Parser's state
// #### @data {Buffer} Incoming data
@@ -142,16 +165,7 @@ Parser.prototype.execute = function execute(state, data, callback) {
// Lazily create framer
if (!this.framer && header.control) {
- if (spdy.protocol[header.version]) {
- this.framer = new spdy.protocol[header.version].Framer(
- spdy.utils.zwrap(this.deflate),
- spdy.utils.zwrap(this.inflate)
- );
-
- // Propagate framer to connection
- this.connection.framer = this.framer;
- this.emit('_framer', this.framer);
- }
+ this.createFramer(header.version);
}
state.type = 'frame-body';
@@ -161,6 +175,16 @@ Parser.prototype.execute = function execute(state, data, callback) {
// Data frame
if (!state.header.control) {
+ this.connection.socket.setNoDelay(true);
+
+ if (this.framer.version >= 3) {
+ this.connection.write(
+ this.framer.windowUpdateFrame(state.header.id, 512)
+ );
+ }
+
+ this.connection.socket.setNoDelay(false);
+
return onFrame(null, {
type: 'DATA',
id: state.header.id,
View
15 lib/spdy/protocol/v2/dictionary.js
@@ -0,0 +1,15 @@
+exports.dictionary = new Buffer([
+ 'optionsgetheadpostputdeletetraceacceptaccept-charsetaccept-encodingaccept-',
+ 'languageauthorizationexpectfromhostif-modified-sinceif-matchif-none-matchi',
+ 'f-rangeif-unmodifiedsincemax-forwardsproxy-authorizationrangerefererteuser',
+ '-agent10010120020120220320420520630030130230330430530630740040140240340440',
+ '5406407408409410411412413414415416417500501502503504505accept-rangesageeta',
+ 'glocationproxy-authenticatepublicretry-afterservervarywarningwww-authentic',
+ 'ateallowcontent-basecontent-encodingcache-controlconnectiondatetrailertran',
+ 'sfer-encodingupgradeviawarningcontent-languagecontent-lengthcontent-locati',
+ 'oncontent-md5content-rangecontent-typeetagexpireslast-modifiedset-cookieMo',
+ 'ndayTuesdayWednesdayThursdayFridaySaturdaySundayJanFebMarAprMayJunJulAugSe',
+ 'pOctNovDecchunkedtext/htmlimage/pngimage/jpgimage/gifapplication/xmlapplic',
+ 'ation/xhtmltext/plainpublicmax-agecharset=iso-8859-1utf-8gzipdeflateHTTP/1',
+ '.1statusversionurl\x00'
+].join(''));
View
2  lib/spdy/protocol/v2/index.js
@@ -8,3 +8,5 @@ try {
module.exports = v2;
v2.Framer = require('./framer').Framer;
+
+v2.dictionary = require('./dictionary').dictionary;
View
180 lib/spdy/protocol/v3/dictionary.js
@@ -0,0 +1,180 @@
+exports.dictionary = new Buffer([
+ 0x00, 0x00, 0x00, 0x07, 0x6f, 0x70, 0x74, 0x69,
+ 0x6f, 0x6e, 0x73, 0x00, 0x00, 0x00, 0x04, 0x68,
+ 0x65, 0x61, 0x64, 0x00, 0x00, 0x00, 0x04, 0x70,
+ 0x6f, 0x73, 0x74, 0x00, 0x00, 0x00, 0x03, 0x70,
+ 0x75, 0x74, 0x00, 0x00, 0x00, 0x06, 0x64, 0x65,
+ 0x6c, 0x65, 0x74, 0x65, 0x00, 0x00, 0x00, 0x05,
+ 0x74, 0x72, 0x61, 0x63, 0x65, 0x00, 0x00, 0x00,
+ 0x06, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x00,
+ 0x00, 0x00, 0x0e, 0x61, 0x63, 0x63, 0x65, 0x70,
+ 0x74, 0x2d, 0x63, 0x68, 0x61, 0x72, 0x73, 0x65,
+ 0x74, 0x00, 0x00, 0x00, 0x0f, 0x61, 0x63, 0x63,
+ 0x65, 0x70, 0x74, 0x2d, 0x65, 0x6e, 0x63, 0x6f,
+ 0x64, 0x69, 0x6e, 0x67, 0x00, 0x00, 0x00, 0x0f,
+ 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x2d, 0x6c,
+ 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x00,
+ 0x00, 0x00, 0x0d, 0x61, 0x63, 0x63, 0x65, 0x70,
+ 0x74, 0x2d, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x73,
+ 0x00, 0x00, 0x00, 0x03, 0x61, 0x67, 0x65, 0x00,
+ 0x00, 0x00, 0x05, 0x61, 0x6c, 0x6c, 0x6f, 0x77,
+ 0x00, 0x00, 0x00, 0x0d, 0x61, 0x75, 0x74, 0x68,
+ 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f,
+ 0x6e, 0x00, 0x00, 0x00, 0x0d, 0x63, 0x61, 0x63,
+ 0x68, 0x65, 0x2d, 0x63, 0x6f, 0x6e, 0x74, 0x72,
+ 0x6f, 0x6c, 0x00, 0x00, 0x00, 0x0a, 0x63, 0x6f,
+ 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e,
+ 0x00, 0x00, 0x00, 0x0c, 0x63, 0x6f, 0x6e, 0x74,
+ 0x65, 0x6e, 0x74, 0x2d, 0x62, 0x61, 0x73, 0x65,
+ 0x00, 0x00, 0x00, 0x10, 0x63, 0x6f, 0x6e, 0x74,
+ 0x65, 0x6e, 0x74, 0x2d, 0x65, 0x6e, 0x63, 0x6f,
+ 0x64, 0x69, 0x6e, 0x67, 0x00, 0x00, 0x00, 0x10,
+ 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2d,
+ 0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65,
+ 0x00, 0x00, 0x00, 0x0e, 0x63, 0x6f, 0x6e, 0x74,
+ 0x65, 0x6e, 0x74, 0x2d, 0x6c, 0x65, 0x6e, 0x67,
+ 0x74, 0x68, 0x00, 0x00, 0x00, 0x10, 0x63, 0x6f,
+ 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2d, 0x6c, 0x6f,
+ 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x00, 0x00,
+ 0x00, 0x0b, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e,
+ 0x74, 0x2d, 0x6d, 0x64, 0x35, 0x00, 0x00, 0x00,
+ 0x0d, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74,
+ 0x2d, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x00, 0x00,
+ 0x00, 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e,
+ 0x74, 0x2d, 0x74, 0x79, 0x70, 0x65, 0x00, 0x00,
+ 0x00, 0x04, 0x64, 0x61, 0x74, 0x65, 0x00, 0x00,
+ 0x00, 0x04, 0x65, 0x74, 0x61, 0x67, 0x00, 0x00,
+ 0x00, 0x06, 0x65, 0x78, 0x70, 0x65, 0x63, 0x74,
+ 0x00, 0x00, 0x00, 0x07, 0x65, 0x78, 0x70, 0x69,
+ 0x72, 0x65, 0x73, 0x00, 0x00, 0x00, 0x04, 0x66,
+ 0x72, 0x6f, 0x6d, 0x00, 0x00, 0x00, 0x04, 0x68,
+ 0x6f, 0x73, 0x74, 0x00, 0x00, 0x00, 0x08, 0x69,
+ 0x66, 0x2d, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x00,
+ 0x00, 0x00, 0x11, 0x69, 0x66, 0x2d, 0x6d, 0x6f,
+ 0x64, 0x69, 0x66, 0x69, 0x65, 0x64, 0x2d, 0x73,
+ 0x69, 0x6e, 0x63, 0x65, 0x00, 0x00, 0x00, 0x0d,
+ 0x69, 0x66, 0x2d, 0x6e, 0x6f, 0x6e, 0x65, 0x2d,
+ 0x6d, 0x61, 0x74, 0x63, 0x68, 0x00, 0x00, 0x00,
+ 0x08, 0x69, 0x66, 0x2d, 0x72, 0x61, 0x6e, 0x67,
+ 0x65, 0x00, 0x00, 0x00, 0x13, 0x69, 0x66, 0x2d,
+ 0x75, 0x6e, 0x6d, 0x6f, 0x64, 0x69, 0x66, 0x69,
+ 0x65, 0x64, 0x2d, 0x73, 0x69, 0x6e, 0x63, 0x65,
+ 0x00, 0x00, 0x00, 0x0d, 0x6c, 0x61, 0x73, 0x74,
+ 0x2d, 0x6d, 0x6f, 0x64, 0x69, 0x66, 0x69, 0x65,
+ 0x64, 0x00, 0x00, 0x00, 0x08, 0x6c, 0x6f, 0x63,
+ 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x00, 0x00, 0x00,
+ 0x0c, 0x6d, 0x61, 0x78, 0x2d, 0x66, 0x6f, 0x72,
+ 0x77, 0x61, 0x72, 0x64, 0x73, 0x00, 0x00, 0x00,
+ 0x06, 0x70, 0x72, 0x61, 0x67, 0x6d, 0x61, 0x00,
+ 0x00, 0x00, 0x12, 0x70, 0x72, 0x6f, 0x78, 0x79,
+ 0x2d, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74,
+ 0x69, 0x63, 0x61, 0x74, 0x65, 0x00, 0x00, 0x00,
+ 0x13, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2d, 0x61,
+ 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61,
+ 0x74, 0x69, 0x6f, 0x6e, 0x00, 0x00, 0x00, 0x05,
+ 0x72, 0x61, 0x6e, 0x67, 0x65, 0x00, 0x00, 0x00,
+ 0x07, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x72,
+ 0x00, 0x00, 0x00, 0x0b, 0x72, 0x65, 0x74, 0x72,
+ 0x79, 0x2d, 0x61, 0x66, 0x74, 0x65, 0x72, 0x00,
+ 0x00, 0x00, 0x06, 0x73, 0x65, 0x72, 0x76, 0x65,
+ 0x72, 0x00, 0x00, 0x00, 0x02, 0x74, 0x65, 0x00,
+ 0x00, 0x00, 0x07, 0x74, 0x72, 0x61, 0x69, 0x6c,
+ 0x65, 0x72, 0x00, 0x00, 0x00, 0x11, 0x74, 0x72,
+ 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x2d, 0x65,
+ 0x6e, 0x63, 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x00,
+ 0x00, 0x00, 0x07, 0x75, 0x70, 0x67, 0x72, 0x61,
+ 0x64, 0x65, 0x00, 0x00, 0x00, 0x0a, 0x75, 0x73,
+ 0x65, 0x72, 0x2d, 0x61, 0x67, 0x65, 0x6e, 0x74,
+ 0x00, 0x00, 0x00, 0x04, 0x76, 0x61, 0x72, 0x79,
+ 0x00, 0x00, 0x00, 0x03, 0x76, 0x69, 0x61, 0x00,
+ 0x00, 0x00, 0x07, 0x77, 0x61, 0x72, 0x6e, 0x69,
+ 0x6e, 0x67, 0x00, 0x00, 0x00, 0x10, 0x77, 0x77,
+ 0x77, 0x2d, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e,
+ 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x00, 0x00,
+ 0x00, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64,
+ 0x00, 0x00, 0x00, 0x03, 0x67, 0x65, 0x74, 0x00,
+ 0x00, 0x00, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75,
+ 0x73, 0x00, 0x00, 0x00, 0x06, 0x32, 0x30, 0x30,
+ 0x20, 0x4f, 0x4b, 0x00, 0x00, 0x00, 0x07, 0x76,
+ 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x00, 0x00,
+ 0x00, 0x08, 0x48, 0x54, 0x54, 0x50, 0x2f, 0x31,
+ 0x2e, 0x31, 0x00, 0x00, 0x00, 0x03, 0x75, 0x72,
+ 0x6c, 0x00, 0x00, 0x00, 0x06, 0x70, 0x75, 0x62,
+ 0x6c, 0x69, 0x63, 0x00, 0x00, 0x00, 0x0a, 0x73,
+ 0x65, 0x74, 0x2d, 0x63, 0x6f, 0x6f, 0x6b, 0x69,
+ 0x65, 0x00, 0x00, 0x00, 0x0a, 0x6b, 0x65, 0x65,
+ 0x70, 0x2d, 0x61, 0x6c, 0x69, 0x76, 0x65, 0x00,
+ 0x00, 0x00, 0x06, 0x6f, 0x72, 0x69, 0x67, 0x69,
+ 0x6e, 0x31, 0x30, 0x30, 0x31, 0x30, 0x31, 0x32,
+ 0x30, 0x31, 0x32, 0x30, 0x32, 0x32, 0x30, 0x35,
+ 0x32, 0x30, 0x36, 0x33, 0x30, 0x30, 0x33, 0x30,
+ 0x32, 0x33, 0x30, 0x33, 0x33, 0x30, 0x34, 0x33,
+ 0x30, 0x35, 0x33, 0x30, 0x36, 0x33, 0x30, 0x37,
+ 0x34, 0x30, 0x32, 0x34, 0x30, 0x35, 0x34, 0x30,
+ 0x36, 0x34, 0x30, 0x37, 0x34, 0x30, 0x38, 0x34,
+ 0x30, 0x39, 0x34, 0x31, 0x30, 0x34, 0x31, 0x31,
+ 0x34, 0x31, 0x32, 0x34, 0x31, 0x33, 0x34, 0x31,
+ 0x34, 0x34, 0x31, 0x35, 0x34, 0x31, 0x36, 0x34,
+ 0x31, 0x37, 0x35, 0x30, 0x32, 0x35, 0x30, 0x34,
+ 0x35, 0x30, 0x35, 0x32, 0x30, 0x33, 0x20, 0x4e,
+ 0x6f, 0x6e, 0x2d, 0x41, 0x75, 0x74, 0x68, 0x6f,
+ 0x72, 0x69, 0x74, 0x61, 0x74, 0x69, 0x76, 0x65,
+ 0x20, 0x49, 0x6e, 0x66, 0x6f, 0x72, 0x6d, 0x61,
+ 0x74, 0x69, 0x6f, 0x6e, 0x32, 0x30, 0x34, 0x20,
+ 0x4e, 0x6f, 0x20, 0x43, 0x6f, 0x6e, 0x74, 0x65,
+ 0x6e, 0x74, 0x33, 0x30, 0x31, 0x20, 0x4d, 0x6f,
+ 0x76, 0x65, 0x64, 0x20, 0x50, 0x65, 0x72, 0x6d,
+ 0x61, 0x6e, 0x65, 0x6e, 0x74, 0x6c, 0x79, 0x34,
+ 0x30, 0x30, 0x20, 0x42, 0x61, 0x64, 0x20, 0x52,
+ 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x34, 0x30,
+ 0x31, 0x20, 0x55, 0x6e, 0x61, 0x75, 0x74, 0x68,
+ 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x34, 0x30,
+ 0x33, 0x20, 0x46, 0x6f, 0x72, 0x62, 0x69, 0x64,
+ 0x64, 0x65, 0x6e, 0x34, 0x30, 0x34, 0x20, 0x4e,
+ 0x6f, 0x74, 0x20, 0x46, 0x6f, 0x75, 0x6e, 0x64,
+ 0x35, 0x30, 0x30, 0x20, 0x49, 0x6e, 0x74, 0x65,
+ 0x72, 0x6e, 0x61, 0x6c, 0x20, 0x53, 0x65, 0x72,
+ 0x76, 0x65, 0x72, 0x20, 0x45, 0x72, 0x72, 0x6f,
+ 0x72, 0x35, 0x30, 0x31, 0x20, 0x4e, 0x6f, 0x74,
+ 0x20, 0x49, 0x6d, 0x70, 0x6c, 0x65, 0x6d, 0x65,
+ 0x6e, 0x74, 0x65, 0x64, 0x35, 0x30, 0x33, 0x20,
+ 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x20,
+ 0x55, 0x6e, 0x61, 0x76, 0x61, 0x69, 0x6c, 0x61,
+ 0x62, 0x6c, 0x65, 0x4a, 0x61, 0x6e, 0x20, 0x46,
+ 0x65, 0x62, 0x20, 0x4d, 0x61, 0x72, 0x20, 0x41,
+ 0x70, 0x72, 0x20, 0x4d, 0x61, 0x79, 0x20, 0x4a,
+ 0x75, 0x6e, 0x20, 0x4a, 0x75, 0x6c, 0x20, 0x41,
+ 0x75, 0x67, 0x20, 0x53, 0x65, 0x70, 0x74, 0x20,
+ 0x4f, 0x63, 0x74, 0x20, 0x4e, 0x6f, 0x76, 0x20,
+ 0x44, 0x65, 0x63, 0x20, 0x30, 0x30, 0x3a, 0x30,
+ 0x30, 0x3a, 0x30, 0x30, 0x20, 0x4d, 0x6f, 0x6e,
+ 0x2c, 0x20, 0x54, 0x75, 0x65, 0x2c, 0x20, 0x57,
+ 0x65, 0x64, 0x2c, 0x20, 0x54, 0x68, 0x75, 0x2c,
+ 0x20, 0x46, 0x72, 0x69, 0x2c, 0x20, 0x53, 0x61,
+ 0x74, 0x2c, 0x20, 0x53, 0x75, 0x6e, 0x2c, 0x20,
+ 0x47, 0x4d, 0x54, 0x63, 0x68, 0x75, 0x6e, 0x6b,
+ 0x65, 0x64, 0x2c, 0x74, 0x65, 0x78, 0x74, 0x2f,
+ 0x68, 0x74, 0x6d, 0x6c, 0x2c, 0x69, 0x6d, 0x61,
+ 0x67, 0x65, 0x2f, 0x70, 0x6e, 0x67, 0x2c, 0x69,
+ 0x6d, 0x61, 0x67, 0x65, 0x2f, 0x6a, 0x70, 0x67,
+ 0x2c, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x2f, 0x67,
+ 0x69, 0x66, 0x2c, 0x61, 0x70, 0x70, 0x6c, 0x69,
+ 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x78,
+ 0x6d, 0x6c, 0x2c, 0x61, 0x70, 0x70, 0x6c, 0x69,
+ 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x78,
+ 0x68, 0x74, 0x6d, 0x6c, 0x2b, 0x78, 0x6d, 0x6c,
+ 0x2c, 0x74, 0x65, 0x78, 0x74, 0x2f, 0x70, 0x6c,
+ 0x61, 0x69, 0x6e, 0x2c, 0x74, 0x65, 0x78, 0x74,
+ 0x2f, 0x6a, 0x61, 0x76, 0x61, 0x73, 0x63, 0x72,
+ 0x69, 0x70, 0x74, 0x2c, 0x70, 0x75, 0x62, 0x6c,
+ 0x69, 0x63, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74,
+ 0x65, 0x6d, 0x61, 0x78, 0x2d, 0x61, 0x67, 0x65,
+ 0x3d, 0x67, 0x7a, 0x69, 0x70, 0x2c, 0x64, 0x65,
+ 0x66, 0x6c, 0x61, 0x74, 0x65, 0x2c, 0x73, 0x64,
+ 0x63, 0x68, 0x63, 0x68, 0x61, 0x72, 0x73, 0x65,
+ 0x74, 0x3d, 0x75, 0x74, 0x66, 0x2d, 0x38, 0x63,
+ 0x68, 0x61, 0x72, 0x73, 0x65, 0x74, 0x3d, 0x69,
+ 0x73, 0x6f, 0x2d, 0x38, 0x38, 0x35, 0x39, 0x2d,
+ 0x31, 0x2c, 0x75, 0x74, 0x66, 0x2d, 0x2c, 0x2a,
+ 0x2c, 0x65, 0x6e, 0x71, 0x3d, 0x30, 0x2e
+]);
View
326 lib/spdy/protocol/v3/framer.js
@@ -0,0 +1,326 @@
+var framer = exports;
+
+var spdy = require('../../../spdy'),
+ Buffer = require('buffer').Buffer,
+ protocol = require('./');
+
+//
+// ### function Framer (deflate, inflate)
+// #### @deflate {zlib.Deflate} Deflate stream
+// #### @inflate {zlib.Inflate} Inflate stream
+// Framer constructor
+//
+function Framer(deflate, inflate) {
+ this.version = 3;
+ this.deflate = deflate;
+ this.inflate = inflate;
+}
+exports.Framer = Framer;
+
+//
+// ### function execute (header, body, callback)
+// #### @header {Object} Frame headers
+// #### @body {Buffer} Frame's body
+// #### @callback {Function} Continuation callback
+// Parse frame (decompress data and create streams)
+//
+Framer.prototype.execute = function execute(header, body, callback) {
+ // SYN_STREAM or SYN_REPLY
+ if (header.type === 0x01 || header.type === 0x02) {
+ var frame = protocol.parseSynHead(header.type, header.flags, body);
+
+ body = body.slice(frame._offset);
+
+ this.inflate(body, function(err, chunks, length) {
+ var pairs = new Buffer(length);
+ for (var i = 0, offset = 0; i < chunks.length; i++) {
+ chunks[i].copy(pairs, offset);
+ offset += chunks[i].length;
+ }
+
+ frame.headers = protocol.parseHeaders(pairs);
+
+ callback(null, frame);
+ });
+ // RST_STREAM
+ } else if (header.type === 0x03) {
+ callback(null, protocol.parseRst(body));
+ // SETTINGS
+ } else if (header.type === 0x04) {
+ callback(null, protocol.parseSettings(body));
+ } else if (header.type === 0x05) {
+ callback(null, { type: 'NOOP' });
+ // PING
+ } else if (header.type === 0x06) {
+ callback(null, { type: 'PING', pingId: body });
+ // GOAWAY
+ } else if (header.type === 0x07) {
+ callback(null, protocol.parseGoaway(body));
+ } else if (header.type === 0x09) {
+ callback(null, protocol.parseWindowUpdate(body));
+ } else {
+ callback(null, { type: 'unknown: ' + header.type, body: body });
+ }
+};
+
+//
+// internal, converts object into spdy dictionary
+//
+function headersToDict(headers, preprocess) {
+ function stringify(value) {
+ if (value !== undefined) {
+ if (Array.isArray(value)) {
+ return value.join('\x00');
+ } else if (typeof value === 'string') {
+ return value;
+ } else {
+ return value.toString();
+ }
+ } else {
+ return '';
+ }
+ }
+
+ // Lower case of all headers keys
+ var loweredHeaders = {};
+ Object.keys(headers || {}).map(function(key) {
+ loweredHeaders[key.toLowerCase()] = headers[key];
+ });
+
+ // Allow outer code to add custom headers or remove something
+ if (preprocess) preprocess(loweredHeaders);
+
+ // Transform object into kv pairs
+ var len = 4,
+ pairs = Object.keys(loweredHeaders).filter(function(key) {
+ var lkey = key.toLowerCase();
+ return lkey !== 'connection' && lkey !== 'keep-alive' &&
+ lkey !== 'proxy-connection' && lkey !== 'transfer-encoding';
+ }).map(function(key) {
+ var klen = Buffer.byteLength(key),
+ value = stringify(loweredHeaders[key]),
+ vlen = Buffer.byteLength(value);
+
+ len += 8 + klen + vlen;
+ return [klen, key, vlen, value];
+ }),
+ result = new Buffer(len);
+
+ result.writeUInt32BE(pairs.length, 0, true);
+
+ var offset = 4;
+ pairs.forEach(function(pair) {
+ // Write key length
+ result.writeUInt32BE(pair[0], offset, true);
+ // Write key
+ result.write(pair[1], offset + 4);
+
+ offset += pair[0] + 4;
+
+ // Write value length
+ result.writeUInt32BE(pair[2], offset, true);
+ // Write value
+ result.write(pair[3], offset + 4);
+
+ offset += pair[2] + 4;
+ });
+
+ return result;
+};
+
+Framer.prototype._synFrame = function _synFrame(type, id, assoc, dict,
+ callback) {
+ // Compress headers
+ this.deflate(dict, function (err, chunks, size) {
+ if (err) return callback(err);
+
+ var offset = type === 'SYN_STREAM' ? 18 : 12,
+ total = (type === 'SYN_STREAM' ? 10 : 4) + size,
+ frame = new Buffer(offset + size);;
+
+ frame.writeUInt16BE(0x8003, 0, true); // Control + Version
+ frame.writeUInt16BE(type === 'SYN_STREAM' ? 1 : 2, 2, true); // type
+ frame.writeUInt32BE(total & 0x00ffffff, 4, true); // No flag support
+ frame.writeUInt32BE(id & 0x7fffffff, 8, true); // Stream-ID
+
+ if (type === 'SYN_STREAM') {
+ frame[4] = 2;
+ frame.writeUInt32BE(assoc & 0x7fffffff, 12, true); // Stream-ID
+ }
+
+ for (var i = 0; i < chunks.length; i++) {
+ chunks[i].copy(frame, offset);
+ offset += chunks[i].length;
+ }
+
+ callback(null, frame);
+ });
+};
+
+//
+// ### function replyFrame (id, code, reason, headers, callback)
+// #### @id {Number} Stream ID
+// #### @code {Number} HTTP Status Code
+// #### @reason {String} (optional)
+// #### @headers {Object|Array} (optional) HTTP headers
+// #### @callback {Function} Continuation function
+// Sends SYN_REPLY frame
+//
+Framer.prototype.replyFrame = function replyFrame(id, code, reason, headers,
+ callback) {
+ var dict = headersToDict(headers, function(headers) {
+ headers[':status'] = code;
+ headers[':version'] = 'HTTP/1.1';
+ });
+
+ this._synFrame('SYN_REPLY', id, null, dict, callback);
+};
+
+//
+// ### function streamFrame (id, assoc, headers, callback)
+// #### @id {Number} stream id
+// #### @assoc {Number} associated stream id
+// #### @meta {Object} meta headers ( method, scheme, url, version )
+// #### @headers {Object} stream headers
+// #### @callback {Function} continuation callback
+// Create SYN_STREAM frame
+// (needed for server push and testing)
+//
+Framer.prototype.streamFrame = function streamFrame(id, assoc, meta, headers,
+ callback) {
+ var dict = headersToDict(headers, function(headers) {
+ headers.status = 200;
+ headers.version = 'HTTP/1.1';
+ headers.url = meta.url;
+ });
+
+ this._synFrame('SYN_STREAM', id, assoc, dict, callback);
+};
+
+//
+// ### function dataFrame (id, fin, data)
+// #### @id {Number} Stream id
+// #### @fin {Bool} Is this data frame last frame
+// #### @data {Buffer} Response data
+// Sends DATA frame
+//
+Framer.prototype.dataFrame = function dataFrame(id, fin, data) {
+ if (!fin && !data.length) return [];
+
+ var frame = new Buffer(8 + data.length);
+
+ frame.writeUInt32BE(id & 0x7fffffff, 0, true);
+ frame.writeUInt32BE(data.length & 0x00ffffff, 4, true);
+ frame.writeUInt8(fin ? 0x01 : 0x0, 4, true);
+
+ if (data.length) data.copy(frame, 8);
+
+ return frame;
+};
+
+//
+// ### function pingFrame (id)
+// #### @id {Buffer} Ping ID
+// Sends PING frame
+//
+Framer.prototype.pingFrame = function pingFrame(id) {
+ var header = new Buffer(12);
+
+ header.writeUInt32BE(0x80030006, 0, true); // Version and type
+ header.writeUInt32BE(0x00000004, 4, true); // Length
+ id.copy(header, 8, 0, 4); // ID
+
+ return header;
+};
+
+//
+// ### function rstFrame (id, code)
+// #### @id {Number} Stream ID
+// #### @code {NUmber} RST Code
+// Sends PING frame
+//
+Framer.prototype.rstFrame = function rstFrame(id, code) {
+ var header;
+
+ if (!(header = Framer.rstCache[code])) {
+ header = new Buffer(16);
+
+ header.writeUInt32BE(0x80030003, 0, true); // Version and type
+ header.writeUInt32BE(0x00000008, 4, true); // Length
+ header.writeUInt32BE(id & 0x7fffffff, 8, true); // Stream ID
+ header.writeUInt32BE(code, 12, true); // Status Code
+
+ Framer.rstCache[code] = header;
+ }
+
+ return header;
+};
+Framer.rstCache = {};
+
+//
+// ### function maxStreamsFrame (count)
+// #### @count {Number} Max Concurrent Streams count
+// Sends SETTINGS frame with MAX_CONCURRENT_STREAMS
+//
+Framer.prototype.maxStreamsFrame = function maxStreamsFrame(count) {
+ var settings;
+
+ if (!(settings = Framer.settingsCache[count])) {
+ settings = new Buffer(20);
+
+ settings.writeUInt32BE(0x80030004, 0, true); // Version and type
+ settings.writeUInt32BE((4 + 8) & 0x00FFFFFF, 4, true); // length
+ settings.writeUInt32BE(0x00000001, 8, true); // Count of entries
+
+ settings.writeUInt32BE(0x01000004, 12, true); // Entry ID and Persist flag
+ settings.writeUInt32BE(count, 16, true); // 100 Streams
+
+ Framer.settingsCache[count] = settings;
+ }
+
+ return settings;
+};
+Framer.settingsCache = {};
+
+//
+// ### function windowSizeFrame (size)
+// #### @size {Number} data transfer window size
+// Sends SETTINGS frame with window size
+//
+Framer.prototype.windowSizeFrame = function windowSizeFrame(size) {
+ var settings;
+
+ if (!(settings = Framer.settingsCache[size])) {
+ settings = new Buffer(28);
+
+ settings.writeUInt32BE(0x80030004, 0, true); // Version and type
+ settings.writeUInt32BE((4 + 8 + 8) & 0x00FFFFFF, 4, true); // length
+ settings.writeUInt32BE(0x00000002, 8, true); // Count of entries
+
+ settings.writeUInt32BE(0x01000004, 12, true); // Entry ID and Persist flag
+ settings.writeUInt32BE(100, 16, true); // 100 Streams
+
+ settings.writeUInt32BE(0x01000007, 20, true); // Entry ID and Persist flag
+ settings.writeUInt32BE(size & 0x7fffffff, 24, true); // Window Size (KB)
+
+ Framer.settingsCache[size] = settings;
+ }
+
+ return settings;
+};
+
+//
+// ### function windowUpdateFrame (id)
+// #### @id {Buffer} WindowUpdate ID
+// Sends WINDOW_UPDATE frame
+//
+Framer.prototype.windowUpdateFrame = function windowUpdateFrame(id, delta) {
+ var header = new Buffer(16);
+
+ header.writeUInt32BE(0x80030009, 0, true); // Version and type
+ header.writeUInt32BE(0x00000008, 4, true); // Length
+ header.writeUInt32BE(id & 0x7fffffff, 8, true); // ID
+ header.writeUInt32BE(delta & 0x7fffffff, 12, true); // delta
+
+ return header;
+};
View
12 lib/spdy/protocol/v3/index.js
@@ -0,0 +1,12 @@
+var v3;
+
+try {
+ v3 = require('./protocol.node');
+} catch (e) {
+ v3 = require('./protocol.js');
+}
+module.exports = v3;
+
+v3.Framer = require('./framer').Framer;
+
+v3.dictionary = require('./dictionary').dictionary;
View
111 lib/spdy/protocol/v3/protocol.js
@@ -0,0 +1,111 @@
+var protocol = exports;
+
+//
+// ### function parseSynHead (type, flags, data)
+// #### @type {Number} Frame type
+// #### @flags {Number} Frame flags
+// #### @data {Buffer} input data
+// Returns parsed syn_* frame's head
+//
+protocol.parseSynHead = function parseSynHead(type, flags, data) {
+ var stream = type === 0x01;
+
+ return {
+ type: stream ? 'SYN_STREAM' : 'SYN_REPLY',
+ id: data.readUInt32BE(0, true) & 0x7fffffff,
+ associated: stream ? data.readUInt32BE(4, true) & 0x7fffffff : 0,
+ priority: stream ? data[8] >> 6 : 0,
+ fin: (flags & 0x01) === 0x01,
+ unidir: (flags & 0x02) === 0x02,
+ _offset: stream ? 10 : 6
+ };
+};
+
+//
+// ### function parseHeaders (pairs)
+// #### @pairs {Buffer} header pairs
+// Returns hashmap of parsed headers
+//
+protocol.parseHeaders = function parseHeaders(pairs) {
+ var count = pairs.readUInt32BE(0, true),
+ headers = {};
+
+ pairs = pairs.slice(4);
+
+ function readString() {
+ var len = pairs.readUInt32BE(0, true),
+ value = pairs.slice(4, 4 + len);
+
+ pairs = pairs.slice(4 + len);
+
+ return value.toString();
+ }
+
+ while(count > 0) {
+ var k = readString().replace(/:/, ''),
+ v = readString();
+
+ headers[k] = v;
+
+ if (k == 'path') {
+ headers['url'] = v;
+ }
+
+ count--;
+ }
+
+ return headers;
+};
+
+//
+// ### function parsesRst frame
+protocol.parseRst = function parseRst(data) {
+ return {
+ type: 'RST_STREAM',
+ id: data.readUInt32BE(0, true) & 0x7fffffff,
+ status: data.readUInt32BE(4, true)
+ };
+};
+
+protocol.parseSettings = function parseSettings(data) {
+ var settings = {},
+ number = data.readUInt32BE(0, true),
+ idMap = {
+ 1: 'upload_bandwidth',
+ 2: 'download_bandwidth',
+ 3: 'round_trip_time',
+ 4: 'max_concurrent_streams',
+ 5: 'current_cwnd',
+ 6: 'download_retrans_rate',
+ 7: 'initial_window_size',
+ 8: 'client_certificate_vector_size'
+ };
+
+ for (var i=0; i<number; i++) {
+ var id = data.readUInt32BE(4 + (i*8), true) & 0x00ffffff,
+ name = idMap[id];
+ settings[id] = settings[name] = data.readUInt32BE(8 + (i*8), true);
+ }
+
+ return {
+ type: 'SETTINGS',
+ settings: settings
+ };
+};
+
+protocol.parseGoaway = function parseGoaway(data) {
+ return {
+ type: 'GOAWAY',
+ lastId: data.readUInt32BE(0, true) & 0x7fffffff
+ };
+};
+
+protocol.parseWindowUpdate = function parseWindowUpdate(data) {
+ var ret = {
+ type: 'WINDOW_UPDATE',
+ id: data.readUInt32BE(0, true) & 0x7fffffff,
+ delta: data.readUInt32BE(4, true) & 0x7fffffff
+ };
+
+ return ret;
+};
View
6 lib/spdy/response.js
@@ -108,6 +108,12 @@ exports.push = function push(url, headers, callback) {
:
url;
+ if (!headers[':scheme']) headers[':scheme'] = 'https';
+ if (!headers[':host']) headers[':host'] = this.frame.headers.host;
+ if (!headers[':status']) headers[':status'] = 200;
+ if (!headers[':version']) headers[':version'] = 'HTTP/1.1';
+ if (!headers[':path']) headers[':path'] = url;
+
this.framer.streamFrame(
id,
this.id,
View
107 lib/spdy/server.js
@@ -19,7 +19,8 @@ function instantiate(HTTPSServer) {
function Server(options, requestListener) {
if (!options) options = {};
if (!options.maxStreams) options.maxStreams = 100;
- options.NPNProtocols = ['spdy/2', 'http/1.1', 'http/1.0'];
+ if (!options.transferWindowSize) options.transferWindowSize = Math.pow(2,16);
+ options.NPNProtocols = ['spdy/3', 'spdy/2', 'http/1.1', 'http/1.0'];
HTTPSServer.call(this, options, requestListener);
@@ -35,7 +36,7 @@ function instantiate(HTTPSServer) {
this.removeAllListeners('secureConnection');
this.on('secureConnection', function secureConnection(socket) {
// Fallback to HTTPS if needed
- if (socket.npnProtocol !== 'spdy/2' && !options.debug) {
+ if ((!socket.npnProtocol || !socket.npnProtocol.match(/spdy/)) && !options.debug) {
return connectionHandler.call(this, socket);
}
@@ -62,6 +63,7 @@ function instantiate(HTTPSServer) {
});
connection.on('error', function(e) {
+ console.log('[secureConnection] error ' + e);
socket.destroy(e.errno === 'EPIPE' ? undefined : e);
});
});
@@ -113,7 +115,7 @@ function Connection(socket, pool, options) {
this._closed = false;
this.pool = pool;
- var pair = pool.get();
+ var pair = pool.get(socket.npnProtocol);
this.deflate = pair.deflate;
this.inflate = pair.inflate;
@@ -126,6 +128,9 @@ function Connection(socket, pool, options) {
this.framer = null;
+ // Data transfer window defaults to 64kb
+ this.transferWindowSize = options.transferWindowSize;
+
// Initialize parser
this.parser = spdy.parser.create(this, this.deflate, this.inflate);
this.parser.on('frame', function (frame) {
@@ -158,7 +163,9 @@ function Connection(socket, pool, options) {
// Fail if not found
if (stream === undefined) {
if (frame.type === 'RST_STREAM') return;
- return self.emit('error', 'Stream ' + frame.id + ' not found');
+ console.log("frame not found: ", frame);
+ self.write(self.framer.rstFrame(frame.id, 2));
+ return;
}
}
@@ -189,11 +196,13 @@ function Connection(socket, pool, options) {
// Respond with same PING
} else if (frame.type === 'PING') {
self.write(self.framer.pingFrame(frame.pingId));
- // Ignore SETTINGS for now
- } else if (frame.type === 'SETTINGS' || frame.type === 'NOOP') {
- // TODO: Handle something?
+ } else if (frame.type === 'SETTINGS') {
+ self.setDefaultTransferWindow(frame.settings);
} else if (frame.type === 'GOAWAY') {
self.goaway = frame.lastId;
+ } else if (frame.type === 'WINDOW_UPDATE') {
+ // console.log("window update ("+ frame.id +"): " + frame.delta);
+ stream.drainWindow(frame.delta);
} else {
console.error('Unknown type: ', frame.type);
}
@@ -212,13 +221,19 @@ function Connection(socket, pool, options) {
}
});
- this.parser.on('_framer', function(framer) {
+ this.parser.on('framer', function(framer) {
// Generate custom settings frame and send
self.write(
framer.maxStreamsFrame(options.maxStreams)
+ // framer.windowSizeFrame(options.transferWindowSize)
);
});
+ // Propagate parser errors
+ this.parser.on('error', function(err) {
+ self.emit('error', err);
+ });
+
// Initialize scheduler
this.scheduler = spdy.scheduler.create(this);
@@ -240,7 +255,7 @@ function Connection(socket, pool, options) {
socket.on('close', function() {
self._closed = true;
- pool.put(pair);
+ pool.put(socket.npnProtocol, pair);
});
socket.on('drain', function () {
@@ -258,11 +273,30 @@ exports.Connection = Connection;
//
Connection.prototype.write = function write(data, encoding) {
if (this.socket.writable) {
+ // this.socket.setNoDelay();
return this.socket.write(data, encoding);
}
};
//
+// ### function setDefaultTransferWindow (settings)
+// #### @settings {Object}
+// Update the default transfer window -- in the connection and in the
+// active streams
+//
+Connection.prototype.setDefaultTransferWindow = function(settings) {
+ if (!settings) return;
+ if (!settings.initial_window_size) return;
+
+ //console.log("settings.initial_window_size: " + settings.initial_window_size);
+ this.transferWindowSize = settings.initial_window_size;
+
+ for (var streamID in this.streams) {
+ this.streams[streamID].updateTransferWindowSize(settings.initial_window_size);
+ }
+};
+
+//
// ### function Stream (connection, frame)
// #### @connection {Connection} SPDY Connection
// #### @frame {Object} SYN_STREAM data
@@ -302,6 +336,11 @@ function Stream(connection, frame) {
this._paused = false;
this._buffer = [];
+ this._transferWindowSize = connection.transferWindowSize;
+ this._initialTransferWindowSize = connection.transferWindowSize;
+
+ this._transferWindowBuffer = [];
+
// Create compression streams
this.deflate = connection.deflate;
this.inflate = connection.inflate;
@@ -412,7 +451,9 @@ Stream.prototype.destroy = function destroy(error) {
if (error) {
if (this.rstCode) {
+ this.connection.socket.setNoDelay(true);
this.connection.write(this.framer.rstFrame(this.id, this.rstCode));
+ this.connection.socket.setNoDelay(false);
}
}
@@ -423,6 +464,17 @@ Stream.prototype.destroy = function destroy(error) {
});
};
+Stream.prototype.drainWindow = function(size) {
+ var new_buffer = this._transferWindowBuffer;
+ this._transferWindowBuffer = [];
+
+ this._transferWindowSize += size;
+
+ for (var i=0; i<new_buffer.length; i++) {
+ this._writeData(new_buffer[i][0], new_buffer[i][1]);
+ }
+}
+
//
// ### function _writeData (fin, buffer)
// #### @fin {Boolean}
@@ -430,9 +482,28 @@ Stream.prototype.destroy = function destroy(error) {
// Internal function
//
Stream.prototype._writeData = function _writeData(fin, buffer) {
+ var len;
+ if (this.framer.version == 3) {
+ if (this._transferWindowSize <= 0) {
+ // console.log('this._transferWindowSize ' + this._transferWindowSize);
+ // console.log('receive window exhausted, buffering ' + buffer.length + ' (' + fin + ')');
+ this._transferWindowBuffer.push([fin, buffer]);
+ return;
+ }
+ len = Math.min(this._transferWindowSize, buffer.length);
+ if (len < buffer.length) {
+ this._transferWindowBuffer.push([fin, buffer.slice(len)]);
+ fin = false;
+ }
+ this._transferWindowSize -= len;
+ }
+
+ // if (buffer.length == 0) {
+ // console.log('sending ' + this.id + ': ' + buffer.length + ' (' + fin + ')');
+ // }
this.lock(function() {
var stream = this,
- frame = this.framer.dataFrame(this.id, fin, buffer);
+ frame = this.framer.dataFrame(this.id, fin, buffer.slice(0, len));
stream.connection.scheduler.schedule(stream, frame);
stream.connection.scheduler.tick();
@@ -496,7 +567,8 @@ Stream.prototype.end = function end(data, encoding) {
this._writeData(true, []);
this.closedBy.server = true;
- this.handleClose();
+ if (this._transferWindowBuffer.length == 0)
+ this.handleClose();
};
//
@@ -545,3 +617,16 @@ Stream.prototype._read = function _read(data) {
self.emit('data', data);
});
};
+
+
+//
+// ### function updateTransferWindowSize (size)
+// #### @size {Integer}
+// Update the internal data transfer window
+//
+Stream.prototype.updateTransferWindowSize = function updateTransferWindowSize(size) {
+ var diff = size - this._initialTransferWindowSize;
+
+ this._transferWindowSize += diff;
+ this._initialTransferWindowSize = size;
+};
View
35 lib/spdy/utils.js
@@ -1,31 +1,18 @@
-var utils = exports;
+var spdy = require('../spdy'),
+ utils = exports;
var zlib = require('zlib'),
Buffer = require('buffer').Buffer;
-// SPDY deflate/inflate dictionary
-var dictionary = new Buffer([
- 'optionsgetheadpostputdeletetraceacceptaccept-charsetaccept-encodingaccept-',
- 'languageauthorizationexpectfromhostif-modified-sinceif-matchif-none-matchi',
- 'f-rangeif-unmodifiedsincemax-forwardsproxy-authorizationrangerefererteuser',
- '-agent10010120020120220320420520630030130230330430530630740040140240340440',
- '5406407408409410411412413414415416417500501502503504505accept-rangesageeta',
- 'glocationproxy-authenticatepublicretry-afterservervarywarningwww-authentic',
- 'ateallowcontent-basecontent-encodingcache-controlconnectiondatetrailertran',
- 'sfer-encodingupgradeviawarningcontent-languagecontent-lengthcontent-locati',
- 'oncontent-md5content-rangecontent-typeetagexpireslast-modifiedset-cookieMo',
- 'ndayTuesdayWednesdayThursdayFridaySaturdaySundayJanFebMarAprMayJunJulAugSe',
- 'pOctNovDecchunkedtext/htmlimage/pngimage/jpgimage/gifapplication/xmlapplic',
- 'ation/xhtmltext/plainpublicmax-agecharset=iso-8859-1utf-8gzipdeflateHTTP/1',
- '.1statusversionurl\x00'
-].join(''));
-
//
// ### function createDeflate ()
// Creates deflate stream with SPDY dictionary
//
-utils.createDeflate = function createDeflate() {
- var deflate = zlib.createDeflate({ dictionary: dictionary, windowBits: 11 });
+utils.createDeflate = function createDeflate(version) {
+ var deflate = zlib.createDeflate({
+ dictionary: spdy.protocol[version].dictionary,
+ windowBits: 11
+ });
// Define lock information early
deflate.locked = false;
@@ -38,8 +25,11 @@ utils.createDeflate = function createDeflate() {
// ### function createInflate ()
// Creates inflate stream with SPDY dictionary
//
-utils.createInflate = function createInflate() {
- var inflate = zlib.createInflate({ dictionary: dictionary, windowBits: 15 });
+utils.createInflate = function createInflate(version) {
+ var inflate = zlib.createInflate({
+ dictionary: spdy.protocol[version].dictionary,
+ windowBits: 15
+ });
// Define lock information early
inflate.locked = false;
@@ -117,4 +107,3 @@ utils.zwrap = function zwrap(stream) {
utils.zstream(stream, data, callback);
};
};
-
View
21 lib/spdy/zlib-pool.js
@@ -6,7 +6,10 @@ var zlibpool = exports,
// Zlib streams pool
//
function Pool() {
- this.pool = [];
+ this.pool = {
+ 'spdy/2': [],
+ 'spdy/3': []
+ };
}
//
@@ -22,13 +25,15 @@ var x = 0;
// ### function get ()
// Returns pair from pool or a new one
//
-Pool.prototype.get = function get(callback) {
- if (this.pool.length > 0) {
- return this.pool.pop();
+Pool.prototype.get = function get(version, callback) {
+ if (this.pool[version].length > 0) {
+ return this.pool[version].pop();
} else {
+ var id = version.split('/', 2)[1];
+
return {
- deflate: spdy.utils.createDeflate(),
- inflate: spdy.utils.createInflate()
+ deflate: spdy.utils.createDeflate(id),
+ inflate: spdy.utils.createInflate(id)
};
}
};
@@ -37,7 +42,7 @@ Pool.prototype.get = function get(callback) {
// ### function put (pair)
// Puts pair into pool
//
-Pool.prototype.put = function put(pair) {
+Pool.prototype.put = function put(version, pair) {
var self = this,
waiting = 2;
@@ -46,7 +51,7 @@ Pool.prototype.put = function put(pair) {
function done() {
if (--waiting === 0) {
- self.pool.push(pair);
+ self.pool[version].push(pair);
}
}
};
View
5 package.json
@@ -21,7 +21,7 @@
],
"dependencies": {},
"devDependencies": {
- "mocha": "~ 0.10.1"
+ "mocha": "1.3.x"
},
"scripts": {
"test": "mocha --ui tdd --growl --reporter spec test/unit/*-test.js"
@@ -29,5 +29,6 @@
"engines": [
"node ~ 0.7.0"
],
- "main": "./lib/spdy"
+ "main": "./lib/spdy",
+ "optionalDependencies": {}
}
View
4 test/unit/framer-test.js
@@ -9,8 +9,8 @@ suite('A Framer of SPDY module', function() {
framer;
setup(function() {
- inflate = spdy.utils.zwrap(spdy.utils.createInflate());
- deflate = spdy.utils.zwrap(spdy.utils.createDeflate());
+ inflate = spdy.utils.zwrap(spdy.utils.createInflate(2));
+ deflate = spdy.utils.zwrap(spdy.utils.createDeflate(2));
framer = new spdy.protocol[2].Framer(deflate, inflate);
});
View
157 test/unit/parser-test.js
@@ -5,87 +5,98 @@ var assert = require('assert'),
suite('A Parser of SPDY module', function() {
var parser;
- setup(function() {
- var deflate = spdy.utils.createDeflate(),
- inflate = spdy.utils.createInflate();
+ [2,3].forEach(function(version) {
+ suite('version ' + version, function() {
+ setup(function() {
+ var deflate = spdy.utils.createDeflate(version),
+ inflate = spdy.utils.createInflate(version);
- parser = new spdy.parser.create(deflate, inflate);
- });
+ parser = new spdy.parser.create({
+ socket: {
+ setNoDelay: function() {}
+ },
+ write: function() {}
+ }, deflate, inflate);
- test('should wait for headers initially', function() {
- assert.equal(parser.waiting, 8);
- });
+ parser.createFramer(version);
+ });
- test('should update buffered property once given < 8 bytes', function() {
- parser.write(new Buffer(5));
- assert.equal(parser.buffered, 5);
- });
+ test('should wait for headers initially', function() {
+ assert.equal(parser.waiting, 8);
+ });
- test('given SYN_STREAM header should start waiting for body', function() {
- parser.write(new Buffer([
- 0x80, 0x02, 0x00, 0x01, // Control frame, version, type (SYN_STREAM)
- 0x00, 0x00, 0x12, 0x34
- ]));
-
- assert.equal(parser.waiting, 0x1234);
- assert.equal(parser.state.type, 'frame-body');
- assert.ok(parser.state.header.control);
- assert.equal(parser.state.header.flags, 0);
- assert.equal(parser.state.header.length, 0x1234);
- });
+ test('should update buffered property once given < 8 bytes', function() {
+ parser.write(new Buffer(5));
+ assert.equal(parser.buffered, 5);
+ });
- test('given DATA header should start waiting for body', function() {
- parser.write(new Buffer([
- 0x00, 0x00, 0x00, 0x01, // Data frame, stream ID
- 0x00, 0x00, 0x12, 0x34
- ]));
-
- assert.equal(parser.waiting, 0x1234);
- assert.equal(parser.state.type, 'frame-body');
- assert.ok(!parser.state.header.control);
- assert.equal(parser.state.header.id, 1);
- assert.equal(parser.state.header.flags, 0);
- assert.equal(parser.state.header.length, 0x1234);
- });
+ test('given SYN_STREAM header should start waiting for body', function() {
+ parser.write(new Buffer([
+ 0x80, 0x02, 0x00, 0x01, // Control frame, version, type (SYN_STREAM)
+ 0x00, 0x00, 0x12, 0x34
+ ]));
- test('given chunked header should not fail', function() {
- parser.write(new Buffer([
- 0x80, 0x02, 0x00, 0x01 // Control frame, version, type (SYN_STREAM)
- ]));
- assert.equal(parser.buffered, 4);
-
- parser.write(new Buffer([
- 0x00, 0x00, 0x12, 0x34
- ]));
- assert.equal(parser.buffered, 0);
-
- assert.equal(parser.waiting, 0x1234);
- assert.equal(parser.state.type, 'frame-body');
- assert.ok(parser.state.header.control);
- assert.equal(parser.state.header.flags, 0);
- assert.equal(parser.state.header.length, 0x1234);
- });
+ assert.equal(parser.waiting, 0x1234);
+ assert.equal(parser.state.type, 'frame-body');
+ assert.ok(parser.state.header.control);
+ assert.equal(parser.state.header.flags, 0);
+ assert.equal(parser.state.header.length, 0x1234);
+ });
- test('given header and body should emit `frame`', function(done) {
- parser.on('frame', function(frame) {
- assert.ok(frame.type === 'DATA');
- assert.equal(frame.id, 1);
- assert.equal(frame.data.length, 4);
- assert.equal(frame.data[0], 0x01);
- assert.equal(frame.data[1], 0x02);
- assert.equal(frame.data[2], 0x03);
- assert.equal(frame.data[3], 0x04);
- done();
- });
+ test('given DATA header should start waiting for body', function() {
+ parser.write(new Buffer([
+ 0x00, 0x00, 0x00, 0x01, // Data frame, stream ID
+ 0x00, 0x00, 0x12, 0x34
+ ]));
- parser.write(new Buffer([
- 0x00, 0x00, 0x00, 0x01, // Data frame, stream ID
- 0x00, 0x00, 0x00, 0x04,
- 0x01, 0x02, 0x03, 0x04 // Body
- ]));
+ assert.equal(parser.waiting, 0x1234);
+ assert.equal(parser.state.type, 'frame-body');
+ assert.ok(!parser.state.header.control);
+ assert.equal(parser.state.header.id, 1);
+ assert.equal(parser.state.header.flags, 0);
+ assert.equal(parser.state.header.length, 0x1234);
+ });
- // Waits for next frame
- assert.equal(parser.waiting, 8);
- assert.equal(parser.state.type, 'frame-head');
+ test('given chunked header should not fail', function() {
+ parser.write(new Buffer([
+ 0x80, 0x02, 0x00, 0x01 // Control frame, version, type (SYN_STREAM)
+ ]));
+ assert.equal(parser.buffered, 4);
+
+ parser.write(new Buffer([
+ 0x00, 0x00, 0x12, 0x34
+ ]));
+ assert.equal(parser.buffered, 0);
+
+ assert.equal(parser.waiting, 0x1234);
+ assert.equal(parser.state.type, 'frame-body');
+ assert.ok(parser.state.header.control);
+ assert.equal(parser.state.header.flags, 0);
+ assert.equal(parser.state.header.length, 0x1234);
+ });
+
+ test('given header and body should emit `frame`', function(done) {
+ parser.on('frame', function(frame) {
+ assert.ok(frame.type === 'DATA');
+ assert.equal(frame.id, 1);
+ assert.equal(frame.data.length, 4);
+ assert.equal(frame.data[0], 0x01);
+ assert.equal(frame.data[1], 0x02);
+ assert.equal(frame.data[2], 0x03);
+ assert.equal(frame.data[3], 0x04);
+ done();
+ });
+
+ parser.write(new Buffer([
+ 0x00, 0x00, 0x00, 0x01, // Data frame, stream ID
+ 0x00, 0x00, 0x00, 0x04,
+ 0x01, 0x02, 0x03, 0x04 // Body
+ ]));
+
+ // Waits for next frame
+ assert.equal(parser.waiting, 8);
+ assert.equal(parser.state.type, 'frame-head');
+ });
+ });
});
});
View
2  test/unit/server-test.js
@@ -39,7 +39,7 @@ suite('A SPDY Server', function() {
'localhost',
{ NPNProtocols: ['spdy/2'] },
function() {
- var deflate = spdy.utils.createDeflate(),
+ var deflate = spdy.utils.createDeflate(2),
chunks = [],
length = 0;

Showing you all comments on commits in this comparison.

@indutny
Owner

I think you should add Tatsuhiro Tsujikawa to the AUTHORS file and README.

@eee-c

Done :)

Something went wrong with that request. Please try again.