h2 0.8.1 → 0.8.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2b78f8a3e3aeed1884179c684adaa31e16cb377bdaa123d0af10f4cb174ff86f
4
- data.tar.gz: 9beec3d11d01b4848118b17e1572275e231f580a48c62f0db28f52c059801c80
3
+ metadata.gz: 49b62827cc586f06022739232d3d7ea24a1b0744e1feb157ba24e0c65a0e9ba2
4
+ data.tar.gz: 90f546cf67e7502a0c64c6df96c3cf1b10c059efa1ab88e134cde4a34225e76e
5
5
  SHA512:
6
- metadata.gz: 0ce8d9218b3cf9fc848ba56563ccdb8760cf63e30c312825ada05652e30dbcc60405109785b29c9466effcc9a3713ba8c57ccdf73c4a2f2b7984772e9a5f8f5a
7
- data.tar.gz: ec1413ce8638d7aa6db8ec4e75f8032581e72dd391c5124e410ad3f9896feaf0f9580ae97895d846407d10e1b8a4a82ff381b57d14929d1d76240655ee55df53
6
+ metadata.gz: 2639436d0e0aa98250f097bca6e6250c9e6f30cf732f58bd8a18fa1accb81d026db7e34af9e2d98fc5cebc2487d503eb3b3c8f27f226c82a3f0356ac1bee40a2
7
+ data.tar.gz: 0aeae299e9d56b22962a344cf463ed9940bb93e58e82d00fdf2da052c768a508a53937136c71efc81ad82aa83e205e216726b1bf25d88cf5fe6cab9fbd7521e2
@@ -1,6 +1,11 @@
1
1
  h2 changelog
2
2
  ============
3
3
 
4
+ ### 0.8.2 22 aug 2018
5
+
6
+ * add gzip/deflate content-encoding support
7
+ * add SSE '-s' option and other CLI improvments
8
+
4
9
  ### 0.8.1 11 aug 2018
5
10
 
6
11
  * update rake / bundler dep versions
data/Gemfile CHANGED
@@ -1,7 +1,7 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
3
  gemspec
4
- # gem 'http-2', path: '../http-2'
4
+ gem 'http-2', path: '../http-2'
5
5
 
6
6
  group :concurrent_ruby do
7
7
  gem 'concurrent-ruby'
@@ -18,8 +18,9 @@ require File.expand_path '../../../test/support/create_certs', __FILE__
18
18
  # crank up the logger level for testing/example purposes
19
19
  #
20
20
  H2::Logger.level = ::Logger::DEBUG
21
+ H2::Server::Connection.include H2::FrameDebugger
21
22
 
22
- port = 1234
23
+ port = 4430#1234
23
24
  addr = Socket.getaddrinfo('localhost', port).first[3]
24
25
  puts "*** Starting server on https://localhost:#{port}"
25
26
 
@@ -69,6 +70,11 @@ sni = {
69
70
  :cert => certs_dir + '/example.com.crt',
70
71
  :key => certs_dir + '/example.com.key',
71
72
  :extra_chain_cert => certs_dir + '/example.com-chain.pem'
73
+ },
74
+ 'vux.nakamura.io' => {
75
+ cert: certs_dir + '/nakamuraio.crt',
76
+ key: certs_dir + '/nakamuraio.key',
77
+ extra_chain_cert: certs_dir + '/nakamuraio-chain.pem'
72
78
  }
73
79
  }
74
80
 
@@ -87,7 +93,7 @@ s = H2::Server::HTTPS.new host: addr, port: port, sni: sni do |connection|
87
93
  #
88
94
  # see +H2::Server::Stream#request+
89
95
  #
90
- if stream.request.path == '/favicon.ico'
96
+ if stream.request.path != '/'
91
97
 
92
98
  # since this is a TLS-enabled server, we could actually test it with a
93
99
  # real browser, which will undoubtedly request /favicon.ico.
@@ -133,9 +139,9 @@ s = H2::Server::HTTPS.new host: addr, port: port, sni: sni do |connection|
133
139
  # have this +H2::Server::PushPromise+ initiate the sub-stream on this
