tipi 0.40 → 0.41

Sign up to get free protection for your applications and to get access to all the features.
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