ds9 1.0.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/.autotest +8 -0
- data/CHANGELOG.md +6 -0
- data/Manifest.txt +13 -0
- data/README.md +185 -0
- data/Rakefile +31 -0
- data/ext/ds9/ds9.c +838 -0
- data/ext/ds9/ds9.h +30 -0
- data/ext/ds9/ds9_frames.c +164 -0
- data/ext/ds9/extconf.rb +16 -0
- data/lib/ds9.rb +106 -0
- data/test/helper.rb +201 -0
- data/test/test_client.rb +169 -0
- data/test/test_ds9.rb +45 -0
- metadata +121 -0
data/ext/ds9/ds9.h
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
#ifndef DS9_H
|
2
|
+
#define DS9_H
|
3
|
+
|
4
|
+
#include <ruby.h>
|
5
|
+
#include <nghttp2/nghttp2.h>
|
6
|
+
|
7
|
+
static const rb_data_type_t ds9_session_type = {
|
8
|
+
"DS9/session",
|
9
|
+
{0, (void (*)(void *))nghttp2_session_del, 0,},
|
10
|
+
0, 0,
|
11
|
+
#ifdef RUBY_TYPED_FREE_IMMEDIATELY
|
12
|
+
RUBY_TYPED_FREE_IMMEDIATELY,
|
13
|
+
#endif
|
14
|
+
};
|
15
|
+
|
16
|
+
static const rb_data_type_t ds9_callbacks_type = {
|
17
|
+
"DS9/callbacks",
|
18
|
+
{0, (void (*)(void *))nghttp2_session_callbacks_del, 0,},
|
19
|
+
0, 0,
|
20
|
+
#ifdef RUBY_TYPED_FREE_IMMEDIATELY
|
21
|
+
RUBY_TYPED_FREE_IMMEDIATELY,
|
22
|
+
#endif
|
23
|
+
};
|
24
|
+
|
25
|
+
void Init_ds9_client(VALUE mDS9, VALUE cDS9Session);
|
26
|
+
void Init_ds9_frames(VALUE mDS9);
|
27
|
+
VALUE WrapDS9Frame(const nghttp2_frame *frame);
|
28
|
+
VALUE WrapDS9FrameHeader(const nghttp2_frame_hd *hd);
|
29
|
+
|
30
|
+
#endif
|
@@ -0,0 +1,164 @@
|
|
1
|
+
#include <ds9.h>
|
2
|
+
|
3
|
+
VALUE mDS9Frames;
|
4
|
+
|
5
|
+
VALUE cDS9FramesFrame;
|
6
|
+
VALUE mDS9FramesFlags;
|
7
|
+
VALUE cDS9FramesData;
|
8
|
+
VALUE cDS9FramesHeaders;
|
9
|
+
VALUE cDS9FramesPriority;
|
10
|
+
VALUE cDS9FramesRstStream;
|
11
|
+
VALUE cDS9FramesSettings;
|
12
|
+
VALUE cDS9FramesPushPromise;
|
13
|
+
VALUE cDS9FramesPing;
|
14
|
+
VALUE cDS9FramesGoaway;
|
15
|
+
VALUE cDS9FramesWindowUpdate;
|
16
|
+
VALUE cDS9FramesContinuation;
|
17
|
+
|
18
|
+
static const rb_data_type_t ds9_frame_type = {
|
19
|
+
"DS9/frame",
|
20
|
+
{0, xfree, 0,},
|
21
|
+
0, 0,
|
22
|
+
#ifdef RUBY_TYPED_FREE_IMMEDIATELY
|
23
|
+
RUBY_TYPED_FREE_IMMEDIATELY,
|
24
|
+
#endif
|
25
|
+
};
|
26
|
+
|
27
|
+
static VALUE frame_type_class(nghttp2_frame *frame)
|
28
|
+
{
|
29
|
+
switch(frame->hd.type) {
|
30
|
+
case NGHTTP2_DATA: return cDS9FramesData;
|
31
|
+
case NGHTTP2_HEADERS: return cDS9FramesHeaders;
|
32
|
+
case NGHTTP2_PRIORITY: return cDS9FramesPriority;
|
33
|
+
case NGHTTP2_RST_STREAM: return cDS9FramesRstStream;
|
34
|
+
case NGHTTP2_SETTINGS: return cDS9FramesSettings;
|
35
|
+
case NGHTTP2_PUSH_PROMISE: return cDS9FramesPushPromise;
|
36
|
+
case NGHTTP2_PING: return cDS9FramesPing;
|
37
|
+
case NGHTTP2_GOAWAY: return cDS9FramesGoaway;
|
38
|
+
case NGHTTP2_WINDOW_UPDATE: return cDS9FramesWindowUpdate;
|
39
|
+
case NGHTTP2_CONTINUATION: return cDS9FramesContinuation;
|
40
|
+
default: return cDS9FramesFrame;
|
41
|
+
}
|
42
|
+
}
|
43
|
+
|
44
|
+
VALUE WrapDS9FrameHeader(const nghttp2_frame_hd *hd)
|
45
|
+
{
|
46
|
+
VALUE klass = rb_const_get(cDS9FramesFrame, rb_intern("Header"));
|
47
|
+
return rb_funcall(klass, rb_intern("new"), 4,
|
48
|
+
INT2NUM(hd->length),
|
49
|
+
INT2NUM(hd->stream_id),
|
50
|
+
INT2NUM(hd->type),
|
51
|
+
INT2NUM(hd->flags));
|
52
|
+
}
|
53
|
+
|
54
|
+
VALUE WrapDS9Frame(const nghttp2_frame *frame)
|
55
|
+
{
|
56
|
+
/* dup the frame so Ruby can manage the struct's memory */
|
57
|
+
nghttp2_frame *dup = xmalloc(sizeof(nghttp2_frame));
|
58
|
+
memcpy(dup, frame, sizeof(nghttp2_frame));
|
59
|
+
|
60
|
+
return TypedData_Wrap_Struct(frame_type_class(dup), &ds9_frame_type, dup);
|
61
|
+
}
|
62
|
+
|
63
|
+
static VALUE frame_stream_id(VALUE self)
|
64
|
+
{
|
65
|
+
nghttp2_frame *frame;
|
66
|
+
TypedData_Get_Struct(self, nghttp2_frame, &ds9_frame_type, frame);
|
67
|
+
|
68
|
+
return INT2NUM(frame->hd.stream_id);
|
69
|
+
}
|
70
|
+
|
71
|
+
static VALUE frame_type(VALUE self)
|
72
|
+
{
|
73
|
+
nghttp2_frame *frame;
|
74
|
+
TypedData_Get_Struct(self, nghttp2_frame, &ds9_frame_type, frame);
|
75
|
+
|
76
|
+
return INT2NUM(frame->hd.type);
|
77
|
+
}
|
78
|
+
|
79
|
+
static VALUE frame_flags(VALUE self)
|
80
|
+
{
|
81
|
+
nghttp2_frame *frame;
|
82
|
+
TypedData_Get_Struct(self, nghttp2_frame, &ds9_frame_type, frame);
|
83
|
+
|
84
|
+
return INT2NUM(frame->hd.flags);
|
85
|
+
}
|
86
|
+
|
87
|
+
static VALUE promised_stream_id(VALUE self)
|
88
|
+
{
|
89
|
+
nghttp2_frame *frame;
|
90
|
+
TypedData_Get_Struct(self, nghttp2_frame, &ds9_frame_type, frame);
|
91
|
+
|
92
|
+
return INT2NUM(frame->push_promise.promised_stream_id);
|
93
|
+
}
|
94
|
+
|
95
|
+
static VALUE frame_header(VALUE self)
|
96
|
+
{
|
97
|
+
nghttp2_frame *frame;
|
98
|
+
TypedData_Get_Struct(self, nghttp2_frame, &ds9_frame_type, frame);
|
99
|
+
|
100
|
+
return WrapDS9FrameHeader((nghttp2_frame_hd *)frame);
|
101
|
+
}
|
102
|
+
|
103
|
+
static VALUE header_category(VALUE self)
|
104
|
+
{
|
105
|
+
nghttp2_frame *frame;
|
106
|
+
TypedData_Get_Struct(self, nghttp2_frame, &ds9_frame_type, frame);
|
107
|
+
|
108
|
+
return INT2NUM(frame->headers.cat);
|
109
|
+
}
|
110
|
+
|
111
|
+
void Init_ds9_frames(VALUE mDS9)
|
112
|
+
{
|
113
|
+
mDS9Frames = rb_define_module_under(mDS9, "Frames");
|
114
|
+
cDS9FramesFrame = rb_define_class_under(mDS9Frames, "Frame", rb_cData);
|
115
|
+
|
116
|
+
mDS9FramesFlags = rb_define_module_under(cDS9FramesFrame, "Flags");
|
117
|
+
|
118
|
+
cDS9FramesData = rb_define_class_under(mDS9Frames, "Data", cDS9FramesFrame);
|
119
|
+
cDS9FramesHeaders = rb_define_class_under(mDS9Frames, "Headers", cDS9FramesFrame);
|
120
|
+
cDS9FramesPriority = rb_define_class_under(mDS9Frames, "Priority", cDS9FramesFrame);
|
121
|
+
cDS9FramesRstStream = rb_define_class_under(mDS9Frames, "RstStream", cDS9FramesFrame);
|
122
|
+
cDS9FramesSettings = rb_define_class_under(mDS9Frames, "Settings", cDS9FramesFrame);
|
123
|
+
cDS9FramesPushPromise = rb_define_class_under(mDS9Frames, "PushPromise", cDS9FramesFrame);
|
124
|
+
cDS9FramesPing = rb_define_class_under(mDS9Frames, "Ping", cDS9FramesFrame);
|
125
|
+
cDS9FramesGoaway = rb_define_class_under(mDS9Frames, "Goaway", cDS9FramesFrame);
|
126
|
+
cDS9FramesWindowUpdate = rb_define_class_under(mDS9Frames, "WindowUpdate", cDS9FramesFrame);
|
127
|
+
cDS9FramesContinuation = rb_define_class_under(mDS9Frames, "Continuation", cDS9FramesFrame);
|
128
|
+
|
129
|
+
rb_define_method(cDS9FramesFrame, "stream_id", frame_stream_id, 0);
|
130
|
+
rb_define_method(cDS9FramesFrame, "type", frame_type, 0);
|
131
|
+
rb_define_method(cDS9FramesFrame, "flags", frame_flags, 0);
|
132
|
+
rb_define_method(cDS9FramesFrame, "header", frame_header, 0);
|
133
|
+
rb_define_method(cDS9FramesPushPromise, "promised_stream_id", promised_stream_id, 0);
|
134
|
+
rb_define_method(cDS9FramesHeaders, "category", header_category, 0);
|
135
|
+
|
136
|
+
rb_define_const(mDS9FramesFlags, "NONE", INT2NUM(NGHTTP2_FLAG_NONE));
|
137
|
+
rb_define_const(mDS9FramesFlags, "END_STREAM", INT2NUM(NGHTTP2_FLAG_END_STREAM));
|
138
|
+
rb_define_const(mDS9FramesFlags, "END_HEADERS", INT2NUM(NGHTTP2_FLAG_END_HEADERS));
|
139
|
+
rb_define_const(mDS9FramesFlags, "ACK", INT2NUM(NGHTTP2_FLAG_ACK));
|
140
|
+
rb_define_const(mDS9FramesFlags, "PADDED", INT2NUM(NGHTTP2_FLAG_PADDED));
|
141
|
+
rb_define_const(mDS9FramesFlags, "PRIORITY", INT2NUM(NGHTTP2_FLAG_PRIORITY));
|
142
|
+
|
143
|
+
rb_define_const(cDS9FramesGoaway, "NO_ERROR", INT2NUM(NGHTTP2_NO_ERROR));
|
144
|
+
rb_define_const(cDS9FramesGoaway, "PROTOCOL_ERROR", INT2NUM(NGHTTP2_PROTOCOL_ERROR));
|
145
|
+
rb_define_const(cDS9FramesGoaway, "INTERNAL_ERROR", INT2NUM(NGHTTP2_INTERNAL_ERROR));
|
146
|
+
rb_define_const(cDS9FramesGoaway, "FLOW_CONTROL_ERROR", INT2NUM(NGHTTP2_FLOW_CONTROL_ERROR));
|
147
|
+
rb_define_const(cDS9FramesGoaway, "SETTINGS_TIMEOUT", INT2NUM(NGHTTP2_SETTINGS_TIMEOUT));
|
148
|
+
rb_define_const(cDS9FramesGoaway, "STREAM_CLOSED", INT2NUM(NGHTTP2_STREAM_CLOSED));
|
149
|
+
rb_define_const(cDS9FramesGoaway, "FRAME_SIZE_ERROR", INT2NUM(NGHTTP2_FRAME_SIZE_ERROR));
|
150
|
+
rb_define_const(cDS9FramesGoaway, "REFUSED_STREAM", INT2NUM(NGHTTP2_REFUSED_STREAM));
|
151
|
+
rb_define_const(cDS9FramesGoaway, "CANCEL", INT2NUM(NGHTTP2_CANCEL));
|
152
|
+
rb_define_const(cDS9FramesGoaway, "COMPRESSION_ERROR", INT2NUM(NGHTTP2_COMPRESSION_ERROR));
|
153
|
+
rb_define_const(cDS9FramesGoaway, "CONNECT_ERROR", INT2NUM(NGHTTP2_CONNECT_ERROR));
|
154
|
+
rb_define_const(cDS9FramesGoaway, "ENHANCE_YOUR_CALM", INT2NUM(NGHTTP2_ENHANCE_YOUR_CALM));
|
155
|
+
rb_define_const(cDS9FramesGoaway, "INADEQUATE_SECURITY", INT2NUM(NGHTTP2_INADEQUATE_SECURITY));
|
156
|
+
rb_define_const(cDS9FramesGoaway, "HTTP_1_1_REQUIRED", INT2NUM(NGHTTP2_HTTP_1_1_REQUIRED));
|
157
|
+
|
158
|
+
rb_define_const(cDS9FramesHeaders, "REQUEST", INT2NUM(NGHTTP2_HCAT_REQUEST));
|
159
|
+
rb_define_const(cDS9FramesHeaders, "RESPONSE", INT2NUM(NGHTTP2_HCAT_RESPONSE));
|
160
|
+
rb_define_const(cDS9FramesHeaders, "PUSH_RESPONSE", INT2NUM(NGHTTP2_HCAT_PUSH_RESPONSE));
|
161
|
+
rb_define_const(cDS9FramesHeaders, "HEADERS", INT2NUM(NGHTTP2_HCAT_HEADERS));
|
162
|
+
}
|
163
|
+
|
164
|
+
/* vim: set noet sws=4 sw=4: */
|
data/ext/ds9/extconf.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'mkmf'
|
2
|
+
|
3
|
+
if with_config("static")
|
4
|
+
ldflags = pkg_config 'libnghttp2', 'libs-only-L'
|
5
|
+
|
6
|
+
archive = File.join ldflags.sub(/^-L/, ''), 'libnghttp2.a'
|
7
|
+
if File.exist? archive
|
8
|
+
$LDFLAGS << " #{archive}"
|
9
|
+
else
|
10
|
+
raise "couldn't find archive"
|
11
|
+
end
|
12
|
+
else
|
13
|
+
pkg_config 'libnghttp2'
|
14
|
+
end
|
15
|
+
|
16
|
+
create_makefile 'ds9'
|
data/lib/ds9.rb
ADDED
@@ -0,0 +1,106 @@
|
|
1
|
+
require 'ds9.so'
|
2
|
+
|
3
|
+
module DS9
|
4
|
+
VERSION = '1.0.0'
|
5
|
+
|
6
|
+
module Frames
|
7
|
+
class Frame
|
8
|
+
Header = Struct.new :length, :stream_id, :type, :flags
|
9
|
+
|
10
|
+
def length
|
11
|
+
header.length
|
12
|
+
end
|
13
|
+
|
14
|
+
def settings?; false; end
|
15
|
+
def headers?; false; end
|
16
|
+
def data?; false; end
|
17
|
+
def push_promise?; false; end
|
18
|
+
def priority?; false; end
|
19
|
+
def rst_stream?; false; end
|
20
|
+
def ping?; false; end
|
21
|
+
def goaway?; false; end
|
22
|
+
def window_update?; false; end
|
23
|
+
def continuation?; false; end
|
24
|
+
|
25
|
+
def end_stream?
|
26
|
+
flags & Flags::END_STREAM > 0
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class Continuation
|
31
|
+
def continuation?; true; end
|
32
|
+
end
|
33
|
+
class WindowUpdate
|
34
|
+
def window_update?; true; end
|
35
|
+
end
|
36
|
+
|
37
|
+
class Goaway
|
38
|
+
def goaway?; true; end
|
39
|
+
end
|
40
|
+
|
41
|
+
class Ping
|
42
|
+
def ping?; true; end
|
43
|
+
end
|
44
|
+
|
45
|
+
class Priority
|
46
|
+
def priority?; true; end
|
47
|
+
end
|
48
|
+
|
49
|
+
class RstStream
|
50
|
+
def rst_stream?; true; end
|
51
|
+
end
|
52
|
+
|
53
|
+
class PushPromise
|
54
|
+
def push_promise?; true; end
|
55
|
+
end
|
56
|
+
|
57
|
+
class Data
|
58
|
+
def data?; true; end
|
59
|
+
end
|
60
|
+
|
61
|
+
class Settings
|
62
|
+
def settings?; true; end
|
63
|
+
end
|
64
|
+
|
65
|
+
class Headers
|
66
|
+
def headers?; true; end
|
67
|
+
def request?; category == REQUEST; end
|
68
|
+
def response?; category == RESPONSE; end
|
69
|
+
def push_response?; category == PUSH_RESPONSE; end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
class Session
|
74
|
+
def initialize
|
75
|
+
cbs = make_callbacks
|
76
|
+
init_internals cbs
|
77
|
+
end
|
78
|
+
|
79
|
+
private
|
80
|
+
|
81
|
+
def send_event string
|
82
|
+
raise NotImplementedError
|
83
|
+
end
|
84
|
+
|
85
|
+
def on_data_source_read stream_id, length
|
86
|
+
raise NotImplementedError
|
87
|
+
end
|
88
|
+
|
89
|
+
def recv_event length
|
90
|
+
raise NotImplementedError
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
class Exception < StandardError
|
95
|
+
def self.abort code
|
96
|
+
raise new(to_string(code), code)
|
97
|
+
end
|
98
|
+
|
99
|
+
attr_reader :code
|
100
|
+
|
101
|
+
def initialize str, code
|
102
|
+
@code = code
|
103
|
+
super(str)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
data/test/helper.rb
ADDED
@@ -0,0 +1,201 @@
|
|
1
|
+
require 'minitest/autorun'
|
2
|
+
require 'ds9'
|
3
|
+
require 'io/wait'
|
4
|
+
require 'thread'
|
5
|
+
require 'stringio'
|
6
|
+
|
7
|
+
Thread.abort_on_exception = true
|
8
|
+
|
9
|
+
trap("INFO") {
|
10
|
+
Thread.list.each do |k|
|
11
|
+
puts "#" * 90
|
12
|
+
puts k.backtrace
|
13
|
+
puts "#" * 90
|
14
|
+
end
|
15
|
+
}
|
16
|
+
|
17
|
+
module DS9
|
18
|
+
class TestCase < Minitest::Test
|
19
|
+
def pipe &block
|
20
|
+
rd1, wr1 = IO.pipe
|
21
|
+
rd2, wr2 = IO.pipe
|
22
|
+
|
23
|
+
server = Server.new rd1, wr2, block
|
24
|
+
server.submit_settings [
|
25
|
+
[DS9::Settings::MAX_CONCURRENT_STREAMS, 100],
|
26
|
+
]
|
27
|
+
|
28
|
+
client = Client.new rd2, wr1, Queue.new
|
29
|
+
client.submit_settings [
|
30
|
+
[DS9::Settings::MAX_CONCURRENT_STREAMS, 100],
|
31
|
+
[DS9::Settings::INITIAL_WINDOW_SIZE, 65535],
|
32
|
+
]
|
33
|
+
[server, client]
|
34
|
+
end
|
35
|
+
|
36
|
+
module IOEvents
|
37
|
+
attr_reader :reader, :writer
|
38
|
+
|
39
|
+
def initialize reader, writer
|
40
|
+
@reader = reader
|
41
|
+
@writer = writer
|
42
|
+
super()
|
43
|
+
end
|
44
|
+
|
45
|
+
def send_event string
|
46
|
+
writer.write_nonblock string
|
47
|
+
end
|
48
|
+
|
49
|
+
def recv_event length
|
50
|
+
case data = reader.read_nonblock(length, nil, exception: false)
|
51
|
+
when :wait_readable
|
52
|
+
DS9::ERR_WOULDBLOCK
|
53
|
+
when nil
|
54
|
+
DS9::ERR_EOF
|
55
|
+
else
|
56
|
+
data
|
57
|
+
end
|
58
|
+
rescue IOError
|
59
|
+
DS9::ERR_EOF
|
60
|
+
end
|
61
|
+
|
62
|
+
def run
|
63
|
+
while want_read? || want_write?
|
64
|
+
if want_write?
|
65
|
+
@writer.wait_writable 0.1
|
66
|
+
send
|
67
|
+
end
|
68
|
+
|
69
|
+
if want_read?
|
70
|
+
@reader.wait_readable 0.1
|
71
|
+
receive
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
class Client < DS9::Client
|
78
|
+
include IOEvents
|
79
|
+
|
80
|
+
class Response
|
81
|
+
attr_reader :stream_id, :body
|
82
|
+
|
83
|
+
def initialize stream_id
|
84
|
+
@stream_id = stream_id
|
85
|
+
@headers = {}
|
86
|
+
@body = StringIO.new
|
87
|
+
end
|
88
|
+
|
89
|
+
def [] k; @headers[k]; end
|
90
|
+
def []= k, v; @headers[k] = v; end
|
91
|
+
end
|
92
|
+
|
93
|
+
attr_reader :responses, :response_queue, :frames
|
94
|
+
|
95
|
+
def initialize read, write, response_queue
|
96
|
+
@response_streams = {}
|
97
|
+
@responses = response_queue
|
98
|
+
@frames = []
|
99
|
+
super(read, write)
|
100
|
+
end
|
101
|
+
|
102
|
+
def on_frame_recv frame
|
103
|
+
@frames << frame
|
104
|
+
end
|
105
|
+
|
106
|
+
def on_begin_headers frame
|
107
|
+
@response_streams[frame.stream_id] = Response.new(frame.stream_id)
|
108
|
+
end
|
109
|
+
|
110
|
+
def on_header name, value, frame, flags
|
111
|
+
@response_streams[frame.stream_id][name] = value
|
112
|
+
end
|
113
|
+
|
114
|
+
def on_stream_close id, err
|
115
|
+
@responses << @response_streams.delete(id)
|
116
|
+
end
|
117
|
+
|
118
|
+
def on_data_chunk_recv stream_id, data, flags
|
119
|
+
@response_streams[stream_id].body << data
|
120
|
+
end
|
121
|
+
|
122
|
+
def terminate_session err
|
123
|
+
super
|
124
|
+
@responses << nil
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
class Server < DS9::Server
|
129
|
+
include IOEvents
|
130
|
+
|
131
|
+
def initialize read, write, app
|
132
|
+
@app = app
|
133
|
+
@read_streams = {}
|
134
|
+
@write_streams = {}
|
135
|
+
super(read, write)
|
136
|
+
end
|
137
|
+
|
138
|
+
def before_frame_send frame
|
139
|
+
end
|
140
|
+
|
141
|
+
def on_begin_headers frame
|
142
|
+
@read_streams[frame.stream_id] = []
|
143
|
+
end
|
144
|
+
|
145
|
+
def on_data_source_read stream_id, length
|
146
|
+
@write_streams[stream_id].body.shift
|
147
|
+
end
|
148
|
+
|
149
|
+
def on_stream_close id, error_code
|
150
|
+
@read_streams.delete id
|
151
|
+
@write_streams.delete id
|
152
|
+
end
|
153
|
+
|
154
|
+
def submit_push_promise stream_id, headers
|
155
|
+
response = Response.new(self, super(stream_id, headers), [])
|
156
|
+
request = Request.new(self, stream_id, Hash[headers])
|
157
|
+
@app.call request, response
|
158
|
+
@write_streams[response.stream_id] = response
|
159
|
+
end
|
160
|
+
|
161
|
+
def on_header name, value, frame, flags
|
162
|
+
@read_streams[frame.stream_id] << [name, value]
|
163
|
+
end
|
164
|
+
|
165
|
+
class Request < Struct.new :stream, :stream_id, :headers
|
166
|
+
def path
|
167
|
+
headers[':path']
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
class Response < Struct.new :stream, :stream_id, :body
|
172
|
+
def push headers
|
173
|
+
stream.submit_push_promise stream_id, headers
|
174
|
+
end
|
175
|
+
|
176
|
+
def submit_response headers
|
177
|
+
stream.submit_response stream_id, headers
|
178
|
+
end
|
179
|
+
|
180
|
+
def finish str
|
181
|
+
body << str
|
182
|
+
body << nil
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
def on_frame_recv frame
|
187
|
+
return unless frame.headers?
|
188
|
+
|
189
|
+
req_headers = @read_streams[frame.stream_id]
|
190
|
+
|
191
|
+
response = Response.new(self, frame.stream_id, [])
|
192
|
+
request = Request.new(self, frame.stream_id, Hash[req_headers])
|
193
|
+
|
194
|
+
@app.call request, response
|
195
|
+
|
196
|
+
@write_streams[frame.stream_id] = response
|
197
|
+
end
|
198
|
+
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|