tp2 0.6 → 0.7

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: 02dcea75056239e37b8cd92705737bf8516719723b63f6610667f657b9d42667
4
- data.tar.gz: 22660d26e8e6c92c84b033b12f8a095e1eb35d5273067020bb9393dc2560581f
3
+ metadata.gz: ed2dc84d547495b29f7f569f5898e14a29ea7114683374d10150618d6fed6ba7
4
+ data.tar.gz: 6cfb58517566700905799005ff089e951f3c5f728310c2c3e5f4fdb6307c3033
5
5
  SHA512:
6
- metadata.gz: '0783815bf52da7c2f1cb813022f2717adaa2cd6b169923d72c5817b255a1a4d2139b46e47b0f46ff085080af4b8fcf3c5065ac2d3b29a974f7417ce13f0cd1e6'
7
- data.tar.gz: f9720342b6c7a9b78b70c45fadbd5f9dc95ed7720f0c40a60716a963e935acdeb01bc8d338b5cce759b81c4b76ee6e0361e37f519f89a9fca3e03a5b5395654a
6
+ metadata.gz: d2785997104626457256a3dc48326b3e37783a21e2f5e8c06d20e7a2afb4443227258f50c0a33fda0e0703f280f44a95ea4b9fbc327c8d971750baef4f9af87b
7
+ data.tar.gz: 5540476e00ce0787c3f967c9a9621bcfd09e629ba0617c9853be04d523e9d530f113cab95afc60b67378017ecd9d9e62731762576eb13e7035c035d4fbb3e6ea
data/CHANGELOG.md CHANGED
@@ -1,6 +1,14 @@
1
+ # Version 0.7 2025-06-03
2
+
3
+ - Introduce tp2 binary
4
+ - Add support for loading tp2 apps and Rack apps
5
+ - Add support for binding to multiple addresses
6
+ - Show banner when running binary
7
+
1
8
  # Version 0.6 2025-05-05
2
9
 
3
10
  - Update UringMachine to 0.10
11
+ - UseUM::Stream for HTTP parsing
4
12
 
5
13
  # Version 0.5.2 2025-05-05
6
14
 
data/bin/tp2 ADDED
@@ -0,0 +1,87 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "tp2"
5
+ require "optparse"
6
+
7
+
8
+ opts = {}
9
+
10
+ parser = OptionParser.new do |o|
11
+ o.banner = "Usage: tp2 [options]"
12
+
13
+ o.on("-c", "--config CONFIG_FILE", String, "TP2 config file to use (default: app.rb)") {
14
+ opts[:app_type] = :tp2
15
+ opts[:app_location] = it
16
+ }
17
+ o.on("-r", "--rack RACK_FILE", String, "Rack config file to use (default: config.ru)") {
18
+ opts[:app_type] = :rack
19
+ opts[:app_location] = it
20
+ }
21
+ o.on("-d", "--dir PATH", String, "Static files at path") {
22
+ opts[:app_type] = :static
23
+ opts[:app_location] = it
24
+ }
25
+
26
+ o.on("-b", "--bind BIND", String, "Bind address (default: http://0.0.0.0:1234). You can specify this flag multiple times to bind to multiple addresses.") {
27
+ opts[:bind] ||= []
28
+ opts[:bind] << it
29
+ }
30
+
31
+ o.on("-h", "--help", "Show this help message") do
32
+ puts o
33
+ exit
34
+ end
35
+
36
+ o.on("-v", "--version", "Show version") do
37
+ puts "TP2 version #{TP2::VERSION}"
38
+ exit
39
+ end
40
+ end
41
+
42
+ begin
43
+ parser.parse!
44
+ rescue StandardError => e
45
+ puts e.message
46
+ puts e.backtrace.join("\n")
47
+ exit
48
+ end
49
+
50
+ opts[:location] = ARGV.shift || '.'
51
+
52
+ def check_app_file(opts, fn, type)
53
+ fn = File.join(opts[:location], fn)
54
+ return false if !File.file?(fn)
55
+
56
+ opts[:app_type] = type
57
+ opts[:app_location] = File.expand_path(fn)
58
+ end
59
+
60
+ def detect_app(opts)
61
+ if File.file?(opts[:location])
62
+ case File.extname(opts[:location])
63
+ when '.rb'
64
+ opts[:app_type] = :tp2
65
+ opts[:app_location] = File.expand_path(opts[:location])
66
+ when '.ru'
67
+ opts[:app_type] = :rack
68
+ opts[:app_location] = File.expand_path(opts[:location])
69
+ else
70
+ raise "Don't know how to serve #{opts[:location]}"
71
+ end
72
+ return
73
+ end
74
+
75
+ if File.directory?(opts[:location])
76
+ return if check_app_file(opts, 'app.rb', :tp2)
77
+ return if check_app_file(opts, 'config.ru', :rack)
78
+ end
79
+
80
+ opts[:app_type] = :static
81
+ opts[:app_location] = Dir.pwd
82
+ end
83
+
84
+ detect_app(opts) if !opts[:app_type]
85
+
86
+ puts TP2::BANNER
87
+ TP2.run(opts)
data/examples/app.rb ADDED
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ TP2.config(
4
+
5
+ ) do |req|
6
+ req.respond('Hello, world!')
7
+ end
data/examples/rack.ru ADDED
@@ -0,0 +1,3 @@
1
+ run do |env|
2
+ [200, {}, ["Hello World"]]
3
+ end
data/examples/simple.rb CHANGED
@@ -12,23 +12,5 @@ app = ->(req) {
12
12
  req.respond('foobar', ':status' => Qeweney::Status::TEAPOT)
13
13
  }
