tipi 0.31 → 0.36

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.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +29 -0
  3. data/Gemfile.lock +10 -4
  4. data/LICENSE +1 -1
  5. data/TODO.md +13 -47
  6. data/bin/tipi +13 -0
  7. data/df/agent.rb +63 -0
  8. data/df/etc_benchmark.rb +15 -0
  9. data/df/multi_agent_supervisor.rb +87 -0
  10. data/df/multi_client.rb +84 -0
  11. data/df/routing_benchmark.rb +60 -0
  12. data/df/sample_agent.rb +89 -0
  13. data/df/server.rb +54 -0
  14. data/df/sse_page.html +29 -0
  15. data/df/stress.rb +24 -0
  16. data/df/ws_page.html +38 -0
  17. data/examples/http_request_ws_server.rb +34 -0
  18. data/examples/http_server.rb +6 -6
  19. data/examples/http_server_forked.rb +4 -5
  20. data/examples/http_server_form.rb +23 -0
  21. data/examples/http_server_throttled_accept.rb +23 -0
  22. data/examples/http_unix_socket_server.rb +17 -0
  23. data/examples/http_ws_server.rb +10 -12
  24. data/examples/routing_server.rb +34 -0
  25. data/examples/websocket_client.rb +1 -2
  26. data/examples/websocket_demo.rb +4 -2
  27. data/examples/ws_page.html +1 -2
  28. data/lib/tipi.rb +7 -5
  29. data/lib/tipi/config_dsl.rb +153 -0
  30. data/lib/tipi/configuration.rb +1 -1
  31. data/lib/tipi/digital_fabric.rb +7 -0
  32. data/lib/tipi/digital_fabric/agent.rb +225 -0
  33. data/lib/tipi/digital_fabric/agent_proxy.rb +265 -0
  34. data/lib/tipi/digital_fabric/executive.rb +100 -0
  35. data/lib/tipi/digital_fabric/executive/index.html +69 -0
  36. data/lib/tipi/digital_fabric/protocol.rb +90 -0
  37. data/lib/tipi/digital_fabric/request_adapter.rb +48 -0
  38. data/lib/tipi/digital_fabric/service.rb +230 -0
  39. data/lib/tipi/http1_adapter.rb +59 -37
  40. data/lib/tipi/http2_adapter.rb +5 -3
  41. data/lib/tipi/http2_stream.rb +19 -7
  42. data/lib/tipi/rack_adapter.rb +11 -3
  43. data/lib/tipi/version.rb +1 -1
  44. data/lib/tipi/websocket.rb +33 -13
  45. data/test/helper.rb +1 -2
  46. data/test/test_http_server.rb +3 -2
  47. data/test/test_request.rb +108 -0
  48. data/tipi.gemspec +7 -3
  49. metadata +59 -7
  50. data/lib/tipi/request.rb +0 -118
@@ -15,6 +15,7 @@ module Tipi
15
15
  @conn = conn
16
16
  @opts = opts
17
17
  @upgrade_headers = upgrade_headers
18
+ @first = true
18
19
 
19
20
  @interface = ::HTTP2::Server.new
20
21
  @connection_fiber = Fiber.current
@@ -37,7 +38,7 @@ module Tipi
37
38
 
38
39
  def upgrade
39
40
  @conn << UPGRADE_MESSAGE
40
- settings = @upgrade_headers['HTTP2-Settings']
41
+ settings = @upgrade_headers['http2-settings']
41
42
  Fiber.current.schedule(nil)
42
43
  @interface.upgrade(settings, @upgrade_headers, '')
43
44
  ensure
@@ -49,7 +50,7 @@ module Tipi
49
50
  @interface.on(:stream) { |stream| start_stream(stream, &block) }
50
51
  upgrade if @upgrade_headers
51
52
 
52
- @conn.read_loop(&@interface.method(:<<))
53
+ @conn.recv_loop(&@interface.method(:<<))
53
54
  rescue SystemCallError, IOError
54
55
  # ignore
55
56
  ensure
@@ -57,7 +58,8 @@ module Tipi
57
58
  end
58
59
 
59
60
  def start_stream(stream, &block)
60
- stream = HTTP2StreamHandler.new(stream, &block)
61
+ stream = HTTP2StreamHandler.new(stream, @conn, @first, &block)
62
+ @first = nil if @first
61
63
  @streams[stream] = true
62
64
  end
63
65
 
@@ -1,15 +1,18 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'http/2'
4
- require_relative './request'
4
+ require 'qeweney/request'
5
5
 
6
6
  module Tipi
7
7
  # Manages an HTTP 2 stream
