toycol 0.1.0 → 0.3.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/ISSUE_TEMPLATE/bug_report.md +29 -0
- data/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
- data/.github/dependabot.yml +9 -0
- data/.rubocop.yml +9 -0
- data/examples/anonymous/Gemfile +5 -0
- data/examples/anonymous/Protocolfile +13 -0
- data/examples/anonymous/config.ru +21 -0
- data/examples/duck/Protocolfile.duck +2 -4
- data/examples/safe_ruby/Gemfile +1 -0
- data/examples/safe_ruby_with_sinatra/Gemfile +1 -0
- data/{bin → exe}/toycol +0 -0
- data/lib/rack/handler/toycol.rb +51 -14
- data/lib/toycol.rb +1 -0
- data/lib/toycol/command.rb +85 -30
- data/lib/toycol/const.rb +21 -0
- data/lib/toycol/protocol.rb +1 -1
- data/lib/toycol/proxy.rb +4 -2
- data/lib/toycol/server.rb +124 -0
- data/lib/toycol/template_generator.rb +60 -0
- data/lib/toycol/templates/application.txt +22 -0
- data/lib/toycol/templates/protocol.txt +39 -0
- data/lib/toycol/version.rb +1 -1
- data/toycol.gemspec +1 -2
- metadata +15 -18
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a9cd0de8dd5ec5a48e22a49c8198492a96162c9ff922a07c897e3af7cdfe779a
|
4
|
+
data.tar.gz: 938b7f68fef82731d61c1937c2ed53753c1bbb301c49434cc6d538edb6f862f9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 53e62c64e3f418da9e0f7068482531bab41466e6d73d560e8bf74ae900cf2587a61d810365eea15f01f194592c70b563320ffe93f170810e4fa6f443f3a73837
|
7
|
+
data.tar.gz: 112910c06cdb28e5f8690fd18373a2d418a466b075cc0ff793816aed2475acb1ccf5657ae8cfb7b3c321cefc1bde44e5a34fd9b9024f3c20d14047938adf6121
|
@@ -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.
|
@@ -0,0 +1,20 @@
|
|
1
|
+
---
|
2
|
+
name: Feature request
|
3
|
+
about: Suggest an idea for this project
|
4
|
+
title: "[FEATURE]"
|
5
|
+
labels: enhancement
|
6
|
+
assignees: ''
|
7
|
+
|
8
|
+
---
|
9
|
+
|
10
|
+
## Is your feature request related to a problem? Please describe.
|
11
|
+
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
12
|
+
|
13
|
+
## Describe the solution you'd like
|
14
|
+
A clear and concise description of what you want to happen.
|
15
|
+
|
16
|
+
## Describe alternatives you've considered
|
17
|
+
A clear and concise description of any alternative solutions or features you've considered.
|
18
|
+
|
19
|
+
## Additional context
|
20
|
+
Add any other context or screenshots about the feature request here.
|
data/.rubocop.yml
CHANGED
@@ -14,6 +14,10 @@ AllCops:
|
|
14
14
|
Style/Documentation:
|
15
15
|
Enabled: false
|
16
16
|
|
17
|
+
Style/StringConcatenation:
|
18
|
+
Exclude:
|
19
|
+
- lib/toycol/server.rb
|
20
|
+
|
17
21
|
Style/StringLiterals:
|
18
22
|
Enabled: true
|
19
23
|
EnforcedStyle: double_quotes
|
@@ -25,9 +29,14 @@ Style/StringLiteralsInInterpolation:
|
|
25
29
|
Layout/LineLength:
|
26
30
|
Max: 120
|
27
31
|
|
32
|
+
Layout/HashAlignment:
|
33
|
+
Exclude:
|
34
|
+
- lib/toycol/server.rb
|
35
|
+
|
28
36
|
Metrics:
|
29
37
|
Exclude:
|
30
38
|
- lib/toycol/proxy.rb
|
39
|
+
- lib/toycol/server.rb
|
31
40
|
|
32
41
|
Metrics/AbcSize:
|
33
42
|
Max: 30
|
@@ -0,0 +1,13 @@
|
|
1
|
+
Toycol::Protocol.define do
|
2
|
+
request.path do |message|
|
3
|
+
%r{(?<path>\/\w*)}.match(message)[:path]
|
4
|
+
end
|
5
|
+
|
6
|
+
request.query do |message|
|
7
|
+
%r{\?(?<query>.+)}.match(message) { |m| m[:query] }
|
8
|
+
end
|
9
|
+
|
10
|
+
request.http_method do |message|
|
11
|
+
"GET"
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rack"
|
4
|
+
require "toycol"
|
5
|
+
|
6
|
+
Toycol::Protocol.use
|
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
|
+
["This app has no protocol name\n"]
|
16
|
+
]
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
run App.new
|
@@ -1,5 +1,3 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
1
|
Toycol::Protocol.define(:duck) do
|
4
2
|
custom_status_codes(
|
5
3
|
600 => "I'm afraid you are not a duck..."
|
@@ -7,11 +5,11 @@ Toycol::Protocol.define(:duck) do
|
|
7
5
|
additional_request_methods "OTHER"
|
8
6
|
|
9
7
|
request.path do |message|
|
10
|
-
%r{(?<path
|
8
|
+
%r{(?<path>\/\w*)}.match(message)[:path]
|
11
9
|
end
|
12
10
|
|
13
11
|
request.query do |message|
|
14
|
-
|
12
|
+
%r{\<3(?<query>.+)}.match(message) { |m| m[:query] }
|
15
13
|
end
|
16
14
|
|
17
15
|
request.http_method do |message|
|
data/examples/safe_ruby/Gemfile
CHANGED
data/{bin → exe}/toycol
RENAMED
File without changes
|
data/lib/rack/handler/toycol.rb
CHANGED
@@ -1,24 +1,61 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "rack"
|
3
4
|
require "rack/handler"
|
4
|
-
require "rack/handler/puma"
|
5
5
|
|
6
6
|
module Rack
|
7
7
|
module Handler
|
8
8
|
class Toycol
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
host
|
15
|
-
port
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
9
|
+
class << self
|
10
|
+
attr_writer :preferred_background_server, :host, :port
|
11
|
+
|
12
|
+
def run(app, _ = {})
|
13
|
+
@app = app
|
14
|
+
@host ||= ::Toycol::DEFAULT_HOST
|
15
|
+
@port ||= "9292"
|
16
|
+
|
17
|
+
if (child_pid = fork)
|
18
|
+
::Toycol::Proxy.new(@host, @port).start
|
19
|
+
Process.waitpid(child_pid)
|
20
|
+
else
|
21
|
+
run_background_server
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def select_background_server
|
28
|
+
case @preferred_background_server
|
29
|
+
when "puma"
|
30
|
+
return "puma" if puma_requireable?
|
31
|
+
|
32
|
+
raise LoadError, "Puma is not installed in your environment."
|
33
|
+
when nil
|
34
|
+
puma_requireable? ? "puma" : "build_in"
|
35
|
+
else
|
36
|
+
"build_in"
|
37
|
+
end
|
38
|
+
rescue LoadError
|
39
|
+
Process.kill(:INT, Process.ppid)
|
40
|
+
abort
|
41
|
+
end
|
42
|
+
|
43
|
+
def puma_requireable?
|
44
|
+
require "rack/handler/puma"
|
45
|
+
true
|
46
|
+
rescue LoadError
|
47
|
+
false
|
48
|
+
end
|
49
|
+
|
50
|
+
def run_background_server
|
51
|
+
case select_background_server
|
52
|
+
when "puma"
|
53
|
+
puts "Toycol starts Puma in single mode, listening on unix://#{::Toycol::UNIX_SOCKET_PATH}"
|
54
|
+
Rack::Handler::Puma.run(@app, **{ Host: ::Toycol::UNIX_SOCKET_PATH, Silent: true })
|
55
|
+
else
|
56
|
+
puts "Toycol starts build-in server, listening on unix://#{::Toycol::UNIX_SOCKET_PATH}"
|
57
|
+
::Toycol::Server.run(@app, **{ Path: ::Toycol::UNIX_SOCKET_PATH, Port: @port })
|
58
|
+
end
|
22
59
|
end
|
23
60
|
end
|
24
61
|
end
|
data/lib/toycol.rb
CHANGED
@@ -6,6 +6,7 @@ require_relative "toycol/const"
|
|
6
6
|
require_relative "toycol/helper"
|
7
7
|
require_relative "toycol/protocol"
|
8
8
|
require_relative "toycol/proxy"
|
9
|
+
require_relative "toycol/server"
|
9
10
|
require_relative "rack/handler/toycol"
|
10
11
|
|
11
12
|
Dir["#{FileUtils.pwd}/Protocolfile*"].sort.each { |f| load f }
|
data/lib/toycol/command.rb
CHANGED
@@ -2,76 +2,124 @@
|
|
2
2
|
|
3
3
|
require "optparse"
|
4
4
|
require_relative "./client"
|
5
|
+
require_relative "./template_generator"
|
5
6
|
|
6
7
|
module Toycol
|
7
8
|
class Command
|
8
9
|
class Options
|
10
|
+
@options = {}
|
11
|
+
|
9
12
|
class << self
|
10
13
|
def parse!(argv)
|
11
|
-
|
12
|
-
|
13
|
-
sub_command_parser = create_sub_command_parser
|
14
|
+
option_parser = create_option_parser
|
15
|
+
sub_command_option_parser = create_sub_command_option_parser
|
14
16
|
|
15
17
|
begin
|
16
18
|
option_parser.order!(argv)
|
17
|
-
options[:command] = argv.shift
|
18
|
-
options[:request_message] = argv.shift if
|
19
|
-
|
20
|
-
|
19
|
+
@options[:command] = argv.shift
|
20
|
+
@options[:request_message] = argv.shift if request_message?(argv.first)
|
21
|
+
@options[:protocol_name] = argv.shift if protocol_name?(argv.first)
|
22
|
+
sub_command_option_parser[@options[:command]].parse!(argv)
|
21
23
|
rescue OptionParser::MissingArgument, OptionParser::InvalidOption, ArgumentError => e
|
22
24
|
abort e.message
|
23
25
|
end
|
24
26
|
|
25
|
-
options
|
27
|
+
@options
|
26
28
|
end
|
27
29
|
|
28
30
|
def create_option_parser
|
29
31
|
OptionParser.new do |opt|
|
30
|
-
opt.banner = "Usage: #{opt.program_name} [-h|--help] [-v|--version]
|
31
|
-
display_adding_summary(opt)
|
32
|
-
|
33
|
-
opt.on_head("-h", "--help", "Show this message") do
|
34
|
-
puts opt.help
|
35
|
-
exit
|
36
|
-
end
|
32
|
+
opt.banner = "Usage: #{opt.program_name} [-h|--help] [-v|--version] COMMAND [arg...]"
|
37
33
|
|
38
34
|
opt.on_head("-v", "--version", "Show Toycol version") do
|
39
35
|
opt.version = Toycol::VERSION
|
40
36
|
puts opt.ver
|
41
37
|
exit
|
42
38
|
end
|
39
|
+
opt.on_head("-h", "--help", "Show this message") { help_command(opt) }
|
40
|
+
|
41
|
+
opt.separator ""
|
42
|
+
opt.separator "Sub commands:"
|
43
|
+
sub_command_summaries.each do |command|
|
44
|
+
opt.separator [opt.summary_indent, command[:name].ljust(31), command[:summary]].join(" ")
|
45
|
+
end
|
43
46
|
end
|
44
47
|
end
|
45
48
|
|
46
|
-
def
|
49
|
+
def create_sub_command_option_parser
|
47
50
|
sub_command_parser = Hash.new { |_k, v| raise ArgumentError, "'#{v}' is not sub command" }
|
48
|
-
sub_command_parser["client"]
|
49
|
-
sub_command_parser["c"]
|
51
|
+
sub_command_parser["client"] = client_option_parser
|
52
|
+
sub_command_parser["c"] = client_option_parser
|
53
|
+
sub_command_parser["server"] = server_option_parser
|
54
|
+
sub_command_parser["s"] = server_option_parser
|
55
|
+
sub_command_parser["generate"] = generator_option_parser
|
56
|
+
sub_command_parser["g"] = generator_option_parser
|
50
57
|
sub_command_parser
|
51
58
|
end
|
52
59
|
|
53
60
|
private
|
54
61
|
|
62
|
+
def request_message?(arg)
|
63
|
+
%w[client c].include?(@options[:command]) \
|
64
|
+
&& arg != "-p" \
|
65
|
+
&& arg != "-h"
|
66
|
+
end
|
67
|
+
|
68
|
+
def protocol_name?(arg)
|
69
|
+
%w[geberate g].include?(@options[:command]) \
|
70
|
+
&& arg != "-t" \
|
71
|
+
&& arg != "-h"
|
72
|
+
end
|
73
|
+
|
74
|
+
def sub_command_summaries
|
75
|
+
[
|
76
|
+
{ name: "client REQUEST_MESSAGE -p PORT", summary: "Send request message to server" },
|
77
|
+
{ name: "server -u SERVER_NAME", summary: "Start proxy and background server" },
|
78
|
+
{ name: "generate NAME -t TYPE", summary: "Generate new protocol or Rack app" }
|
79
|
+
]
|
80
|
+
end
|
81
|
+
|
55
82
|
def client_option_parser
|
56
83
|
OptionParser.new do |opt|
|
57
|
-
opt.on("-p
|
58
|
-
::Toycol::Client.port =
|
84
|
+
opt.on("-p PORT_NUMBER", "--port PORT_NUMBER", "listen on PORT (default: 9292)") do |port|
|
85
|
+
::Toycol::Client.port = port
|
59
86
|
end
|
87
|
+
|
88
|
+
opt.on_head("-h", "--help", "Show this message") { help_command(opt) }
|
60
89
|
end
|
61
90
|
end
|
62
91
|
|
63
|
-
def
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
92
|
+
def server_option_parser
|
93
|
+
OptionParser.new do |opt|
|
94
|
+
opt.on("-o HOST", "--host HOST", "bind to HOST (default: localhost)") do |host|
|
95
|
+
::Rack::Handler::Toycol.host = host
|
96
|
+
end
|
97
|
+
|
98
|
+
opt.on("-p PORT_NUMBER", "--port PORT_NUMBER", "listen on PORT (default: 9292)") do |port|
|
99
|
+
::Rack::Handler::Toycol.port = port
|
100
|
+
end
|
101
|
+
|
102
|
+
opt.on("-u SERVER_NAME", "--use SERVER_NAME", "switch using SERVER(puma/build_in)") do |server_name|
|
103
|
+
::Rack::Handler::Toycol.preferred_background_server = server_name
|
104
|
+
end
|
105
|
+
|
106
|
+
opt.on_head("-h", "--help", "Show this message") { help_command(opt) }
|
68
107
|
end
|
69
108
|
end
|
70
109
|
|
71
|
-
def
|
72
|
-
|
73
|
-
|
74
|
-
|
110
|
+
def generator_option_parser
|
111
|
+
OptionParser.new do |opt|
|
112
|
+
opt.on("-t TYPE", "--type TYPE", "generate TYPE of template (default: :all)") do |type|
|
113
|
+
@options[:template_type] = type
|
114
|
+
end
|
115
|
+
|
116
|
+
opt.on_head("-h", "--help", "Show this message") { help_command(opt) }
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def help_command(parser)
|
121
|
+
puts parser.help
|
122
|
+
exit
|
75
123
|
end
|
76
124
|
end
|
77
125
|
end
|
@@ -89,7 +137,14 @@ module Toycol
|
|
89
137
|
command = options.delete(:command)
|
90
138
|
|
91
139
|
case command
|
92
|
-
when "client", "c"
|
140
|
+
when "client", "c"
|
141
|
+
::Toycol::Client.execute!(options[:request_message])
|
142
|
+
when "server", "s"
|
143
|
+
ARGV.push("-q", "-s", "toycol")
|
144
|
+
Rack::Server.start
|
145
|
+
when "generate", "g"
|
146
|
+
type = options[:template_type] || "all"
|
147
|
+
::Toycol::TemplateGenerator.generate!(type: type, name: options[:protocol_name])
|
93
148
|
end
|
94
149
|
end
|
95
150
|
end
|
data/lib/toycol/const.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Toycol
|
4
|
+
# For HTTP Protocol
|
4
5
|
DEFAULT_HTTP_REQUEST_METHODS = %w[
|
5
6
|
GET
|
6
7
|
HEAD
|
@@ -77,5 +78,25 @@ module Toycol
|
|
77
78
|
511 => "Network Authentication Required"
|
78
79
|
}.freeze
|
79
80
|
|
81
|
+
# For environment
|
82
|
+
ENVIRONMENT = ENV["RACK_ENV"] || "development"
|
83
|
+
DEFAULT_HOST = ENVIRONMENT == "development" ? "localhost" : "0.0.0.0"
|
84
|
+
|
85
|
+
# For connection from proxy server to app server
|
80
86
|
UNIX_SOCKET_PATH = ENV["TOYCOL_SOCKET_PATH"] || "/tmp/toycol.socket"
|
87
|
+
|
88
|
+
# Rack compartible environment
|
89
|
+
PATH_INFO = "PATH_INFO"
|
90
|
+
QUERY_STRING = "QUERY_STRING"
|
91
|
+
REQUEST_METHOD = "REQUEST_METHOD"
|
92
|
+
SERVER_NAME = "SERVER_NAME"
|
93
|
+
SERVER_PORT = "SERVER_PORT"
|
94
|
+
CONTENT_LENGTH = "CONTENT_LENGTH"
|
95
|
+
RACK_VERSION = "rack.version"
|
96
|
+
RACK_INPUT = "rack.input"
|
97
|
+
RACK_ERRORS = "rack.errors"
|
98
|
+
RACK_MULTITHREAD = "rack.multithread"
|
99
|
+
RACK_MULTIPROCESS = "rack.multiprocess"
|
100
|
+
RACK_RUN_ONCE = "rack.run_once"
|
101
|
+
RACK_URL_SCHEME = "rack.url_scheme"
|
81
102
|
end
|
data/lib/toycol/protocol.rb
CHANGED
data/lib/toycol/proxy.rb
CHANGED
@@ -92,9 +92,11 @@ module Toycol
|
|
92
92
|
response_message = response_message.join
|
93
93
|
puts "[Toycol] Received response message from server: #{response_message.lines.first}"
|
94
94
|
|
95
|
-
|
95
|
+
response_line = response_message.lines.first
|
96
|
+
status_number = response_line[9..11]
|
97
|
+
status_message = response_line[12..].strip
|
96
98
|
|
97
|
-
if (custom_message = @protocol.status_message(
|
99
|
+
if (custom_message = @protocol.status_message(status_number.to_i)) != status_message
|
98
100
|
response_message = response_message.sub(status_message, custom_message)
|
99
101
|
puts "[Toycol] Status message has been translated to custom status message: #{custom_message}"
|
100
102
|
end
|
@@ -0,0 +1,124 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "stringio"
|
4
|
+
|
5
|
+
module Toycol
|
6
|
+
class Server
|
7
|
+
BACKLOG = 1024
|
8
|
+
CHUNK_SIZE = 1024 * 16
|
9
|
+
|
10
|
+
class << self
|
11
|
+
def run(app, **options)
|
12
|
+
new(app, **options).run
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def initialize(app, **options)
|
17
|
+
@app = app
|
18
|
+
@path = options[:Path]
|
19
|
+
@port = options[:Port]
|
20
|
+
@env = default_env
|
21
|
+
@returned_status = nil
|
22
|
+
@returned_headers = nil
|
23
|
+
@returned_body = nil
|
24
|
+
end
|
25
|
+
|
26
|
+
def run
|
27
|
+
verify_file_path!
|
28
|
+
server = UNIXServer.new @path
|
29
|
+
server.listen BACKLOG
|
30
|
+
|
31
|
+
loop do
|
32
|
+
trap(:INT) { exit }
|
33
|
+
|
34
|
+
socket = server.accept
|
35
|
+
|
36
|
+
request_message = []
|
37
|
+
request_message << socket.readpartial(CHUNK_SIZE) until socket.eof?
|
38
|
+
request_message = request_message.join
|
39
|
+
assign_parsed_attributes!(request_message)
|
40
|
+
|
41
|
+
@returned_status, @returned_headers, @returned_body = @app.call(@env)
|
42
|
+
|
43
|
+
socket.puts response_message
|
44
|
+
socket.close_write
|
45
|
+
socket.close
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def default_env
|
52
|
+
{
|
53
|
+
::Toycol::PATH_INFO => "",
|
54
|
+
::Toycol::QUERY_STRING => "",
|
55
|
+
::Toycol::REQUEST_METHOD => "",
|
56
|
+
::Toycol::SERVER_NAME => "toycol_server",
|
57
|
+
::Toycol::SERVER_PORT => @port.to_s,
|
58
|
+
::Toycol::CONTENT_LENGTH => "0",
|
59
|
+
::Toycol::RACK_VERSION => Rack::VERSION,
|
60
|
+
::Toycol::RACK_INPUT => stringio(""),
|
61
|
+
::Toycol::RACK_ERRORS => $stderr,
|
62
|
+
::Toycol::RACK_MULTITHREAD => false,
|
63
|
+
::Toycol::RACK_MULTIPROCESS => false,
|
64
|
+
::Toycol::RACK_RUN_ONCE => false,
|
65
|
+
::Toycol::RACK_URL_SCHEME => "http"
|
66
|
+
}
|
67
|
+
end
|
68
|
+
|
69
|
+
def response_message
|
70
|
+
"#{response_status_code}#{response_headers}\r\n#{response_body}"
|
71
|
+
end
|
72
|
+
|
73
|
+
def response_status_code
|
74
|
+
"HTTP/1.1 #{@returned_status} #{::Toycol::DEFAULT_HTTP_STATUS_CODES[@returned_status.to_i] || "CUSTOM"}\r\n"
|
75
|
+
end
|
76
|
+
|
77
|
+
def response_headers
|
78
|
+
@returned_headers["Content-Length"] = response_body.size unless @returned_headers["Content-Length"]
|
79
|
+
|
80
|
+
@returned_headers.map { |k, v| "#{k}: #{v}" }.join("\r\n") + "\r\n"
|
81
|
+
end
|
82
|
+
|
83
|
+
def response_body
|
84
|
+
res = []
|
85
|
+
@returned_body.each { |body| res << body }
|
86
|
+
res.join
|
87
|
+
end
|
88
|
+
|
89
|
+
def stringio(body = "")
|
90
|
+
StringIO.new(body).set_encoding("ASCII-8BIT")
|
91
|
+
end
|
92
|
+
|
93
|
+
def verify_file_path!
|
94
|
+
return unless File.exist? @path
|
95
|
+
|
96
|
+
begin
|
97
|
+
bound_file = UNIXSocket.new @path
|
98
|
+
rescue SystemCallError, IOError
|
99
|
+
File.unlink @path
|
100
|
+
else
|
101
|
+
bound_file.close
|
102
|
+
raise "[Toycol] Address already in use: #{@path}"
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def assign_parsed_attributes!(request_message)
|
107
|
+
request_line, *request_headers, request_body = request_message.split("\r\n").reject(&:empty?)
|
108
|
+
request_method, request_path, = request_line.split
|
109
|
+
request_path, query_string = request_path.split("?")
|
110
|
+
|
111
|
+
@env[::Toycol::REQUEST_METHOD] = request_method
|
112
|
+
@env[::Toycol::PATH_INFO] = request_path
|
113
|
+
@env[::Toycol::QUERY_STRING] = query_string || ""
|
114
|
+
@env[::Toycol::CONTENT_LENGTH]
|
115
|
+
|
116
|
+
request_headers.each do |request_header|
|
117
|
+
k, v = request_header.split(":").map(&:strip)
|
118
|
+
@env["::Toycol::#{k.tr("-", "_").upcase}"] = v
|
119
|
+
end
|
120
|
+
|
121
|
+
@env[::Toycol::RACK_INPUT] = stringio(request_body)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Toycol
|
4
|
+
class TemplateGenerator
|
5
|
+
class << self
|
6
|
+
def generate!(type:, name:)
|
7
|
+
raise StandardError, "Unknown Type: This type of template can't be generated" unless valid? type
|
8
|
+
|
9
|
+
if type == "all"
|
10
|
+
new(name, "protocol").generate!
|
11
|
+
new(name, "app").generate!
|
12
|
+
else
|
13
|
+
new(name, type).generate!
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def valid?(type)
|
20
|
+
%w[all app protocol].include? type
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def initialize(name, type)
|
25
|
+
@name = name
|
26
|
+
@type = type
|
27
|
+
end
|
28
|
+
|
29
|
+
def generate!
|
30
|
+
raise StandardError, "#{filename} already exists" unless Dir.glob(filename).empty?
|
31
|
+
|
32
|
+
File.open(filename, "w") { |f| f.print template_text_for_new }
|
33
|
+
puts "Generate #{filename} in #{FileUtils.pwd}"
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def filename
|
39
|
+
@filename ||= case @type
|
40
|
+
when "protocol" then "Protocolfile#{@name ? ".#{@name}" : nil}"
|
41
|
+
when "app" then "config#{@name ? "_#{@name}" : nil}.ru"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def template_text_for_new
|
46
|
+
if @name
|
47
|
+
template_text.sub(":PROTOCOL_NAME", ":#{@name}")
|
48
|
+
else
|
49
|
+
template_text.sub("\(:PROTOCOL_NAME\)", "")
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def template_text
|
54
|
+
case @type
|
55
|
+
when "protocol" then File.open("lib/toycol/templates/protocol.txt", "r", &:read)
|
56
|
+
when "app" then File.open("lib/toycol/templates/application.txt", "r", &:read)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require "rack"
|
2
|
+
require "toycol"
|
3
|
+
|
4
|
+
# Specify which protocol to use
|
5
|
+
Toycol::Protocol.use(:PROTOCOL_NAME)
|
6
|
+
|
7
|
+
class App
|
8
|
+
def call(env)
|
9
|
+
# Define your app on request method, request path, request query etc
|
10
|
+
# For example:
|
11
|
+
# case env['REQUEST_METHOD']
|
12
|
+
# when 'GET'
|
13
|
+
# [
|
14
|
+
# 200,
|
15
|
+
# { 'Content-Type' => 'text/html' },
|
16
|
+
# ["Hello, This app is running by :#{name} protocol."]
|
17
|
+
# ]
|
18
|
+
# end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
run App.new
|
@@ -0,0 +1,39 @@
|
|
1
|
+
Toycol::Protocol.define(:PROTOCOL_NAME) do
|
2
|
+
# For example
|
3
|
+
# client would send:
|
4
|
+
# quack, quack /posts<3user_id=1
|
5
|
+
# server would interpret client message:
|
6
|
+
# GET /posts?user_id=1
|
7
|
+
|
8
|
+
|
9
|
+
# [OPTIONAL] You can define your additional request methods:
|
10
|
+
# For example:
|
11
|
+
# additional_request_methods OTHER
|
12
|
+
|
13
|
+
# [OPTIONAL] You can define your own response status code:
|
14
|
+
# For example:
|
15
|
+
# define_status_codes(
|
16
|
+
# 600 => "I'm afraid you are not a duck..."
|
17
|
+
# )
|
18
|
+
|
19
|
+
# [REQUIRED] Define how you parse request path from request message
|
20
|
+
request.path do |message|
|
21
|
+
# For example:
|
22
|
+
# %r{(?<path>\/\w*)}.match(message)[:path]
|
23
|
+
end
|
24
|
+
|
25
|
+
# [REQUIRED] Define how you parse query from request message
|
26
|
+
request.query do |message|
|
27
|
+
# For example:
|
28
|
+
# %r{\<3(?<query>.+)}.match(message) { |m| m[:query] }
|
29
|
+
end
|
30
|
+
|
31
|
+
# [REQUIRED] Define how you parse query from request message
|
32
|
+
request.http_method do |message|
|
33
|
+
# For example:
|
34
|
+
# case message.scan(/quack/).size
|
35
|
+
# when 2 then "GET"
|
36
|
+
# else "OTHER"
|
37
|
+
# end
|
38
|
+
end
|
39
|
+
end
|
data/lib/toycol/version.rb
CHANGED
data/toycol.gemspec
CHANGED
@@ -24,9 +24,8 @@ Gem::Specification.new do |spec|
|
|
24
24
|
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{\A(?:test|spec|features)/}) }
|
25
25
|
end
|
26
26
|
spec.bindir = "exe"
|
27
|
-
spec.executables
|
27
|
+
spec.executables << "toycol"
|
28
28
|
spec.require_paths = ["lib"]
|
29
29
|
|
30
|
-
spec.add_dependency "puma"
|
31
30
|
spec.add_dependency "rack", "~> 2.0"
|
32
31
|
end
|
metadata
CHANGED
@@ -1,29 +1,15 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: toycol
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.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-
|
11
|
+
date: 2021-07-25 00:00:00.000000000 Z
|
12
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
13
|
- !ruby/object:Gem::Dependency
|
28
14
|
name: rack
|
29
15
|
requirement: !ruby/object:Gem::Requirement
|
@@ -41,10 +27,14 @@ dependencies:
|
|
41
27
|
description: Toy Application Protocol framework
|
42
28
|
email:
|
43
29
|
- shioi.mm@gmail.com
|
44
|
-
executables:
|
30
|
+
executables:
|
31
|
+
- toycol
|
45
32
|
extensions: []
|
46
33
|
extra_rdoc_files: []
|
47
34
|
files:
|
35
|
+
- ".github/ISSUE_TEMPLATE/bug_report.md"
|
36
|
+
- ".github/ISSUE_TEMPLATE/feature_request.md"
|
37
|
+
- ".github/dependabot.yml"
|
48
38
|
- ".github/workflows/main.yml"
|
49
39
|
- ".gitignore"
|
50
40
|
- ".rubocop.yml"
|
@@ -56,7 +46,9 @@ files:
|
|
56
46
|
- Rakefile
|
57
47
|
- bin/console
|
58
48
|
- bin/setup
|
59
|
-
-
|
49
|
+
- examples/anonymous/Gemfile
|
50
|
+
- examples/anonymous/Protocolfile
|
51
|
+
- examples/anonymous/config.ru
|
60
52
|
- examples/duck/Gemfile
|
61
53
|
- examples/duck/Protocolfile.duck
|
62
54
|
- examples/duck/config_duck.ru
|
@@ -74,6 +66,7 @@ files:
|
|
74
66
|
- examples/unsafe_ruby/Gemfile
|
75
67
|
- examples/unsafe_ruby/Protocolfile.unsafe_ruby
|
76
68
|
- examples/unsafe_ruby/config_unsafe_ruby.ru
|
69
|
+
- exe/toycol
|
77
70
|
- lib/rack/handler/toycol.rb
|
78
71
|
- lib/toycol.rb
|
79
72
|
- lib/toycol/client.rb
|
@@ -82,6 +75,10 @@ files:
|
|
82
75
|
- lib/toycol/helper.rb
|
83
76
|
- lib/toycol/protocol.rb
|
84
77
|
- lib/toycol/proxy.rb
|
78
|
+
- lib/toycol/server.rb
|
79
|
+
- lib/toycol/template_generator.rb
|
80
|
+
- lib/toycol/templates/application.txt
|
81
|
+
- lib/toycol/templates/protocol.txt
|
85
82
|
- lib/toycol/version.rb
|
86
83
|
- toycol.gemspec
|
87
84
|
homepage: https://github.com/shioimm/toycol
|