14
14
 
15
- machine = UM.new
16
- server = TP2::Server.new(machine, '0.0.0.0', 1234, &app)
17
- puts "Listening on port 1234..."
18
-
19
- server_fiber = machine.spin { server.run }
20
-
21
- sig_queue = UM::Queue.new
22
- trap('SIGINT') { machine.push(sig_queue, :SIGINT) }
23
-
24
- puts "Running... (pid: #{Process.pid})"
25
- STDOUT.flush
26
-
27
- # wait for signal
28
- sig = machine.shift(sig_queue)
29
-
30
- puts "Got signal (#{sig}), shutting down gracefully..."
31
- machine.schedule(server_fiber, UM::Terminate.new)
32
- machine.join(server_fiber)
33
-
34
- puts
15
+ TP2.config(&app)
16
+ TP2.run
@@ -10,8 +10,7 @@ module TP2
10
10
  def initialize(machine, fd, &app)
11
11
  @machine = machine
12
12
  @fd = fd
13
- @buffer = String.new('', capacity: 4096)
14
- @sio = StringIO.new(@buffer)
13
+ @stream = UM::Stream.new(machine, fd)
15
14
  @app = app
16
15
  end
17
16
 
@@ -80,10 +79,6 @@ module TP2
80
79
 
81
80
  # response API
82
81
 
83
- CRLF = "\r\n"
84
- ZERO_CRLF_CRLF = "0\r\n\r\n"
85
- CRLF_ZERO_CRLF_CRLF = "\r\n0\r\n\r\n"
86
-
87
82
  SEND_FLAGS = UM::MSG_NOSIGNAL | UM::MSG_WAITALL
88
83
 
89
84
  # Sends response including headers and body. Waits for the request to complete
@@ -139,13 +134,16 @@ module TP2
139
134
  # @return [void]
140
135
  def finish(_request)
141
136
  # request.tx_incr(5)
142
- @machine.send(@fd, ZERO_CRLF_CRLF, ZERO_CRLF_CRLF.bytesize, SEND_FLAGS)
137
+ @machine.send(@fd, "0\r\n\r\n", "0\r\n\r\n".bytesize, SEND_FLAGS)
143
138
  end
144
139
 
145
140
  private
146
141
 