8
8
  class HTTP2StreamHandler
9
9
  attr_accessor :__next__
10
+ attr_reader :conn
10
11
 
11
- def initialize(stream, &block)
12
+ def initialize(stream, conn, first, &block)
12
13
  @stream = stream
14
+ @conn = conn
15
+ @first = first
13
16
  @connection_fiber = Fiber.current
14
17
  @stream_fiber = spin { |req| handle_request(req, &block) }
15
18
 
@@ -43,7 +46,11 @@ module Tipi
43
46
  end
44
47
 
45
48
  def on_headers(headers)
46
- @request = Request.new(headers.to_h, self)
49
+ @request = Qeweney::Request.new(headers.to_h, self)
50
+ if @first
51
+ @request.headers[':first'] = true
52
+ @first = false
53
+ end
47
54
  @stream_fiber.schedule @request
48
55
  end
49
56
 
@@ -96,7 +103,7 @@ module Tipi
96
103
 
97
104
  # response API
98
105
  def respond(chunk, headers)
99
- headers[':status'] ||= '200'
106
+ headers[':status'] ||= Qeweney::Status::OK
100
107
  @stream.headers(headers, end_stream: false)
101
108
  @stream.data(chunk, end_stream: true)
102
109
  @headers_sent = true
@@ -105,21 +112,26 @@ module Tipi
105
112
  def send_headers(headers, empty_response = false)
106
113
  return if @headers_sent
107
114
 
108
- headers[':status'] ||= (empty_response ? 204 : 200).to_s
115
+ headers[':status'] ||= (empty_response ? Qeweney::Status::NO_CONTENT : Qeweney::Status::OK).to_s
109
116
  @stream.headers(headers, end_stream: false)
110
117
  @headers_sent = true
111
118
  end
112
119
 
113
120
  def send_chunk(chunk, done: false)
114
121
  send_headers({}, false) unless @headers_sent
115
- @stream.data(chunk, end_stream: done)
122
+
123
+ if chunk
124
+ @stream.data(chunk, end_stream: done)
125
+ elsif done
126
+ @stream.close
127
+ end
116
128
  end
117
129
 
118
130
  def finish
119
131
  if @headers_sent
120
132
  @stream.close
121
133
  else
122
- headers[':status'] ||= '204'
134
+ headers[':status'] ||= Qeweney::Status::NO_CONTENT
123
135
  @stream.headers(headers, end_stream: true)
124
136
  end
125
137
  end
@@ -62,16 +62,24 @@ module Tipi
62
62
  when 'REQUEST_METHOD' then request.method
63
63
  when 'PATH_INFO' then request.path
64
64
  when 'QUERY_STRING' then request.query_string || ''
65
- when 'SERVER_NAME' then request.headers['Host']
65
+ when 'SERVER_NAME' then request.headers['host']
66
66
  when 'rack.input' then InputStream.new(request)
67
67
  when HTTP_HEADER_RE then request.headers[$1.downcase]
68
68
  else RACK_ENV[key]
69
69
  end
70
70
  end
71
-
71
+
72
72
  def respond(request, (status_code, headers, body))
73
73
  headers[':status'] = status_code.to_s
74
- request.respond(body.first, headers)
74
+
75
+ content =
76
+ if body.respond_to?(:to_path)
77
+ File.open(body.to_path, 'rb') { |f| f.read }
78
+ else
79
+ body.first
80
+ end
81
+
82
+ request.respond(content, headers)
75
83
  end
76
84
  end
77
85
  end
data/lib/tipi/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Tipi
4
- VERSION = '0.31'
4
+ VERSION = '0.36'
5
5
  end
@@ -7,13 +7,13 @@ module Tipi
7
7
  # Websocket connection
8
8
  class Websocket
9
9
  def self.handler(&block)