134
140
  # stream by sending initial headers.
135
141
  #
136
- # see +H2::Server::PushPromise#make_on+
142
+ # see +H2::Server::Stream::#make_promise+
137
143
  #
138
- js_promise.make_on stream
144
+ stream.make_promise js_promise
139
145
 
140
146
  # respond with 200 and HTML body
141
147
  #
@@ -150,8 +156,8 @@ s = H2::Server::HTTPS.new host: addr, port: port, sni: sni do |connection|
150
156
  # but for `js_promise`, we must keep it ourselves. in this case, we keep
151
157
  # it "synchronously", but we may also call `#keep_async` to queue it.
152
158
  #
153
- # see +H2::Server::PushPromise#keep+
154
- # see +H2::Server::PushPromise#keep_async+
159
+ # see +H2::Server::Stream::PushPromise#keep+
160
+ # see +H2::Server::Stream::PushPromise#keep_async+
155
161
  #
156
162
  js_promise.keep
157
163
 
@@ -18,8 +18,9 @@ require File.expand_path '../../../test/support/create_certs', __FILE__
18
18
  # crank up the logger level for testing/example purposes
19
19
  #
20
20
  H2::Logger.level = ::Logger::DEBUG
21
+ H2::Server::Connection.include H2::FrameDebugger
21
22
 
22
- port = 1234
23
+ port = 4430#1234
23
24
  addr = Socket.getaddrinfo('localhost', port).first[3]
24
25
  puts "*** Starting server on https://localhost:#{port}"
25
26
 
@@ -32,9 +33,14 @@ puts "*** Starting server on https://localhost:#{port}"
32
33
  certs_dir = File.expand_path '../../../tmp/certs', __FILE__
33
34
  sni = {
34
35
  'localhost' => {
35
- :cert => certs_dir + '/server.crt',
36
- :key => certs_dir + '/server.key',
37
- # :extra_chain_cert => certs_dir + '/chain.pem'
36
+ cert: certs_dir + '/server.crt',
37
+ key: certs_dir + '/server.key',
38
+ # extra_chain_cert: certs_dir + '/chain.pem'
39
+ },
40
+ 'vux.nakamura.io' => {
41
+ cert: certs_dir + '/nakamuraio.crt',
42
+ key: certs_dir + '/nakamuraio.key',
43
+ extra_chain_cert: certs_dir + '/nakamuraio-chain.pem'
38
44
  }
39
45
  }
40
46
 
@@ -147,7 +153,7 @@ s = H2::Server::HTTPS.new host: addr, port: port, sni: sni do |connection|
147
153
  stream.respond status: 404,
148
154
  body: "should have been pushed..."
149
155
 
150
- else
156
+ when '/'
151
157
 
152
158
  # initiate a push promise sub-stream, and queue the "keep" handler.
153
159
  # since a push promise may be canceled, we queue the handler on the server reactor,
@@ -166,6 +172,9 @@ s = H2::Server::HTTPS.new host: addr, port: port, sni: sni do |connection|
166
172
  #
167
173
  stream.respond status: 200, body: data[:html]
168
174
 
175
+ else
176
+ stream.respond status: 404
177
+
169
178
  end
170
179
  end
171
180
  end
data/exe/h2 CHANGED
@@ -19,10 +19,13 @@ end # }}}
19
19
  options = {
20
20
  body: nil,
21
21
  block: false,
22
- debug: false,
22
+ celluloid: false,
23
+ concurrent: false,
24
+ deflate: false,
23
25
  headers: {},
24
26
  goaway: false,
25
- method: :get,
27
+ gzip: false,
28
+ method: nil,
26
29
  tls: {},
27
30
  verbose: false
28
31
  }
@@ -43,23 +46,31 @@ OptionParser.new do |o|
43
46
  end
44
47
 
45
48
  o.on '--celluloid', 'use celluloid actor pool' do
