tipi 0.37 → 0.40

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.
@@ -13,9 +13,17 @@ puts 'Listening on port 4411...'
13
13
 
14
14
  spin do
15
15
  Tipi.serve('0.0.0.0', 4411, opts) do |req|
16
- req.respond("Hello world!\n")
17
- rescue Exception => e
18
- p e
16
+ if req.path == '/stream'
17
+ req.send_headers('Foo' => 'Bar')
18
+ sleep 1
19
+ req.send_chunk("foo\n")
20
+ sleep 1
21
+ req.send_chunk("bar\n")
22
+ req.finish
23
+ else
24
+ req.respond("Hello world!\n")
25
+ end
26
+ p req.transfer_counts
19
27
  end
20
28
  p 'done...'
21
29
  end.await
@@ -11,11 +11,13 @@ opts = {
11
11
  dont_linger: true
12
12
  }
13
13
 
14
+ server = Tipi.listen('0.0.0.0', 1234, opts)
15
+
14
16
  child_pids = []
15
17
  8.times do
16
18
  pid = Polyphony.fork do
17
19
  puts "forked pid: #{Process.pid}"
18
- Tipi.serve('0.0.0.0', 1234, opts) do |req|
20
+ server.each do |req|
19
21
  req.respond("Hello world! from pid: #{Process.pid}\n")
20
22
  end
21
23
  rescue Interrupt
@@ -25,4 +27,6 @@ end
25
27
 
26
28
  puts 'Listening on port 1234'
27
29
 
30
+ trap('SIGINT') { exit! }
31
+
28
32
  child_pids.each { |pid| Thread.current.backend.waitpid(pid) }
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+ require 'tipi'
5
+
6
+ opts = {
7
+ reuse_addr: true,
8
+ dont_linger: true
9
+ }
10
+
11
+ puts "pid: #{Process.pid}"
12
+ puts 'Listening on port 4411...'
13
+
14
+ app = Tipi.route do |req|
15
+ req.on 'stream' do
16
+ req.send_headers('Foo' => 'Bar')
17
+ sleep 1
18
+ req.send_chunk("foo\n")
19
+ sleep 1
20
+ req.send_chunk("bar\n")
21
+ req.finish
22
+ end
23
+ req.default do
24
+ req.respond("Hello world!\n")
25
+ end
26
+ end
27
+
28
+ trap('INT') { exit! }
29
+ Tipi.serve('0.0.0.0', 4411, opts, &app)
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+ require 'tipi'
5
+ require 'fileutils'
6
+
7
+ opts = {
8
+ reuse_addr: true,
9
+ dont_linger: true
10
+ }
11
+
12
+ puts "pid: #{Process.pid}"
13
+ puts 'Listening on port 4411...'
14
+
15
+ root_path = FileUtils.pwd
16
+
17
+ trap('INT') { exit! }
18
+
19
+ app = Tipi.route do |req|
20
+ req.on('normal') do
21
+ path = File.join(root_path, req.route_relative_path)
22
+ if File.file?(path)
23
+ req.serve_file(path)
24
+ else
25
+ req.respond(nil, ':status' => Qeweney::Status::NOT_FOUND)
26
+ end
27
+ end
28
+ req.on('spliced') do
29
+ path = File.join(root_path, req.route_relative_path)
30
+ if File.file?(path)
31
+ req.serve_file(path, respond_from_io: true)
32
+ else
33
+ req.respond(nil, ':status' => Qeweney::Status::NOT_FOUND)
34
+ end
35
+ end
36
+ end
37
+
38
+ Tipi.serve('0.0.0.0', 4411, opts, &app)
@@ -3,9 +3,9 @@
3
3
  require 'bundler/setup'
4
4
  require 'tipi'
5
5
 
6
- $throttler = throttle(1000)
6
+ $throttler = Polyphony::Throttler.new(1000)
7
7
  opts = { reuse_addr: true, dont_linger: true }
8
- spin do
8
+ server = spin do
9
9
  Tipi.serve('0.0.0.0', 1234, opts) do |req|
10
10
  $throttler.call { req.respond("Hello world!\n") }
11
11
  end
@@ -13,3 +13,4 @@ end
13
13
 
14
14
  puts "pid: #{Process.pid}"
15
15
  puts 'Listening on port 1234...'
