h2 0.6.1 → 0.7.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 +4 -4
- data/CHANGELOG.md +8 -0
- data/Gemfile +2 -4
- data/README.md +5 -5
- data/Rakefile +16 -1
- data/examples/server/push_promise.rb +7 -2
- data/examples/server/sse.rb +103 -0
- data/exe/h2 +4 -1
- data/lib/h2.rb +16 -0
- data/lib/h2/server.rb +23 -5
- data/lib/h2/server/connection.rb +1 -1
- data/lib/h2/server/https.rb +0 -1
- data/lib/h2/server/push_promise.rb +2 -8
- data/lib/h2/server/stream.rb +15 -2
- data/lib/h2/server/stream/event_source.rb +91 -0
- data/lib/h2/server/stream/request.rb +12 -1
- data/lib/h2/server/stream/response.rb +25 -47
- data/lib/h2/version.rb +1 -1
- metadata +4 -3
- data/lib/h2/reel/ext.rb +0 -48
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a59b70f55abe247ca3ae333b72031a32f880fa1844bd8058585a4090bcc55969
|
4
|
+
data.tar.gz: d28023b7123b0819c1f540070831188fd99460f11fe9914c2d8c00c7e4dd4e6d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a7e502860f2f753ed37f419ba41b78c9399fe8a6278cbfe937ccd7c04f8da0c7fd53fdbc960454ac389a3b4a4073d3a39737a643fdf09235aac281b7f15f67eb
|
7
|
+
data.tar.gz: 2dfa20d7aafdc0e37cd61aba4c6a2d9696c4efb58de3786f87c628f08c65bb5437baebdc93dd2acd4f27abde49021f2ac82d58d06ea9d133359d6d9098c8c6b4
|
data/CHANGELOG.md
CHANGED
@@ -1,6 +1,14 @@
|
|
1
1
|
h2 changelog
|
2
2
|
============
|
3
3
|
|
4
|
+
### 0.7.0 2 aug 2018
|
5
|
+
|
6
|
+
* `Server::Stream::Request#path` now removes query string
|
7
|
+
* add `H2::Server::Stream::Request#query_string`
|
8
|
+
* `H2::Server::Stream::Response` body now accepts any object that `respond_to? :each`
|
9
|
+
* remove Reel completely, base from Celluloid::IO
|
10
|
+
* add SSE support
|
11
|
+
|
4
12
|
### 0.6.1 27 jul 2018
|
5
13
|
|
6
14
|
* fix race between reading and sending first frame
|
data/Gemfile
CHANGED
@@ -2,14 +2,12 @@ source 'https://rubygems.org'
|
|
2
2
|
|
3
3
|
gemspec
|
4
4
|
|
5
|
-
gem 'http-2', path: '../http-2'
|
6
|
-
|
7
5
|
group :concurrent_ruby do
|
8
6
|
gem 'concurrent-ruby'
|
9
7
|
end
|
10
8
|
|
11
9
|
group :celluloid do
|
12
|
-
gem 'celluloid'
|
10
|
+
gem 'celluloid', '~> 0.17', '>= 0.17.3'
|
13
11
|
end
|
14
12
|
|
15
13
|
group :development, :test do
|
@@ -17,5 +15,5 @@ group :development, :test do
|
|
17
15
|
gem 'certificate_authority'
|
18
16
|
gem 'guard-rake'
|
19
17
|
gem 'pry-byebug', platforms: [:mri]
|
20
|
-
gem '
|
18
|
+
gem 'celluloid-io', '~> 0.17', '>= 0.17.3'
|
21
19
|
end
|
data/README.md
CHANGED
@@ -12,8 +12,8 @@ H2 uses:
|
|
12
12
|
## Server Usage
|
13
13
|
|
14
14
|
Server API is currently optional, and must be required separately. The server
|
15
|
-
uses [
|
16
|
-
|
15
|
+
uses [Celluloid::IO](https://github.com/celluloid/celluloid-io), but since this API is optional,
|
16
|
+
celluloid-io must be separately added to `Gemfile`. It is currently based on `celluloid-io-0.17.3`.
|
17
17
|
|
18
18
|
```ruby
|
19
19
|
require 'h2/server'
|
@@ -135,9 +135,9 @@ require 'h2/client/celluloid'
|
|
135
135
|
|
136
136
|
This will lazily fire up a celluloid pool, with defaults defined by Celluloid.
|
137
137
|
|
138
|
-
NOTE: if you've added
|
139
|
-
loaded in your Ruby VM already; however, you must still require this to
|
140
|
-
the client use Celluloid actor pools.
|
138
|
+
NOTE: if you've added celluloid-io and required the 'h2/server' API, Celluloid
|
139
|
+
will be loaded in your Ruby VM already; however, you must still require this to
|
140
|
+
have the client use Celluloid actor pools.
|
141
141
|
|
142
142
|
#### Concurrent-Ruby ThreadPoolExecutor
|
143
143
|
|
data/Rakefile
CHANGED
@@ -3,7 +3,7 @@ require "rake/testtask"
|
|
3
3
|
|
4
4
|
task default: :test
|
5
5
|
|
6
|
-
Rake::TestTask.new :test do |t|
|
6
|
+
Rake::TestTask.new :test => ['test:certs'] do |t|
|
7
7
|
t.test_files = FileList['test/**/*_test.rb']
|
8
8
|
end
|
9
9
|
|
@@ -18,4 +18,19 @@ namespace :test do
|
|
18
18
|
end
|
19
19
|
end
|
20
20
|
|
21
|
+
task :certs do
|
22
|
+
certs_dir = Pathname.new File.expand_path '../tmp/certs', __FILE__
|
23
|
+
ca_file = certs_dir.join('ca.crt').to_s
|
24
|
+
require_relative 'test/support/create_certs' unless File.exist? ca_file
|
25
|
+
end
|
26
|
+
|
27
|
+
task :nginx => [:certs] do
|
28
|
+
system "docker build -t h2_nginx_http2 test/support/nginx"
|
29
|
+
puts "\nstarting nginx with http/2 support"
|
30
|
+
puts "using document root: test/support/nginx/"
|
31
|
+
puts "using TLS certs: tmp/certs/server.*"
|
32
|
+
puts "listening at https://localhost:4430/"
|
33
|
+
system "docker run --rm -v `pwd`/tmp/certs:/usr/local/nginx/certs -v `pwd`/test/support/nginx:/usr/local/nginx/html -p 4430:443 -it h2_nginx_http2"
|
34
|
+
end
|
35
|
+
|
21
36
|
end
|
@@ -7,8 +7,8 @@ require 'h2/server'
|
|
7
7
|
H2::Logger.level = ::Logger::DEBUG
|
8
8
|
H2.verbose!
|
9
9
|
|
10
|
-
port =
|
11
|
-
addr = Socket.getaddrinfo('localhost', port).first[3]
|
10
|
+
port = 4430
|
11
|
+
addr = '127.0.0.1' #Socket.getaddrinfo('localhost', port).first[3]
|
12
12
|
certs_dir = File.expand_path '../../../tmp/certs', __FILE__
|
13
13
|
dog_png = File.read File.expand_path '../dog.png', __FILE__
|
14
14
|
push_promise = '<html><body>wait for it...<img src="/dog.png"/><script src="/pushed.js"></script></body></html>'
|
@@ -19,6 +19,11 @@ sni = {
|
|
19
19
|
:cert => certs_dir + '/server.crt',
|
20
20
|
:key => certs_dir + '/server.key',
|
21
21
|
# :extra_chain_cert => certs_dir + '/chain.pem'
|
22
|
+
},
|
23
|
+
'vux.nakamura.io' => {
|
24
|
+
:cert => certs_dir + '/nakamuraio.crt',
|
25
|
+
:key => certs_dir + '/nakamuraio.key',
|
26
|
+
:extra_chain_cert => certs_dir + '/nakamuraio-chain.pem'
|
22
27
|
}
|
23
28
|
}
|
24
29
|
|
@@ -0,0 +1,103 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# Run with: bundle exec examples/server/sse/sse.rb
|
3
|
+
|
4
|
+
require 'bundler/setup'
|
5
|
+
require 'h2/server'
|
6
|
+
|
7
|
+
H2::Logger.level = ::Logger::DEBUG
|
8
|
+
H2.verbose!
|
9
|
+
|
10
|
+
port = 4430
|
11
|
+
addr = Socket.getaddrinfo('localhost', port).first[3]
|
12
|
+
certs_dir = File.expand_path '../../../tmp/certs', __FILE__
|
13
|
+
data, key = Hash.new {|h,k| h[k] = ''}, nil
|
14
|
+
|
15
|
+
sni = {
|
16
|
+
'localhost' => {
|
17
|
+
:cert => certs_dir + '/server.crt',
|
18
|
+
:key => certs_dir + '/server.key',
|
19
|
+
# :extra_chain_cert => certs_dir + '/chain.pem'
|
20
|
+
}
|
21
|
+
}
|
22
|
+
|
23
|
+
event_sources = []
|
24
|
+
|
25
|
+
puts "*** Starting server on https://#{addr}:#{port}"
|
26
|
+
s = H2::Server::HTTPS.new host: addr, port: port, sni: sni do |connection|
|
27
|
+
connection.each_stream do |stream|
|
28
|
+
case stream.request.path
|
29
|
+
when '/favicon.ico'
|
30
|
+
stream.respond status: 404
|
31
|
+
|
32
|
+
when '/events'
|
33
|
+
es = stream.to_eventsource
|
34
|
+
event_sources << es
|
35
|
+
|
36
|
+
when '/msg'
|
37
|
+
if stream.request.method == :post
|
38
|
+
msg = stream.request.body
|
39
|
+
event_sources.each {|es| es.event name: 'msg', data: msg}
|
40
|
+
stream.respond status: 201
|
41
|
+
else
|
42
|
+
stream.respond status: 404
|
43
|
+
end
|
44
|
+
|
45
|
+
when '/sse.js'
|
46
|
+
stream.respond status: 404,
|
47
|
+
body: "should have been pushed..."
|
48
|
+
|
49
|
+
else
|
50
|
+
stream.push_promise path: '/sse.js',
|
51
|
+
headers: { 'content-type' => 'application/javascript' },
|
52
|
+
body: data[:javascript]
|
53
|
+
|
54
|
+
stream.respond status: 200, body: data[:html]
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
DATA.each_line do |l|
|
60
|
+
if l.start_with?('@@')
|
61
|
+
key = l.strip[2..-1].to_sym
|
62
|
+
else
|
63
|
+
data[key] << l
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
sleep
|
68
|
+
|
69
|
+
__END__
|
70
|
+
@@html
|
71
|
+
<!DOCTYPE html>
|
72
|
+
<html lang="en">
|
73
|
+
<head>
|
74
|
+
<meta charset="UTF-8">
|
75
|
+
<title>SSE example</title>
|
76
|
+
<script src="/sse.js"></script>
|
77
|
+
</head>
|
78
|
+
<body>
|
79
|
+
<form id="say">
|
80
|
+
say: <input type="text" id="words"/>
|
81
|
+
</form>
|
82
|
+
<br/>
|
83
|
+
<div><ol id="list"></ol></div>
|
84
|
+
</body>
|
85
|
+
</html>
|
86
|
+
|
87
|
+
@@javascript
|
88
|
+
document.addEventListener('DOMContentLoaded', () => {
|
89
|
+
let sse = new EventSource('/events');
|
90
|
+
sse.addEventListener('msg', (msg) => {
|
91
|
+
let item = document.createElement('li');
|
92
|
+
item.innerHTML = msg.data;
|
93
|
+
document.getElementById('list').appendChild(item);
|
94
|
+
});
|
95
|
+
|
96
|
+
let w = document.getElementById('words');
|
97
|
+
document.getElementById('say').onsubmit = (e) => {
|
98
|
+
e.preventDefault();
|
99
|
+
fetch('/msg', {method: 'post', body: w.value})
|
100
|
+
.then(() => { w.value = ''; });
|
101
|
+
};
|
102
|
+
w.focus();
|
103
|
+
});
|
data/exe/h2
CHANGED
@@ -136,8 +136,11 @@ if options[:verbose]
|
|
136
136
|
end
|
137
137
|
|
138
138
|
puts s.body
|
139
|
+
c.block! if options[:block] or !s.pushes.empty?
|
140
|
+
s.pushes.each do |p|
|
141
|
+
puts "push promise: #{p.headers[':path']}"
|
142
|
+
end
|
139
143
|
|
140
|
-
c.block! if options[:block]
|
141
144
|
c.goaway if options[:goaway]
|
142
145
|
c.close
|
143
146
|
|
data/lib/h2.rb
CHANGED
@@ -161,6 +161,22 @@ module H2
|
|
161
161
|
|
162
162
|
end
|
163
163
|
|
164
|
+
module HeaderStringifier
|
165
|
+
|
166
|
+
private
|
167
|
+
def stringify_headers hash
|
168
|
+
hash.keys.each do |k|
|
169
|
+
hash[k] = hash[k].to_s unless String === hash[k]
|
170
|
+
if Symbol === k
|
171
|
+
key = k.to_s.gsub '_', '-'
|
172
|
+
hash[key] = hash.delete k
|
173
|
+
end
|
174
|
+
end
|
175
|
+
hash
|
176
|
+
end
|
177
|
+
|
178
|
+
end
|
179
|
+
|
164
180
|
end
|
165
181
|
|
166
182
|
require 'h2/client'
|
data/lib/h2/server.rb
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
require 'celluloid/current'
|
2
|
-
require '
|
3
|
-
require 'h2/reel/ext'
|
2
|
+
require 'celluloid/io'
|
4
3
|
require 'h2'
|
5
4
|
|
6
5
|
module H2
|
@@ -20,13 +19,28 @@ module H2
|
|
20
19
|
|
21
20
|
end
|
22
21
|
|
23
|
-
# base H2 server, a
|
22
|
+
# base H2 server, a +Celluoid::IO+ production
|
24
23
|
#
|
25
|
-
class Server
|
24
|
+
class Server
|
25
|
+
include Celluloid::IO
|
26
|
+
|
27
|
+
TCP_DEFAULT_BACKLOG = 100
|
28
|
+
|
29
|
+
execute_block_on_receiver :initialize
|
30
|
+
finalizer :shutdown
|
26
31
|
|
27
32
|
def initialize server, **options, &on_connection
|
33
|
+
@server = server
|
34
|
+
@options = options
|
28
35
|
@on_connection = on_connection
|
29
|
-
|
36
|
+
|
37
|
+
backlog = options.fetch :backlog, TCP_DEFAULT_BACKLOG
|
38
|
+
@server.listen backlog
|
39
|
+
async.run
|
40
|
+
end
|
41
|
+
|
42
|
+
def shutdown
|
43
|
+
@server.close if @server
|
30
44
|
end
|
31
45
|
|
32
46
|
# build a new connection object, run it through the given block, and
|
@@ -74,6 +88,10 @@ module H2
|
|
74
88
|
super @tcpserver, options, &on_connection
|
75
89
|
end
|
76
90
|
|
91
|
+
def run
|
92
|
+
loop { async.handle_connection @server.accept }
|
93
|
+
end
|
94
|
+
|
77
95
|
end
|
78
96
|
|
79
97
|
end
|
data/lib/h2/server/connection.rb
CHANGED
data/lib/h2/server/https.rb
CHANGED
@@ -9,15 +9,9 @@ module H2
|
|
9
9
|
|
10
10
|
# build a new +PushPromise+ for the path, with the headers and body given
|
11
11
|
#
|
12
|
-
def initialize path
|
12
|
+
def initialize path:, headers: {}, body: nil
|
13
13
|
@path = path
|
14
|
-
|
15
|
-
headers = body_or_headers.dup
|
16
|
-
@body = body
|
17
|
-
else
|
18
|
-
headers = {}
|
19
|
-
@body = body_or_headers
|
20
|
-
end
|
14
|
+
@body = body
|
21
15
|
|
22
16
|
@promise_headers = {
|
23
17
|
METHOD_KEY => GET,
|
data/lib/h2/server/stream.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
require 'h2/server/stream/event_source'
|
1
2
|
require 'h2/server/stream/request'
|
2
3
|
require 'h2/server/stream/response'
|
3
4
|
require 'h2/server/push_promise'
|
@@ -51,8 +52,8 @@ module H2
|
|
51
52
|
if @closed
|
52
53
|
log :warn, 'stream closed before response sent'
|
53
54
|
else
|
54
|
-
response.respond_on(stream)
|
55
55
|
log :info, response
|
56
|
+
response.respond_on(stream)
|
56
57
|
@responded = true
|
57
58
|
end
|
58
59
|
end
|
@@ -72,7 +73,7 @@ module H2
|
|
72
73
|
headers.merge! AUTHORITY_KEY => @request.authority,
|
73
74
|
SCHEME_KEY => @request.scheme
|
74
75
|
|
75
|
-
PushPromise.new path, headers, body
|
76
|
+
PushPromise.new path: path, headers: headers, body: body
|
76
77
|
end
|
77
78
|
|
78
79
|
# begin the new push promise stream from this +@stream+ by sending the
|
@@ -120,6 +121,16 @@ module H2
|
|
120
121
|
Logger.__send__ level, "[stream #{@stream.id}] #{msg}"
|
121
122
|
end
|
122
123
|
|
124
|
+
# make this stream into an SSE event source
|
125
|
+
#
|
126
|
+
# raises +StreamError+ if the request's content-type is not valid
|
127
|
+
#
|
128
|
+
# @return [H2::Server::Stream::EventSource]
|
129
|
+
#
|
130
|
+
def to_eventsource headers: {}
|
131
|
+
EventSource.new stream: self, headers: headers
|
132
|
+
end
|
133
|
+
|
123
134
|
protected
|
124
135
|
|
125
136
|
# bind parser events to this instance
|
@@ -174,5 +185,7 @@ module H2
|
|
174
185
|
end
|
175
186
|
|
176
187
|
end
|
188
|
+
|
189
|
+
class StreamError < StandardError; end
|
177
190
|
end
|
178
191
|
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
module H2
|
2
|
+
class Server
|
3
|
+
class Stream
|
4
|
+
class EventSource
|
5
|
+
include HeaderStringifier
|
6
|
+
|
7
|
+
DATA_TEMPL = "data: %s\n\n"
|
8
|
+
EVENT_TEMPL = "event: %s\n#{DATA_TEMPL}"
|
9
|
+
SSE_HEADER = {
|
10
|
+
STATUS_KEY => '200',
|
11
|
+
:content_type => 'text/event-stream'
|
12
|
+
}
|
13
|
+
|
14
|
+
# build and return +EventSource+ instance, ready for pushing out data
|
15
|
+
# or named events. checks accept header in the request, then responds
|
16
|
+
# with valid headers for beginning an SSE stream
|
17
|
+
#
|
18
|
+
# @param [H2::Server::Stream] stream: the +Stream+ instance
|
19
|
+
# @param [Hash] headers: optional headers to add to the intial response
|
20
|
+
#
|
21
|
+
# @return [H2::Server::Stream::EventSource]
|
22
|
+
#
|
23
|
+
def initialize stream:, headers: {}
|
24
|
+
@closed = false
|
25
|
+
@stream = stream
|
26
|
+
@parser = @stream.stream
|
27
|
+
@headers = headers
|
28
|
+
|
29
|
+
check_accept_header
|
30
|
+
init_response
|
31
|
+
end
|
32
|
+
|
33
|
+
# checks accept header in the request and raises a +StreamError+ if not
|
34
|
+
# valid for SSE
|
35
|
+
#
|
36
|
+
def check_accept_header
|
37
|
+
accept = @stream.request.headers['accept']
|
38
|
+
unless accept == SSE_HEADER[:content_type]
|
39
|
+
raise StreamError, "invalid header accept: #{accept}"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# responds with SSE headers on this stream
|
44
|
+
#
|
45
|
+
def init_response
|
46
|
+
headers = SSE_HEADER.merge @headers
|
47
|
+
@parser.headers stringify_headers(headers)
|
48
|
+
rescue ::HTTP2::Error::StreamClosed => sc
|
49
|
+
@stream.log :warn, "stream closed early by client"
|
50
|
+
end
|
51
|
+
|
52
|
+
# send out a named event with the given data
|
53
|
+
#
|
54
|
+
# this would be handled by `es.addEventListener('name', (msg)=>{})`
|
55
|
+
#
|
56
|
+
# @param [String] name: the name of the event
|
57
|
+
# @param [String] data: data associated with this event
|
58
|
+
#
|
59
|
+
def event name:, data:
|
60
|
+
e = EVENT_TEMPL % [name, data]
|
61
|
+
@parser.data e, end_stream: false
|
62
|
+
end
|
63
|
+
|
64
|
+
# send out a message with the given data
|
65
|
+
#
|
66
|
+
# this would be handled by `es.onmessage((msg)=>{})`
|
67
|
+
#
|
68
|
+
# @param [String] data associated with this event
|
69
|
+
#
|
70
|
+
def data str
|
71
|
+
d = DATA_TEMPL % str
|
72
|
+
@parser.data d, end_stream: false
|
73
|
+
end
|
74
|
+
|
75
|
+
# emit a final frame on this stream with +end_stream+ flag
|
76
|
+
#
|
77
|
+
def close
|
78
|
+
@parser.data ''
|
79
|
+
@closed = true
|
80
|
+
end
|
81
|
+
|
82
|
+
# @return [Boolean] true if this stream is closed
|
83
|
+
#
|
84
|
+
def closed?
|
85
|
+
@closed
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -44,7 +44,18 @@ module H2
|
|
44
44
|
# retreive the path from the stream request headers
|
45
45
|
#
|
46
46
|
def path
|
47
|
-
@path
|
47
|
+
return @path if defined?(@path)
|
48
|
+
@path = headers[PATH_KEY]
|
49
|
+
@path = @path.split('?').first if @path
|
50
|
+
end
|
51
|
+
|
52
|
+
# retreive the query string from the stream request headers
|
53
|
+
#
|
54
|
+
def query_string
|
55
|
+
return @query_string if defined?(@query_string)
|
56
|
+
@query_string = headers[PATH_KEY].index '?'
|
57
|
+
return if @query_string.nil?
|
58
|
+
@query_string = headers[PATH_KEY][(@query_string + 1)..-1]
|
48
59
|
end
|
49
60
|
|
50
61
|
# retreive the scheme from the stream request headers
|
@@ -2,16 +2,26 @@ module H2
|
|
2
2
|
class Server
|
3
3
|
class Stream
|
4
4
|
class Response
|
5
|
+
include HeaderStringifier
|
5
6
|
|
6
7
|
attr_reader :body, :content_length, :headers, :status, :stream
|
7
8
|
|
8
9
|
# build a new +Response+ object
|
9
10
|
#
|
11
|
+
# @param [H2::Server::Stream] stream: Stream instance associated with this response
|
12
|
+
# @param [Integer] status: HTTP status code
|
13
|
+
# @param [Hash] headers: response headers
|
14
|
+
# @param [String,#each] body: response body. NOTE: may be any object that
|
15
|
+
# `respond_to? :each` which both yields and returns
|
16
|
+
# String objects.
|
17
|
+
#
|
18
|
+
# @return [H2::Server::Stream::Response]
|
19
|
+
#
|
10
20
|
def initialize stream:, status:, headers: {}, body: ''
|
11
|
-
@stream
|
12
|
-
@headers
|
13
|
-
@body
|
14
|
-
|
21
|
+
@stream = stream
|
22
|
+
@headers = headers
|
23
|
+
@body = body
|
24
|
+
@status = status
|
15
25
|
|
16
26
|
init_content_length
|
17
27
|
end
|
@@ -19,20 +29,18 @@ module H2
|
|
19
29
|
# sets the content length in the headers by the byte size of +@body+
|
20
30
|
#
|
21
31
|
def init_content_length
|
22
|
-
@
|
23
|
-
|
32
|
+
return if @headers.any? {|k,_| k.downcase == CONTENT_LENGTH_KEY}
|
33
|
+
return if @body.respond_to?(:each)
|
34
|
+
@content_length = case
|
35
|
+
when String === @body
|
24
36
|
@body.bytesize
|
25
|
-
when IO
|
26
|
-
@body.stat.size
|
27
37
|
when NilClass
|
28
38
|
'0'
|
29
39
|
else
|
30
40
|
raise TypeError, "can't render #{@body.class} as a response body"
|
31
41
|
end
|
32
42
|
|
33
|
-
|
34
|
-
@headers[CONTENT_LENGTH_KEY] = @content_length
|
35
|
-
end
|
43
|
+
@headers[CONTENT_LENGTH_KEY] = @content_length
|
36
44
|
end
|
37
45
|
|
38
46
|
# the corresponding +Request+ to this +Response+
|
@@ -41,38 +49,21 @@ module H2
|
|
41
49
|
stream.request
|
42
50
|
end
|
43
51
|
|
44
|
-
# send the headers and body out on +s+, an +HTTP2::Stream+ object
|
52
|
+
# send the headers and body out on +s+, an +HTTP2::Stream+ object, and
|
53
|
+
# close the stream when complete.
|
45
54
|
#
|
46
55
|
# NOTE: +:status+ must come first?
|
47
56
|
#
|
48
57
|
def respond_on s
|
49
58
|
headers = { STATUS_KEY => @status.to_s }.merge @headers
|
50
59
|
s.headers stringify_headers(headers)
|
51
|
-
|
52
|
-
when String
|
60
|
+
if String === @body
|
53
61
|
s.data @body
|
54
|
-
when IO
|
55
|
-
raise NotImplementedError # TODO
|
56
|
-
else
|
57
|
-
end
|
58
|
-
end
|
59
|
-
|
60
|
-
# sets +@status+ either from given integer value (HTTP status code) or by
|
61
|
-
# mapping a +Symbol+ in +Reel::Response::SYMBOL_TO_STATUS_CODE+ to one
|
62
|
-
#
|
63
|
-
def status= status
|
64
|
-
case status
|
65
|
-
when Integer
|
66
|
-
@status = status
|
67
|
-
when Symbol
|
68
|
-
if code = ::Reel::Response::SYMBOL_TO_STATUS_CODE[status]
|
69
|
-
self.status = code
|
70
|
-
else
|
71
|
-
raise ArgumentError, "unrecognized status symbol: #{status}"
|
72
|
-
end
|
73
62
|
else
|
74
|
-
|
63
|
+
stream.log :error, "unexpected @body: #{caller[0]}"
|
75
64
|
end
|
65
|
+
rescue ::HTTP2::Error::StreamClosed => sc
|
66
|
+
stream.log :warn, "stream closed early by client"
|
76
67
|
end
|
77
68
|
|
78
69
|
def to_s
|
@@ -80,19 +71,6 @@ module H2
|
|
80
71
|
end
|
81
72
|
alias to_str to_s
|
82
73
|
|
83
|
-
private
|
84
|
-
|
85
|
-
def stringify_headers hash
|
86
|
-
hash.keys.each do |k|
|
87
|
-
hash[k] = hash[k].to_s unless String === hash[k]
|
88
|
-
if Symbol === k
|
89
|
-
key = k.to_s.gsub '_', '-'
|
90
|
-
hash[key] = hash.delete k
|
91
|
-
end
|
92
|
-
end
|
93
|
-
hash
|
94
|
-
end
|
95
|
-
|
96
74
|
end
|
97
75
|
end
|
98
76
|
end
|
data/lib/h2/version.rb
CHANGED
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.
|
4
|
+
version: 0.7.0
|
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-
|
11
|
+
date: 2018-08-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: http-2
|
@@ -108,6 +108,7 @@ files:
|
|
108
108
|
- examples/server/hello_world.rb
|
109
109
|
- examples/server/https_hello_world.rb
|
110
110
|
- examples/server/push_promise.rb
|
111
|
+
- examples/server/sse.rb
|
111
112
|
- exe/h2
|
112
113
|
- h2.gemspec
|
113
114
|
- lib/h2.rb
|
@@ -115,12 +116,12 @@ files:
|
|
115
116
|
- lib/h2/client/celluloid.rb
|
116
117
|
- lib/h2/client/concurrent.rb
|
117
118
|
- lib/h2/client/tcp_socket.rb
|
118
|
-
- lib/h2/reel/ext.rb
|
119
119
|
- lib/h2/server.rb
|
120
120
|
- lib/h2/server/connection.rb
|
121
121
|
- lib/h2/server/https.rb
|
122
122
|
- lib/h2/server/push_promise.rb
|
123
123
|
- lib/h2/server/stream.rb
|
124
|
+
- lib/h2/server/stream/event_source.rb
|
124
125
|
- lib/h2/server/stream/request.rb
|
125
126
|
- lib/h2/server/stream/response.rb
|
126
127
|
- lib/h2/stream.rb
|
data/lib/h2/reel/ext.rb
DELETED
@@ -1,48 +0,0 @@
|
|
1
|
-
require 'reel/connection'
|
2
|
-
require 'reel/request'
|
3
|
-
require 'reel/server'
|
4
|
-
|
5
|
-
# see also: https://github.com/celluloid/reel/pull/228
|
6
|
-
|
7
|
-
|
8
|
-
# this is a little sneaky, not as direct as the PR above, but the least
|
9
|
-
# invasive way i could come up with to get access to the server from the
|
10
|
-
# request.
|
11
|
-
|
12
|
-
module Reel
|
13
|
-
|
14
|
-
# we add a `server` accessor to +Connection+...
|
15
|
-
#
|
16
|
-
class Request
|
17
|
-
attr_reader :connection
|
18
|
-
end
|
19
|
-
|
20
|
-
# ... and a `connection` reader to +Request+.
|
21
|
-
#
|
22
|
-
class Connection
|
23
|
-
attr_accessor :server
|
24
|
-
end
|
25
|
-
|
26
|
-
end
|
27
|
-
|
28
|
-
module H2
|
29
|
-
module Reel
|
30
|
-
module ServerConnection
|
31
|
-
|
32
|
-
# then we hijack +Server+ construction, and wrap the callback at the last
|
33
|
-
# minute with one that sets the server on every connection, before
|
34
|
-
# calling the original.
|
35
|
-
#
|
36
|
-
def initialize server, options = {}, &callback
|
37
|
-
super
|
38
|
-
@og_callback = @callback
|
39
|
-
@callback = ->(conn) {
|
40
|
-
conn.server = self
|
41
|
-
@og_callback[conn]
|
42
|
-
}
|
43
|
-
end
|
44
|
-
end
|
45
|
-
|
46
|
-
::Reel::Server.prepend ServerConnection
|
47
|
-
end
|
48
|
-
end
|