49
+ raise ArgumentError, '--celluloid and --concurrent are mutually exclusive' if options[:concurrent]
46
50
  require 'h2/client/celluloid'
51
+ options[:celluloid] = true
47
52
  end
48
53
 
49
54
  o.on '--concurrent', 'use concurrent-ruby thread pool' do
55
+ raise ArgumentError, '--celluloid and --concurrent are mutually exclusive' if options[:celluloid]
50
56
  require 'h2/client/concurrent'
57
+ options[:concurrent] = true
51
58
  end
52
59
 
53
- o.on '-d', '--data [DATA]', String, 'post body data' do |d|
54
- options[:method] = :post if options[:method].nil?
60
+ o.on '-d', '--data [DATA]', String, 'post body data (implies POST, override with -X)' do |d|
61
+ options[:method] ||= :post
55
62
  options[:body] = d
56
63
  end
57
64
 
58
65
  o.on '--debug', 'debug output' do
59
- options[:debug] = true
66
+ H2::Client.include H2::FrameDebugger
60
67
  end
61
68
 
62
- o.on '-sse', '--eventsource', 'send event-stream headers and print messages as they arrive' do
69
+ o.on '--deflate', 'request "deflate" content-encoding' do
70
+ options[:deflate] = true
71
+ end
72
+
73
+ o.on '-s', '--sse', 'send event-stream headers and print messages as they arrive' do
63
74
  options[:headers]['accept'] = H2::EVENT_SOURCE_CONTENT_TYPE
64
75
  end
65
76
 
@@ -67,15 +78,19 @@ OptionParser.new do |o|
67
78
  options[:goaway] = true
68
79
  end
69
80
 
81
+ o.on '--gzip', 'request "gzip" content-encoding' do
82
+ options[:gzip] = true
83
+ end
84
+
70
85
  o.on '-h', '--help', 'show this help/usage' do
71
86
  puts o
72
87
  exit
73
88
  end
74
89
 
75
90
  o.on '-H [VALUE]', '--header [VALUE]', String, 'include header in request (format: "key: value")' do |h|
76
- warn "psuedo-headers not supported via CLI" if h[0] == ':'
91
+ raise ArgumentError, "psuedo-headers not supported via CLI" if h[0] == ':'
77
92
  kv = h.split(':').map &:strip
78
- options[:headers][kv[0]] = kv[1]
93
+ options[:headers][kv[0].downcase] = kv[1]
79
94
  end
80
95
 
81
96
  o.on '-v', '--verbose', 'turn on verbosity' do
@@ -98,6 +113,9 @@ OptionParser.new do |o|
98
113
 
99
114
  end.parse!
100
115
 
116
+ # start with nil, allow -d and/or -X to set, but default to get
117
+ options[:method] ||= :get
118
+
101
119
  # }}}
102
120
 
103
121
  # --- parse URL {{{
@@ -116,17 +134,18 @@ client = {
116
134
 
117
135
  client[:tls] = options[:tls] unless options[:tls].empty?
118
136
 
119
- c = H2::Client.new **client do |c|
120
- if options[:debug]
121
- c.client.on(:frame_received) {|f| puts "<< #{f.inspect}" }
122
- c.client.on(:frame_sent) {|f| puts ">> #{f.inspect}" }
123
- end
124
- end
137
+ c = H2::Client.new **client
125
138
 
126
139
  # }}}
127
140
 
128
141
  # --- build & send request {{{
129
142
 
143
+ ce = []
144
+ ce << :gzip if options[:gzip]
145
+ ce << :deflate if options[:deflate]
146
+ ce = ce.join ','
147
+ options[:headers][H2::ACCEPT_ENCODING_KEY] = ce unless ce.empty?
148
+
130
149
  request = {
131
150
  body: options[:body],
132
151
  headers: options[:headers],
@@ -138,7 +157,7 @@ if options[:verbose]
138
157
  method: options[:method],
139
158
  path: url.request_uri,
140
159
  headers: request[:headers]
141
- ).each {|k,v| puts ">> #{k}: #{v}".green}
160
+ ).each {|k,v| STDERR.puts ">> #{k}: #{v}" }
142
161
  end
