syntropy 0.36.0 → 0.38.0
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/CHANGELOG.md +23 -0
- data/Gemfile +4 -0
- data/TODO.md +4 -0
- data/bin/syntropy +12 -2
- data/cmd/help.rb +4 -0
- data/cmd/new/template/.gitignore +2 -1
- data/cmd/new/template/config/Caddyfile +5 -0
- data/cmd/new/template/docker-compose.yml +28 -3
- data/cmd/new.rb +7 -1
- data/cmd/serve.rb +3 -1
- data/cmd/version.rb +14 -0
- data/examples/basic/counter_api.rb +1 -1
- data/examples/blog/app/posts/[id]/edit.rb +1 -1
- data/examples/blog/app/posts/[id]/index.rb +1 -1
- data/examples/blog/app/posts/index.rb +1 -1
- data/examples/blog/app/posts/new.rb +1 -1
- data/examples/github/app/[org]/[repo]/index.rb +0 -0
- data/examples/github/app/[org]/[repo]/issues/[id].rb +0 -0
- data/examples/github/app/[org]/index.rb +0 -0
- data/examples/github/app/collections.rb +0 -0
- data/examples/github/app/explore.rb +0 -0
- data/examples/github/app/index.rb +0 -0
- data/lib/syntropy/app.rb +6 -2
- data/lib/syntropy/controller_extensions.rb +136 -0
- data/lib/syntropy/http/io_extensions.rb +9 -0
- data/lib/syntropy/http/server_connection.rb +1 -0
- data/lib/syntropy/json_api.rb +5 -0
- data/lib/syntropy/module_loader.rb +46 -42
- data/lib/syntropy/routing_tree.rb +14 -14
- data/lib/syntropy/storage/schema.rb +3 -3
- data/lib/syntropy/test.rb +29 -11
- data/lib/syntropy/version.rb +1 -1
- data/lib/syntropy.rb +3 -6
- data/test/bm_router_proc.rb +14 -15
- data/test/fixtures/app/_lib/klass.rb +1 -1
- data/test/fixtures/app/api+.rb +1 -1
- data/test/fixtures/app/bad_mod_arity.rb +3 -0
- data/test/fixtures/app/by_method.rb +1 -1
- data/test/fixtures/app/post_ct.rb +1 -1
- data/test/fixtures/app_errors/_error.rb +3 -0
- data/test/fixtures/app_errors/foo/_error.rb +3 -0
- data/test/fixtures/app_errors/foo/bar/_error.rb +3 -0
- data/test/fixtures/app_errors/foo/bar/baz/index.rb +3 -0
- data/test/fixtures/app_errors/foo/bar/index.rb +3 -0
- data/test/fixtures/app_errors/foo/index.rb +3 -0
- data/test/fixtures/app_errors/index.rb +3 -0
- data/test/fixtures/app_hooks/_hook.rb +4 -0
- data/test/fixtures/app_hooks/foo/_hook.rb +4 -0
- data/test/fixtures/app_hooks/foo/bar/_hook.rb +4 -0
- data/test/fixtures/app_hooks/foo/bar/baz/_hook.rb +4 -0
- data/test/fixtures/app_hooks/foo/bar/baz/index.rb +3 -0
- data/test/fixtures/app_hooks/foo/bar/index.rb +3 -0
- data/test/fixtures/app_hooks/foo/index.rb +3 -0
- data/test/fixtures/app_hooks/index.rb +3 -0
- data/test/fixtures/app_multi_site/_site.rb +1 -1
- data/test/fixtures/controllers/by_host/bar.com/index.rb +3 -0
- data/test/fixtures/controllers/by_host/foo.com/index.rb +3 -0
- data/test/fixtures/controllers/by_host_dir.rb +1 -0
- data/test/fixtures/controllers/by_host_dir_map.rb +4 -0
- data/test/fixtures/controllers/by_host_map.rb +4 -0
- data/test/fixtures/controllers/by_http_method.rb +9 -0
- data/test/fixtures/controllers/jsonrpc_endpoint.rb +0 -0
- data/test/test_app.rb +86 -1
- data/test/test_controller.rb +71 -0
- data/test/test_http_protocol.rb +54 -0
- data/test/test_module_loader.rb +43 -5
- data/test/test_routing_tree.rb +1 -0
- data/test/test_test.rb +1 -1
- metadata +34 -2
- data/lib/syntropy/utils.rb +0 -87
|
@@ -339,8 +339,8 @@ module Syntropy
|
|
|
339
339
|
target: { kind:, fn: },
|
|
340
340
|
# In case we're at the tree root, we need to copy over the hook and
|
|
341
341
|
# error refs.
|
|
342
|
-
hook:
|
|
343
|
-
error:
|
|
342
|
+
hook: parent[:hook],
|
|
343
|
+
error: parent[:error]
|
|
344
344
|
}
|
|
345
345
|
end
|
|
346
346
|
nil
|
|
@@ -458,9 +458,9 @@ module Syntropy
|
|
|
458
458
|
emit_router_proc_prelude(buffer)
|
|
459
459
|
segment_idx = 1
|
|
460
460
|
if @root[:path] != '/'
|
|
461
|
-
|
|
462
|
-
segment_idx =
|
|
463
|
-
emit_root_validate_guard(buffer:,
|
|
461
|
+
root_segments = @root[:path].split('/')
|
|
462
|
+
segment_idx = root_segments.size
|
|
463
|
+
emit_root_validate_guard(buffer:, root_segments:)
|
|
464
464
|
end
|
|
465
465
|
|
|
466
466
|
visit_routing_tree_entry(buffer:, entry: @root, segment_idx:)
|
|
@@ -492,18 +492,18 @@ module Syntropy
|
|
|
492
492
|
def emit_router_proc_prelude(buffer)
|
|
493
493
|
emit_code_line(buffer, '->(path, params) {')
|
|
494
494
|
emit_code_line(buffer, ' entry = @static_map[path]; return entry if entry')
|
|
495
|
-
emit_code_line(buffer, '
|
|
495
|
+
emit_code_line(buffer, ' segments = path.split("/")')
|
|
496
496
|
end
|
|
497
497
|
|
|
498
498
|
# Emits root path validation guard code.
|
|
499
499
|
#
|
|
500
500
|
# @param buffer [String] output buffer
|
|
501
|
-
# @param
|
|
501
|
+
# @param root_segments [Array<String>] root path segments
|
|
502
502
|
# @return [void]
|
|
503
|
-
def emit_root_validate_guard(buffer:,
|
|
503
|
+
def emit_root_validate_guard(buffer:, root_segments:)
|
|
504
504
|
validate_parts = []
|
|
505
|
-
(1...
|
|
506
|
-
validate_parts << "(
|
|
505
|
+
(1...root_segments.size).each do |i|
|
|
506
|
+
validate_parts << "(segments[#{i}] != #{root_segments[i].inspect})"
|
|
507
507
|
end
|
|
508
508
|
emit_code_line(buffer, " return nil if #{validate_parts.join(' || ')}")
|
|
509
509
|
end
|
|
@@ -568,7 +568,7 @@ module Syntropy
|
|
|
568
568
|
|
|
569
569
|
# Get next segment
|
|
570
570
|
if !case_buffer.empty?
|
|
571
|
-
emit_code_line(buffer, "#{ws}case (
|
|
571
|
+
emit_code_line(buffer, "#{ws}case (s = segments[#{segment_idx}])")
|
|
572
572
|
buffer << case_buffer
|
|
573
573
|
emit_code_line(buffer, "#{ws}end")
|
|
574
574
|
end
|
|
@@ -630,7 +630,7 @@ module Syntropy
|
|
|
630
630
|
next if child_entry[:static]
|
|
631
631
|
|
|
632
632
|
emit_code_line(buffer, "#{ws}when #{k.inspect}")
|
|
633
|
-
if_clause = child_entry[:handle_subtree] ? '' : " if !
|
|
633
|
+
if_clause = child_entry[:handle_subtree] ? '' : " if !segments[#{segment_idx + 1}]"
|
|
634
634
|
|
|
635
635
|
child_path = child_entry[:path]
|
|
636
636
|
route_value = "@dynamic_map[#{child_path.inspect}]"
|
|
@@ -657,12 +657,12 @@ module Syntropy
|
|
|
657
657
|
# parametric route
|
|
658
658
|
if param_entry
|
|
659
659
|
if when_count == 0
|
|
660
|
-
emit_code_line(buffer, "#{ws}when
|
|
660
|
+
emit_code_line(buffer, "#{ws}when s")
|
|
661
661
|
else
|
|
662
662
|
emit_code_line(buffer, "#{ws}else")
|
|
663
663
|
end
|
|
664
664
|
|
|
665
|
-
emit_code_line(buffer, "#{ws} params[#{param_entry[:param].inspect}] =
|
|
665
|
+
emit_code_line(buffer, "#{ws} params[#{param_entry[:param].inspect}] = s")
|
|
666
666
|
visit_routing_tree_entry(buffer:, entry: param_entry, indent: indent + 1, segment_idx: segment_idx + 1)
|
|
667
667
|
# wildcard route
|
|
668
668
|
elsif entry[:handle_subtree]
|
|
@@ -81,10 +81,10 @@ module Syntropy
|
|
|
81
81
|
end
|
|
82
82
|
|
|
83
83
|
def set_schema_version(db, version)
|
|
84
|
-
db.execute <<~SQL,
|
|
84
|
+
db.execute <<~SQL, version
|
|
85
85
|
insert into __syntropy_schema__ (k, v)
|
|
86
|
-
values ('version',
|
|
87
|
-
on conflict(k) do update set v =
|
|
86
|
+
values ('version', $1)
|
|
87
|
+
on conflict(k) do update set v = $1
|
|
88
88
|
SQL
|
|
89
89
|
end
|
|
90
90
|
end
|
data/lib/syntropy/test.rb
CHANGED
|
@@ -11,12 +11,9 @@ module Syntropy
|
|
|
11
11
|
class Test < Minitest::Test
|
|
12
12
|
HTTP = Syntropy::HTTP
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
# @return [void]
|
|
18
|
-
def self.env=(env)
|
|
19
|
-
@@env = env
|
|
14
|
+
class << self
|
|
15
|
+
# Gets/sets app environment for tests
|
|
16
|
+
attr_accessor :env
|
|
20
17
|
end
|
|
21
18
|
|
|
22
19
|
attr_reader :machine, :app
|
|
@@ -25,7 +22,7 @@ module Syntropy
|
|
|
25
22
|
#
|
|
26
23
|
# @return [Hash] test app environment
|
|
27
24
|
def env
|
|
28
|
-
|
|
25
|
+
self.class.env
|
|
29
26
|
end
|
|
30
27
|
|
|
31
28
|
# Loads and returns a module with the given reference.
|
|
@@ -101,17 +98,38 @@ module Syntropy
|
|
|
101
98
|
post(path, 'application/x-www-form-urlencoded', URI.encode_www_form(data), **)
|
|
102
99
|
end
|
|
103
100
|
|
|
101
|
+
# Makes an HTTP PATCH request to the test app.
|
|
102
|
+
#
|
|
103
|
+
# @param path [String] request path
|
|
104
|
+
# @param content_type [String, nil] content MIME type
|
|
105
|
+
# @param body [String] request body
|
|
106
|
+
# @param headers [Hash] request headers
|
|
107
|
+
# @return [Syntropy::Request]
|
|
108
|
+
def patch(path, content_type, body, **headers)
|
|
109
|
+
headers = headers.merge('content-type' => content_type) if content_type
|
|
110
|
+
http_request(
|
|
111
|
+
headers.merge(
|
|
112
|
+
{
|
|
113
|
+
':method' => 'PATCH',
|
|
114
|
+
':path' => path
|
|
115
|
+
}
|
|
116
|
+
),
|
|
117
|
+
body
|
|
118
|
+
)
|
|
119
|
+
end
|
|
120
|
+
|
|
104
121
|
# Sets up a test instance.
|
|
105
122
|
#
|
|
106
123
|
# @return [void]
|
|
107
124
|
def setup
|
|
108
|
-
|
|
125
|
+
env = self.class.env
|
|
126
|
+
raise 'Environment not set' if !env
|
|
109
127
|
|
|
110
|
-
Syntropy.load_config(
|
|
128
|
+
Syntropy.load_config(env)
|
|
111
129
|
|
|
112
130
|
@machine = UM.new
|
|
113
131
|
@app = Syntropy::App.new(
|
|
114
|
-
|
|
132
|
+
**env.merge(
|
|
115
133
|
machine: @machine,
|
|
116
134
|
test_mode: true
|
|
117
135
|
)
|
|
@@ -119,7 +137,7 @@ module Syntropy
|
|
|
119
137
|
@test_harness = Syntropy::TestHarness.new(@app)
|
|
120
138
|
|
|
121
139
|
@db = load_module('/_lib/storage', raise_on_missing: false)
|
|
122
|
-
@db&.migrate!
|
|
140
|
+
@db&.migrate! if @db.respond_to?(:migrate!)
|
|
123
141
|
end
|
|
124
142
|
|
|
125
143
|
# Cleans up a test instance.
|
data/lib/syntropy/version.rb
CHANGED
data/lib/syntropy.rb
CHANGED
|
@@ -17,15 +17,12 @@ require 'syntropy/papercraft_extensions'
|
|
|
17
17
|
require 'syntropy/routing_tree'
|
|
18
18
|
require 'syntropy/json_api'
|
|
19
19
|
require 'syntropy/side_run'
|
|
20
|
-
require 'syntropy/utils'
|
|
21
20
|
require 'syntropy/version'
|
|
22
21
|
|
|
23
22
|
# Syntropy is a web framework for building web apps in Ruby. Syntropy uses
|
|
24
23
|
# UringMachine for I/O and concurrency, and provides a comprehensive and
|
|
25
24
|
# flexible solution for writing web apps with minimal boilerplate.
|
|
26
25
|
module Syntropy
|
|
27
|
-
extend Utilities
|
|
28
|
-
|
|
29
26
|
class << self
|
|
30
27
|
attr_accessor :machine, :dev_mode, :test_mode
|
|
31
28
|
|
|
@@ -83,9 +80,9 @@ module Syntropy
|
|
|
83
80
|
logger: nil
|
|
84
81
|
)
|
|
85
82
|
loader = ModuleLoader.new(loader_env)
|
|
86
|
-
config = loader.load(env[:mode])
|
|
87
|
-
|
|
88
|
-
|
|
83
|
+
if (config = loader.load(env[:mode], raise_on_missing: false))
|
|
84
|
+
env[:config] = config
|
|
85
|
+
end
|
|
89
86
|
end
|
|
90
87
|
|
|
91
88
|
private
|
data/test/bm_router_proc.rb
CHANGED
|
@@ -138,8 +138,8 @@ def make_tmp_file_tree(dir, spec)
|
|
|
138
138
|
dir
|
|
139
139
|
end
|
|
140
140
|
|
|
141
|
-
app_root = "/tmp/#{__FILE__.gsub('/', '-')}-#{SecureRandom.hex}"
|
|
142
|
-
make_tmp_file_tree(app_root, {
|
|
141
|
+
$app_root = "/tmp/#{__FILE__.gsub('/', '-')}-#{SecureRandom.hex}"
|
|
142
|
+
make_tmp_file_tree($app_root, {
|
|
143
143
|
'index.rb': "export ->(req) { req.redirect('/hello') }",
|
|
144
144
|
'hello': {
|
|
145
145
|
'index.rb': "export ->(req) { req.respond('Hello!', 'Content-Type' => 'text/html') }",
|
|
@@ -147,13 +147,13 @@ make_tmp_file_tree(app_root, {
|
|
|
147
147
|
}
|
|
148
148
|
})
|
|
149
149
|
|
|
150
|
-
machine = UM.new
|
|
151
|
-
syntropy_app = Syntropy::App.new(
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
)
|
|
156
|
-
proc = ->(req) { syntropy_app.(req) }
|
|
150
|
+
# machine = UM.new
|
|
151
|
+
# syntropy_app = Syntropy::App.new(
|
|
152
|
+
# app_root: app_root,
|
|
153
|
+
# mount_path: '/',
|
|
154
|
+
# machine: machine
|
|
155
|
+
# )
|
|
156
|
+
# proc = ->(req) { syntropy_app.(req) }
|
|
157
157
|
|
|
158
158
|
module ::Kernel
|
|
159
159
|
def mock_req(headers, body = nil)
|
|
@@ -161,11 +161,11 @@ module ::Kernel
|
|
|
161
161
|
end
|
|
162
162
|
end
|
|
163
163
|
|
|
164
|
-
puts '*' * 40
|
|
164
|
+
# puts '*' * 40
|
|
165
165
|
|
|
166
|
-
req = mock_req(':method' => 'GET', ':path' => '/hello/world')
|
|
167
|
-
proc.(req)
|
|
168
|
-
p [req.response_status, req.response_headers, req.response_body]
|
|
166
|
+
# req = mock_req(':method' => 'GET', ':path' => '/hello/world')
|
|
167
|
+
# proc.(req)
|
|
168
|
+
# p [req.response_status, req.response_headers, req.response_body]
|
|
169
169
|
|
|
170
170
|
################################################################################
|
|
171
171
|
|
|
@@ -185,9 +185,8 @@ BM.run do
|
|
|
185
185
|
def setup
|
|
186
186
|
machine = UM.new
|
|
187
187
|
syntropy_app = Syntropy::App.new(
|
|
188
|
-
app_root: app_root,
|
|
188
|
+
app_root: $app_root,
|
|
189
189
|
mount_path: '/',
|
|
190
|
-
# watch_files: 0.05,
|
|
191
190
|
machine: machine
|
|
192
191
|
)
|
|
193
192
|
@app = ->(req) { syntropy_app.(req) }
|
data/test/fixtures/app/api+.rb
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export
|
|
1
|
+
export dispatch_by_host('.')
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export dispatch_by_host('by_host')
|
|
File without changes
|
data/test/test_app.rb
CHANGED
|
@@ -119,6 +119,11 @@ class AppTest < Minitest::Test
|
|
|
119
119
|
}
|
|
120
120
|
assert_equal HTTP::INTERNAL_SERVER_ERROR, req.response_status
|
|
121
121
|
|
|
122
|
+
req = @test_harness.no_raise_internal_server_error {
|
|
123
|
+
@test_harness.request(':method' => 'GET', ':path' => '/test/bad_mod_arity')
|
|
124
|
+
}
|
|
125
|
+
assert_equal HTTP::INTERNAL_SERVER_ERROR, req.response_status
|
|
126
|
+
|
|
122
127
|
req = @test_harness.request(':method' => 'GET', ':path' => '/test/.well-known/foo')
|
|
123
128
|
assert_equal HTTP::OK, req.response_status
|
|
124
129
|
assert_equal 'foo', req.response_body
|
|
@@ -183,6 +188,86 @@ class AppTest < Minitest::Test
|
|
|
183
188
|
end
|
|
184
189
|
end
|
|
185
190
|
|
|
191
|
+
class MiddlewareHooksTest < Minitest::Test
|
|
192
|
+
HTTP = Syntropy::HTTP
|
|
193
|
+
|
|
194
|
+
APP_ROOT = File.join(__dir__, 'fixtures/app_hooks')
|
|
195
|
+
|
|
196
|
+
def setup
|
|
197
|
+
@machine = UM.new
|
|
198
|
+
|
|
199
|
+
@tmp_path = '/test/tmp'
|
|
200
|
+
@tmp_fn = File.join(APP_ROOT, 'tmp.rb')
|
|
201
|
+
|
|
202
|
+
@app = Syntropy::App.new(
|
|
203
|
+
app_root: APP_ROOT,
|
|
204
|
+
mount_path: '/',
|
|
205
|
+
watch_files: 0.05,
|
|
206
|
+
machine: @machine
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
@test_harness = Syntropy::TestHarness.new(@app)
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
def test_middleware_composition
|
|
213
|
+
req = @test_harness.request(':method' => 'GET', ':path' => '/')
|
|
214
|
+
assert_equal HTTP::OK, req.response_status
|
|
215
|
+
assert_equal 'root: root', req.response_body
|
|
216
|
+
|
|
217
|
+
req = @test_harness.request(':method' => 'GET', ':path' => '/foo')
|
|
218
|
+
assert_equal HTTP::OK, req.response_status
|
|
219
|
+
assert_equal 'foo: root foo', req.response_body
|
|
220
|
+
|
|
221
|
+
req = @test_harness.request(':method' => 'GET', ':path' => '/foo/bar')
|
|
222
|
+
assert_equal HTTP::OK, req.response_status
|
|
223
|
+
assert_equal 'bar: root foo bar', req.response_body
|
|
224
|
+
|
|
225
|
+
req = @test_harness.request(':method' => 'GET', ':path' => '/foo/bar/baz')
|
|
226
|
+
assert_equal HTTP::OK, req.response_status
|
|
227
|
+
assert_equal 'baz: root foo bar baz', req.response_body
|
|
228
|
+
end
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
class ErrorHandlerTest < Minitest::Test
|
|
232
|
+
HTTP = Syntropy::HTTP
|
|
233
|
+
|
|
234
|
+
APP_ROOT = File.join(__dir__, 'fixtures/app_errors')
|
|
235
|
+
|
|
236
|
+
def setup
|
|
237
|
+
@machine = UM.new
|
|
238
|
+
|
|
239
|
+
@tmp_path = '/test/tmp'
|
|
240
|
+
@tmp_fn = File.join(APP_ROOT, 'tmp.rb')
|
|
241
|
+
|
|
242
|
+
@app = Syntropy::App.new(
|
|
243
|
+
app_root: APP_ROOT,
|
|
244
|
+
mount_path: '/',
|
|
245
|
+
watch_files: 0.05,
|
|
246
|
+
machine: @machine
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
@test_harness = Syntropy::TestHarness.new(@app)
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
def test_error_handlers
|
|
253
|
+
req = @test_harness.request(':method' => 'GET', ':path' => '/')
|
|
254
|
+
assert_equal HTTP::TEAPOT, req.response_status
|
|
255
|
+
assert_equal 'root: root', req.response_body
|
|
256
|
+
|
|
257
|
+
req = @test_harness.request(':method' => 'GET', ':path' => '/foo')
|
|
258
|
+
assert_equal HTTP::TEAPOT, req.response_status
|
|
259
|
+
assert_equal 'foo: foo', req.response_body
|
|
260
|
+
|
|
261
|
+
req = @test_harness.request(':method' => 'GET', ':path' => '/foo/bar')
|
|
262
|
+
assert_equal HTTP::TEAPOT, req.response_status
|
|
263
|
+
assert_equal 'bar: bar', req.response_body
|
|
264
|
+
|
|
265
|
+
req = @test_harness.request(':method' => 'GET', ':path' => '/foo/bar/baz')
|
|
266
|
+
assert_equal HTTP::TEAPOT, req.response_status
|
|
267
|
+
assert_equal 'bar: baz', req.response_body
|
|
268
|
+
end
|
|
269
|
+
end
|
|
270
|
+
|
|
186
271
|
class CustomAppTest < Minitest::Test
|
|
187
272
|
HTTP = Syntropy::HTTP
|
|
188
273
|
|
|
@@ -220,7 +305,7 @@ class MultiSiteAppTest < Minitest::Test
|
|
|
220
305
|
@test_harness = Syntropy::TestHarness.new(@app)
|
|
221
306
|
end
|
|
222
307
|
|
|
223
|
-
def
|
|
308
|
+
def test_dispatch_by_host
|
|
224
309
|
req = @test_harness.request(':method' => 'GET', ':path' => '/', 'host' => 'blah')
|
|
225
310
|
assert_nil req.response_body
|
|
226
311
|
assert_equal HTTP::BAD_REQUEST, req.response_status
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'helper'
|
|
4
|
+
require 'syntropy/test'
|
|
5
|
+
|
|
6
|
+
class DispatchByHostTest < Syntropy::Test
|
|
7
|
+
self.env = {
|
|
8
|
+
app_root: File.join(__dir__, 'fixtures/controllers'),
|
|
9
|
+
mount_path: '/'
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
def test_dispatch_by_host_dir
|
|
13
|
+
req = get('/by_host_dir', 'host' => 'sqdf')
|
|
14
|
+
assert_equal HTTP::BAD_REQUEST, req.response_status
|
|
15
|
+
|
|
16
|
+
req = get('/by_host_dir', 'host' => 'foo.com')
|
|
17
|
+
assert_equal HTTP::OK, req.response_status
|
|
18
|
+
assert_equal 'foo', req.response_body
|
|
19
|
+
|
|
20
|
+
req = get('/by_host_dir', 'host' => 'bar.com')
|
|
21
|
+
assert_equal HTTP::OK, req.response_status
|
|
22
|
+
assert_equal 'bar', req.response_body
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def test_dispatch_by_host_map
|
|
26
|
+
req = get('/by_host_map', 'host' => 'sqdf')
|
|
27
|
+
assert_equal HTTP::BAD_REQUEST, req.response_status
|
|
28
|
+
|
|
29
|
+
req = get('/by_host_map', 'host' => 'foofoo')
|
|
30
|
+
assert_equal HTTP::OK, req.response_status
|
|
31
|
+
assert_equal 'foo', req.response_body
|
|
32
|
+
|
|
33
|
+
req = get('/by_host_map', 'host' => 'barbar')
|
|
34
|
+
assert_equal HTTP::OK, req.response_status
|
|
35
|
+
assert_equal 'bar', req.response_body
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def test_dispatch_by_host_dir_map
|
|
39
|
+
req = get('/by_host_dir_map', 'host' => 'sqdf')
|
|
40
|
+
assert_equal HTTP::BAD_REQUEST, req.response_status
|
|
41
|
+
|
|
42
|
+
req = get('/by_host_dir_map', 'host' => 'foo.com')
|
|
43
|
+
assert_equal HTTP::OK, req.response_status
|
|
44
|
+
assert_equal 'foo', req.response_body
|
|
45
|
+
|
|
46
|
+
req = get('/by_host_dir_map', 'host' => 'foofoo')
|
|
47
|
+
assert_equal HTTP::OK, req.response_status
|
|
48
|
+
assert_equal 'foo', req.response_body
|
|
49
|
+
|
|
50
|
+
req = get('/by_host_dir_map', 'host' => 'bar.com')
|
|
51
|
+
assert_equal HTTP::OK, req.response_status
|
|
52
|
+
assert_equal 'bar', req.response_body
|
|
53
|
+
|
|
54
|
+
req = get('/by_host_dir_map', 'host' => 'barbar')
|
|
55
|
+
assert_equal HTTP::OK, req.response_status
|
|
56
|
+
assert_equal 'bar', req.response_body
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def test_dispatch_by_http_method
|
|
60
|
+
req = get('/by_http_method')
|
|
61
|
+
assert_equal HTTP::OK, req.response_status
|
|
62
|
+
assert_equal 'get', req.response_body
|
|
63
|
+
|
|
64
|
+
req = post('/by_http_method', nil, nil)
|
|
65
|
+
assert_equal HTTP::OK, req.response_status
|
|
66
|
+
assert_equal 'post', req.response_body
|
|
67
|
+
|
|
68
|
+
req = patch('/by_http_method', nil, nil)
|
|
69
|
+
assert_equal HTTP::METHOD_NOT_ALLOWED, req.response_status
|
|
70
|
+
end
|
|
71
|
+
end
|
data/test/test_http_protocol.rb
CHANGED
|
@@ -248,3 +248,57 @@ class HTTPProtocolResponseTest < HTTPProtocolTest
|
|
|
248
248
|
assert_raises(Syntropy::ProtocolError) { @io.http_read_response_headers }
|
|
249
249
|
end
|
|
250
250
|
end
|
|
251
|
+
|
|
252
|
+
class PipelineTest < HTTPProtocolTest
|
|
253
|
+
def test_pipeline_post_zero_content_length
|
|
254
|
+
msg = "POST /counter_api?q=incr HTTP/1.1\r\n" +
|
|
255
|
+
"Host: localhost:1234\r\n" +
|
|
256
|
+
"User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:151.0) Gecko/20100101 Firefox/151.0\r\n" +
|
|
257
|
+
"Accept: */*\r\n" +
|
|
258
|
+
"Accept-Language: en-US,en;q=0.9\r\n" +
|
|
259
|
+
"Accept-Encoding: gzip, deflate, br, zstd\r\n" +
|
|
260
|
+
"Referer: http://localhost:1234/counter\r\n" +
|
|
261
|
+
"Origin: http://localhost:1234\r\n" +
|
|
262
|
+
"Connection: keep-alive\r\n" +
|
|
263
|
+
"Sec-Fetch-Dest: empty\r\n" +
|
|
264
|
+
"Sec-Fetch-Mode: cors\r\n" +
|
|
265
|
+
"Sec-Fetch-Site: same-origin\r\n" +
|
|
266
|
+
"Priority: u=0\r\nPragma: no-cache\r\n" +
|
|
267
|
+
"Cache-Control: no-cache\r\n" +
|
|
268
|
+
"Content-Length: 0\r\n\r\n"
|
|
269
|
+
|
|
270
|
+
write(msg * 3)
|
|
271
|
+
3.times {
|
|
272
|
+
h = @io.http_read_request_headers
|
|
273
|
+
assert_equal '*/*', h['accept']
|
|
274
|
+
assert_nil @io.http_read_body_chunk(h)
|
|
275
|
+
}
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
def test_pipeline_post_with_body
|
|
279
|
+
msg = "POST /counter_api?q=incr HTTP/1.1\r\n" +
|
|
280
|
+
"Host: localhost:1234\r\n" +
|
|
281
|
+
"User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:151.0) Gecko/20100101 Firefox/151.0\r\n" +
|
|
282
|
+
"Accept: */*\r\n" +
|
|
283
|
+
"Accept-Language: en-US,en;q=0.9\r\n" +
|
|
284
|
+
"Accept-Encoding: gzip, deflate, br, zstd\r\n" +
|
|
285
|
+
"Referer: http://localhost:1234/counter\r\n" +
|
|
286
|
+
"Origin: http://localhost:1234\r\n" +
|
|
287
|
+
"Connection: keep-alive\r\n" +
|
|
288
|
+
"Sec-Fetch-Dest: empty\r\n" +
|
|
289
|
+
"Sec-Fetch-Mode: cors\r\n" +
|
|
290
|
+
"Sec-Fetch-Site: same-origin\r\n" +
|
|
291
|
+
"Priority: u=0\r\nPragma: no-cache\r\n" +
|
|
292
|
+
"Cache-Control: no-cache\r\n" +
|
|
293
|
+
"Content-Length: 3\r\n\r\n" +
|
|
294
|
+
"abc"
|
|
295
|
+
|
|
296
|
+
write(msg * 3)
|
|
297
|
+
3.times {
|
|
298
|
+
h = @io.http_read_request_headers
|
|
299
|
+
assert_equal '*/*', h['accept']
|
|
300
|
+
assert_equal 'abc', @io.http_read_body_chunk(h)
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
end
|
|
304
|
+
end
|