http-2 0.6.1 → 0.6.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.autotest +2 -1
- data/Gemfile +8 -1
- data/README.md +27 -21
- data/example/README.md +53 -0
- data/example/client.rb +88 -30
- data/example/helper.rb +5 -0
- data/example/keys/mycert.pem +24 -0
- data/example/keys/mykey.pem +27 -0
- data/example/server.rb +47 -8
- data/http-2.gemspec +0 -2
- data/lib/http/2.rb +2 -0
- data/lib/http/2/buffer.rb +21 -4
- data/lib/http/2/client.rb +50 -0
- data/lib/http/2/compressor.rb +197 -181
- data/lib/http/2/connection.rb +57 -83
- data/lib/http/2/emitter.rb +2 -2
- data/lib/http/2/error.rb +5 -0
- data/lib/http/2/framer.rb +32 -31
- data/lib/http/2/server.rb +55 -0
- data/lib/http/2/stream.rb +1 -1
- data/lib/http/2/version.rb +1 -1
- data/spec/buffer_spec.rb +23 -0
- data/spec/client_spec.rb +93 -0
- data/spec/compressor_spec.rb +89 -80
- data/spec/connection_spec.rb +24 -75
- data/spec/emitter_spec.rb +8 -0
- data/spec/framer_spec.rb +36 -40
- data/spec/helper.rb +6 -2
- data/spec/server_spec.rb +50 -0
- data/spec/stream_spec.rb +23 -30
- metadata +13 -30
| @@ -0,0 +1,55 @@ | |
| 1 | 
            +
            module HTTP2
         | 
| 2 | 
            +
             | 
| 3 | 
            +
              # HTTP 2.0 server connection class that implements appropriate header
         | 
| 4 | 
            +
              # compression / decompression algorithms and stream management logic.
         | 
| 5 | 
            +
              #
         | 
| 6 | 
            +
              # Your code is responsible for feeding request data to the server object,
         | 
| 7 | 
            +
              # which in turn performs all of the necessary HTTP 2.0 decoding / encoding,
         | 
| 8 | 
            +
              # state management, and the rest. A simple example:
         | 
| 9 | 
            +
              #
         | 
| 10 | 
            +
              # @example
         | 
| 11 | 
            +
              #     socket = YourTransport.new
         | 
| 12 | 
            +
              #
         | 
| 13 | 
            +
              #     conn = HTTP2::Server.new
         | 
| 14 | 
            +
              #     conn.on(:stream) do |stream|
         | 
| 15 | 
            +
              #       ...
         | 
| 16 | 
            +
              #     end
         | 
| 17 | 
            +
              #
         | 
| 18 | 
            +
              #     while bytes = socket.read
         | 
| 19 | 
            +
              #       conn << bytes
         | 
| 20 | 
            +
              #     end
         | 
| 21 | 
            +
              #
         | 
| 22 | 
            +
              class Server < Connection
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                # Initialize new HTTP 2.0 server object.
         | 
| 25 | 
            +
                def initialize(*args)
         | 
| 26 | 
            +
                  @stream_id    = 2
         | 
| 27 | 
            +
                  @state        = :new
         | 
| 28 | 
            +
                  @compressor   = Header::Compressor.new(:response)
         | 
| 29 | 
            +
                  @decompressor = Header::Decompressor.new(:request)
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                  super
         | 
| 32 | 
            +
                end
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                private
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                # Handle locally initiated server-push event emitted by the stream.
         | 
| 37 | 
            +
                #
         | 
| 38 | 
            +
                # @param args [Array]
         | 
| 39 | 
            +
                # @param callback [Proc]
         | 
| 40 | 
            +
                def promise(*args, &callback)
         | 
| 41 | 
            +
                  parent, headers, flags = *args
         | 
| 42 | 
            +
                  promise = new_stream(parent: parent)
         | 
| 43 | 
            +
                  promise.send({
         | 
| 44 | 
            +
                    type: :push_promise,
         | 
| 45 | 
            +
                    flags: flags,
         | 
| 46 | 
            +
                    stream: parent.id,
         | 
| 47 | 
            +
                    promise_stream: promise.id,
         | 
| 48 | 
            +
                    payload: headers.to_a
         | 
| 49 | 
            +
                  })
         | 
| 50 | 
            +
             | 
| 51 | 
            +
                  callback.call(promise)
         | 
| 52 | 
            +
                end
         | 
| 53 | 
            +
              end
         | 
| 54 | 
            +
             | 
| 55 | 
            +
            end
         | 
    
        data/lib/http/2/stream.rb
    CHANGED
    
    
    
        data/lib/http/2/version.rb
    CHANGED
    
    
    
        data/spec/buffer_spec.rb
    ADDED
    
    | @@ -0,0 +1,23 @@ | |
| 1 | 
            +
            require "helper"
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            describe HTTP2::Buffer do
         | 