147
- RE_REQUEST_LINE = /^([a-z]+)\s+([^\s]+)\s+(http\/[0-9\.]{1,3})/i
148
- RE_HEADER_LINE = /^([a-z0-9\-]+)\:\s+(.+)/i
142
+ RE_REQUEST_LINE = /^([a-z]+)\s+([^\s]+)\s+(http\/[0-9\.]{1,3})/i.freeze
143
+ RE_HEADER_LINE = /^([a-z0-9\-]+)\:\s+(.+)/i.freeze
144
+ MAX_REQUEST_LINE_LEN = 1 << 14 # 16KB
145
+ MAX_HEADER_LINE_LEN = 1 << 10 # 1KB
146
+ MAX_CHUNK_SIZE_LEN = 16
149
147
 
150
148
  class ProtocolError < StandardError
151
149
  end
@@ -160,11 +158,12 @@ module TP2
160
158
  end
161
159
 
162
160
  def parse_headers
163
- headers = get_request_line
161
+ buf = String.new(capacity: 4096)
162
+ headers = get_request_line(buf)
164
163
  return nil if !headers
165
164
 
166
165
  while true
167
- line = get_line
166
+ line = @stream.get_line(buf, MAX_HEADER_LINE_LEN)
168
167
  break if line.nil? || line.empty?
169
168
 
170
169
  m = line.match(RE_HEADER_LINE)
@@ -176,18 +175,8 @@ module TP2
176
175
  headers
177
176
  end
178
177
 
179
- def get_line
180
- while true
181
- line = @sio.gets(chomp: true)
182
- return line if line
183
-
184
- res = @machine.read(@fd, @buffer, 65536, -1)
185
- return nil if res == 0
186
- end
187
- end
188
-
189
- def get_request_line
190
- line = get_line
178
+ def get_request_line(buf)
179
+ line = @stream.get_line(buf, MAX_REQUEST_LINE_LEN)
191
180
  return nil if !line
192
181
 
193
182
  m = line.match(RE_REQUEST_LINE)
@@ -209,48 +198,30 @@ module TP2
209
198
  end
210
199
 
211
200
  def read(len, buf = nil, raise_on_eof = true)
212
- from_sio = @sio.read(len)
213
- if from_sio
214
- left = len - from_sio&.bytesize
215
- if buf
216
- buf << from_sio
217
- else
218
- buf = +from_sio
219
- end
220
- else
221
- left = len
222
- buf ||= +''
201
+ str = @stream.get_string(buf, len)
202
+ if !str && raise_on_eof
203
+ raise ProtocolError, "Missing data"
223
204
  end
224
205
 
225
- while left > 0
226
- res = @machine.read(@fd, buf, left, -1)
227
- if res == 0
228
- raise ProtocolError, "Incomplete body" if raise_on_eof
229
-
230
- return buf
231
- end
232
-
233
-
234
- left -= res
235
- end
236
- buf
206
+ str
237
207
  end
238
208
 
239
- def read_chunk(headers, buf)
240
- chunk_size = get_line
241
- return nil if !chunk_size
209
+ def read_chunk(headers, buffer)
210
+ tmp = String.new(capacity: 256)
211
+ chunk_size_str = @stream.get_line(tmp, MAX_CHUNK_SIZE_LEN)
212
+ return nil if !chunk_size_str
242
213
 
243
- chunk_size = chunk_size.to_i(16)
214
+ chunk_size = chunk_size_str.to_i(16)
244
215
  if chunk_size == 0
245
216
  headers[':body-done-reading'] = true
246
- get_line
217
+ @stream.get_line(tmp, 0)
247
218
  return nil
248
219
  end
249
220
 
250
- chunk = read(chunk_size, buf)
251
- get_line
221
+ chunk = @stream.get_string(nil, chunk_size)
222
+ @stream.get_line(tmp, 0)
252
223
 
253
- chunk
224
+ buffer ? (buffer << chunk) : chunk
254
225
  end
255
226
 
256
227
  def http1_1?(request)
@@ -274,7 +245,7 @@ module TP2
274
245
 
275
246
  collect_header_lines(lines, k, v)
276
247
  end