143
162
 
144
163
  s = c.__send__ options[:method], **request
@@ -148,7 +167,7 @@ s = c.__send__ options[:method], **request
148
167
  # --- print response & close {{{
149
168
 
150
169
  if options[:verbose]
151
- s.headers.each {|k,v| puts "<< #{k}: #{v}".yellow}
170
+ s.headers.each {|k,v| STDERR.puts "<< #{k}: #{v}" }
152
171
  end
153
172
 
154
173
  if s.eventsource?
@@ -159,7 +178,7 @@ end
159
178
 
160
179
  c.block! if options[:block] or !s.pushes.empty?
161
180
  s.pushes.each do |p|
162
- puts "push promise: #{p.headers[':path']}"
181
+ STDERR.puts "push promise: #{p.headers[':path']}"
163
182
  end
164
183
 
165
184
  c.goaway if options[:goaway]
data/lib/h2.rb CHANGED
@@ -3,6 +3,8 @@
3
3
  require 'http/2'
4
4
  require 'logger'
5
5
  require 'uri'
6
+ require 'zlib'
7
+
6
8
  require 'h2/version'
7
9
 
8
10
  module H2
@@ -29,8 +31,13 @@ module H2
29
31
  :put
30
32
  ]
31
33
 
34
+ ACCEPT_ENCODING_KEY = 'accept-encoding'
35
+ CONTENT_ENCODING_KEY = 'content-encoding'
32
36
  CONTENT_TYPE_KEY = 'content-type'
37
+
33
38
  EVENT_SOURCE_CONTENT_TYPE = 'text/event-stream'
39
+ GZIP_ENCODING = 'gzip'
40
+ DEFLATE_ENCODING = 'deflate'
34
41
 
35
42
  Logger = ::Logger.new STDOUT
36
43
 
@@ -172,3 +179,26 @@ end
172
179
 
173
180
  require 'h2/client'
174
181
  require 'h2/stream'
182
+
183
+ if RUBY_VERSION < '2.4.0'
184
+ module Zlib
185
+ class << self
186
+ def gunzip string
187
+ sio = StringIO.new string
188
+ gz = Zlib::GzipReader.new sio, encoding: Encoding::ASCII_8BIT
189
+ gz.read
190
+ ensure
191
+ gz && gz.close
192
+ end
193
+
194
+ def gzip string, level: nil, strategy: nil
195
+ sio = StringIO.new
196
+ sio.binmode
197
+ gz = Zlib::GzipWriter.new sio, level, strategy
198
+ gz.write string
199
+ gz.close
200
+ sio.string
201
+ end
202
+ end
203
+ end
204
+ end
@@ -5,6 +5,7 @@ require 'h2/client/tcp_socket'
5
5
  module H2
6
6
  class Client
7
7
  include Blockable
8
+ include HeaderStringifier
8
9
  include On
9
10
 