| 4 | 
            +
             | 
| 5 | 
            +
              let(:b) { Buffer.new("émalgré") }
         | 
| 6 | 
            +
             | 
| 7 | 
            +
              it "should force 8-bit encoding" do
         | 
| 8 | 
            +
                b.encoding.to_s.should eq "ASCII-8BIT"
         | 
| 9 | 
            +
              end
         | 
| 10 | 
            +
             | 
| 11 | 
            +
              it "should return bytesize of the buffer" do
         | 
| 12 | 
            +
                b.size.should eq 9
         | 
| 13 | 
            +
              end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
              it "should read single byte at a time" do
         | 
| 16 | 
            +
                9.times { b.read(1).should_not be_nil }
         | 
| 17 | 
            +
              end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
              it "should unpack an unsigned 32-bit int" do
         | 
| 20 | 
            +
                Buffer.new([256].pack("N")).read_uint32.should eq 256
         | 
| 21 | 
            +
              end
         | 
| 22 | 
            +
             | 
| 23 | 
            +
            end
         | 
    
        data/spec/client_spec.rb
    ADDED
    
    | @@ -0,0 +1,93 @@ | |
| 1 | 
            +
            require "helper"
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            describe HTTP2::Client do
         | 
| 4 | 
            +
              before(:each) do
         | 
| 5 | 
            +
                @client = Client.new
         | 
| 6 | 
            +
              end
         | 
| 7 | 
            +
             | 
| 8 | 
            +
              let(:f) { Framer.new }
         | 
| 9 | 
            +
             | 
| 10 | 
            +
              context "initialization and settings" do
         | 
| 11 | 
            +
                it "should return odd stream IDs" do
         | 
| 12 | 
            +
                  @client.new_stream.id.should_not be_even
         | 
| 13 | 
            +
                end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                it "should emit connection header and SETTINGS on new client connection" do
         | 
| 16 | 
            +
                  frames = []
         | 
| 17 | 
            +
                  @client.on(:frame) { |bytes| frames << bytes }
         | 
| 18 | 
            +
                  @client.ping("12345678")
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                  frames[0].should eq CONNECTION_HEADER
         | 
| 21 | 
            +
                  f.parse(frames[1])[:type].should eq :settings
         | 
| 22 | 
            +
                end
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                it "should initialize client with custom connection settings" do
         | 
| 25 | 
            +
                  frames = []
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                  @client = Client.new(streams: 200)
         | 
| 28 | 
            +
                  @client.on(:frame) { |bytes| frames << bytes }
         | 
| 29 | 
            +
                  @client.ping("12345678")
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                  frame = f.parse(frames[1])
         | 
| 32 | 
            +
                  frame[:type].should eq :settings
         | 
| 33 | 
            +
                  frame[:payload][:settings_max_concurrent_streams].should eq 200
         | 
| 34 | 
            +
                end
         | 
| 35 | 
            +
              end
         | 
| 36 | 
            +
             | 
| 37 | 
            +
              context "push" do
         | 
| 38 | 
            +
                it "should disallow client initiated push" do
         | 
| 39 | 
            +
                  expect do
         | 
| 40 | 
            +
                    @client.promise({}) {}
         | 
| 41 | 
            +
                  end.to raise_error(Exception)
         | 
| 42 | 
            +
                end
         | 
| 43 | 
            +
             | 
| 44 | 
            +
                it "should raise error on PUSH_PROMISE against stream 0" do
         | 
| 45 | 
            +
                  expect {
         | 
| 46 | 
            +
                    @client << set_stream_id(f.generate(PUSH_PROMISE), 0)
         | 
| 47 | 
            +
                  }.to raise_error(ProtocolError)
         | 
| 48 | 
            +
                end
         | 
| 49 | 
            +
             | 
| 50 | 
            +
                it "should raise error on PUSH_PROMISE against bogus stream" do
         | 
| 51 | 
            +
                  expect {
         | 
| 52 | 
            +
                    @client << set_stream_id(f.generate(PUSH_PROMISE), 31415)
         | 
| 53 | 
            +
                  }.to raise_error(ProtocolError)
         | 
| 54 | 
            +
                end
         | 
| 55 | 
            +
             | 
| 56 | 
            +
                it "should raise error on PUSH_PROMISE against non-idle stream" do
         | 
| 57 | 
            +
                  expect {
         | 
| 58 | 
            +
                    s = @client.new_stream
         | 
| 59 | 
            +
                    s.send HEADERS
         | 
| 60 | 
            +
             | 
| 61 | 
            +
                    @client << set_stream_id(f.generate(PUSH_PROMISE), s.id)
         | 
| 62 | 
            +
                    @client << set_stream_id(f.generate(PUSH_PROMISE), s.id)
         | 
| 63 | 
            +
                 }.to raise_error(ProtocolError)
         | 
