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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: fa3ba996d357a12053551efdb6f4f60b89db4807
4
- data.tar.gz: 24e779cd44010f0d19158199c75f849ff9db37c2
2
+ SHA256:
3
+ metadata.gz: 01fed28540c96e99ddb6513b914d305459d44518385ae18449a718089bf206ff
4
+ data.tar.gz: adee017da6138cbe133f12d33ce01b2d1e9dbd73f1fbb5b694b4f7ef02e377be
5
5
  SHA512:
6
- metadata.gz: 5cd6a8a0d6e38d1374c1c0cde4e3c34cd46639a28e76df77e024281322570e33e1ed38afd5f62371604693fa5e4306675311f8b5a9913d90c225306b3c88a828
7
- data.tar.gz: ca98ca2db7715ac4ad8e7ec22b0eecdbceb76f31e5ba701026f5c450e0d38b0664c6e91030c2b624704213a2688446005c980bdad6cd335bba31d4128ae6b23d
6
+ metadata.gz: 3bba90e6ae993d0f9fa1654482760da4696cbd8858e2a609b4e45ac206e812ddd8ad09835d04e3be61ffc7c28e1b8e4876e3b8752309fb735826269af04af6c2
7
+ data.tar.gz: 455f543137a742a2c0b9f10408ee02b9aaf7f1546e6fec7a18815928c3d184abcaf54d4f8299356e10578df0f5cb9b2e4a05d76feaa1b9d60f6bdf98a7f2bed2
@@ -1,3 +1,7 @@
1
+ ### 1.1.0
2
+
3
+ * Added support for post bodies
4
+
1
5
  === 1.0.0 / 2015-06-23
2
6
 
3
7
  * 1 major enhancement
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', 'aaron@tenderlovemaking.com')
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']
@@ -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, &copy_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 VALUE session_submit_request(VALUE self, VALUE settings)
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
- Check_Type(settings, T_ARRAY);
512
- niv = RARRAY_LEN(settings);
513
- nva = xcalloc(niv, sizeof(nghttp2_nv));
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
- copy_list_to_nv(settings, nva, niv);
595
+ nva = xcalloc(niv, sizeof(nghttp2_nv));
516
596
 
517
- rv = nghttp2_submit_request(session, NULL, nva, niv, NULL, NULL);
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
- niv = RARRAY_LEN(headers);
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
- copy_list_to_nv(headers, nva, niv);
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
- rb_define_method(cDS9Session, "submit_request", session_submit_request, 1);
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);
@@ -2,6 +2,7 @@
2
2
  #define DS9_H
3
3
 
4
4
  #include <ruby.h>
5
+ #include <ruby/io.h>
5
6
  #include <nghttp2/nghttp2.h>
6
7
 
7
8
  static const rb_data_type_t ds9_session_type = {
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.0.0'
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
@@ -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
 
@@ -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.0.0
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: 2016-01-06 00:00:00.000000000 Z
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.8'
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.8'
26
+ version: '5.11'
27
27
  - !ruby/object:Gem::Dependency
28
- name: rdoc
28
+ name: rake-compiler
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - "~>"
31
+ - - ">="
32
32
  - !ruby/object:Gem::Version
33
- version: '4.0'
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: '4.0'
40
+ version: 0.4.1
41
41
  - !ruby/object:Gem::Dependency
42
- name: rake-compiler
42
+ name: rdoc
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
45
  - - ">="
46
46
  - !ruby/object:Gem::Version
47
- version: 0.4.1
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: 0.4.1
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.14'
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.14'
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
- - aaron@tenderlovemaking.com
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.5.1
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