16
+ server.await
@@ -16,7 +16,16 @@ opts = {
16
16
  puts "pid: #{Process.pid}"
17
17
  puts 'Listening on port 1234...'
18
18
  Tipi.serve('0.0.0.0', 1234, opts) do |req|
19
- req.respond("Hello world!\n")
19
+ p path: req.path
20
+ if req.path == '/stream'
21
+ req.send_headers('Foo' => 'Bar')
22
+ sleep 0.5
23
+ req.send_chunk("foo\n")
24
+ sleep 0.5
25
+ req.send_chunk("bar\n", done: true)
26
+ else
27
+ req.respond("Hello world!\n")
28
+ end
20
29
  # req.send_headers
21
30
  # req.send_chunk("Method: #{req.method}\n")
22
31
  # req.send_chunk("Path: #{req.path}\n")
@@ -2,6 +2,7 @@
2
2
 
3
3
  require 'bundler/setup'
4
4
  require 'tipi'
5
+ require 'tipi/websocket'
5
6
  require 'localhost/authority'
6
7
 
7
8
  def ws_handler(conn)
@@ -26,7 +27,7 @@ opts = {
26
27
  dont_linger: true,
27
28
  secure_context: authority.server_context,
28
29
  upgrade: {
29
- websocket: Polyphony::Websocket.handler(&method(:ws_handler))
30
+ websocket: Tipi::Websocket.handler(&method(:ws_handler))
30
31
  }
31
32
  }
32
33
 
@@ -4,6 +4,11 @@ require 'bundler/setup'
4
4
  require 'tipi'
5
5
 
6
6
  app_path = ARGV.first || File.expand_path('./config.ru', __dir__)
7
+ unless File.file?(app_path)
8
+ STDERR.puts "Please provide rack config file (there are some in the examples directory.)"
9
+ exit!
10
+ end
11
+
7
12
  app = Tipi::RackAdapter.load(app_path)
8
13
  opts = { reuse_addr: true, dont_linger: true }
9
14
 
@@ -5,7 +5,7 @@ require 'tipi'
5
5
  require 'localhost/authority'
6
6
 
7
7
  app_path = ARGV.first || File.expand_path('./config.ru', __dir__)
8
- app = Polyphony::HTTP::Server::RackAdapter.load(app_path)
8
+ app = Tipi::RackAdapter.load(app_path)
9
9
 
10
10
  authority = Localhost::Authority.fetch
11
11
  opts = {
@@ -5,15 +5,16 @@ require 'tipi'
5
5
  require 'localhost/authority'
6
6
 
7
7
  app_path = ARGV.first || File.expand_path('./config.ru', __dir__)
8
- app = Polyphony::HTTP::Server::RackAdapter.load(app_path)
8
+ app = Tipi::RackAdapter.load(app_path)
9
9
 
10
10
  authority = Localhost::Authority.fetch
11
11
  opts = {
12
12
  reuse_addr: true,
13
+ reuse_port: true,
13
14
  dont_linger: true,
14
15
  secure_context: authority.server_context
15
16
  }
16
- server = Polyphony::HTTP::Server.listen('0.0.0.0', 1234, opts)
17
+ server = Tipi.listen('0.0.0.0', 1234, opts)
17
18
  puts 'Listening on port 1234'
18
19
 
19
20
  child_pids = []
@@ -24,4 +25,4 @@ child_pids = []
24
25
  end
25
26
  end
26
27
 
27
- child_pids.each { |pid| EV::Child.new(pid).await }
28
+ child_pids.each { |pid| Thread.current.backend.waitpid(pid) }
@@ -12,17 +12,18 @@ puts "pid: #{Process.pid}"
12
12
  puts 'Listening on port 4411...'
13
13
 
14
14
  app = Tipi.route do |r|
15
- r.root do
15
+
16
+ r.on_root do
16
17
  r.redirect '/hello'
17
18
  end
18
19
  r.on 'hello' do
19
- r.get 'world' do
20
+ r.on_get 'world' do
20
21
  r.respond 'Hello world'
21
22
  end
22
- r.get do
23
+ r.on_get do
23
24
  r.respond 'Hello'
24
25
  end
25
- r.post do
26
+ r.on_post do
26
27
  puts 'Someone said Hello'
27
28
  r.redirect '/'
28
29
  end
@@ -1,13 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'bundler/inline'
4
-
5
- gemfile do
6
- source 'https://rubygems.org'
7
- gem 'polyphony', '~> 0.44'
8
- gem 'tipi', '~> 0.31'
9
- end
10
-
3
+ require 'bundler/setup'
4
+ require 'tipi'
11
5
  require 'tipi/websocket'
12
6
 
13
7
  class WebsocketClient
@@ -6,7 +6,7 @@
6
6
  <body>
7
7
  <script>
8
8
  var connect = function () {
9
- var exampleSocket = new WebSocket("wss://dev.realiteq.net/");
9
+ var exampleSocket = new WebSocket("ws://localhost:4411/");
10
10
 
11
11
  exampleSocket.onopen = function (event) {
12
12
  document.querySelector('#status').innerText = 'connected';
@@ -30,4 +30,4 @@
30
30
  <h1 id="status">disconnected</h1>
31
31
  <h1 id="msg"></h1>
32
32
  </body>
33
- </html>
33
+ </html>
data/lib/tipi.rb CHANGED
@@ -4,6 +4,12 @@ require 'polyphony'
4
4
  require_relative './tipi/http1_adapter'
5
5
  require_relative './tipi/http2_adapter'
6
6
  require_relative './tipi/configuration'
7
+ require_relative './tipi/response_extensions'
8
+ require 'qeweney/request'
9
+
10
+ class Qeweney::Request
11
+ include Tipi::ResponseExtensions
12
+ end
7
13
 
8
14
  module Tipi
9
15
  ALPN_PROTOCOLS = %w[h2 http/1.1].freeze
@@ -119,7 +119,7 @@ module DigitalFabric
119
119
 
120
120
  def recv_df_message(msg)
121
121
  @last_recv = Time.now
122
- case msg['kind']
122
+ case msg[Protocol::Attribute::KIND]
123
123
  when Protocol::SHUTDOWN
124
124
  recv_shutdown
125
125
  when Protocol::HTTP_REQUEST
@@ -130,7 +130,7 @@ module DigitalFabric
130
130
  recv_ws_request(msg)
131
131
  when Protocol::CONN_DATA, Protocol::CONN_CLOSE,
132
132
  Protocol::WS_DATA, Protocol::WS_CLOSE
133
- fiber = @requests[msg['id']]
133
+ fiber = @requests[msg[Protocol::Attribute::ID]]
134
134
  fiber << msg if fiber
135
135
  end
136
136
  end
@@ -140,7 +140,7 @@ module DigitalFabric
140
140
  # messages. This is so we can correctly stop long-running requests
141
141
  # upon graceful shutdown
142
142
  if is_long_running_request_response?(msg)
143
- id = msg[:id]
143
+ id = msg[Protocol::Attribute::ID]
144
144
  @long_running_requests[id] = @requests[id]
145
145
  end
146
146
  @last_send = Time.now
@@ -148,11 +148,11 @@ module DigitalFabric
148
148
  end
149
149
 
150
150
  def is_long_running_request_response?(msg)
151
- case msg[:kind]
151
+ case msg[Protocol::Attribute::KIND]
152
152
  when Protocol::HTTP_UPGRADE
153
153
  true
154
154
  when Protocol::HTTP_RESPONSE
155
- msg[:body] && !msg[:complete]
155
+ !msg[Protocol::Attribute::HttpResponse::COMPLETE]
156
156
  end
157
157
  end
158
158
 
@@ -165,7 +165,7 @@ module DigitalFabric
165
165
 
166
166
  def recv_http_request(msg)
167
167
  req = prepare_http_request(msg)
168
- id = msg['id']
168
+ id = msg[Protocol::Attribute::ID]
169
169
  @requests[id] = spin do
170
170
  http_request(req)
171
171
  rescue IOError, Errno::ECONNREFUSED, Errno::EPIPE
@@ -180,17 +180,20 @@ module DigitalFabric
180
180
  end
181
181
 
182
182
  def prepare_http_request(msg)
183
- req = Qeweney::Request.new(msg['headers'], RequestAdapter.new(self, msg))
184
- req.buffer_body_chunk(msg['body']) if msg['body']
185
- req.complete! if msg['complete']
183
+ headers = msg[Protocol::Attribute::HttpRequest::HEADERS]
184
+ body_chunk = msg[Protocol::Attribute::HttpRequest::BODY_CHUNK]
185
+ complete = msg[Protocol::Attribute::HttpRequest::COMPLETE]
186
+ req = Qeweney::Request.new(headers, RequestAdapter.new(self, msg))
187
+ req.buffer_body_chunk(body_chunk) if body_chunk
188
+ req.complete! if complete
186
189
  req
187
190
  end
188
191
 
189
192
  def recv_http_request_body(msg)
190
- fiber = @requests[msg['id']]
193
+ fiber = @requests[msg[Protocol::Attribute::ID]]
191
194
  return unless fiber
192
195
 
193
- fiber << msg['body']
196
+ fiber << msg[Protocol::Attribute::HttpRequestBody::BODY]
194
197
  end
195
198
 
196
199
  def get_http_request_body(id, limit)
@@ -199,8 +202,8 @@ module DigitalFabric
199
202
  end
200
203
 
201
204
  def recv_ws_request(msg)
202
- req = Qeweney::Request.new(msg['headers'], RequestAdapter.new(self, msg))
203
- id = msg['id']
205
+ req = Qeweney::Request.new(msg[Protocol::Attribute::WS::HEADERS], RequestAdapter.new(self, msg))
206
+ id = msg[Protocol::Attribute::ID]
204
207
  @requests[id] = @long_running_requests[id] = spin do
205
208
  ws_request(req)
206
209
  rescue IOError, Errno::ECONNREFUSED, Errno::EPIPE
@@ -31,6 +31,7 @@ module DigitalFabric
31
31
  def run
32
32
  @fiber = Fiber.current
33
33
  @service.mount(route, self)
34
+ @mounted = true
34
35
  keep_alive_timer = spin_loop(interval: 5) { keep_alive }
35
36
  process_incoming_messages(false)
36
37
  rescue GracefulShutdown
@@ -38,7 +39,7 @@ module DigitalFabric
38
39
  process_incoming_messages(true)
39
40
  ensure
40
41
  keep_alive_timer&.stop
41
- @service.unmount(self)
42
+ unmount
42
43
  end
43
44
 
44
45
  def process_incoming_messages(shutdown = false)
@@ -48,7 +49,15 @@ module DigitalFabric
48
49
  recv_df_message(msg)
49
50
  return if shutdown && @requests.empty?
50
51
  end
51
- rescue TimeoutError, IOError
52
+ rescue TimeoutError, IOError, SystemCallError
53
+ # ignore and just return in order to terminate the proxy
54
+ end
55
+
56
+ def unmount
57
+ return unless @mounted
58
+
59
+ @service.unmount(self)
60
+ @mounted = nil
52
61
  end
53
62
 
54
63
  def shutdown
@@ -82,9 +91,16 @@ module DigitalFabric
82
91
 
83
92
  def recv_df_message(message)
84
93
  @last_recv = Time.now
85
- return if message['kind'] == Protocol::PING
94
+ # puts "<<< #{message.inspect}"
86
95
 
87
- handler = @requests[message['id']]
96
+ case message[Protocol::Attribute::KIND]
97
+ when Protocol::PING
98
+ return
99
+ when Protocol::UNMOUNT
100
+ return unmount
101
+ end
102
+
103
+ handler = @requests[message[Protocol::Attribute::ID]]
88
104
  if !handler
89
105
  # puts "Unknown request id in #{message}"
90
106
  return
@@ -94,6 +110,8 @@ module DigitalFabric
94
110
  end
95
111
 
96
112
  def send_df_message(message)
113
+ # puts ">>> #{message.inspect}" unless message[Protocol::Attribute::KIND] == Protocol::PING
114
+
97
115
  @last_send = Time.now
98
116
  @conn << message.to_msgpack
99
117
  end
@@ -130,41 +148,51 @@ module DigitalFabric
130
148
  t1 = Time.now
131
149
  @service.record_latency_measurement(t1 - t0)
132
150
  end
133
- return if http_request_message(id, req, message)
151
+ kind = message[Protocol::Attribute::KIND]
152
+ attributes = message[Protocol::Attribute::HttpRequest::HEADERS..-1]
153
+ return if http_request_message(id, req, kind, attributes)
134
154
  end
135
155
  end
136
156
  rescue => e
137
- req.respond("Error: #{e.inspect}", ':status' => Qeweney::Status::INTERNAL_SERVER_ERROR)
157
+ p "Internal server error: #{e.inspect}"
158
+ puts e.backtrace.join("\n")
159
+ http_request_send_error_response(e)
160
+ end
161
+
162
+ def http_request_send_error_response(error)
163
+ response = format("Error: %s\n%s", error.inspect, error.backtrace.join("\n"))
164
+ req.respond(response, ':status' => Qeweney::Status::INTERNAL_SERVER_ERROR)
165
+ rescue IOError, SystemCallError
166
+ # ignore
138
167
  end
139
168
 
140
169
  # @return [Boolean] true if response is complete
141
- def http_request_message(id, req, message)
142
- case message['kind']
170
+ def http_request_message(id, req, kind, message)
171
+ case kind
143
172
  when Protocol::HTTP_UPGRADE
144
- http_custom_upgrade(id, req, message)
173
+ http_custom_upgrade(id, req, *message)
145
174
  true
146
175
  when Protocol::HTTP_GET_REQUEST_BODY
147
- http_get_request_body(id, req, message)
176
+ http_get_request_body(id, req, *message)
148
177
  false
149
178
  when Protocol::HTTP_RESPONSE
150
- headers = message['headers']
151
- body = message['body']
152
- done = message['complete']
153
- req.send_headers(headers) if headers && !req.headers_sent?
154
- req.send_chunk(body, done: done) if body or done
155
- done
179
+ http_response(id, req, *message)
156
180
  else
157
181
  # invalid message
158
182
  true
159
183
  end
160
184
  end
161
185
 
186
+ def send_transfer_count(key, rx, tx)
187
+ send_df_message(Protocol.transfer_count(key, rx, tx))
188
+ end
189
+
162
190
  HTTP_RESPONSE_UPGRADE_HEADERS = { ':status' => Qeweney::Status::SWITCHING_PROTOCOLS }
163
191
 
164
- def http_custom_upgrade(id, req, message)
192
+ def http_custom_upgrade(id, req, headers)
165
193
  # send upgrade response
166
- upgrade_headers = message['headers'] ?
167
- message['headers'].merge(HTTP_RESPONSE_UPGRADE_HEADERS) :
194
+ upgrade_headers = headers ?
195
+ headers.merge(HTTP_RESPONSE_UPGRADE_HEADERS) :
168
196
  HTTP_RESPONSE_UPGRADE_HEADERS
169
197
  req.send_headers(upgrade_headers, true)
170
198
 
@@ -182,9 +210,9 @@ module DigitalFabric
182
210
  end
183
211
 
184
212
  def http_custom_upgrade_message(conn, message)
185
- case message['kind']
213
+ case message[Protocol::Attribute::KIND]
186
214
  when Protocol::CONN_DATA
187
- conn << message['data']
215
+ conn << message[:Protocol::Attribute::ConnData::DATA]
188
216
  false
189
217
  when Protocol::CONN_CLOSE
190
218
  true
@@ -194,8 +222,30 @@ module DigitalFabric
194
222
  end
195
223
  end
196
224
 
197
- def http_get_request_body(id, req, message)
198
- case (limit = message['limit'])
225
+ def http_response(id, req, body, headers, complete, transfer_count_key)
226
+ if !req.headers_sent? && complete
227
+ req.respond(body, headers|| {})
228
+ if transfer_count_key
229
+ rx, tx = req.transfer_counts
230
+ send_transfer_count(transfer_count_key, rx, tx)
231
+ end
232
+ true
233
+ else
234
+ req.send_headers(headers) if headers && !req.headers_sent?
235
+ req.send_chunk(body, done: complete) if body or complete
236
+
237
+ if complete && transfer_count_key
238
+ rx, tx = req.transfer_counts
239
+ send_transfer_count(transfer_count_key, rx, tx)
240
+ end
241
+ complete
242
+ end
243
+ rescue IOError, SystemCallError
244
+ # ignore error
245
+ end
246
+
247
+ def http_get_request_body(id, req, limit)
248
+ case limit
199
249
  when nil
200
250
  body = req.read
201
251
  else
@@ -225,9 +275,9 @@ module DigitalFabric
225
275
  with_request do |id|
226
276
  send_df_message(Protocol.ws_request(id, req.headers))
227
277
  response = receive
228
- case response['kind']
278
+ case response[0]
229
279
  when Protocol::WS_RESPONSE
230
- headers = response['headers'] || {}
280
+ headers = response[2] || {}
231
281
  status = headers[':status'] || Qeweney::Status::SWITCHING_PROTOCOLS
232
282
  if status != Qeweney::Status::SWITCHING_PROTOCOLS
233
283
  req.respond(nil, headers)
@@ -239,6 +289,8 @@ module DigitalFabric
239
289
  req.respond(nil, ':status' => Qeweney::Status::SERVICE_UNAVAILABLE)
240
290
  end
241
291
  end
292
+ rescue IOError, SystemCallError
293
+ # ignore
242
294
  end
243
295
 
244
296
  def run_websocket_connection(id, websocket)
@@ -248,9 +300,9 @@ module DigitalFabric
248
300
  end
249
301
  end
250
302
  while (message = receive)
251
- case message['kind']
303
+ case message[Protocol::Attribute::KIND]
252
304
  when Protocol::WS_DATA
253
- websocket << message['data']
305
+ websocket << message[Protocol::Attribute::WS::DATA]
254
306
  when Protocol::WS_CLOSE
255
307
  return
256
308
  else