tipi 0.41 → 0.46

Sign up to get free protection for your applications and to get access to all the features.
Files changed (69) hide show
  1. checksums.yaml +4 -4
  2. data/.github/FUNDING.yml +1 -0
  3. data/.github/workflows/test.yml +3 -1
  4. data/.gitignore +3 -1
  5. data/CHANGELOG.md +34 -0
  6. data/Gemfile +7 -1
  7. data/Gemfile.lock +53 -33
  8. data/README.md +184 -8
  9. data/Rakefile +1 -7
  10. data/benchmarks/bm_http1_parser.rb +85 -0
  11. data/bin/benchmark +37 -0
  12. data/bin/h1pd +6 -0
  13. data/bin/tipi +3 -21
  14. data/bm.png +0 -0
  15. data/df/agent.rb +1 -1
  16. data/df/sample_agent.rb +2 -2
  17. data/df/server.rb +3 -1
  18. data/df/server_utils.rb +48 -46
  19. data/examples/full_service.rb +13 -0
  20. data/examples/hello.rb +5 -0
  21. data/examples/hello.ru +3 -3
  22. data/examples/http1_parser.rb +10 -8
  23. data/examples/http_server.js +1 -1
  24. data/examples/http_server.rb +4 -1
  25. data/examples/http_server_graceful.rb +1 -1
  26. data/examples/https_server.rb +41 -15
  27. data/examples/rack_server_forked.rb +26 -0
  28. data/examples/rack_server_https_forked.rb +1 -1
  29. data/examples/servername_cb.rb +37 -0
  30. data/examples/websocket_demo.rb +1 -1
  31. data/lib/tipi/acme.rb +320 -0
  32. data/lib/tipi/cli.rb +93 -0
  33. data/lib/tipi/config_dsl.rb +13 -13
  34. data/lib/tipi/configuration.rb +2 -2
  35. data/lib/tipi/controller/bare_polyphony.rb +0 -0
  36. data/lib/tipi/controller/bare_stock.rb +10 -0
  37. data/lib/tipi/controller/extensions.rb +37 -0
  38. data/lib/tipi/controller/stock_http1_adapter.rb +15 -0
  39. data/lib/tipi/controller/web_polyphony.rb +353 -0
  40. data/lib/tipi/controller/web_stock.rb +635 -0
  41. data/lib/tipi/controller.rb +12 -0
  42. data/lib/tipi/digital_fabric/agent.rb +5 -5
  43. data/lib/tipi/digital_fabric/agent_proxy.rb +15 -8
  44. data/lib/tipi/digital_fabric/executive.rb +7 -3
  45. data/lib/tipi/digital_fabric/protocol.rb +3 -3
  46. data/lib/tipi/digital_fabric/request_adapter.rb +0 -4
  47. data/lib/tipi/digital_fabric/service.rb +17 -18
  48. data/lib/tipi/handler.rb +2 -2
  49. data/lib/tipi/http1_adapter.rb +85 -124
  50. data/lib/tipi/http2_adapter.rb +29 -16
  51. data/lib/tipi/http2_stream.rb +52 -57
  52. data/lib/tipi/rack_adapter.rb +2 -2
  53. data/lib/tipi/response_extensions.rb +1 -1
  54. data/lib/tipi/supervisor.rb +75 -0
  55. data/lib/tipi/version.rb +1 -1
  56. data/lib/tipi/websocket.rb +3 -3
  57. data/lib/tipi.rb +9 -7
  58. data/test/coverage.rb +2 -2
  59. data/test/helper.rb +60 -12
  60. data/test/test_http_server.rb +14 -41
  61. data/test/test_request.rb +2 -29
  62. data/tipi.gemspec +10 -10
  63. metadata +80 -54
  64. data/examples/automatic_certificate.rb +0 -193
  65. data/ext/tipi/extconf.rb +0 -12
  66. data/ext/tipi/http1_parser.c +0 -534
  67. data/ext/tipi/http1_parser.h +0 -18
  68. data/ext/tipi/tipi_ext.c +0 -5
  69. data/lib/tipi/http1_adapter_new.rb +0 -293
