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 +4 -4
- data/.gitignore +3 -1
- data/CHANGELOG.md +7 -0
- data/Gemfile.lock +23 -13
- data/Rakefile +7 -3
- data/df/server.rb +15 -103
- data/df/server_utils.rb +173 -0
- data/examples/http1_parser.rb +53 -0
- data/examples/http_server.rb +12 -3
- data/examples/http_server_static.rb +6 -18
- data/ext/tipi/extconf.rb +12 -0
- data/ext/tipi/http1_parser.c +534 -0
- data/ext/tipi/http1_parser.h +18 -0
- data/ext/tipi/tipi_ext.c +5 -0
- data/lib/tipi.rb +2 -1
- data/lib/tipi/digital_fabric/agent.rb +5 -3
- data/lib/tipi/digital_fabric/agent_proxy.rb +12 -5
- data/lib/tipi/digital_fabric/executive.rb +2 -2
- data/lib/tipi/digital_fabric/protocol.rb +16 -1
- data/lib/tipi/digital_fabric/service.rb +69 -40
- data/lib/tipi/http1_adapter.rb +2 -2
- data/lib/tipi/http1_adapter_new.rb +293 -0
- data/lib/tipi/rack_adapter.rb +2 -53
- data/lib/tipi/response_extensions.rb +1 -1
- data/lib/tipi/version.rb +1 -1
- data/tipi.gemspec +6 -2
- metadata +44 -9
- data/e +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5a83c181c05d8d8c8de50f236feab65b800b1bd266c2f0a4d4696d4853ac39fd
|
4
|
+
data.tar.gz: d9a84fc9f72c3a5565f587e35fb4ed206c83c863fbb0b4b470449919a687463b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e9c13f387c3431888d9d43c88d5bd00831c4e2e8976bf8ae4e1acc479b8d50fa888cd58af0fc433da2dab8e3ea09b99ca0adbdcc61abc71457a805c26aa03687
|
7
|
+
data.tar.gz: df01a54c7c46928c67da321cfc830d8f33e8af7fa8197cd5930f3de73838efd4849d51b10af35b1a5297f7f484b296ff2ed6146dbb8d66476bf0b714f1dbfd68
|
data/.gitignore
CHANGED
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.
|
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.
|
10
|
-
qeweney (~> 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
|
-
|
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.
|
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.
|
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.
|
39
|
-
localhost (1.1.
|
44
|
+
json (2.5.1)
|
45
|
+
localhost (1.1.8)
|
40
46
|
minitest (5.11.3)
|
41
|
-
minitest-reporters (1.4.
|
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.
|
49
|
-
qeweney (0.
|
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
|
-
|
54
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
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
|
-
|
12
|
+
log('Starting DF server')
|
13
|
+
Fiber.await(*listeners)
|
103
14
|
rescue Interrupt
|
104
|
-
|
105
|
-
service.graceful_shutdown
|
106
|
-
|
15
|
+
log('Got SIGINT, shutting down gracefully')
|
16
|
+
@service.graceful_shutdown
|
17
|
+
rescue SystemExit
|
18
|
+
# ignore
|
107
19
|
rescue Exception => e
|
108
|
-
|
109
|
-
|
110
|
-
|
20
|
+
log("Uncaught exception", error: e, backtrace: e.backtrace)
|
21
|
+
ensure
|
22
|
+
log('DF server stopped')
|
111
23
|
end
|
data/df/server_utils.rb
ADDED
@@ -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
|