277
- lines << CRLF
248
+ lines << "\r\n"
278
249
  lines
279
250
  end
280
251
 
@@ -298,7 +269,7 @@ module TP2
298
269
  if chunked
299
270
  +"HTTP/1.1 #{status}\r\nTransfer-Encoding: chunked\r\n"
300
271
  else
301
- +"HTTP/1.1 #{status}\r\nContent-Length: #{body.is_a?(String) ? body.bytesize : body.to_i}\r\n"
272
+ +"HTTP/1.1 #{status}\r\nContent-Length: #{body.bytesize}\r\n"
302
273
  end
303
274
  end
304
275
 
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rack'
4
+
5
+ module TP2
6
+ module RackAdapter
7
+ class << self
8
+ def run(&app)
9
+ ->(req) { respond(req, app.(env(req))) }
10
+ end
11
+
12
+ def load(path)
13
+ src = IO.read(path)
14
+ instance_eval(src, path, 1)
15
+ end
16
+
17
+ def env(request)
18
+ Qeweney.rack_env_from_request(request)
19
+ end
20
+
21
+ def respond(request, (status_code, headers, body))
22
+ headers[':status'] = status_code.to_s
23
+
24
+ content =
25
+ if body.respond_to?(:to_path)
26
+ File.open(body.to_path, 'rb') { |f| f.read }
27
+ else
28
+ body.first
29
+ end
30
+
31
+ request.respond(content, headers)
32
+ end
33
+ end
34
+ end
35
+ end
data/lib/tp2/server.rb CHANGED
@@ -2,22 +2,57 @@
2
2
 
3
3
  require 'tp2/http1_adapter'
4
4
  require 'tp2/request_extensions'
5
+ require 'tp2/rack_adapter'
5
6
 
6
7
  module TP2
7
8
  class Server
8
9
  PENDING_REQUESTS_GRACE_PERIOD = 0.1
9
10
  PENDING_REQUESTS_TIMEOUT_PERIOD = 5
10
11
 
11
- def initialize(machine, hostname, port, &app)
12
+ def self.rack_app(opts)
13
+ raise "Missing app location" if !opts[:app_location]
14
+
15
+ TP2::RackAdapter.load(opts[:app_location])
16
+ end
17
+
18
+ def self.tp2_app(opts)
19
+ if opts[:app_location]
20
+ puts "Loading app at #{opts[:app_location]}"
21
+ require opts[:app_location]
22
+
23
+ opts.merge!(TP2.config)
24
+ end
25
+ opts[:app]
26
+ end
27
+
28
+ def self.static_app(opts)
29
+ end
30
+
31
+
32
+ def initialize(machine, opts, &app)
12
33
  @machine = machine
13
- @hostname = hostname
14
- @port = port
15
- @app = app
34
+ @opts = opts
35
+ @app = app || app_from_opts
36
+ @server_fds = []
37
+ @accept_fibers = []
16
38
  end
17
39
 
40
+ def app_from_opts
41
+ case @opts[:app_type]
42
+ when nil, :tp2
43
+ Server.tp2_app(@opts)
44
+ when :rack
45
+ Server.rack_app(@opts)
46
+ when :static
47
+ Server.static_app(@opts)
48
+ else
49
+ raise "Invalid app type #{@opts[:app_type].inspect}"
50
+ end
51
+ end
52
+
18
53
  def run
19
54
  setup
20
- accept_incoming
55
+ @machine.join(*@accept_fibers)
21
56
  rescue UM::Terminate
22
57
  graceful_shutdown
23
58
  end
@@ -25,51 +60,85 @@ module TP2
25
60
  private
26
61
 
27
62
  def setup
28
- @server_fd = @machine.socket(UM::AF_INET, UM::SOCK_STREAM, 0, 0)
29
- @machine.setsockopt(@server_fd, UM::SOL_SOCKET, UM::SO_REUSEADDR, true)
30
- @machine.bind(@server_fd, @hostname, @port)
31
- @machine.listen(@server_fd, UM::SOMAXCONN)
32
-
63
+ bind_info = get_bind_entries
64
+ bind_info.each do |(host, port)|
65
+ fd = setup_server_socket(host, port)
66
+ @server_fds << fd
67
+ @accept_fibers << @machine.spin { accept_incoming(fd) }
68
+ end
69
+ bind_string = bind_info.map { it.join(':') }.join(", ")
70
+ puts "Listening on #{bind_string}"
71
+
33
72
  # map fibers
