right_speed 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 +7 -0
- data/.github/workflows/main.yml +35 -0
- data/.gitignore +10 -0
- data/Gemfile +13 -0
- data/LICENSE.txt +21 -0
- data/README.md +56 -0
- data/Rakefile +12 -0
- data/bin/right_speed +106 -0
- data/lib/rack/handler/right_speed.rb +35 -0
- data/lib/right_speed/connection_closer.rb +27 -0
- data/lib/right_speed/const.rb +8 -0
- data/lib/right_speed/env.rb +10 -0
- data/lib/right_speed/handler.rb +193 -0
- data/lib/right_speed/listener.rb +69 -0
- data/lib/right_speed/logger.rb +9 -0
- data/lib/right_speed/processor.rb +121 -0
- data/lib/right_speed/ractor_helper.rb +55 -0
- data/lib/right_speed/server.rb +77 -0
- data/lib/right_speed/version.rb +5 -0
- data/lib/right_speed/worker/accepter.rb +30 -0
- data/lib/right_speed/worker/base.rb +22 -0
- data/lib/right_speed/worker/reader.rb +34 -0
- data/lib/right_speed.rb +8 -0
- data/right_speed.gemspec +34 -0
- metadata +138 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: a21c10b045dfb5591600c16d61a7fa9d09771db4960e0d3c3021b90c8fbdc48c
|
4
|
+
data.tar.gz: a71b06cde0b6b1028aabdab601673d827040824db08d6fce181c9c9816669d74
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 490fb8ff447db76d7cdc16980d547de1b078d9648afe9534804a638abb708d63f572b84a64489707fedad4dfd29caf046b96456b390064ada334a7b14953b556
|
7
|
+
data.tar.gz: 52e3d420c923a0593f463aeff793911000bce92b8855723f02fa7ffc4e3da3744779d264e29f5ee0b4e46f5a63e5570a39c33f4c79d8c1cd3c2f4e718c6315bf
|
@@ -0,0 +1,35 @@
|
|
1
|
+
name: Ruby
|
2
|
+
|
3
|
+
on: [push,pull_request]
|
4
|
+
|
5
|
+
jobs:
|
6
|
+
build:
|
7
|
+
runs-on: ubuntu-latest
|
8
|
+
steps:
|
9
|
+
- uses: actions/checkout@v2
|
10
|
+
- name: Set up Ruby
|
11
|
+
uses: ruby/setup-ruby@v1
|
12
|
+
with:
|
13
|
+
ruby-version: 3.0.2
|
14
|
+
- name: Run the default task
|
15
|
+
run: |
|
16
|
+
gem install bundler
|
17
|
+
bundle install
|
18
|
+
bundle exec rake
|
19
|
+
- name: Run the actual server
|
20
|
+
run: |
|
21
|
+
bundle exec ruby bin/right_speed -c snippets/test.ru &
|
22
|
+
sleep 5
|
23
|
+
output=$(curl -s http://127.0.0.1:8080/)
|
24
|
+
kill %1
|
25
|
+
echo "Output: $output"
|
26
|
+
test "$output" = "Yaaay"
|
27
|
+
- name: Run rackup
|
28
|
+
# Using production not to use middlewares for development (lint, etc)
|
29
|
+
run: |
|
30
|
+
bundle exec rackup snippets/test.ru -s right_speed -E production -O Host=127.0.0.1 -O Port=8081 &
|
31
|
+
sleep 5
|
32
|
+
output=$(curl -s http://127.0.0.1:8081/)
|
33
|
+
kill %1
|
34
|
+
echo "Output: $output"
|
35
|
+
test "$output" = "Yaaay"
|
data/.gitignore
ADDED
data/Gemfile
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
source "https://rubygems.org"
|
4
|
+
|
5
|
+
# Specify your gem's dependencies in right_speed.gemspec
|
6
|
+
gemspec
|
7
|
+
|
8
|
+
gem "rake", "~> 13.0"
|
9
|
+
|
10
|
+
gem "test-unit", "~> 3.0"
|
11
|
+
|
12
|
+
# The updated libraries for ractor-safety
|
13
|
+
gem "http_parser.rb", git: 'https://github.com/tmm1/http_parser.rb.git'
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2021 Satoshi Moris Tagomori
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
# RightSpeed
|
2
|
+
|
3
|
+
RightSpeed is **an experimental application server** to host Rack applications, on Ractor workers, to test/verify that your application is Ractor-safe/Ractor-ready or not.
|
4
|
+
Ractor is an experimental feature of Ruby 3.0, thus **this application server is also not for production environments**.
|
5
|
+
|
6
|
+
Currently, RightSpeed supports the very limited set of Rack protocol specifications. Unsupported features are, for example:
|
7
|
+
|
8
|
+
* Writing logs into files
|
9
|
+
* Daemonizing processes
|
10
|
+
* Reloading applications without downtime
|
11
|
+
* Handling session objects (using `rack.session`)
|
12
|
+
* Handling multipart contents flexisbly (using `rack.multipart.buffer_size` nor `rack.multipart.tempfile_factory`)
|
13
|
+
* [Hijacking](https://github.com/rack/rack/blob/master/SPEC.rdoc#label-Hijacking)
|
14
|
+
|
15
|
+
## Changelog
|
16
|
+
|
17
|
+
* v0.1.0:
|
18
|
+
* The first release just before RubyKaigi Takeout 2021
|
19
|
+
|
20
|
+
## Usage
|
21
|
+
|
22
|
+
Use the latest Ruby 3.x release!
|
23
|
+
|
24
|
+
Install `right_speed` by `gem` command (`gem i right_speed`), then use it directly:
|
25
|
+
|
26
|
+
```
|
27
|
+
$ right_speed -c config.ru -p 8080 --workers 8
|
28
|
+
|
29
|
+
# See right_speed --help for full options:
|
30
|
+
$ right_speed --help
|
31
|
+
Usage: right_speed [options]
|
32
|
+
|
33
|
+
OPTIONS
|
34
|
+
--config, -c PATH The path of the rackup configuration file (default: config.ru)
|
35
|
+
--port, -p PORT The port number to listen (default: 8080)
|
36
|
+
--backlog NUM The number of backlog
|
37
|
+
--workers NUM The number of Ractors (default: CPU cores)
|
38
|
+
--worker-type TYPE The type of workers, available options are read/accept (default: read)
|
39
|
+
--help Show this message
|
40
|
+
```
|
41
|
+
|
42
|
+
Or, use `rackup` with `-s right_speed`:
|
43
|
+
|
44
|
+
```
|
45
|
+
$ rackup config.ru -s right_speed -p 8080 -O Workers=8
|
46
|
+
```
|
47
|
+
|
48
|
+
The default number of worker Ractors is the number of CPU cores.
|
49
|
+
|
50
|
+
## Contributing
|
51
|
+
|
52
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/tagomoris/right_speed.
|
53
|
+
|
54
|
+
## License
|
55
|
+
|
56
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
data/bin/right_speed
ADDED
@@ -0,0 +1,106 @@
|
|
1
|
+
#!ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require_relative "../lib/right_speed/env"
|
5
|
+
require_relative "../lib/right_speed/server"
|
6
|
+
require "getoptlong"
|
7
|
+
|
8
|
+
module RightSpeed
|
9
|
+
module Command
|
10
|
+
Options = Struct.new(
|
11
|
+
:rackup, :port, :backlog,
|
12
|
+
:workers, :worker_type,
|
13
|
+
keyword_init: true,
|
14
|
+
)
|
15
|
+
|
16
|
+
COMMAND_OPTIONS = [
|
17
|
+
['--config', '-c', GetoptLong::REQUIRED_ARGUMENT],
|
18
|
+
['--port', '-p', GetoptLong::REQUIRED_ARGUMENT],
|
19
|
+
['--backlog', GetoptLong::REQUIRED_ARGUMENT],
|
20
|
+
['--workers', GetoptLong::REQUIRED_ARGUMENT],
|
21
|
+
['--worker-type', GetoptLong::REQUIRED_ARGUMENT],
|
22
|
+
['--help', GetoptLong::NO_ARGUMENT],
|
23
|
+
]
|
24
|
+
|
25
|
+
DEFAULT_RACKUP_PATH = 'config.ru'
|
26
|
+
DEFAULT_PORT = Server::DEFAULT_PORT
|
27
|
+
DEFAULT_WORKERS = Env.processors
|
28
|
+
DEFAULT_WORKER_TYPE = Server::DEFAULT_WORKER_TYPE
|
29
|
+
DEFAULT_LISTENER_TYPE = Server::DEFAULT_LISTENER_TYPE
|
30
|
+
|
31
|
+
AVAILABLE_WORKER_TYPES = Server::AVAILABLE_WORKER_TYPES.map(:to_s).join('/')
|
32
|
+
AVAILABLE_LISTENER_TYPES = Server::AVAILABLE_LISTENER_TYPES.map(:to_s).join('/')
|
33
|
+
|
34
|
+
def self.show_help(error: false, error_message: nil)
|
35
|
+
STDERR.puts(error_message, "\n") if error_message
|
36
|
+
STDERR.puts <<~EOS
|
37
|
+
Usage: right_speed [options]
|
38
|
+
|
39
|
+
OPTIONS
|
40
|
+
--config, -c PATH The path of the rackup configuration file (default: #{DEFAULT_RACKUP_PATH})
|
41
|
+
--port, -p PORT The port number to listen (default: #{DEFAULT_PORT})
|
42
|
+
--backlog NUM The number of backlog
|
43
|
+
--workers NUM The number of Ractors (default: CPU cores, #{DEFAULT_WORKERS})
|
44
|
+
--worker-type TYPE The type of workers (available: #{AVAILABLE_WORKER_TYPES}, default: #{DEFAULT_WORKER_TYPE})
|
45
|
+
--help Show this message
|
46
|
+
EOS
|
47
|
+
exit(error ? 1 : 0)
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.integer_value(value, name)
|
51
|
+
Integer(value)
|
52
|
+
rescue
|
53
|
+
show_help(error: true, error_message: "#{name} should be an Integer: #{value}")
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.parse_command_line_options
|
57
|
+
optparse = GetoptLong.new
|
58
|
+
optparse.set_options(*COMMAND_OPTIONS)
|
59
|
+
options = Options.new(
|
60
|
+
rackup: DEFAULT_RACKUP_PATH,
|
61
|
+
port: DEFAULT_PORT,
|
62
|
+
backlog: nil,
|
63
|
+
workers: DEFAULT_WORKERS,
|
64
|
+
worker_type: DEFAULT_WORKER_TYPE,
|
65
|
+
)
|
66
|
+
worker_type = :read
|
67
|
+
optparse.each_option do |name, value|
|
68
|
+
case name
|
69
|
+
when '--config'
|
70
|
+
options.rackup = value
|
71
|
+
when '--port'
|
72
|
+
options.port = integer_value(value, "Port number")
|
73
|
+
when '--backlog'
|
74
|
+
options.backlog = integer_value(value, "Backlog")
|
75
|
+
when '--workers'
|
76
|
+
options.workers = integer_value(value, "Workers")
|
77
|
+
when '--worker-type'
|
78
|
+
options.worker_type = value.to_sym
|
79
|
+
when '--help'
|
80
|
+
show_help
|
81
|
+
else
|
82
|
+
show_help(error: true, error_messsage: "Unknown option: #{name}")
|
83
|
+
end
|
84
|
+
end
|
85
|
+
options
|
86
|
+
end
|
87
|
+
|
88
|
+
def self.start
|
89
|
+
options = parse_command_line_options
|
90
|
+
server = begin
|
91
|
+
RightSpeed::Server.new(
|
92
|
+
port: options.port,
|
93
|
+
app: options.rackup,
|
94
|
+
backlog: options.backlog,
|
95
|
+
workers: options.workers,
|
96
|
+
worker_type: options.worker_type,
|
97
|
+
)
|
98
|
+
rescue => e
|
99
|
+
show_help(error: true, error_message: "Failed to launch the server, " + e.message)
|
100
|
+
end
|
101
|
+
server.run
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
RightSpeed::Command.start
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "right_speed/server"
|
4
|
+
|
5
|
+
module Rack
|
6
|
+
module Handler
|
7
|
+
class RightSpeed
|
8
|
+
def self.run(app, **options)
|
9
|
+
environment = ENV['RACK_ENV'] || 'development'
|
10
|
+
default_host = environment == 'development' ? '127.0.0.1' : '0.0.0.0'
|
11
|
+
|
12
|
+
host = options.delete(:Host) || default_host
|
13
|
+
port = options.delete(:Port) || 8080
|
14
|
+
workers = options.delete(:Workers) || ::RightSpeed::Env.processors
|
15
|
+
server = ::RightSpeed::Server.new(app: app, host: host, port: port, workers: workers)
|
16
|
+
|
17
|
+
yield server if block_given?
|
18
|
+
|
19
|
+
server.run
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.valid_options
|
23
|
+
environment = ENV['RACK_ENV'] || 'development'
|
24
|
+
default_host = environment == 'development' ? '127.0.0.1' : '0.0.0.0'
|
25
|
+
{
|
26
|
+
"Host=HOST" => "Hostname to listen on (default: #{default_host})",
|
27
|
+
"Port=PORT" => "Port to listen on (default: 8080)",
|
28
|
+
"Workers=NUM" => "Number of workers (default: #{::RightSpeed::Env.processors})",
|
29
|
+
}
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
register :right_speed, ::Rack::Handler::RightSpeed
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "logger"
|
4
|
+
|
5
|
+
module RightSpeed
|
6
|
+
class ConnectionCloser
|
7
|
+
def run(workers)
|
8
|
+
@ractor = Ractor.new(workers) do |workers|
|
9
|
+
logger = RightSpeed.logger
|
10
|
+
while workers.size > 0
|
11
|
+
r, conn = Ractor.select(*workers, move: true)
|
12
|
+
if conn == :closing
|
13
|
+
workers.delete(r)
|
14
|
+
next
|
15
|
+
end
|
16
|
+
begin
|
17
|
+
conn.close
|
18
|
+
rescue => e
|
19
|
+
logger.debug { "Error while closing a connection #{conn}, #{e.class}:#{e.message}" }
|
20
|
+
end
|
21
|
+
end
|
22
|
+
rescue => e
|
23
|
+
logger.error { "Unexpected error, #{e.class}:#{e.message}" }
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,193 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require "logger"
|
3
|
+
require "stringio"
|
4
|
+
require "http/parser"
|
5
|
+
require "rack"
|
6
|
+
|
7
|
+
require "pp"
|
8
|
+
|
9
|
+
require_relative "./const"
|
10
|
+
|
11
|
+
module RightSpeed
|
12
|
+
class Handler
|
13
|
+
def initialize(app)
|
14
|
+
@app = app
|
15
|
+
end
|
16
|
+
|
17
|
+
def session(conn)
|
18
|
+
Session.new(self, conn)
|
19
|
+
end
|
20
|
+
|
21
|
+
def process(session, client, request)
|
22
|
+
# https://github.com/rack/rack/blob/master/SPEC.rdoc
|
23
|
+
env = {
|
24
|
+
# TODO: replace the keys using constants: https://github.com/rack/rack/blob/master/lib/rack.rb
|
25
|
+
'HTTP_VERSION' => request.http_version,
|
26
|
+
'PATH_INFO' => request.path_info,
|
27
|
+
'QUERY_STRING' => request.query_string || "",
|
28
|
+
'REMOTE_ADDR' => client.addr,
|
29
|
+
'REQUEST_METHOD' => request.http_method,
|
30
|
+
'REQUEST_PATH' => request.path_info,
|
31
|
+
'REQUEST_URI' => request.request_uri,
|
32
|
+
'SCRIPT_NAME' => "",
|
33
|
+
'SERVER_NAME' => client.server_addr,
|
34
|
+
'SERVER_PORT' => client.server_port.to_s,
|
35
|
+
'SERVER_PROTOCOL' => request.http_version,
|
36
|
+
'SERVER_SOFTWARE' => RightSpeed::SOFTWARE_NAME,
|
37
|
+
**request.headers_in_env_style,
|
38
|
+
### Rack specific keys
|
39
|
+
'rack.version' => RightSpeed::RACK_VERSION,
|
40
|
+
'rack.url_scheme' => 'http', # http or https, depending on the request URL.
|
41
|
+
'rack.input' => request.body, # The input stream.
|
42
|
+
'rack.errors' => $stderr, # The error stream.
|
43
|
+
'rack.multithread' => true,
|
44
|
+
'rack.multiprocess' => false,
|
45
|
+
'rack.run_once' => false,
|
46
|
+
'rack.hijack?' => false, # https://github.com/rack/rack/blob/master/SPEC.rdoc#label-Hijacking
|
47
|
+
### Optional Rack keys
|
48
|
+
## 'rack.session'
|
49
|
+
# A hash like interface for storing request session data.
|
50
|
+
# The store must implement:
|
51
|
+
# store(key, value) (aliased as []=); fetch(key, default = nil) (aliased as []);
|
52
|
+
# delete(key); clear; to_hash (returning unfrozen Hash instance);
|
53
|
+
'rack.logger' => session.logger,
|
54
|
+
# A common object interface for logging messages.
|
55
|
+
# The object must implement:
|
56
|
+
# info(message, &block),debug(message, &block),warn(message, &block),error(message, &block),fatal(message, &block)
|
57
|
+
## 'rack.multipart.buffer_size'
|
58
|
+
# An Integer hint to the multipart parser as to what chunk size to use for reads and writes.
|
59
|
+
## 'rack.multipart.tempfile_factory'
|
60
|
+
# An object responding to #call with two arguments, the filename and content_type given for the multipart form field,
|
61
|
+
# and returning an IO-like object that responds to #<< and optionally #rewind. This factory will be used to instantiate
|
62
|
+
# the tempfile for each multipart form file upload field, rather than the default class of Tempfile.
|
63
|
+
}
|
64
|
+
status, headers, body = @app.call(env)
|
65
|
+
Response.new(http_version: request.http_version, status_code: status, headers: headers, body: body)
|
66
|
+
end
|
67
|
+
|
68
|
+
class Client
|
69
|
+
attr_reader :addr, :port, :server_addr, :server_port
|
70
|
+
|
71
|
+
def initialize(conn)
|
72
|
+
_, @port, _, @addr = conn.peeraddr
|
73
|
+
_, @server_port, _, @server_addr = conn.addr
|
74
|
+
if @server_addr == "::1"
|
75
|
+
@server_addr = "localhost"
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
class Request
|
81
|
+
attr_reader :http_method, :http_version, :request_url, :headers, :body, :path_info, :query_string
|
82
|
+
|
83
|
+
def initialize(client:, http_method:, http_version:, request_url:, headers:, body:)
|
84
|
+
@client = client
|
85
|
+
@http_method = http_method
|
86
|
+
@http_version = "HTTP/" + http_version.map(&:to_s).join(".")
|
87
|
+
@request_url = request_url
|
88
|
+
@headers = headers
|
89
|
+
@body = StringIO.new(body)
|
90
|
+
|
91
|
+
@path_info, @query_string = request_url.split('?')
|
92
|
+
end
|
93
|
+
|
94
|
+
def request_uri
|
95
|
+
"http://#{@client.server_addr}:#{@client.server_port}#{request_url}"
|
96
|
+
end
|
97
|
+
|
98
|
+
def headers_in_env_style
|
99
|
+
headers = {}
|
100
|
+
@headers.each do |key, value|
|
101
|
+
headers["HTTP_" + key.gsub("-", "_").upcase] = value
|
102
|
+
end
|
103
|
+
headers
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
class Response
|
108
|
+
STATUS_MESSAGE_MAP = {
|
109
|
+
200 => "OK",
|
110
|
+
}.freeze
|
111
|
+
|
112
|
+
attr_reader :body
|
113
|
+
|
114
|
+
def initialize(http_version:, status_code:, headers:, body:)
|
115
|
+
@http_version = http_version
|
116
|
+
@status_code = status_code
|
117
|
+
@status_message = STATUS_MESSAGE_MAP.fetch(status_code, "Unknown")
|
118
|
+
@headers = headers
|
119
|
+
@body = body
|
120
|
+
end
|
121
|
+
|
122
|
+
def status
|
123
|
+
"#{@http_version} #{@status_code} #{@status_message}\r\n"
|
124
|
+
end
|
125
|
+
|
126
|
+
def headers
|
127
|
+
@headers.map{|key, value| "#{key}: #{value}\r\n" }.join + "\r\n"
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
class Session
|
132
|
+
READ_CHUNK_LENGTH = 1024
|
133
|
+
|
134
|
+
attr_reader :logger
|
135
|
+
|
136
|
+
def initialize(handler, conn)
|
137
|
+
@logger = RightSpeed.logger
|
138
|
+
@handler = handler
|
139
|
+
@conn = conn
|
140
|
+
@client = Client.new(conn)
|
141
|
+
|
142
|
+
# https://github.com/tmm1/http_parser.rb
|
143
|
+
@parser = Http::Parser.new(self, default_header_value_type: :mixed)
|
144
|
+
@reading = true
|
145
|
+
@method = nil
|
146
|
+
@url = nil
|
147
|
+
@headers = nil
|
148
|
+
@body = String.new
|
149
|
+
end
|
150
|
+
|
151
|
+
# TODO: implement handling of "Connection" and "Keep-Alive"
|
152
|
+
# https://developer.mozilla.org/ja/docs/Web/HTTP/Headers/Connection
|
153
|
+
# https://developer.mozilla.org/ja/docs/Web/HTTP/Headers/Keep-Alive
|
154
|
+
|
155
|
+
def process
|
156
|
+
while @reading && !@conn.eof?
|
157
|
+
@parser << @conn.readpartial(READ_CHUNK_LENGTH)
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
def on_headers_complete(headers)
|
162
|
+
@headers = headers
|
163
|
+
@method = @parser.http_method
|
164
|
+
@url = @parser.request_url
|
165
|
+
end
|
166
|
+
|
167
|
+
def on_body(chunk)
|
168
|
+
@body << chunk
|
169
|
+
end
|
170
|
+
|
171
|
+
def on_message_complete
|
172
|
+
# @logger.debug {
|
173
|
+
# "complete to read the request, headers:#{@headers}, body:#{@body}"
|
174
|
+
# }
|
175
|
+
request = Request.new(
|
176
|
+
client: @client, http_method: @method, http_version: @parser.http_version,
|
177
|
+
request_url: @url, headers: @headers, body: @body
|
178
|
+
)
|
179
|
+
response = @handler.process(self, @client, request)
|
180
|
+
send_response(response)
|
181
|
+
@reading = false
|
182
|
+
end
|
183
|
+
|
184
|
+
def send_response(response)
|
185
|
+
@conn.write response.status
|
186
|
+
@conn.write response.headers
|
187
|
+
response.body.each do |part|
|
188
|
+
@conn.write part
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "logger"
|
4
|
+
|
5
|
+
module RightSpeed
|
6
|
+
module Listener
|
7
|
+
def self.setup(listener_type:, host:, port:, backlog: nil)
|
8
|
+
case listener_type
|
9
|
+
when :roundrobin
|
10
|
+
RoundRobinListener.new(host, port, backlog)
|
11
|
+
else
|
12
|
+
SimpleListener.new(host, port, backlog)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class SimpleListener
|
17
|
+
attr_reader :sock
|
18
|
+
|
19
|
+
def initialize(host, port, backlog)
|
20
|
+
@host = host
|
21
|
+
@port = port
|
22
|
+
@backlog = backlog
|
23
|
+
@sock = nil
|
24
|
+
end
|
25
|
+
|
26
|
+
def run(_processor)
|
27
|
+
@running = true
|
28
|
+
@sock = TCPServer.open(@host, @port)
|
29
|
+
@sock.listen(@backlog) if @backlog
|
30
|
+
@sock
|
31
|
+
end
|
32
|
+
|
33
|
+
def wait
|
34
|
+
# do nothing
|
35
|
+
end
|
36
|
+
|
37
|
+
def stop
|
38
|
+
@running = false
|
39
|
+
if @sock
|
40
|
+
@sock.close rescue nil
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
class RoundRobinListener < SimpleListener
|
46
|
+
def run(processor)
|
47
|
+
@running = true
|
48
|
+
@ractor = Ractor.new(@host, @port, @backlog, processor) do |host, port, backlog, processor|
|
49
|
+
logger = RightSpeed.logger
|
50
|
+
sock = TCPServer.open(host, port)
|
51
|
+
sock.listen(backlog) if backlog
|
52
|
+
logger.info { "listening #{host}:#{port}" }
|
53
|
+
while conn = sock.accept
|
54
|
+
processor.process(conn)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def wait
|
60
|
+
@ractor.take
|
61
|
+
end
|
62
|
+
|
63
|
+
def stop
|
64
|
+
@running = false
|
65
|
+
@ractor = nil # TODO: terminate the Ractor if possible
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
module RightSpeed
|
2
|
+
def self.logger
|
3
|
+
return Ractor.current[:logger] if Ractor.current[:logger]
|
4
|
+
logger = Logger.new($stderr)
|
5
|
+
logger.formatter = lambda {|severity, datetime, progname, msg| "[#{datetime}] #{severity} #{msg}\n" }
|
6
|
+
Ractor.current[:logger] = logger
|
7
|
+
logger
|
8
|
+
end
|
9
|
+
end
|
@@ -0,0 +1,121 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rack/builder'
|
4
|
+
|
5
|
+
require_relative 'worker/accepter'
|
6
|
+
require_relative 'worker/reader'
|
7
|
+
require_relative 'connection_closer'
|
8
|
+
|
9
|
+
module RightSpeed
|
10
|
+
module Processor
|
11
|
+
def self.setup(app:, worker_type:, workers:)
|
12
|
+
app = if app.respond_to?(:call)
|
13
|
+
app
|
14
|
+
elsif app.is_a?(String) # rackup config path
|
15
|
+
build_app(app)
|
16
|
+
else
|
17
|
+
raise "Unexpected app #{app}"
|
18
|
+
end
|
19
|
+
handler = Ractor.make_shareable(Handler.new(app))
|
20
|
+
case worker_type
|
21
|
+
when :read
|
22
|
+
ReadProcessor.new(workers, handler)
|
23
|
+
when :accept
|
24
|
+
AcceptProcessor.new(workers, handler)
|
25
|
+
else
|
26
|
+
raise "Unknown worker type #{worker_type}"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.build_app(ru)
|
31
|
+
app = Rack::Builder.parse_file(ru)
|
32
|
+
if app.respond_to?(:call)
|
33
|
+
app
|
34
|
+
elsif app.is_a?(Array) && app[0].respond_to?(:call)
|
35
|
+
# Rack::Builder returns [app, options] but options will be deprecated
|
36
|
+
app[0]
|
37
|
+
else
|
38
|
+
raise "Failed to build Rack app from #{ru}: #{app}"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
class Base
|
43
|
+
def initialize(workers, handler)
|
44
|
+
raise "BUG: use implementation class"
|
45
|
+
end
|
46
|
+
|
47
|
+
def configure(listener:)
|
48
|
+
raise "BUG: not implemented"
|
49
|
+
end
|
50
|
+
|
51
|
+
def run
|
52
|
+
raise "BUG: not implemented"
|
53
|
+
end
|
54
|
+
|
55
|
+
def process(conn)
|
56
|
+
raise "BUG: not implemented"
|
57
|
+
end
|
58
|
+
|
59
|
+
def wait
|
60
|
+
raise "BUG: not implemented"
|
61
|
+
# ractors.each{|r| r.take}
|
62
|
+
# finalizer.close rescue nil
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
class ReadProcessor < Base
|
67
|
+
def initialize(workers, handler)
|
68
|
+
@worker_num = workers
|
69
|
+
@handler = handler
|
70
|
+
@workers = workers.times.map{|i| Worker::Reader.new(id: i, handler: @handler)}
|
71
|
+
@closer = ConnectionCloser.new
|
72
|
+
@counter = 0
|
73
|
+
end
|
74
|
+
|
75
|
+
def configure(listener:)
|
76
|
+
@listener = listener
|
77
|
+
end
|
78
|
+
|
79
|
+
def run
|
80
|
+
@workers.each{|w| w.run}
|
81
|
+
@closer.run(@workers.map{|w| w.ractor})
|
82
|
+
@listener.run(self)
|
83
|
+
end
|
84
|
+
|
85
|
+
def process(conn)
|
86
|
+
current, @counter = @counter, @counter + 1
|
87
|
+
@workers[current % @worker_num].process(conn)
|
88
|
+
end
|
89
|
+
|
90
|
+
def wait
|
91
|
+
@workers.each{|w| w.wait}
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
class AcceptProcessor < Base
|
96
|
+
def initialize(workers, handler)
|
97
|
+
@worker_num = workers
|
98
|
+
@handler = handler
|
99
|
+
@workers = workers.times.map{|i| Worker::Accepter.new(id: i, handler: @handler) }
|
100
|
+
end
|
101
|
+
|
102
|
+
def configure(listener:)
|
103
|
+
@listener = listener
|
104
|
+
@workers.each do |w|
|
105
|
+
w.configure(listener.sock)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def run
|
110
|
+
@workers.each do |w|
|
111
|
+
w.run
|
112
|
+
end
|
113
|
+
# TODO: connection closer
|
114
|
+
end
|
115
|
+
|
116
|
+
def wait
|
117
|
+
@workers.each{|w| w.wait}
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "uri"
|
4
|
+
require "rack"
|
5
|
+
|
6
|
+
module RightSpeed
|
7
|
+
module RactorHelper
|
8
|
+
def self.uri_hook
|
9
|
+
# Use 3.1.0-dev!
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.rack_hook
|
13
|
+
ip_filter = Ractor.make_shareable(Rack::Request.ip_filter)
|
14
|
+
overwrite_method(Rack::Request::Helpers, :trusted_proxy?) do |ip|
|
15
|
+
ip_filter.call(ip)
|
16
|
+
end
|
17
|
+
overwrite_method(Rack::Request::Helpers, :query_parser, Rack::Utils.default_query_parser)
|
18
|
+
overwrite_const(Rack::ShowExceptions, :TEMPLATE, Rack::ShowExceptions::TEMPLATE)
|
19
|
+
freeze_all_constants(::Rack)
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.freeze_all_constants(mojule, touch_list=[])
|
23
|
+
touch_list << mojule
|
24
|
+
mojule.constants.each do |const_name|
|
25
|
+
const = begin
|
26
|
+
mojule.const_get(const_name)
|
27
|
+
rescue LoadError
|
28
|
+
# ignore unloadable modules (autoload, probably)
|
29
|
+
nil
|
30
|
+
end
|
31
|
+
next unless const
|
32
|
+
if const.is_a?(Module) && !touch_list.include?(const)
|
33
|
+
# not freeze Module/Class because we're going to do monkey patching...
|
34
|
+
freeze_all_constants(const, touch_list)
|
35
|
+
else
|
36
|
+
const.freeze
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.overwrite_method(mojule, name, value=nil, &block)
|
42
|
+
if block_given?
|
43
|
+
mojule.define_method(name, Ractor.make_shareable(block))
|
44
|
+
else
|
45
|
+
v = Ractor.make_shareable(value)
|
46
|
+
mojule.define_method(name, Ractor.make_shareable(->(){ v }))
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.overwrite_const(mojule, name, value)
|
51
|
+
v = Ractor.make_shareable(value)
|
52
|
+
mojule.const_set(name, value)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "socket"
|
4
|
+
require "logger"
|
5
|
+
require "webrick"
|
6
|
+
|
7
|
+
require_relative "processor"
|
8
|
+
require_relative "listener"
|
9
|
+
require_relative "env"
|
10
|
+
require_relative "ractor_helper"
|
11
|
+
|
12
|
+
module RightSpeed
|
13
|
+
CONFIG_HOOK_KEY = 'right_speed_config_hooks'
|
14
|
+
|
15
|
+
class Server
|
16
|
+
DEFAULT_HOST = "127.0.0.1"
|
17
|
+
DEFAULT_PORT = 8080
|
18
|
+
DEFAULT_WORKER_TYPE = :read
|
19
|
+
DEFAULT_WORKERS = Env.processors
|
20
|
+
DEFAULT_SCHEDULER_TYPE = :roundrobin
|
21
|
+
|
22
|
+
AVAILABLE_WORKER_TYPES = [:read, :accept]
|
23
|
+
AVAILABLE_LISTENER_TYPES = [:roundrobin, :fair]
|
24
|
+
|
25
|
+
attr_reader :config_hooks
|
26
|
+
|
27
|
+
def initialize(
|
28
|
+
app:,
|
29
|
+
host: DEFAULT_HOST,
|
30
|
+
port: DEFAULT_PORT,
|
31
|
+
workers: DEFAULT_WORKERS,
|
32
|
+
worker_type: DEFAULT_WORKER_TYPE,
|
33
|
+
scheduler_type: DEFAULT_SCHEDULER_TYPE,
|
34
|
+
backlog: nil
|
35
|
+
)
|
36
|
+
@host = host
|
37
|
+
@port = port
|
38
|
+
@app = app
|
39
|
+
@workers = workers
|
40
|
+
@worker_type = worker_type
|
41
|
+
@listener_type = case @worker_type
|
42
|
+
when :read then scheduler_type
|
43
|
+
else :listen
|
44
|
+
end
|
45
|
+
@backlog = backlog
|
46
|
+
@config_hooks = []
|
47
|
+
@logger = nil
|
48
|
+
end
|
49
|
+
|
50
|
+
def run
|
51
|
+
logger = RightSpeed.logger
|
52
|
+
logger.info { "Start running with #{@workers} workers" }
|
53
|
+
|
54
|
+
hooks = @config_hooks + (Ractor.current[RightSpeed::CONFIG_HOOK_KEY] || [])
|
55
|
+
hooks.each do |hook|
|
56
|
+
if hook.respond_to?(:call)
|
57
|
+
hook.call
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
RactorHelper.uri_hook
|
62
|
+
RactorHelper.rack_hook
|
63
|
+
|
64
|
+
begin
|
65
|
+
processor = Processor.setup(app: @app, worker_type: @worker_type, workers: @workers)
|
66
|
+
listener = Listener.setup(listener_type: @listener_type, host: @host, port: @port, backlog: nil)
|
67
|
+
processor.configure(listener: listener)
|
68
|
+
processor.run
|
69
|
+
listener.wait
|
70
|
+
processor.wait
|
71
|
+
ensure
|
72
|
+
listener.stop rescue nil
|
73
|
+
processor.stop rescue nil
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require_relative 'base'
|
2
|
+
|
3
|
+
module RightSpeed
|
4
|
+
module Worker
|
5
|
+
class Accepter < Base
|
6
|
+
def configure(sock)
|
7
|
+
@sock = sock
|
8
|
+
end
|
9
|
+
|
10
|
+
def run
|
11
|
+
@ractor = Ractor.new(@id, @sock) do |id, sock|
|
12
|
+
while conn = sock.accept
|
13
|
+
begin
|
14
|
+
data = conn.read
|
15
|
+
# TODO: process it
|
16
|
+
logger.info "[read|#{id}] Data: #{data}"
|
17
|
+
conn.write "200 OK"
|
18
|
+
ensure
|
19
|
+
conn.close rescue nil
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def wait
|
26
|
+
@ractor.take
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require_relative "../logger"
|
2
|
+
require_relative "../handler"
|
3
|
+
|
4
|
+
module RightSpeed
|
5
|
+
module Worker
|
6
|
+
class Base
|
7
|
+
def initialize(id:, handler:)
|
8
|
+
@id = id
|
9
|
+
@handler = handler
|
10
|
+
@ractor = nil
|
11
|
+
end
|
12
|
+
|
13
|
+
def ractor
|
14
|
+
@ractor
|
15
|
+
end
|
16
|
+
|
17
|
+
def stop
|
18
|
+
@ractor # TODO: terminate if possible
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require_relative "base"
|
2
|
+
require_relative "../logger"
|
3
|
+
|
4
|
+
module RightSpeed
|
5
|
+
module Worker
|
6
|
+
class Reader < Base
|
7
|
+
def run
|
8
|
+
@ractor = Ractor.new(@id, @handler) do |id, handler|
|
9
|
+
logger = RightSpeed.logger
|
10
|
+
while conn = Ractor.receive
|
11
|
+
begin
|
12
|
+
handler.session(conn).process
|
13
|
+
# TODO: keep-alive?
|
14
|
+
Ractor.yield(conn, move: true) # to yield closing connections to ConnectionCloser
|
15
|
+
rescue => e
|
16
|
+
logger.error { "Unexpected error: #{e.message}\n" + e.backtrace.map{"\t#{_1}\n"}.join }
|
17
|
+
# TODO: print backtrace in better way
|
18
|
+
end
|
19
|
+
end
|
20
|
+
logger.info { "Worker#{id}: Finishing the Ractor" }
|
21
|
+
Ractor.yield(:closing) # to tell the outgoing path will be closed when stopping
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def process(conn)
|
26
|
+
@ractor.send(conn, move: true)
|
27
|
+
end
|
28
|
+
|
29
|
+
def wait
|
30
|
+
@ractor.take
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
data/lib/right_speed.rb
ADDED
data/right_speed.gemspec
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "lib/right_speed/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = "right_speed"
|
7
|
+
spec.version = RightSpeed::VERSION
|
8
|
+
spec.authors = ["Satoshi Moris Tagomori"]
|
9
|
+
spec.email = ["tagomoris@gmail.com"]
|
10
|
+
|
11
|
+
spec.summary = "HTTP server implementation using Ractor"
|
12
|
+
spec.description = "HTTP server, which provides traffic under the support of Ractor"
|
13
|
+
spec.homepage = "https://github.com/tagomoris/right_speed"
|
14
|
+
spec.license = "MIT"
|
15
|
+
spec.required_ruby_version = Gem::Requirement.new(">= 3.0.2")
|
16
|
+
|
17
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
18
|
+
|
19
|
+
# Specify which files should be added to the gem when it is released.
|
20
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
21
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
22
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{\A(?:test|spec|features|snippets)/}) }
|
23
|
+
end
|
24
|
+
spec.bindir = "bin"
|
25
|
+
spec.executables = spec.files.grep(%r{\Abin/}) { |f| File.basename(f) }
|
26
|
+
spec.require_paths = ["lib"]
|
27
|
+
|
28
|
+
spec.add_runtime_dependency "webrick", "~> 1.7"
|
29
|
+
spec.add_runtime_dependency "rack", "~> 2.2"
|
30
|
+
spec.add_runtime_dependency "concurrent-ruby", "~> 1.1"
|
31
|
+
spec.add_runtime_dependency "http_parser.rb", "~> 0.8"
|
32
|
+
|
33
|
+
spec.add_development_dependency "test-unit"
|
34
|
+
end
|
metadata
ADDED
@@ -0,0 +1,138 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: right_speed
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Satoshi Moris Tagomori
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2021-09-09 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: webrick
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.7'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.7'
|
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.2'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '2.2'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: concurrent-ruby
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1.1'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1.1'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: http_parser.rb
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0.8'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0.8'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: test-unit
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
description: HTTP server, which provides traffic under the support of Ractor
|
84
|
+
email:
|
85
|
+
- tagomoris@gmail.com
|
86
|
+
executables:
|
87
|
+
- right_speed
|
88
|
+
extensions: []
|
89
|
+
extra_rdoc_files: []
|
90
|
+
files:
|
91
|
+
- ".github/workflows/main.yml"
|
92
|
+
- ".gitignore"
|
93
|
+
- Gemfile
|
94
|
+
- LICENSE.txt
|
95
|
+
- README.md
|
96
|
+
- Rakefile
|
97
|
+
- bin/right_speed
|
98
|
+
- lib/rack/handler/right_speed.rb
|
99
|
+
- lib/right_speed.rb
|
100
|
+
- lib/right_speed/connection_closer.rb
|
101
|
+
- lib/right_speed/const.rb
|
102
|
+
- lib/right_speed/env.rb
|
103
|
+
- lib/right_speed/handler.rb
|
104
|
+
- lib/right_speed/listener.rb
|
105
|
+
- lib/right_speed/logger.rb
|
106
|
+
- lib/right_speed/processor.rb
|
107
|
+
- lib/right_speed/ractor_helper.rb
|
108
|
+
- lib/right_speed/server.rb
|
109
|
+
- lib/right_speed/version.rb
|
110
|
+
- lib/right_speed/worker/accepter.rb
|
111
|
+
- lib/right_speed/worker/base.rb
|
112
|
+
- lib/right_speed/worker/reader.rb
|
113
|
+
- right_speed.gemspec
|
114
|
+
homepage: https://github.com/tagomoris/right_speed
|
115
|
+
licenses:
|
116
|
+
- MIT
|
117
|
+
metadata:
|
118
|
+
homepage_uri: https://github.com/tagomoris/right_speed
|
119
|
+
post_install_message:
|
120
|
+
rdoc_options: []
|
121
|
+
require_paths:
|
122
|
+
- lib
|
123
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
124
|
+
requirements:
|
125
|
+
- - ">="
|
126
|
+
- !ruby/object:Gem::Version
|
127
|
+
version: 3.0.2
|
128
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
129
|
+
requirements:
|
130
|
+
- - ">="
|
131
|
+
- !ruby/object:Gem::Version
|
132
|
+
version: '0'
|
133
|
+
requirements: []
|
134
|
+
rubygems_version: 3.3.0.dev
|
135
|
+
signing_key:
|
136
|
+
specification_version: 4
|
137
|
+
summary: HTTP server implementation using Ractor
|
138
|
+
test_files: []
|