http-protocol 0.1.0
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 +7 -0
- data/.gitignore +11 -0
- data/.rspec +3 -0
- data/.travis.yml +23 -0
- data/Gemfile +11 -0
- data/README.md +64 -0
- data/Rakefile +6 -0
- data/http-protocol.gemspec +24 -0
- data/lib/http/protocol.rb +21 -0
- data/lib/http/protocol/error.rb +80 -0
- data/lib/http/protocol/headers.rb +203 -0
- data/lib/http/protocol/http2/client.rb +49 -0
- data/lib/http/protocol/http2/connection.rb +309 -0
- data/lib/http/protocol/http2/continuation_frame.rb +86 -0
- data/lib/http/protocol/http2/data_frame.rb +64 -0
- data/lib/http/protocol/http2/flow_control.rb +85 -0
- data/lib/http/protocol/http2/frame.rb +187 -0
- data/lib/http/protocol/http2/framer.rb +105 -0
- data/lib/http/protocol/http2/goaway_frame.rb +62 -0
- data/lib/http/protocol/http2/headers_frame.rb +95 -0
- data/lib/http/protocol/http2/padded.rb +91 -0
- data/lib/http/protocol/http2/ping_frame.rb +82 -0
- data/lib/http/protocol/http2/priority_frame.rb +82 -0
- data/lib/http/protocol/http2/push_promise_frame.rb +67 -0
- data/lib/http/protocol/http2/reset_stream_frame.rb +74 -0
- data/lib/http/protocol/http2/server.rb +47 -0
- data/lib/http/protocol/http2/settings_frame.rb +242 -0
- data/lib/http/protocol/http2/stream.rb +313 -0
- data/lib/http/protocol/http2/window_update_frame.rb +89 -0
- data/lib/http/protocol/version.rb +25 -0
- metadata +128 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: a248a6b8419639fb567474da1539e38772b452b706d21ef12c52c02adb60352d
|
4
|
+
data.tar.gz: 302d1563c4c6516d88d2487ef63eeac1fa9277739194d8bf60716b0a40e52f9b
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: a69be0f9448ab54d044326705a3fe70b7b37fc673aef27d04e4c8ac3d2669374a1e4ad25a3385ef227ce2884c7ce80a27bf2b86f140735479e1df48b2cb3bbb9
|
7
|
+
data.tar.gz: 7d09ffea378ea59cd1e9465669226f6e8b40f1e8e16530a74ec77050aeb159cecf60f4dd9d65c36b059b5405a7cc7c12850e14c5dc428169a542914e059a4203
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
language: ruby
|
2
|
+
sudo: required
|
3
|
+
dist: xenial
|
4
|
+
cache: bundler
|
5
|
+
|
6
|
+
before_script:
|
7
|
+
- gem update --system
|
8
|
+
- gem install bundler
|
9
|
+
|
10
|
+
matrix:
|
11
|
+
include:
|
12
|
+
- rvm: 2.3
|
13
|
+
- rvm: 2.4
|
14
|
+
- rvm: 2.5
|
15
|
+
- rvm: 2.6
|
16
|
+
- rvm: jruby-head
|
17
|
+
env: JRUBY_OPTS="--debug -X+O"
|
18
|
+
- rvm: ruby-head
|
19
|
+
- rvm: rbx-3
|
20
|
+
allow_failures:
|
21
|
+
- rvm: ruby-head
|
22
|
+
- rvm: jruby-head
|
23
|
+
- rvm: rbx-3
|
data/Gemfile
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
source "https://rubygems.org"
|
2
|
+
|
3
|
+
git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
|
4
|
+
|
5
|
+
# Specify your gem's dependencies in http-protocol.gemspec
|
6
|
+
gemspec
|
7
|
+
|
8
|
+
group :test do
|
9
|
+
gem 'pry'
|
10
|
+
gem 'covered', require: 'covered/rspec' if RUBY_VERSION >= "2.6.0"
|
11
|
+
end
|
data/README.md
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
# HTTP::Protocol
|
2
|
+
|
3
|
+
Provides abstractions for working with the HTTP protocol with a focus on on HTTP/2.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
gem 'http-protocol'
|
11
|
+
```
|
12
|
+
|
13
|
+
And then execute:
|
14
|
+
|
15
|
+
$ bundle
|
16
|
+
|
17
|
+
Or install it yourself as:
|
18
|
+
|
19
|
+
$ gem install http-protocol
|
20
|
+
|
21
|
+
## Usage
|
22
|
+
|
23
|
+
### HTTP2
|
24
|
+
|
25
|
+
```ruby
|
26
|
+
framer = HTTP::Protocol::HTTP2::Framer.new(io)
|
27
|
+
|
28
|
+
frame = framer.read_frame
|
29
|
+
|
30
|
+
frame.write(io)
|
31
|
+
```
|
32
|
+
|
33
|
+
## Contributing
|
34
|
+
|
35
|
+
1. Fork it
|
36
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
37
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
38
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
39
|
+
5. Create new Pull Request
|
40
|
+
|
41
|
+
## License
|
42
|
+
|
43
|
+
Released under the MIT license.
|
44
|
+
|
45
|
+
Copyright, 2018, by [Samuel G. D. Williams](http://www.codeotaku.com/samuel-williams).
|
46
|
+
Copyright, 2013, by Ilya Grigorik.
|
47
|
+
|
48
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
49
|
+
of this software and associated documentation files (the "Software"), to deal
|
50
|
+
in the Software without restriction, including without limitation the rights
|
51
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
52
|
+
copies of the Software, and to permit persons to whom the Software is
|
53
|
+
furnished to do so, subject to the following conditions:
|
54
|
+
|
55
|
+
The above copyright notice and this permission notice shall be included in
|
56
|
+
all copies or substantial portions of the Software.
|
57
|
+
|
58
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
59
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
60
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
61
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
62
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
63
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
64
|
+
THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
|
2
|
+
require_relative "lib/http/protocol/version"
|
3
|
+
|
4
|
+
Gem::Specification.new do |spec|
|
5
|
+
spec.name = "http-protocol"
|
6
|
+
spec.version = HTTP::Protocol::VERSION
|
7
|
+
spec.authors = ["Samuel Williams"]
|
8
|
+
spec.email = ["samuel.williams@oriontransfer.co.nz"]
|
9
|
+
|
10
|
+
spec.summary = "Provides abstractions to handle HTTP1 and HTTP2 protocols."
|
11
|
+
spec.homepage = "https://github.com/socketry/http-protocol"
|
12
|
+
|
13
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
14
|
+
f.match(%r{^(test|spec|features)/})
|
15
|
+
end
|
16
|
+
|
17
|
+
spec.require_paths = ["lib"]
|
18
|
+
|
19
|
+
spec.add_dependency "http-hpack", "~> 0.1.0"
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.16"
|
22
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
23
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
24
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# Copyright, 2018, by Samuel G. D. Williams. <http://www.codeotaku.com>
|
2
|
+
#
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
# of this software and associated documentation files (the "Software"), to deal
|
5
|
+
# in the Software without restriction, including without limitation the rights
|
6
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
# copies of the Software, and to permit persons to whom the Software is
|
8
|
+
# furnished to do so, subject to the following conditions:
|
9
|
+
#
|
10
|
+
# The above copyright notice and this permission notice shall be included in
|
11
|
+
# all copies or substantial portions of the Software.
|
12
|
+
#
|
13
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
# THE SOFTWARE.
|
20
|
+
|
21
|
+
require "http/protocol/version"
|
@@ -0,0 +1,80 @@
|
|
1
|
+
# Copyright, 2018, by Samuel G. D. Williams. <http://www.codeotaku.com>
|
2
|
+
# Copyright, 2013, by Ilya Grigorik.
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
5
|
+
# of this software and associated documentation files (the "Software"), to deal
|
6
|
+
# in the Software without restriction, including without limitation the rights
|
7
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
8
|
+
# copies of the Software, and to permit persons to whom the Software is
|
9
|
+
# furnished to do so, subject to the following conditions:
|
10
|
+
#
|
11
|
+
# The above copyright notice and this permission notice shall be included in
|
12
|
+
# all copies or substantial portions of the Software.
|
13
|
+
#
|
14
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
15
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
16
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
17
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
18
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
19
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
20
|
+
# THE SOFTWARE.
|
21
|
+
|
22
|
+
module HTTP
|
23
|
+
module Protocol
|
24
|
+
class Error < StandardError
|
25
|
+
end
|
26
|
+
|
27
|
+
# Raised if connection header is missing or invalid indicating that
|
28
|
+
# this is an invalid HTTP 2.0 request - no frames are emitted and the
|
29
|
+
# connection must be aborted.
|
30
|
+
class HandshakeError < Error
|
31
|
+
end
|
32
|
+
|
33
|
+
# Raised by stream or connection handlers, results in GOAWAY frame
|
34
|
+
# which signals termination of the current connection. You *cannot*
|
35
|
+
# recover from this exception, or any exceptions subclassed from it.
|
36
|
+
class ProtocolError < Error
|
37
|
+
def initialize(message, code = nil)
|
38
|
+
super(message)
|
39
|
+
|
40
|
+
@code = code
|
41
|
+
end
|
42
|
+
|
43
|
+
attr :code
|
44
|
+
end
|
45
|
+
|
46
|
+
# When the frame payload does not match expectations.
|
47
|
+
#
|
48
|
+
# @see ProtocolError
|
49
|
+
class FrameSizeError < ProtocolError
|
50
|
+
end
|
51
|
+
|
52
|
+
# Raised on invalid flow control frame or command.
|
53
|
+
#
|
54
|
+
# @see ProtocolError
|
55
|
+
class FlowControlError < ProtocolError
|
56
|
+
end
|
57
|
+
|
58
|
+
# Raised on invalid stream processing: invalid frame type received or
|
59
|
+
# sent, or invalid command issued.
|
60
|
+
class InternalError < ProtocolError
|
61
|
+
end
|
62
|
+
|
63
|
+
#
|
64
|
+
# -- Recoverable errors -------------------------------------------------
|
65
|
+
#
|
66
|
+
|
67
|
+
# Raised if stream has been closed and new frames cannot be sent.
|
68
|
+
class StreamClosed < Error
|
69
|
+
end
|
70
|
+
|
71
|
+
# Raised if connection has been closed (or draining) and new stream
|
72
|
+
# cannot be opened.
|
73
|
+
class ConnectionClosed < Error
|
74
|
+
end
|
75
|
+
|
76
|
+
# Raised if stream limit has been reached and new stream cannot be opened.
|
77
|
+
class StreamLimitExceeded < Error
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,203 @@
|
|
1
|
+
# Copyright, 2017, by Samuel G. D. Williams. <http://www.codeotaku.com>
|
2
|
+
#
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
# of this software and associated documentation files (the "Software"), to deal
|
5
|
+
# in the Software without restriction, including without limitation the rights
|
6
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
# copies of the Software, and to permit persons to whom the Software is
|
8
|
+
# furnished to do so, subject to the following conditions:
|
9
|
+
#
|
10
|
+
# The above copyright notice and this permission notice shall be included in
|
11
|
+
# all copies or substantial portions of the Software.
|
12
|
+
#
|
13
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
# THE SOFTWARE.
|
20
|
+
|
21
|
+
module HTTP
|
22
|
+
module Protocol
|
23
|
+
class Headers
|
24
|
+
class Split < Array
|
25
|
+
COMMA = /\s*,\s*/
|
26
|
+
|
27
|
+
def initialize(value)
|
28
|
+
super(value.split(COMMA))
|
29
|
+
end
|
30
|
+
|
31
|
+
def << value
|
32
|
+
super value.split(COMMA)
|
33
|
+
end
|
34
|
+
|
35
|
+
def to_s
|
36
|
+
join(", ")
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
class Multiple < Array
|
41
|
+
def initialize(value)
|
42
|
+
super()
|
43
|
+
|
44
|
+
self << value
|
45
|
+
end
|
46
|
+
|
47
|
+
def to_s
|
48
|
+
join("\n")
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.[] hash
|
53
|
+
self.new(hash.to_a)
|
54
|
+
end
|
55
|
+
|
56
|
+
def initialize(fields = [])
|
57
|
+
@fields = fields
|
58
|
+
@indexed = to_h
|
59
|
+
end
|
60
|
+
|
61
|
+
attr :fields
|
62
|
+
|
63
|
+
def freeze
|
64
|
+
return if frozen?
|
65
|
+
|
66
|
+
@indexed = to_h
|
67
|
+
|
68
|
+
super
|
69
|
+
end
|
70
|
+
|
71
|
+
def empty?
|
72
|
+
@fields.empty?
|
73
|
+
end
|
74
|
+
|
75
|
+
def each(&block)
|
76
|
+
@fields.each(&block)
|
77
|
+
end
|
78
|
+
|
79
|
+
def include? key
|
80
|
+
self[key] != nil
|
81
|
+
end
|
82
|
+
|
83
|
+
# Delete all headers with the given key, and return the value of the last one, if any.
|
84
|
+
def delete(key)
|
85
|
+
values, @fields = @fields.partition do |field|
|
86
|
+
field.first.downcase == key
|
87
|
+
end
|
88
|
+
|
89
|
+
if @indexed
|
90
|
+
@indexed.delete(key)
|
91
|
+
end
|
92
|
+
|
93
|
+
if field = values.last
|
94
|
+
return field.last
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def slice!(keys)
|
99
|
+
values, @fields = @fields.partition do |field|
|
100
|
+
keys.include?(field.first.downcase)
|
101
|
+
end
|
102
|
+
|
103
|
+
if @indexed
|
104
|
+
keys.each do |key|
|
105
|
+
@indexed.delete(key)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def add(key, value)
|
111
|
+
self[key] = value
|
112
|
+
end
|
113
|
+
|
114
|
+
def []= key, value
|
115
|
+
@fields << [key, value]
|
116
|
+
|
117
|
+
if @indexed
|
118
|
+
# It would be good to do some kind of validation here.
|
119
|
+
merge(@indexed, key.downcase, value)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
MERGE_POLICY = {
|
124
|
+
# Headers which may only be specified once.
|
125
|
+
'content-type' => false,
|
126
|
+
'content-disposition' => false,
|
127
|
+
'content-length' => false,
|
128
|
+
'user-agent' => false,
|
129
|
+
'referer' => false,
|
130
|
+
'host' => false,
|
131
|
+
'authorization' => false,
|
132
|
+
'proxy-authorization' => false,
|
133
|
+
'if-modified-since' => false,
|
134
|
+
'if-unmodified-since' => false,
|
135
|
+
'from' => false,
|
136
|
+
'location' => false,
|
137
|
+
'max-forwards' => false,
|
138
|
+
|
139
|
+
'connection' => Split,
|
140
|
+
|
141
|
+
# Headers specifically for proxies:
|
142
|
+
'via' => Split,
|
143
|
+
'x-forwarded-for' => Split,
|
144
|
+
|
145
|
+
# Headers which may be specified multiple times, but which can't be concatenated.
|
146
|
+
'set-cookie' => Multiple,
|
147
|
+
'www-authenticate' => Multiple,
|
148
|
+
'proxy-authenticate' => Multiple
|
149
|
+
}.tap{|hash| hash.default = Split}
|
150
|
+
|
151
|
+
def merge(hash, key, value)
|
152
|
+
if policy = MERGE_POLICY[key]
|
153
|
+
if current_value = hash[key]
|
154
|
+
current_value << value
|
155
|
+
else
|
156
|
+
hash[key] = policy.new(value)
|
157
|
+
end
|
158
|
+
else
|
159
|
+
raise ArgumentError, "Header #{key} can only be set once!" if hash.include?(key)
|
160
|
+
|
161
|
+
# We can't merge these, we only expose the last one set.
|
162
|
+
hash[key] = value
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
def [] key
|
167
|
+
@indexed ||= to_h
|
168
|
+
|
169
|
+
@indexed[key]
|
170
|
+
end
|
171
|
+
|
172
|
+
def to_h
|
173
|
+
@fields.inject({}) do |hash, (key, value)|
|
174
|
+
merge(hash, key.downcase, value)
|
175
|
+
|
176
|
+
hash
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
def == other
|
181
|
+
if other.is_a? Hash
|
182
|
+
to_h == other
|
183
|
+
else
|
184
|
+
@fields == other.fields
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
class Merged
|
189
|
+
def initialize(*all)
|
190
|
+
@all = all
|
191
|
+
end
|
192
|
+
|
193
|
+
def each(&block)
|
194
|
+
@all.each do |headers|
|
195
|
+
headers.each do |key, value|
|
196
|
+
yield key, value.to_s
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|