ds9 1.0.0 → 1.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 +5 -5
- data/CHANGELOG.md +4 -0
- data/Rakefile +1 -1
- data/ext/ds9/ds9.c +120 -9
- data/ext/ds9/ds9.h +1 -0
- data/lib/ds9.rb +12 -1
- data/test/helper.rb +14 -3
- data/test/test_client.rb +121 -0
- metadata +22 -16
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 01fed28540c96e99ddb6513b914d305459d44518385ae18449a718089bf206ff
|
4
|
+
data.tar.gz: adee017da6138cbe133f12d33ce01b2d1e9dbd73f1fbb5b694b4f7ef02e377be
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3bba90e6ae993d0f9fa1654482760da4696cbd8858e2a609b4e45ac206e812ddd8ad09835d04e3be61ffc7c28e1b8e4876e3b8752309fb735826269af04af6c2
|
7
|
+
data.tar.gz: 455f543137a742a2c0b9f10408ee02b9aaf7f1546e6fec7a18815928c3d184abcaf54d4f8299356e10578df0f5cb9b2e4a05d76feaa1b9d60f6bdf98a7f2bed2
|
data/CHANGELOG.md
CHANGED
data/Rakefile
CHANGED
@@ -12,7 +12,7 @@ gem 'rake-compiler', '>= 0.4.1'
|
|
12
12
|
require "rake/extensiontask"
|
13
13
|
|
14
14
|
Hoe.spec 'ds9' do
|
15
|
-
developer('Aaron Patterson', '
|
15
|
+
developer('Aaron Patterson', 'tenderlove@ruby-lang.org')
|
16
16
|
self.readme_file = 'README.md'
|
17
17
|
self.history_file = 'CHANGELOG.md'
|
18
18
|
self.extra_rdoc_files = FileList['*.md']
|
data/ext/ds9/ds9.c
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
#include <ds9.h>
|
2
2
|
#include <assert.h>
|
3
3
|
|
4
|
+
typedef void (*copy_header_func_t)(VALUE, nghttp2_nv *, size_t);
|
5
|
+
|
4
6
|
VALUE mDS9;
|
5
7
|
VALUE cDS9Session;
|
6
8
|
VALUE cDS9Client;
|
@@ -300,6 +302,35 @@ static void copy_list_to_nv(VALUE list, nghttp2_nv * head, size_t niv)
|
|
300
302
|
}
|
301
303
|
}
|
302
304
|
|
305
|
+
struct hash_copy_ctx {
|
306
|
+
nghttp2_nv * head;
|
307
|
+
};
|
308
|
+
|
309
|
+
static int
|
310
|
+
hash_copy_i(VALUE name, VALUE value, struct hash_copy_ctx * ctx)
|
311
|
+
{
|
312
|
+
nghttp2_nv * head = ctx->head;
|
313
|
+
|
314
|
+
head->name = (uint8_t *)StringValuePtr(name);
|
315
|
+
head->namelen = RSTRING_LEN(name);
|
316
|
+
|
317
|
+
head->value = (uint8_t *)StringValuePtr(value);
|
318
|
+
head->valuelen = RSTRING_LEN(value);
|
319
|
+
head->flags = NGHTTP2_NV_FLAG_NONE;
|
320
|
+
|
321
|
+
ctx->head = head + 1;
|
322
|
+
|
323
|
+
return ST_CONTINUE;
|
324
|
+
}
|
325
|
+
|
326
|
+
static void copy_hash_to_nv(VALUE hash, nghttp2_nv * head, size_t niv)
|
327
|
+
{
|
328
|
+
struct hash_copy_ctx copy_ctx;
|
329
|
+
copy_ctx.head = head;
|
330
|
+
|
331
|
+
rb_hash_foreach(hash, hash_copy_i, ©_ctx);
|
332
|
+
}
|
333
|
+
|
303
334
|
static VALUE allocate_session(VALUE klass)
|
304
335
|
{
|
305
336
|
return TypedData_Wrap_Struct(klass, &ds9_session_type, 0);
|
@@ -498,23 +529,89 @@ static VALUE session_outbound_queue_size(VALUE self)
|
|
498
529
|
return INT2NUM(nghttp2_session_get_outbound_queue_size(session));
|
499
530
|
}
|
500
531
|
|
501
|
-
static
|
532
|
+
static ssize_t
|
533
|
+
ruby_read(nghttp2_session *session, int32_t stream_id, uint8_t *buf, size_t length,
|
534
|
+
uint32_t *data_flags, nghttp2_data_source *source, void *user_data)
|
535
|
+
{
|
536
|
+
VALUE ret = rb_funcall(source->ptr, rb_intern("read"), 1, INT2NUM(length));
|
537
|
+
|
538
|
+
if (NIL_P(ret)) {
|
539
|
+
*data_flags |= NGHTTP2_DATA_FLAG_EOF;
|
540
|
+
return 0;
|
541
|
+
} else {
|
542
|
+
memcpy(buf, RSTRING_PTR(ret), RSTRING_LEN(ret));
|
543
|
+
return RSTRING_LEN(ret);
|
544
|
+
}
|
545
|
+
}
|
546
|
+
|
547
|
+
static ssize_t
|
548
|
+
file_read(nghttp2_session *session, int32_t stream_id, uint8_t *buf, size_t length,
|
549
|
+
uint32_t *data_flags, nghttp2_data_source *source, void *user_data)
|
550
|
+
{
|
551
|
+
ssize_t nread;
|
552
|
+
rb_io_t * fptr;
|
553
|
+
|
554
|
+
fptr = (rb_io_t *)source->ptr;
|
555
|
+
rb_io_check_readable(fptr);
|
556
|
+
|
557
|
+
nread = read(fptr->fd, buf, length);
|
558
|
+
|
559
|
+
if (nread == -1) {
|
560
|
+
return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
|
561
|
+
}
|
562
|
+
|
563
|
+
if (nread == 0) {
|
564
|
+
*data_flags |= NGHTTP2_DATA_FLAG_EOF;
|
565
|
+
}
|
566
|
+
return nread;
|
567
|
+
}
|
568
|
+
|
569
|
+
static VALUE session_submit_request(VALUE self, VALUE settings, VALUE body)
|
502
570
|
{
|
503
571
|
size_t niv, i;
|
504
572
|
nghttp2_nv *nva, *head;
|
505
573
|
nghttp2_session *session;
|
574
|
+
nghttp2_data_provider provider;
|
506
575
|
int rv;
|
576
|
+
copy_header_func_t copy_func;
|
507
577
|
|
508
578
|
TypedData_Get_Struct(self, nghttp2_session, &ds9_session_type, session);
|
509
579
|
CheckSelf(session);
|
510
580
|
|
511
|
-
|
512
|
-
|
513
|
-
|
581
|
+
switch(TYPE(settings))
|
582
|
+
{
|
583
|
+
case T_ARRAY:
|
584
|
+
niv = RARRAY_LEN(settings);
|
585
|
+
copy_func = copy_list_to_nv;
|
586
|
+
break;
|
587
|
+
case T_HASH:
|
588
|
+
niv = RHASH_SIZE(settings);
|
589
|
+
copy_func = copy_hash_to_nv;
|
590
|
+
break;
|
591
|
+
default:
|
592
|
+
Check_Type(settings, T_ARRAY);
|
593
|
+
}
|
514
594
|
|
515
|
-
|
595
|
+
nva = xcalloc(niv, sizeof(nghttp2_nv));
|
516
596
|
|
517
|
-
|
597
|
+
copy_func(settings, nva, niv);
|
598
|
+
|
599
|
+
if (NIL_P(body)) {
|
600
|
+
rv = nghttp2_submit_request(session, NULL, nva, niv, NULL, NULL);
|
601
|
+
} else {
|
602
|
+
if (TYPE(body) == T_FILE) {
|
603
|
+
rb_io_t * rb_file;
|
604
|
+
GetOpenFile(body, rb_file);
|
605
|
+
/* Treat as a file descriptor */
|
606
|
+
provider.source.ptr = rb_file;
|
607
|
+
provider.read_callback = file_read;
|
608
|
+
} else {
|
609
|
+
provider.source.ptr = body;
|
610
|
+
provider.read_callback = ruby_read;
|
611
|
+
}
|
612
|
+
|
613
|
+
rv = nghttp2_submit_request(session, NULL, nva, niv, &provider, NULL);
|
614
|
+
}
|
518
615
|
|
519
616
|
xfree(nva);
|
520
617
|
|
@@ -632,14 +729,28 @@ static VALUE server_submit_response(VALUE self, VALUE stream_id, VALUE headers)
|
|
632
729
|
nghttp2_nv *nva, *head;
|
633
730
|
nghttp2_data_provider provider;
|
634
731
|
int rv;
|
732
|
+
copy_header_func_t copy_func;
|
635
733
|
|
636
734
|
TypedData_Get_Struct(self, nghttp2_session, &ds9_session_type, session);
|
637
735
|
CheckSelf(session);
|
638
736
|
|
639
|
-
|
737
|
+
switch(TYPE(headers))
|
738
|
+
{
|
739
|
+
case T_ARRAY:
|
740
|
+
niv = RARRAY_LEN(headers);
|
741
|
+
copy_func = copy_list_to_nv;
|
742
|
+
break;
|
743
|
+
case T_HASH:
|
744
|
+
niv = RHASH_SIZE(headers);
|
745
|
+
copy_func = copy_hash_to_nv;
|
746
|
+
break;
|
747
|
+
default:
|
748
|
+
Check_Type(headers, T_ARRAY);
|
749
|
+
}
|
750
|
+
|
640
751
|
nva = xcalloc(niv, sizeof(nghttp2_nv));
|
641
752
|
|
642
|
-
|
753
|
+
copy_func(headers, nva, niv);
|
643
754
|
|
644
755
|
provider.read_callback = rb_data_read_callback;
|
645
756
|
|
@@ -809,7 +920,7 @@ void Init_ds9(void)
|
|
809
920
|
rb_define_method(cDS9Session, "stream_local_closed?", session_stream_local_closed_p, 1);
|
810
921
|
rb_define_method(cDS9Session, "stream_remote_closed?", session_stream_remote_closed_p, 1);
|
811
922
|
|
812
|
-
|
923
|
+
rb_define_private_method(cDS9Session, "submit_request", session_submit_request, 2);
|
813
924
|
rb_define_private_method(cDS9Session, "make_callbacks", make_callbacks, 0);
|
814
925
|
rb_define_private_method(cDS9Client, "init_internals", client_init_internals, 1);
|
815
926
|
rb_define_private_method(cDS9Server, "init_internals", server_init_internals, 1);
|
data/ext/ds9/ds9.h
CHANGED
data/lib/ds9.rb
CHANGED
@@ -1,7 +1,8 @@
|
|
1
1
|
require 'ds9.so'
|
2
|
+
require 'stringio'
|
2
3
|
|
3
4
|
module DS9
|
4
|
-
VERSION = '1.
|
5
|
+
VERSION = '1.1.0'
|
5
6
|
|
6
7
|
module Frames
|
7
8
|
class Frame
|
@@ -103,4 +104,14 @@ module DS9
|
|
103
104
|
super(str)
|
104
105
|
end
|
105
106
|
end
|
107
|
+
|
108
|
+
class Client
|
109
|
+
def submit_request headers, body = nil
|
110
|
+
case body
|
111
|
+
when String
|
112
|
+
body = StringIO.new body
|
113
|
+
end
|
114
|
+
super(headers, body)
|
115
|
+
end
|
116
|
+
end
|
106
117
|
end
|
data/test/helper.rb
CHANGED
@@ -78,7 +78,7 @@ module DS9
|
|
78
78
|
include IOEvents
|
79
79
|
|
80
80
|
class Response
|
81
|
-
attr_reader :stream_id, :body
|
81
|
+
attr_reader :stream_id, :body, :headers
|
82
82
|
|
83
83
|
def initialize stream_id
|
84
84
|
@stream_id = stream_id
|
@@ -131,6 +131,7 @@ module DS9
|
|
131
131
|
def initialize read, write, app
|
132
132
|
@app = app
|
133
133
|
@read_streams = {}
|
134
|
+
@read_post_streams = {}
|
134
135
|
@write_streams = {}
|
135
136
|
super(read, write)
|
136
137
|
end
|
@@ -159,10 +160,13 @@ module DS9
|
|
159
160
|
end
|
160
161
|
|
161
162
|
def on_header name, value, frame, flags
|
163
|
+
if name == ":method" && value == "POST"
|
164
|
+
@read_post_streams[frame.stream_id] = []
|
165
|
+
end
|
162
166
|
@read_streams[frame.stream_id] << [name, value]
|
163
167
|
end
|
164
168
|
|
165
|
-
class Request < Struct.new :stream, :stream_id, :headers
|
169
|
+
class Request < Struct.new :stream, :stream_id, :headers, :body
|
166
170
|
def path
|
167
171
|
headers[':path']
|
168
172
|
end
|
@@ -183,13 +187,20 @@ module DS9
|
|
183
187
|
end
|
184
188
|
end
|
185
189
|
|
190
|
+
def on_data_chunk_recv id, data, flags
|
191
|
+
@read_post_streams[id] << data
|
192
|
+
end
|
193
|
+
|
186
194
|
def on_frame_recv frame
|
187
|
-
return unless frame.headers?
|
195
|
+
return unless (frame.data? || frame.headers?) && frame.end_stream?
|
188
196
|
|
189
197
|
req_headers = @read_streams[frame.stream_id]
|
190
198
|
|
191
199
|
response = Response.new(self, frame.stream_id, [])
|
192
200
|
request = Request.new(self, frame.stream_id, Hash[req_headers])
|
201
|
+
if @read_post_streams[frame.stream_id]
|
202
|
+
request.body = @read_post_streams[frame.stream_id].join
|
203
|
+
end
|
193
204
|
|
194
205
|
@app.call request, response
|
195
206
|
|
data/test/test_client.rb
CHANGED
@@ -131,6 +131,82 @@ class TestClient < DS9::TestCase
|
|
131
131
|
assert_equal ["omglolwut", "lololol"], responses.map(&:body).map(&:string)
|
132
132
|
end
|
133
133
|
|
134
|
+
def test_post
|
135
|
+
body = 'omglolwut'
|
136
|
+
|
137
|
+
server, client = pipe do |req, res|
|
138
|
+
case req.path
|
139
|
+
when '/'
|
140
|
+
res.submit_response [[":status", '200'],
|
141
|
+
["server", 'test server'],
|
142
|
+
["date", 'Sat, 27 Jun 2015 17:29:21 GMT'],
|
143
|
+
["X-Whatever", "blah"]]
|
144
|
+
res.finish req.body
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
s = Thread.new { server.run }
|
149
|
+
c = Thread.new { client.run }
|
150
|
+
|
151
|
+
client.submit_request [
|
152
|
+
[':method', 'POST'],
|
153
|
+
[':path', '/'],
|
154
|
+
[':scheme', 'https'],
|
155
|
+
[':authority', ['localhost', '8080'].join(':')],
|
156
|
+
['accept', '*/*'],
|
157
|
+
['user-agent', 'test'],
|
158
|
+
], body
|
159
|
+
|
160
|
+
responses = []
|
161
|
+
while response = client.responses.pop
|
162
|
+
responses << response
|
163
|
+
if responses.length == 1
|
164
|
+
client.terminate_session DS9::NO_ERROR
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
s.join
|
169
|
+
c.join
|
170
|
+
assert_equal [body], responses.map(&:body).map(&:string)
|
171
|
+
end
|
172
|
+
|
173
|
+
def test_post_file
|
174
|
+
server, client = pipe do |req, res|
|
175
|
+
case req.path
|
176
|
+
when '/'
|
177
|
+
res.submit_response [[":status", '200'],
|
178
|
+
["server", 'test server'],
|
179
|
+
["date", 'Sat, 27 Jun 2015 17:29:21 GMT'],
|
180
|
+
["X-Whatever", "blah"]]
|
181
|
+
res.finish req.body
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
s = Thread.new { server.run }
|
186
|
+
c = Thread.new { client.run }
|
187
|
+
|
188
|
+
client.submit_request [
|
189
|
+
[':method', 'POST'],
|
190
|
+
[':path', '/'],
|
191
|
+
[':scheme', 'https'],
|
192
|
+
[':authority', ['localhost', '8080'].join(':')],
|
193
|
+
['accept', '*/*'],
|
194
|
+
['user-agent', 'test'],
|
195
|
+
], File.open(__FILE__, "r")
|
196
|
+
|
197
|
+
responses = []
|
198
|
+
while response = client.responses.pop
|
199
|
+
responses << response
|
200
|
+
if responses.length == 1
|
201
|
+
client.terminate_session DS9::NO_ERROR
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
s.join
|
206
|
+
c.join
|
207
|
+
assert_equal File.read(__FILE__), responses.map(&:body).map(&:string).first
|
208
|
+
end
|
209
|
+
|
134
210
|
def test_request
|
135
211
|
body = 'omglolwut'
|
136
212
|
|
@@ -166,4 +242,49 @@ class TestClient < DS9::TestCase
|
|
166
242
|
c.join
|
167
243
|
assert_equal ["omglolwut"], responses.map(&:body).map(&:string)
|
168
244
|
end
|
245
|
+
|
246
|
+
def test_request_response_with_hashes
|
247
|
+
body = 'omglolwut'
|
248
|
+
|
249
|
+
req_hash = {
|
250
|
+
':method' => 'GET',
|
251
|
+
':path' => '/',
|
252
|
+
':scheme' => 'https',
|
253
|
+
':authority' => ['localhost', '8080'].join(':'),
|
254
|
+
'accept' => '*/*',
|
255
|
+
'user-agent' => 'test',
|
256
|
+
}
|
257
|
+
|
258
|
+
res_hash = {
|
259
|
+
":status" => '200',
|
260
|
+
"server" => 'test server',
|
261
|
+
"date" => 'Sat, 27 Jun 2015 17:29:21 GMT',
|
262
|
+
"x-whatever" => "blah"
|
263
|
+
}
|
264
|
+
|
265
|
+
server, client = pipe do |req, res|
|
266
|
+
assert_equal req_hash, req.headers
|
267
|
+
|
268
|
+
res.submit_response res_hash
|
269
|
+
res.finish body
|
270
|
+
end
|
271
|
+
|
272
|
+
client.submit_request req_hash
|
273
|
+
|
274
|
+
s = Thread.new { server.run }
|
275
|
+
c = Thread.new { client.run }
|
276
|
+
|
277
|
+
responses = []
|
278
|
+
while response = client.responses.pop
|
279
|
+
responses << response
|
280
|
+
if responses.length == 1
|
281
|
+
client.terminate_session DS9::NO_ERROR
|
282
|
+
end
|
283
|
+
end
|
284
|
+
|
285
|
+
s.join
|
286
|
+
c.join
|
287
|
+
assert_equal res_hash, responses.first.headers
|
288
|
+
assert_equal ["omglolwut"], responses.map(&:body).map(&:string)
|
289
|
+
end
|
169
290
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ds9
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Aaron Patterson
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2018-09-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: minitest
|
@@ -16,61 +16,67 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '5.
|
19
|
+
version: '5.11'
|
20
20
|
type: :development
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '5.
|
26
|
+
version: '5.11'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
|
-
name:
|
28
|
+
name: rake-compiler
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
|
-
- - "
|
31
|
+
- - ">="
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version:
|
33
|
+
version: 0.4.1
|
34
34
|
type: :development
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
|
-
- - "
|
38
|
+
- - ">="
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version:
|
40
|
+
version: 0.4.1
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
|
-
name:
|
42
|
+
name: rdoc
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
45
|
- - ">="
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version:
|
47
|
+
version: '4.0'
|
48
|
+
- - "<"
|
49
|
+
- !ruby/object:Gem::Version
|
50
|
+
version: '6'
|
48
51
|
type: :development
|
49
52
|
prerelease: false
|
50
53
|
version_requirements: !ruby/object:Gem::Requirement
|
51
54
|
requirements:
|
52
55
|
- - ">="
|
53
56
|
- !ruby/object:Gem::Version
|
54
|
-
version:
|
57
|
+
version: '4.0'
|
58
|
+
- - "<"
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '6'
|
55
61
|
- !ruby/object:Gem::Dependency
|
56
62
|
name: hoe
|
57
63
|
requirement: !ruby/object:Gem::Requirement
|
58
64
|
requirements:
|
59
65
|
- - "~>"
|
60
66
|
- !ruby/object:Gem::Version
|
61
|
-
version: '3.
|
67
|
+
version: '3.17'
|
62
68
|
type: :development
|
63
69
|
prerelease: false
|
64
70
|
version_requirements: !ruby/object:Gem::Requirement
|
65
71
|
requirements:
|
66
72
|
- - "~>"
|
67
73
|
- !ruby/object:Gem::Version
|
68
|
-
version: '3.
|
74
|
+
version: '3.17'
|
69
75
|
description: |-
|
70
76
|
This library allows you to write HTTP/2 clients and servers. It is a wrapper
|
71
77
|
around nghttp2.
|
72
78
|
email:
|
73
|
-
-
|
79
|
+
- tenderlove@ruby-lang.org
|
74
80
|
executables: []
|
75
81
|
extensions:
|
76
82
|
- ext/ds9/extconf.rb
|
@@ -114,7 +120,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
114
120
|
version: '0'
|
115
121
|
requirements: []
|
116
122
|
rubyforge_project:
|
117
|
-
rubygems_version: 2.
|
123
|
+
rubygems_version: 2.7.6
|
118
124
|
signing_key:
|
119
125
|
specification_version: 4
|
120
126
|
summary: This library allows you to write HTTP/2 clients and servers
|