| 64 | 
            +
                end
         | 
| 65 | 
            +
             | 
| 66 | 
            +
                it "should emit stream object for received PUSH_PROMISE" do
         | 
| 67 | 
            +
                  s = @client.new_stream
         | 
| 68 | 
            +
                  s.send HEADERS
         | 
| 69 | 
            +
             | 
| 70 | 
            +
                  promise = nil
         | 
| 71 | 
            +
                  @client.on(:promise) {|s| promise = s }
         | 
| 72 | 
            +
                  @client << set_stream_id(f.generate(PUSH_PROMISE), s.id)
         | 
| 73 | 
            +
             | 
| 74 | 
            +
                  promise.id.should eq 2
         | 
| 75 | 
            +
                  promise.state.should eq :reserved_remote
         | 
| 76 | 
            +
                end
         | 
| 77 | 
            +
             | 
| 78 | 
            +
                it "should auto RST_STREAM promises against locally-RST stream" do
         | 
| 79 | 
            +
                  s = @client.new_stream
         | 
| 80 | 
            +
                  s.send HEADERS
         | 
| 81 | 
            +
                  s.close
         | 
| 82 | 
            +
             | 
| 83 | 
            +
                  @client.stub(:send)
         | 
| 84 | 
            +
                  @client.should_receive(:send) do |frame|
         | 
| 85 | 
            +
                    frame[:type].should eq :rst_stream
         | 
| 86 | 
            +
                    frame[:stream].should eq 2
         | 
| 87 | 
            +
                  end
         | 
| 88 | 
            +
             | 
| 89 | 
            +
                  @client << set_stream_id(f.generate(PUSH_PROMISE), s.id)
         | 
| 90 | 
            +
                end
         | 
| 91 | 
            +
              end
         | 
| 92 | 
            +
             | 
| 93 | 
            +
            end
         | 
    
        data/spec/compressor_spec.rb
    CHANGED
    
    | @@ -10,25 +10,25 @@ describe HTTP2::Header do | |
| 10 10 | 
             
                  it "should encode 10 using a 5-bit prefix" do
         | 
| 11 11 | 
             
                    buf = c.integer(10, 5)
         | 
| 12 12 | 
             
                    buf.should eq [10].pack('C')
         | 
