spdy 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
5
+ misc.md
data/.rspec ADDED
File without changes
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "http://rubygems.org"
2
+
3
+ gemspec
data/README.md ADDED
@@ -0,0 +1,42 @@
1
+ # SPDY: An experimental protocol for a faster web
2
+
3
+ SPDY was developed at Google as part of the "let's make the web faster" initiative. SPDY ("SPeeDY") is an application-layer protocol for transporting content over the web, designed specifically for minimal latency. In lab tests, SPDY shows 64% reduction in page load times! For more details, check out the [official site](https://sites.google.com/a/chromium.org/dev/spdy).
4
+
5
+ Today, SPDY is built into Chrome + Google web-server infrastructure and is currently serving over 90% of all the SSL traffic. Yes, you read that right.. If you're using Chrome, and you're using Google products, chances are, you are fetching the content from Google servers over SPDY, not HTTP.
6
+
7
+ See: [Life beyond HTTP 1.1: Google's SPDY](http://www.igvita.com/2011/04/07/life-beyond-http-11-googles-spdy)
8
+
9
+ ## Protocol Parser
10
+
11
+ SPDY specification (Draft 2) defines its own framing and message exchange protocol which is layered on top of a raw TCP/SSL connection. This gem implements a basic, pure Ruby parser for the SPDY protocol:
12
+
13
+ s = SPDY::Parser.new
14
+
15
+ s.on_headers_complete { |stream_id, associated_stream, priority, headers| ... }
16
+ s.on_body { |stream_id, data| ... }
17
+ s.on_message_complete { |stream_id| ... }
18
+
19
+ s << recieved_data
20
+
21
+ However, parsing the data is not enough, to do the full exchange you also have to respond to a SPDY client with appropriate 'control' and 'data' frames:
22
+
23
+ sr = SPDY::Protocol::Control::SynReply.new
24
+ headers = {'Content-Type' => 'text/plain', 'status' => '200 OK', 'version' => 'HTTP/1.1'}
25
+ sr.create(:stream_id => 1, :headers => headers)
26
+ send_data sr.to_binary_s
27
+
28
+ # or, to send a data frame
29
+
30
+ d = SPDY::Protocol::Data::Frame.new
31
+ d.create(:stream_id => 1, :data => "This is SPDY.")
32
+ send_data d.to_binary_s
33
+
34
+ See example eventmachine server in *examples/spdy_server.rb* for a minimal SPDY "hello world" server.
35
+
36
+ ## Todo:
37
+
38
+ - implement support for all [control frames](https://sites.google.com/a/chromium.org/dev/spdy/spdy-protocol/spdy-protocol-draft2#TOC-Control-frames1)
39
+
40
+ ### License
41
+
42
+ (MIT License) - Copyright (c) 2011 Ilya Grigorik
data/Rakefile ADDED
@@ -0,0 +1,7 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
3
+
4
+ require 'rspec/core/rake_task'
5
+
6
+ desc "Run all RSpec tests"
7
+ RSpec::Core::RakeTask.new(:spec)
@@ -0,0 +1,54 @@
1
+ $: << 'lib' << '../lib'
2
+
3
+ require 'eventmachine'
4
+ require 'spdy'
5
+
6
+ class SPDYHandler < EM::Connection
7
+ def post_init
8
+ @parser = SPDY::Parser.new
9
+ @parser.on_headers_complete do |stream_id, associated_stream, priority, headers|
10
+ p [:SPDY_HEADERS, headers]
11
+
12
+ sr = SPDY::Protocol::Control::SynReply.new
13
+ h = {'Content-Type' => 'text/plain', 'status' => '200 OK', 'version' => 'HTTP/1.1'}
14
+ sr.create(:stream_id => 1, :headers => h)
15
+ send_data sr.to_binary_s
16
+
17
+ p [:SPDY, :sent, :SYN_REPLY]
18
+
19
+ d = SPDY::Protocol::Data::Frame.new
20
+ d.create(:stream_id => 1, :data => "This is SPDY.")
21
+ send_data d.to_binary_s
22
+
23
+ p [:SPDY, :sent, :DATA]
24
+
25
+ d = SPDY::Protocol::Data::Frame.new
26
+ d.create(:stream_id => 1, :flags => 1)
27
+ send_data d.to_binary_s
28
+
29
+ p [:SPDY, :sent, :DATA_FIN]
30
+ end
31
+
32
+ start_tls
33
+ end
34
+
35
+ def receive_data(data)
36
+ @parser << data
37
+ end
38
+
39
+ def unbind
40
+ p [:SPDY, :connection_closed]
41
+ end
42
+ end
43
+
44
+ EM.run do
45
+ EM.start_server '0.0.0.0', 10000, SPDYHandler
46
+ end
47
+
48
+ # (1) start the SPDY eventmachine server
49
+ # > ruby spdy_server.rb
50
+ #
51
+ # (2) start Chrome and force it to use SPDY over SSL.. on OSX:
52
+ # > /Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --use-spdy=ssl
53
+ #
54
+ # (3) visit https://localhost:1000/
@@ -0,0 +1,68 @@
1
+ module SPDY
2
+ module Zlib
3
+
4
+ DICT = \
5
+ "optionsgetheadpostputdeletetraceacceptaccept-charsetaccept-encodingaccept-" \
6
+ "languageauthorizationexpectfromhostif-modified-sinceif-matchif-none-matchi" \
7
+ "f-rangeif-unmodifiedsincemax-forwardsproxy-authorizationrangerefererteuser" \
8
+ "-agent10010120020120220320420520630030130230330430530630740040140240340440" \
9
+ "5406407408409410411412413414415416417500501502503504505accept-rangesageeta" \
10
+ "glocationproxy-authenticatepublicretry-afterservervarywarningwww-authentic" \
11
+ "ateallowcontent-basecontent-encodingcache-controlconnectiondatetrailertran" \
12
+ "sfer-encodingupgradeviawarningcontent-languagecontent-lengthcontent-locati" \
13
+ "oncontent-md5content-rangecontent-typeetagexpireslast-modifiedset-cookieMo" \
14
+ "ndayTuesdayWednesdayThursdayFridaySaturdaySundayJanFebMarAprMayJunJulAugSe" \
15
+ "pOctNovDecchunkedtext/htmlimage/pngimage/jpgimage/gifapplication/xmlapplic" \
16
+ "ation/xhtmltext/plainpublicmax-agecharset=iso-8859-1utf-8gzipdeflateHTTP/1" \
17
+ ".1statusversionurl\0"
18
+
19
+ CHUNK = 10*1024 # this is silly, but it'll do for now
20
+
21
+ def self.inflate(data)
22
+ in_buf = FFI::MemoryPointer.from_string(data)
23
+ out_buf = FFI::MemoryPointer.new(CHUNK)
24
+
25
+ zstream = FFI::Zlib::Z_stream.new
26
+ zstream[:avail_in] = in_buf.size
27
+ zstream[:avail_out] = CHUNK
28
+ zstream[:next_in] = in_buf
29
+ zstream[:next_out] = out_buf
30
+
31
+ result = FFI::Zlib.inflateInit(zstream)
32
+ raise "invalid stream" if result != FFI::Zlib::Z_OK
33
+
34
+ result = FFI::Zlib.inflate(zstream, FFI::Zlib::Z_SYNC_FLUSH)
35
+ raise "invalid stream" if result != FFI::Zlib::Z_NEED_DICT
36
+
37
+ result = FFI::Zlib.inflateSetDictionary(zstream, DICT, DICT.size)
38
+ raise "invalid dictionary" if result != FFI::Zlib::Z_OK
39
+
40
+ result = FFI::Zlib.inflate(zstream, FFI::Zlib::Z_SYNC_FLUSH)
41
+ raise "cannot inflate" if result != FFI::Zlib::Z_OK
42
+
43
+ out_buf.get_bytes(0, zstream[:total_out])
44
+ end
45
+
46
+ def self.deflate(data)
47
+ in_buf = FFI::MemoryPointer.from_string(data)
48
+ out_buf = FFI::MemoryPointer.new(CHUNK)
49
+
50
+ zstream = FFI::Zlib::Z_stream.new
51
+ zstream[:avail_in] = in_buf.size-1
52
+ zstream[:avail_out] = CHUNK
53
+ zstream[:next_in] = in_buf
54
+ zstream[:next_out] = out_buf
55
+
56
+ result = FFI::Zlib.deflateInit(zstream, -1)
57
+ raise "invalid stream" if result != FFI::Zlib::Z_OK
58
+
59
+ result = FFI::Zlib.deflateSetDictionary(zstream, DICT, DICT.size)
60
+ raise "invalid dictionary" if result != FFI::Zlib::Z_OK
61
+
62
+ result = FFI::Zlib.deflate(zstream, FFI::Zlib::Z_SYNC_FLUSH)
63
+ raise "cannot deflate" if result != FFI::Zlib::Z_OK
64
+
65
+ out_buf.get_bytes(0, zstream[:total_out])
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,88 @@
1
+ module SPDY
2
+ class Parser
3
+ include Protocol
4
+
5
+ def initialize
6
+ @buffer = ''
7
+ end
8
+
9
+ def <<(data)
10
+ @buffer << data
11
+ try_parse
12
+ end
13
+
14
+ def on_headers_complete(&blk)
15
+ @on_headers_complete = blk
16
+ end
17
+
18
+ def on_body(&blk)
19
+ @on_body = blk
20
+ end
21
+
22
+ def on_message_complete(&blk)
23
+ @on_message_complete = blk
24
+ end
25
+
26
+ private
27
+
28
+ def try_parse
29
+ type = @buffer[0,1].unpack('C').first >> 7 & 0x01
30
+ pckt = nil
31
+
32
+ case type
33
+ when CONTROL_BIT
34
+ return if @buffer.size < 12
35
+ pckt = Control::Header.new.read(@buffer[0,12])
36
+
37
+ case pckt.type.to_i
38
+ when 1 then # SYN_STREAM
39
+ pckt = Control::SynStream.new
40
+ pckt.read(@buffer)
41
+
42
+ headers = {}
43
+ if pckt.data.size > 0
44
+ data = Zlib.inflate(pckt.data.to_s)
45
+ headers = NV.new.read(data).to_h
46
+ end
47
+
48
+ if @on_headers_complete
49
+ @on_headers_complete.call(pckt.header.stream_id.to_i,
50
+ pckt.associated_to_stream_id.to_i,
51
+ pckt.pri.to_i,
52
+ headers)
53
+ end
54
+
55
+ when 2 then # SYN_REPLY
56
+ raise 'SYN_REPLY not handled yet'
57
+ else
58
+ raise 'invalid control frame'
59
+ end
60
+
61
+ @on_message_complete.call(pckt.header.stream_id) if @on_message_complete && fin?(pckt.header)
62
+
63
+ when DATA_BIT
64
+ return if @buffer.size < 8
65
+
66
+ pckt = Data::Frame.new.read(@buffer)
67
+ @on_body.call(pckt.stream_id, pckt.data) if @on_body
68
+ @on_message_complete.call(pckt.stream_id) if @on_message_complete && fin?(pckt)
69
+
70
+ else
71
+ raise 'unknown packet type'
72
+ end
73
+
74
+ # remove parsed data from the buffer
75
+ @buffer.slice!(0..pckt.num_bytes)
76
+
77
+ rescue IOError
78
+ # rescue partial parse and wait for more data
79
+ end
80
+
81
+ private
82
+
83
+ def fin?(packet)
84
+ (packet.flags == 1) rescue false
85
+ end
86
+
87
+ end
88
+ end
@@ -0,0 +1,122 @@
1
+ module SPDY
2
+ module Protocol
3
+
4
+ CONTROL_BIT = 1
5
+ DATA_BIT = 0
6
+ VERSION = 2
7
+
8
+ module Control
9
+ class Header < BinData::Record
10
+ hide :u1
11
+
12
+ bit1 :frame, :initial_value => CONTROL_BIT
13
+ bit15 :version, :initial_value => VERSION
14
+ bit16 :type
15
+
16
+ bit8 :flags
17
+ bit24 :len
18
+
19
+ bit1 :u1
20
+ bit31 :stream_id
21
+ end
22
+
23
+ class SynStream < BinData::Record
24
+ hide :u1, :u2
25
+
26
+ header :header
27
+
28
+ bit1 :u1
29
+ bit31 :associated_to_stream_id
30
+
31
+ bit2 :pri
32
+ bit14 :u2
33
+
34
+ string :data, :read_length => lambda { header.len - 10 }
35
+ end
36
+
37
+ class SynReply < BinData::Record
38
+ attr_accessor :uncompressed_data
39
+
40
+ header :header
41
+ bit16 :unused
42
+ string :data, :read_length => lambda { header.len - 6 }
43
+
44
+ def parse(chunk)
45
+ self.read(chunk)
46
+
47
+ data = Zlib.inflate(self.data.to_s)
48
+ self.uncompressed_data = NV.new.read(data)
49
+ self
50
+ end
51
+
52
+ def create(opts = {})
53
+ self.header.type = 2
54
+ self.header.len = 6
55
+
56
+ self.header.flags = opts[:flags] || 0
57
+ self.header.stream_id = opts[:stream_id]
58
+
59
+ nv = SPDY::Protocol::NV.new
60
+ nv.create(opts[:headers])
61
+
62
+ nv = SPDY::Zlib.deflate(nv.to_binary_s)
63
+ self.header.len = self.header.len.to_i + nv.size
64
+ self.data = nv
65
+
66
+ self
67
+ end
68
+ end
69
+ end
70
+
71
+ module Data
72
+ class Frame < BinData::Record
73
+ bit1 :frame, :initial_value => DATA_BIT
74
+ bit31 :stream_id
75
+
76
+ bit8 :flags, :initial_value => 0
77
+ bit24 :len, :initial_value => 0
78
+
79
+ string :data, :read_length => :len
80
+
81
+ def create(opts = {})
82
+ self.stream_id = opts[:stream_id]
83
+ self.flags = opts[:flags] if opts[:flags]
84
+
85
+ if opts[:data]
86
+ self.len = opts[:data].size
87
+ self.data = opts[:data]
88
+ end
89
+
90
+ self
91
+ end
92
+ end
93
+ end
94
+
95
+ class NV < BinData::Record
96
+ bit16 :pairs
97
+ array :headers, :initial_length => :pairs do
98
+ bit16 :name_len
99
+ string :name_data, :read_length => :name_len
100
+
101
+ bit16 :value_len
102
+ string :value_data, :read_length => :value_len
103
+ end
104
+
105
+ def create(opts = {})
106
+ opts.each do |k, v|
107
+ self.headers << {:name_len => k.size, :name_data => k, :value_len => v.size, :value_data => v}
108
+ end
109
+
110
+ self.pairs = opts.size
111
+ self
112
+ end
113
+
114
+ def to_h
115
+ headers.inject({}) do |h, v|
116
+ h[v.name_data.to_s] = v.value_data.to_s
117
+ h
118
+ end
119
+ end
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,3 @@
1
+ module Spdy
2
+ VERSION = "0.0.1"
3
+ end
data/lib/spdy.rb ADDED
@@ -0,0 +1,6 @@
1
+ require 'bindata'
2
+ require 'ffi/zlib'
3
+
4
+ require 'spdy/protocol'
5
+ require 'spdy/compressor'
6
+ require 'spdy/parser'
data/spdy.gemspec ADDED
@@ -0,0 +1,25 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "spdy/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "spdy"
7
+ s.version = Spdy::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Ilya Grigorik"]
10
+ s.email = ["ilya@igvita.com"]
11
+ s.homepage = "https://github.com/igrigorik/spdy"
12
+ s.summary = "SPDY is an experiment with protocols for the web"
13
+ s.description = s.summary
14
+
15
+ s.rubyforge_project = "spdy"
16
+
17
+ s.add_dependency "bindata"
18
+ s.add_dependency "ffi-zlib"
19
+ s.add_development_dependency "rspec"
20
+
21
+ s.files = `git ls-files`.split("\n")
22
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
23
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
24
+ s.require_paths = ["lib"]
25
+ end
@@ -0,0 +1,14 @@
1
+ require 'helper'
2
+
3
+ describe SPDY::Zlib do
4
+ it "should inflate header with custom dictionary" do
5
+ SPDY::Zlib.inflate(COMPRESSED_HEADER).should match('HTTP/1.1')
6
+ end
7
+
8
+ it "should deflate header with custom dictionary" do
9
+ orig = SPDY::Zlib.inflate(COMPRESSED_HEADER)
10
+ rinse = SPDY::Zlib.inflate(SPDY::Zlib.deflate(orig))
11
+
12
+ orig.should == rinse
13
+ end
14
+ end
data/spec/helper.rb ADDED
@@ -0,0 +1,15 @@
1
+ require 'rspec'
2
+ require 'spdy'
3
+
4
+ COMPRESSED_HEADER = "8\xEA\xDF\xA2Q\xB2b\xE0f`\x83\xA4\x17\x86(4\xDBu0\xAC\xD7\x06\x89\xC2\xFDa]hk\xA0g\xA9\x83p\x13X\xC0B\a\xEE?\x1D-}-\xB0\x98)\x03\x1Fjff\x90\xF3\f\xF6\x87:U\a\xECV\xB0:s\x1D\x88zc\x06~\xB4\xEC\xCE \b\xF2\x8C\x0E\xD47:\xC5)\xC9\x19p5\xB0\x14\xC2\xC0\x97\x9A\xA7\e\x1A\xAC\x93\nu\b\x03\e$V\x19Z\x8A\x8D\x8C\xD3\xD2u\x8D\v\x8C\xCC\x8D\x92\v,\xCB\r\xE2\x8Bl\xCD\xAD\x15\f\xB3\xCD\v\xCDu3\rR\xCC\xD3\x8B\v\r-\xCC\x81\xA2\x06\xD6\n\xF1 '\x96$\xA5&\x96\x18\x01d[\x9Cj\x9CUQ\x92dT\x99e\x9C\x9A\x93\x93j\f\x94\x8D//)\x8F/\xCB,\x8E\afy[k\x85\xB2\xC4\xBC\xCC\x92\xCA\xF8\xCC\x14\xDB4c#\x8B\xE4$3\x13c\x93d`\xFEM12N154OI32O\x03\x16\x04\xA6\x96I\f,\xA0\xC2\x88\x81\x0F\x94bs@L+K\x03\x03\x03\x06\xB6\\`!\x98\x9F\xC2\xC0\xEC\xEE\x1A\xC2\xC0V\f\xCC7\xB9\xA9\f\xAC\x19%%\x05\xC5\f\xCC\xA0\bb\xD4g\xE0B\x94*\fe\xBE\xF9U\x9999\x89\xFA\xA6z\x06\x00)h\xF8&&g\xE6\x95\xE4\x17gX+x\x02\x13z\x8E\x02P@\xC1?X!B\xC1\xD0 \xDE,\xDE\\S\xC1\x11\x18\x87\xA9\xE1\xA9I\xDE\x99%\xFA\xA6\xC6&zF&\n\x1A\xDE\x1E!\xBE>:\n9\x99\xD9\xA9\n\xEE\xA9\xC9\xD9\xF9\x9A\n\xCE\x19\xC0\xD22U\xDF\xD0P\xCF@\xCF\xCC\xD2L\xCF\xC8B!81-\xB1(\x13\xAA\x89\x81\x1D\x9Af\x188`I\t\x00\x00\x00\xFF\xFF"
5
+
6
+ SYN_STREAM = "\x80\x02\x00\x01\x01\x00\x01E\x00\x00\x00\x01\x00\x00\x00\x00\x00\x008\xEA\xDF\xA2Q\xB2b\xE0f`\x83\xA4\x17\x86(4\xDBu0\xAC\xD7\x06\x89\xC2\xFDa]hk\xA0g\xA9\x83p\x13X\xC0B\a\xEE?\x1D-}-\xB0\x98)\x03\x1Fjff\x90\xF3\f\xF6\x87:U\a\xECV\xB0:s\x1D\x88zc\x06~\xB4\xEC\xCE \b\xF2\x8C\x0E\xD47:\xC5)\xC9\x19p5\xB0\x14\xC2\xC0\x97\x9A\xA7\e\x1A\xAC\x93\nu\b\x03/J:d\xE0\x84\x86\x96\xAD\x01\x03\v\xA8``\xE0342\xD73\x00BC+K\x03\x03\x03\x06\xB6\\`\x81\x94\x9F\xC2\xC0\xEC\xEE\x1A\xC2\xC0V\f\xD4\x9B\x9B\xCA\xC0\x9AQRRP\xCC\xC0\f\n,\x11}{\x80\x80a\x9Do\x9B\xA8\x06,\x10\x80\xC5\x86mVq~\x1E\x03\x17\"\xD33\x94\xF9\xE6We\xE6\xE4$\xEA\x9B\xEA\x19(h\xF8&&g\xE6\x95\xE4\x17gX+x\x02\xD3a\x8E\x02P@\xC1?X!B\xC1\xD0 \xDE,\xDE\\S\xC1\x11\x18\xC4\xA9\xE1\xA9I\xDE\x99%\xFA\xA6\xC6&zF&\n\x1A\xDE\x1E!\xBE>:\n9\x99\xD9\xA9\n\xEE\xA9\xC9\xD9\xF9\x9A\n\xCE\x19\xC0\xC2,U\xDF\xD0\x10\xE8X3K3=#\v\x85\xE0\xC4\xB4\xC4\xA2L\xA8&\x06vh\x942p\xC0b\x1A\x00\x00\x00\xFF\xFF"
7
+
8
+ SYN_REPLY = "\x80\x02\x00\x02\x00\x00\x005\x00\x00\x00\x01\x00\x00x\xbb\xdf\xa2Q\xb2b`f\xe0q\x86\x06R\x080\x90\x18\xb8\x10v0\xb0A\x943\xb0\x01\x93\xb1\x82\xbf7\x03;T#\x03\x07\xcc<\x00\x00\x00\x00\xff\xff"
9
+
10
+ RST_STREAM = "\x80\x02\x00\x03\x00\x00\x00\b\x00\x00\x00\x01\x00\x00\x00\x01"
11
+
12
+ DATA = "\x00\x00\x00\x01\x00\x00\x00\rThis is SPDY."
13
+ DATA_FIN = "\x00\x00\x00\x01\x01\x00\x00\x00"
14
+
15
+ NV = "\x00\x03\x00\x0cContent-Type\x00\ntext/plain\x00\x06status\x00\x06200 OK\x00\x07version\x00\x08HTTP/1.1"
@@ -0,0 +1,115 @@
1
+ require 'helper'
2
+
3
+ describe SPDY::Parser do
4
+ let(:s) { SPDY::Parser.new }
5
+
6
+ context "callbacks" do
7
+ it "should accept header callback" do
8
+ lambda do
9
+ s.on_headers_complete {}
10
+ end.should_not raise_error
11
+ end
12
+
13
+ it "should accept body callback" do
14
+ lambda do
15
+ s.on_body {}
16
+ end.should_not raise_error
17
+ end
18
+
19
+ it "should accept message complete callback" do
20
+ lambda do
21
+ s.on_message_complete {}
22
+ end.should_not raise_error
23
+ end
24
+ end
25
+
26
+ it "should accept incoming data" do
27
+ lambda { s << DATA }.should_not raise_error
28
+ end
29
+
30
+ it "should reassemble broken packets" do
31
+ stream, data = nil
32
+ s.on_body { |stream_id, d| stream, data = stream_id, d }
33
+
34
+ lambda { s << DATA[0...DATA.size - 10] }.should_not raise_error
35
+ lambda { s << DATA[DATA.size-10..DATA.size] }.should_not raise_error
36
+
37
+ stream.should == 1
38
+ data.should == 'This is SPDY.'
39
+
40
+ fired = false
41
+ s.on_headers_complete { fired = true }
42
+ s << SYN_STREAM
43
+
44
+ fired.should be_true
45
+ end
46
+
47
+ context "CONTROL" do
48
+ it "should parse SYN_STREAM packet" do
49
+ fired = false
50
+ s.on_headers_complete { fired = true }
51
+ s << SYN_STREAM
52
+
53
+ fired.should be_true
54
+ end
55
+
56
+ it "should return parsed headers" do
57
+ sid, asid, pri, headers = nil
58
+ s.on_headers_complete do |stream, astream, priority, head|
59
+ sid = stream; asid = astream; pri = priority; headers = head
60
+ end
61
+
62
+ s << SYN_STREAM
63
+
64
+ sid.should == 1
65
+ asid.should == 0
66
+ pri.should == 0
67
+
68
+ headers.class.should == Hash
69
+ headers['version'].should == "HTTP/1.1"
70
+ end
71
+ end
72
+
73
+ context "DATA" do
74
+ it "should parse data packet" do
75
+ stream, data = nil
76
+ s.on_body { |stream_id, d| stream, data = stream_id, d }
77
+ s << DATA
78
+
79
+ stream.should == 1
80
+ data.should == 'This is SPDY.'
81
+ end
82
+ end
83
+
84
+ context "FIN" do
85
+ it "should invoke message_complete on FIN flag in CONTROL packet" do
86
+ f1, f2 = false
87
+ s.on_headers_complete { f1 = true }
88
+ s.on_message_complete { |s| f2 = s }
89
+
90
+ sr = SPDY::Protocol::Control::SynStream.new
91
+ sr.header.stream_id = 3
92
+ sr.header.type = 1
93
+ sr.header.flags = 0x01
94
+ sr.header.len = 10
95
+
96
+ s << sr.to_binary_s
97
+
98
+ f1.should be_true
99
+ f2.should == 3
100
+ end
101
+
102
+ it "should invoke message_complete on FIN flag in DATA packet" do
103
+ f1, f2 = false
104
+ s.on_body { f1 = true }
105
+ s.on_message_complete { |s| f2 = s }
106
+
107
+ s << DATA_FIN
108
+
109
+ f1.should be_true
110
+ f2.should == 1
111
+ end
112
+
113
+ end
114
+
115
+ end
@@ -0,0 +1,63 @@
1
+ require 'helper'
2
+
3
+ describe SPDY::Protocol do
4
+
5
+ context "NV" do
6
+ it "should create an NV packet" do
7
+ nv = SPDY::Protocol::NV.new
8
+ nv.create({'Content-Type' => 'text/plain', 'status' => '200 OK', 'version' => 'HTTP/1.1'})
9
+
10
+ nv.to_binary_s.should == NV
11
+ end
12
+ end
13
+
14
+ context "SYN_REPLY" do
15
+ it "should create a SYN_REPLY packet" do
16
+ sr = SPDY::Protocol::Control::SynReply.new
17
+
18
+ headers = {'Content-Type' => 'text/plain', 'status' => '200 OK', 'version' => 'HTTP/1.1'}
19
+ sr.create(:stream_id => 1, :headers => headers)
20
+
21
+ sr.header.version.should == 2
22
+ sr.header.stream_id.should == 1
23
+
24
+ sr.header.len.should > 50
25
+ sr.data.should_not be_nil
26
+
27
+ sr.to_binary_s.should == SYN_REPLY
28
+ end
29
+
30
+ it "should parse SYN_REPLY packet" do
31
+ sr = SPDY::Protocol::Control::SynReply.new
32
+ sr.parse(SYN_REPLY)
33
+
34
+ sr.header.type.should == 2
35
+ sr.uncompressed_data.to_h.class.should == Hash
36
+ sr.uncompressed_data.to_h['status'].should == '200 OK'
37
+
38
+ sr.to_binary_s.should == SYN_REPLY
39
+ end
40
+ end
41
+
42
+ context "DATA" do
43
+ it "should create a data frame" do
44
+ data = "This is SPDY."
45
+
46
+ d = SPDY::Protocol::Data::Frame.new
47
+ d.create(:stream_id => 1, :data => data)
48
+
49
+ d.to_binary_s.should == DATA
50
+ end
51
+
52
+ it "should create a FIN data frame" do
53
+ d = SPDY::Protocol::Data::Frame.new
54
+ d.create(:stream_id => 1, :flags => 1)
55
+
56
+ d.to_binary_s.should == DATA_FIN
57
+ end
58
+ end
59
+
60
+ # context "RST_STREAM" do
61
+ # it "should parse reset packet"
62
+ # end
63
+ end
metadata ADDED
@@ -0,0 +1,107 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: spdy
3
+ version: !ruby/object:Gem::Version
4
+ prerelease:
5
+ version: 0.0.1
6
+ platform: ruby
7
+ authors:
8
+ - Ilya Grigorik
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2011-04-07 00:00:00 -04:00
14
+ default_executable:
15
+ dependencies:
16
+ - !ruby/object:Gem::Dependency
17
+ name: bindata
18
+ prerelease: false
19
+ requirement: &id001 !ruby/object:Gem::Requirement
20
+ none: false
21
+ requirements:
22
+ - - ">="
23
+ - !ruby/object:Gem::Version
24
+ version: "0"
25
+ type: :runtime
26
+ version_requirements: *id001
27
+ - !ruby/object:Gem::Dependency
28
+ name: ffi-zlib
29
+ prerelease: false
30
+ requirement: &id002 !ruby/object:Gem::Requirement
31
+ none: false
32
+ requirements:
33
+ - - ">="
34
+ - !ruby/object:Gem::Version
35
+ version: "0"
36
+ type: :runtime
37
+ version_requirements: *id002
38
+ - !ruby/object:Gem::Dependency
39
+ name: rspec
40
+ prerelease: false
41
+ requirement: &id003 !ruby/object:Gem::Requirement
42
+ none: false
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: "0"
47
+ type: :development
48
+ version_requirements: *id003
49
+ description: SPDY is an experiment with protocols for the web
50
+ email:
51
+ - ilya@igvita.com
52
+ executables: []
53
+
54
+ extensions: []
55
+
56
+ extra_rdoc_files: []
57
+
58
+ files:
59
+ - .gitignore
60
+ - .rspec
61
+ - Gemfile
62
+ - README.md
63
+ - Rakefile
64
+ - examples/spdy_server.rb
65
+ - lib/spdy.rb
66
+ - lib/spdy/compressor.rb
67
+ - lib/spdy/parser.rb
68
+ - lib/spdy/protocol.rb
69
+ - lib/spdy/version.rb
70
+ - spdy.gemspec
71
+ - spec/compressor_spec.rb
72
+ - spec/helper.rb
73
+ - spec/parser_spec.rb
74
+ - spec/protocol_spec.rb
75
+ has_rdoc: true
76
+ homepage: https://github.com/igrigorik/spdy
77
+ licenses: []
78
+
79
+ post_install_message:
80
+ rdoc_options: []
81
+
82
+ require_paths:
83
+ - lib
84
+ required_ruby_version: !ruby/object:Gem::Requirement
85
+ none: false
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: "0"
90
+ required_rubygems_version: !ruby/object:Gem::Requirement
91
+ none: false
92
+ requirements:
93
+ - - ">="
94
+ - !ruby/object:Gem::Version
95
+ version: "0"
96
+ requirements: []
97
+
98
+ rubyforge_project: spdy
99
+ rubygems_version: 1.6.2
100
+ signing_key:
101
+ specification_version: 3
102
+ summary: SPDY is an experiment with protocols for the web
103
+ test_files:
104
+ - spec/compressor_spec.rb
105
+ - spec/helper.rb
106
+ - spec/parser_spec.rb
107
+ - spec/protocol_spec.rb