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.
@@ -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
@@ -0,0 +1,14 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in butterflies.gemspec
4
+ gemspec
@@ -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.
@@ -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
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+
@@ -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
@@ -0,0 +1 @@
1
+ run Proc.new { |env| [200, {'Content-Type' => 'text/html'}, ["Hello, World from #{Thread.current.object_id}"]] }
@@ -0,0 +1,10 @@
1
+ require 'socket'
2
+ require 'thread'
3
+ require "butterflies/version"
4
+ require "butterflies/master"
5
+ require "butterflies/acceptor"
6
+ require "butterflies/rack_default"
7
+
8
+ module Butterflies
9
+ # Your code goes here...
10
+ end
@@ -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,13 @@
1
+ module Butterflies
2
+ class Master
3
+
4
+ def initialize(app)
5
+ @app = app
6
+ end
7
+
8
+ def run
9
+ @acceptors = 4.times.map { Acceptor.new(@app) }
10
+ @acceptors.map(&:join)
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,5 @@
1
+ module Rack::Handler
2
+ def self.default(options = {})
3
+ Rack::Handler::Butterflies
4
+ end
5
+ end
@@ -0,0 +1,3 @@
1
+ module Butterflies
2
+ VERSION = "0.0.1"
3
+ 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: []