toycol 0.0.1 → 0.1.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/.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
|
- - ">="
|