tub 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.
data/.rvmrc ADDED
@@ -0,0 +1 @@
1
+ rvm --create use default@tub > /dev/null
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in genesis_graphs.gemspec
4
+ gemspec
@@ -0,0 +1,40 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ tub (0.1.0)
5
+ activesupport
6
+ thin
7
+
8
+ GEM
9
+ remote: http://rubygems.org/
10
+ specs:
11
+ ZenTest (4.4.2)
12
+ activesupport (3.0.3)
13
+ autotest (4.4.6)
14
+ ZenTest (>= 4.4.1)
15
+ daemons (1.1.0)
16
+ diff-lcs (1.1.2)
17
+ eventmachine (0.12.10)
18
+ rack (1.2.1)
19
+ rspec (2.4.0)
20
+ rspec-core (~> 2.4.0)
21
+ rspec-expectations (~> 2.4.0)
22
+ rspec-mocks (~> 2.4.0)
23
+ rspec-core (2.4.0)
24
+ rspec-expectations (2.4.0)
25
+ diff-lcs (~> 1.1.2)
26
+ rspec-mocks (2.4.0)
27
+ thin (1.2.7)
28
+ daemons (>= 1.0.9)
29
+ eventmachine (>= 0.12.6)
30
+ rack (>= 1.0.0)
31
+
32
+ PLATFORMS
33
+ ruby
34
+
35
+ DEPENDENCIES
36
+ activesupport
37
+ autotest
38
+ rspec
39
+ thin
40
+ tub!
@@ -0,0 +1,48 @@
1
+ Try It
2
+ ======
3
+
4
+ Make a server
5
+
6
+ require 'tub'
7
+
8
+ class Echo < EM::Connection
9
+ def receive_data(data)
10
+ send_data(data)
11
+ end
12
+ end
13
+
14
+ app = lambda{|env| [200, {'Content-Type' => 'text/plain'}, ["Hello world!"]]}
15
+
16
+ Thin::Server.start('0.0.0.0', 1345, app, :backend => TUB::UpgradableTcpServer, :handler_on_upgrade => Echo)
17
+
18
+ Connect to the server
19
+
20
+ telnet localhost 1345
21
+
22
+ Speak HTTP
23
+
24
+ GET / HTTP/1.1
25
+ Host: example.com
26
+
27
+
28
+ Now switch protocols
29
+
30
+ GET / HTTP/1.1
31
+ Host: example.com
32
+ Upgrade: Echo/1.0
33
+ Connection: Upgrade
34
+
35
+ And then type anything you want, it'll echo back to you
36
+
37
+ Hello world
38
+ Hello world
39
+
40
+ Have fun!
41
+
42
+
43
+ TODO
44
+ ======
45
+ * Make it actually care about what protocol the client requests and which you offer
46
+ * Figure out rackup file
47
+ * Show a sample app working in Heroku, with a sample client
48
+ * Remove the Rack wrapper app and make it part of the example, but customizable, so you control when upgrade occurs
@@ -0,0 +1,32 @@
1
+ #--
2
+ # Copyright (c) 2011 Tim Shadel
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # "Software"), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ #++
23
+
24
+ module ThinUpgradableBackend
25
+ ROOT = File.expand_path(File.dirname(__FILE__))
26
+ end
27
+ TUB = ThinUpgradableBackend
28
+
29
+ require "#{TUB::ROOT}/tub/version"
30
+ require "#{TUB::ROOT}/tub/upgradable_connection"
31
+ require "#{TUB::ROOT}/tub/upgradable_tcp_server"
32
+ require "#{TUB::ROOT}/tub/thin_persistent_101"
@@ -0,0 +1,35 @@
1
+ #--
2
+ # Copyright (c) 2011 Tim Shadel
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # "Software"), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ #++
23
+
24
+ module Thin
25
+
26
+ # This is a workaround, and should be removed as soon as Heroku deploys a Thin version
27
+ # that includes some version of https://github.com/macournoyer/thin/pull/32
28
+ class Response
29
+ alias :old_persistent? :persistent?
30
+ def persistent?
31
+ old_persistent? || (@status == 100 || @status == 101)
32
+ end
33
+ end
34
+
35
+ end
@@ -0,0 +1,56 @@
1
+ #--
2
+ # Copyright (c) 2011 Tim Shadel
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # "Software"), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ #++
23
+
24
+ require 'active_support/core_ext/class/attribute_accessors'
25
+ require 'eventmachine'
26
+
27
+ module TUB
28
+ class UpgradableConnection < EM::Connection
29
+ cattr_accessor :initial_handler_class
30
+ cattr_accessor :upgraded_handler_class
31
+
32
+ attr_accessor :handler
33
+
34
+ def post_init
35
+ @handler = UpgradableConnection.initial_handler_class.new @signature
36
+ @upgraded = false
37
+ end
38
+
39
+ def receive_data(data)
40
+ @handler.receive_data(data)
41
+ end
42
+
43
+ def unbind
44
+ @handler.unbind
45
+ end
46
+
47
+ def upgrade!
48
+ return if @upgraded
49
+ @handler = UpgradableConnection.upgraded_handler_class.new @signature
50
+ @upgraded = true
51
+ end
52
+
53
+ end
54
+ end
55
+
56
+
@@ -0,0 +1,72 @@
1
+ #--
2
+ # Copyright (c) 2011 Tim Shadel
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # "Software"), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ #++
23
+
24
+ require 'thin'
25
+
26
+ module TUB
27
+
28
+ class UpgradeApp
29
+ def initialize(conn, app)
30
+ @conn = conn
31
+ @app = app
32
+ end
33
+
34
+ def call(env)
35
+ if env["HTTP_UPGRADE"]
36
+ @conn.upgrade!
37
+ [101, { "Upgrade" => env["HTTP_UPGRADE"], "Connection" => "Upgrade" }, []]
38
+ else
39
+ @app.call(env)
40
+ end
41
+ end
42
+ end
43
+
44
+ class UpgradableTcpServer < Thin::Backends::TcpServer
45
+ attr_accessor :ssl
46
+ # Thin::Server assumes we'll have options
47
+ def initialize(host, port, options)
48
+ super(host, port)
49
+ UpgradableConnection.initial_handler_class = Thin::Connection
50
+ raise ":handler_on_upgrade must be provided" unless options[:handler_on_upgrade]
51
+ UpgradableConnection.upgraded_handler_class = options[:handler_on_upgrade]
52
+ end
53
+
54
+ # Connect the server
55
+ def connect
56
+ @signature = EventMachine.start_server(@host, @port, UpgradableConnection, &method(:initialize_connection))
57
+ end
58
+
59
+ protected
60
+ # Initialize the HTTP handler when the connection is first created.
61
+ def initialize_connection(connection)
62
+ connection.handler.backend = self
63
+ ourApp = UpgradeApp.new connection, @server.app
64
+ connection.handler.app = ourApp
65
+ connection.handler.comm_inactivity_timeout = @timeout
66
+ connection.handler.threaded = @threaded
67
+ connection.handler.can_persist!
68
+
69
+ @connections << connection.handler
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,32 @@
1
+ #--
2
+ # Copyright (c) 2011 Tim Shadel
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # "Software"), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ #++
23
+
24
+ module ThinUpgradableBackend
25
+ module VERSION #:nodoc:
26
+ MAJOR = 0
27
+ MINOR = 1
28
+ TINY = 0
29
+
30
+ STRING = [MAJOR, MINOR, TINY].join('.')
31
+ end
32
+ end
@@ -0,0 +1,79 @@
1
+ require 'bundler'
2
+ Bundler.setup
3
+
4
+ require 'thin'
5
+ require 'tub'
6
+
7
+ unless Object.const_defined?(:DEFAULT_TEST_ADDRESS)
8
+ DEFAULT_TEST_ADDRESS = '0.0.0.0'
9
+ DEFAULT_TEST_PORT = 3333
10
+ end
11
+
12
+ module Helpers
13
+
14
+ def start_server(address=DEFAULT_TEST_ADDRESS, port=DEFAULT_TEST_PORT, options={}, &app)
15
+ @server = Thin::Server.new(address, port, options, app)
16
+ # @server.ssl = options[:ssl]
17
+ @server.threaded = options[:threaded]
18
+ @server.timeout = 3
19
+
20
+ @thread = Thread.new { @server.start }
21
+ if options[:wait_for_socket]
22
+ wait_for_socket(address, port)
23
+ else
24
+ # If we can't ping the address fallback to just wait for the server to run
25
+ sleep 1 until @server.running?
26
+ end
27
+ end
28
+
29
+ def stop_server
30
+ @server.stop!
31
+ @thread.kill
32
+ sleep 0.5
33
+ raise "Reactor still running, wtf?" if EventMachine.reactor_running?
34
+ end
35
+
36
+ def wait_for_socket(address=DEFAULT_TEST_ADDRESS, port=DEFAULT_TEST_PORT, timeout=5)
37
+ Timeout.timeout(timeout) do
38
+ loop do
39
+ begin
40
+ TCPSocket.new(address, port).close
41
+ return true
42
+ rescue
43
+ end
44
+ end
45
+ end
46
+ end
47
+
48
+
49
+ def send_data(data)
50
+ socket = TCPSocket.new(@server.host, @server.port)
51
+ socket.write data
52
+ out = socket.read
53
+ socket.close
54
+ out
55
+ end
56
+
57
+ def parse_response(response)
58
+ raw_headers, body = response.split("\r\n\r\n", 2)
59
+ raw_status, raw_headers = raw_headers.split("\r\n", 2)
60
+
61
+ status = raw_status.match(%r{\AHTTP/1.1\s+(\d+)\b}).captures.first.to_i
62
+ headers = Hash[ *raw_headers.split("\r\n").map { |h| h.split(/:\s+/, 2) }.flatten ]
63
+
64
+ [ status, headers, body ]
65
+ end
66
+
67
+ def get(url)
68
+ Net::HTTP.get(URI.parse("http://#{@server.host}:#{@server.port}" + url))
69
+ end
70
+
71
+ def post(url, params={})
72
+ Net::HTTP.post_form(URI.parse("http://#{@server.host}:#{@server.port}" + url), params).body
73
+ end
74
+
75
+ end
76
+
77
+ RSpec.configure do |config|
78
+ config.include Helpers
79
+ end
@@ -0,0 +1,73 @@
1
+ require 'spec_helper'
2
+
3
+ class Echo < EM::Connection
4
+ def receive_data(data)
5
+ send_data(data)
6
+ end
7
+ end
8
+
9
+
10
+ describe TUB::UpgradableTcpServer do
11
+ before do
12
+ start_server DEFAULT_TEST_ADDRESS, DEFAULT_TEST_PORT, :backend => TUB::UpgradableTcpServer, :handler_on_upgrade => Echo do |env|
13
+ body = env.inspect + env['rack.input'].read
14
+ [200, { 'Content-Type' => 'text/html' }, body]
15
+ end
16
+ end
17
+
18
+ it 'should serve HTTP from the Rack app' do
19
+ get('/?cthis').should include('cthis')
20
+ end
21
+
22
+ it 'should switch from HTTP to Echo when requested' do
23
+ socket = TCPSocket.new(@server.host, @server.port)
24
+ socket.sync = true
25
+ request = "GET / HTTP/1.1\r\nHost: arst.com\r\nUpgrade: Echo/1.0\r\nConnection: Upgrade\r\n\r\n"
26
+
27
+ socket.print request
28
+ out = ''
29
+ selection = IO.select([socket], nil, nil, 0.3)
30
+ while selection && selection[0]
31
+ begin
32
+ line = socket.readline
33
+ out << line
34
+ selection = IO.select([socket], nil, nil, 0.3)
35
+ rescue => ex
36
+ STDERR.puts ex
37
+ break
38
+ end
39
+ end
40
+
41
+ status, headers, body = parse_response(out)
42
+ status.should == 101
43
+ headers['Upgrade'].should == 'Echo/1.0'
44
+ headers['Connection'].should == 'Upgrade'
45
+
46
+ selection = IO.select(nil, [socket], nil, 1.5)
47
+
48
+ ["I am in ", " echo", "mode"].each do |e|
49
+ socket.puts(e)
50
+ end
51
+
52
+ selection = IO.select([socket], nil, nil, 0.3)
53
+ while selection && selection[0]
54
+ begin
55
+ line = socket.readline
56
+ out << line
57
+ selection = IO.select([socket], nil, nil, 0.3)
58
+ rescue => ex
59
+ STDERR.puts ex
60
+ break
61
+ end
62
+ end
63
+ out.should match("am")
64
+ out.should match("echo")
65
+ out.should match("mode")
66
+
67
+ socket.close
68
+ end
69
+
70
+ after do
71
+ stop_server
72
+ end
73
+ end
@@ -0,0 +1,26 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "tub/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "tub"
7
+ s.version = ThinUpgradableBackend::VERSION::STRING
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Tim Shadel"]
10
+ s.email = ["github@timshadel.com"]
11
+ s.homepage = "http://github.com/timshadel/tub"
12
+ s.summary = %q{Make a Thin backend that can serve Rack, then easily be upgraded to a completely different EM-based protocol.}
13
+ s.description = %q{The HTTP spec details a way to use HTTP/1.1 as a platform for transitioning to newer protocols by starting the conversation with HTTP/1.1. There are no restrictions on what that protocol can be.}
14
+
15
+ # s.rubyforge_project = "tub"
16
+
17
+ s.add_development_dependency "rspec"
18
+ s.add_development_dependency "autotest"
19
+ s.add_dependency "thin"
20
+ s.add_dependency "activesupport"
21
+
22
+ s.files = `git ls-files`.split("\n")
23
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
24
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
25
+ s.require_paths = ["lib"]
26
+ end
metadata ADDED
@@ -0,0 +1,128 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: tub
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 1
8
+ - 0
9
+ version: 0.1.0
10
+ platform: ruby
11
+ authors:
12
+ - Tim Shadel
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2011-01-22 00:00:00 -07:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: rspec
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ segments:
29
+ - 0
30
+ version: "0"
31
+ type: :development
32
+ version_requirements: *id001
33
+ - !ruby/object:Gem::Dependency
34
+ name: autotest
35
+ prerelease: false
36
+ requirement: &id002 !ruby/object:Gem::Requirement
37
+ none: false
38
+ requirements:
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ segments:
42
+ - 0
43
+ version: "0"
44
+ type: :development
45
+ version_requirements: *id002
46
+ - !ruby/object:Gem::Dependency
47
+ name: thin
48
+ prerelease: false
49
+ requirement: &id003 !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ segments:
55
+ - 0
56
+ version: "0"
57
+ type: :runtime
58
+ version_requirements: *id003
59
+ - !ruby/object:Gem::Dependency
60
+ name: activesupport
61
+ prerelease: false
62
+ requirement: &id004 !ruby/object:Gem::Requirement
63
+ none: false
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ segments:
68
+ - 0
69
+ version: "0"
70
+ type: :runtime
71
+ version_requirements: *id004
72
+ description: The HTTP spec details a way to use HTTP/1.1 as a platform for transitioning to newer protocols by starting the conversation with HTTP/1.1. There are no restrictions on what that protocol can be.
73
+ email:
74
+ - github@timshadel.com
75
+ executables: []
76
+
77
+ extensions: []
78
+
79
+ extra_rdoc_files: []
80
+
81
+ files:
82
+ - .rvmrc
83
+ - Gemfile
84
+ - Gemfile.lock
85
+ - Readme.md
86
+ - lib/tub.rb
87
+ - lib/tub/thin_persistent_101.rb
88
+ - lib/tub/upgradable_connection.rb
89
+ - lib/tub/upgradable_tcp_server.rb
90
+ - lib/tub/version.rb
91
+ - spec/spec_helper.rb
92
+ - spec/upgradable_backend_spec.rb
93
+ - tub.gemspec
94
+ has_rdoc: true
95
+ homepage: http://github.com/timshadel/tub
96
+ licenses: []
97
+
98
+ post_install_message:
99
+ rdoc_options: []
100
+
101
+ require_paths:
102
+ - lib
103
+ required_ruby_version: !ruby/object:Gem::Requirement
104
+ none: false
105
+ requirements:
106
+ - - ">="
107
+ - !ruby/object:Gem::Version
108
+ segments:
109
+ - 0
110
+ version: "0"
111
+ required_rubygems_version: !ruby/object:Gem::Requirement
112
+ none: false
113
+ requirements:
114
+ - - ">="
115
+ - !ruby/object:Gem::Version
116
+ segments:
117
+ - 0
118
+ version: "0"
119
+ requirements: []
120
+
121
+ rubyforge_project:
122
+ rubygems_version: 1.3.7
123
+ signing_key:
124
+ specification_version: 3
125
+ summary: Make a Thin backend that can serve Rack, then easily be upgraded to a completely different EM-based protocol.
126
+ test_files:
127
+ - spec/spec_helper.rb
128
+ - spec/upgradable_backend_spec.rb