@@ -0,0 +1,635 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ever'
4
+ require 'localhost/authority'
5
+ require 'http/parser'
6
+ require 'qeweney'
7
+ require 'tipi/rack_adapter'
8
+ require_relative './extensions'
9
+
10
+ module Tipi
11
+ class Listener
12
+ def initialize(server, &handler)
13
+ @server = server
14
+ @handler = handler
15
+ end
16
+
17
+ def accept
18
+ socket, _addrinfo = @server.accept
19
+ @handler.call(socket)
20
+ end
21
+ end
22
+
23
+ class Connection
24
+ def io_ready
25
+ raise NotImplementedError
26
+ end
27
+ end
28
+
29
+ class HTTP1Connection < Connection
30
+ attr_reader :io
31
+
32
+ def initialize(io, evloop, &app)
33
+ @io = io
34
+ @evloop = evloop
35
+ @parser = Http::Parser.new(self)
36
+ @app = app
37
+ setup_read_request
38
+ end
39
+
40
+ def setup_read_request
41
+ @request_complete = nil
42
+ @request = nil
43
+ @response_buffer = nil
44
+ end
45
+
46
+ def on_headers_complete(headers)
47
+ headers = normalize_headers(headers)
48
+ headers[':path'] = @parser.request_url
49
+ headers[':method'] = @parser.http_method.downcase
50
+ scheme = (proto = headers['x-forwarded-proto']) ?
51
+ proto.downcase : scheme_from_connection
52
+ headers[':scheme'] = scheme
53
+ @request = Qeweney::Request.new(headers, self)
54
+ end
55
+
56
+ def normalize_headers(headers)
57
+ headers.each_with_object({}) do |(k, v), h|
58
+ k = k.downcase
59
+ hk = h[k]
60
+ if hk
61
+ hk = h[k] = [hk] unless hk.is_a?(Array)
62
+ v.is_a?(Array) ? hk.concat(v) : hk << v
63
+ else
64
+ h[k] = v
65
+ end
66
+ end
67
+ end
68
+
69
+ def scheme_from_connection
70
+ @io.is_a?(OpenSSL::SSL::SSLSocket) ? 'https' : 'http'
71
+ end
72
+
73
+ def on_body(chunk)
74
+ @request.buffer_body_chunk(chunk)
75
+ end
76
+
77
+ def on_message_complete
78
+ @request_complete = true
79
+ end
80
+
81
+ def io_ready
82
+ if !@request_complete
83
+ handle_read_request
84
+ else
85
+ handle_write_response
86
+ end
87
+ end
88
+
89
+ def handle_read_request
90
+ result = @io.read_nonblock(16384, exception: false)
91
+ case result
92
+ when :wait_readable
93
+ watch_io(false)
94
+ when :wait_writable
95
+ watch_io(true)
96
+ when nil
97
+ close_io
98
+ else
99
+ @parser << result
100
+ if @request_complete
101
+ handle_request
102
+ # @response = handle_request(@request_headers, @request_body)
103
+ # handle_write_response
104
+ else
105
+ watch_io(false)
106
+ end
107
+ end
108
+ rescue HTTP::Parser::Error, SystemCallError, IOError
109
+ close_io
110
+ end
111
+
112
+ def watch_io(rw)
113
+ @evloop.watch_io(self, @io, rw, true)
114
+ # @evloop.emit([:watch_io, self, @io, rw, true])
115
+ end
116
+
117
+ def close_io
118
+ @evloop.emit([:close_io, self, @io])
119
+ end
120
+
121
+ def handle_request
122
+ @app.call(@request)
123
+ # req = Qeweney::Request.new(headers, self)
124
+ # response_body = "Hello, world!"
125
+ # "HTTP/1.1 200 OK\nContent-Length: #{response_body.bytesize}\n\n#{response_body}"
126
+ end
127
+
128
+ # response API
129
+
130
+ CRLF = "\r\n"
131
+ CRLF_ZERO_CRLF_CRLF = "\r\n0\r\n\r\n"
132
+
133
+ # Sends response including headers and body. Waits for the request to complete
134
+ # if not yet completed. The body is sent using chunked transfer encoding.
135
+ # @param request [Qeweney::Request] HTTP request
136
+ # @param body [String] response body
137
+ # @param headers
138
+ def respond(request, body, headers)
139
+ formatted_headers = format_headers(headers, body, false)
140
+ request.tx_incr(formatted_headers.bytesize + (body ? body.bytesize : 0))
141
+ if body
142
+ handle_write(formatted_headers + body)
143
+ else
144
+ handle_write(formatted_headers)
145
+ end
146
+ end
147
+
148
+ # Sends response headers. If empty_response is truthy, the response status
149
+ # code will default to 204, otherwise to 200.
150
+ # @param request [Qeweney::Request] HTTP request
151
+ # @param headers [Hash] response headers
152
+ # @param empty_response [boolean] whether a response body will be sent
153
+ # @param chunked [boolean] whether to use chunked transfer encoding
154
+ # @return [void]
155
+ def send_headers(request, headers, empty_response: false, chunked: true)
156
+ formatted_headers = format_headers(headers, !empty_response, http1_1?(request) && chunked)
157
+ request.tx_incr(formatted_headers.bytesize)
158
+ handle_write(formatted_headers)
159
+ end
160
+
161
+ def http1_1?(request)
162
+ request.headers[':protocol'] == 'http/1.1'
163
+ end
164
+
165
+ # Sends a response body chunk. If no headers were sent, default headers are
166
+ # sent using #send_headers. if the done option is true(thy), an empty chunk
167
+ # will be sent to signal response completion to the client.
168
+ # @param request [Qeweney::Request] HTTP request
169
+ # @param chunk [String] response body chunk
170
+ # @param done [boolean] whether the response is completed
171
+ # @return [void]
172
+ def send_chunk(request, chunk, done: false)
173
+ data = +''
174
+ data << "#{chunk.bytesize.to_s(16)}\r\n#{chunk}\r\n" if chunk
175
+ data << "0\r\n\r\n" if done
176
+ return if data.empty?
177
+
178
+ request.tx_incr(data.bytesize)
179
+ handle_write(data)
180
+ end
181
+
182
+ # Finishes the response to the current request. If no headers were sent,
183
+ # default headers are sent using #send_headers.
184
+ # @return [void]
185
+ def finish(request)
186
+ request.tx_incr(5)
187
+ handle_write("0\r\n\r\n")
188
+ end
189
+
190
+ INTERNAL_HEADER_REGEXP = /^:/.freeze
191
+
192
+ # Formats response headers into an array. If empty_response is true(thy),
193
+ # the response status code will default to 204, otherwise to 200.
194
+ # @param headers [Hash] response headers
195
+ # @param body [boolean] whether a response body will be sent
196
+ # @param chunked [boolean] whether to use chunked transfer encoding
197
+ # @return [String] formatted response headers
198
+ def format_headers(headers, body, chunked)
199
+ status = headers[':status']
200
+ status ||= (body ? Qeweney::Status::OK : Qeweney::Status::NO_CONTENT)
201
+ lines = format_status_line(body, status, chunked)
202
+ headers.each do |k, v|
203
+ next if k =~ INTERNAL_HEADER_REGEXP
204
+
205
+ collect_header_lines(lines, k, v)
206
+ end
207
+ lines << CRLF
208
+ lines
209
+ end
210
+
211
+ def format_status_line(body, status, chunked)
212
+ if !body
213
+ empty_status_line(status)
214
+ else
215
+ with_body_status_line(status, body, chunked)
216
+ end
217
+ end
218
+
219
+ def empty_status_line(status)
220
+ if status == 204
221
+ +"HTTP/1.1 #{status}\r\n"
222
+ else
223
+ +"HTTP/1.1 #{status}\r\nContent-Length: 0\r\n"
224
+ end
225
+ end
226
+
227
+ def with_body_status_line(status, body, chunked)
228
+ if chunked
229
+ +"HTTP/1.1 #{status}\r\nTransfer-Encoding: chunked\r\n"
230
+ else
231
+ +"HTTP/1.1 #{status}\r\nContent-Length: #{body.is_a?(String) ? body.bytesize : body.to_i}\r\n"
232
+ end
233
+ end
234
+
235
+ def collect_header_lines(lines, key, value)
236
+ if value.is_a?(Array)
237
+ value.inject(lines) { |_, item| lines << "#{key}: #{item}\r\n" }
238
+ else
239
+ lines << "#{key}: #{value}\r\n"
240
+ end
241
+ end
242
+
243
+ def handle_write(data = nil)
244
+ if data
245
+ if @response_buffer
246
+ @response_buffer << data
247
+ else
248
+ @response_buffer = +data
249
+ end
250
+ end
251
+
252
+ result = @io.write_nonblock(@response_buffer, exception: false)
253
+ case result
254
+ when :wait_readable
255
+ watch_io(false)
256
+ when :wait_writable
257
+ watch_io(true)
258
+ when nil
259
+ close_io
260
+ else
261
+ setup_read_request
262
+ watch_io(false)
263
+ end
264
+ end
265
+ end
266
+
267
+ class Controller
268
+ def initialize(opts)
269
+ @opts = opts
270
+ @path = File.expand_path(@opts['path'])
271
+ @service = prepare_service
272
+ end
273
+
274
+ WORKER_COUNT_RANGE = (1..32).freeze
275
+
276
+ def run
277
+ worker_count = (@opts['workers'] || 1).to_i.clamp(WORKER_COUNT_RANGE)
278
+ return run_worker if worker_count == 1
279
+
280
+ supervise_workers(worker_count)
281
+ end
282
+
283
+ private
284
+
285
+ def supervise_workers(worker_count)
286
+ supervisor = spin do
287
+ worker_count.times do
288
+ pid = fork { run_worker }
289
+ puts "Forked worker pid: #{pid}"
290
+ Process.wait(pid)
291
+ puts "Done worker pid: #{pid}"
292
+ end
293
+ # supervise(restart: :always)
294
+ rescue Polyphony::Terminate
295
+ # TODO: find out how Terminate can leak like that (it's supposed to be
296
+ # caught in Fiber#run)
297
+ end
298
+ # trap('SIGTERM') { supervisor.terminate(true) }
299
+ # trap('SIGINT') do
300
+ # trap('SIGINT') { exit! }
301
+ # supervisor.terminate(true)
302
+ # end
303
+
304
+ # supervisor.await
305
+ end
306
+
307
+ def run_worker
308
+ @evloop = Ever::Loop.new
309
+ start_server(@service)
310
+ trap('SIGTERM') { @evloop.stop }
311
+ trap('SIGINT') do
312
+ trap('SIGINT') { exit! }
313
+ @evloop.stop
314
+ end
315
+ run_evloop
316
+ end
317
+
318
+ def run_evloop
319
+ @evloop.each do |event|
320
+ case event
321
+ when Listener
322
+ event.accept
323
+ when Connection
324
+ event.io_ready
325
+ when Array
326
+ cmd, key, io, rw, oneshot = event
327
+ case cmd
328
+ when :watch_io
329
+ @evloop.watch_io(key, io, rw, oneshot)
330
+ when :close_io
331
+ io.close
332
+ end
333
+ end
334
+ end
335
+ end
336
+
337
+ def prepare_service
338
+ if File.file?(@path)
339
+ File.extname(@path) == '.ru' ? rack_service : tipi_service
340
+ elsif File.directory?(@path)
341
+ static_service
342
+ else
343
+ raise "Invalid path specified #{@path}"
344
+ end
345
+ end
346
+
347
+ def start_app
348
+ if File.extname(@path) == '.ru'
349
+ start_rack_app
350
+ else
351
+ require(@path)
352
+ end
353
+ end
354
+
355
+ def rack_service
356
+ puts "Loading Rack app from #{@path}"
357
+ app = Tipi::RackAdapter.load(@path)
358
+ web_service(app)
359
+ end
360
+
361
+ def tipi_service
362
+ puts "Loading Tipi app from #{@path}"
363
+ require(@path)
364
+ app = Tipi.app
365
+ if !app
366
+ raise "No app define. The app to run should be set using `Tipi.app = ...`"
367
+ end
368
+ web_service(app)
369
+ end
370
+
371
+ def static_service
372
+ puts "Serving static files from #{@path}"
373
+ app = proc do |req|
374
+ p req: req
375
+ full_path = find_path(@path, req.path)
376
+ if full_path
377
+ req.serve_file(full_path)
378
+ else
379
+ req.respond(nil, ':status' => Qeweney::Status::NOT_FOUND)
380
+ end
381
+ end
382
+ web_service(app)
383
+ end
384
+
385
+ def web_service(app)
386
+ app = add_connection_headers(app)
387
+
388
+ prepare_listener(@opts['listen'], app)
389
+ end
390
+
391
+ def prepare_listener(spec, app)
392
+ case spec.shift
393
+ when 'http'
394
+ case spec.size
395
+ when 2
396
+ host, port = spec
397
+ port ||= 80
398
+ when 1
399
+ host = '0.0.0.0'
400
+ port = spec.first || 80
401
+ else
402
+ raise "Invalid listener spec"
403
+ end
404
+ prepare_http_listener(port, app)
405
+ when 'https'
406
+ case spec.size
407
+ when 2
408
+ host, port = spec
409
+ port ||= 80
410
+ when 1
411
+ host = 'localhost'
412
+ port = spec.first || 80
413
+ else
414
+ raise "Invalid listener spec"
415
+ end
416
+ port ||= 443
417
+ prepare_https_listener(host, port, app)
418
+ when 'full'
419
+ host, http_port, https_port = spec
420
+ http_port ||= 80
421
+ https_port ||= 443
422
+ prepare_full_service_listeners(host, http_port, https_port, app)
423
+ end
424
+ end
425
+
426
+ def prepare_http_listener(port, app)
427
+ puts "Listening for HTTP on localhost:#{port}"
428
+
429
+ proc do
430
+ start_listener('HTTP', port) do |socket|
431
+ start_client(socket, &app)
432
+ end
433
+ end
434
+ end
435
+
436
+ def start_client(socket, &app)
437
+ conn = HTTP1Connection.new(socket, @evloop, &app)
438
+ conn.watch_io(false)
439
+ end
440
+
441
+ LOCALHOST_REGEXP = /^(.+\.)?localhost$/.freeze
442
+
443
+ def prepare_https_listener(host, port, app)
444
+ localhost = host =~ LOCALHOST_REGEXP
445
+ return prepare_localhost_https_listener(port, app) if localhost
446
+
447
+ raise "No certificate found for #{host}"
448
+ # TODO: implement loading certificate
449
+ end
450
+
451
+ def prepare_localhost_https_listener(port, app)
452
+ puts "Listening for HTTPS on localhost:#{port}"
453
+
454
+ authority = Localhost::Authority.fetch
455
+ ctx = authority.server_context
456
+ ctx.ciphers = 'ECDH+aRSA'
457
+ Polyphony::Net.setup_alpn(ctx, Tipi::ALPN_PROTOCOLS)
458
+
459
+ proc do
460
+ https_listener = spin_accept_loop('HTTPS', port) do |socket|
461
+ start_https_connection_fiber(socket, ctx, nil, app)
462
+ rescue Exception => e
463
+ puts "Exception in https_listener block: #{e.inspect}\n#{e.backtrace.inspect}"
464
+ end
465
+ end
466
+ end
467
+
468
+ def prepare_full_service_listeners(host, http_port, https_port, app)
469
+ puts "Listening for HTTP on localhost:#{http_port}"
470
+ puts "Listening for HTTPS on localhost:#{https_port}"
471
+
472
+ redirect_host = (https_port == 443) ? host : "#{host}:#{https_port}"
473
+ redirect_app = ->(r) { r.redirect("https://#{redirect_host}#{r.path}") }
474
+ ctx = OpenSSL::SSL::SSLContext.new
475
+ ctx.ciphers = 'ECDH+aRSA'
476
+ Polyphony::Net.setup_alpn(ctx, Tipi::ALPN_PROTOCOLS)
477
+ certificate_store = create_certificate_store
478
+
479
+ proc do
480
+ challenge_handler = Tipi::ACME::HTTPChallengeHandler.new
481
+ certificate_manager = Tipi::ACME::CertificateManager.new(
482
+ master_ctx: ctx,
483
+ store: certificate_store,
484
+ challenge_handler: challenge_handler
485
+ )
486
+ http_app = certificate_manager.challenge_routing_app(redirect_app)
487
+
488
+ http_listener = spin_accept_loop('HTTP', http_port) do |socket|
489
+ Tipi.client_loop(socket, @opts, &http_app)
490
+ end
491
+
492
+ ssl_accept_thread_pool = Polyphony::ThreadPool.new(4)
493
+
494
+ https_listener = spin_accept_loop('HTTPS', https_port) do |socket|
495
+ start_https_connection_fiber(socket, ctx, ssl_accept_thread_pool, app)
496
+ rescue Exception => e
497
+ puts "Exception in https_listener block: #{e.inspect}\n#{e.backtrace.inspect}"
498
+ end
499
+ end
500
+
501
+ end
502
+
503
+ INVALID_PATH_REGEXP = /\/?(\.\.|\.)\//
504
+
505
+ def find_path(base, path)
506
+ return nil if path =~ INVALID_PATH_REGEXP
507
+
508
+ full_path = File.join(base, path)
509
+ return full_path if File.file?(full_path)
510
+ return find_path(full_path, 'index') if File.directory?(full_path)
511
+
512
+ qualified = "#{full_path}.html"
513
+ return qualified if File.file?(qualified)
514
+
515
+ nil
516
+ end
517
+
518
+ SOCKET_OPTS = {
519
+ reuse_addr: true,
520
+ reuse_port: true,
521
+ dont_linger: true,
522
+ }.freeze
523
+
524
+ def start_listener(name, port, &block)
525
+ host = '0.0.0.0'
526
+ socket = ::Socket.new(:INET, :STREAM).tap do |s|
527
+ s.setsockopt(Socket::SOL_SOCKET, Socket::SO_REUSEADDR, 1)
528
+ s.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_REUSEPORT, 1)
529
+ s.setsockopt(Socket::SOL_SOCKET, Socket::SO_LINGER, [0, 0].pack('ii'))
530
+ addr = ::Socket.sockaddr_in(port, host)
531
+ s.bind(addr)
532
+ s.listen(Socket::SOMAXCONN)
533
+ end
534
+ listener = Listener.new(socket, &block)
535
+ @evloop.watch_io(listener, socket, false, false)
536
+ end
537
+
538
+ def spin_accept_loop(name, port, &block)
539
+ spin do
540
+ server = Polyphony::Net.tcp_listen('0.0.0.0', port, SOCKET_OPTS)
541
+ loop do
542
+ socket = server.accept
543
+ spin_connection_handler(name, socket, block)
544
+ rescue Polyphony::BaseException => e
545
+ raise
546
+ rescue Exception => e
547
+ puts "#{name} listener uncaught exception: #{e.inspect}"
548
+ end
549
+ ensure
550
+ finalize_listener(server) if server
551
+ end
552
+ end
553
+
554
+ def spin_connection_handler(name, socket, block)
555
+ spin do
556
+ block.(socket)
557
+ rescue Polyphony::BaseException
558
+ raise
559
+ rescue Exception => e
560
+ puts "Uncaught error in #{name} handler: #{e.inspect}"
561
+ p e.backtrace
562
+ end
563
+ end
564
+
565
+ def finalize_listener(server)
566
+ fiber = Fiber.current
567
+ gracefully_terminate_conections(fiber) if fiber.graceful_shutdown?
568
+ server.close
569
+ rescue Polyphony::BaseException
570
+ raise
571
+ rescue Exception => e
572
+ trace "Exception in finalize_listener: #{e.inspect}"
573
+ end
574
+
575
+ def gracefully_terminate_conections(fiber)
576
+ supervisor = spin { supervise }.detach
577
+ fiber.attach_all_children_to(supervisor)
578
+
579
+ # terminating the supervisor will
580
+ supervisor.terminate(true)
581
+ end
582
+
583
+ def add_connection_headers(app)
584
+ app
585
+ # proc do |req|
586
+ # conn = req.adapter.conn
587
+ # # req.headers[':peer'] = conn.peeraddr(false)[2]
588
+ # req.headers[':scheme'] ||= conn.is_a?(OpenSSL::SSL::SSLSocket) ? 'https' : 'http'
589
+ # app.(req)
590
+ # end
591
+ end
592
+
593
+ def ssl_accept(client)
594
+ client.accept
595
+ true
596
+ rescue Polyphony::BaseException
597
+ raise
598
+ rescue Exception => e
599
+ p e
600
+ e
601
+ end
602
+
603
+ def start_https_connection_fiber(socket, ctx, thread_pool, app)
604
+ client = OpenSSL::SSL::SSLSocket.new(socket, ctx)
605
+ client.sync_close = true
606
+
607
+ result = thread_pool ?
608
+ thread_pool.process { ssl_accept(client) } : ssl_accept(client)
609
+
610
+ if result.is_a?(Exception)
611
+ puts "Exception in SSL handshake: #{result.inspect}"
612
+ return
613
+ end
614
+
615
+ Tipi.client_loop(client, @opts, &app)
616
+ rescue => e
617
+ puts "Uncaught error in HTTPS connection fiber: #{e.inspect} bt: #{e.backtrace.inspect}"
618
+ ensure
619
+ (client ? client.close : socket.close) rescue nil
620
+ end
621
+
622
+ CERTIFICATE_STORE_DEFAULT_DIR = File.expand_path('~/.tipi').freeze
623
+ CERTIFICATE_STORE_DEFAULT_DB_PATH = File.join(
624
+ CERTIFICATE_STORE_DEFAULT_DIR, 'certificates.db').freeze
625
+
626
+ def create_certificate_store
627
+ FileUtils.mkdir(CERTIFICATE_STORE_DEFAULT_DIR) rescue nil
628
+ Tipi::ACME::SQLiteCertificateStore.new(CERTIFICATE_STORE_DEFAULT_DB_PATH)
629
+ end
630
+
631
+ def start_server(service)
632
+ service.call
633
+ end
634
+ end
635
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+ require 'json'
5
+
6
+ # get opts from STDIN
7
+ opts = JSON.parse(ARGV[0]) rescue nil
8
+ mod_path = "./controller/#{opts['app_type']}_#{opts['mode']}"
9
+ require_relative mod_path
10
+
11
+ controller = Tipi::Controller.new(opts)
12
+ controller.run
@@ -46,7 +46,7 @@ module DigitalFabric
46
46
  df_upgrade