10
- proc { |client, header|
11
- block.(new(client, header))
10
+ proc { |conn, headers|
11
+ block.(new(conn, headers))
12
12
  }
13
13
  end
14
14
 
15
- def initialize(client, headers)
16
- @client = client
15
+ def initialize(conn, headers)
16
+ @conn = conn
17
17
  @headers = headers
18
18
  setup(headers)
19
19
  end
@@ -28,22 +28,38 @@ module Tipi
28
28
  HTTP
29
29
 
30
30
  def setup(headers)
31
- key = headers['Sec-WebSocket-Key']
32
- @version = headers['Sec-WebSocket-Version'].to_i
31
+ key = headers['sec-websocket-key']
32
+ @version = headers['sec-websocket-version'].to_i
33
33
  accept = Digest::SHA1.base64digest([key, S_WS_GUID].join)
34
- @client << format(UPGRADE_RESPONSE, accept: accept)
34
+ @conn << format(UPGRADE_RESPONSE, accept: accept)
35
35
 
36
36
  @reader = ::WebSocket::Frame::Incoming::Server.new(version: @version)
37
37
  end
38
38
 
39
39
  def recv
40
- loop do
41
- data = @client.readpartial(8192)
42
- break nil unless data
43
-
40
+ if (msg = @reader.next)
41
+ return msg.to_s
42
+ end
43
+
44
+ @conn.recv_loop do |data|
44
45
  @reader << data
45
46
  if (msg = @reader.next)
46
- break msg.to_s
47
+ return msg.to_s
48
+ end
49
+ end
50
+
51
+ nil
52
+ end
53
+
54
+ def recv_loop
55
+ if (msg = @reader.next)
56
+ yield msg.to_s
57
+ end
58
+
59
+ @conn.recv_loop do |data|
60
+ @reader << data
61
+ while (msg = @reader.next)
62
+ yield msg.to_s
47
63
  end
48
64
  end
49
65
  end
@@ -52,8 +68,12 @@ module Tipi
52
68
  frame = ::WebSocket::Frame::Outgoing::Server.new(
53
69
  version: @version, data: data, type: :text
54
70
  )
55
- @client << frame.to_s
71
+ @conn << frame.to_s
56
72
  end
57
73
  alias_method :<<, :send
74
+
75
+ def close
76
+ @conn.close
77
+ end
58
78
  end
59
79
  end
data/test/helper.rb CHANGED
@@ -33,8 +33,7 @@ class MiniTest::Test
33
33
 
34
34
  def teardown
35
35
  # puts "* teardown #{self.name.inspect} Fiber.current: #{Fiber.current.inspect}"
36
- Fiber.current.terminate_all_children
37
- Fiber.current.await_all_children
36
+ Fiber.current.shutdown_all_children
38
37
  rescue => e
39
38
  puts e
40
39
  puts e.backtrace.join("\n")
@@ -25,7 +25,9 @@ class IO
25
25
  eg(
26
26
  :read => ->(*args) { input.read(*args) },
27
27
  :read_loop => ->(*args, &block) { input.read_loop(*args, &block) },
28
+ :recv_loop => ->(*args, &block) { input.read_loop(*args, &block) },
28
29
  :readpartial => ->(*args) { input.readpartial(*args) },
30
+ :recv => ->(*args) { input.readpartial(*args) },
29
31
  :<< => ->(*args) { output.write(*args) },
30
32
  :write => ->(*args) { output.write(*args) },
31
33
  :close => -> { output.close },
@@ -89,7 +91,6 @@ class HTTP1ServerTest < MiniTest::Test
89
91
  end
90
92
 
91
93
  def test_that_server_maintains_connection_when_using_keep_alives
92
- puts 'test_that_server_maintains_connection_when_using_keep_alives'
93
94
  @server, connection = spin_server do |req|
94
95
  req.respond('Hi', {})
95
96
  end
@@ -121,7 +122,7 @@ class HTTP1ServerTest < MiniTest::Test
121
122
 
122
123
  def test_pipelining_client
123
124
  @server, connection = spin_server do |req|
124
- if req.headers['Foo'] == 'bar'
125
+ if req.headers['foo'] == 'bar'
125
126
  req.respond('Hello, foobar!', {})
126
127
  else
127
128
  req.respond('Hello, world!', {})
@@ -0,0 +1,108 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'helper'
4
+ require 'tipi'
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
+ :read_loop => ->(*args, &block) { input.read_loop(*args, &block) },
28
+ :recv_loop => ->(*args, &block) { input.read_loop(*args, &block) },
29
+ :readpartial => ->(*args) { input.readpartial(*args) },
30
+ :recv => ->(*args) { input.readpartial(*args) },
31
+ :<< => ->(*args) { output.write(*args) },
32
+ :write => ->(*args) { output.write(*args) },
33
+ :close => -> { output.close },
34
+ :eof? => -> { output2.closed? }
35
+ )
36
+ end
37
+ end
38
+
39
+ class RequestHeadersTest < MiniTest::Test
40
+ def teardown
41
+ @server&.interrupt if @server&.alive?
42
+ snooze
43
+ super
44
+ end
45
+
46
+ def spin_server(opts = {}, &handler)
47
+ server_connection, client_connection = IO.server_client_mockup
48
+ coproc = spin do
49
+ Tipi.client_loop(server_connection, opts, &handler)
50
+ end
51
+ [coproc, client_connection, server_connection]
52
+ end
53
+
54
+ def test_request_headers
55
+ req = nil
56
+ @server, connection = spin_server do |r|
57
+ req = r
58
+ req.respond('Hello, world!')
59
+ end
60
+
61
+ connection << "GET /titi HTTP/1.1\r\nHost: blah.com\r\nFoo: bar\r\nhi: 1\r\nHi: 2\r\nhi: 3\r\n\r\n"
62
+
63
+ snooze
64
+
65
+ assert_kind_of Qeweney::Request, req
66
+ assert_equal 'blah.com', req.headers['host']
67
+ assert_equal 'bar', req.headers['foo']
68
+ assert_equal ['1', '3', '2'], req.headers['hi']
69
+ assert_equal 'get', req.headers[':method']
70
+ assert_equal '/titi', req.headers[':path']
71
+ end
72
+
73
+ def test_request_host
74
+ req = nil
75
+ @server, connection = spin_server do |r|
76
+ req = r
77
+ req.respond('Hello, world!')
78
+ end
79
+
80
+ connection << "GET /titi HTTP/1.1\nHost: blah.com\nFoo: bar\nhi: 1\nHi: 2\nhi: 3\n\n"
81
+ snooze
82
+ assert_equal 'blah.com', req.host
83
+ end
84
+
85
+ def test_request_connection
86
+ req = nil
87
+ @server, connection = spin_server do |r|
88
+ req = r
89
+ req.respond('Hello, world!')
90
+ end
91
+
92
+ connection << "GET /titi HTTP/1.1\nConnection: keep-alive\nFoo: bar\nhi: 1\nHi: 2\nhi: 3\n\n"
93
+ snooze
94
+ assert_equal 'keep-alive', req.connection
95
+ end
96
+
97
+ def test_request_upgrade_protocol
98
+ req = nil
99
+ @server, connection = spin_server do |r|
100
+ req = r
101
+ req.respond('Hello, world!')
102
+ end
103
+
104
+ connection << "GET /titi HTTP/1.1\nConnection: upgrade\nUpgrade: foobar\n\n"
105
+ snooze
106
+ assert_equal 'foobar', req.upgrade_protocol
107
+ end
108
+ end
data/tipi.gemspec CHANGED
@@ -6,7 +6,7 @@ Gem::Specification.new do |s|
6
6
  s.licenses = ['MIT']
7
7
  s.summary = 'Tipi - the All-in-one Web Server for Ruby Apps'
8
8
  s.author = 'Sharon Rosner'
9
- s.email = 'ciconia@gmail.com'
9
+ s.email = 'sharon@noteflakes.com'
10
10
  s.files = `git ls-files`.split
11
11
  s.homepage = 'http://github.com/digital-fabric/tipi'
12
12
  s.metadata = {
@@ -19,13 +19,17 @@ Gem::Specification.new do |s|
19
19
 
20
20
  s.executables = ['tipi']
21
21
 
22
- s.add_runtime_dependency 'polyphony', '~>0.44'
22
+ s.add_runtime_dependency 'polyphony', '~>0.51.0'
23
+ s.add_runtime_dependency 'qeweney', '~>0.3'
23
24
 
24
25
  s.add_runtime_dependency 'http_parser.rb', '~>0.6.0'
25
26
  s.add_runtime_dependency 'http-2', '~>0.10.0'
26
27
  s.add_runtime_dependency 'rack', '>=2.0.8', '<2.3.0'
27
28
  s.add_runtime_dependency 'websocket', '~>1.2.8'
28
-
29
+
30
+ # for digital fabric
31
+ s.add_runtime_dependency 'msgpack', '~>1.4.2'
32
+
29
33
  s.add_development_dependency 'rake', '~>12.3.3'
30
34
  s.add_development_dependency 'localhost', '~>1.1.4'
31
35
  s.add_development_dependency 'minitest', '~>5.11.3'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tipi
3
3
  version: !ruby/object:Gem::Version
4
- version: '0.31'
4
+ version: '0.36'
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sharon Rosner
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-07-28 00:00:00.000000000 Z
11
+ date: 2021-02-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: polyphony
@@ -16,14 +16,28 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '0.44'
19
+ version: 0.51.0
20
20
  type: :runtime
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: '0.44'
26
+ version: 0.51.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: qeweney
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0.3'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0.3'
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: http_parser.rb
29
43
  requirement: !ruby/object:Gem::Requirement
@@ -86,6 +100,20 @@ dependencies:
86
100
  - - "~>"
87
101
  - !ruby/object:Gem::Version
88
102
  version: 1.2.8
103
+ - !ruby/object:Gem::Dependency
104
+ name: msgpack
105
+ requirement: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - "~>"
108
+ - !ruby/object:Gem::Version
109
+ version: 1.4.2
110
+ type: :runtime
111
+ prerelease: false
112
+ version_requirements: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - "~>"
115
+ - !ruby/object:Gem::Version
116
+ version: 1.4.2
89
117
  - !ruby/object:Gem::Dependency
90
118
  name: rake
91
119
  requirement: !ruby/object:Gem::Requirement
@@ -157,7 +185,7 @@ dependencies:
157
185
  - !ruby/object:Gem::Version
158
186
  version: 0.17.1
159
187
  description:
160
- email: ciconia@gmail.com
188
+ email: sharon@noteflakes.com
161
189
  executables:
162
190
  - tipi
163
191
  extensions: []
@@ -174,18 +202,32 @@ files:
174
202
  - Rakefile
175
203
  - TODO.md
176
204
  - bin/tipi
205
+ - df/agent.rb
206
+ - df/etc_benchmark.rb
207
+ - df/multi_agent_supervisor.rb
208
+ - df/multi_client.rb
209
+ - df/routing_benchmark.rb
210
+ - df/sample_agent.rb
211
+ - df/server.rb
212
+ - df/sse_page.html
213
+ - df/stress.rb
214
+ - df/ws_page.html
177
215
  - docs/README.md
178
216
  - docs/tipi-logo.png
179
217
  - examples/cuba.ru
180
218
  - examples/hanami-api.ru
181
219
  - examples/hello.ru
220
+ - examples/http_request_ws_server.rb
182
221
  - examples/http_server.js
183
222
  - examples/http_server.rb
184
223
  - examples/http_server_forked.rb
224
+ - examples/http_server_form.rb
185
225
  - examples/http_server_graceful.rb
186
226
  - examples/http_server_simple.rb
187
227
  - examples/http_server_throttled.rb
228
+ - examples/http_server_throttled_accept.rb
188
229
  - examples/http_server_timeout.rb
230
+ - examples/http_unix_socket_server.rb
189
231
  - examples/http_ws_server.rb
190
232
  - examples/https_server.rb
191
233
  - examples/https_server_forked.rb
@@ -193,6 +235,7 @@ files:
193
235
  - examples/rack_server.rb
194
236
  - examples/rack_server_https.rb
195
237
  - examples/rack_server_https_forked.rb
238
+ - examples/routing_server.rb
196
239
  - examples/websocket_client.rb
197
240
  - examples/websocket_demo.rb
198
241
  - examples/websocket_secure_server.rb
@@ -200,13 +243,21 @@ files:
200
243
  - examples/ws_page.html
201
244
  - examples/wss_page.html
202
245
  - lib/tipi.rb
246
+ - lib/tipi/config_dsl.rb
203
247
  - lib/tipi/configuration.rb
248
+ - lib/tipi/digital_fabric.rb
249
+ - lib/tipi/digital_fabric/agent.rb
250
+ - lib/tipi/digital_fabric/agent_proxy.rb
251
+ - lib/tipi/digital_fabric/executive.rb
252
+ - lib/tipi/digital_fabric/executive/index.html
253
+ - lib/tipi/digital_fabric/protocol.rb
254
+ - lib/tipi/digital_fabric/request_adapter.rb
255
+ - lib/tipi/digital_fabric/service.rb
204
256
  - lib/tipi/handler.rb
205
257
  - lib/tipi/http1_adapter.rb
206
258
  - lib/tipi/http2_adapter.rb
207
259
  - lib/tipi/http2_stream.rb
208
260
  - lib/tipi/rack_adapter.rb
209
- - lib/tipi/request.rb
210
261
  - lib/tipi/version.rb
211
262
  - lib/tipi/websocket.rb
212
263
  - test/coverage.rb
@@ -214,6 +265,7 @@ files:
214
265
  - test/helper.rb
215
266
  - test/run.rb
216
267
  - test/test_http_server.rb
268
+ - test/test_request.rb
217
269
  - tipi.gemspec
218
270
  homepage: http://github.com/digital-fabric/tipi
219
271
  licenses:
@@ -239,7 +291,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
239
291
  - !ruby/object:Gem::Version
240
292
  version: '0'
241
293
  requirements: []
242
- rubygems_version: 3.1.2
294
+ rubygems_version: 3.1.4
243
295
  signing_key:
244
296
  specification_version: 4
245
297
  summary: Tipi - the All-in-one Web Server for Ruby Apps