h2 0.8.1 → 0.8.2

Sign up to get free protection for your applications and to get access to all the features.
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