toycol 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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