butterflies 0.0.1
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/.gitignore +14 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +77 -0
- data/Rakefile +2 -0
- data/butterflies.gemspec +24 -0
- data/example/config.ru +1 -0
- data/lib/butterflies.rb +10 -0
- data/lib/butterflies/acceptor.rb +108 -0
- data/lib/butterflies/master.rb +13 -0
- data/lib/butterflies/rack_default.rb +5 -0
- data/lib/butterflies/version.rb +3 -0
- data/lib/rack/handler/indian.rb +27 -0
- metadata +99 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: ce23457f770db65a47f7aabeb9c4fc581b283eeb
|
4
|
+
data.tar.gz: b6be8a5b8a668ddeee13ab0075cf1faf95311c1f
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 26f86d39b06db05e84581c880fd38002863768c99d7ec53b42c25d666c7c765abf13a7246ced6c36cef2bce3d1b030cf8fff3cb86404865b411483432b7582b8
|
7
|
+
data.tar.gz: 8b6f4737f29a8bf9a11f7dc02f674a1ca4b76c01c5aeed666b4a983adb6fb345d8e661987b457519bd853549efe757490853c036610b9e143c6bd43aa3e23f39
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Richard Bishop
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
# Butterflies
|
2
|
+
|
3
|
+
Butterflies is an experimental web server for Ruby applications that takes
|
4
|
+
advantage of modern Linux kernel (version 3.14+) features and threads.
|
5
|
+
|
6
|
+
## Design
|
7
|
+
|
8
|
+
Butterflies is inspired by the Erlang HTTP web server Cowboy. While we can't emulate
|
9
|
+
everything Cowboy does in Ruby, due to the differences between Ruby and Erlang,
|
10
|
+
we can borrow a little bit from Cowboy's design.
|
11
|
+
|
12
|
+
Cowboy works by spawning a listening socket and then sharing that socket with a
|
13
|
+
pool (potentially thousands of acceptors) of Erlang processes that accept on
|
14
|
+
the socket. Erlang's unit of concurrency is known as a process. Erlang
|
15
|
+
processes spawn very quickly so this model works well. In Ruby we have threads
|
16
|
+
for concurrency, which unfortunately don't spawn that fast. It is best to spawn
|
17
|
+
the threads you need upfront and re-use them.
|
18
|
+
|
19
|
+
The two premier Ruby web servers today, Unicorn and Puma, work in very
|
20
|
+
interesting and different ways. Unicorn is a lot like Cowboy in that it shares
|
21
|
+
a listening socket among many child processes that then accept on this socket.
|
22
|
+
The problem is that Unicorn's processes are operating processes which consume a
|
23
|
+
lot more memory than a Thread or an Erlang process. This also means one process
|
24
|
+
is only ever handling one request at a time. The advantage of this approach is
|
25
|
+
that the kernel of the underlying operating system load balances requests for
|
26
|
+
you.
|
27
|
+
|
28
|
+
Puma works by using threads for handling requests. Puma manages this by having
|
29
|
+
a thread listen for new connections and then pushing the connections onto a
|
30
|
+
queue. There is then a pool of threads that monitor and handle requests. Puma
|
31
|
+
also has a Reactor where you can check-in long lived connections.This is ideal
|
32
|
+
because regardless of the runtime you're using you can serve more than one
|
33
|
+
request in parallel per operating system process. The disadvantage is that you
|
34
|
+
have only one acceptor and have to manage the log of waiting connections
|
35
|
+
instead of letting the operating systen kernel do this for you.
|
36
|
+
|
37
|
+
Like Puma, Butterflies is designed using threads. Butterflies takes advantage of the
|
38
|
+
`SO_REUSEPORT` socket option that is available in Linux kernel version 3.9+ and
|
39
|
+
BSD operating systems. Each Butterflies thread can listen and accept connections on
|
40
|
+
the same port. The back log of connections will be load balanced by the
|
41
|
+
underlying kernel so you get performance and reliability without having to
|
42
|
+
manage that complexity yourself. You get the simplicity of Unicorn with the
|
43
|
+
parallel request handling of Puma!
|
44
|
+
|
45
|
+
## Installation
|
46
|
+
|
47
|
+
Add this line to your application's Gemfile:
|
48
|
+
|
49
|
+
```ruby
|
50
|
+
gem 'butterflies'
|
51
|
+
```
|
52
|
+
|
53
|
+
And then execute:
|
54
|
+
|
55
|
+
$ bundle
|
56
|
+
|
57
|
+
Or install it yourself as:
|
58
|
+
|
59
|
+
$ gem install butterflies
|
60
|
+
|
61
|
+
## Usage
|
62
|
+
|
63
|
+
This gem is not ready to be used yet.
|
64
|
+
|
65
|
+
## TODO
|
66
|
+
|
67
|
+
- [ ] epoll FFI binding for Ruby or just use nio4r
|
68
|
+
- [ ] TCP_FASTOPEN (Linux kernel 3.7+)
|
69
|
+
- [ ] TCP_AUTOCORKING (Linux kernel 3.14+)
|
70
|
+
|
71
|
+
## Contributing
|
72
|
+
|
73
|
+
1. Fork it ( https://github.com/[my-github-username]/butterflies/fork )
|
74
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
75
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
76
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
77
|
+
5. Create a new Pull Request
|
data/Rakefile
ADDED
data/butterflies.gemspec
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'butterflies/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "butterflies"
|
8
|
+
spec.version = Butterflies::VERSION
|
9
|
+
spec.authors = ["Richard Bishop"]
|
10
|
+
spec.email = ["richard@rubiquity.com"]
|
11
|
+
spec.summary = %q{An experimental web server using modern Linux kernel features.}
|
12
|
+
spec.description = %q{An experimental web server using modern Linux kernel features.}
|
13
|
+
spec.homepage = "https://github.com/rbishop/butterflies"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_runtime_dependency "http_parser.rb", "~> 0.6"
|
22
|
+
spec.add_development_dependency "bundler", "~> 1.7"
|
23
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
24
|
+
end
|
data/example/config.ru
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
run Proc.new { |env| [200, {'Content-Type' => 'text/html'}, ["Hello, World from #{Thread.current.object_id}"]] }
|
data/lib/butterflies.rb
ADDED
@@ -0,0 +1,108 @@
|
|
1
|
+
require "http/parser"
|
2
|
+
require "stringio"
|
3
|
+
require "io/wait"
|
4
|
+
|
5
|
+
Thread.abort_on_exception = true
|
6
|
+
|
7
|
+
module Butterflies
|
8
|
+
class Acceptor
|
9
|
+
attr_reader :thr
|
10
|
+
|
11
|
+
def initialize(app)
|
12
|
+
@app = app
|
13
|
+
accept
|
14
|
+
end
|
15
|
+
|
16
|
+
def accept
|
17
|
+
@thr = Thread.new do
|
18
|
+
puts "Spawning: #{Thread.current.object_id}"
|
19
|
+
server = Socket.new(:INET, :STREAM)
|
20
|
+
server.setsockopt(Socket::SOL_SOCKET, Socket::SO_REUSEPORT, 1)
|
21
|
+
sockaddr = Socket.pack_sockaddr_in(3000, '127.0.0.1')
|
22
|
+
server.bind(sockaddr)
|
23
|
+
server.listen(1024)
|
24
|
+
|
25
|
+
loop do
|
26
|
+
begin
|
27
|
+
client, _ = server.accept_nonblock
|
28
|
+
puts Thread.current.object_id
|
29
|
+
|
30
|
+
parser = Http::Parser.new(self)
|
31
|
+
eof = false
|
32
|
+
parser.on_headers_complete = proc { eof = true }
|
33
|
+
|
34
|
+
until eof
|
35
|
+
body = client.read_nonblock(64, exception: false)
|
36
|
+
parser << body unless body == :wait_readable || body.nil?
|
37
|
+
|
38
|
+
if body == :wait_readable && !eof
|
39
|
+
IO.select([client])
|
40
|
+
next
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
status, headers, body = @app.call(env.merge(header_vars(parser.headers)))
|
45
|
+
response = headers.reduce("HTTP/1.1 #{status} OK\r\n") do |res, (key, value)|
|
46
|
+
res += "#{key}: #{value}\r\n"
|
47
|
+
end
|
48
|
+
|
49
|
+
response += "\r\n"
|
50
|
+
body.each do |line|
|
51
|
+
response += line
|
52
|
+
end
|
53
|
+
|
54
|
+
until response == ""
|
55
|
+
bytes = client.write_nonblock(response, exception: false)
|
56
|
+
|
57
|
+
if bytes == :wait_writeable
|
58
|
+
puts 'selecting'
|
59
|
+
IO.select(nil, [client])
|
60
|
+
next
|
61
|
+
end
|
62
|
+
|
63
|
+
response = response.byteslice(bytes..-1)
|
64
|
+
end
|
65
|
+
|
66
|
+
body.close
|
67
|
+
client.close
|
68
|
+
rescue IO::WaitReadable
|
69
|
+
IO.select([server])
|
70
|
+
next
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
@thr
|
76
|
+
end
|
77
|
+
|
78
|
+
def join
|
79
|
+
@thr.join
|
80
|
+
end
|
81
|
+
|
82
|
+
def env
|
83
|
+
{
|
84
|
+
"REQUEST_METHOD" => "GET",
|
85
|
+
"SCRIPT_NAME" => "",
|
86
|
+
"PATH_INFO" => "",
|
87
|
+
"QUERY_STRING" => "",
|
88
|
+
"SERVER_NAME" => "Butterflies #{Butterflies::VERSION}",
|
89
|
+
"SERVER_PORT" => "3000",
|
90
|
+
"rack.version" => Rack::VERSION,
|
91
|
+
"rack.url_scheme" => "http",
|
92
|
+
"rack.multithread" => true,
|
93
|
+
"rack.multiprocess" => false,
|
94
|
+
"rack.run_once" => false,
|
95
|
+
"rack.input" => StringIO.new.set_encoding(Encoding::ASCII_8BIT),
|
96
|
+
"rack.errors" => $stderr
|
97
|
+
}
|
98
|
+
end
|
99
|
+
|
100
|
+
def header_vars(headers)
|
101
|
+
hdrs = {}
|
102
|
+
headers.each_pair do |key, value|
|
103
|
+
hdrs["HTTP_#{key.upcase.gsub("-", "_")}"] = value
|
104
|
+
end
|
105
|
+
hdrs
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'rack/handler'
|
2
|
+
require 'butterflies'
|
3
|
+
|
4
|
+
module Rack
|
5
|
+
module Handler
|
6
|
+
module Butterflies
|
7
|
+
def self.run(app, opts = {})
|
8
|
+
master = ::Butterflies::Master.new(app)
|
9
|
+
|
10
|
+
puts "Butterflies #{::Butterflies::VERSION} starting..."
|
11
|
+
puts "* Process ID #{Process.pid}"
|
12
|
+
puts "* Environment: #{ENV['RACK_ENV']}"
|
13
|
+
puts "* Listening on localhost:3000"
|
14
|
+
|
15
|
+
begin
|
16
|
+
master.run
|
17
|
+
rescue Interrupt
|
18
|
+
puts "* Shutting down..."
|
19
|
+
master.stop
|
20
|
+
puts "* See-ya!"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
register :butterflies, Butterflies
|
26
|
+
end
|
27
|
+
end
|
metadata
ADDED
@@ -0,0 +1,99 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: butterflies
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Richard Bishop
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-12-04 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: http_parser.rb
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0.6'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0.6'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: bundler
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.7'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.7'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '10.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '10.0'
|
55
|
+
description: An experimental web server using modern Linux kernel features.
|
56
|
+
email:
|
57
|
+
- richard@rubiquity.com
|
58
|
+
executables: []
|
59
|
+
extensions: []
|
60
|
+
extra_rdoc_files: []
|
61
|
+
files:
|
62
|
+
- ".gitignore"
|
63
|
+
- Gemfile
|
64
|
+
- LICENSE.txt
|
65
|
+
- README.md
|
66
|
+
- Rakefile
|
67
|
+
- butterflies.gemspec
|
68
|
+
- example/config.ru
|
69
|
+
- lib/butterflies.rb
|
70
|
+
- lib/butterflies/acceptor.rb
|
71
|
+
- lib/butterflies/master.rb
|
72
|
+
- lib/butterflies/rack_default.rb
|
73
|
+
- lib/butterflies/version.rb
|
74
|
+
- lib/rack/handler/indian.rb
|
75
|
+
homepage: https://github.com/rbishop/butterflies
|
76
|
+
licenses:
|
77
|
+
- MIT
|
78
|
+
metadata: {}
|
79
|
+
post_install_message:
|
80
|
+
rdoc_options: []
|
81
|
+
require_paths:
|
82
|
+
- lib
|
83
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
84
|
+
requirements:
|
85
|
+
- - ">="
|
86
|
+
- !ruby/object:Gem::Version
|
87
|
+
version: '0'
|
88
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
89
|
+
requirements:
|
90
|
+
- - ">="
|
91
|
+
- !ruby/object:Gem::Version
|
92
|
+
version: '0'
|
93
|
+
requirements: []
|
94
|
+
rubyforge_project:
|
95
|
+
rubygems_version: 2.2.2
|
96
|
+
signing_key:
|
97
|
+
specification_version: 4
|
98
|
+
summary: An experimental web server using modern Linux kernel features.
|
99
|
+
test_files: []
|