34
- @fiber_map = {}
73
+ @connection_fiber_map = {}
74
+ end
75
+
76
+ def get_bind_entries
77
+ bind = @opts[:bind]
78
+ case bind
79
+ when Array
80
+ bind.map { bind_info(it) }
81
+ when String
82
+ [bind_info(bind)]
83
+ else
84
+ # default
85
+ [['0.0.0.0', 1234]]
86
+ end
87
+ end
35
88
 
36
- # puts "Listening on #{@hostname}:#{@port}"
89
+ def bind_info(bind_string)
90
+ parts = bind_string.split(':')
91
+ [parts[0], parts[1].to_i]
37
92
  end
38
93
 
39
- def accept_incoming
40
- @machine.accept_each(@server_fd) do |fd|
94
+ def setup_server_socket(host, port)
95
+ fd = @machine.socket(UM::AF_INET, UM::SOCK_STREAM, 0, 0)
96
+ @machine.setsockopt(fd, UM::SOL_SOCKET, UM::SO_REUSEADDR, true)
97
+ @machine.setsockopt(fd, UM::SOL_SOCKET, UM::SO_REUSEPORT, true)
98
+ @machine.bind(fd, host, port)
99
+ @machine.listen(fd, UM::SOMAXCONN)
100
+ fd
101
+ end
102
+
103
+ def accept_incoming(listen_fd)
104
+ @machine.accept_each(listen_fd) do |fd|
41
105
  conn = HTTP1Adapter.new(@machine, fd, &@app)
42
106
  f = @machine.spin(conn) do
43
107
  it.run
44
108
  ensure
45
- @fiber_map.delete(f)
109
+ @connection_fiber_map.delete(f)
46
110
  end
47
- @fiber_map[f] = true
111
+ @connection_fiber_map[f] = true
48
112
  end
49
113
  end
50
114
 
115
+ def close_all_server_fds
116
+ @server_fds.each { @machine.close(it) }
117
+ end
118
+
51
119
  def graceful_shutdown
52
120
  # stop listening
53
- @machine.close(@server_fd)
121
+ close_all_server_fds
122
+ @machine.snooze
54
123
 
55
- return if @fiber_map.empty?
124
+ return if @connection_fiber_map.empty?
56
125
 
57
126
  # sleep for a bit, let requests finish
58
127
  @machine.sleep(PENDING_REQUESTS_GRACE_PERIOD)
59
- return if @fiber_map.empty?
128
+ return if @connection_fiber_map.empty?
60
129
 
61
130
  # terminate pending fibers
62
- pending = @fiber_map.keys
131
+ pending = @connection_fiber_map.keys
63
132
  signal = UM::Terminate.new
64
133
  pending.each { @machine.schedule(it, signal) }
65
134
 
66
135
  @machine.timeout(PENDING_REQUESTS_TIMEOUT_PERIOD, UM::Terminate) do
67
- @machine.join(*@fiber_map.keys)
68
- rescue UM::Terminate
136
+ @machine.join(*@connection_fiber_map.keys)
137
+ rescue UM::Terminate
69
138
  # timeout on waiting for adapters to finish running, do nothing
70
139
  end
71
140
  ensure
72
- @machine.close(@server_fd)
141
+ @machine.close(@server_fd) rescue nil
73
142
  end
74
143
  end
75
144
  end
data/lib/tp2/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module TP2
2
- VERSION = '0.6'
2
+ VERSION = '0.7'
3
3
  end
data/lib/tp2.rb CHANGED
@@ -1,4 +1,61 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'uringmachine'
4
+ require 'tp2/version'
4
5
  require 'tp2/server'
