polyphony-http 0.24

Sign up to get free protection for your applications and to get access to all the features.
Files changed (58) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +56 -0
  3. data/CHANGELOG.md +6 -0
  4. data/Gemfile +3 -0
  5. data/Gemfile.lock +51 -0
  6. data/LICENSE +21 -0
  7. data/README.md +47 -0
  8. data/Rakefile +20 -0
  9. data/TODO.md +59 -0
  10. data/bin/poly +11 -0
  11. data/docs/README.md +38 -0
  12. data/docs/summary.md +60 -0
  13. data/examples/cuba.ru +22 -0
  14. data/examples/happy_eyeballs.rb +37 -0
  15. data/examples/http2_raw.rb +135 -0
  16. data/examples/http_client.rb +28 -0
  17. data/examples/http_get.rb +33 -0
  18. data/examples/http_parse_experiment.rb +123 -0
  19. data/examples/http_proxy.rb +83 -0
  20. data/examples/http_server.js +24 -0
  21. data/examples/http_server.rb +21 -0
  22. data/examples/http_server_forked.rb +29 -0
  23. data/examples/http_server_graceful.rb +27 -0
  24. data/examples/http_server_simple.rb +11 -0
  25. data/examples/http_server_throttled.rb +15 -0
  26. data/examples/http_server_timeout.rb +35 -0
  27. data/examples/http_ws_server.rb +37 -0
  28. data/examples/https_raw_client.rb +12 -0
  29. data/examples/https_server.rb +22 -0
  30. data/examples/https_wss_server.rb +39 -0
  31. data/examples/rack_server.rb +12 -0
  32. data/examples/rack_server_https.rb +19 -0
  33. data/examples/rack_server_https_forked.rb +27 -0
  34. data/examples/websocket_secure_server.rb +27 -0
  35. data/examples/websocket_server.rb +24 -0
  36. data/examples/ws_page.html +34 -0
  37. data/examples/wss_page.html +34 -0
  38. data/lib/polyphony/http.rb +16 -0
  39. data/lib/polyphony/http/client/agent.rb +131 -0
  40. data/lib/polyphony/http/client/http1.rb +129 -0
  41. data/lib/polyphony/http/client/http2.rb +180 -0
  42. data/lib/polyphony/http/client/response.rb +32 -0
  43. data/lib/polyphony/http/client/site_connection_manager.rb +109 -0
  44. data/lib/polyphony/http/server.rb +49 -0
  45. data/lib/polyphony/http/server/http1.rb +267 -0
  46. data/lib/polyphony/http/server/http2.rb +78 -0
  47. data/lib/polyphony/http/server/http2_stream.rb +135 -0
  48. data/lib/polyphony/http/server/rack.rb +64 -0
  49. data/lib/polyphony/http/server/request.rb +118 -0
  50. data/lib/polyphony/http/version.rb +7 -0
  51. data/lib/polyphony/websocket.rb +59 -0
  52. data/polyphony-http.gemspec +34 -0
  53. data/test/coverage.rb +45 -0
  54. data/test/eg.rb +27 -0
  55. data/test/helper.rb +35 -0
  56. data/test/run.rb +5 -0
  57. data/test/test_http_server.rb +313 -0
  58. metadata +245 -0
