toycol 0.1.0 → 0.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9eb79d412c62ad9b86922964fa583b4fa1ed64b941c74654874b93d385d5665e
4
- data.tar.gz: 8d915a6bfc7677096164b7fa773e32fad7669ff0b847eb2306be885899298362
3
+ metadata.gz: cfdb9a6d989ee0d0939ff53ded3c9603e08326194e6d8546e09ed0e292557022
4
+ data.tar.gz: d9b9779d6c2ca6c01710c6ecd1758384654aac1835ea343345ebc68a155ea8cc
5
5
  SHA512:
6
- metadata.gz: c988be01719a879b367ea0da7323bc00100d4373f3516ab504e03fb92d26b3c2f0e3fe9e4411f84d139b97100d2e3342cebb4eb2e344674dadcd62ecadbf06bb
7
- data.tar.gz: fc5ab9812a9f87006052c22614b6cdae7257db0919c3f95f0f30bba5e238b0787ba2c83ba08eb363089d06df41edd68ff5d048e31fb4116fc1a91bfe7823c872
6
+ metadata.gz: 59d599b9b4c781428193c5574d5c093492875046d57d842ca120195a548c752ab343dee372e6c2042cf82009bb7066978c520f099ba3196d0a788fe60248f8e7
7
+ data.tar.gz: 02033640bf6077a6125578bc6ec453c536639eb8882adbef3fd6eec6f0a2ffbedfe32dc9ef7dbb7deda6d85fafd25cdb006a9baf8e57ac4f4d87289fc38638f5
@@ -0,0 +1,9 @@
1
+ version: 2
2
+ updates:
3
+ - package-ecosystem: bundler
4
+ directory: "/"
5
+ schedule:
6
+ interval: daily
7
+ time: "06:00"
8
+ open-pull-requests-limit: 10
9
+ rebase-strategy: "disabled"
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
@@ -2,4 +2,5 @@
2
2
 
3
3
  source "https://rubygems.org"
4
4
 
5
+ gem "puma"
5
6
  gem "toycol", path: "../../"
@@ -4,6 +4,7 @@ source "https://rubygems.org"
4
4
 
5
5
  # git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
6
6
 
7
+ gem "puma"
7
8
  gem "sinatra"
8
9
  gem "sinatra-contrib"
9
10
  gem "toycol", path: "../../"
data/{bin → exe}/toycol RENAMED
File without changes
@@ -1,24 +1,59 @@
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
- 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 })
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
+ puts "Puma is not installed in your environment."
33
+ raise LoadError
34
+ when nil
35
+ puma_requireable? ? "puma" : "build_in"
36
+ else
37
+ "build_in"
38
+ end
39
+ end
40
+
41
+ def puma_requireable?
42
+ require "rack/handler/puma"
43
+ true
44
+ rescue LoadError
45
+ false
46
+ end
47
+
48
+ def run_background_server
49
+ case select_background_server
50
+ when "puma"
51
+ puts "Toycol starts Puma in single mode, listening on unix://#{::Toycol::UNIX_SOCKET_PATH}"
52
+ Rack::Handler::Puma.run(@app, **{ Host: ::Toycol::UNIX_SOCKET_PATH, Silent: true })
53
+ else
54
+ puts "Toycol starts build-in server, listening on unix://#{::Toycol::UNIX_SOCKET_PATH}"
55
+ ::Toycol::Server.run(@app, **{ Path: ::Toycol::UNIX_SOCKET_PATH, Port: @port })
56
+ end
22
57
  end
23
58
  end
24
59
  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 }
@@ -8,16 +8,15 @@ module Toycol
8
8
  class Options
9
9
  class << self
10
10
  def parse!(argv)
11
- options = {}
12
- option_parser = create_option_parser
13
- sub_command_parser = create_sub_command_parser
11
+ options = {}
12
+ option_parser = create_option_parser
13
+ sub_command_option_parser = create_sub_command_option_parser
14
14
 
15
15
  begin
16
16
  option_parser.order!(argv)
17
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)
18
+ options[:request_message] = argv.shift if %w[client c].include?(options[:command]) && argv.first != "-h"
19
+ sub_command_option_parser[options[:command]].parse!(argv)
21
20
  rescue OptionParser::MissingArgument, OptionParser::InvalidOption, ArgumentError => e
22
21
  abort e.message
23
22
  end
@@ -27,44 +26,66 @@ module Toycol
27
26
 
28
27
  def create_option_parser
29
28
  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
29
+ opt.banner = "Usage: #{opt.program_name} [-h|--help] [-v|--version] COMMAND [arg...]"
37
30
 
38
31
  opt.on_head("-v", "--version", "Show Toycol version") do
39
32
  opt.version = Toycol::VERSION
40
33
  puts opt.ver
41
34
  exit
42
35
  end
36
+ opt.on_head("-h", "--help", "Show this message") { help_command(opt) }
37
+
38
+ opt.separator ""
39
+ opt.separator "Sub commands:"
40
+ sub_command_summaries.each do |command|
41
+ opt.separator [opt.summary_indent, command[:name].ljust(31), command[:summary]].join(" ")
42
+ end
43
43
  end
44
44
  end
45
45
 
46
- def create_sub_command_parser
46
+ def create_sub_command_option_parser
47
47
  sub_command_parser = Hash.new { |_k, v| raise ArgumentError, "'#{v}' is not sub command" }