6
+
7
+ module TP2
8
+ BANNER = (
9
+ "\n" +
10
+ " ooo\n" +
11
+ " oo\n" +
12
+ " o\n" +
13
+ " \\|/ TP2 - a modern web server for Ruby apps\n" +
14
+ " / \\ \n" +
15
+ " / \\ https://github.com/noteflakes/tp2\n" +
16
+ "⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺\n"
17
+ )
18
+
19
+
20
+ class << self
21
+ def run(opts = nil, &app)
22
+ return if @in_run
23
+
24
+ opts ||= @config || {}
25
+ begin
26
+ @in_run = true
27
+ machine = UM.new
28
+ server = Server.new(machine, opts, &app)
29
+
30
+ setup_signal_handling(machine)
31
+
32
+ server.run
33
+ ensure
34
+ @in_run = false
35
+ end
36
+ end
37
+
38
+ def config(opts = nil, &app)
39
+ return @config if !opts && !app
40
+
41
+ @config = opts || {}
42
+ @config[:app] = app if app
43
+ end
44
+
45
+ private
46
+
47
+ def setup_signal_handling(machine)
48
+ queue = UM::Queue.new
49
+ trap('SIGINT') { machine.push(queue, :SIGINT) }
50
+ machine.spin { terminate_on_signal(machine, queue, Fiber.current) }
51
+ end
52
+
53
+ # waits for signal from queue, then terminates given fiber
54
+ # to be done
55
+ def terminate_on_signal(machine, queue, fiber)
56
+ sig = machine.shift(queue)
57
+ puts "\nGot signal (#{sig}), shutting down gracefully..."
58
+ machine.schedule(fiber, UM::Terminate.new)
59
+ end
60
+ end
61
+ end
@@ -69,13 +69,16 @@ class HTTP1AdapterTest < Minitest::Test
69
69
  end
70
70
 
71
71
  def test_pipelined_requests
72
- write_http_request <<~HTTP.crlf_lines
72
+ msg = <<~HTTP.crlf_lines
73
73
  GET /foo HTTP/1.1
74
74
  Server: foo.com
75
75
 
76
76
  SCHMET /bar HTTP/1.1
77
77
 
78
+
79
+
78
80
  HTTP
81
+ write_http_request msg
79
82
 
80
83
  @adapter.run
81
84
  assert_equal 2, @reqs.size
@@ -142,7 +145,7 @@ class HTTP1AdapterTest < Minitest::Test
142
145
  end
143
146
 
144
147
  def test_pipelined_requests_with_body_chunked
145
- write_http_request <<~HTTP.crlf_lines
148
+ msg = <<~HTTP.crlf_lines
146
149
  POST /foo HTTP/1.1
147
150
  Server: foo.com
148
151
  Transfer-Encoding: chunked
@@ -161,7 +164,10 @@ class HTTP1AdapterTest < Minitest::Test
161
164
  123456789abcdefghijklmnopqrstuv
162
165
  0
163
166
 
167
+
168
+
164
169
  HTTP
170
+ write_http_request(msg)
165
171
 
166
172
  @bodies = []
167
173
  @hook = ->(req) { @bodies << req.read }
@@ -197,6 +203,8 @@ class HTTP1AdapterTest < Minitest::Test
197
203
  end
198
204
 
199
205
  def test_body_to_eof
206
+ skip
207
+
200
208
  write_http_request <<~HTTP.crlf_lines
201
209
  POST /foo HTTP/1.1
202
210
  Server: foo.com
data/test/test_server.rb CHANGED
@@ -2,9 +2,6 @@
2
2
 
3
3
  require_relative './helper'
4
4
 
5
- class String
6
- end
7
-
8
5
  class ServerTest < Minitest::Test
9
6
  def make_socket_pair
10
7
  port = 10000 + rand(30000)
@@ -28,7 +25,7 @@ class ServerTest < Minitest::Test
28
25
  def setup
29
26
  @machine = UM.new
30
27
  @port = 10000 + rand(30000)