10
11
  PARSER_EVENTS = [
@@ -152,18 +153,6 @@ module H2
152
153
  stream
153
154
  end
154
155
 
155
- # mutates the given hash into +String+ keys and values
156
- #
157
- # @param [Hash] hash the headers +Hash+ to stringify
158
- #
159
- def stringify_headers hash
160
- hash.keys.each do |key|
161
- hash[key] = hash[key].to_s unless String === hash[key]
162
- hash[key.to_s] = hash.delete key unless String === key
163
- end
164
- hash
165
- end
166
-
167
156
  # builds headers +Hash+ with appropriate ordering
168
157
  #
169
158
  # @see https://http2.github.io/http2-spec/#rfc.section.8.1.2.1
@@ -11,18 +11,23 @@ module H2
11
11
  class Server
12
12
  include Celluloid::IO
13
13
 
14
- TCP_DEFAULT_BACKLOG = 100
14
+ DEFAULT_OPTIONS = {
15
+ backlog: 100,
16
+ deflate: true,
17
+ gzip: true
18
+ }
15
19
 
16
20
  execute_block_on_receiver :initialize
17
21
  finalizer :shutdown
18
22
 
23
+ attr_reader :options
24
+
19
25
  def initialize server, **options, &on_connection
20
26
  @server = server
21
- @options = options
27
+ @options = DEFAULT_OPTIONS.merge options
22
28
  @on_connection = on_connection
23
29
 
24
- backlog = options.fetch :backlog, TCP_DEFAULT_BACKLOG
25
- @server.listen backlog
30
+ @server.listen @options[:backlog]
26
31
  async.run
27
32
  end
28
33
 
@@ -1,8 +1,3 @@
1
- require 'h2/server/stream/event_source'
2
- require 'h2/server/stream/request'
3
- require 'h2/server/stream/response'
4
- require 'h2/server/push_promise'
5
-
6
1
  module H2
7
2
  class Server
8
3
  class Stream
@@ -187,6 +182,43 @@ module H2
187
182
 
188
183
  end
189
184
 
185
+ module ContentEncoder
186
+
187
+ # checks the request for accept-encoding headers and processes body
188
+ # accordingly
189
+ #
190
+ def check_accept_encoding
191
+ if accept = @stream.request.headers[ACCEPT_ENCODING_KEY]
192
+ accept.split(',').map(&:strip).each do |encoding|
193
+ case encoding
194
+ when GZIP_ENCODING
195
+ if @stream.connection.server.options[:gzip]
196
+ @body = ::Zlib.gzip @body
197
+ @headers[CONTENT_ENCODING_KEY] = GZIP_ENCODING
198
+ break
199
+ end
200
+
201
+ # "deflate" has issues: https://zlib.net/zlib_faq.html#faq39
202
+ #
203
+ when DEFLATE_ENCODING
204
+ if @stream.connection.server.options[:deflate]
205
+ @body = ::Zlib.deflate @body
206
+ @headers[CONTENT_ENCODING_KEY] = DEFLATE_ENCODING
207
+ break
208
+ end
209
+
210
+ end
211
+ end
212
+ end
213
+ end
214
+
215
+ end
216
+
190
217
  class StreamError < StandardError; end
191
218
  end
192
219
  end
220
+
221
+ require 'h2/server/stream/event_source'
222
+ require 'h2/server/stream/request'
223
+ require 'h2/server/stream/response'
224
+ require 'h2/server/stream/push_promise'
@@ -25,8 +25,11 @@ module H2
25
25
  @stream = stream
26
26
  @parser = @stream.stream
27
27
  @headers = headers
28
+ @gzip = false
29
+ @deflate = false
28
30
 
29
31
  check_accept_header
32
+ check_accept_encoding
30
33
  init_response
31
34
  end
32
35
 
@@ -40,6 +43,34 @@ module H2
40
43
  end
41
44
  end
42
45
 
46
+ # checks the request for accept-encoding headers and processes data &
47
+ # events accordingly
48
+ #
49
+ def check_accept_encoding
50
+ if accept = @stream.request.headers[ACCEPT_ENCODING_KEY]
51
+ accept.split(',').map(&:strip).each do |encoding|
52
+ case encoding
53
+ when GZIP_ENCODING
54
+ if @stream.connection.server.options[:gzip]
55
+ @gzip = true
56
+ @headers[CONTENT_ENCODING_KEY] = GZIP_ENCODING
57
+ break
58
+ end
59
+
60
+ # "deflate" has issues: https://zlib.net/zlib_faq.html#faq39
61
+ #
62
+ when DEFLATE_ENCODING
63
+ if @stream.connection.server.options[:deflate]
64
+ @deflate = true
65
+ @headers[CONTENT_ENCODING_KEY] = DEFLATE_ENCODING
66
+ break
67
+ end
68
+
69
+ end
70
+ end
71
+ end
72
+ end
73
+
43
74
  # responds with SSE headers on this stream
44
75
  #
45
76
  def init_response
@@ -57,7 +88,7 @@ module H2
57
88
  # @param [String] data: data associated with this event
58
89
  #
59
90
  def event name:, data:
60
- e = EVENT_TEMPL % [name, data]
91
+ e = encode_content(EVENT_TEMPL % [name, data])
61
92
  @parser.data e, end_stream: false
62
93
  end
63
94
 
@@ -68,7 +99,7 @@ module H2
68
99
  # @param [String] data associated with this event
69
100
  #
70
101
  def data str
71
- d = DATA_TEMPL % str
102
+ d = encode_content(DATA_TEMPL % str)
72
103
  @parser.data d, end_stream: false
73
104
  end
74
105
 
@@ -85,6 +116,16 @@ module H2
85
116
  @closed
86
117
  end
87
118
 
119
+ private
120
+
121
+ # content-encoding helper for #event and #data methods
122
+ #
123
+ def encode_content str
124
+ str = ::Zlib.gzip str if @gzip
125
+ str = ::Zlib.deflate str if @deflate
126
+ str
127
+ end
128
+
88
129
  end
89
130
  end
90
131
  end
@@ -0,0 +1,123 @@
1
+ module H2
2
+ class Server
3
+ class Stream
4
+ class PushPromise
5
+ include ContentEncoder
6
+ include HeaderStringifier
7
+
8
+ GET = 'GET'
9
+ STATUS = '200'
10
+
11
+ attr_reader :content_length, :path, :push_stream
12
+
13
+ # build a new +PushPromise+ for the path, with the headers and body given
14
+ #
15
+ def initialize path:, headers: {}, body: nil
16
+ @path = path
17
+ @body = body
18
+ @headers = headers
19
+
20
+ @promise_headers = {
21
+ METHOD_KEY => GET,
22
+ AUTHORITY_KEY => headers.delete(AUTHORITY_KEY),
23
+ PATH_KEY => @path,
24
+ SCHEME_KEY => headers.delete(SCHEME_KEY)
25
+ }
26
+
27
+ @fsm = FSM.new
28
+ end
29
+
30
+ # create a new promise stream from +stream+, send the headers and set
31
+ # +@push_stream+ from the callback
32
+ #
33
+ def make_on stream
34
+ return unless @fsm.state == :init
35
+ @stream = stream
36
+ @headers = {STATUS_KEY => STATUS}.merge stringify_headers(@headers)
37
+
38
+ check_accept_encoding
39
+ @content_length = @body.bytesize.to_s
40
+ @headers.merge! CONTENT_LENGTH_KEY => @content_length
41
+
42
+ @stream.stream.promise(@promise_headers, end_headers: false) do |push|
43
+ push.headers @headers
44
+ @push_stream = push
45
+ @push_stream.on(:close){|err| cancel! if err == :cancel}
46
+ end
47
+ @fsm.transition :made
48
+ self
49
+ end
50
+
51
+ def keep_async
52
+ @stream.connection.server.async.handle_push_promise self
53
+ end
54
+
55
+ # deliver the body for this promise, optionally splicing into +size+ chunks
56
+ #
57
+ def keep size = nil
58
+ return false unless @fsm.state == :made
59
+
60
+ if size.nil?
61
+ @push_stream.data @body
62
+ else
63
+ body = @body
64
+
65
+ if body.bytesize > size
66
+ body = @body.dup
67
+ while body.bytesize > size
68
+ @push_stream.data body.slice!(0, size), end_stream: false
69
+ yield if block_given?
70
+ end
71
+ else
72
+ yield if block_given?
73
+ end
74
+
75
+ @push_stream.data body
76
+ end
77
+
78
+ @fsm.transition :kept
79
+ log :info, self
80
+ @stream.on_complete
81
+ end
82
+
83
+ def kept?
84
+ @fsm.state == :kept
85
+ end
86
+
87
+ def canceled?
88
+ @fsm.state == :canceled
89
+ end
90
+
91
+ # cancel this promise, most likely due to a RST_STREAM frame from the
92
+ # client (already in cache, etc...)
93
+ #
94
+ def cancel!
95
+ @fsm.transition :canceled
96
+ @stream.on_complete
97
+ end
98
+
99
+ def log level, msg
100
+ Logger.__send__ level, "[stream #{@push_stream.id}] #{msg}"
101
+ end
102
+
103
+ def to_s
104
+ request = @stream.request
105
+ %{#{request.addr} "push #{@path} HTTP/2" #{STATUS} #{@content_length}}
106
+ end
107
+ alias to_str to_s
108
+
109
+ # simple state machine to guarantee promise process
110
+ #
111
+ class FSM
112
+ include Celluloid::FSM
113
+ default_state :init
114
+ state :init, to: [:canceled, :made]
115
+ state :made, to: [:canceled, :kept]
116
+ state :kept
117
+ state :canceled
118
+ end
119
+
120
+ end
121
+ end
122
+ end
123
+ end
@@ -2,6 +2,7 @@ module H2
2
2
  class Server
3
3
  class Stream
4
4
  class Response
5
+ include ContentEncoder
5
6
  include HeaderStringifier
6
7
 
7
8
  attr_reader :body, :content_length, :headers, :status, :stream
@@ -23,6 +24,7 @@ module H2
23
24
  @body = body
24
25
  @status = status
25
26
 
27
+ check_accept_encoding
26
28
  init_content_length
27
29
  end
28
30
 
@@ -33,6 +33,8 @@ module H2
33
33
  @stream = stream
34
34
 
35
35
  @eventsource = false
36
+ @gzip = false
37
+ @deflate = false
36
38
 
37
39
  init_blocking
38
40
  yield self if block_given?
@@ -104,6 +106,10 @@ module H2
104
106
  loop do
105
107
  event = @body.pop
106
108
  break if event == :close
109
+
110
+ event = ::Zlib.gunzip event if @gzip
111
+ event = ::Zlib.inflate event if @deflate
112
+
107
113
  yield event
108
114
  end
109
115
  else
@@ -119,7 +125,14 @@ module H2
119
125
  @parent.add_push self if @parent && push?
120
126
  @client.last_stream = self
121
127
  @closed = true
122
- @body << :close if @eventsource
128
+
129
+ if @eventsource
130
+ @body << :close
131
+ else
132
+ @body = Zlib.gunzip @body if @gzip
133
+ @body = Zlib.inflate @body if @deflate
134
+ end
135
+
123
136
  unblock!
124
137
  on :close
125
138
  end
@@ -130,7 +143,6 @@ module H2
130
143
 
131
144
  @stream.on(:data) do |d|
132
145
  on :data, d
133
- unblock! if @eventsource
134
146
  @body << d
135
147
  end
136
148
  end
@@ -139,6 +151,7 @@ module H2
139
151
  #
140
152
  def add_headers h
141
153
  check_event_source h
154
+ check_content_encoding h
142
155
  h = Hash[h]
143
156
  on :headers, h
144
157
  @headers.merge! h
@@ -151,6 +164,19 @@ module H2
151
164
  if h.any? {|e| e[0] == CONTENT_TYPE_KEY && e[1] == EVENT_SOURCE_CONTENT_TYPE }
152
165
  @eventsource = true
153
166
  @body = Queue.new
167
+ unblock!
168
+ end
169
+ end
170
+
171
+ # checks for content encoding headers and sets flags
172
+ #
173
+ def check_content_encoding h
174
+ return if @gzip or @deflate
175
+ h.each do |e|
176
+ if e[0] == CONTENT_ENCODING_KEY
177
+ @gzip = true if e[1] == GZIP_ENCODING
178
+ @deflate = true if e[1] == DEFLATE_ENCODING
179
+ end
154
180
  end
155
181
  end
156
182
 
@@ -1,5 +1,5 @@
1
1
  module H2
2
- VERSION = '0.8.1'
2
+ VERSION = '0.8.2'
3
3
 
4
4
  class << self
5
5
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: h2
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.1
4
+ version: 0.8.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kenichi Nakamura
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2018-08-12 00:00:00.000000000 Z
11
+ date: 2018-08-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: http-2
@@ -104,9 +104,9 @@ files:
104
104
  - lib/h2/server.rb
105
105
  - lib/h2/server/connection.rb
106
106
  - lib/h2/server/https.rb
107
- - lib/h2/server/push_promise.rb
108
107
  - lib/h2/server/stream.rb
109
108
  - lib/h2/server/stream/event_source.rb
109
+ - lib/h2/server/stream/push_promise.rb
110
110
  - lib/h2/server/stream/request.rb
111
111
  - lib/h2/server/stream/response.rb
112
112
  - lib/h2/stream.rb
@@ -1,119 +0,0 @@
1
- module H2
2
- class Server
3
- class PushPromise
4
-
5
- GET = 'GET'
6
- STATUS = '200'
7
-
8
- attr_reader :content_length, :path, :push_stream
9
-
10
- # build a new +PushPromise+ for the path, with the headers and body given
11
- #
12
- def initialize path:, headers: {}, body: nil
13
- @path = path
14
- @body = body
15
-
16
- @promise_headers = {
17
- METHOD_KEY => GET,
18
- AUTHORITY_KEY => headers.delete(AUTHORITY_KEY),
19
- PATH_KEY => @path,
20
- SCHEME_KEY => headers.delete(SCHEME_KEY)
21
- }
22
-
23
- @content_length = @body.bytesize.to_s
24
-
25
- @push_headers = {
26
- STATUS_KEY => STATUS,
27
- CONTENT_LENGTH_KEY => @content_length
28
- }.merge headers
29
-
30
- @fsm = FSM.new
31
- end
32
-
33
- # create a new promise stream from +stream+, send the headers and set
34
- # +@push_stream+ from the callback
35
- #
36
- def make_on stream
37
- return unless @fsm.state == :init
38
- @stream = stream
39
- @stream.stream.promise(@promise_headers, end_headers: false) do |push|
40
- push.headers @push_headers
41
- @push_stream = push
42
- @push_stream.on(:close){|err| cancel! if err == :cancel}
43
- end
44
- @fsm.transition :made
45
- self
46
- end
47
-
48
- def keep_async
49
- @stream.connection.server.async.handle_push_promise self
50
- end
51
-
52
- # deliver the body for this promise, optionally splicing into +size+ chunks
53
- #
54
- def keep size = nil
55
- return false unless @fsm.state == :made
56
-
57
- if size.nil?
58
- @push_stream.data @body
59
- else
60
- body = @body
61
-
62
- if body.bytesize > size
63
- body = @body.dup
64
- while body.bytesize > size
65
- @push_stream.data body.slice!(0, size), end_stream: false
66
- yield if block_given?
67
- end
68
- else
69
- yield if block_given?
70
- end
71
-
72
- @push_stream.data body
73
- end
74
-
75
- @fsm.transition :kept
76
- log :info, self
77
- @stream.on_complete
78
- end
79
-
80
- def kept?
81
- @fsm.state == :kept
82
- end
83
-
84
- def canceled?
85
- @fsm.state == :canceled
86
- end
87
-
88
- # cancel this promise, most likely due to a RST_STREAM frame from the
89
- # client (already in cache, etc...)
90
- #
91
- def cancel!
92
- @fsm.transition :canceled
93
- @stream.on_complete
94
- end
95
-
96
- def log level, msg
97
- Logger.__send__ level, "[stream #{@push_stream.id}] #{msg}"
98
- end
99
-
100
- def to_s
101
- request = @stream.request
102
- %{#{request.addr} "push #{@path} HTTP/2" #{STATUS} #{@content_length}}
103
- end
104
- alias to_str to_s
105
-
106
- # simple state machine to guarantee promise process
107
- #
108
- class FSM
109
- include Celluloid::FSM
110
- default_state :init
111
- state :init, to: [:canceled, :made]
112
- state :made, to: [:canceled, :kept]
113
- state :kept
114
- state :canceled
115
- end
116
-
117
- end
118
- end
119
- end