toycol 0.0.1 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/main.yml +4 -1
- data/.gitignore +1 -0
- data/.rubocop.yml +17 -1
- data/CHANGELOG.md +1 -1
- data/README.md +2 -4
- data/bin/toycol +6 -0
- 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 +5 -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 +9 -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/lib/rack/handler/toycol.rb +28 -0
- data/lib/toycol.rb +19 -1
- data/lib/toycol/client.rb +38 -0
- data/lib/toycol/command.rb +96 -0
- data/lib/toycol/const.rb +81 -0
- data/lib/toycol/helper.rb +27 -0
- data/lib/toycol/protocol.rb +126 -0
- data/lib/toycol/proxy.rb +114 -0
- data/lib/toycol/version.rb +1 -1
- data/toycol.gemspec +3 -6
- metadata +57 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9eb79d412c62ad9b86922964fa583b4fa1ed64b941c74654874b93d385d5665e
|
4
|
+
data.tar.gz: 8d915a6bfc7677096164b7fa773e32fad7669ff0b847eb2306be885899298362
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c988be01719a879b367ea0da7323bc00100d4373f3516ab504e03fb92d26b3c2f0e3fe9e4411f84d139b97100d2e3342cebb4eb2e344674dadcd62ecadbf06bb
|
7
|
+
data.tar.gz: fc5ab9812a9f87006052c22614b6cdae7257db0919c3f95f0f30bba5e238b0787ba2c83ba08eb363089d06df41edd68ff5d048e31fb4116fc1a91bfe7823c872
|
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,10 @@ 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
|
13
16
|
|
14
17
|
Style/StringLiterals:
|
15
18
|
Enabled: true
|
@@ -21,3 +24,16 @@ Style/StringLiteralsInInterpolation:
|
|
21
24
|
|
22
25
|
Layout/LineLength:
|
23
26
|
Max: 120
|
27
|
+
|
28
|
+
Metrics:
|
29
|
+
Exclude:
|
30
|
+
- lib/toycol/proxy.rb
|
31
|
+
|
32
|
+
Metrics/AbcSize:
|
33
|
+
Max: 30
|
34
|
+
|
35
|
+
Metrics/ClassLength:
|
36
|
+
Max: 400
|
37
|
+
|
38
|
+
Metrics/MethodLength:
|
39
|
+
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
|
|
data/bin/toycol
ADDED
@@ -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
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "sinatra/base"
|
4
|
+
require "sinatra/reloader"
|
5
|
+
require "toycol"
|
6
|
+
require_relative "post"
|
7
|
+
|
8
|
+
Toycol::Protocol.use(:safe_ruby_with_sinatra)
|
9
|
+
|
10
|
+
class App < Sinatra::Base
|
11
|
+
set :server, :toycol
|
12
|
+
set :port, 9292
|
13
|
+
|
14
|
+
get "/posts" do
|
15
|
+
@posts = params[:user_id] ? Post.where(user_id: params[:user_id]) : Post.all
|
16
|
+
|
17
|
+
erb :index
|
18
|
+
end
|
19
|
+
|
20
|
+
post "/posts" do
|
21
|
+
Post.new(user_id: params[:user_id], body: params[:body])
|
22
|
+
@posts = Post.all
|
23
|
+
|
24
|
+
erb :index
|
25
|
+
end
|
26
|
+
|
27
|
+
run! if app_file == $PROGRAM_NAME
|
28
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Post
|
4
|
+
@posts ||= []
|
5
|
+
|
6
|
+
class << self
|
7
|
+
def all
|
8
|
+
@posts
|
9
|
+
end
|
10
|
+
|
11
|
+
def where(user_id: nil)
|
12
|
+
@posts.select do |post|
|
13
|
+
post.user_id == Integer(user_id) if user_id
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def insert(record)
|
18
|
+
@posts << record
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
attr_reader :user_id, :body
|
23
|
+
|
24
|
+
def initialize(user_id:, body:)
|
25
|
+
@user_id = Integer(user_id)
|
26
|
+
@body = body
|
27
|
+
|
28
|
+
self.class.insert self
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# Initial records
|
33
|
+
Post.new(user_id: 1, body: "I love Ruby!")
|
34
|
+
Post.new(user_id: 2, body: "I love RubyKaigi!")
|
@@ -0,0 +1,32 @@
|
|
1
|
+
<html>
|
2
|
+
<head>
|
3
|
+
<meta charset="UTF-8">
|
4
|
+
</head>
|
5
|
+
|
6
|
+
<body>
|
7
|
+
<h1>This app is running on Safe Ruby protocol</h1>
|
8
|
+
|
9
|
+
<div>
|
10
|
+
<ul>
|
11
|
+
<% @posts.each do |post| %>
|
12
|
+
<li><%= "User<#{post.user_id}> #{post.body}" %></li>
|
13
|
+
<% end %>
|
14
|
+
</ul>
|
15
|
+
</div>
|
16
|
+
|
17
|
+
<div>
|
18
|
+
<form action='/posts' method='post'>
|
19
|
+
<div>
|
20
|
+
<label>User ID</label>
|
21
|
+
<input type="number" name="user_id">
|
22
|
+
</div>
|
23
|
+
<div>
|
24
|
+
<label>Content</label>
|
25
|
+
<input type="text" name="body">
|
26
|
+
</div>
|
27
|
+
<div>
|
28
|
+
<input type="submit" value="Post">
|
29
|
+
</div>
|
30
|
+
</form>
|
31
|
+
</body>
|
32
|
+
</html>
|
@@ -0,0 +1,15 @@
|
|
1
|
+
Toycol::Protocol.define(:ruby) do |message|
|
2
|
+
using Module.new {
|
3
|
+
refine String do
|
4
|
+
def get
|
5
|
+
Toycol::Protocol.request.path do |message|
|
6
|
+
/['"](?<path>.+)['"]/.match(message)[:path]
|
7
|
+
end
|
8
|
+
|
9
|
+
Toycol::Protocol.request.http_method { |_| "GET" }
|
10
|
+
end
|
11
|
+
end
|
12
|
+
}
|
13
|
+
|
14
|
+
instance_eval message
|
15
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rack"
|
4
|
+
require "toycol"
|
5
|
+
|
6
|
+
Toycol::Protocol.use(: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
|
+
[
|
15
|
+
200,
|
16
|
+
{ "Content-Type" => "text/html" },
|
17
|
+
["I love Ruby!\n", "I've successfully accessed using instance_eval!\n"]
|
18
|
+
]
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
run App.new
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rack/handler"
|
4
|
+
require "rack/handler/puma"
|
5
|
+
|
6
|
+
module Rack
|
7
|
+
module Handler
|
8
|
+
class Toycol
|
9
|
+
def self.run(app, options = {})
|
10
|
+
if (child_pid = fork)
|
11
|
+
environment = ENV["RACK_ENV"] || "development"
|
12
|
+
default_host = environment == "development" ? "localhost" : "0.0.0.0"
|
13
|
+
|
14
|
+
host = options.delete(:Host) || default_host
|
15
|
+
port = options.delete(:Port) || "9292"
|
16
|
+
|
17
|
+
::Toycol::Proxy.new(host, port).start
|
18
|
+
Process.waitpid(child_pid)
|
19
|
+
else
|
20
|
+
puts "Toycol starts Puma in single mode, listening on unix://#{::Toycol::UNIX_SOCKET_PATH}"
|
21
|
+
Rack::Handler::Puma.run(app, **{ Host: ::Toycol::UNIX_SOCKET_PATH, Silent: true })
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
register :toycol, Toycol
|
27
|
+
end
|
28
|
+
end
|
data/lib/toycol.rb
CHANGED
@@ -1,8 +1,26 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "fileutils"
|
4
|
+
|
5
|
+
require_relative "toycol/const"
|
6
|
+
require_relative "toycol/helper"
|
7
|
+
require_relative "toycol/protocol"
|
8
|
+
require_relative "toycol/proxy"
|
9
|
+
require_relative "rack/handler/toycol"
|
10
|
+
|
11
|
+
Dir["#{FileUtils.pwd}/Protocolfile*"].sort.each { |f| load f }
|
12
|
+
|
13
|
+
require_relative "toycol/command"
|
3
14
|
require_relative "toycol/version"
|
4
15
|
|
5
16
|
module Toycol
|
6
17
|
class Error < StandardError; end
|
7
|
-
|
18
|
+
|
19
|
+
class UnauthorizedMethodError < Error; end
|
20
|
+
|
21
|
+
class UnauthorizedRequestError < Error; end
|
22
|
+
|
23
|
+
class UndefinedRequestMethodError < Error; end
|
24
|
+
|
25
|
+
class UnknownStatusCodeError < Error; end
|
8
26
|
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "socket"
|
4
|
+
|
5
|
+
module Toycol
|
6
|
+
class Client
|
7
|
+
@port = 9292
|
8
|
+
CHUNK_SIZE = 1024 * 16
|
9
|
+
|
10
|
+
class << self
|
11
|
+
attr_writer :port
|
12
|
+
|
13
|
+
def execute!(request_message, &block)
|
14
|
+
socket = TCPSocket.new("localhost", @port)
|
15
|
+
socket.write(request_message)
|
16
|
+
puts "[Toycol] Sent request message: #{request_message}\n---"
|
17
|
+
|
18
|
+
response_message = []
|
19
|
+
response_message << socket.readpartial(CHUNK_SIZE) until socket.eof?
|
20
|
+
response_message = response_message.join
|
21
|
+
|
22
|
+
block ||= default_proc
|
23
|
+
block.call(response_message)
|
24
|
+
ensure
|
25
|
+
socket.close
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def default_proc
|
31
|
+
proc do |message|
|
32
|
+
puts "[Toycol] Received response message:\n\n"
|
33
|
+
puts message
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "optparse"
|
4
|
+
require_relative "./client"
|
5
|
+
|
6
|
+
module Toycol
|
7
|
+
class Command
|
8
|
+
class Options
|
9
|
+
class << self
|
10
|
+
def parse!(argv)
|
11
|
+
options = {}
|
12
|
+
option_parser = create_option_parser
|
13
|
+
sub_command_parser = create_sub_command_parser
|
14
|
+
|
15
|
+
begin
|
16
|
+
option_parser.order!(argv)
|
17
|
+
options[:command] = argv.shift
|
18
|
+
options[:request_message] = argv.shift if %w[client c].include? options[:command]
|
19
|
+
|
20
|
+
sub_command_parser[options[:command]].parse!(argv)
|
21
|
+
rescue OptionParser::MissingArgument, OptionParser::InvalidOption, ArgumentError => e
|
22
|
+
abort e.message
|
23
|
+
end
|
24
|
+
|
25
|
+
options
|
26
|
+
end
|
27
|
+
|
28
|
+
def create_option_parser
|
29
|
+
OptionParser.new do |opt|
|
30
|
+
opt.banner = "Usage: #{opt.program_name} [-h|--help] [-v|--version] <command> <args>"
|
31
|
+
display_adding_summary(opt)
|
32
|
+
|
33
|
+
opt.on_head("-h", "--help", "Show this message") do
|
34
|
+
puts opt.help
|
35
|
+
exit
|
36
|
+
end
|
37
|
+
|
38
|
+
opt.on_head("-v", "--version", "Show Toycol version") do
|
39
|
+
opt.version = Toycol::VERSION
|
40
|
+
puts opt.ver
|
41
|
+
exit
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def create_sub_command_parser
|
47
|
+
sub_command_parser = Hash.new { |_k, v| raise ArgumentError, "'#{v}' is not sub command" }
|
48
|
+
sub_command_parser["client"] = client_option_parser
|
49
|
+
sub_command_parser["c"] = client_option_parser
|
50
|
+
sub_command_parser
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def client_option_parser
|
56
|
+
OptionParser.new do |opt|
|
57
|
+
opt.on("-p=PORT_NUMBER", "--port=PORT_NUMBER", "Set port number") do |n|
|
58
|
+
::Toycol::Client.port = n
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def display_adding_summary(opt)
|
64
|
+
opt.separator ""
|
65
|
+
opt.separator "Client command options:"
|
66
|
+
client_command_help_messages.each do |command|
|
67
|
+
opt.separator [opt.summary_indent, command[:name].ljust(31), command[:summary]].join(" ")
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def client_command_help_messages
|
72
|
+
[
|
73
|
+
{ name: "client -p=PORT_NUMBER", summary: "Send request to server" }
|
74
|
+
]
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def self.run(argv)
|
80
|
+
new(argv).execute
|
81
|
+
end
|
82
|
+
|
83
|
+
def initialize(argv)
|
84
|
+
@argv = argv
|
85
|
+
end
|
86
|
+
|
87
|
+
def execute
|
88
|
+
options = Options.parse!(@argv)
|
89
|
+
command = options.delete(:command)
|
90
|
+
|
91
|
+
case command
|
92
|
+
when "client", "c" then ::Toycol::Client.execute!(options[:request_message])
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
data/lib/toycol/const.rb
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Toycol
|
4
|
+
DEFAULT_HTTP_REQUEST_METHODS = %w[
|
5
|
+
GET
|
6
|
+
HEAD
|
7
|
+
POST
|
8
|
+
OPTIONS
|
9
|
+
PUT
|
10
|
+
DELETE
|
11
|
+
TRACE
|
12
|
+
PATCH
|
13
|
+
LINK
|
14
|
+
UNLINK
|
15
|
+
].freeze
|
16
|
+
|
17
|
+
DEFAULT_HTTP_STATUS_CODES = {
|
18
|
+
100 => "Continue",
|
19
|
+
101 => "Switching Protocols",
|
20
|
+
102 => "Processing",
|
21
|
+
200 => "OK",
|
22
|
+
201 => "Created",
|
23
|
+
202 => "Accepted",
|
24
|
+
203 => "Non-Authoritative Information",
|
25
|
+
204 => "No Content",
|
26
|
+
205 => "Reset Content",
|
27
|
+
206 => "Partial Content",
|
28
|
+
207 => "Multi-Status",
|
29
|
+
208 => "Already Reported",
|
30
|
+
226 => "IM Used",
|
31
|
+
300 => "Multiple Choices",
|
32
|
+
301 => "Moved Permanently",
|
33
|
+
302 => "Found",
|
34
|
+
303 => "See Other",
|
35
|
+
304 => "Not Modified",
|
36
|
+
305 => "Use Proxy",
|
37
|
+
307 => "Temporary Redirect",
|
38
|
+
308 => "Permanent Redirect",
|
39
|
+
400 => "Bad Request",
|
40
|
+
401 => "Unauthorized",
|
41
|
+
402 => "Payment Required",
|
42
|
+
403 => "Forbidden",
|
43
|
+
404 => "Not Found",
|
44
|
+
405 => "Method Not Allowed",
|
45
|
+
406 => "Not Acceptable",
|
46
|
+
407 => "Proxy Authentication Required",
|
47
|
+
408 => "Request Timeout",
|
48
|
+
409 => "Conflict",
|
49
|
+
410 => "Gone",
|
50
|
+
411 => "Length Required",
|
51
|
+
412 => "Precondition Failed",
|
52
|
+
413 => "Payload Too Large",
|
53
|
+
414 => "URI Too Long",
|
54
|
+
415 => "Unsupported Media Type",
|
55
|
+
416 => "Range Not Satisfiable",
|
56
|
+
417 => "Expectation Failed",
|
57
|
+
418 => "I'm A Teapot",
|
58
|
+
421 => "Misdirected Request",
|
59
|
+
422 => "Unprocessable Entity",
|
60
|
+
423 => "Locked",
|
61
|
+
424 => "Failed Dependency",
|
62
|
+
426 => "Upgrade Required",
|
63
|
+
428 => "Precondition Required",
|
64
|
+
429 => "Too Many Requests",
|
65
|
+
431 => "Request Header Fields Too Large",
|
66
|
+
451 => "Unavailable For Legal Reasons",
|
67
|
+
500 => "Internal Server Error",
|
68
|
+
501 => "Not Implemented",
|
69
|
+
502 => "Bad Gateway",
|
70
|
+
503 => "Service Unavailable",
|
71
|
+
504 => "Gateway Timeout",
|
72
|
+
505 => "HTTP Version Not Supported",
|
73
|
+
506 => "Variant Also Negotiates",
|
74
|
+
507 => "Insufficient Storage",
|
75
|
+
508 => "Loop Detected",
|
76
|
+
510 => "Not Extended",
|
77
|
+
511 => "Network Authentication Required"
|
78
|
+
}.freeze
|
79
|
+
|
80
|
+
UNIX_SOCKET_PATH = ENV["TOYCOL_SOCKET_PATH"] || "/tmp/toycol.socket"
|
81
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Toycol
|
4
|
+
module Helper
|
5
|
+
private
|
6
|
+
|
7
|
+
def safe_execution!(&block)
|
8
|
+
safe_executionable_tp.enable(&block)
|
9
|
+
end
|
10
|
+
|
11
|
+
def safe_executionable_tp
|
12
|
+
@safe_executionable_tp ||= TracePoint.new(:script_compiled) do |tp|
|
13
|
+
if tp.binding.receiver == Toycol::Protocol && tp.method_id.to_s.match?(unauthorized_methods_regex)
|
14
|
+
raise Toycol::UnauthorizedMethodError, <<~ERROR
|
15
|
+
- Unauthorized method was called!
|
16
|
+
You can't use methods that may cause injections in your protocol.
|
17
|
+
Ex. Kernel.#eval, Kernel.#exec, Kernel.#require and so on.
|
18
|
+
ERROR
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def unauthorized_methods_regex
|
24
|
+
/(.*eval|.*exec|`.+|%x\(|system|open|require|load)/
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,126 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Toycol
|
4
|
+
# This class is for protocol definition and parsing request messages
|
5
|
+
class Protocol
|
6
|
+
@definements = {}
|
7
|
+
@protocol_name = nil
|
8
|
+
@http_status_codes = Toycol::DEFAULT_HTTP_STATUS_CODES.dup
|
9
|
+
@http_request_methods = Toycol::DEFAULT_HTTP_REQUEST_METHODS.dup
|
10
|
+
@custom_status_codes = nil
|
11
|
+
@additional_request_methods = nil
|
12
|
+
|
13
|
+
class << self
|
14
|
+
# For protocol definition
|
15
|
+
def define(protocol_name = nil, &block)
|
16
|
+
@definements[protocol_name] = block
|
17
|
+
end
|
18
|
+
|
19
|
+
# For application which use the protocol
|
20
|
+
def use(protocol_name)
|
21
|
+
@protocol_name = protocol_name
|
22
|
+
end
|
23
|
+
|
24
|
+
# For server which use the protocol
|
25
|
+
def run!(message)
|
26
|
+
@request_message = message.chomp
|
27
|
+
|
28
|
+
return unless (block = @definements[@protocol_name])
|
29
|
+
|
30
|
+
instance_exec(@request_message, &block)
|
31
|
+
end
|
32
|
+
|
33
|
+
# For protocol definition: Define custom status codes
|
34
|
+
if RUBY_VERSION >= "2.7"
|
35
|
+
def custom_status_codes(**custom_status_codes)
|
36
|
+
@custom_status_codes = custom_status_codes
|
37
|
+
end
|
38
|
+
else
|
39
|
+
def custom_status_codes(custom_status_codes)
|
40
|
+
@custom_status_codes = custom_status_codes
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# For protocol definition: Define adding request methods
|
45
|
+
def additional_request_methods(*additional_request_methods)
|
46
|
+
@additional_request_methods = additional_request_methods
|
47
|
+
end
|
48
|
+
|
49
|
+
# For protocol definition: Define how to parse the request message
|
50
|
+
def request
|
51
|
+
@request ||= Class.new do
|
52
|
+
def self.path(&block)
|
53
|
+
@path = block
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.query(&block)
|
57
|
+
@query = block
|
58
|
+
end
|
59
|
+
|
60
|
+
def self.http_method(&block)
|
61
|
+
@http_method = block
|
62
|
+
end
|
63
|
+
|
64
|
+
def self.input(&block)
|
65
|
+
@input = block
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# For server: Get the request path
|
71
|
+
def request_path
|
72
|
+
request_path = request.instance_variable_get("@path").call(request_message)
|
73
|
+
|
74
|
+
raise UnauthorizedRequestError, "This request path is too long" if request_path.size >= 2048
|
75
|
+
|
76
|
+
if request_path.scan(%r{[/\w\d\-_]}).size < request_path.size
|
77
|
+
raise UnauthorizedRequestError,
|
78
|
+
"This request path contains unauthorized character"
|
79
|
+
end
|
80
|
+
|
81
|
+
request_path
|
82
|
+
end
|
83
|
+
|
84
|
+
# For server: Get the request method
|
85
|
+
def request_method
|
86
|
+
@http_request_methods.concat @additional_request_methods if @additional_request_methods
|
87
|
+
request_method = request.instance_variable_get("@http_method").call(request_message)
|
88
|
+
|
89
|
+
unless @http_request_methods.include? request_method
|
90
|
+
raise UndefinedRequestMethodError, "This request method is undefined"
|
91
|
+
end
|
92
|
+
|
93
|
+
request_method
|
94
|
+
end
|
95
|
+
|
96
|
+
# For server: Get the query string
|
97
|
+
def query
|
98
|
+
return unless (parse_query_block = request.instance_variable_get("@query"))
|
99
|
+
|
100
|
+
parse_query_block.call(request_message)
|
101
|
+
end
|
102
|
+
|
103
|
+
# For server: Get the input body
|
104
|
+
def input
|
105
|
+
return unless (parsed_input_block = request.instance_variable_get("@input"))
|
106
|
+
|
107
|
+
parsed_input_block.call(request_message)
|
108
|
+
end
|
109
|
+
|
110
|
+
# For server: Get the message of status code
|
111
|
+
def status_message(status)
|
112
|
+
@http_status_codes.merge!(@custom_status_codes) if @custom_status_codes
|
113
|
+
|
114
|
+
unless (message = @http_status_codes[status])
|
115
|
+
raise UnknownStatusCodeError, "Application returns unknown status code"
|
116
|
+
end
|
117
|
+
|
118
|
+
message
|
119
|
+
end
|
120
|
+
|
121
|
+
private
|
122
|
+
|
123
|
+
attr_reader :request_message
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
data/lib/toycol/proxy.rb
ADDED
@@ -0,0 +1,114 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "socket"
|
4
|
+
|
5
|
+
module Toycol
|
6
|
+
class Proxy
|
7
|
+
include Helper
|
8
|
+
|
9
|
+
def initialize(host, port)
|
10
|
+
@host = host
|
11
|
+
@port = port
|
12
|
+
@request_method = nil
|
13
|
+
@path = nil
|
14
|
+
@query = nil
|
15
|
+
@input = nil
|
16
|
+
@protocol = ::Toycol::Protocol
|
17
|
+
@proxy = TCPServer.new(@host, @port)
|
18
|
+
end
|
19
|
+
|
20
|
+
CHUNK_SIZE = 1024 * 16
|
21
|
+
|
22
|
+
def start
|
23
|
+
puts <<~MESSAGE
|
24
|
+
Toycol is running on #{@host}:#{@port}
|
25
|
+
=> Use Ctrl-C to stop
|
26
|
+
MESSAGE
|
27
|
+
|
28
|
+
loop do
|
29
|
+
trap(:INT) { shutdown }
|
30
|
+
|
31
|
+
@client = @proxy.accept
|
32
|
+
|
33
|
+
while !@client.closed? && !@client.eof?
|
34
|
+
begin
|
35
|
+
request = @client.readpartial(CHUNK_SIZE)
|
36
|
+
puts "[Toycol] Received message: #{request.inspect.chomp}"
|
37
|
+
|
38
|
+
safe_execution! { @protocol.run!(request) }
|
39
|
+
assign_parsed_attributes!
|
40
|
+
|
41
|
+
http_request_message = build_http_request_message
|
42
|
+
puts "[Toycol] Message has been translated to HTTP request message: #{http_request_message.inspect}"
|
43
|
+
transfer_to_server(http_request_message)
|
44
|
+
rescue StandardError => e
|
45
|
+
puts "#{e.class} #{e.message} - closing socket."
|
46
|
+
e.backtrace.each { |l| puts "\t#{l}" }
|
47
|
+
@proxy.close
|
48
|
+
@client.close
|
49
|
+
end
|
50
|
+
end
|
51
|
+
@client.close
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def assign_parsed_attributes!
|
58
|
+
@request_method = @protocol.request_method
|
59
|
+
@path = @protocol.request_path
|
60
|
+
@query = @protocol.query
|
61
|
+
@input = @protocol.input
|
62
|
+
end
|
63
|
+
|
64
|
+
def build_http_request_message
|
65
|
+
request_message = "#{request_line}#{request_header}\r\n"
|
66
|
+
request_message += @input if @input
|
67
|
+
request_message
|
68
|
+
end
|
69
|
+
|
70
|
+
def request_line
|
71
|
+
"#{@request_method} #{request_path} HTTP/1.1\r\n"
|
72
|
+
end
|
73
|
+
|
74
|
+
def request_path
|
75
|
+
return @path unless @request_method == "GET"
|
76
|
+
|
77
|
+
"#{@path}#{"?#{@query}" if @query && !@query.empty?}"
|
78
|
+
end
|
79
|
+
|
80
|
+
def request_header
|
81
|
+
"Content-Length: #{@input&.bytesize || 0}\r\n"
|
82
|
+
end
|
83
|
+
|
84
|
+
def transfer_to_server(request_message)
|
85
|
+
UNIXSocket.open(Toycol::UNIX_SOCKET_PATH) do |server|
|
86
|
+
server.write request_message
|
87
|
+
server.close_write
|
88
|
+
puts "[Toycol] Successed to Send HTTP request message to server"
|
89
|
+
|
90
|
+
response_message = []
|
91
|
+
response_message << server.readpartial(CHUNK_SIZE) until server.eof?
|
92
|
+
response_message = response_message.join
|
93
|
+
puts "[Toycol] Received response message from server: #{response_message.lines.first}"
|
94
|
+
|
95
|
+
_, status_code, status_message = response_message.lines.first.split
|
96
|
+
|
97
|
+
if (custom_message = @protocol.status_message(status_code.to_i)) != status_message
|
98
|
+
response_message = response_message.sub(status_message, custom_message)
|
99
|
+
puts "[Toycol] Status message has been translated to custom status message: #{custom_message}"
|
100
|
+
end
|
101
|
+
|
102
|
+
@client.write response_message
|
103
|
+
@client.close_write
|
104
|
+
puts "[Toycol] Finished to response to client"
|
105
|
+
server.close
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def shutdown
|
110
|
+
puts "[Toycol] Catched SIGINT -> Stop to server"
|
111
|
+
exit
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
data/lib/toycol/version.rb
CHANGED
data/toycol.gemspec
CHANGED
@@ -12,7 +12,7 @@ Gem::Specification.new do |spec|
|
|
12
12
|
spec.description = "Toy Application Protocol framework"
|
13
13
|
spec.homepage = "https://github.com/shioimm/toycol"
|
14
14
|
spec.license = "MIT"
|
15
|
-
spec.required_ruby_version = Gem::Requirement.new(">=
|
15
|
+
spec.required_ruby_version = Gem::Requirement.new(">= 2.6.0")
|
16
16
|
|
17
17
|
spec.metadata["homepage_uri"] = spec.homepage
|
18
18
|
spec.metadata["source_code_uri"] = "https://github.com/shioimm/toycol"
|
@@ -27,9 +27,6 @@ Gem::Specification.new do |spec|
|
|
27
27
|
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
28
28
|
spec.require_paths = ["lib"]
|
29
29
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
# For more information and examples about making a new gem, checkout our
|
34
|
-
# guide at: https://bundler.io/guides/creating_gem.html
|
30
|
+
spec.add_dependency "puma"
|
31
|
+
spec.add_dependency "rack", "~> 2.0"
|
35
32
|
end
|
metadata
CHANGED
@@ -1,15 +1,43 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: toycol
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Misaki Shioi
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-07-
|
12
|
-
dependencies:
|
11
|
+
date: 2021-07-20 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: puma
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rack
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '2.0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '2.0'
|
13
41
|
description: Toy Application Protocol framework
|
14
42
|
email:
|
15
43
|
- shioi.mm@gmail.com
|
@@ -28,7 +56,32 @@ files:
|
|
28
56
|
- Rakefile
|
29
57
|
- bin/console
|
30
58
|
- bin/setup
|
59
|
+
- bin/toycol
|
60
|
+
- examples/duck/Gemfile
|
61
|
+
- examples/duck/Protocolfile.duck
|
62
|
+
- examples/duck/config_duck.ru
|
63
|
+
- examples/rubylike/Gemfile
|
64
|
+
- examples/rubylike/Protocolfile.rubylike
|
65
|
+
- examples/rubylike/config_rubylike.ru
|
66
|
+
- examples/safe_ruby/Gemfile
|
67
|
+
- examples/safe_ruby/Protocolfile.safe_ruby
|
68
|
+
- examples/safe_ruby/config_safe_ruby.ru
|
69
|
+
- examples/safe_ruby_with_sinatra/Gemfile
|
70
|
+
- examples/safe_ruby_with_sinatra/Protocolfile.safe_ruby_with_sinatra
|
71
|
+
- examples/safe_ruby_with_sinatra/app.rb
|
72
|
+
- examples/safe_ruby_with_sinatra/post.rb
|
73
|
+
- examples/safe_ruby_with_sinatra/views/index.erb
|
74
|
+
- examples/unsafe_ruby/Gemfile
|
75
|
+
- examples/unsafe_ruby/Protocolfile.unsafe_ruby
|
76
|
+
- examples/unsafe_ruby/config_unsafe_ruby.ru
|
77
|
+
- lib/rack/handler/toycol.rb
|
31
78
|
- lib/toycol.rb
|
79
|
+
- lib/toycol/client.rb
|
80
|
+
- lib/toycol/command.rb
|
81
|
+
- lib/toycol/const.rb
|
82
|
+
- lib/toycol/helper.rb
|
83
|
+
- lib/toycol/protocol.rb
|
84
|
+
- lib/toycol/proxy.rb
|
32
85
|
- lib/toycol/version.rb
|
33
86
|
- toycol.gemspec
|
34
87
|
homepage: https://github.com/shioimm/toycol
|
@@ -46,7 +99,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
46
99
|
requirements:
|
47
100
|
- - ">="
|
48
101
|
- !ruby/object:Gem::Version
|
49
|
-
version:
|
102
|
+
version: 2.6.0
|
50
103
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
51
104
|
requirements:
|
52
105
|
- - ">="
|