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