ruby_wolf 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/.rspec +2 -0
- data/.travis.yml +5 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +80 -0
- data/Rakefile +6 -0
- data/bin/ruby_wolf +10 -0
- data/lib/ruby_wolf/cli.rb +56 -0
- data/lib/ruby_wolf/configuration.rb +25 -0
- data/lib/ruby_wolf/connection.rb +45 -0
- data/lib/ruby_wolf/handler.rb +84 -0
- data/lib/ruby_wolf/server.rb +79 -0
- data/lib/ruby_wolf/version.rb +3 -0
- data/lib/ruby_wolf/worker.rb +71 -0
- data/lib/ruby_wolf.rb +28 -0
- data/ruby_wolf.gemspec +28 -0
- data/spec/ruby_wolf/cli_spec.rb +115 -0
- data/spec/ruby_wolf/configuration_spec.rb +46 -0
- data/spec/ruby_wolf_spec.rb +7 -0
- data/spec/spec_helper.rb +2 -0
- metadata +148 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 14d13bd7de3c5cca903738aefbe18c1d9e512a49
|
4
|
+
data.tar.gz: e92866e154a80c358571348ebedbcc3fa7c0038b
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 18c7b7288ab955e023beaf8de4a58fd763d68712fbe1b3589280b8c9899ee621a20bab20d1c6c666644597d1522428958a4397b373175c50a37586cf75ebb55b
|
7
|
+
data.tar.gz: 238eadfe11529cae062586465abf71ec3b72df78d4f5cf24a8822265663158c9d2e67d4b132c6682643a7b4309e536d1ff1d61194432809ca80602d84ec61b08
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/CODE_OF_CONDUCT.md
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
# Contributor Covenant Code of Conduct
|
2
|
+
|
3
|
+
## Our Pledge
|
4
|
+
|
5
|
+
In the interest of fostering an open and welcoming environment, we as
|
6
|
+
contributors and maintainers pledge to making participation in our project and
|
7
|
+
our community a harassment-free experience for everyone, regardless of age, body
|
8
|
+
size, disability, ethnicity, gender identity and expression, level of experience,
|
9
|
+
nationality, personal appearance, race, religion, or sexual identity and
|
10
|
+
orientation.
|
11
|
+
|
12
|
+
## Our Standards
|
13
|
+
|
14
|
+
Examples of behavior that contributes to creating a positive environment
|
15
|
+
include:
|
16
|
+
|
17
|
+
* Using welcoming and inclusive language
|
18
|
+
* Being respectful of differing viewpoints and experiences
|
19
|
+
* Gracefully accepting constructive criticism
|
20
|
+
* Focusing on what is best for the community
|
21
|
+
* Showing empathy towards other community members
|
22
|
+
|
23
|
+
Examples of unacceptable behavior by participants include:
|
24
|
+
|
25
|
+
* The use of sexualized language or imagery and unwelcome sexual attention or
|
26
|
+
advances
|
27
|
+
* Trolling, insulting/derogatory comments, and personal or political attacks
|
28
|
+
* Public or private harassment
|
29
|
+
* Publishing others' private information, such as a physical or electronic
|
30
|
+
address, without explicit permission
|
31
|
+
* Other conduct which could reasonably be considered inappropriate in a
|
32
|
+
professional setting
|
33
|
+
|
34
|
+
## Our Responsibilities
|
35
|
+
|
36
|
+
Project maintainers are responsible for clarifying the standards of acceptable
|
37
|
+
behavior and are expected to take appropriate and fair corrective action in
|
38
|
+
response to any instances of unacceptable behavior.
|
39
|
+
|
40
|
+
Project maintainers have the right and responsibility to remove, edit, or
|
41
|
+
reject comments, commits, code, wiki edits, issues, and other contributions
|
42
|
+
that are not aligned to this Code of Conduct, or to ban temporarily or
|
43
|
+
permanently any contributor for other behaviors that they deem inappropriate,
|
44
|
+
threatening, offensive, or harmful.
|
45
|
+
|
46
|
+
## Scope
|
47
|
+
|
48
|
+
This Code of Conduct applies both within project spaces and in public spaces
|
49
|
+
when an individual is representing the project or its community. Examples of
|
50
|
+
representing a project or community include using an official project e-mail
|
51
|
+
address, posting via an official social media account, or acting as an appointed
|
52
|
+
representative at an online or offline event. Representation of a project may be
|
53
|
+
further defined and clarified by project maintainers.
|
54
|
+
|
55
|
+
## Enforcement
|
56
|
+
|
57
|
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
58
|
+
reported by contacting the project team at nguyenquangminh0711@gmail.com. All
|
59
|
+
complaints will be reviewed and investigated and will result in a response that
|
60
|
+
is deemed necessary and appropriate to the circumstances. The project team is
|
61
|
+
obligated to maintain confidentiality with regard to the reporter of an incident.
|
62
|
+
Further details of specific enforcement policies may be posted separately.
|
63
|
+
|
64
|
+
Project maintainers who do not follow or enforce the Code of Conduct in good
|
65
|
+
faith may face temporary or permanent repercussions as determined by other
|
66
|
+
members of the project's leadership.
|
67
|
+
|
68
|
+
## Attribution
|
69
|
+
|
70
|
+
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
71
|
+
available at [http://contributor-covenant.org/version/1/4][version]
|
72
|
+
|
73
|
+
[homepage]: http://contributor-covenant.org
|
74
|
+
[version]: http://contributor-covenant.org/version/1/4/
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2017 Nguyễn Quang Minh
|
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,80 @@
|
|
1
|
+
# RubyWolf
|
2
|
+
|
3
|
+
Ruby wolf is a tiny ruby web server for rack-based application. This server follows pre-forked and event driven with kqueue / epoll approach. Honestly, this web server is written for study and research purpose. I'm sure it could be used anywhere. So, don't use it in real world :)
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
gem 'ruby_wolf'
|
11
|
+
```
|
12
|
+
|
13
|
+
And then execute:
|
14
|
+
|
15
|
+
$ bundle
|
16
|
+
|
17
|
+
Or install it yourself as:
|
18
|
+
|
19
|
+
$ gem install ruby_wolf
|
20
|
+
|
21
|
+
## Usage
|
22
|
+
|
23
|
+
Start your rack-based application with the following command:
|
24
|
+
|
25
|
+
`ruby_wolf -p 3000`
|
26
|
+
|
27
|
+
To explore the provided options, please use `ruby_wolf --help`
|
28
|
+
|
29
|
+
## Benchmark
|
30
|
+
|
31
|
+
Benchmark with some Hello world application:
|
32
|
+
|
33
|
+
### RubyWolf
|
34
|
+
|
35
|
+
```
|
36
|
+
50% 7
|
37
|
+
66% 11
|
38
|
+
75% 14
|
39
|
+
80% 15
|
40
|
+
90% 19
|
41
|
+
95% 24
|
42
|
+
98% 31
|
43
|
+
99% 33
|
44
|
+
100% 51 (longest request)
|
45
|
+
```
|
46
|
+
|
47
|
+
### Puma
|
48
|
+
|
49
|
+
```
|
50
|
+
50% 25
|
51
|
+
66% 27
|
52
|
+
75% 28
|
53
|
+
80% 29
|
54
|
+
90% 31
|
55
|
+
95% 32
|
56
|
+
98% 35
|
57
|
+
99% 37
|
58
|
+
100% 39 (longest request)
|
59
|
+
```
|
60
|
+
|
61
|
+
### Thin
|
62
|
+
|
63
|
+
```
|
64
|
+
50% 22
|
65
|
+
66% 23
|
66
|
+
75% 24
|
67
|
+
80% 24
|
68
|
+
90% 28
|
69
|
+
95% 30
|
70
|
+
98% 34
|
71
|
+
99% 36
|
72
|
+
100% 227 (longest request)
|
73
|
+
```
|
74
|
+
|
75
|
+
Note that Hello world application is not considered to be a real application. Thus this benchmark doesn't mean much
|
76
|
+
|
77
|
+
## License
|
78
|
+
|
79
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
80
|
+
|
data/Rakefile
ADDED
data/bin/ruby_wolf
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
|
3
|
+
module RubyWolf
|
4
|
+
class CLI
|
5
|
+
attr_reader :app, :server, :configs
|
6
|
+
|
7
|
+
def initialize(args)
|
8
|
+
@args = args
|
9
|
+
@configs = RubyWolf::Configuration.new
|
10
|
+
@app_root = `pwd`.to_s.strip
|
11
|
+
end
|
12
|
+
|
13
|
+
def run
|
14
|
+
parse_options
|
15
|
+
raise 'Rack file not found' unless File.exist?(rack_file)
|
16
|
+
|
17
|
+
@server = RubyWolf::Server.new(rack_file, configs)
|
18
|
+
@server.start
|
19
|
+
end
|
20
|
+
|
21
|
+
def parse_options
|
22
|
+
opt_parser = OptionParser.new do |opts|
|
23
|
+
opts.banner = 'Usage: ruby_wolf [options]'
|
24
|
+
|
25
|
+
opts.on('-d', '--daemon', 'Demonize this web server to run background') do
|
26
|
+
@configs[:daemon] = true
|
27
|
+
end
|
28
|
+
|
29
|
+
opts.on('-h HOST', '--port=HOST', 'Binding host') do |arg|
|
30
|
+
@configs[:host] = arg
|
31
|
+
end
|
32
|
+
|
33
|
+
opts.on('-p PORT', '--port=PORT', 'Port of the program') do |arg|
|
34
|
+
@configs[:port] = arg.to_i
|
35
|
+
end
|
36
|
+
|
37
|
+
opts.on('-w WORKER', '--worker=WORKER', 'Number of worker processes') do |arg|
|
38
|
+
@configs[:worker] = arg.to_i
|
39
|
+
end
|
40
|
+
|
41
|
+
opts.on('-h', '--help', 'Show the usages') do
|
42
|
+
puts opts
|
43
|
+
exit
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
opt_parser.parse!(@args)
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def rack_file
|
53
|
+
"#{@app_root}/config.ru"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module RubyWolf
|
2
|
+
class Configuration < BasicObject
|
3
|
+
DEFAULT_DAEMON = false
|
4
|
+
DEFAULT_HOST = '0.0.0.0'.freeze
|
5
|
+
DEFAULT_PORT = 3000
|
6
|
+
DEFAULT_WORKER = 4
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@configs = {
|
10
|
+
daemon: DEFAULT_DAEMON,
|
11
|
+
worker: DEFAULT_WORKER,
|
12
|
+
host: DEFAULT_HOST,
|
13
|
+
port: DEFAULT_PORT
|
14
|
+
}
|
15
|
+
end
|
16
|
+
|
17
|
+
def []=(key, value)
|
18
|
+
@configs[key] = value
|
19
|
+
end
|
20
|
+
|
21
|
+
def [](key)
|
22
|
+
@configs[key]
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module RubyWolf
|
2
|
+
class Connection
|
3
|
+
attr_reader :socket, :read_data, :write_data
|
4
|
+
|
5
|
+
def initialize(socket)
|
6
|
+
@socket = socket
|
7
|
+
@read_data = ''
|
8
|
+
@write_data = ''
|
9
|
+
|
10
|
+
@reading = true
|
11
|
+
end
|
12
|
+
|
13
|
+
def need_to_read?
|
14
|
+
@reading
|
15
|
+
end
|
16
|
+
|
17
|
+
def read
|
18
|
+
@read_data << socket.read_nonblock(RubyWolf::READ_SIZE)
|
19
|
+
@reading = false if @read_data.end_with?(RubyWolf::CRLF)
|
20
|
+
rescue EOFError
|
21
|
+
@reading = false
|
22
|
+
end
|
23
|
+
|
24
|
+
def enqueue_write(data)
|
25
|
+
@write_data += data
|
26
|
+
end
|
27
|
+
|
28
|
+
def write
|
29
|
+
writen = socket.write_nonblock(@write_data)
|
30
|
+
@write_data[0..writen] = ''
|
31
|
+
end
|
32
|
+
|
33
|
+
def need_to_write?
|
34
|
+
!@write_data.length.zero?
|
35
|
+
end
|
36
|
+
|
37
|
+
def to_io
|
38
|
+
@socket
|
39
|
+
end
|
40
|
+
|
41
|
+
def close
|
42
|
+
@socket.close
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
require 'http/parser'
|
2
|
+
|
3
|
+
module RubyWolf
|
4
|
+
class Handler
|
5
|
+
|
6
|
+
attr_reader :app, :env, :connection, :response, :callback
|
7
|
+
|
8
|
+
def initialize(app, connection, &callback)
|
9
|
+
@app = app
|
10
|
+
@connection = connection
|
11
|
+
@callback = callback
|
12
|
+
@env = {}
|
13
|
+
@response = ''
|
14
|
+
end
|
15
|
+
|
16
|
+
def process
|
17
|
+
prepare_rack_env
|
18
|
+
parse_request
|
19
|
+
generate_response
|
20
|
+
callback.call(response) if callback
|
21
|
+
response
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def prepare_rack_env
|
27
|
+
@env = ENV.to_h
|
28
|
+
@env.delete 'HTTP_CONTENT_LENGTH'
|
29
|
+
url_scheme = %w(yes on 1).include?(ENV[::Rack::HTTPS]) ? 'https' : 'http'
|
30
|
+
@env.update(
|
31
|
+
::Rack::RACK_VERSION => ::Rack::VERSION,
|
32
|
+
::Rack::RACK_INPUT => STDIN,
|
33
|
+
::Rack::RACK_ERRORS => STDERR,
|
34
|
+
::Rack::RACK_MULTITHREAD => false,
|
35
|
+
::Rack::RACK_MULTIPROCESS => true,
|
36
|
+
::Rack::RACK_RUNONCE => true,
|
37
|
+
::Rack::RACK_URL_SCHEME => url_scheme,
|
38
|
+
::Rack::SERVER_PROTOCOL => 'HTTP/1.1'
|
39
|
+
)
|
40
|
+
end
|
41
|
+
|
42
|
+
def parse_request
|
43
|
+
parser = Http::Parser.new
|
44
|
+
parser.on_headers_complete = proc do
|
45
|
+
env[::Rack::HTTP_VERSION] = parser.http_version
|
46
|
+
env[::Rack::REQUEST_METHOD] = parser.http_method
|
47
|
+
|
48
|
+
uri = URI.parse(parser.request_url)
|
49
|
+
env[::Rack::REQUEST_PATH] = uri.path
|
50
|
+
env[::Rack::QUERY_STRING] = uri.query
|
51
|
+
end
|
52
|
+
parser << connection.read_data
|
53
|
+
RubyWolf.log(
|
54
|
+
[
|
55
|
+
"HTTP/#{env[::Rack::HTTP_VERSION].join('.')}",
|
56
|
+
env[::Rack::REQUEST_METHOD],
|
57
|
+
"#{env[::Rack::REQUEST_PATH]}?#{env[::Rack::QUERY_STRING]}"
|
58
|
+
].join(' ')
|
59
|
+
)
|
60
|
+
end
|
61
|
+
|
62
|
+
def generate_response
|
63
|
+
status, headers, body = app.call(env)
|
64
|
+
RubyWolf.log(
|
65
|
+
"Response #{env[::Rack::SERVER_PROTOCOL]} #{status}"
|
66
|
+
)
|
67
|
+
compose_response(status, headers, body)
|
68
|
+
end
|
69
|
+
|
70
|
+
def compose_response(status, headers, body)
|
71
|
+
@response += "#{env[::Rack::SERVER_PROTOCOL]} #{status} #{RubyWolf::CRLF}"
|
72
|
+
headers.each do |key, value|
|
73
|
+
@response += "#{key}: #{value}#{RubyWolf::CRLF}"
|
74
|
+
end
|
75
|
+
|
76
|
+
@response += RubyWolf::CRLF
|
77
|
+
body.each do |part|
|
78
|
+
@response += part
|
79
|
+
end
|
80
|
+
ensure
|
81
|
+
body.close if body.respond_to? :close
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
require 'logger'
|
2
|
+
|
3
|
+
module RubyWolf
|
4
|
+
class Server
|
5
|
+
attr_reader :app, :configs, :socket, :workers
|
6
|
+
|
7
|
+
def initialize(rack_file, configs)
|
8
|
+
@rack_file = rack_file
|
9
|
+
@configs = configs
|
10
|
+
@workers = []
|
11
|
+
end
|
12
|
+
|
13
|
+
def start
|
14
|
+
trap_signal
|
15
|
+
setup_rack
|
16
|
+
setup_socket
|
17
|
+
configs[:worker].times do
|
18
|
+
workers << RubyWolf::Worker.new(self)
|
19
|
+
end
|
20
|
+
Process.detach if configs[:daemon]
|
21
|
+
workers.each(&:start)
|
22
|
+
handle_loop
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def setup_rack
|
28
|
+
RubyWolf.log('~~~ Ruby Wolf ~~~')
|
29
|
+
RubyWolf.log('Loading Rack application')
|
30
|
+
@app, _rack_options = ::Rack::Builder.parse_file(@rack_file)
|
31
|
+
end
|
32
|
+
|
33
|
+
def setup_socket
|
34
|
+
@socket = TCPServer.new(configs[:host], configs[:port])
|
35
|
+
RubyWolf.log("Server is running on #{configs[:host]}:#{configs[:port]}")
|
36
|
+
RubyWolf.log("Process pid is #{Process.pid}")
|
37
|
+
RubyWolf.log("Number of worker: #{configs[:worker]}")
|
38
|
+
end
|
39
|
+
|
40
|
+
def handle_loop
|
41
|
+
while stopped_pid = Process.wait do
|
42
|
+
stopped_worker = workers.find { |w| w.pid == stopped_pid }
|
43
|
+
next unless stopped_worker
|
44
|
+
|
45
|
+
RubyWolf.log("Worker with pid #{stopped_pid} suddenly stopped", :error)
|
46
|
+
|
47
|
+
sleep(1)
|
48
|
+
worker = RubyWolf::Worker.new(self)
|
49
|
+
worker.start
|
50
|
+
|
51
|
+
workers << worker
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def trap_signal
|
56
|
+
Signal.trap(:INT) do
|
57
|
+
if RubyWolf::MAIN_PID == Process.pid
|
58
|
+
puts "Stopping server\n"
|
59
|
+
else
|
60
|
+
puts "Stopping worker #{Process.pid} \n"
|
61
|
+
end
|
62
|
+
exit
|
63
|
+
end
|
64
|
+
|
65
|
+
Signal.trap(:TERM) do
|
66
|
+
if RubyWolf::MAIN_PID == Process.pid
|
67
|
+
puts "Stopping server\n"
|
68
|
+
workers.each do |w|
|
69
|
+
Process.kill(:TERM, w.pid)
|
70
|
+
end
|
71
|
+
else
|
72
|
+
puts "Stopping worker #{Process.pid} \n"
|
73
|
+
end
|
74
|
+
sleep 1
|
75
|
+
exit
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
module RubyWolf
|
2
|
+
class Worker
|
3
|
+
attr_reader :pid, :server, :app, :socket, :connections
|
4
|
+
|
5
|
+
def initialize(server)
|
6
|
+
@server = server
|
7
|
+
@app = server.app
|
8
|
+
@socket = server.socket
|
9
|
+
@connections = []
|
10
|
+
end
|
11
|
+
|
12
|
+
def start
|
13
|
+
@pid = fork do
|
14
|
+
RubyWolf.log('Worker is ready')
|
15
|
+
handle_loop
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def handle_loop
|
22
|
+
loop do
|
23
|
+
need_to_read = connections.select(&:need_to_read?)
|
24
|
+
need_to_write = connections.select(&:need_to_write?)
|
25
|
+
|
26
|
+
ready_to_read, ready_to_write, = IO.select(
|
27
|
+
need_to_read + [socket],
|
28
|
+
need_to_write
|
29
|
+
)
|
30
|
+
|
31
|
+
handle_read(ready_to_read)
|
32
|
+
handle_write(ready_to_write)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def handle_read(ready_to_read)
|
37
|
+
ready_to_read.each do |connection|
|
38
|
+
if connection == socket
|
39
|
+
accept_connection
|
40
|
+
else
|
41
|
+
connection.read
|
42
|
+
handle_request(connection) unless connection.need_to_read?
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def handle_write(ready_to_write)
|
48
|
+
ready_to_write.each do |connection|
|
49
|
+
connection.write
|
50
|
+
close_connection(connection) unless connection.need_to_write?
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def handle_request(connection)
|
55
|
+
handler = RubyWolf::Handler.new(app, connection) do |response|
|
56
|
+
connection.enqueue_write(response)
|
57
|
+
end
|
58
|
+
handler.process
|
59
|
+
end
|
60
|
+
|
61
|
+
def accept_connection
|
62
|
+
@connections << RubyWolf::Connection.new(socket.accept_nonblock)
|
63
|
+
rescue IO::WaitReadable, Errno::EINTR
|
64
|
+
end
|
65
|
+
|
66
|
+
def close_connection(connection)
|
67
|
+
connection.close
|
68
|
+
@connections.delete(connection)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
data/lib/ruby_wolf.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'byebug'
|
2
|
+
require 'rack'
|
3
|
+
|
4
|
+
require 'ruby_wolf/version'
|
5
|
+
require 'ruby_wolf/configuration'
|
6
|
+
require 'ruby_wolf/connection'
|
7
|
+
require 'ruby_wolf/handler'
|
8
|
+
require 'ruby_wolf/server'
|
9
|
+
require 'ruby_wolf/worker'
|
10
|
+
require 'ruby_wolf/cli'
|
11
|
+
|
12
|
+
module RubyWolf
|
13
|
+
MAIN_PID = Process.pid
|
14
|
+
CRLF = "\r\n".freeze
|
15
|
+
READ_SIZE = 16 * 1024
|
16
|
+
|
17
|
+
def self.logger
|
18
|
+
@logger ||= Logger.new(STDOUT)
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.log(content, mode = :info)
|
22
|
+
contents = content.to_s.split("\n")
|
23
|
+
object = Process.pid == MAIN_PID ? '[Main]' : "[Worker #{Process.pid}]"
|
24
|
+
contents.each do |line|
|
25
|
+
logger.send(mode, "#{object} #{line}")
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
data/ruby_wolf.gemspec
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'ruby_wolf/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'ruby_wolf'
|
8
|
+
spec.version = RubyWolf::VERSION
|
9
|
+
spec.authors = ['Nguyễn Quang Minh']
|
10
|
+
spec.email = ['nguyenquangminh0711@gmail.com']
|
11
|
+
|
12
|
+
spec.summary = 'A simple ruby web server using pre-fork and event loop'
|
13
|
+
spec.description = 'My simple implementation of Rack web server using pre-fork and event loop'
|
14
|
+
spec.homepage = 'https://github.com/nguyenquangminh0711/ruby_wolf'
|
15
|
+
|
16
|
+
spec.license = 'MIT'
|
17
|
+
|
18
|
+
spec.files = `git ls-files`.split($/)
|
19
|
+
spec.bindir = 'bin'
|
20
|
+
spec.executables = ['ruby_wolf']
|
21
|
+
spec.require_paths = ['lib']
|
22
|
+
|
23
|
+
spec.add_development_dependency 'rspec', '~>3.5.0', '>=3.5.0'
|
24
|
+
spec.add_development_dependency 'byebug', '~>9.0.0', '>=8.0.0'
|
25
|
+
|
26
|
+
spec.add_runtime_dependency 'rack', '~>2.0.0', '>=2.0.0'
|
27
|
+
spec.add_runtime_dependency 'http_parser.rb', '~>0.6.0', '>=0.6.0'
|
28
|
+
end
|
@@ -0,0 +1,115 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe RubyWolf::CLI do
|
4
|
+
let(:cli) { RubyWolf::CLI.new(args) }
|
5
|
+
|
6
|
+
before { cli.parse_options }
|
7
|
+
|
8
|
+
describe '#run' do
|
9
|
+
let(:args) { ['-d'] }
|
10
|
+
let(:app_double) { double }
|
11
|
+
|
12
|
+
context 'Rack file found' do
|
13
|
+
before do
|
14
|
+
allow(File).to receive(:exist?).and_return(true)
|
15
|
+
allow(::Rack::Builder).to receive(:parse_file).and_return(app_double)
|
16
|
+
allow_any_instance_of(RubyWolf::Server).to receive(:start)
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'creates Rack app from the rack file' do
|
20
|
+
cli.run
|
21
|
+
expect(cli.app).to eql(app_double)
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'starts ruby wolf server' do
|
25
|
+
expect_any_instance_of(RubyWolf::Server).to receive(:start)
|
26
|
+
cli.run
|
27
|
+
expect(cli.server).to be_a(RubyWolf::Server)
|
28
|
+
expect(cli.server.app).to eql(app_double)
|
29
|
+
expect(cli.server.configs).to eq(cli.configs)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
context 'Rack file not found' do
|
34
|
+
before do
|
35
|
+
allow(File).to receive(:exist?).and_return(false)
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'raises exception' do
|
39
|
+
expect do
|
40
|
+
cli.run
|
41
|
+
end.to raise_error(/rack file not found/i)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
describe '#parse_options' do
|
47
|
+
describe 'daemon option' do
|
48
|
+
context 'default daemon' do
|
49
|
+
let(:args) { [] }
|
50
|
+
it { expect(cli.configs[:daemon]).to eql(false) }
|
51
|
+
end
|
52
|
+
|
53
|
+
context 'short form' do
|
54
|
+
let(:args) { ['-d'] }
|
55
|
+
it { expect(cli.configs[:daemon]).to eql(true) }
|
56
|
+
end
|
57
|
+
|
58
|
+
context 'full form' do
|
59
|
+
let(:args) { ['--daemon'] }
|
60
|
+
it { expect(cli.configs[:daemon]).to eql(true) }
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
describe 'host option' do
|
65
|
+
context 'default host' do
|
66
|
+
let(:args) { [] }
|
67
|
+
it { expect(cli.configs[:host]).to eql('0.0.0.0') }
|
68
|
+
end
|
69
|
+
|
70
|
+
context 'short form' do
|
71
|
+
let(:args) { ['-h localhost'] }
|
72
|
+
it { expect(cli.configs[:host]).to eql('localhost') }
|
73
|
+
end
|
74
|
+
|
75
|
+
context 'full form' do
|
76
|
+
let(:args) { ['--host=localhost'] }
|
77
|
+
it { expect(cli.configs[:host]).to eql('localhost') }
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
describe 'port option' do
|
82
|
+
context 'default port' do
|
83
|
+
let(:args) { [] }
|
84
|
+
it { expect(cli.configs[:port]).to eql(3000) }
|
85
|
+
end
|
86
|
+
|
87
|
+
context 'short form' do
|
88
|
+
let(:args) { ['-p 5000'] }
|
89
|
+
it { expect(cli.configs[:port]).to eql(5000) }
|
90
|
+
end
|
91
|
+
|
92
|
+
context 'full form' do
|
93
|
+
let(:args) { ['--port=5000'] }
|
94
|
+
it { expect(cli.configs[:port]).to eql(5000) }
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
describe 'worker option' do
|
99
|
+
context 'default worker' do
|
100
|
+
let(:args) { [] }
|
101
|
+
it { expect(cli.configs[:worker]).to eql(4) }
|
102
|
+
end
|
103
|
+
|
104
|
+
context 'short form' do
|
105
|
+
let(:args) { ['-w 20'] }
|
106
|
+
it { expect(cli.configs[:worker]).to eql(20) }
|
107
|
+
end
|
108
|
+
|
109
|
+
context 'full form' do
|
110
|
+
let(:args) { ['--worker=20'] }
|
111
|
+
it { expect(cli.configs[:port]).to eql(20) }
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe RubyWolf::Configuration do
|
4
|
+
let(:configs) { RubyWolf::Configuration.new }
|
5
|
+
|
6
|
+
describe '[]=' do
|
7
|
+
context 'key not exists' do
|
8
|
+
before do
|
9
|
+
configs[:hihi] = 'Test'
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'store the value under the key' do
|
13
|
+
expect(configs[:hihi]).to eql('Test')
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
context 'key exists' do
|
18
|
+
before do
|
19
|
+
configs[:hihi] = 'Test'
|
20
|
+
configs[:hihi] = 'or not to test'
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'updates the value under the key' do
|
24
|
+
expect(configs[:hihi]).to eql('or not to test')
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
describe '[]' do
|
30
|
+
context 'key exists' do
|
31
|
+
before do
|
32
|
+
configs[:hihi] = 'What'
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'returns the value under the key' do
|
36
|
+
expect(configs[:hihi]).to eql('What')
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
context 'key not exists' do
|
41
|
+
it 'returns nil' do
|
42
|
+
expect(configs[:not_exist]).to eql(nil)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,148 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ruby_wolf
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.2.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Nguyễn Quang Minh
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-03-01 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rspec
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 3.5.0
|
20
|
+
- - ">="
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 3.5.0
|
23
|
+
type: :development
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - "~>"
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 3.5.0
|
30
|
+
- - ">="
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 3.5.0
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: byebug
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - "~>"
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: 9.0.0
|
40
|
+
- - ">="
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: 8.0.0
|
43
|
+
type: :development
|
44
|
+
prerelease: false
|
45
|
+
version_requirements: !ruby/object:Gem::Requirement
|
46
|
+
requirements:
|
47
|
+
- - "~>"
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: 9.0.0
|
50
|
+
- - ">="
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: 8.0.0
|
53
|
+
- !ruby/object:Gem::Dependency
|
54
|
+
name: rack
|
55
|
+
requirement: !ruby/object:Gem::Requirement
|
56
|
+
requirements:
|
57
|
+
- - "~>"
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
version: 2.0.0
|
60
|
+
- - ">="
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: 2.0.0
|
63
|
+
type: :runtime
|
64
|
+
prerelease: false
|
65
|
+
version_requirements: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - "~>"
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: 2.0.0
|
70
|
+
- - ">="
|
71
|
+
- !ruby/object:Gem::Version
|
72
|
+
version: 2.0.0
|
73
|
+
- !ruby/object:Gem::Dependency
|
74
|
+
name: http_parser.rb
|
75
|
+
requirement: !ruby/object:Gem::Requirement
|
76
|
+
requirements:
|
77
|
+
- - "~>"
|
78
|
+
- !ruby/object:Gem::Version
|
79
|
+
version: 0.6.0
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 0.6.0
|
83
|
+
type: :runtime
|
84
|
+
prerelease: false
|
85
|
+
version_requirements: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: 0.6.0
|
90
|
+
- - ">="
|
91
|
+
- !ruby/object:Gem::Version
|
92
|
+
version: 0.6.0
|
93
|
+
description: My simple implementation of Rack web server using pre-fork and event
|
94
|
+
loop
|
95
|
+
email:
|
96
|
+
- nguyenquangminh0711@gmail.com
|
97
|
+
executables:
|
98
|
+
- ruby_wolf
|
99
|
+
extensions: []
|
100
|
+
extra_rdoc_files: []
|
101
|
+
files:
|
102
|
+
- ".gitignore"
|
103
|
+
- ".rspec"
|
104
|
+
- ".travis.yml"
|
105
|
+
- CODE_OF_CONDUCT.md
|
106
|
+
- Gemfile
|
107
|
+
- LICENSE.txt
|
108
|
+
- README.md
|
109
|
+
- Rakefile
|
110
|
+
- bin/ruby_wolf
|
111
|
+
- lib/ruby_wolf.rb
|
112
|
+
- lib/ruby_wolf/cli.rb
|
113
|
+
- lib/ruby_wolf/configuration.rb
|
114
|
+
- lib/ruby_wolf/connection.rb
|
115
|
+
- lib/ruby_wolf/handler.rb
|
116
|
+
- lib/ruby_wolf/server.rb
|
117
|
+
- lib/ruby_wolf/version.rb
|
118
|
+
- lib/ruby_wolf/worker.rb
|
119
|
+
- ruby_wolf.gemspec
|
120
|
+
- spec/ruby_wolf/cli_spec.rb
|
121
|
+
- spec/ruby_wolf/configuration_spec.rb
|
122
|
+
- spec/ruby_wolf_spec.rb
|
123
|
+
- spec/spec_helper.rb
|
124
|
+
homepage: https://github.com/nguyenquangminh0711/ruby_wolf
|
125
|
+
licenses:
|
126
|
+
- MIT
|
127
|
+
metadata: {}
|
128
|
+
post_install_message:
|
129
|
+
rdoc_options: []
|
130
|
+
require_paths:
|
131
|
+
- lib
|
132
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
133
|
+
requirements:
|
134
|
+
- - ">="
|
135
|
+
- !ruby/object:Gem::Version
|
136
|
+
version: '0'
|
137
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
138
|
+
requirements:
|
139
|
+
- - ">="
|
140
|
+
- !ruby/object:Gem::Version
|
141
|
+
version: '0'
|
142
|
+
requirements: []
|
143
|
+
rubyforge_project:
|
144
|
+
rubygems_version: 2.6.10
|
145
|
+
signing_key:
|
146
|
+
specification_version: 4
|
147
|
+
summary: A simple ruby web server using pre-fork and event loop
|
148
|
+
test_files: []
|