tipi 0.40 → 0.41

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
2
  SHA256:
3
- metadata.gz: 3e78bf3b20c7ec6704c26869694cf69e389ae1c0c2f2e44c82d0104d2ecaded9
4
- data.tar.gz: dac8b0014cbc3a6ee5b146f3645bd38f3d55b21d5c17d969eecaa62c1e8b9306
3
+ metadata.gz: 5a83c181c05d8d8c8de50f236feab65b800b1bd266c2f0a4d4696d4853ac39fd
4
+ data.tar.gz: d9a84fc9f72c3a5565f587e35fb4ed206c83c863fbb0b4b470449919a687463b
5
5
  SHA512:
6
- metadata.gz: fa53cedcd271ba9dbc37821315d27bf14b1eac17451c912eb3dcaa3ceae3b0b738f5d10678cf390f0534edd9d352008e71dec95c46566cad513e3b2d783ea000
7
- data.tar.gz: 3cf95c22b05c8eaebdf646963d930977ddb010c3ca9174f081cee674e315f5ac672059b31f833ec337c78e3ec43770f72f6bba145ad00e2bd8c1ed8d0acb2174
6
+ metadata.gz: e9c13f387c3431888d9d43c88d5bd00831c4e2e8976bf8ae4e1acc479b8d50fa888cd58af0fc433da2dab8e3ea09b99ca0adbdcc61abc71457a805c26aa03687
7
+ data.tar.gz: df01a54c7c46928c67da321cfc830d8f33e8af7fa8197cd5930f3de73838efd4849d51b10af35b1a5297f7f484b296ff2ed6146dbb8d66476bf0b714f1dbfd68
data/.gitignore CHANGED
@@ -54,4 +54,6 @@ build-iPhoneSimulator/
54
54
 
55
55
  # Used by RuboCop. Remote config files pulled in from inherit_from directive.
56
56
  # .rubocop-https?--*