47
47
  @connected = true
48
48
  @msgpack_reader = MessagePack::Unpacker.new
49
-
49
+
50
50
  process_incoming_requests
51
51
  rescue IOError, Errno::ECONNREFUSED, Errno::ECONNRESET, Errno::EPIPE, TimeoutError
52
52
  log 'Disconnected' if @connected
@@ -70,9 +70,9 @@ module DigitalFabric
70
70
  Upgrade: df
71
71
  DF-Token: %s
72
72
  DF-Mount: %s
73
-
73
+
74
74
  HTTP
75
-
75
+
76
76
  def df_upgrade
77
77
  @socket << format(UPGRADE_REQUEST, @token, mount_point)
78
78
  while (line = @socket.gets)
@@ -172,8 +172,9 @@ module DigitalFabric
172
172
  http_request(req)
173
173
  rescue IOError, Errno::ECONNREFUSED, Errno::EPIPE
174
174
  # ignore
175
- rescue Polyphony::Terminate
175
+ rescue Polyphony::Terminate => e
176
176
  req.respond(nil, { ':status' => Qeweney::Status::SERVICE_UNAVAILABLE }) if Fiber.current.graceful_shutdown?
177
+ raise e
177
178
  ensure
178
179
  @requests.delete(id)
179
180
  @long_running_requests.delete(id)
@@ -187,7 +188,6 @@ module DigitalFabric
187
188
  complete = msg[Protocol::Attribute::HttpRequest::COMPLETE]
188
189
  req = Qeweney::Request.new(headers, RequestAdapter.new(self, msg))
189
190
  req.buffer_body_chunk(body_chunk) if body_chunk
190
- req.complete! if complete
191
191
  req
192
192
  end
193
193