| 13 | 
            -
                    d.integer( | 
| 13 | 
            +
                    d.integer(Buffer.new(buf), 5).should eq 10
         | 
| 14 14 | 
             
                  end
         | 
| 15 15 |  | 
| 16 16 | 
             
                  it "should encode 10 using a 0-bit prefix" do
         | 
| 17 17 | 
             
                    buf = c.integer(10, 0)
         | 
| 18 18 | 
             
                    buf.should eq [10].pack('C')
         | 
| 19 | 
            -
                    d.integer( | 
| 19 | 
            +
                    d.integer(Buffer.new(buf), 0).should eq 10
         | 
| 20 20 | 
             
                  end
         | 
| 21 21 |  | 
| 22 22 | 
             
                  it "should encode 1337 using a 5-bit prefix" do
         | 
| 23 23 | 
             
                    buf = c.integer(1337, 5)
         | 
| 24 24 | 
             
                    buf.should eq [31,128+26,10].pack('C*')
         | 
| 25 | 
            -
                    d.integer( | 
| 25 | 
            +
                    d.integer(Buffer.new(buf), 5).should eq 1337
         | 
| 26 26 | 
             
                  end
         | 
| 27 27 |  | 
| 28 28 | 
             
                  it "should encode 1337 using a 0-bit prefix" do
         | 
| 29 29 | 
             
                    buf = c.integer(1337,0)
         | 
| 30 30 | 
             
                    buf.should eq [128+57,10].pack('C*')
         | 
| 31 | 
            -
                    d.integer( | 
| 31 | 
            +
                    d.integer(Buffer.new(buf), 0).should eq 1337
         | 
| 32 32 | 
             
                  end
         | 
| 33 33 | 
             
                end
         | 
| 34 34 |  | 
| @@ -37,7 +37,7 @@ describe HTTP2::Header do | |
| 37 37 | 
             
                    ascii = "abcdefghij"
         | 
| 38 38 | 
             
                    str = c.string(ascii)
         | 
| 39 39 |  | 
| 40 | 
            -
                    buf =  | 
| 40 | 
            +
                    buf = Buffer.new(str+"trailer")
         | 
| 41 41 | 
             
                    d.string(buf).should eq ascii
         | 
| 42 42 | 
             
                  end
         | 
| 43 43 |  | 
| @@ -45,7 +45,7 @@ describe HTTP2::Header do | |
| 45 45 | 
             
                    utf8 = "éáűőúöüó€"
         | 
| 46 46 | 
             
                    str = c.string(utf8)
         | 
| 47 47 |  | 
| 48 | 
            -
                    buf =  | 
| 48 | 
            +
                    buf = Buffer.new(str+"trailer")
         | 
| 49 49 | 
             
                    d.string(buf).should eq utf8
         | 
| 50 50 | 
             
                  end
         | 
| 51 51 |  | 
| @@ -53,7 +53,7 @@ describe HTTP2::Header do | |
| 53 53 | 
             
                    utf8 = "éáűőúöüó€"*100
         | 
| 54 54 | 
             
                    str = c.string(utf8)
         | 
| 55 55 |  | 
| 56 | 
            -
                    buf =  | 
| 56 | 
            +
                    buf = Buffer.new(str+"trailer")
         | 
| 57 57 | 
             
                    d.string(buf).should eq utf8
         | 
| 58 58 | 
             
                  end
         | 
| 59 59 | 
             
                end
         | 
| @@ -62,120 +62,105 @@ describe HTTP2::Header do | |
| 62 62 | 
             
              context "header representation" do
         | 
| 63 63 | 
             
                it "should handle indexed representation" do
         | 
| 64 64 | 
             
                  h = {name: 10, type: :indexed}
         | 
| 65 | 
            -
             | 
| 66 | 
            -
                  indexed = StringIO.new(c.header(h))
         | 
| 67 | 
            -
                  d.header(indexed).should eq h
         | 
| 65 | 
            +
                  d.header(c.header(h)).should eq h
         | 
| 68 66 | 
             
                end
         | 
| 69 67 |  | 
| 70 68 | 
             
                context "literal w/o indexing representation" do
         | 
| 71 69 | 
             
                  it "should handle indexed header" do
         | 
| 72 70 | 
             
                    h = {name: 10, value: "my-value", type: :noindex}
         | 
| 73 | 
            -
             | 
| 74 | 
            -
                    literal = StringIO.new(c.header(h))
         | 
| 75 | 
            -
                    d.header(literal).should eq h
         | 
| 71 | 
            +
                    d.header(c.header(h)).should eq h
         | 
| 76 72 | 
             
                  end
         | 
| 77 73 |  | 
| 78 74 | 
             
                  it "should handle literal header" do
         | 
| 79 75 | 
             
                    h = {name: "x-custom", value: "my-value", type: :noindex}
         | 
| 80 | 
            -
             | 
| 81 | 
            -
                    literal = StringIO.new(c.header(h))
         | 
| 82 | 
            -
                    d.header(literal).should eq h
         | 
| 76 | 
            +
                    d.header(c.header(h)).should eq h
         | 
| 83 77 | 
             
                  end
         | 
| 84 78 | 
             
                end
         | 
| 85 79 |  | 
| 86 80 | 
             
                context "literal w/ incremental indexing" do
         | 
| 87 81 | 
             
                  it "should handle indexed header" do
         | 
| 88 82 | 
             
                    h = {name: 10, value: "my-value", type: :incremental}
         | 
| 89 | 
            -
             | 
| 90 | 
            -
                    literal = StringIO.new(c.header(h))
         | 
| 91 | 
            -
                    d.header(literal).should eq h
         | 
| 83 | 
            +
                    d.header(c.header(h)).should eq h
         | 
| 92 84 | 
             
                  end
         | 
| 93 85 |  | 
| 94 86 | 
             
                  it "should handle literal header" do
         | 
| 95 87 | 
             
                    h = {name: "x-custom", value: "my-value", type: :incremental}
         | 
| 96 | 
            -
             | 
| 97 | 
            -
                    literal = StringIO.new(c.header(h))
         | 
| 98 | 
            -
                    d.header(literal).should eq h
         | 
| 88 | 
            +
                    d.header(c.header(h)).should eq h
         | 
| 99 89 | 
             
                  end
         | 
| 100 90 | 
             
                end
         | 
| 101 91 |  | 
| 102 92 | 
             
                context "literal w/ substitution indexing" do
         | 
| 103 93 | 
             
                  it "should handle indexed header" do
         | 
| 104 94 | 
             
                    h = {name: 1, value: "my-value", index: 10, type: :substitution}
         | 
| 105 | 
            -
             | 
| 106 | 
            -
                    literal = StringIO.new(c.header(h))
         | 
| 107 | 
            -
                    d.header(literal).should eq h
         | 
| 95 | 
            +
                    d.header(c.header(h)).should eq h
         | 
| 108 96 | 
             
                  end
         | 
| 109 97 |  | 
| 110 98 | 
             
                  it "should handle literal header" do
         | 
| 111 99 | 
             
                    h = {name: "x-new", value: "my-value", index: 10, type: :substitution}
         | 
| 112 | 
            -
             | 
| 113 | 
            -
                    literal = StringIO.new(c.header(h))
         | 
| 114 | 
            -
                    d.header(literal).should eq h
         | 
| 100 | 
            +
                    d.header(c.header(h)).should eq h
         | 
| 115 101 | 
             
                  end
         | 
| 116 102 | 
             
                end
         | 
| 117 103 | 
             
              end
         | 
| 118 104 |  | 
| 119 105 | 
             
              context "differential coding" do
         | 
| 120 106 | 
             
                context "shared compression context" do
         | 
| 121 | 
            -
                  before(:each) { @cc =  | 
| 107 | 
            +
                  before(:each) { @cc = EncodingContext.new(:request) }
         | 
| 122 108 |  | 
| 123 109 | 
             
                  it "should be initialized with pre-defined headers" do
         | 
| 124 | 
            -
                    cc =  | 
| 125 | 
            -
                    cc.table.size.should eq  | 
| 110 | 
            +
                    cc = EncodingContext.new(:request)
         | 
| 111 | 
            +
                    cc.table.size.should eq 30
         | 
| 126 112 |  | 
| 127 | 
            -
                    cc =  | 
| 128 | 
            -
                    cc.table.size.should eq  | 
| 113 | 
            +
                    cc = EncodingContext.new(:response)
         | 
| 114 | 
            +
                    cc.table.size.should eq 30
         | 
| 129 115 | 
             
                  end
         | 
| 130 116 |  | 
| 131 117 | 
             
                  it "should be initialized with empty working set" do
         | 
| 132 | 
            -
                    @cc. | 
| 118 | 
            +
                    @cc.refset.should be_empty
         | 
| 133 119 | 
             
                  end
         | 
| 134 120 |  | 
| 135 121 | 
             
                  it "should update working set based on prior state" do
         | 
| 136 | 
            -
                    @cc. | 
| 137 | 
            -
                    @cc.workset.should be_empty
         | 
| 122 | 
            +
                    @cc.refset.should be_empty
         | 
| 138 123 |  | 
| 139 124 | 
             
                    @cc.process({name: 0, type: :indexed})
         | 
| 140 | 
            -
                    @cc. | 
| 141 | 
            -
                    @cc.workset.should eq [[0, [":scheme", "http"]]]
         | 
| 125 | 
            +
                    @cc.refset.should eq [[0, [":scheme", "http"]]]
         | 
| 142 126 |  | 
| 143 127 | 
             
                    @cc.process({name: 0, type: :indexed})
         | 
| 144 | 
            -
                    @cc. | 
| 145 | 
            -
                    @cc.workset.should be_empty
         | 
| 128 | 
            +
                    @cc.refset.should be_empty
         | 
| 146 129 | 
             
                  end
         | 
| 147 130 |  | 
| 148 131 | 
             
                  context "processing" do
         | 
| 149 132 | 
             
                    it "should toggle index representation headers in working set" do
         | 
| 150 133 | 
             
                      @cc.process({name: 0, type: :indexed})
         | 
| 151 | 
            -
                      @cc. | 
| 134 | 
            +
                      @cc.refset.first.should eq [0, [":scheme", "http"]]
         | 
| 152 135 |  | 
| 153 136 | 
             
                      @cc.process({name: 0, type: :indexed})
         | 
| 154 | 
            -
                      @cc. | 
| 137 | 
            +
                      @cc.refset.should be_empty
         | 
| 155 138 | 
             
                    end
         | 
| 156 139 |  | 
| 157 140 | 
             
                    context "no indexing" do
         | 
| 158 141 | 
             
                      it "should process indexed header with literal value" do
         | 
| 159 142 | 
             
                        original_table = @cc.table
         | 
| 160 143 |  | 
| 161 | 
            -
                        @cc.process({name: 3, value: "/path", type: :noindex})
         | 
| 162 | 
            -
                         | 
| 144 | 
            +
                        emit = @cc.process({name: 3, value: "/path", type: :noindex})
         | 
| 145 | 
            +
                        emit.should eq [":path", "/path"]
         | 
| 146 | 
            +
                        @cc.refset.should be_empty
         | 
| 163 147 | 
             
                        @cc.table.should eq original_table
         | 
| 164 148 | 
             
                      end
         | 
| 165 149 |  | 
| 166 150 | 
             
                      it "should process indexed header with default value" do
         | 
| 167 151 | 
             
                        original_table = @cc.table
         | 
| 168 152 |  | 
| 169 | 
            -
                        @cc.process({name: 3, type: :noindex})
         | 
| 170 | 
            -
                         | 
| 153 | 
            +
                        emit = @cc.process({name: 3, type: :noindex})
         | 
| 154 | 
            +
                        emit.should eq [":path", "/"]
         | 
| 171 155 | 
             
                        @cc.table.should eq original_table
         | 
| 172 156 | 
             
                      end
         | 
| 173 157 |  | 
| 174 158 | 
             
                      it "should process literal header with literal value" do
         | 
| 175 159 | 
             
                        original_table = @cc.table
         | 
| 176 160 |  | 
| 177 | 
            -
                        @cc.process({name: "x-custom", value: "random", type: :noindex})
         | 
| 178 | 
            -
                         | 
| 161 | 
            +
                        emit = @cc.process({name: "x-custom", value: "random", type: :noindex})
         | 
| 162 | 
            +
                        emit.should eq ["x-custom", "random"]
         | 
| 163 | 
            +
                        @cc.refset.should be_empty
         | 
| 179 164 | 
             
                        @cc.table.should eq original_table
         | 
| 180 165 | 
             
                      end
         | 
| 181 166 | 
             
                    end
         | 
| @@ -185,7 +170,7 @@ describe HTTP2::Header do | |
| 185 170 | 
             
                        original_table = @cc.table.dup
         | 
| 186 171 |  | 
| 187 172 | 
             
                        @cc.process({name: "x-custom", value: "random", type: :incremental})
         | 
| 188 | 
            -
                        @cc. | 
| 173 | 
            +
                        @cc.refset.first.should eq [original_table.size, ["x-custom", "random"]]
         | 
| 189 174 | 
             
                        (@cc.table - original_table).should eq [["x-custom", "random"]]
         | 
| 190 175 | 
             
                      end
         | 
| 191 176 | 
             
                    end
         | 
| @@ -200,9 +185,9 @@ describe HTTP2::Header do | |
| 200 185 | 
             
                          index: idx, type: :substitution
         | 
| 201 186 | 
             
                        })
         | 
| 202 187 |  | 
| 203 | 
            -
                        @cc. | 
| 188 | 
            +
                        @cc.refset.first.should eq [idx, ["x-custom", "random"]]
         | 
| 204 189 | 
             
                        (@cc.table - original_table).should eq [["x-custom", "random"]]
         | 
| 205 | 
            -
                        (original_table - @cc.table).should eq [[" | 
| 190 | 
            +
                        (original_table - @cc.table).should eq [["via", ""]]
         | 
| 206 191 | 
             
                      end
         | 
| 207 192 |  | 
| 208 193 | 
             
                      it "should raise error on invalid substitution index" do
         | 
| @@ -217,7 +202,7 @@ describe HTTP2::Header do | |
| 217 202 |  | 
| 218 203 | 
             
                    context "size bounds" do
         | 
| 219 204 | 
             
                      it "should drop headers from beginning of table" do
         | 
| 220 | 
            -
                        cc =  | 
| 205 | 
            +
                        cc = EncodingContext.new(:request, 2048)
         | 
| 221 206 | 
             
                        original_table = cc.table.dup
         | 
| 222 207 | 
             
                        original_size = original_table.join.bytesize +
         | 
| 223 208 | 
             
                                        original_table.size * 32
         | 
| @@ -233,7 +218,7 @@ describe HTTP2::Header do | |
| 233 218 | 
             
                       end
         | 
| 234 219 |  | 
| 235 220 | 
             
                      it "should prepend on dropped substitution index" do
         | 
| 236 | 
            -
                        cc =  | 
| 221 | 
            +
                        cc = EncodingContext.new(:request, 2048)
         | 
| 237 222 | 
             
                        original_table = cc.table.dup
         | 
| 238 223 | 
             
                        original_size = original_table.join.bytesize +
         | 
| 239 224 | 
             
                                        original_table.size * 32
         | 
| @@ -248,39 +233,54 @@ describe HTTP2::Header do | |
| 248 233 | 
             
                        cc.table[1][0].should eq ":scheme"
         | 
| 249 234 | 
             
                      end
         | 
| 250 235 | 
             
                    end
         | 
| 236 | 
            +
             | 
| 237 | 
            +
                    it "should clear table if entry exceeds table size" do
         | 
| 238 | 
            +
                      cc = EncodingContext.new(:request, 2048)
         | 
| 239 | 
            +
             | 
| 240 | 
            +
                      h = { name: "x-custom", value: "a", index: 0, type: :incremental }
         | 
| 241 | 
            +
                      e = { name: "large", value: "a" * 2048, index: 0}
         | 
| 242 | 
            +
             | 
| 243 | 
            +
                      cc.process(h)
         | 
| 244 | 
            +
                      cc.process(e.merge({type: :substitution}))
         | 
| 245 | 
            +
                      cc.table.should be_empty
         | 
| 246 | 
            +
             | 
| 247 | 
            +
                      cc.process(h)
         | 
| 248 | 
            +
                      cc.process(e.merge({type: :incremental}))
         | 
| 249 | 
            +
                      cc.table.should be_empty
         | 
| 250 | 
            +
                    end
         | 
| 251 251 | 
             
                  end
         | 
| 252 252 | 
             
                end
         | 
| 253 253 |  | 
| 254 254 | 
             
                context "integration" do
         | 
| 255 | 
            -
                  before (:all) { @cc =  | 
| 255 | 
            +
                  before (:all) { @cc = EncodingContext.new(:request) }
         | 
| 256 256 |  | 
| 257 257 | 
             
                  it "should match first header set in spec appendix" do
         | 
| 258 258 | 
             
                    req_headers = [
         | 
| 259 259 | 
             
                      {name: 3, value: "/my-example/index.html"},
         | 
| 260 | 
            -
                      {name:  | 
| 261 | 
            -
                      {name: " | 
| 260 | 
            +
                      {name: 11, value: "my-user-agent"},
         | 
| 261 | 
            +
                      {name: "mynewheader", value: "first"}
         | 
| 262 262 | 
             
                    ]
         | 
| 263 263 |  | 
| 264 264 | 
             
                    req_headers.each {|h| @cc.process(h.merge({type: :incremental})) }
         | 
| 265 265 |  | 
| 266 | 
            -
                    @cc.table[ | 
| 267 | 
            -
                    @cc.table[ | 
| 268 | 
            -
                    @cc.table[ | 
| 266 | 
            +
                    @cc.table[30].should eq [":path", "/my-example/index.html"]
         | 
| 267 | 
            +
                    @cc.table[31].should eq ["user-agent", "my-user-agent"]
         | 
| 268 | 
            +
                    @cc.table[32].should eq req_headers[2].values
         | 
| 269 269 | 
             
                  end
         | 
| 270 270 |  | 
| 271 271 | 
             
                  it "should match second header set in spec appendix" do
         | 
| 272 | 
            -
                    @cc.process({name:  | 
| 273 | 
            -
                    @cc.process({name:  | 
| 272 | 
            +
                    @cc.process({name: 30, type: :indexed})
         | 
| 273 | 
            +
                    @cc.process({name: 31, type: :indexed})
         | 
| 274 274 | 
             
                    @cc.process({
         | 
| 275 275 | 
             
                      name: 3, value: "/my-example/resources/script.js",
         | 
| 276 | 
            -
                      index:  | 
| 276 | 
            +
                      index: 30, type: :substitution
         | 
| 277 277 | 
             
                    })
         | 
| 278 | 
            -
                    @cc.process({name:  | 
| 278 | 
            +
                    @cc.process({name: 32, value: "second", type: :incremental})
         | 
| 279 279 |  | 
| 280 | 
            -
                    @cc.table[ | 
| 281 | 
            -
                    @cc.table[ | 
| 282 | 
            -
                    @cc.table[ | 
| 283 | 
            -
                    @cc.table[ | 
| 280 | 
            +
                    @cc.table[30].should eq [":path", "/my-example/resources/script.js"]
         | 
| 281 | 
            +
                    @cc.table[31].should eq ["user-agent", "my-user-agent"]
         | 
| 282 | 
            +
                    @cc.table[32].should eq ["mynewheader", "first"]
         | 
| 283 | 
            +
                    @cc.table[33].should eq ["mynewheader", "second"]
         | 
| 284 284 | 
             
                  end
         | 
| 285 285 | 
             
                end
         | 
| 286 286 | 
             
              end
         | 
| @@ -297,12 +297,12 @@ describe HTTP2::Header do | |
| 297 297 | 
             
                  0x44, # (literal header with incremental indexing, name index = 3)
         | 
| 298 298 | 
             
                  0x16, # (header value string length = 22)
         | 
| 299 299 | 
             
                  "/my-example/index.html".bytes,
         | 
| 300 | 
            -
                   | 
| 300 | 
            +
                  0x4C, # (literal header with incremental indexing, name index = 11)
         | 
| 301 301 | 
             
                  0x0D, # (header value string length = 13)
         | 
| 302 302 | 
             
                  "my-user-agent".bytes,
         | 
| 303 303 | 
             
                  0x40, # (literal header with incremental indexing, new name)
         | 
| 304 304 | 
             
                  0x0B, # (header name string length = 11)
         | 
| 305 | 
            -
                  " | 
| 305 | 
            +
                  "mynewheader".bytes,
         | 
| 306 306 | 
             
                  0x05, # (header value string length = 5)
         | 
| 307 307 | 
             
                  "first".bytes
         | 
| 308 308 | 
             
                ].flatten
         | 
| @@ -310,7 +310,7 @@ describe HTTP2::Header do | |
| 310 310 | 
             
                E1_HEADERS = [
         | 
| 311 311 | 
             
                  [":path", "/my-example/index.html"],
         | 
| 312 312 | 
             
                  ["user-agent", "my-user-agent"],
         | 
| 313 | 
            -
                  [" | 
| 313 | 
            +
                  ["mynewheader", "first"]
         | 
| 314 314 | 
             
                ]
         | 
| 315 315 |  | 
| 316 316 | 
             
                it "should match first header set in spec appendix" do
         | 
| @@ -318,18 +318,18 @@ describe HTTP2::Header do | |
| 318 318 | 
             
                end
         | 
| 319 319 |  | 
| 320 320 | 
             
                it "should decode first header set in spec appendix" do
         | 
| 321 | 
            -
                  @dc.decode( | 
| 321 | 
            +
                  @dc.decode(Buffer.new(E1_BYTES.pack("C*"))).should eq E1_HEADERS
         | 
| 322 322 | 
             
                end
         | 
| 323 323 |  | 
| 324 324 | 
             
                E2_BYTES = [
         | 
| 325 | 
            -
                   | 
| 326 | 
            -
                   | 
| 325 | 
            +
                  0x9e, # (indexed header, index = 30: removal from reference set)
         | 
| 326 | 
            +
                  0xa0, # (indexed header, index = 32: removal from reference set)
         | 
| 327 327 | 
             
                  0x04, # (literal header, substitution indexing, name index = 3)
         | 
| 328 | 
            -
                   | 
| 328 | 
            +
                  0x1e, # (replaced entry index = 30)
         | 
| 329 329 | 
             
                  0x1f, # (header value string length = 31)
         | 
| 330 330 | 
             
                  "/my-example/resources/script.js".bytes,
         | 
| 331 331 | 
             
                  0x5f,
         | 
| 332 | 
            -
                   | 
| 332 | 
            +
                  0x02, # (literal header, incremental indexing, name index = 32)
         | 
| 333 333 | 
             
                  0x06, # (header value string length = 6)
         | 
| 334 334 | 
             
                  "second".bytes
         | 
| 335 335 | 
             
                ].flatten
         | 
| @@ -337,7 +337,7 @@ describe HTTP2::Header do | |
| 337 337 | 
             
                E2_HEADERS = [
         | 
| 338 338 | 
             
                  [":path", "/my-example/resources/script.js"],
         | 
| 339 339 | 
             
                  ["user-agent", "my-user-agent"],
         | 
| 340 | 
            -
                  [" | 
| 340 | 
            +
                  ["mynewheader", "second"]
         | 
| 341 341 | 
             
                ]
         | 
| 342 342 |  | 
| 343 343 | 
             
                it "should match second header set in spec appendix" do
         | 
| @@ -356,15 +356,15 @@ describe HTTP2::Header do | |
| 356 356 | 
             
                end
         | 
| 357 357 |  | 
| 358 358 | 
             
                it "should decode second header set in spec appendix" do
         | 
| 359 | 
            -
                  @dc.decode( | 
| 359 | 
            +
                  @dc.decode(Buffer.new(E2_BYTES.pack("C*"))).should match_array E2_HEADERS
         | 
| 360 360 | 
             
                end
         | 
| 361 361 |  | 
| 362 362 | 
             
                it "encode-decode should be invariant" do
         | 
| 363 363 | 
             
                  cc = Compressor.new(:request)
         | 
| 364 364 | 
             
                  dc = Decompressor.new(:request)
         | 
| 365 365 |  | 
| 366 | 
            -
                  E1_HEADERS.should match_array dc.decode( | 
| 367 | 
            -
                  E2_HEADERS.should match_array dc.decode( | 
| 366 | 
            +
                  E1_HEADERS.should match_array dc.decode(cc.encode(E1_HEADERS))
         | 
| 367 | 
            +
                  E2_HEADERS.should match_array dc.decode(cc.encode(E2_HEADERS))
         | 
| 368 368 | 
             
                end
         | 
| 369 369 |  | 
| 370 370 | 
             
                it "should encode-decode request set of headers" do
         | 
| @@ -378,7 +378,16 @@ describe HTTP2::Header do | |
| 378 378 | 
             
                    ["accept", "*/*"]
         | 
| 379 379 | 
             
                  ]
         | 
| 380 380 |  | 
| 381 | 
            -
                  dc.decode( | 
| 381 | 
            +
                  dc.decode(cc.encode(req)).should eq req
         | 
| 382 | 
            +
                end
         | 
| 383 | 
            +
             | 
| 384 | 
            +
                it "should downcase all request header names" do
         | 
| 385 | 
            +
                  cc = Compressor.new(:request)
         | 
| 386 | 
            +
                  dc = Decompressor.new(:request)
         | 
| 387 | 
            +
             | 
| 388 | 
            +
                  req = [["Accept", "IMAGE/PNG"]]
         | 
| 389 | 
            +
                  recv = dc.decode(cc.encode(req))
         | 
| 390 | 
            +
                  recv.should eq [["accept", "IMAGE/PNG"]]
         | 
| 382 391 | 
             
                end
         | 
| 383 392 | 
             
              end
         | 
| 384 393 | 
             
            end
         |