data/test/eg.rb ADDED
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kernel
4
+ RE_CONST = /^[A-Z]/.freeze
5
+ RE_ATTR = /^@(.+)$/.freeze
6
+
7
+ def eg(hash)
8
+ Module.new.tap do |m|
9
+ s = m.singleton_class
10
+ hash.each do |k, v|
11
+ case k
12
+ when RE_CONST
13
+ m.const_set(k, v)
14
+ when RE_ATTR
15
+ m.instance_variable_set(k, v)
16
+ else
17
+ block = if v.respond_to?(:to_proc)
18
+ proc { |*args| instance_exec(*args, &v) }
19
+ else
20
+ proc { v }
21
+ end
22
+ s.define_method(k, &block)
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
data/test/helper.rb ADDED
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+
5
+ require 'fileutils'
6
+ require_relative './eg'
7
+
8
+ require_relative './coverage' if ENV['COVERAGE']
9
+
10
+ require 'minitest/autorun'
11
+ require 'minitest/reporters'
12
+
13
+ require 'polyphony'
14
+
15
+ ::Exception.__disable_sanitized_backtrace__ = true
16
+
17
+ Minitest::Reporters.use! [
18
+ Minitest::Reporters::SpecReporter.new
19
+ ]
20
+
21
+ class MiniTest::Test
22
+ def teardown
23
+ # wait for any remaining scheduled work
24
+ Gyro.run
25
+ Polyphony.reset!
26
+ end
27
+ end
28
+
29
+ module Kernel
30
+ def capture_exception
31
+ yield
32
+ rescue Exception => e
33
+ e
34
+ end
35
+ end
data/test/run.rb ADDED
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ Dir.glob("#{__dir__}/test_*.rb").each do |path|
4
+ require(path)
5
+ end
@@ -0,0 +1,313 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'helper'
4
+ require 'polyphony/http'
5
+
6
+ class String
7
+ def http_lines
8
+ gsub "\n", "\r\n"
9
+ end
10
+ end
11
+
12
+ class IO
13
+ # Creates two mockup sockets for simulating server-client communication
14
+ def self.server_client_mockup
15
+ server_in, client_out = IO.pipe
16
+ client_in, server_out = IO.pipe
17
+
18
+ server_connection = mockup_connection(server_in, server_out, client_out)
19
+ client_connection = mockup_connection(client_in, client_out, server_out)
20
+
21
+ [server_connection, client_connection]
22
+ end
23
+
24
+ def self.mockup_connection(input, output, output2)
25
+ eg(
26
+ :read => ->(*args) { input.read(*args) },
27
+ :readpartial => ->(*args) { input.readpartial(*args) },
28
+ :<< => ->(*args) { output.write(*args) },
29
+ :write => ->(*args) { output.write(*args) },
30
+ :close => -> { output.close },
31
+ :eof? => -> { output2.closed? }
32
+ )
33
+ end
34
+ end
35
+
36
+ class HTTP1ServerTest < MiniTest::Test
37
+ def teardown
38
+ @server&.interrupt if @server&.alive?
39
+ snooze
40
+ super
41
+ end
42
+
43
+ def spin_server(opts = {}, &handler)
44
+ server_connection, client_connection = IO.server_client_mockup
45
+ coproc = spin do
46
+ Polyphony::HTTP::Server.client_loop(server_connection, opts, &handler)
47
+ end
48
+ [coproc, client_connection, server_connection]
49
+ end
50
+
51
+ def test_that_server_uses_content_length_in_http_1_0
52
+ @server, connection = spin_server do |req|
53
+ req.respond('Hello, world!', {})
54
+ end
55
+
56
+ # using HTTP 1.0, server should close connection after responding
57
+ connection << "GET / HTTP/1.0\r\n\r\n"
58
+
59
+ response = connection.readpartial(8192)
60
+ expected = <<~HTTP.chomp.http_lines
61
+ HTTP/1.0 200
62
+ Content-Length: 13
63
+
64
+ Hello, world!
65
+ HTTP
66
+ assert_equal(expected, response)
67
+ end
68
+
69
+ def test_that_server_uses_chunked_encoding_in_http_1_1
70
+ @server, connection = spin_server do |req|
71
+ req.respond('Hello, world!')
72
+ end
73
+
74
+ # using HTTP 1.0, server should close connection after responding
75
+ connection << "GET / HTTP/1.1\r\n\r\n"
76
+
77
+ response = connection.readpartial(8192)
78
+ expected = <<~HTTP.http_lines
79
+ HTTP/1.1 200
80
+ Transfer-Encoding: chunked
81
+
82
+ d
83
+ Hello, world!
84
+ 0
85
+
86
+ HTTP
87
+ assert_equal(expected, response)
88
+ end
89
+
90
+ def test_that_server_maintains_connection_when_using_keep_alives
91
+ puts 'test_that_server_maintains_connection_when_using_keep_alives'
92
+ @server, connection = spin_server do |req|
93
+ req.respond('Hi', {})
94
+ end
95
+
96
+ connection << "GET / HTTP/1.0\r\nConnection: keep-alive\r\n\r\n"
97
+ response = connection.readpartial(8192)
98
+ assert !connection.eof?
99
+ assert_equal("HTTP/1.0 200\r\nContent-Length: 2\r\n\r\nHi", response)
100
+
101
+ connection << "GET / HTTP/1.1\r\n\r\n"
102
+ response = connection.readpartial(8192)
103
+ assert !connection.eof?
104
+ expected = <<~HTTP.http_lines
105
+ HTTP/1.1 200
106
+ Transfer-Encoding: chunked
107
+
108
+ 2
109
+ Hi
110
+ 0
111
+
112
+ HTTP
113
+ assert_equal(expected, response)
114
+
115
+ connection << "GET / HTTP/1.0\r\n\r\n"
116
+ response = connection.readpartial(8192)
117
+ assert connection.eof?
118
+ assert_equal("HTTP/1.0 200\r\nContent-Length: 2\r\n\r\nHi", response)
119
+ end
120
+
121
+ def test_pipelining_client
122
+ @server, connection = spin_server do |req|
123
+ if req.headers['Foo'] == 'bar'
124
+ req.respond('Hello, foobar!', {})
125
+ else
126
+ req.respond('Hello, world!', {})
127
+ end
128
+ end
129
+
130
+ connection << "GET / HTTP/1.1\r\n\r\nGET / HTTP/1.1\r\nFoo: bar\r\n\r\n"
131
+ response = connection.readpartial(8192)
132
+
133
+ expected = <<~HTTP.http_lines
134
+ HTTP/1.1 200
135
+ Transfer-Encoding: chunked
136
+
137
+ d
138
+ Hello, world!
139
+ 0
140
+
141
+ HTTP/1.1 200
142
+ Transfer-Encoding: chunked
143
+
144
+ e
145
+ Hello, foobar!
146
+ 0
147
+
148
+ HTTP
149
+ assert_equal(expected, response)
150
+ end
151
+
152
+ def test_body_chunks
153
+ chunks = []
154
+ request = nil
155
+ @server, connection = spin_server do |req|
156
+ request = req
157
+ req.send_headers
158
+ req.each_chunk do |c|
159
+ chunks << c
160
+ req << c.upcase
161
+ end
162
+ req.finish
163
+ end
164
+
165
+ connection << <<~HTTP.http_lines
166
+ POST / HTTP/1.1
167
+ Transfer-Encoding: chunked
168
+
169
+ 6
170
+ foobar
171
+ HTTP
172
+ 2.times { snooze }
173
+ assert request
174
+ assert_equal %w[foobar], chunks
175
+ assert !request.complete?
176
+
177
+ connection << "6\r\nbazbud\r\n"
178
+ snooze
179
+ assert_equal %w[foobar bazbud], chunks
180
+ assert !request.complete?
181
+
182
+ connection << "0\r\n\r\n"
183
+ snooze
184
+ assert_equal %w[foobar bazbud], chunks
185
+ assert request.complete?
186
+
187
+ 2.times { snooze }
188
+
189
+ response = connection.readpartial(8192)
190
+
191
+ expected = <<~HTTP.http_lines
192
+ HTTP/1.1 200
193
+ Transfer-Encoding: chunked
194
+
195
+ 6
196
+ FOOBAR
197
+ 6
198
+ BAZBUD
199
+ 0
200
+
201
+ HTTP
202
+ assert_equal(expected, response)
203
+ end
204
+
205
+ def test_upgrade
206
+ done = nil
207
+
208
+ opts = {
209
+ upgrade: {
210
+ echo: lambda do |conn, _headers|
211
+ conn << <<~HTTP.http_lines
212
+ HTTP/1.1 101 Switching Protocols
213
+ Upgrade: echo
214
+ Connection: Upgrade
215
+
216
+ HTTP
217
+
218
+ while (data = conn.readpartial(8192))
219
+ conn << data
220
+ snooze
221
+ end
222
+ done = true
223
+ end
224
+ }
225
+ }
226
+
227
+ @server, connection = spin_server(opts) do |req|
228
+ req.respond('Hi')
229
+ end
230
+
231
+ connection << "GET / HTTP/1.1\r\n\r\n"
232
+ response = connection.readpartial(8192)
233
+ assert !connection.eof?
234
+ expected = <<~HTTP.http_lines
235
+ HTTP/1.1 200
236
+ Transfer-Encoding: chunked
237
+
238
+ 2
239
+ Hi
240
+ 0
241
+
242
+ HTTP
243
+ assert_equal(expected, response)
244
+
245
+ connection << <<~HTTP.http_lines
246
+ GET / HTTP/1.1
247
+ Upgrade: echo
248
+ Connection: upgrade
249
+
250
+ HTTP
251
+
252
+ snooze
253
+ response = connection.readpartial(8192)
254
+ assert !connection.eof?
255
+ expected = <<~HTTP.http_lines
256
+ HTTP/1.1 101 Switching Protocols
257
+ Upgrade: echo
258
+ Connection: Upgrade
259
+
260
+ HTTP
261
+ assert_equal(expected, response)
262
+
263
+ assert !done
264
+
265
+ connection << 'foo'
266
+ assert_equal 'foo', connection.readpartial(8192)
267
+
268
+ connection << 'bar'
269
+ assert_equal 'bar', connection.readpartial(8192)
270
+
271
+ connection.close
272
+ assert !done
273
+ snooze
274
+ assert done
275
+ end
276
+
277
+ def test_big_download
278
+ chunk_size = 100_000
279
+ chunk_count = 10
280
+ chunk = '*' * chunk_size
281
+ @server, connection = spin_server do |req|
282
+ req.send_headers
283
+ chunk_count.times do
284
+ req << chunk
285
+ snooze
286
+ end
287
+ req.finish
288
+ req.adapter.close
289
+ end
290
+
291
+ response = +''
292
+ count = 0
293
+
294
+ connection << "GET / HTTP/1.1\r\n\r\n"
295
+ while (data = connection.readpartial(chunk_size * 2))
296
+ response << data
297
+ count += 1
298
+ snooze
299
+ end
300
+
301
+ chunks = "#{chunk_size.to_s(16)}\n#{'*' * chunk_size}\n" * chunk_count
302
+ expected = <<~HTTP.http_lines
303
+ HTTP/1.1 200
304
+ Transfer-Encoding: chunked
305
+
306
+ #{chunks}0
307
+
308
+ HTTP
309
+
310
+ assert_equal expected, response
311
+ assert_equal chunk_count * 2 + 1, count
312
+ end
313
+ end
metadata ADDED
@@ -0,0 +1,245 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: polyphony-http
3
+ version: !ruby/object:Gem::Version
4
+ version: '0.24'
5
+ platform: ruby
6
+ authors:
7
+ - Sharon Rosner
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2020-02-17 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: modulation
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: polyphony
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0.30'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0.30'
41
+ - !ruby/object:Gem::Dependency
42
+ name: http_parser.rb
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 0.6.0
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 0.6.0
55
+ - !ruby/object:Gem::Dependency
56
+ name: http-2
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: 0.10.0
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: 0.10.0
69
+ - !ruby/object:Gem::Dependency
70
+ name: rack
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: 2.0.8
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: 2.0.8
83
+ - !ruby/object:Gem::Dependency
84
+ name: websocket
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: 1.2.8
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: 1.2.8
97
+ - !ruby/object:Gem::Dependency
98
+ name: localhost
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: 1.1.4
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: 1.1.4
111
+ - !ruby/object:Gem::Dependency
112
+ name: minitest
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: 5.11.3
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: 5.11.3
125
+ - !ruby/object:Gem::Dependency
126
+ name: minitest-reporters
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: 1.4.2
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: 1.4.2
139
+ - !ruby/object:Gem::Dependency
140
+ name: simplecov
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - "~>"
144
+ - !ruby/object:Gem::Version
145
+ version: 0.17.1
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - "~>"
151
+ - !ruby/object:Gem::Version
152
+ version: 0.17.1
153
+ description:
154
+ email: ciconia@gmail.com
155
+ executables:
156
+ - poly
157
+ extensions: []
158
+ extra_rdoc_files:
159
+ - README.md
160
+ files:
161
+ - ".gitignore"
162
+ - CHANGELOG.md
163
+ - Gemfile
164
+ - Gemfile.lock
165
+ - LICENSE
166
+ - README.md
167
+ - Rakefile
168
+ - TODO.md
169
+ - bin/poly
170
+ - docs/README.md
171
+ - docs/summary.md
172
+ - examples/cuba.ru
173
+ - examples/happy_eyeballs.rb
174
+ - examples/http2_raw.rb
175
+ - examples/http_client.rb
176
+ - examples/http_get.rb
177
+ - examples/http_parse_experiment.rb
178
+ - examples/http_proxy.rb
179
+ - examples/http_server.js
180
+ - examples/http_server.rb
181
+ - examples/http_server_forked.rb
182
+ - examples/http_server_graceful.rb
183
+ - examples/http_server_simple.rb
184
+ - examples/http_server_throttled.rb
185
+ - examples/http_server_timeout.rb
186
+ - examples/http_ws_server.rb
187
+ - examples/https_raw_client.rb
188
+ - examples/https_server.rb
189
+ - examples/https_wss_server.rb
190
+ - examples/rack_server.rb
191
+ - examples/rack_server_https.rb
192
+ - examples/rack_server_https_forked.rb
193
+ - examples/websocket_secure_server.rb
194
+ - examples/websocket_server.rb
195
+ - examples/ws_page.html
196
+ - examples/wss_page.html
197
+ - lib/polyphony/http.rb
198
+ - lib/polyphony/http/client/agent.rb
199
+ - lib/polyphony/http/client/http1.rb
200
+ - lib/polyphony/http/client/http2.rb
201
+ - lib/polyphony/http/client/response.rb
202
+ - lib/polyphony/http/client/site_connection_manager.rb
203
+ - lib/polyphony/http/server.rb
204
+ - lib/polyphony/http/server/http1.rb
205
+ - lib/polyphony/http/server/http2.rb
206
+ - lib/polyphony/http/server/http2_stream.rb
207
+ - lib/polyphony/http/server/rack.rb
208
+ - lib/polyphony/http/server/request.rb
209
+ - lib/polyphony/http/version.rb
210
+ - lib/polyphony/websocket.rb
211
+ - polyphony-http.gemspec
212
+ - test/coverage.rb
213
+ - test/eg.rb
214
+ - test/helper.rb
215
+ - test/run.rb
216
+ - test/test_http_server.rb
217
+ homepage: http://github.com/digital-fabric/polyphony-http
218
+ licenses:
219
+ - MIT
220
+ metadata:
221
+ source_code_uri: https://github.com/digital-fabric/polyphony-http
222
+ post_install_message:
223
+ rdoc_options:
224
+ - "--title"
225
+ - polyphony
226
+ - "--main"
227
+ - README.md
228
+ require_paths:
229
+ - lib
230
+ required_ruby_version: !ruby/object:Gem::Requirement
231
+ requirements:
232
+ - - ">="
233
+ - !ruby/object:Gem::Version
234
+ version: '2.6'
235
+ required_rubygems_version: !ruby/object:Gem::Requirement
236
+ requirements:
237
+ - - ">="
238
+ - !ruby/object:Gem::Version
239
+ version: '0'
240
+ requirements: []
241
+ rubygems_version: 3.0.6
242
+ signing_key:
243
+ specification_version: 4
244
+ summary: 'Polyphony-http: Polyphonic HTTP for Ruby'
245
+ test_files: []