31
- @server = TP2::Server.new(@machine, '127.0.0.1', @port) { @app&.call(it) }
28
+ @server = TP2::Server.new(@machine, { bind: "127.0.0.1:#{@port}" }) { @app&.call(it) }
32
29
  @f_server = @machine.spin do
33
30
  @server.run
34
31
  rescue STOP
@@ -106,9 +103,17 @@ class ServerTest < Minitest::Test
106
103
  end
107
104
 
108
105
  def test_pipelined_requests_with_body
109
- skip
106
+ # skip
110
107
 
111
- write_http_request <<~HTTP.crlf_lines
108
+ @bodies = []
109
+ @reqs = []
110
+ @app = ->(req) {
111
+ @reqs << req
112
+ @bodies << req.read
113
+ req.respond("method: #{req.method}")
114
+ }
115
+
116
+ msg = <<~HTTP.crlf_lines
112
117
  POST /foo HTTP/1.1
113
118
  Server: foo.com
114
119
  Content-Length: 3
@@ -119,12 +124,10 @@ class ServerTest < Minitest::Test
119
124
 
120
125
  defghi
121
126
  HTTP
127
+ write_http_request msg
122
128
 
123
- @bodies = []
124
- @hook = ->(req) { @bodies << req.read }
125
-
126
- @adapter.run
127
- assert_equal 2, @reqs.size
129
+ read_client_side
130
+ assert_equal 2, @bodies.size
128
131
 
129
132
  req0 = @reqs.shift
130
133
  headers = req0.headers
data/tp2.gemspec CHANGED
@@ -18,7 +18,9 @@ Gem::Specification.new do |s|
18
18
  s.extra_rdoc_files = ['README.md']
19
19
  s.require_paths = ['lib']
20
20
  s.required_ruby_version = '>= 3.4'
21
+ s.executables = ['tp2']
21
22
 
22
- s.add_dependency 'uringmachine', '0.10'
23
+ s.add_dependency 'uringmachine', '0.12'
23
24
  s.add_dependency 'qeweney', '0.21'
25
+ s.add_dependency 'rack', '3.1.15'
24
26
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tp2
3
3
  version: !ruby/object:Gem::Version
4
- version: '0.6'
4
+ version: '0.7'
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sharon Rosner
@@ -15,14 +15,14 @@ dependencies:
15
15
  requirements:
16
16
  - - '='
17
17
  - !ruby/object:Gem::Version
18
- version: '0.10'
18
+ version: '0.12'
19
19
  type: :runtime
20
20
  prerelease: false
21
21
  version_requirements: !ruby/object:Gem::Requirement
22
22
  requirements:
23
23
  - - '='
24
24
  - !ruby/object:Gem::Version
25
- version: '0.10'
25
+ version: '0.12'
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: qeweney
28
28
  requirement: !ruby/object:Gem::Requirement
@@ -37,8 +37,23 @@ dependencies:
37
37
  - - '='
38
38
  - !ruby/object:Gem::Version
39
39
  version: '0.21'
40
+ - !ruby/object:Gem::Dependency
41
+ name: rack
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - '='
45
+ - !ruby/object:Gem::Version
46
+ version: 3.1.15
47
+ type: :runtime
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - '='
52
+ - !ruby/object:Gem::Version
53
+ version: 3.1.15
40
54
  email: sharon@noteflakes.com
41
- executables: []
55
+ executables:
56
+ - tp2
42
57
  extensions: []
43
58
  extra_rdoc_files:
44
59
  - README.md
@@ -50,9 +65,13 @@ files:
50
65
  - README.md
51
66
  - Rakefile
52
67
  - TODO.md
68
+ - bin/tp2
69
+ - examples/app.rb
70
+ - examples/rack.ru
53
71
  - examples/simple.rb
54
72
  - lib/tp2.rb
55
73
  - lib/tp2/http1_adapter.rb
74
+ - lib/tp2/rack_adapter.rb
56
75
  - lib/tp2/request_extensions.rb
57
76
  - lib/tp2/server.rb
58
77
  - lib/tp2/version.rb