48
48
  sub_command_parser["client"] = client_option_parser
49
49
  sub_command_parser["c"] = client_option_parser
50
+ sub_command_parser["server"] = server_option_parser
51
+ sub_command_parser["s"] = server_option_parser
50
52
  sub_command_parser
51
53
  end
52
54
 
53
55
  private
54
56
 
57
+ def sub_command_summaries
58
+ [
59
+ { name: "client REQUEST_MESSAGE -p PORT", summary: "Send request message to server" },
60
+ { name: "server -u SERVER_NAME", summary: "Start proxy and background server" }
61
+ ]
62
+ end
63
+
55
64
  def client_option_parser
56
65
  OptionParser.new do |opt|
57
- opt.on("-p=PORT_NUMBER", "--port=PORT_NUMBER", "Set port number") do |n|
58
- ::Toycol::Client.port = n
66
+ opt.on("-p PORT_NUMBER", "--port PORT_NUMBER", "listen on PORT (default: 9292)") do |port|
67
+ ::Toycol::Client.port = port
59
68
  end
69
+
70
+ opt.on_head("-h", "--help", "Show this message") { help_command(opt) }
60
71
  end
61
72
  end
62
73
 
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(" ")
74
+ def server_option_parser
75
+ OptionParser.new do |opt|
76
+ opt.on("-o HOST", "--host HOST", "bind to HOST (default: localhost)") do |host|
77
+ ::Rack::Handler::Toycol.host = host
78
+ end
79
+
80
+ opt.on("-p PORT_NUMBER", "--port PORT_NUMBER", "listen on PORT (default: 9292)") do |port|
81
+ ::Rack::Handler::Toycol.port = port
82
+ end
83
+
84
+ opt.on("-u SERVER_NAME", "--use SERVER_NAME", "switch using SERVER(puma/build_in)") do |server_name|
85
+ ::Rack::Handler::Toycol.preferred_background_server = server_name
86
+ end
87
+
88
+ opt.on_head("-h", "--help", "Show this message") { help_command(opt) }
68
89
  end
69
90
  end
70
91
 
@@ -73,6 +94,17 @@ module Toycol
73
94
  { name: "client -p=PORT_NUMBER", summary: "Send request to server" }
74
95
  ]
75
96
  end
97
+
98
+ def server_command_help_messages
99
+ [
100
+ { name: "server -u=SERVER_NAME", summary: "Start proxy & background server" }
101
+ ]
102
+ end
103
+
104
+ def help_command(parser)
105
+ puts parser.help
106
+ exit
107
+ end
76
108
  end
77
109
  end
78
110
 
@@ -89,7 +121,11 @@ module Toycol
89
121
  command = options.delete(:command)
90
122
 
91
123
  case command
92
- when "client", "c" then ::Toycol::Client.execute!(options[:request_message])
124
+ when "client", "c"
125
+ ::Toycol::Client.execute!(options[:request_message])
126
+ when "server", "s"
127
+ ARGV.push("-q", "-s", "toycol")
128
+ Rack::Server.start
93
129
  end
94
130
  end
95
131
  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/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
- _, status_code, status_message = response_message.lines.first.split
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(status_code.to_i)) != 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,123 @@
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 => "",
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[REQUEST_METHOD] = request_method
112
+ @env[PATH_INFO] = request_path
113
+ @env[QUERY_STRING] = query_string || ""
114
+
115
+ request_headers.each do |request_header|
116
+ k, v = request_header.split(":").map(&:strip)
117
+ @env[k.tr("-", "_").upcase] = v
118
+ end
119
+
120
+ @env[RACK_INPUT] = stringio(request_body)
121
+ end
122
+ end
123
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Toycol
4
- VERSION = "0.1.0"
4
+ VERSION = "0.2.0"
5
5
  end
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 = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
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.1.0
4
+ version: 0.2.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-20 00:00:00.000000000 Z
11
+ date: 2021-07-23 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,12 @@ 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/dependabot.yml"
48
36
  - ".github/workflows/main.yml"
49
37
  - ".gitignore"
50
38
  - ".rubocop.yml"
@@ -56,7 +44,6 @@ files:
56
44
  - Rakefile
57
45
  - bin/console
58
46
  - bin/setup
59
- - bin/toycol
60
47
  - examples/duck/Gemfile
61
48
  - examples/duck/Protocolfile.duck
62
49
  - examples/duck/config_duck.ru
@@ -74,6 +61,7 @@ files:
74
61
  - examples/unsafe_ruby/Gemfile
75
62
  - examples/unsafe_ruby/Protocolfile.unsafe_ruby
76
63
  - examples/unsafe_ruby/config_unsafe_ruby.ru
64
+ - exe/toycol
77
65
  - lib/rack/handler/toycol.rb
78
66
  - lib/toycol.rb
79
67
  - lib/toycol/client.rb
@@ -82,6 +70,7 @@ files:
82
70
  - lib/toycol/helper.rb
83
71
  - lib/toycol/protocol.rb
84
72
  - lib/toycol/proxy.rb
73
+ - lib/toycol/server.rb
85
74
  - lib/toycol/version.rb
86
75
  - toycol.gemspec
87
76
  homepage: https://github.com/shioimm/toycol