qeweney 0.5 → 0.6
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 +5 -0
- data/Gemfile.lock +3 -1
- data/examples/routing_benchmark.rb +151 -0
- data/lib/qeweney.rb +1 -1
- data/lib/qeweney/rack.rb +104 -0
- data/lib/qeweney/request.rb +1 -0
- data/lib/qeweney/response.rb +9 -0
- data/lib/qeweney/routing.rb +22 -12
- data/lib/qeweney/version.rb +1 -1
- data/qeweney.gemspec +1 -0
- data/test/helper.rb +2 -0
- data/test/test_response.rb +17 -0
- data/test/test_routing.rb +75 -0
- metadata +19 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: dc2a56f6f21001d4122d990b735f952521f2949b6d76811bc2f5e528f60ca8ed
|
4
|
+
data.tar.gz: e7f1a6fa8e788ef4bf1612ac043f847497f3c4b9558a8959ad75182806106088
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4be0bb1c58d67ccaa8550b6b1ce59dfbc5cdb6a87d19ede17702293eb521bec416d9932b54627eb43ba832c55dee4e4320983d6eb14fdae4131d751eb459391a
|
7
|
+
data.tar.gz: 5cf454ddf41665c9abf6cdba864161257c2d11da5c60add1926cc27e9ba5fcef3a3eeba003815e4cbd8095d70632ed999390cfd07912a135cb33813155876762
|
data/CHANGELOG.md
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,13 +1,14 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
qeweney (0.
|
4
|
+
qeweney (0.6)
|
5
5
|
escape_utils (~> 1.2.1)
|
6
6
|
|
7
7
|
GEM
|
8
8
|
remote: https://rubygems.org/
|
9
9
|
specs:
|
10
10
|
ansi (1.5.0)
|
11
|
+
benchmark-ips (2.8.4)
|
11
12
|
builder (3.2.4)
|
12
13
|
escape_utils (1.2.1)
|
13
14
|
minitest (5.11.3)
|
@@ -23,6 +24,7 @@ PLATFORMS
|
|
23
24
|
ruby
|
24
25
|
|
25
26
|
DEPENDENCIES
|
27
|
+
benchmark-ips (~> 2.8.3)
|
26
28
|
minitest (~> 5.11.3)
|
27
29
|
minitest-reporters (~> 1.4.2)
|
28
30
|
qeweney!
|
@@ -0,0 +1,151 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'qeweney'
|
5
|
+
require 'benchmark/ips'
|
6
|
+
|
7
|
+
module Qeweney
|
8
|
+
class MockAdapter
|
9
|
+
attr_reader :calls
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
@calls = []
|
13
|
+
end
|
14
|
+
|
15
|
+
def method_missing(sym, *args)
|
16
|
+
calls << [sym, *args]
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.mock(headers = {})
|
21
|
+
Request.new(headers, MockAdapter.new)
|
22
|
+
end
|
23
|
+
|
24
|
+
class Request
|
25
|
+
def response_calls
|
26
|
+
adapter.calls
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def create_mock_request
|
32
|
+
Qeweney.mock(':path' => '/hello/world', ':method' => 'post')
|
33
|
+
end
|
34
|
+
|
35
|
+
CTApp = ->(r) do
|
36
|
+
r.route do
|
37
|
+
r.on_root { r.redirect '/hello' }
|
38
|
+
r.on('hello') do
|
39
|
+
r.on_get('world') { r.respond 'Hello world' }
|
40
|
+
r.on_get { r.respond 'Hello' }
|
41
|
+
r.on_post do
|
42
|
+
# puts 'Someone said Hello'
|
43
|
+
r.redirect '/'
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
FlowControlApp = ->(r) do
|
50
|
+
if r.path == '/'
|
51
|
+
return r.redirect '/hello'
|
52
|
+
elsif r.current_path_part == 'hello'
|
53
|
+
r.enter_route
|
54
|
+
if r.method == 'get' && r.current_path_part == 'world'
|
55
|
+
return r.respond('Hello world')
|
56
|
+
elsif r.method == 'get'
|
57
|
+
return r.respond('Hello')
|
58
|
+
elsif r.method == 'post'
|
59
|
+
return r.redirect('/')
|
60
|
+
end
|
61
|
+
r.leave_route
|
62
|
+
end
|
63
|
+
r.respond(nil, ':status' => 404)
|
64
|
+
end
|
65
|
+
|
66
|
+
class Qeweney::Request
|
67
|
+
def get?
|
68
|
+
method == 'get'
|
69
|
+
end
|
70
|
+
|
71
|
+
def post?
|
72
|
+
method == 'post'
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
NicerFlowControlApp = ->(r) do
|
77
|
+
case r.current_path_part
|
78
|
+
when '/'
|
79
|
+
return r.redirect('/hello')
|
80
|
+
when 'hello'
|
81
|
+
r.enter_route
|
82
|
+
if r.get? && r.current_route == 'world'
|
83
|
+
return r.respond('Hello world')
|
84
|
+
elsif r.get?
|
85
|
+
return r.respond('Hello')
|
86
|
+
elsif r.post?
|
87
|
+
return r.redirect('/')
|
88
|
+
end
|
89
|
+
r.leave_route
|
90
|
+
end
|
91
|
+
r.respond(nil, ':status' => 404)
|
92
|
+
end
|
93
|
+
|
94
|
+
OptimizedRubyApp = ->(r) do
|
95
|
+
path = r.path
|
96
|
+
if path == '/'
|
97
|
+
return r.redirect('/hello')
|
98
|
+
elsif path =~ /^\/hello(.+)/
|
99
|
+
method = r.method
|
100
|
+
if method == 'get'
|
101
|
+
rest = Regexp.last_match(1)
|
102
|
+
if rest == '/world'
|
103
|
+
return r.respond('Hello world')
|
104
|
+
else
|
105
|
+
return r.respond('Hello')
|
106
|
+
end
|
107
|
+
elsif method == 'post'
|
108
|
+
# puts 'Someone said Hello'
|
109
|
+
return r.redirect('/')
|
110
|
+
end
|
111
|
+
end
|
112
|
+
r.respond(nil, ':status' => 404)
|
113
|
+
end
|
114
|
+
|
115
|
+
def test
|
116
|
+
r = create_mock_request
|
117
|
+
puts '* catch/throw'
|
118
|
+
CTApp.call(r)
|
119
|
+
p r.response_calls
|
120
|
+
|
121
|
+
r = create_mock_request
|
122
|
+
puts '* classic flow control'
|
123
|
+
FlowControlApp.call(r)
|
124
|
+
p r.response_calls
|
125
|
+
|
126
|
+
r = create_mock_request
|
127
|
+
puts '* nicer flow control'
|
128
|
+
NicerFlowControlApp.call(r)
|
129
|
+
p r.response_calls
|
130
|
+
|
131
|
+
r = create_mock_request
|
132
|
+
puts '* optimized Ruby'
|
133
|
+
OptimizedRubyApp.call(r)
|
134
|
+
p r.response_calls
|
135
|
+
end
|
136
|
+
|
137
|
+
def benchmark
|
138
|
+
Benchmark.ips do |x|
|
139
|
+
x.config(:time => 3, :warmup => 1)
|
140
|
+
|
141
|
+
x.report("catch/throw") { CTApp.call(create_mock_request) }
|
142
|
+
x.report("flow control") { FlowControlApp.call(create_mock_request) }
|
143
|
+
x.report("nicer flow control") { NicerFlowControlApp.call(create_mock_request) }
|
144
|
+
x.report("hand-optimized") { OptimizedRubyApp.call(create_mock_request) }
|
145
|
+
|
146
|
+
x.compare!
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
test
|
151
|
+
benchmark
|
data/lib/qeweney.rb
CHANGED
data/lib/qeweney/rack.rb
ADDED
@@ -0,0 +1,104 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Qeweney
|
4
|
+
class RackRequestAdapter
|
5
|
+
def initialize(env)
|
6
|
+
@env = env
|
7
|
+
@response_headers = {}
|
8
|
+
@response_body = []
|
9
|
+
end
|
10
|
+
|
11
|
+
def request_headers
|
12
|
+
request_http_headers.merge(
|
13
|
+
':scheme' => @env['rack.url_scheme'],
|
14
|
+
':method' => @env['REQUEST_METHOD'].downcase,
|
15
|
+
':path' => request_path_from_env
|
16
|
+
)
|
17
|
+
end
|
18
|
+
|
19
|
+
def request_path_from_env
|
20
|
+
path = File.join(@env['SCRIPT_NAME'], @env['PATH_INFO'])
|
21
|
+
path = path + "?#{@env['QUERY_STRING']}" if @env['QUERY_STRING']
|
22
|
+
path
|
23
|
+
end
|
24
|
+
|
25
|
+
def request_http_headers
|
26
|
+
headers = {}
|
27
|
+
@env.each do |k, v|
|
28
|
+
next unless k =~ /^HTTP_(.+)$/
|
29
|
+
|
30
|
+
headers[Regexp.last_match(1).downcase.gsub('_', '-')] = v
|
31
|
+
end
|
32
|
+
headers
|
33
|
+
end
|
34
|
+
|
35
|
+
def respond(body, headers)
|
36
|
+
@response_body << body
|
37
|
+
@response_headers = headers
|
38
|
+
end
|
39
|
+
|
40
|
+
def send_headers(headers, empty_response: nil)
|
41
|
+
@response_headers = headers
|
42
|
+
end
|
43
|
+
|
44
|
+
def send_chunk(body, done: false)
|
45
|
+
@response_body << body
|
46
|
+
end
|
47
|
+
|
48
|
+
def finish
|
49
|
+
end
|
50
|
+
|
51
|
+
def rack_response
|
52
|
+
@status = @response_headers.delete(':status')
|
53
|
+
[
|
54
|
+
@status,
|
55
|
+
@response_headers,
|
56
|
+
@response_body
|
57
|
+
]
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def self.rack(&block)
|
62
|
+
proc do |env|
|
63
|
+
adapter = RackRequestAdapter.new(env)
|
64
|
+
req = Request.new(adapter.request_headers, adapter)
|
65
|
+
block.(req)
|
66
|
+
adapter.rack_response
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def self.rack_env_from_request(request)
|
71
|
+
Hash.new do |h, k|
|
72
|
+
h[k] = env_value_from_request(request, k)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
RACK_ENV = {
|
77
|
+
'SCRIPT_NAME' => '',
|
78
|
+
'rack.version' => [1, 3],
|
79
|
+
'SERVER_PORT' => '80', # ?
|
80
|
+
'rack.url_scheme' => 'http', # ?
|
81
|
+
'rack.errors' => STDERR, # ?
|
82
|
+
'rack.multithread' => false,
|
83
|
+
'rack.run_once' => false,
|
84
|
+
'rack.hijack?' => false,
|
85
|
+
'rack.hijack' => nil,
|
86
|
+
'rack.hijack_io' => nil,
|
87
|
+
'rack.session' => nil,
|
88
|
+
'rack.logger' => nil,
|
89
|
+
'rack.multipart.buffer_size' => nil,
|
90
|
+
'rack.multipar.tempfile_factory' => nil
|
91
|
+
}
|
92
|
+
|
93
|
+
def self.env_value_from_request(request, key)
|
94
|
+
case key
|
95
|
+
when 'REQUEST_METHOD' then request.method
|
96
|
+
when 'PATH_INFO' then request.path
|
97
|
+
when 'QUERY_STRING' then request.query_string || ''
|
98
|
+
when 'SERVER_NAME' then request.headers['host']
|
99
|
+
when 'rack.input' then InputStream.new(request)
|
100
|
+
when HTTP_HEADER_RE then request.headers[$1.gsub('_', '-').downcase]
|
101
|
+
else RACK_ENV[key]
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
data/lib/qeweney/request.rb
CHANGED
data/lib/qeweney/response.rb
CHANGED
@@ -142,5 +142,14 @@ module Qeweney
|
|
142
142
|
)
|
143
143
|
respond(buf.string, headers)
|
144
144
|
end
|
145
|
+
|
146
|
+
def serve_rack(app)
|
147
|
+
response = app.(Qeweney.rack_env_from_request(self))
|
148
|
+
headers = (response[1] || {}).merge(':status' => response[0])
|
149
|
+
respond(response[2].join, headers)
|
150
|
+
|
151
|
+
# TODO: send separate chunks for multi-part body
|
152
|
+
# TODO: add support for streaming body
|
153
|
+
end
|
145
154
|
end
|
146
155
|
end
|
data/lib/qeweney/routing.rb
CHANGED
@@ -1,8 +1,13 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Qeweney
|
4
|
+
def self.route(&block)
|
5
|
+
->(r) { r.route(&block) }
|
6
|
+
end
|
7
|
+
|
4
8
|
module RoutingMethods
|
5
9
|
def route(&block)
|
10
|
+
(@path_parts ||= path.split('/'))[@path_parts_idx ||= 1]
|
6
11
|
res = catch(:stop) { yield self }
|
7
12
|
return if res == :found
|
8
13
|
|
@@ -11,36 +16,41 @@ module Qeweney
|
|
11
16
|
|
12
17
|
def route_found(&block)
|
13
18
|
catch(:stop, &block)
|
14
|
-
throw :stop, :found
|
19
|
+
throw :stop, headers_sent? ? :found : nil
|
15
20
|
end
|
16
21
|
|
17
22
|
@@regexp_cache = {}
|
18
23
|
|
19
|
-
def
|
20
|
-
@
|
24
|
+
def route_part
|
25
|
+
@path_parts[@path_parts_idx]
|
21
26
|
end
|
22
|
-
|
23
|
-
def on(route = nil, &block)
|
24
|
-
@__routing_path__ ||= path
|
25
27
|
|
26
|
-
|
27
|
-
|
28
|
-
|
28
|
+
def enter_route
|
29
|
+
@path_parts_idx += 1
|
30
|
+
end
|
31
|
+
|
32
|
+
def leave_route
|
33
|
+
@path_parts_idx -= 1
|
34
|
+
end
|
29
35
|
|
30
|
-
|
36
|
+
def on(route = nil, &block)
|
37
|
+
if route
|
38
|
+
return unless @path_parts[@path_parts_idx] == route
|
31
39
|
end
|
32
40
|
|
41
|
+
enter_route
|
33
42
|
route_found(&block)
|
43
|
+
leave_route
|
34
44
|
end
|
35
45
|
|
36
46
|
def is(route = '/', &block)
|
37
|
-
return unless @
|
47
|
+
return unless @path_parts[@path_parts_idx] == route && @path_parts_idx >= @path_parts.size
|
38
48
|
|
39
49
|
route_found(&block)
|
40
50
|
end
|
41
51
|
|
42
52
|
def on_root(&block)
|
43
|
-
return unless @
|
53
|
+
return unless @path_parts_idx > @path_parts.size - 1
|
44
54
|
|
45
55
|
route_found(&block)
|
46
56
|
end
|
data/lib/qeweney/version.rb
CHANGED
data/qeweney.gemspec
CHANGED
data/test/helper.rb
CHANGED
data/test/test_response.rb
CHANGED
@@ -166,3 +166,20 @@ class UpgradeTest < MiniTest::Test
|
|
166
166
|
], r.response_calls
|
167
167
|
end
|
168
168
|
end
|
169
|
+
|
170
|
+
class ServeRackTest < MiniTest::Test
|
171
|
+
def test_serve_rack
|
172
|
+
r = Qeweney.mock(
|
173
|
+
':method' => 'get',
|
174
|
+
':path' => '/foo/bar?a=1&b=2%2F3',
|
175
|
+
'accept' => 'blah'
|
176
|
+
)
|
177
|
+
r.serve_rack(->(env) {
|
178
|
+
[404, {'Foo' => 'Bar'}, ["#{env['REQUEST_METHOD']} #{env['PATH_INFO']}"]]
|
179
|
+
})
|
180
|
+
|
181
|
+
assert_equal [
|
182
|
+
[:respond, "get /foo/bar", {':status' => 404, 'Foo' => 'Bar' }]
|
183
|
+
], r.response_calls
|
184
|
+
end
|
185
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'helper'
|
4
|
+
|
5
|
+
class RoutingTest < MiniTest::Test
|
6
|
+
App1 = ->(r) do
|
7
|
+
r.route do
|
8
|
+
r.on_root { r.redirect '/hello' }
|
9
|
+
r.on('hello') do
|
10
|
+
r.on_get('world') { r.respond 'Hello world' }
|
11
|
+
r.on_get { r.respond 'Hello' }
|
12
|
+
r.on_post do
|
13
|
+
r.redirect '/'
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def test_app1
|
20
|
+
r = Qeweney.mock(':path' => '/foo')
|
21
|
+
App1.(r)
|
22
|
+
assert_equal [[:respond, nil, { ':status' => 404 }]], r.response_calls
|
23
|
+
|
24
|
+
r = Qeweney.mock(':path' => '/')
|
25
|
+
App1.(r)
|
26
|
+
assert_equal [[:respond, nil, { ':status' => 302, 'Location' => '/hello' }]], r.response_calls
|
27
|
+
|
28
|
+
r = Qeweney.mock(':path' => '/hello', ':method' => 'foo')
|
29
|
+
App1.(r)
|
30
|
+
assert_equal [[:respond, nil, { ':status' => 404 }]], r.response_calls
|
31
|
+
|
32
|
+
r = Qeweney.mock(':path' => '/hello', ':method' => 'get')
|
33
|
+
App1.(r)
|
34
|
+
assert_equal [[:respond, 'Hello', {}]], r.response_calls
|
35
|
+
|
36
|
+
r = Qeweney.mock(':path' => '/hello', ':method' => 'post')
|
37
|
+
App1.(r)
|
38
|
+
assert_equal [[:respond, nil, { ':status' => 302, 'Location' => '/' }]], r.response_calls
|
39
|
+
|
40
|
+
r = Qeweney.mock(':path' => '/hello/world', ':method' => 'get')
|
41
|
+
App1.(r)
|
42
|
+
assert_equal [[:respond, 'Hello world', {}]], r.response_calls
|
43
|
+
end
|
44
|
+
|
45
|
+
def test_on_root
|
46
|
+
app = Qeweney.route do |r|
|
47
|
+
r.on_root { r.respond('root') }
|
48
|
+
r.on('foo') {
|
49
|
+
r.on_root { r.respond('foo root') }
|
50
|
+
r.on('bar') {
|
51
|
+
r.on_root { r.respond('bar root') }
|
52
|
+
r.on('baz') {
|
53
|
+
r.on_root { r.respond('baz root') }
|
54
|
+
}
|
55
|
+
}
|
56
|
+
}
|
57
|
+
end
|
58
|
+
|
59
|
+
r = Qeweney.mock(':path' => '/')
|
60
|
+
app.(r)
|
61
|
+
assert_equal [[:respond, 'root', {}]], r.response_calls
|
62
|
+
|
63
|
+
r = Qeweney.mock(':path' => '/foo')
|
64
|
+
app.(r)
|
65
|
+
assert_equal [[:respond, 'foo root', {}]], r.response_calls
|
66
|
+
|
67
|
+
r = Qeweney.mock(':path' => '/foo/bar')
|
68
|
+
app.(r)
|
69
|
+
assert_equal [[:respond, 'bar root', {}]], r.response_calls
|
70
|
+
|
71
|
+
r = Qeweney.mock(':path' => '/foo/bar/baz')
|
72
|
+
app.(r)
|
73
|
+
assert_equal [[:respond, 'baz root', {}]], r.response_calls
|
74
|
+
end
|
75
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: qeweney
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: '0.
|
4
|
+
version: '0.6'
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sharon Rosner
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-
|
11
|
+
date: 2021-03-03 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: escape_utils
|
@@ -66,6 +66,20 @@ dependencies:
|
|
66
66
|
- - "~>"
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: 1.4.2
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: benchmark-ips
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: 2.8.3
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 2.8.3
|
69
83
|
description:
|
70
84
|
email: sharon@noteflakes.com
|
71
85
|
executables: []
|
@@ -82,8 +96,10 @@ files:
|
|
82
96
|
- README.md
|
83
97
|
- Rakefile
|
84
98
|
- TODO.md
|
99
|
+
- examples/routing_benchmark.rb
|
85
100
|
- lib/qeweney.rb
|
86
101
|
- lib/qeweney/mime_types.rb
|
102
|
+
- lib/qeweney/rack.rb
|
87
103
|
- lib/qeweney/request.rb
|
88
104
|
- lib/qeweney/request_info.rb
|
89
105
|
- lib/qeweney/response.rb
|
@@ -95,6 +111,7 @@ files:
|
|
95
111
|
- test/run.rb
|
96
112
|
- test/test_request_info.rb
|
97
113
|
- test/test_response.rb
|
114
|
+
- test/test_routing.rb
|
98
115
|
homepage: http://github.com/digital-fabric/qeweney
|
99
116
|
licenses:
|
100
117
|
- MIT
|