57
- log
57
+ log
58
+
59
+ lib/tipi_ext*
data/CHANGELOG.md CHANGED
@@ -1,3 +1,10 @@
1
+ ## 0.41 2021-07-26
2
+
3
+ - Fix Rack adapter (#11)
4
+ - Introduce experimental HTTP/1 parser
5
+ - More work on DF server
6
+ - Allow setting chunk size in `#respond_from_io`
7
+
1
8
  ## 0.40 2021-06-24
2
9
 
3
10
  - Implement serving static files using splice_chunks (nice performance boost for
data/Gemfile.lock CHANGED
@@ -1,13 +1,13 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- tipi (0.40)
4
+ tipi (0.41)
5
5
  acme-client (~> 2.0.8)
6
6
  http-2 (~> 0.10.0)
7
7
  http_parser.rb (~> 0.6.0)
8
8
  msgpack (~> 1.4.2)
9
- polyphony (~> 0.57.0)
10
- qeweney (~> 0.10.0)
9
+ polyphony (~> 0.64)
10
+ qeweney (~> 0.11)
11
11
  rack (>= 2.0.8, < 2.3.0)
12
12
  websocket (~> 1.2.8)
13
13
 
@@ -18,40 +18,48 @@ GEM
18
18
  faraday (>= 0.17, < 2.0.0)
19
19
  ansi (1.5.0)
20
20
  builder (3.2.4)
21
- docile (1.3.2)
21
+ cuba (3.9.3)
22
+ rack (>= 1.6.0)
23
+ docile (1.4.0)
22
24
  escape_utils (1.2.1)
23
- faraday (1.4.3)
25
+ faraday (1.5.1)
24
26
  faraday-em_http (~> 1.0)
25
27
  faraday-em_synchrony (~> 1.0)
26
28
  faraday-excon (~> 1.1)
29
+ faraday-httpclient (~> 1.0.1)
27
30
  faraday-net_http (~> 1.0)
28
31
  faraday-net_http_persistent (~> 1.1)
32
+ faraday-patron (~> 1.0)
29
33
  multipart-post (>= 1.2, < 3)
30
34
  ruby2_keywords (>= 0.0.4)
31
35
  faraday-em_http (1.0.0)
32
36
  faraday-em_synchrony (1.0.0)
33
37
  faraday-excon (1.1.0)
38
+ faraday-httpclient (1.0.1)
34
39
  faraday-net_http (1.0.1)
35
- faraday-net_http_persistent (1.1.0)
40
+ faraday-net_http_persistent (1.2.0)
41
+ faraday-patron (1.0.0)
36
42
  http-2 (0.10.2)
37
43
  http_parser.rb (0.6.0)
38
- json (2.3.1)
39
- localhost (1.1.4)
44
+ json (2.5.1)
45
+ localhost (1.1.8)
40
46
  minitest (5.11.3)
41
- minitest-reporters (1.4.2)
47
+ minitest-reporters (1.4.3)
42
48
  ansi
43
49
  builder
44
50
  minitest (>= 5.0)
45
51
  ruby-progressbar
46
52
  msgpack (1.4.2)
47
53
  multipart-post (2.1.1)
48
- polyphony (0.57.0)
49
- qeweney (0.10)
54
+ polyphony (0.64)
55
+ qeweney (0.11)
50
56
  escape_utils (~> 1.2.1)
51
57
  rack (2.2.3)
52
58
  rake (12.3.3)
53
- ruby-progressbar (1.10.1)
54
- ruby2_keywords (0.0.4)
59
+ rake-compiler (1.1.1)
60
+ rake
61
+ ruby-progressbar (1.11.0)
62
+ ruby2_keywords (0.0.5)
55
63
  simplecov (0.17.1)
56
64
  docile (~> 1.1)
57
65
  json (>= 1.8, < 3)
@@ -63,10 +71,12 @@ PLATFORMS
63
71
  ruby
64
72
 
65
73
  DEPENDENCIES
74
+ cuba (~> 3.9.3)
66
75
  localhost (~> 1.1.4)
67
76
  minitest (~> 5.11.3)
68
77
  minitest-reporters (~> 1.4.2)
69
78
  rake (~> 12.3.3)
79
+ rake-compiler (= 1.1.1)
70
80
  simplecov (~> 0.17.1)
71
81
  tipi!
72
82
 
data/Rakefile CHANGED
@@ -3,10 +3,14 @@
3
3
  require "bundler/gem_tasks"
4
4
  require "rake/clean"
5
5
 
6
- # frozen_string_literal: true
6
+ require "rake/extensiontask"
7
+ Rake::ExtensionTask.new("tipi_ext") do |ext|
8
+ ext.ext_dir = "ext/tipi"
9
+ end
10
+
11
+ task :recompile => [:clean, :compile]
12
+ task :default => [:compile, :test]
7
13
 
8
- task :default => [:test]
9
14
  task :test do
10
15
  exec 'ruby test/run.rb'
11
16
  end
12
-
data/df/server.rb CHANGED
@@ -1,111 +1,23 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'bundler/setup'
4
- require 'tipi'
5
- require 'tipi/digital_fabric'
6
- require 'tipi/digital_fabric/executive'
7
- require 'json'
8
- require 'fileutils'
9
- require 'localhost/authority'
3
+ require_relative 'server_utils'
10
4
 
11
- FileUtils.cd(__dir__)
12
-
13
- service = DigitalFabric::Service.new(token: 'foobar')
14
- executive = DigitalFabric::Executive.new(service, { host: 'executive.realiteq.net' })
15
-
16
- GC.disable
17
- Thread.current.backend.idle_gc_period = 60
18
-
19
- # spin_loop(interval: 60) { GC.start }
20
-
21
- class Polyphony::BaseException
22
- attr_reader :caller_backtrace
23
- end
24
-
25
- puts "pid: #{Process.pid}"
26
-
27
- http_listener = spin do
28
- opts = {
29
- reuse_addr: true,
30
- dont_linger: true,
31
- }
32
- puts 'Listening for HTTP on localhost:10080'
33
- server = Polyphony::Net.tcp_listen('0.0.0.0', 10080, opts)
34
- server.accept_loop do |client|
35
- spin do
36
- service.incr_connection_count
37
- Tipi.client_loop(client, opts) { |req| service.http_request(req) }
38
- ensure
39
- service.decr_connection_count
40
- end
41
- rescue Exception => e
42
- puts "HTTP accept_loop error: #{e.inspect}"
43
- puts e.backtrace.join("\n")
44
- end
45
- end
46
-
47
- CERTIFICATE_REGEXP = /(-----BEGIN CERTIFICATE-----\n[^-]+-----END CERTIFICATE-----\n)/.freeze
48
-
49
- https_listener = spin do
50
- private_key = OpenSSL::PKey::RSA.new IO.read('../../reality/ssl/privkey.pem')
51
- c = IO.read('../../reality/ssl/cacert.pem')
52
- certificates = c.scan(CERTIFICATE_REGEXP).map { |p| OpenSSL::X509::Certificate.new(p.first) }
53
- ctx = OpenSSL::SSL::SSLContext.new
54
- cert = certificates.shift
55
- puts "Certificate expires: #{cert.not_after.inspect}"
56
- ctx.add_certificate(cert, private_key, certificates)
57
- ctx.ciphers = 'ECDH+aRSA'
58
-
59
- # TODO: further limit ciphers
60
- # ref: https://github.com/socketry/falcon/blob/3ec805b3ceda0a764a2c5eb68cde33897b6a35ff/lib/falcon/environments/tls.rb
61
- # ref: https://github.com/socketry/falcon/blob/3ec805b3ceda0a764a2c5eb68cde33897b6a35ff/lib/falcon/tls.rb
62
-
63
- opts = {
64
- reuse_addr: true,
65
- dont_linger: true,
66
- secure_context: ctx,
67
- alpn_protocols: Tipi::ALPN_PROTOCOLS
68
- }
69
-
70
- puts 'Listening for HTTPS on localhost:10443'
71
- server = Polyphony::Net.tcp_listen('0.0.0.0', 10443, opts)
72
- loop do
73
- client = server.accept
74
- spin do
75
- service.incr_connection_count
76
- Tipi.client_loop(client, opts) { |req| service.http_request(req) }
77
- rescue Exception => e
78
- puts "Exception: #{e.inspect}"
79
- puts e.backtrace.join("\n")
80
- ensure
81
- service.decr_connection_count
82
- end
83
- rescue Polyphony::BaseException
84
- raise
85
- rescue OpenSSL::SSL::SSLError, SystemCallError => e
86
- puts "HTTPS accept error: #{e.inspect}"
87
- rescue Exception => e
88
- puts "HTTPS accept error: #{e.inspect}"
89
- puts e.backtrace.join("\n")
90
- end
91
- end
92
-
93
- UNIX_SOCKET_PATH = '/tmp/df.sock'
94
- unix_listener = spin do
95
- puts "Listening on #{UNIX_SOCKET_PATH}"
96
- FileUtils.rm(UNIX_SOCKET_PATH) if File.exists?(UNIX_SOCKET_PATH)
97
- socket = UNIXServer.new(UNIX_SOCKET_PATH)
98
- Tipi.accept_loop(socket, {}) { |req| service.http_request(req) }
99
- end
5
+ listeners = [
6
+ listen_http,
7
+ listen_https,
8
+ listen_unix
9
+ ]
100
10
 
101
11
  begin
102
- Fiber.await(http_listener, https_listener, unix_listener)
12
+ log('Starting DF server')
13
+ Fiber.await(*listeners)
103
14
  rescue Interrupt
104
- puts "Got SIGINT, shutting down gracefully"
105
- service.graceful_shutdown
106
- puts "post graceful shutdown"
15
+ log('Got SIGINT, shutting down gracefully')
16
+ @service.graceful_shutdown
17
+ rescue SystemExit
18
+ # ignore
107
19
  rescue Exception => e
108
- puts '*' * 40
109
- p e
110
- puts e.backtrace.join("\n")
20
+ log("Uncaught exception", error: e, backtrace: e.backtrace)
21
+ ensure
22
+ log('DF server stopped')
111
23
  end
@@ -0,0 +1,173 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+ require 'tipi'
5
+ require 'tipi/digital_fabric'
6
+ require 'tipi/digital_fabric/executive'
7
+ require 'json'
8
+ require 'fileutils'
9
+ require 'time'
10
+
11
+ module ::Kernel
12
+ def trace(*args)
13
+ STDOUT.orig_write(format_trace(args))
14
+ end
15
+
16
+ def format_trace(args)
17
+ if args.first.is_a?(String)
18
+ if args.size > 1
19
+ format("%s: %p\n", args.shift, args)
20
+ else
21
+ format("%s\n", args.first)
22
+ end
23
+ else
24
+ format("%p\n", args.size == 1 ? args.first : args)
25
+ end
26
+ end
27
+ end
28
+
29
+ FileUtils.cd(__dir__)
30
+
31
+ @service = DigitalFabric::Service.new(token: 'foobar')
32
+ @executive = DigitalFabric::Executive.new(@service, { host: '@executive.realiteq.net' })
33
+
34
+ @pid = Process.pid
35
+
36
+ def log(msg, **ctx)
37
+ text = format(
38
+ "%s (%d) %s\n",
39
+ Time.now.strftime('%Y-%m-%d %H:%M:%S.%3N'),
40
+ @pid,
41
+ msg
42
+ )
43
+ STDOUT.orig_write text
44
+ return if ctx.empty?
45
+
46
+ ctx.each { |k, v| STDOUT.orig_write format(" %s: %s\n", k, v.inspect) }
47
+ end
48
+
49
+ def listen_http
50
+ spin(:http_listener) do
51
+ opts = {
52
+ reuse_addr: true,
53
+ dont_linger: true,
54
+ }
55
+ log('Listening for HTTP on localhost:10080')
56
+ server = Polyphony::Net.tcp_listen('0.0.0.0', 10080, opts)
57
+ id = 0
58
+ loop do
59
+ client = server.accept
60
+ # log("Accept HTTP connection", client: client)
61
+ spin("http#{id += 1}") do
62
+ @service.incr_connection_count
63
+ Tipi.client_loop(client, opts) { |req| @service.http_request(req) }
64
+ ensure
65
+ # log("Done with HTTP connection", client: client)
66
+ @service.decr_connection_count
67
+ end
68
+ rescue => e
69
+ log("HTTP accept loop error", error: e, backtrace: e.backtrace)
70
+ end
71
+ end
72
+ end
73
+
74
+ CERTIFICATE_REGEXP = /(-----BEGIN CERTIFICATE-----\n[^-]+-----END CERTIFICATE-----\n)/.freeze
75
+
76
+ def listen_https
77
+ spin('https_listener') do
78
+ private_key = OpenSSL::PKey::RSA.new IO.read('../../reality/ssl/privkey.pem')
79
+ c = IO.read('../../reality/ssl/cacert.pem')
80
+ certificates = c.scan(CERTIFICATE_REGEXP).map { |p| OpenSSL::X509::Certificate.new(p.first) }
81
+ ctx = OpenSSL::SSL::SSLContext.new
82
+ cert = certificates.shift
83
+ log "SSL Certificate expires: #{cert.not_after.inspect}"
84
+ ctx.add_certificate(cert, private_key, certificates)
85
+ ctx.ciphers = 'ECDH+aRSA'
86
+
87
+ # TODO: further limit ciphers
88
+ # ref: https://github.com/socketry/falcon/blob/3ec805b3ceda0a764a2c5eb68cde33897b6a35ff/lib/falcon/environments/tls.rb
89
+ # ref: https://github.com/socketry/falcon/blob/3ec805b3ceda0a764a2c5eb68cde33897b6a35ff/lib/falcon/tls.rb
90
+
91
+ opts = {
92
+ reuse_addr: true,
93
+ dont_linger: true,
94
+ secure_context: ctx,
95
+ alpn_protocols: Tipi::ALPN_PROTOCOLS
96
+ }
97
+
98
+ log('Listening for HTTPS on localhost:10443')
99
+ server = Polyphony::Net.tcp_listen('0.0.0.0', 10443, opts)
100
+ id = 0
101
+ loop do
102
+ client = server.accept
103
+ # log('Accept HTTPS client connection', client: client)
104
+ spin("https#{id += 1}") do
105
+ @service.incr_connection_count
106
+ Tipi.client_loop(client, opts) { |req| @service.http_request(req) }
107
+ rescue => e
108
+ log('Error while handling HTTPS client', client: client, error: e, backtrace: e.backtrace)
109
+ ensure
110
+ # log("Done with HTTP connection", client: client)
111
+ @service.decr_connection_count
112
+ end
113
+ rescue OpenSSL::SSL::SSLError, SystemCallError, TypeError => e
114
+ # log('HTTPS accept error', error: e, backtrace: e.backtrace)
115
+ rescue => e
116
+ log('HTTPS accept (unknown) error', error: e, backtrace: e.backtrace)
117
+ end
118
+ end
119
+ end
120
+
121
+ UNIX_SOCKET_PATH = '/tmp/df.sock'
122
+ def listen_unix
123
+ spin(:unix_listener) do
124
+ log("Listening on #{UNIX_SOCKET_PATH}")
125
+ FileUtils.rm(UNIX_SOCKET_PATH) if File.exists?(UNIX_SOCKET_PATH)
126
+ socket = UNIXServer.new(UNIX_SOCKET_PATH)
127
+
128
+ id = 0
129
+ socket.accept_loop do |client|
130
+ # log('Accept Unix connection', client: client)
131
+ spin("unix#{id += 1}") do
132
+ Tipi.client_loop(client, {}) { |req| @service.http_request(req, true) }
133
+ end
134
+ rescue OpenSSL::SSL::SSLError
135
+ # disregard
136
+ end
137
+ end
138
+ end
139
+
140
+ def listen_df
141
+ spin(:df_listener) do
142
+ opts = {
143
+ reuse_addr: true,
144
+ dont_linger: true,
145
+ }
146
+ log('Listening for DF connections on localhost:4321')
147
+ server = Polyphony::Net.tcp_listen('0.0.0.0', 4321, opts)
148
+
149
+ id = 0
150
+ server.accept_loop do |client|
151
+ # log('Accept DF connection', client: client)
152
+ spin("df#{id += 1}") do
153
+ Tipi.client_loop(client, {}) { |req| @service.http_request(req, true) }
154
+ end
155
+ rescue OpenSSL::SSL::SSLError
156
+ # disregard
157
+ end
158
+ end
159
+ end
160
+
161
+ # Thread.backend.trace_proc = proc do |event, fiber, value, pri|
162
+ # fiber_id = fiber.tag || fiber.inspect
163
+ # case event
164
+ # when :fiber_schedule
165
+ # log format("=> %s %s %s %s", event, fiber_id, value.inspect, pri ? '' : '(priority)')
166
+ # when :fiber_run
167
+ # log format("=> %s %s %s", event, fiber_id, value.inspect)
168
+ # when :fiber_create, :fiber_terminate
169
+ # log format("=> %s %s", event, fiber_id)
170
+ # else
171
+ # log format("=> %s", event)
172
+ # end
173
+ # end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'polyphony'
4
+ require_relative '../lib/tipi_ext'
5
+
6
+ i, o = IO.pipe
7
+
8
+ module ::Kernel
9
+ def trace(*args)
10
+ STDOUT.orig_write(format_trace(args))
11
+ end
12
+
13
+ def format_trace(args)
14
+ if args.first.is_a?(String)
15
+ if args.size > 1
16
+ format("%s: %p\n", args.shift, args)
17
+ else
18
+ format("%s\n", args.first)
19
+ end
20
+ else
21
+ format("%p\n", args.size == 1 ? args.first : args)
22
+ end
23
+ end
24
+ end
25
+
26
+ f = spin do
27
+ parser = Tipi::HTTP1Parser.new(i)
28
+ while true
29
+ trace '*' * 40
30
+ headers = parser.parse_headers
31
+ break unless headers
32
+ trace headers
33
+
34
+ body = parser.read_body(headers)
35
+ trace "body: #{body ? body.bytesize : 0} bytes"
36
+ end
37
+ end
38
+
39
+ o << "GET /a HTTP/1.1\r\n\r\n"
40
+ o << "GET /b HTTP/1.1\r\n\r\n"
41
+
42
+ # o << "GET / HTTP/1.1\r\nHost: localhost:10080\r\nUser-Agent: curl/7.74.0\r\nAccept: */*\r\n\r\n"
43
+
44
+ # o << "post /?q=time&blah=blah HTTP/1\r\nHost: dev.realiteq.net\r\n\r\n"
45
+
46
+ # data = " " * 4000000
47
+ # o << "get /?q=time HTTP/1.1\r\nContent-Length: #{data.bytesize}\r\n\r\n#{data}"
48
+
49
+ # o << "get /?q=time HTTP/1.1\r\nCookie: foo\r\nCookie: bar\r\n\r\n"
50
+
51
+ # o.close
52
+
53
+ f.await