toycol 0.0.1 → 0.2.2
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/.github/ISSUE_TEMPLATE/bug_report.md +29 -0
- data/.github/dependabot.yml +9 -0
- data/.github/workflows/main.yml +4 -1
- data/.gitignore +1 -0
- data/.rubocop.yml +26 -1
- data/CHANGELOG.md +1 -1
- data/README.md +2 -4
- data/examples/duck/Gemfile +5 -0
- data/examples/duck/Protocolfile.duck +23 -0
- data/examples/duck/config_duck.ru +59 -0
- data/examples/rubylike/Gemfile +5 -0
- data/examples/rubylike/Protocolfile.rubylike +18 -0
- data/examples/rubylike/config_rubylike.ru +27 -0
- data/examples/safe_ruby/Gemfile +6 -0
- data/examples/safe_ruby/Protocolfile.safe_ruby +47 -0
- data/examples/safe_ruby/config_safe_ruby.ru +55 -0
- data/examples/safe_ruby_with_sinatra/Gemfile +10 -0
- data/examples/safe_ruby_with_sinatra/Protocolfile.safe_ruby_with_sinatra +44 -0
- data/examples/safe_ruby_with_sinatra/app.rb +28 -0
- data/examples/safe_ruby_with_sinatra/post.rb +34 -0
- data/examples/safe_ruby_with_sinatra/views/index.erb +32 -0
- data/examples/unsafe_ruby/Gemfile +5 -0
- data/examples/unsafe_ruby/Protocolfile.unsafe_ruby +15 -0
- data/examples/unsafe_ruby/config_unsafe_ruby.ru +24 -0
- data/exe/toycol +6 -0
- data/lib/rack/handler/toycol.rb +65 -0
- data/lib/toycol.rb +20 -1
- data/lib/toycol/client.rb +38 -0
- data/lib/toycol/command.rb +132 -0
- data/lib/toycol/const.rb +102 -0
- data/lib/toycol/helper.rb +27 -0
- data/lib/toycol/protocol.rb +126 -0
- data/lib/toycol/proxy.rb +116 -0
- data/lib/toycol/server.rb +124 -0
- data/lib/toycol/version.rb +1 -1
- data/toycol.gemspec +3 -7
- metadata +48 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 102026f42040d677807fb5fe1cd08797f564951096c15ac2c0150a757cc82926
|
4
|
+
data.tar.gz: d7b4312381d610821b197f688c871acbe26a6d70146c67441d23cbd52a0ed697
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 11eb3611d926c804f1f0d632f40daf2a37b7f663bdb449c69c2353d608dba87161723f4380d5dedfa78a46f1d264dde625b9bdd30ba6dda6e64eed12964f83cf
|
7
|
+
data.tar.gz: 9c38815ef3861ad35ac635f713fb2f6b459ee6cbc64cbe9da56ec355381a5f21c30189e28ebe92f961406b99d9349631b52571729fd8c290d94cd61c0eefd4ea
|
@@ -0,0 +1,29 @@
|
|
1
|
+
---
|
2
|
+
name: Bug report
|
3
|
+
about: Create a report to help us improve
|
4
|
+
title: "[BUG]"
|
5
|
+
labels: bug
|
6
|
+
assignees: ''
|
7
|
+
|
8
|
+
---
|
9
|
+
|
10
|
+
## Describe the bug
|
11
|
+
A clear and concise description of what the bug is.
|
12
|
+
|
13
|
+
## To Reproduce
|
14
|
+
Steps to reproduce the behavior:
|
15
|
+
1. Go to '...'
|
16
|
+
2. Click on '....'
|
17
|
+
3. Scroll down to '....'
|
18
|
+
4. See error
|
19
|
+
|
20
|
+
## Expected behavior
|
21
|
+
A clear and concise description of what you expected to happen.
|
22
|
+
|
23
|
+
## Environment
|
24
|
+
- Ruby Version:
|
25
|
+
- Rack Version:
|
26
|
+
- Puma Version:
|
27
|
+
|
28
|
+
## Additional context
|
29
|
+
Add any other context about the problem here.
|
data/.github/workflows/main.yml
CHANGED
@@ -5,12 +5,15 @@ on: [push,pull_request]
|
|
5
5
|
jobs:
|
6
6
|
build:
|
7
7
|
runs-on: ubuntu-latest
|
8
|
+
strategy:
|
9
|
+
matrix:
|
10
|
+
ruby: [2.6, 2.7, 3.0, head]
|
8
11
|
steps:
|
9
12
|
- uses: actions/checkout@v2
|
10
13
|
- name: Set up Ruby
|
11
14
|
uses: ruby/setup-ruby@v1
|
12
15
|
with:
|
13
|
-
ruby-version:
|
16
|
+
ruby-version: ${{ matrix.ruby }}
|
14
17
|
bundler-cache: true
|
15
18
|
- name: Run the default task
|
16
19
|
run: bundle exec rake
|
data/.gitignore
CHANGED
data/.rubocop.yml
CHANGED
@@ -9,7 +9,14 @@ AllCops:
|
|
9
9
|
- 'Rakefile'
|
10
10
|
- 'toycol.gemspec'
|
11
11
|
NewCops: enable
|
12
|
-
TargetRubyVersion:
|
12
|
+
TargetRubyVersion: 2.6
|
13
|
+
|
14
|
+
Style/Documentation:
|
15
|
+
Enabled: false
|
16
|
+
|
17
|
+
Style/StringConcatenation:
|
18
|
+
Exclude:
|
19
|
+
- lib/toycol/server.rb
|
13
20
|
|
14
21
|
Style/StringLiterals:
|
15
22
|
Enabled: true
|
@@ -21,3 +28,21 @@ Style/StringLiteralsInInterpolation:
|
|
21
28
|
|
22
29
|
Layout/LineLength:
|
23
30
|
Max: 120
|
31
|
+
|
32
|
+
Layout/HashAlignment:
|
33
|
+
Exclude:
|
34
|
+
- lib/toycol/server.rb
|
35
|
+
|
36
|
+
Metrics:
|
37
|
+
Exclude:
|
38
|
+
- lib/toycol/proxy.rb
|
39
|
+
- lib/toycol/server.rb
|
40
|
+
|
41
|
+
Metrics/AbcSize:
|
42
|
+
Max: 30
|
43
|
+
|
44
|
+
Metrics/ClassLength:
|
45
|
+
Max: 400
|
46
|
+
|
47
|
+
Metrics/MethodLength:
|
48
|
+
Max: 15
|
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
@@ -1,8 +1,6 @@
|
|
1
1
|
# Toycol
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
TODO: Delete this and the text above, and describe your gem
|
3
|
+
Toy Application Protocol framework
|
6
4
|
|
7
5
|
## Installation
|
8
6
|
|
@@ -22,7 +20,7 @@ Or install it yourself as:
|
|
22
20
|
|
23
21
|
## Usage
|
24
22
|
|
25
|
-
|
23
|
+
WIP
|
26
24
|
|
27
25
|
## Development
|
28
26
|
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
Toycol::Protocol.define(:duck) do
|
4
|
+
custom_status_codes(
|
5
|
+
600 => "I'm afraid you are not a duck..."
|
6
|
+
)
|
7
|
+
additional_request_methods "OTHER"
|
8
|
+
|
9
|
+
request.path do |message|
|
10
|
+
%r{(?<path>/\w*)}.match(message)[:path]
|
11
|
+
end
|
12
|
+
|
13
|
+
request.query do |message|
|
14
|
+
/\?(?<query>.+)/.match(message) { |m| m[:query] }
|
15
|
+
end
|
16
|
+
|
17
|
+
request.http_method do |message|
|
18
|
+
case message.scan(/quack/).size
|
19
|
+
when 2 then "GET"
|
20
|
+
else "OTHER"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rack"
|
4
|
+
require "toycol"
|
5
|
+
|
6
|
+
Toycol::Protocol.use(:duck)
|
7
|
+
|
8
|
+
# Duck protocol application
|
9
|
+
class App
|
10
|
+
def call(env)
|
11
|
+
case env["REQUEST_METHOD"]
|
12
|
+
when "GET"
|
13
|
+
case env["PATH_INFO"]
|
14
|
+
when "/posts"
|
15
|
+
return app_for_get_with_query if env["QUERY_STRING"] == "user_id=1"
|
16
|
+
|
17
|
+
app_for_get
|
18
|
+
when "/" then app_for_get_to_root
|
19
|
+
end
|
20
|
+
when "OTHER" then app_for_other
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def app_for_get_with_query
|
27
|
+
[
|
28
|
+
200,
|
29
|
+
{ "Content-Type" => "text/html" },
|
30
|
+
["quack quack, I am the No.1 duck\n"]
|
31
|
+
]
|
32
|
+
end
|
33
|
+
|
34
|
+
def app_for_get
|
35
|
+
[
|
36
|
+
200,
|
37
|
+
{ "Content-Type" => "text/html" },
|
38
|
+
["quack quack, quack quack, quack, quack\n", "quack quack, I am the No.1 duck\n"]
|
39
|
+
]
|
40
|
+
end
|
41
|
+
|
42
|
+
def app_for_get_to_root
|
43
|
+
[
|
44
|
+
200,
|
45
|
+
{ "Content-Type" => "text/html" },
|
46
|
+
["Hello, This app is running on Sample duck protocol.\n"]
|
47
|
+
]
|
48
|
+
end
|
49
|
+
|
50
|
+
def app_for_other
|
51
|
+
[
|
52
|
+
600,
|
53
|
+
{ "Content-Type" => "text/html" },
|
54
|
+
["Sorry, this application is only for ducks...\n"]
|
55
|
+
]
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
run App.new
|
@@ -0,0 +1,18 @@
|
|
1
|
+
Toycol::Protocol.define(:rubylike) do
|
2
|
+
custom_status_codes(
|
3
|
+
700 => "Hmm...",
|
4
|
+
)
|
5
|
+
|
6
|
+
additional_request_methods "OTHER"
|
7
|
+
|
8
|
+
request.path do |message|
|
9
|
+
/['"](?<path>.+)['"]/.match(message)[:path]
|
10
|
+
end
|
11
|
+
|
12
|
+
request.http_method do |message|
|
13
|
+
case /\.(?<method>[A-z]+)/.match(message)&.captures&.first
|
14
|
+
when "get" then "GET"
|
15
|
+
else "OTHER"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rack"
|
4
|
+
require "toycol"
|
5
|
+
|
6
|
+
Toycol::Protocol.use(:rubylike)
|
7
|
+
|
8
|
+
class App
|
9
|
+
def call(env)
|
10
|
+
case env["REQUEST_METHOD"]
|
11
|
+
when "GET"
|
12
|
+
[
|
13
|
+
200,
|
14
|
+
{ "Content-Type" => "text/html" },
|
15
|
+
["I love ruby!\n", "I love RubyKaigi!\n"]
|
16
|
+
]
|
17
|
+
when "OTHER"
|
18
|
+
[
|
19
|
+
700,
|
20
|
+
{ "Content-Type" => "text/html" },
|
21
|
+
["Sorry, but I'd like you to speak more like a Ruby programmer...\n"]
|
22
|
+
]
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
run App.new
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module SafeRuby
|
2
|
+
PARSER_REGEX = /["'](?<path>\/.*)["']\.(?<method>[A-z]+)/
|
3
|
+
QUERY_REGEX = /query?.*\{(?<query>.*)\}/
|
4
|
+
INPUT_REGEX = /input?.*\{(?<input>.*)\}/
|
5
|
+
end
|
6
|
+
|
7
|
+
Toycol::Protocol.define(:safe_ruby) do |message|
|
8
|
+
using Module.new {
|
9
|
+
refine String do
|
10
|
+
# Ex. '/posts'.get
|
11
|
+
# Ex. (with query string) '/posts'.get(query: { user_id: 2 })
|
12
|
+
def get(options = {})
|
13
|
+
Toycol::Protocol.request.query { options[:query] } if options[:query]
|
14
|
+
Toycol::Protocol.request.http_method { "GET" }
|
15
|
+
end
|
16
|
+
|
17
|
+
# Ex. '/posts'.post(input: { user_id: 1, body: 'This is a post request' })
|
18
|
+
def post(options = {})
|
19
|
+
Toycol::Protocol.request.input { options[:input] } if options[:input]
|
20
|
+
Toycol::Protocol.request.http_method { "POST" }
|
21
|
+
end
|
22
|
+
|
23
|
+
def parse_as_queries
|
24
|
+
split(",").map { |str| str.scan(/\w+/).join("=") }
|
25
|
+
end
|
26
|
+
|
27
|
+
def parse_as_inputs
|
28
|
+
split(",").map { |str| str.split(":").map { |s| s.strip! && s.gsub(/['"]/, "") }.join("=") }
|
29
|
+
end
|
30
|
+
end
|
31
|
+
}
|
32
|
+
|
33
|
+
path, method = SafeRuby::PARSER_REGEX.match(message) { |m| [m[:path], m[:method]] }
|
34
|
+
query = SafeRuby::QUERY_REGEX.match(message) { |m| m[:query].parse_as_queries }&.join("&")
|
35
|
+
input = SafeRuby::INPUT_REGEX.match(message) { |m| m[:input].parse_as_inputs }&.join("&")
|
36
|
+
args = {}
|
37
|
+
|
38
|
+
request.path { path }
|
39
|
+
|
40
|
+
%i[query input].each do |k|
|
41
|
+
if v = binding.local_variable_get(k)
|
42
|
+
args[*k] = v
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
request_path.public_send(method, args)
|
47
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rack"
|
4
|
+
require "toycol"
|
5
|
+
|
6
|
+
Toycol::Protocol.use(:safe_ruby)
|
7
|
+
|
8
|
+
class App
|
9
|
+
def call(env)
|
10
|
+
case env["REQUEST_METHOD"]
|
11
|
+
when "GET"
|
12
|
+
case env["PATH_INFO"]
|
13
|
+
when "/posts"
|
14
|
+
return app_for_get_with_query if env["QUERY_STRING"] == "user_id=2"
|
15
|
+
|
16
|
+
app_for_get
|
17
|
+
end
|
18
|
+
when "POST"
|
19
|
+
input = env["rack.input"].gets
|
20
|
+
created = input.split("&").map { |str| str.split("=") }.to_h
|
21
|
+
|
22
|
+
app_for_post(user_id: created["user_id"], body: created["body"])
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def app_for_get_with_query
|
29
|
+
[
|
30
|
+
200,
|
31
|
+
{ "Content-Type" => "text/html" },
|
32
|
+
["User<2> I love RubyKaigi!\n"]
|
33
|
+
]
|
34
|
+
end
|
35
|
+
|
36
|
+
def app_for_get
|
37
|
+
[
|
38
|
+
200,
|
39
|
+
{ "Content-Type" => "text/html" },
|
40
|
+
["User<1> I love Ruby!\n", "User<2> I love RubyKaigi!\n"]
|
41
|
+
]
|
42
|
+
end
|
43
|
+
|
44
|
+
def app_for_post(user_id:, body:)
|
45
|
+
[
|
46
|
+
201,
|
47
|
+
{ "Content-Type" => "text/html", "Location" => "/posts" },
|
48
|
+
["User<1> I love Ruby!\n",
|
49
|
+
"User<2> I love RubyKaigi!\n",
|
50
|
+
"User<#{user_id}> #{body}\n"]
|
51
|
+
]
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
run App.new
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module SafeRubyWithSinatra
|
2
|
+
PARSER_REGEX = /["'](?<path>\/.*)["']\.(?<method>[A-z]+)/
|
3
|
+
QUERY_REGEX = /query?.*\{(?<query>.*)\}/
|
4
|
+
INPUT_REGEX = /input?.*\{(?<input>.*)\}/
|
5
|
+
end
|
6
|
+
|
7
|
+
Toycol::Protocol.define(:safe_ruby_with_sinatra) do |message|
|
8
|
+
using Module.new {
|
9
|
+
refine String do
|
10
|
+
def get(options = {})
|
11
|
+
Toycol::Protocol.request.query { options[:query] } if options[:query]
|
12
|
+
Toycol::Protocol.request.http_method { "GET" }
|
13
|
+
end
|
14
|
+
|
15
|
+
def post(options = {})
|
16
|
+
Toycol::Protocol.request.input { options[:input] } if options[:input]
|
17
|
+
Toycol::Protocol.request.http_method { "POST" }
|
18
|
+
end
|
19
|
+
|
20
|
+
def parse_as_queries
|
21
|
+
split(",").map { |str| str.scan(/\w+/).join("=") }
|
22
|
+
end
|
23
|
+
|
24
|
+
def parse_as_inputs
|
25
|
+
split(",").map { |str| str.split(":").map { |s| s.strip! && s.gsub(/['"]/, "") }.join("=") }
|
26
|
+
end
|
27
|
+
end
|
28
|
+
}
|
29
|
+
|
30
|
+
path, method = SafeRubyWithSinatra::PARSER_REGEX.match(message) { |m| [m[:path], m[:method]] }
|
31
|
+
query = SafeRubyWithSinatra::QUERY_REGEX.match(message) { |m| m[:query].parse_as_queries }&.join("&")
|
32
|
+
input = SafeRubyWithSinatra::INPUT_REGEX.match(message) { |m| m[:input].parse_as_inputs }&.join("&")
|
33
|
+
args = {}
|
34
|
+
|
35
|
+
request.path { path }
|
36
|
+
|
37
|
+
%i[query input].each do |k|
|
38
|
+
if v = binding.local_variable_get(k)
|
39
|
+
args[*k] = v
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
request_path.public_send(method, args)
|
44
|
+
end
|