push 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,205 @@
1
+ <!doctype html>
2
+ <html>
3
+ <head>
4
+ <title>Cross-domain XHMLHttpRequest proxy</title>
5
+ <%= javascript_include_tag *%w[json2 easyXDM] %>
6
+ <script type="text/javascript">
7
+ /*
8
+ * This is a CORS (Cross-Origin Resource Sharing) and AJAX enabled endpoint for easyXDM.
9
+ * The ACL code is adapted from pmxdr (http://github.com/eligrey/pmxdr/) by Eli Grey (http://eligrey.com/)
10
+ *
11
+ */
12
+ // From http://peter.michaux.ca/articles/feature-detection-state-of-the-art-browser-scripting
13
+ function isHostMethod(object, property){
14
+ var t = typeof object[property];
15
+ return t == 'function' ||
16
+ (!!(t == 'object' && object[property])) ||
17
+ t == 'unknown';
18
+ }
19
+
20
+ /**
21
+ * Creates a cross-browser XMLHttpRequest object
22
+ * @return {XMLHttpRequest} A XMLHttpRequest object.
23
+ */
24
+ var getXhr = (function(){
25
+ if (isHostMethod(window, "XMLHttpRequest")) {
26
+ return function(){
27
+ return new XMLHttpRequest();
28
+ };
29
+ }
30
+ else {
31
+ var item = (function(){
32
+ var list = ["Microsoft", "Msxml2", "Msxml3"], i = list.length;
33
+ while (i--) {
34
+ try {
35
+ item = list[i] + ".XMLHTTP";
36
+ var obj = new ActiveXObject(item);
37
+ return item;
38
+ }
39
+ catch (e) {
40
+ }
41
+ }
42
+ }());
43
+ return function(){
44
+ return new ActiveXObject(item);
45
+ };
46
+ }
47
+ }());
48
+
49
+ // this file is by default set up to use Access Control - this means that it will use the headers set by the server to decide whether or not to allow the call to return
50
+ // TODO turn this back on once the CORS headers are implemented
51
+ var useAccessControl = false;
52
+ // always trusted origins, can be exact strings or regular expressions
53
+ var alwaysTrustedOrigins = [(/\.?easyxdm\.net/), (/xdm1/)];
54
+
55
+ // instantiate a new easyXDM object which will handle the request
56
+ var remote = new easyXDM.Rpc({
57
+ local: "../name.html",
58
+ swf: "../easyxdm.swf"
59
+ }, {
60
+ local: {
61
+ // define the exposed method
62
+ request: function(config, success, error){
63
+ // apply default values if not set
64
+ easyXDM.apply(config, {
65
+ method: "POST",
66
+ headers: {
67
+ "Content-Type": "application/x-www-form-urlencoded",
68
+ "X-Requested-With": "XMLHttpRequest"
69
+ },
70
+ success: Function.prototype,
71
+ error: function(msg){
72
+ throw new Error(msg);
73
+ },
74
+ data: {},
75
+ timeout: 10 * 1000
76
+ }, true);
77
+
78
+ var isPOST = config.method == "POST";
79
+
80
+ // convert the data into a format we can send to the server
81
+ var pairs = [];
82
+ for (var key in config.data) {
83
+ if (config.data.hasOwnProperty(key)) {
84
+ pairs.push(encodeURIComponent(key) + "=" + encodeURIComponent(config.data[key]));
85
+ }
86
+ }
87
+ var data = pairs.join("&");
88
+
89
+ // create the XMLHttpRequest object
90
+ var req = getXhr();
91
+ req.open(config.method, config.url + (isPOST ? "" : "?" + data), true);
92
+
93
+ // apply the request headers
94
+ for (var prop in config.headers) {
95
+ if (config.headers.hasOwnProperty(prop) && config.headers[prop]) {
96
+ req.setRequestHeader(prop, config.headers[prop]);
97
+ }
98
+ }
99
+
100
+ // set a timeout
101
+ var timeout;
102
+ timeout = setTimeout(function(){
103
+ req.abort();
104
+
105
+ if(req && req.readyState != 4){
106
+ error({
107
+ message: "timeout after " + config.timeout + " second",
108
+ status: 0,
109
+ data: null,
110
+ toString: function(){
111
+ return this.message + " Status: " + this.status;
112
+ }
113
+ }, null);
114
+ }
115
+ }, config.timeout);
116
+
117
+ // check if this origin should always be trusted
118
+ var alwaysTrusted = false, i = alwaysTrustedOrigins.length;
119
+ while (i-- && !alwaysTrusted) {
120
+ if (alwaysTrustedOrigins[i] instanceof RegExp) {
121
+ alwaysTrusted = alwaysTrustedOrigins[i].test(remote.origin);
122
+ }
123
+ else if (typeof alwaysTrustedOrigins[i] == "string") {
124
+ alwaysTrusted = (remote.origin === alwaysTrustedOrigins[i]);
125
+ }
126
+ }
127
+
128
+
129
+ // define the onreadystate handler
130
+ req.onreadystatechange = function(){
131
+ if (req.readyState == 4) {
132
+ clearTimeout(timeout);
133
+
134
+ // parse the response headers
135
+ var rawHeaders = req.getAllResponseHeaders(), headers = {}, headers_lowercase = {}, reHeader = /([\w-_]+):\s+(.*)$/gm, m;
136
+ while ((m = reHeader.exec(rawHeaders))) {
137
+ headers_lowercase[m[1].toLowerCase()] = headers[m[1]] = m[2];
138
+ }
139
+
140
+ // In IE status 204 is translated to 1223
141
+ var normalizedStatus = req.status;
142
+
143
+ if(normalizedStatus === 1223) {
144
+ normalizedStatus = 204;
145
+ }
146
+
147
+ if (normalizedStatus < 200 || normalizedStatus >= 300) {
148
+ if (useAccessControl) {
149
+ error("INVALID_STATUS_CODE");
150
+ }
151
+ else {
152
+ error("INVALID_STATUS_CODE", {
153
+ status: normalizedStatus,
154
+ data: req.responseText
155
+ });
156
+ }
157
+ }
158
+ else {
159
+
160
+ var errorMessage;
161
+ if (useAccessControl) {
162
+ // normalize the valuse access controls
163
+ var aclAllowedOrigin = (headers_lowercase["access-control-allow-origin"] || "").replace(/\s/g, "");
164
+ var aclAllowedMethods = (headers_lowercase["access-control-allow-methods"] || "").replace(/\s/g, "");
165
+
166
+ // determine if origin is trusted
167
+ if (alwaysTrusted || aclAllowedOrigin == "*" || aclAllowedOrigin.indexOf(remote.origin) != -1) {
168
+ // determine if the request method was allowed
169
+ if (aclAllowedMethods && aclAllowedMethods != "*" && aclAllowedMethods.indexOf(config.method) == -1) {
170
+ errorMessage = "DISALLOWED_REQUEST_METHOD";
171
+ }
172
+ }
173
+ else {
174
+ errorMessage = "DISALLOWED_ORIGIN";
175
+ }
176
+
177
+ }
178
+
179
+ if (errorMessage) {
180
+ error(errorMessage);
181
+ }
182
+ else {
183
+ success({
184
+ data: req.responseText,
185
+ status: normalizedStatus,
186
+ headers: headers
187
+ });
188
+ }
189
+ }
190
+ // reset the handler
191
+ req.onreadystatechange = Function.prototype;
192
+ req = null;
193
+ }
194
+ };
195
+
196
+ // issue the request
197
+ req.send(isPOST ? data : "");
198
+ }
199
+ }
200
+ });
201
+ </script>
202
+ </head>
203
+ <body>
204
+ </body>
205
+ </html>
@@ -0,0 +1,46 @@
1
+ module Push::Transport::Controller
2
+ class WebSocket < Cramp::Websocket
3
+ include Push::Logger
4
+
5
+ on_start :bind_queue
6
+ on_finish :unbind_queue
7
+ on_data :message_received
8
+
9
+ def bind_queue
10
+ logger.info "Subscribed to '#{exchange}'"
11
+ queue.bind(channel.fanout(exchange)).subscribe(:ack => true) {|header, message|
12
+ header.ack
13
+ render message
14
+ logger.info "Sent message: #{message}"
15
+ }
16
+ end
17
+
18
+ def unbind_queue
19
+ queue.unsubscribe {
20
+ channel.close
21
+ logger.info "Unsubscribed from '#{exchange}'"
22
+ }
23
+ end
24
+
25
+ def message_received(data)
26
+ logger.info "Received #{data}" # Who cares? Do nothing.
27
+ end
28
+
29
+ private
30
+ def channel
31
+ @channel ||= AMQP::Channel.new
32
+ end
33
+
34
+ def queue
35
+ @queue ||= channel.queue("#{sid}@#{exchange}", :arguments => {'x-expires' => Push.config.amqp.queue_ttl})
36
+ end
37
+
38
+ def sid
39
+ @sid ||= UUID.new.generate
40
+ end
41
+
42
+ def exchange
43
+ request.env['PATH_INFO']
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,3 @@
1
+ module Push
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,27 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "push/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "push"
7
+ s.version = Push::VERSION
8
+ s.authors = ["Brad Gessler"]
9
+ s.email = ["brad@bradgessler.com"]
10
+ s.homepage = ""
11
+ s.summary = %q{Build realtime Ruby web applications}
12
+ s.description = %q{Push is a realtime web application toolkit for building realtime Ruby web applications.}
13
+
14
+ s.rubyforge_project = "push"
15
+
16
+ s.files = `git ls-files`.split("\n")
17
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
+ s.require_paths = ["lib"]
20
+
21
+ # specify any dependencies here; for example:
22
+ s.add_development_dependency "rspec"
23
+ s.add_development_dependency "guard-rspec"
24
+ s.add_runtime_dependency "rack", ">= 1.1.0"
25
+ s.add_runtime_dependency "uuid"
26
+ s.add_runtime_dependency "async_sinatra"
27
+ end
@@ -0,0 +1,147 @@
1
+ require 'spec_helper'
2
+
3
+ describe Push do
4
+ before(:all) do
5
+ Push.config.backend = :test
6
+ end
7
+
8
+ context "config" do
9
+ before(:each) do
10
+ @env = Push::Configuration.new
11
+ end
12
+
13
+ it "should have backend" do
14
+ @env.backend.should eql(:amqp)
15
+ end
16
+
17
+ context "amqp" do
18
+ it "should default host to 127.0.0.1" do
19
+ @env.amqp.host.should eql('127.0.0.1')
20
+ end
21
+
22
+ it "should default username to guest" do
23
+ @env.amqp.username.should eql('guest')
24
+ end
25
+
26
+ it "should default password to guest" do
27
+ @env.amqp.password.should eql('guest')
28
+ end
29
+
30
+ it "should default vhost to /" do
31
+ @env.amqp.vhost.should eql('/')
32
+ end
33
+
34
+ it "should have queue_ttl" do
35
+ @env.amqp.queue_ttl.should eql(5)
36
+ end
37
+ end
38
+
39
+ it "should have logger" do
40
+ @env.logger.should be_instance_of(Logger)
41
+ end
42
+
43
+ context "web_socket_url" do
44
+ it "should have web_socket_url" do
45
+ @env.web_socket.url.should eql('ws://localhost:3000/_push')
46
+ end
47
+ end
48
+
49
+ context "long_poll_url" do
50
+ it "should have long_poll_url" do
51
+ @env.long_poll.url.should eql('http://localhost:3000/_push')
52
+ end
53
+
54
+ it "should have long_poll_timeout" do
55
+ @env.long_poll.timeout.should eql(30)
56
+ end
57
+ end
58
+
59
+ context "from_hash" do
60
+ before(:each) do
61
+ @env.from_hash({
62
+ 'backend' => 'test',
63
+ 'web_socket' => {
64
+ 'url' => 'ws://push.polleverywhere.com'
65
+ },
66
+ 'long_poll' => {
67
+ 'url' => 'http://push.polleverywhere.com'
68
+ },
69
+ 'amqp' => {
70
+ 'host' => 'intra.push.polleverywhere.net',
71
+ 'port' => 999,
72
+ 'username' => 'brad',
73
+ 'password' => 'fun',
74
+ 'queue_ttl' => 10,
75
+ 'vhost' => 'hi'
76
+ }
77
+ })
78
+ end
79
+
80
+ it "should config backend" do
81
+ @env.backend.should eql(:test)
82
+ end
83
+
84
+ it "should config web_socket url" do
85
+ @env.web_socket.url.should eql('ws://push.polleverywhere.com')
86
+ end
87
+
88
+ it "should config long_poll url" do
89
+ @env.long_poll.url.should eql('http://push.polleverywhere.com')
90
+ end
91
+
92
+ context "amqp" do
93
+ it "should config host" do
94
+ @env.amqp.host.should eql('intra.push.polleverywhere.net')
95
+ end
96
+
97
+ it "should config username" do
98
+ @env.amqp.username.should eql('brad')
99
+ end
100
+
101
+ it "should config password" do
102
+ @env.amqp.password.should eql('fun')
103
+ end
104
+
105
+ it "should config vhost" do
106
+ @env.amqp.vhost.should eql('hi')
107
+ end
108
+
109
+ it "should config queue_ttl" do
110
+ @env.amqp.queue_ttl.should eql(10)
111
+ end
112
+ end
113
+
114
+ end
115
+ end
116
+
117
+ context "backend" do
118
+ context "adapters" do
119
+ before(:all) do
120
+ @an_adapter = Class.new
121
+ end
122
+
123
+ it "register new adapter" do
124
+ lambda{
125
+ Push::Backend.register_adapter(:super_cool, @an_adapter)
126
+ }.should change(Push::Backend.adapters, :count).by(1)
127
+ end
128
+
129
+ it "should return instance of an adapter" do
130
+ Push::Backend.register_adapter(:super_cool, @an_adapter)
131
+ Push::Backend.adapter(:super_cool).should be_an_instance_of(@an_adapter)
132
+ end
133
+ end
134
+ end
135
+
136
+ context "producer" do
137
+ before(:all) do
138
+ @producer = Push::Producer.new
139
+ end
140
+
141
+ it "should publish" do
142
+ lambda{
143
+ @producer.publish('hi').to('/exchange')
144
+ }.should change(@producer.backend.exchange['/exchange'], :count).by(1)
145
+ end
146
+ end
147
+ end
@@ -0,0 +1,4 @@
1
+ require 'rubygems'
2
+ require 'bundler/setup'
3
+ require 'push'
4
+ require 'rspec'
metadata ADDED
@@ -0,0 +1,127 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: push
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Brad Gessler
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2011-11-18 00:00:00.000000000Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rspec
16
+ requirement: &70258581885400 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: *70258581885400
25
+ - !ruby/object:Gem::Dependency
26
+ name: guard-rspec
27
+ requirement: &70258581884700 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: *70258581884700
36
+ - !ruby/object:Gem::Dependency
37
+ name: rack
38
+ requirement: &70258581883280 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ! '>='
42
+ - !ruby/object:Gem::Version
43
+ version: 1.1.0
44
+ type: :runtime
45
+ prerelease: false
46
+ version_requirements: *70258581883280
47
+ - !ruby/object:Gem::Dependency
48
+ name: uuid
49
+ requirement: &70258581873460 !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ type: :runtime
56
+ prerelease: false
57
+ version_requirements: *70258581873460
58
+ - !ruby/object:Gem::Dependency
59
+ name: async_sinatra
60
+ requirement: &70258581871780 !ruby/object:Gem::Requirement
61
+ none: false
62
+ requirements:
63
+ - - ! '>='
64
+ - !ruby/object:Gem::Version
65
+ version: '0'
66
+ type: :runtime
67
+ prerelease: false
68
+ version_requirements: *70258581871780
69
+ description: Push is a realtime web application toolkit for building realtime Ruby
70
+ web applications.
71
+ email:
72
+ - brad@bradgessler.com
73
+ executables: []
74
+ extensions: []
75
+ extra_rdoc_files: []
76
+ files:
77
+ - .gitignore
78
+ - .rbenv-version
79
+ - Gemfile
80
+ - Guardfile
81
+ - README.md
82
+ - Rakefile
83
+ - lib/push.rb
84
+ - lib/push/backend.rb
85
+ - lib/push/configuration.rb
86
+ - lib/push/logging.rb
87
+ - lib/push/producer.rb
88
+ - lib/push/transport.rb
89
+ - lib/push/transport/controller/http_long_poll.rb
90
+ - lib/push/transport/controller/http_long_poll/public/flash/easyxdm.swf
91
+ - lib/push/transport/controller/http_long_poll/public/javascripts/easyXDM.debug.js
92
+ - lib/push/transport/controller/http_long_poll/public/javascripts/easyXDM.js
93
+ - lib/push/transport/controller/http_long_poll/public/javascripts/json2.js
94
+ - lib/push/transport/controller/http_long_poll/views/proxy.erb
95
+ - lib/push/transport/controller/web_socket.rb
96
+ - lib/push/version.rb
97
+ - push.gemspec
98
+ - spec/pusher_spec.rb
99
+ - spec/spec_helper.rb
100
+ homepage: ''
101
+ licenses: []
102
+ post_install_message:
103
+ rdoc_options: []
104
+ require_paths:
105
+ - lib
106
+ required_ruby_version: !ruby/object:Gem::Requirement
107
+ none: false
108
+ requirements:
109
+ - - ! '>='
110
+ - !ruby/object:Gem::Version
111
+ version: '0'
112
+ required_rubygems_version: !ruby/object:Gem::Requirement
113
+ none: false
114
+ requirements:
115
+ - - ! '>='
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ requirements: []
119
+ rubyforge_project: push
120
+ rubygems_version: 1.8.10
121
+ signing_key:
122
+ specification_version: 3
123
+ summary: Build realtime Ruby web applications
124
+ test_files:
125
+ - spec/pusher_spec.rb
126
+ - spec/spec_helper.rb
127
+ has_rdoc: