puggernaut 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/.gitignore ADDED
@@ -0,0 +1,6 @@
1
+ .DS_Store
2
+ *.gem
3
+ coverage
4
+ log/*
5
+ pkg
6
+ tmp
data/LICENSE ADDED
@@ -0,0 +1,18 @@
1
+ Copyright (c) 2010
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
4
+ this software and associated documentation files (the "Software"), to deal in
5
+ the Software without restriction, including without limitation the rights to
6
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
7
+ the Software, and to permit persons to whom the Software is furnished to do so,
8
+ subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all
11
+ copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
15
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
16
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
17
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
18
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,93 @@
1
+ Puggernaut
2
+ ==========
3
+
4
+ Simple server push implementation using eventmachine and long polling.
5
+
6
+ ![Puggernaut](/winton/puggernaut/raw/master/puggernaut.png)
7
+
8
+ Requirements
9
+ ------------
10
+
11
+ <pre>
12
+ gem install puggernaut
13
+ </pre>
14
+
15
+ How it works
16
+ ------------
17
+
18
+ Puggernaut consists of three pieces:
19
+
20
+ * TCP client to send push messages
21
+ * TCP server to receive push messages
22
+ * HTTP server to deliver long poll requests
23
+
24
+ Start it up
25
+ -----------
26
+
27
+ Run the <code>puggernaut</code> binary with optional port numbers:
28
+
29
+ <pre>
30
+ puggernaut &lt;http port&gt; &lt;tcp port&gt;
31
+ </pre>
32
+
33
+ The default HTTP and TCP ports are 8000 and 8001, respectively.
34
+
35
+ Set up proxy pass
36
+ -----------------
37
+
38
+ You will need to set up a URL on your public facing web server that points to the Puggernaut HTTP server.
39
+
40
+ If you do not see your web server below, [Google](http://google.com) is your friend.
41
+
42
+ ### Apache
43
+
44
+ *http.conf*
45
+
46
+ <pre>
47
+ ProxyPass /long_poll http://localhost:8000/
48
+ ProxyPassReverse /long_poll http://localhost:8000/
49
+ </pre>
50
+
51
+ ### Nginx
52
+
53
+ *nginx.conf*
54
+
55
+ <pre>
56
+ location /long_poll {
57
+ proxy_pass http://localhost:8000/;
58
+ }
59
+ </pre>
60
+
61
+ Send push messages
62
+ ------------------
63
+
64
+ <pre>
65
+ require 'puggernaut'
66
+
67
+ client = Puggernaut::Client.new("localhost:8001", "localhost:9001")
68
+ client.push :channel => "message"
69
+ client.push :channel => [ "message 1", "message 2" ], :channel_2 => "message"
70
+ </pre>
71
+
72
+ The <code>Client.new</code> initializer accepts any number of TCP server addresses.
73
+
74
+ Receive push messages
75
+ ---------------------
76
+
77
+ Include [puggernaut.js](/winton/puggernaut/public/puggernaut.js) into to your HTML page.
78
+
79
+ Javascript client example:
80
+
81
+ <pre>
82
+ Puggernaut.path = '/long_poll';
83
+
84
+ Puggernaut
85
+ .watch('channel', function(e, message) {
86
+ // do something with message
87
+ })
88
+ .watch('channel_2', function(e, message) {
89
+ // do something with message
90
+ });
91
+
92
+ Puggernaut.unwatch('channel');
93
+ </pre>
data/Rakefile ADDED
@@ -0,0 +1,90 @@
1
+ require File.dirname(__FILE__) + '/lib/puggernaut/gems'
2
+
3
+ Puggernaut::Gems.activate %w(rake rspec)
4
+
5
+ require 'rake'
6
+ require 'spec/rake/spectask'
7
+
8
+ def gemspec
9
+ @gemspec ||= begin
10
+ file = File.expand_path('../puggernaut.gemspec', __FILE__)
11
+ eval(File.read(file), binding, file)
12
+ end
13
+ end
14
+
15
+ if defined?(Spec::Rake::SpecTask)
16
+ desc "Run specs"
17
+ Spec::Rake::SpecTask.new do |t|
18
+ t.spec_files = FileList['spec/**/*_spec.rb']
19
+ t.spec_opts = %w(-fs --color)
20
+ t.warning = true
21
+ end
22
+ task :spec
23
+ task :default => :spec
24
+ end
25
+
26
+ desc "Build gem(s)"
27
+ task :gem do
28
+ old_gemset = ENV['GEMSET']
29
+ root = File.expand_path('../', __FILE__)
30
+ pkg = "#{root}/pkg"
31
+ system "rm -Rf #{pkg}"
32
+ Puggernaut::Gems.gemset_names.each do |gemset|
33
+ ENV['GEMSET'] = gemset.to_s
34
+ system "cd #{root} && gem build puggernaut.gemspec"
35
+ system "mkdir -p #{pkg} && mv *.gem pkg"
36
+ end
37
+ ENV['GEMSET'] = old_gemset
38
+ end
39
+
40
+ namespace :gem do
41
+ desc "Install gem(s)"
42
+ task :install do
43
+ Rake::Task['gem'].invoke
44
+ Dir["#{File.dirname(__FILE__)}/pkg/*.gem"].each do |pkg|
45
+ system "gem install #{pkg} --no-ri --no-rdoc"
46
+ end
47
+ end
48
+
49
+ desc "Push gem(s)"
50
+ task :push do
51
+ Rake::Task['gem'].invoke
52
+ Dir["#{File.dirname(__FILE__)}/pkg/*.gem"].each do |pkg|
53
+ system "gem push #{pkg}"
54
+ end
55
+ end
56
+ end
57
+
58
+ namespace :gems do
59
+ desc "Install gem dependencies (DEV=0 DOCS=0 GEMSPEC=default SUDO=0)"
60
+ task :install do
61
+ dev = ENV['DEV'] == '1'
62
+ docs = ENV['DOCS'] == '1' ? '' : '--no-ri --no-rdoc'
63
+ gemset = ENV['GEMSET']
64
+ sudo = ENV['SUDO'] == '1' ? 'sudo' : ''
65
+
66
+ Puggernaut::Gems.gemset = gemset if gemset
67
+
68
+ if dev
69
+ gems = Puggernaut::Gems.gemspec.development_dependencies
70
+ else
71
+ gems = Puggernaut::Gems.gemspec.dependencies
72
+ end
73
+
74
+ gems.each do |name|
75
+ name = name.to_s
76
+ version = Puggernaut::Gems.versions[name]
77
+ if Gem.source_index.find_name(name, version).empty?
78
+ version = version ? "-v #{version}" : ''
79
+ system "#{sudo} gem install #{name} #{version} #{docs}"
80
+ else
81
+ puts "already installed: #{name} #{version}"
82
+ end
83
+ end
84
+ end
85
+ end
86
+
87
+ desc "Validate the gemspec"
88
+ task :gemspec do
89
+ gemspec.validate
90
+ end
data/bin/puggernaut ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require File.expand_path(File.dirname(__FILE__) + "/../lib/puggernaut")
4
+
5
+ Puggernaut::Server.new(*ARGV)
data/config.ru ADDED
@@ -0,0 +1,3 @@
1
+ require "#{File.dirname(__FILE__)}/lib/puggernaut/spec_server"
2
+
3
+ run SpecServer
@@ -0,0 +1,5 @@
1
+ puggernaut:
2
+ eventmachine: ~>0.12.10
3
+ rake: >=0.8.7
4
+ rspec: ~>1.0
5
+ sinatra: ~>1.1.0
@@ -0,0 +1,11 @@
1
+ name: puggernaut
2
+ version: 0.1.0
3
+ authors:
4
+ - Winton Welsh
5
+ email: mail@wintoni.us
6
+ homepage: http://github.com/winton/puggernaut
7
+ summary: Simple server push implementation using eventmachine and long polling
8
+ description: Simple server push implementation using eventmachine and long polling
9
+ dependencies:
10
+ - eventmachine
11
+ development_dependencies: null
data/lib/puggernaut.rb ADDED
@@ -0,0 +1,14 @@
1
+ require File.dirname(__FILE__) + '/puggernaut/gems'
2
+
3
+ Puggernaut::Gems.activate %w(eventmachine)
4
+
5
+ require 'eventmachine'
6
+
7
+ $:.unshift File.dirname(__FILE__)
8
+
9
+ require 'puggernaut/logger'
10
+ require 'puggernaut/client'
11
+ require 'puggernaut/server'
12
+
13
+ module Puggernaut
14
+ end
@@ -0,0 +1,66 @@
1
+ require "#{File.dirname(__FILE__)}/logger"
2
+ require 'socket'
3
+
4
+ module Puggernaut
5
+ class Client
6
+
7
+ include Logger
8
+
9
+ attr_accessor :connections
10
+
11
+ def initialize(*servers)
12
+ @connections = {}
13
+ @retry = []
14
+ @servers = servers.collect { |s| s.split(':') }
15
+ end
16
+
17
+ def close
18
+ @connections.each do |host_port, connection|
19
+ connection.close
20
+ logger.info "Client#close - #{host_port}"
21
+ end
22
+ end
23
+
24
+ def push(messages)
25
+ messages = messages.collect do |channel, message|
26
+ if message.is_a?(::Array)
27
+ message.collect { |m| "#{channel}|#{m}" }.join("\n")
28
+ else
29
+ "#{channel}|#{message}"
30
+ end
31
+ end
32
+ unless messages.empty?
33
+ @servers.each do |(host, port)|
34
+ send host, port, messages.join("\n")
35
+ end
36
+ end
37
+ end
38
+
39
+ private
40
+
41
+ def send(host, port, data, try_retry=true)
42
+ try if try_retry
43
+ begin
44
+ host_port = "#{host}:#{port}"
45
+ logger.info "Client#send - #{host_port} - #{data}"
46
+ connection = @connections[host_port] ||= TCPSocket.open(host, port)
47
+ connection.print(data)
48
+ response = connection.gets
49
+ raise 'not ok' if !response || !response.include?('OK')
50
+ rescue Exception => e
51
+ logger.info "Client#send - Exception - #{e.message} - #{host_port} - #{data}"
52
+ @retry << [ host, port, data ]
53
+ @retry.shift if @retry.length > 10
54
+ end
55
+ try if try_retry
56
+ end
57
+
58
+ def try
59
+ @retry.length.times do
60
+ host, port, data = @retry.shift
61
+ @connections.delete("#{host}:#{port}")
62
+ send host, port, data, false
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,154 @@
1
+ unless defined?(Puggernaut::Gems)
2
+
3
+ require 'yaml'
4
+
5
+ module Puggernaut
6
+ module Gems
7
+ class <<self
8
+
9
+ attr_accessor :config
10
+ attr_reader :gemset, :gemsets, :versions
11
+
12
+ class SimpleStruct
13
+ attr_reader :hash
14
+
15
+ def initialize(hash)
16
+ @hash = hash
17
+ @hash.each do |key, value|
18
+ self.class.send(:define_method, key) { @hash[key] }
19
+ self.class.send(:define_method, "#{key}=") { |v| @hash[key] = v }
20
+ end
21
+ end
22
+ end
23
+
24
+ Gems.config = SimpleStruct.new(
25
+ :gemsets => [ "#{File.expand_path('../../../', __FILE__)}/config/gemsets.yml" ],
26
+ :gemspec => "#{File.expand_path('../../../', __FILE__)}/config/gemspec.yml",
27
+ :warn => true
28
+ )
29
+
30
+ def activate(*gems)
31
+ begin
32
+ require 'rubygems' unless defined?(::Gem)
33
+ rescue LoadError
34
+ puts "rubygems library could not be required" if @config.warn
35
+ end
36
+
37
+ self.gemset ||= gemset_from_loaded_specs
38
+
39
+ gems.flatten.collect(&:to_sym).each do |name|
40
+ version = @versions[name]
41
+ vendor = File.expand_path("../../../vendor/#{name}/lib", __FILE__)
42
+ if File.exists?(vendor)
43
+ $:.unshift vendor
44
+ elsif defined?(gem)
45
+ gem name.to_s, version
46
+ else
47
+ puts "#{name} #{"(#{version})" if version} failed to activate" if @config.warn
48
+ end
49
+ end
50
+ end
51
+
52
+ def dependencies
53
+ dependency_filter(@gemspec.dependencies, @gemset)
54
+ end
55
+
56
+ def development_dependencies
57
+ dependency_filter(@gemspec.development_dependencies, @gemset)
58
+ end
59
+
60
+ def gemset=(gemset)
61
+ if gemset
62
+ @gemset = gemset.to_sym
63
+
64
+ @gemsets = @config.gemsets.reverse.collect { |config|
65
+ if config.is_a?(::String)
66
+ YAML::load(File.read(config)) rescue {}
67
+ elsif config.is_a?(::Hash)
68
+ config
69
+ end
70
+ }.inject({}) do |hash, config|
71
+ deep_merge(hash, symbolize_keys(config))
72
+ end
73
+
74
+ @versions = (@gemsets[gemspec.name.to_sym] || {}).inject({}) do |hash, (key, value)|
75
+ if !value.is_a?(::Hash) && value
76
+ hash[key] = value
77
+ elsif key == @gemset
78
+ (value || {}).each { |k, v| hash[k] = v }
79
+ end
80
+ hash
81
+ end
82
+ else
83
+ @gemset = nil
84
+ @gemsets = nil
85
+ @versions = nil
86
+ end
87
+ end
88
+
89
+ def gemset_names
90
+ (
91
+ [ :default ] +
92
+ @gemsets[gemspec.name.to_sym].inject([]) { |array, (key, value)|
93
+ array.push(key) if value.is_a?(::Hash) || value.nil?
94
+ array
95
+ }
96
+ ).uniq
97
+ end
98
+
99
+ def gemspec(reload=false)
100
+ if @gemspec && !reload
101
+ @gemspec
102
+ else
103
+ data = YAML::load(File.read(@config.gemspec)) rescue {}
104
+ @gemspec = SimpleStruct.new(data)
105
+ end
106
+ end
107
+
108
+ private
109
+
110
+ def deep_merge(first, second)
111
+ merger = lambda do |key, v1, v2|
112
+ Hash === v1 && Hash === v2 ? v1.merge(v2, &merger) : v2
113
+ end
114
+ first.merge(second, &merger)
115
+ end
116
+
117
+ def dependency_filter(dependencies, match)
118
+ (dependencies || []).inject([]) { |array, value|
119
+ if value.is_a?(::Hash)
120
+ array += value[match.to_s] if value[match.to_s]
121
+ else
122
+ array << value
123
+ end
124
+ array
125
+ }.uniq.collect(&:to_sym)
126
+ end
127
+
128
+ def gemset_from_loaded_specs
129
+ if defined?(Gem)
130
+ Gem.loaded_specs.each do |name, spec|
131
+ if name == gemspec.name
132
+ return :default
133
+ elsif name[0..gemspec.name.length] == "#{gemspec.name}-"
134
+ return name[gemspec.name.length+1..-1].to_sym
135
+ end
136
+ end
137
+ :default
138
+ else
139
+ :none
140
+ end
141
+ end
142
+
143
+ def symbolize_keys(hash)
144
+ return {} unless hash.is_a?(::Hash)
145
+ hash.inject({}) do |options, (key, value)|
146
+ value = symbolize_keys(value) if value.is_a?(::Hash)
147
+ options[(key.to_sym rescue key) || key] = value
148
+ options
149
+ end
150
+ end
151
+ end
152
+ end
153
+ end
154
+ end