shooting_star 1.0.5 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +10 -0
- data/ext/asteroid.c +5 -2
- data/lib/shooting_star.rb +1 -1
- data/lib/shooting_star/server.rb +64 -63
- data/lib/shooting_star/shooter.rb +1 -1
- data/vendor/plugins/meteor_strike/generators/meteor/templates/controller.rb +1 -1
- data/vendor/plugins/meteor_strike/generators/meteor/templates/model.rb +36 -2
- data/vendor/plugins/meteor_strike/generators/meteor/templates/view.rhtml +1 -1
- data/vendor/plugins/meteor_strike/lib/meteor_strike.rb +39 -27
- metadata +2 -2
data/History.txt
CHANGED
@@ -1,3 +1,13 @@
|
|
1
|
+
*** 2.0.0 / 2007-05-07
|
2
|
+
+ 2 major enhancements:
|
3
|
+
+ Comet server clustering.
|
4
|
+
+ Arbitrary subdomain acceptance.
|
5
|
+
+ 4 minor enhancements:
|
6
|
+
+ Unlimited use of tags.
|
7
|
+
+ Detailed error reporting.
|
8
|
+
+ All exceptions came to be caught.
|
9
|
+
+ Cooperated with ruby's threading mechanism.
|
10
|
+
|
1
11
|
*** 1.0.5 / 2007-04-18
|
2
12
|
+ 2 minor enhancements:
|
3
13
|
+ Added signature to log.
|
data/ext/asteroid.c
CHANGED
@@ -239,8 +239,11 @@ static VALUE asteroid_server_write_and_close(VALUE Self){
|
|
239
239
|
|
240
240
|
int dispatch(){
|
241
241
|
int i, s, len;
|
242
|
-
|
243
|
-
|
242
|
+
while(1){
|
243
|
+
TRAP_BEG;
|
244
|
+
s = asteroid_poll_wait(epoll_fd, events, EVENT_BUF_SIZE, -1);
|
245
|
+
TRAP_END;
|
246
|
+
if(s <= 0) break;
|
244
247
|
for(i = 0; i < s; ++i){
|
245
248
|
asteroid_poll_event_t event = events[i];
|
246
249
|
int fd = AST_POLL_EVENT_SOCK(&event);
|
data/lib/shooting_star.rb
CHANGED
data/lib/shooting_star/server.rb
CHANGED
@@ -9,6 +9,8 @@ module ShootingStar
|
|
9
9
|
# The module which will be included by servant who was born in the Asteroid.
|
10
10
|
# This idea is from EventMachine.
|
11
11
|
module Server
|
12
|
+
class MethodNotAcceptable < StandardError; end
|
13
|
+
|
12
14
|
attr_reader :signature
|
13
15
|
@@servers = {}
|
14
16
|
@@uids = {}
|
@@ -24,7 +26,54 @@ module ShootingStar
|
|
24
26
|
# receive the data sent from client.
|
25
27
|
def receive_data(data)
|
26
28
|
@data += data
|
27
|
-
|
29
|
+
header, body = @data.split(/\n\n|\r\r|\n\r\n\r|\r\n\r\n/, 2)
|
30
|
+
return unless body
|
31
|
+
data = @data
|
32
|
+
headers = header.split(/[\n\r]+/)
|
33
|
+
head = headers.shift
|
34
|
+
method, path, protocol = head.split(/\s+/)
|
35
|
+
raise MethodNotAcceptable unless method.downcase == 'post'
|
36
|
+
# recognize header
|
37
|
+
hdr = headers.inject({}) do |hash, line|
|
38
|
+
key, value = line.split(/ *?: */, 2)
|
39
|
+
hash[key.downcase] = value if key
|
40
|
+
hash
|
41
|
+
end
|
42
|
+
# check data arrival
|
43
|
+
return if body.length < hdr['content-length'].to_i
|
44
|
+
@data = ''
|
45
|
+
# recognize parameter
|
46
|
+
@params = Hash.new
|
47
|
+
body.split('&').each do |item|
|
48
|
+
key, value = item.split('=', 2)
|
49
|
+
@params[key] = CGI.unescape(value) if value && value.length > 0
|
50
|
+
end
|
51
|
+
# load or create session informations
|
52
|
+
@signature ||= @params['sig']
|
53
|
+
@channel ||= path[1..-1].split('?', 2)[0]
|
54
|
+
@query = "channel=#{@channel}&sig=#{@signature}"
|
55
|
+
# process verb
|
56
|
+
unless @params['__t__']
|
57
|
+
make_connection(path)
|
58
|
+
else
|
59
|
+
unless Channel[@channel]
|
60
|
+
Channel.new(@channel)
|
61
|
+
log "Channel opened: #{@channel}"
|
62
|
+
end
|
63
|
+
unless @@servers[@signature] || @params['__t__'] == 'rc'
|
64
|
+
notify(:event => :enter, :uid => @uid, :tag => @tag)
|
65
|
+
log "Connected: #{@uid}"
|
66
|
+
end
|
67
|
+
@uid = @@uids[@signature] ||= @params['uid']
|
68
|
+
@tag = @@tags[@signature] ||=
|
69
|
+
(@params['tag'] || '').split(',').map{|i| CGI.unescape(i)}
|
70
|
+
@executing = @@executings[@signature] ||= Hash.new
|
71
|
+
@@servers[@signature] = self
|
72
|
+
wait_for
|
73
|
+
end
|
74
|
+
rescue Exception => e
|
75
|
+
log "ERROR: #{e.message}\n#{e.backtrace.join("\n")}\n#{data}"
|
76
|
+
write_and_close
|
28
77
|
end
|
29
78
|
|
30
79
|
# detect disconnection from the client and clean it up.
|
@@ -91,7 +140,7 @@ module ShootingStar
|
|
91
140
|
def uid; @@uids[@signature] end
|
92
141
|
def tag; @@tags[@signature] end
|
93
142
|
|
94
|
-
#
|
143
|
+
# accessor which maps signatures to servers.
|
95
144
|
def self.[](signature)
|
96
145
|
@@servers[signature]
|
97
146
|
end
|
@@ -105,7 +154,7 @@ module ShootingStar
|
|
105
154
|
Time.now - @committed_at > ShootingStar::CONFIG.session_timeout
|
106
155
|
end
|
107
156
|
|
108
|
-
# broadcast
|
157
|
+
# broadcast event to clients.
|
109
158
|
def notify(params = {})
|
110
159
|
return unless Channel[@channel]
|
111
160
|
event_id = ShootingStar::timestamp
|
@@ -123,57 +172,6 @@ module ShootingStar
|
|
123
172
|
@waiting = true
|
124
173
|
end
|
125
174
|
|
126
|
-
# give a response to the request or keep them waiting.
|
127
|
-
def response
|
128
|
-
headers = @data.split("\n")
|
129
|
-
head = headers.shift
|
130
|
-
method, path, protocol = head.split(/\s+/)
|
131
|
-
# recognize header
|
132
|
-
hdr = headers.inject({}) do |hash, line|
|
133
|
-
key, value = line.chop.split(/ *?: */, 2)
|
134
|
-
hash[key.downcase] = value if key
|
135
|
-
hash
|
136
|
-
end
|
137
|
-
# recognize parameter
|
138
|
-
@params = Hash.new
|
139
|
-
if @query = path.split('?', 2)[1]
|
140
|
-
if @query = @query.split('#', 2)[0]
|
141
|
-
@query.split('&').each do |item|
|
142
|
-
key, value = item.split('=', 2)
|
143
|
-
@params[key] = CGI.unescape(value) if value && value.length > 0
|
144
|
-
end
|
145
|
-
end
|
146
|
-
end
|
147
|
-
# load or create session informations
|
148
|
-
@signature ||= @params['sig']
|
149
|
-
@channel ||= path[1..-1].split('?', 2)[0]
|
150
|
-
@query = "channel=#{@channel}&sig=#{@signature}"
|
151
|
-
# process verb
|
152
|
-
if method == 'GET'
|
153
|
-
make_connection(path)
|
154
|
-
else
|
155
|
-
unless Channel[@channel]
|
156
|
-
Channel.new(@channel)
|
157
|
-
log "Channel opened: #{@channel}"
|
158
|
-
end
|
159
|
-
unless @@servers[@signature] || @params['__t__'] == 'rc'
|
160
|
-
notify(:event => :enter, :uid => @uid, :tag => @tag)
|
161
|
-
log "Connected: #{@uid}"
|
162
|
-
end
|
163
|
-
@uid = @@uids[@signature] ||= @params['uid']
|
164
|
-
@tag = @@tags[@signature] ||=
|
165
|
-
(@params['tag'] || '').split(',').map{|i| CGI.unescape(i)}
|
166
|
-
@executing = @@executings[@signature] ||= Hash.new
|
167
|
-
@@servers[@signature] = self
|
168
|
-
wait_for
|
169
|
-
end
|
170
|
-
rescue
|
171
|
-
log "ERROR: #{$!.pretty_print}\n#{@data}"
|
172
|
-
raise
|
173
|
-
ensure
|
174
|
-
@data = ''
|
175
|
-
end
|
176
|
-
|
177
175
|
# add execution line to the buffer.
|
178
176
|
def execute(id, params)
|
179
177
|
sweep_timeout = ShootingStar::CONFIG.sweep_timeout
|
@@ -185,33 +183,36 @@ module ShootingStar
|
|
185
183
|
var iframe = document.createElement('iframe');
|
186
184
|
var remove = function(){document.body.removeChild(iframe)};
|
187
185
|
var timer = setTimeout(remove, #{sweep_timeout});
|
188
|
-
iframe.onload = function(){
|
189
|
-
|
186
|
+
iframe.onload = function(){
|
187
|
+
clearTimeout(timer);
|
188
|
+
setTimeout(remove, 0);
|
189
|
+
};
|
190
190
|
iframe.src = '#{@params['execute']}/#{id}?#{query}';
|
191
|
+
document.body.appendChild(iframe);
|
191
192
|
})();
|
192
193
|
EOH
|
193
194
|
end
|
194
195
|
|
195
196
|
# make client connect us.
|
196
197
|
def make_connection(path)
|
197
|
-
path.sub!(%r[\&__ts__=.*$], '&__ts__=')
|
198
|
+
#path.sub!(%r[\&__ts__=.*$], '&__ts__=')
|
198
199
|
assets = URI.parse(@params['execute'])
|
199
200
|
assets.path = '/javascripts/prototype.js'
|
200
201
|
assets.query = assets.fragment = nil
|
202
|
+
|
201
203
|
send_data "HTTP/1.1 200 OK\nContent-Type: text/html\n\n" +
|
202
204
|
<<-"EOH"
|
203
205
|
<html><head><script type="text/javascript" src="#{assets}"></script>
|
204
206
|
<script type="text/javascript">
|
205
207
|
//<![CDATA[
|
206
208
|
var connect = function(reconnect)
|
207
|
-
{ var
|
208
|
-
|
209
|
-
|
210
|
-
].join(''),
|
209
|
+
{ var body = $H(#{@params.to_json});
|
210
|
+
body.__t__ = reconnect ? 'rc' : 'c';
|
211
|
+
var request = new Ajax.Request(#{path.to_json},
|
211
212
|
{evalScript: true, onComplete: function(xhr){
|
212
213
|
setTimeout(function(){connect(true)},
|
213
214
|
xhr.getResponseHeader('Content-Type') ? 0 : 1000);
|
214
|
-
}});
|
215
|
+
}, postBody: body.toQueryString()});
|
215
216
|
var disconnect = function()
|
216
217
|
{ request.options.onComplete = function(){};
|
217
218
|
request.transport.abort();
|
@@ -222,7 +223,7 @@ module ShootingStar
|
|
222
223
|
//]]>
|
223
224
|
</script></head><body></body></html>
|
224
225
|
EOH
|
225
|
-
rescue
|
226
|
+
rescue Exception
|
226
227
|
ensure
|
227
228
|
write_and_close
|
228
229
|
end
|
@@ -10,7 +10,7 @@ class MeteorController < ApplicationController
|
|
10
10
|
@javascript = meteor.javascript
|
11
11
|
else
|
12
12
|
@javascript = %Q[setTimeout(function(){
|
13
|
-
meteorStrike
|
13
|
+
meteorStrike[#{@channel.to_json}].event(#{params.to_json});}, 0);]
|
14
14
|
end
|
15
15
|
end
|
16
16
|
|
@@ -1,9 +1,43 @@
|
|
1
1
|
require 'drb/drb'
|
2
2
|
|
3
3
|
class Meteor < ActiveRecord::Base
|
4
|
+
class Shooter
|
5
|
+
COUNTERS = [:count, :count_with]
|
6
|
+
LISTINGS = [:listeners, :listeners_with, :channels, :signatures]
|
7
|
+
|
8
|
+
def initialize(config)
|
9
|
+
uris = config['shooting_star']['shooter']
|
10
|
+
@shooters = [uris].flatten.map{|uri| DRbObject.new_with_uri(uri)}
|
11
|
+
end
|
12
|
+
|
13
|
+
COUNTERS.each do |m|
|
14
|
+
define_method(m){|*a| call(m, *a).inject(0){|r,c| r += c}}
|
15
|
+
end
|
16
|
+
|
17
|
+
LISTINGS.each do |m|
|
18
|
+
define_method(m){|*a| call(m, *a).inject([]){|r,c| r.concat c}}
|
19
|
+
end
|
20
|
+
|
21
|
+
def method_missing(method, *args, &block)
|
22
|
+
call(method, *args, &block).first
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
def call(method, *args, &block)
|
27
|
+
@shooters.inject([]) do |result, shooter|
|
28
|
+
begin
|
29
|
+
result << shooter.__send__(method, *args, &block)
|
30
|
+
rescue Exception => e
|
31
|
+
logger.error "#{e.message}\n#{e.backtrace.join("\n")}"
|
32
|
+
ensure
|
33
|
+
result
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
4
39
|
def self.shooter
|
5
|
-
@@shooter ||=
|
6
|
-
configurations[RAILS_ENV]['shooting_star']['shooter'])
|
40
|
+
@@shooter ||= ::Meteor::Shooter.new(configurations[RAILS_ENV])
|
7
41
|
end
|
8
42
|
|
9
43
|
def self.shoot(channel, javascript, tag = [])
|
@@ -4,7 +4,7 @@
|
|
4
4
|
(function(){
|
5
5
|
var channel = #{@channel.to_json};
|
6
6
|
var javascript = #{@javascript.to_json};
|
7
|
-
var meteorStrike = parent.parent.meteorStrike;
|
7
|
+
var meteorStrike = parent.parent.meteorStrike[channel];
|
8
8
|
if(meteorStrike) meteorStrike.execute(javascript);
|
9
9
|
else alert('Connection is not established to the ShootingStar server.');
|
10
10
|
})();
|
@@ -16,21 +16,37 @@ module MeteorStrike
|
|
16
16
|
cc += 'no-store, no-cache, must-revalidate, max-age=0, '
|
17
17
|
cc += 'post-check=0, pre-check=0'
|
18
18
|
controller.headers['Cache-Control'] = cc
|
19
|
-
@meteor_strike =
|
19
|
+
@meteor_strike = 0
|
20
|
+
end
|
21
|
+
@meteor_strike += 1
|
22
|
+
config = ActiveRecord::Base.configurations[RAILS_ENV]['shooting_star']
|
23
|
+
server = config['server'].kind_of?(Array) ?
|
24
|
+
config['server'][rand(config['server'].length)] : config['server']
|
25
|
+
shooting_star_uri = "#{server}/#{channel}"
|
26
|
+
if config['random_subdomain'] && /[A-z]/ === server
|
27
|
+
subdomain = (1..6).map{(rand(26)+?a).chr}.to_s
|
28
|
+
shooting_star_uri = [subdomain, shooting_star_uri].join('.')
|
20
29
|
end
|
21
|
-
config = ActiveRecord::Base.configurations[RAILS_ENV]
|
22
|
-
shooting_star_uri = "#{config['shooting_star']['server']}/#{channel}"
|
23
30
|
uri = url_for(:only_path => false).split('/')[0..2].join('/')
|
24
31
|
uid = options[:uid] ? CGI.escape(options[:uid].to_s) : ''
|
25
32
|
tags = options[:tag] || []
|
26
33
|
tag = tags.map{|i| CGI.escape(i.to_s)}.join(',')
|
27
|
-
base_uri = "http://#{shooting_star_uri}?execute=#{uri}/meteor/strike"
|
28
34
|
update_uri = "#{uri}/meteor/update"
|
29
35
|
sig = Meteor.shooter.signature
|
36
|
+
iframe_id = "meteor-strike-#{@meteor_strike}"
|
37
|
+
iframe_body = <<-"EOH"
|
38
|
+
EOH
|
30
39
|
<<-"EOH"
|
40
|
+
<div style="position: absolute; top: -99999px; left: -99999px">
|
41
|
+
<iframe id="#{iframe_id}" name="#{iframe_id}"></iframe>
|
42
|
+
<form id="#{iframe_id}-form" target="#{iframe_id}" method="POST"
|
43
|
+
action="http://#{shooting_star_uri}">
|
44
|
+
<input name="execute" value="#{uri}/meteor/strike" />
|
45
|
+
<input name="tag" /><input name="uid" /><input name="sig" />
|
46
|
+
</form></div>
|
31
47
|
<script type="text/javascript">
|
32
48
|
//<![CDATA[
|
33
|
-
var meteorStrike;
|
49
|
+
var meteorStrike = meteorStrike || $H();
|
34
50
|
Event.observe(window, 'load', function(){
|
35
51
|
var channel = #{channel.to_json};
|
36
52
|
var UID = #{uid.to_json}, TAGS = #{tags.to_json};
|
@@ -38,33 +54,29 @@ module MeteorStrike
|
|
38
54
|
var encode = function(i){return encodeURIComponent(i)};
|
39
55
|
return $A(tags).uniq().map(encode).join(',');
|
40
56
|
};
|
41
|
-
meteorStrike = meteorStrike || new Object;
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
new Ajax.Request(
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
'&sig=', #{sig.to_json}].join(''));
|
57
|
+
var ms = meteorStrike[channel] = meteorStrike[channel] || new Object;
|
58
|
+
ms.getTags = function(){return TAGS};
|
59
|
+
ms.execute = function(js){eval(js)};
|
60
|
+
ms.event = function(params){#{options[:event]}};
|
61
|
+
ms.update = function(uid, tags){
|
62
|
+
new Ajax.Request(#{update_uri.to_json}, {postBody: $H({
|
63
|
+
channel: channel, uid: uid || UID,
|
64
|
+
tag: encodeTags(tags || TAGS), sig: #{sig.to_json}
|
65
|
+
}).toQueryString(), asynchronous: false});
|
51
66
|
UID = uid, TAGS = tags;
|
52
67
|
};
|
53
|
-
|
54
|
-
|
68
|
+
ms.tuneIn = function(tags){
|
69
|
+
ms.update(UID, TAGS.concat(tags || []).uniq());
|
55
70
|
};
|
56
|
-
|
57
|
-
|
71
|
+
ms.tuneOut = function(tags){
|
72
|
+
ms.update(UID, Array.prototype.without.apply(TAGS, tags));
|
58
73
|
};
|
59
74
|
setTimeout(function(){
|
60
|
-
var
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
iframe.height = "0";
|
66
|
-
iframe.style.border = '0px';
|
67
|
-
document.body.appendChild(iframe);
|
75
|
+
var form = $("#{iframe_id}-form");
|
76
|
+
form.uid.value = #{uid.to_json};
|
77
|
+
form.tag.value = #{tag.to_json};
|
78
|
+
form.sig.value = #{sig.to_json};
|
79
|
+
form.submit();
|
68
80
|
setTimeout(function(){#{options[:connected]}}, 0);
|
69
81
|
}, 0);
|
70
82
|
});
|
metadata
CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.9.0
|
|
3
3
|
specification_version: 1
|
4
4
|
name: shooting_star
|
5
5
|
version: !ruby/object:Gem::Version
|
6
|
-
version:
|
7
|
-
date: 2007-
|
6
|
+
version: 2.0.0
|
7
|
+
date: 2007-05-03 00:00:00 +09:00
|
8
8
|
summary: Our goal is development of practical comet server which will be achieving over 100,000 simultaneous connections per host. On this purpose, we abandon portability and use system calls depending on particular OS such as epoll and kqueue.
|
9
9
|
require_paths:
|
10
10
|
- lib
|