shooting_star 1.0.3 → 1.0.4
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +15 -0
- data/Manifest.txt +1 -0
- data/Rakefile +2 -0
- data/ext/asteroid.c +18 -4
- data/lib/shooting_star/channel.rb +4 -3
- data/lib/shooting_star/server.rb +47 -36
- data/lib/shooting_star.rb +8 -7
- data/vendor/plugins/meteor_strike/generators/meteor/meteor_generator.rb +3 -0
- data/vendor/plugins/meteor_strike/generators/meteor/templates/controller.rb +8 -1
- data/vendor/plugins/meteor_strike/generators/meteor/templates/helper.rb +2 -0
- data/vendor/plugins/meteor_strike/lib/meteor_strike.rb +34 -1
- data/vendor/plugins/meteor_strike/test/meteor_strike_test.rb +11 -3
- metadata +3 -2
data/History.txt
CHANGED
@@ -1,3 +1,18 @@
|
|
1
|
+
*** 1.0.4 / 2007-03-29
|
2
|
+
+ 2 major enhancements:
|
3
|
+
+ Cached rendering result of meteor/strike.
|
4
|
+
+ Improved error and disconnection detection when reading socket.
|
5
|
+
+ 5 minor enhancements:
|
6
|
+
+ Disabled caching when back.
|
7
|
+
+ Added meteor_helper.
|
8
|
+
+ Added timer to remove forsaken iframes for Safari.
|
9
|
+
+ Added configuration option 'sweep_timeout'.
|
10
|
+
+ Added 'session_timeout' option.
|
11
|
+
+ 2 minor bugfixes:
|
12
|
+
+ Solved problem on caching of browsers of MacOS.
|
13
|
+
+ Added workaround for Safari's strange behaviour after back navigation.
|
14
|
+
+ Solved problem on connection management.
|
15
|
+
|
1
16
|
*** 1.0.3 / 2007-03-22
|
2
17
|
+ 2 major enhancements:
|
3
18
|
+ Rails plugin is installed when shooting_star init.
|
data/Manifest.txt
CHANGED
@@ -32,6 +32,7 @@ vendor/plugins/meteor_strike/generators/meteor/meteor_generator.rb
|
|
32
32
|
vendor/plugins/meteor_strike/generators/meteor/templates
|
33
33
|
vendor/plugins/meteor_strike/generators/meteor/templates/controller.rb
|
34
34
|
vendor/plugins/meteor_strike/generators/meteor/templates/model.rb
|
35
|
+
vendor/plugins/meteor_strike/generators/meteor/templates/helper.rb
|
35
36
|
vendor/plugins/meteor_strike/generators/meteor/templates/view.rhtml
|
36
37
|
vendor/plugins/meteor_strike/generators/meteor/templates/migration.rb
|
37
38
|
vendor/plugins/meteor_strike/generators/meteor/templates/unit_test.rb
|
data/Rakefile
CHANGED
@@ -42,6 +42,8 @@ task 'update_generator_template_files' do
|
|
42
42
|
templates + '/controller.rb'
|
43
43
|
cp 'app/models/meteor.rb',
|
44
44
|
templates + '/model.rb'
|
45
|
+
cp 'app/helpers/meteor_helper.rb',
|
46
|
+
templates + '/helper.rb'
|
45
47
|
cp 'app/views/meteor/strike.rhtml',
|
46
48
|
templates + '/view.rhtml'
|
47
49
|
cp 'test/unit/meteor_test.rb',
|
data/ext/asteroid.c
CHANGED
@@ -97,7 +97,7 @@ static int asteroid_poll_wait(
|
|
97
97
|
#endif
|
98
98
|
#ifdef HAVE_SYS_EPOLL_H
|
99
99
|
#define AST_POLL_EVENT_SOCK(event) ((event)->data.fd)
|
100
|
-
#define AST_POLL_EVENT_CAN_READ(event) ((event)->events & (EPOLLIN
|
100
|
+
#define AST_POLL_EVENT_CAN_READ(event) ((event)->events & (EPOLLIN|EPOLLPRI))
|
101
101
|
#endif
|
102
102
|
|
103
103
|
#ifdef SO_NOSIGPIPE
|
@@ -206,15 +206,29 @@ static VALUE asteroid_s_stop(VALUE Self){
|
|
206
206
|
|
207
207
|
static VALUE asteroid_server_send_data(VALUE Self, VALUE Data){
|
208
208
|
VALUE Fd = rb_iv_get(Self, "@fd");
|
209
|
-
int fd = FIX2INT(Fd);
|
209
|
+
int fd = FIX2INT(Fd), remain = RSTRING(Data)->len, len;
|
210
210
|
char *data = StringValuePtr(Data);
|
211
|
-
send(fd, data,
|
211
|
+
while((len = send(fd, data, remain, MSG_NOSIGNAL)) > 0){
|
212
|
+
remain -= len;
|
213
|
+
data += len;
|
214
|
+
}
|
215
|
+
if(len == -1 && errno != EAGAIN){
|
216
|
+
if(rb_respond_to(Self, rb_intern("unbind"))){
|
217
|
+
rb_funcall(Self, rb_intern("unbind"), 0);
|
218
|
+
}
|
219
|
+
}
|
212
220
|
return Qnil;
|
213
221
|
}
|
214
222
|
|
215
223
|
static VALUE asteroid_server_write_and_close(VALUE Self){
|
216
224
|
VALUE Fd = rb_iv_get(Self, "@fd");
|
217
225
|
int fd = FIX2INT(Fd);
|
226
|
+
char buf[1];
|
227
|
+
if(read(fd, buf, 1) == -1 && errno != EAGAIN){
|
228
|
+
if(rb_respond_to(Self, rb_intern("unbind"))){
|
229
|
+
rb_funcall(Self, rb_intern("unbind"), 0);
|
230
|
+
}
|
231
|
+
}
|
218
232
|
asteroid_poll_event_t event;
|
219
233
|
memset(&event, 0, sizeof(event));
|
220
234
|
asteroid_poll_remove(epoll_fd, &event, fd);
|
@@ -239,7 +253,7 @@ int dispatch(){
|
|
239
253
|
buf[len] = '\0';
|
240
254
|
rb_str_concat(Buf, rb_str_new2(buf));
|
241
255
|
}
|
242
|
-
if(len == -1){
|
256
|
+
if(len == -1 && errno == EAGAIN){
|
243
257
|
if(rb_respond_to(Server, rb_intern("receive_data"))){
|
244
258
|
rb_funcall(Server, rb_intern("receive_data"), 1, Buf);
|
245
259
|
}
|
@@ -33,9 +33,10 @@ module ShootingStar
|
|
33
33
|
def self.sweep; @@channels.delete_if{|k,v| v.waiters.empty?} end
|
34
34
|
|
35
35
|
def self.cleanup(channel)
|
36
|
-
|
37
|
-
|
38
|
-
|
36
|
+
if @@channels[channel] && @@channels[channel].waiters.empty?
|
37
|
+
@@channels.delete(channel)
|
38
|
+
end
|
39
|
+
!@@channels.include?(channel)
|
39
40
|
end
|
40
41
|
end
|
41
42
|
end
|
data/lib/shooting_star/server.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'json'
|
2
2
|
require 'cgi'
|
3
|
+
require 'uri'
|
3
4
|
require 'md5'
|
4
5
|
require 'set'
|
5
6
|
require 'form_encoder'
|
@@ -32,17 +33,20 @@ module ShootingStar
|
|
32
33
|
if channel = Channel[@channel]
|
33
34
|
channel.leave(self)
|
34
35
|
notify(:event => :leave, :uid => @uid, :tag => @tag)
|
35
|
-
Channel.cleanup(@channel)
|
36
36
|
end
|
37
37
|
@@servers.delete(@signature)
|
38
38
|
@@uids.delete(@signature)
|
39
39
|
@@tags.delete(@signature)
|
40
40
|
@@executings.delete(@signature)
|
41
41
|
log "Disconnected: #{@uid}"
|
42
|
+
if Channel.cleanup(@channel)
|
43
|
+
log "Channel closed: #{@channel}"
|
44
|
+
end
|
42
45
|
end
|
43
46
|
|
44
47
|
# respond to an execution command. it'll be buffered.
|
45
48
|
def respond(id, params)
|
49
|
+
return unbind && false unless @waiting || !session_timeout?
|
46
50
|
@executing = @@executings[@signature] ||= Hash.new
|
47
51
|
if params[:tag] && !params[:tag].empty? && !@tag.empty?
|
48
52
|
return false if (params[:tag] & @tag).empty?
|
@@ -58,6 +62,7 @@ module ShootingStar
|
|
58
62
|
return false if @execution.empty?
|
59
63
|
send_data "HTTP/1.1 200 OK\nContent-Type: text/javascript\n\n"
|
60
64
|
send_data @execution
|
65
|
+
@committed_at = Time.now
|
61
66
|
@waiting = nil
|
62
67
|
@execution = ''
|
63
68
|
@executing = Hash.new
|
@@ -94,6 +99,12 @@ module ShootingStar
|
|
94
99
|
private
|
95
100
|
def log(*arg, &block) ShootingStar::log(*arg, &block) end
|
96
101
|
|
102
|
+
# check session timeout
|
103
|
+
def session_timeout?
|
104
|
+
return true unless @committed_at
|
105
|
+
Time.now - @committed_at > ShootingStar::CONFIG.session_timeout
|
106
|
+
end
|
107
|
+
|
97
108
|
# broadcast an event to clients.
|
98
109
|
def notify(params = {})
|
99
110
|
return unless Channel[@channel]
|
@@ -112,13 +123,6 @@ module ShootingStar
|
|
112
123
|
@waiting = true
|
113
124
|
end
|
114
125
|
|
115
|
-
# clean up channel and it'll be closed if no one's listening.
|
116
|
-
def cleanup(channel)
|
117
|
-
if Channel.cleanup(channel)
|
118
|
-
log "Channel closed: #{@channel}"
|
119
|
-
end
|
120
|
-
end
|
121
|
-
|
122
126
|
# give a response to the request or keep them waiting.
|
123
127
|
def response
|
124
128
|
headers = @data.split("\n")
|
@@ -143,26 +147,24 @@ module ShootingStar
|
|
143
147
|
# load or create session informations
|
144
148
|
@signature ||= @params['sig']
|
145
149
|
@channel ||= path[1..-1].split('?', 2)[0]
|
146
|
-
@uid = @@uids[@signature] ||= @params['uid']
|
147
|
-
@tag = @@tags[@signature] ||=
|
148
|
-
(@params['tag'] || '').split(',').map{|i| CGI.unescape(i)}
|
149
|
-
@executing = @@executings[@signature] ||= Hash.new
|
150
|
-
@@servers[@signature] = self
|
151
|
-
# make uncacheable path
|
152
|
-
@timestamp = ShootingStar::timestamp
|
153
|
-
path += (path.index('?') ? '&' : '?') + "timestamp=#{@timestamp}"
|
154
150
|
@query = "channel=#{@channel}&sig=#{@signature}"
|
155
|
-
# prepare channel
|
156
|
-
unless Channel[@channel]
|
157
|
-
Channel.new(@channel)
|
158
|
-
log "Channel opened: #{@channel}"
|
159
|
-
end
|
160
151
|
# process verb
|
161
152
|
if method == 'GET'
|
162
153
|
make_connection(path)
|
163
|
-
notify(:event => :enter, :uid => @uid, :tag => @tag)
|
164
|
-
log "Connected: #{@uid}"
|
165
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
|
166
168
|
wait_for
|
167
169
|
end
|
168
170
|
rescue
|
@@ -174,33 +176,40 @@ module ShootingStar
|
|
174
176
|
|
175
177
|
# add execution line to the buffer.
|
176
178
|
def execute(id, params)
|
179
|
+
sweep_timeout = ShootingStar::CONFIG.sweep_timeout
|
177
180
|
@executing[id] = params
|
178
|
-
|
181
|
+
query = @query.sub(%r[\&sig=\d+], '')
|
182
|
+
query += "&" + FormEncoder.encode(params) if params
|
179
183
|
@execution += <<-"EOH"
|
180
184
|
(function(){
|
181
185
|
var iframe = document.createElement('iframe');
|
182
186
|
var remove = function(){document.body.removeChild(iframe)};
|
183
|
-
|
184
|
-
iframe.
|
187
|
+
var timer = setTimeout(remove, #{sweep_timeout});
|
188
|
+
iframe.onload = function(){clearTimeout(timer); setTimeout(remove, 0)};
|
185
189
|
document.body.appendChild(iframe);
|
190
|
+
iframe.src = '#{@params['execute']}/#{id}?#{query}';
|
186
191
|
})();
|
187
192
|
EOH
|
188
193
|
end
|
189
194
|
|
190
195
|
# make client connect us.
|
191
196
|
def make_connection(path)
|
197
|
+
path.sub!(%r[\&__ts__=.*$], '&__ts__=')
|
198
|
+
assets = URI.parse(@params['execute'])
|
199
|
+
assets.path = '/javascripts/prototype.js'
|
200
|
+
assets.query = assets.fragment = nil
|
192
201
|
send_data "HTTP/1.1 200 OK\nContent-Type: text/html\n\n" +
|
193
202
|
<<-"EOH"
|
194
|
-
<html><head><script type=
|
195
|
-
|
196
|
-
></script>
|
197
|
-
<script type='text/javascript'>
|
203
|
+
<html><head><script type="text/javascript" src="#{assets}"></script>
|
204
|
+
<script type="text/javascript">
|
198
205
|
//<![CDATA[
|
199
|
-
var connect = function()
|
200
|
-
{ var request = new Ajax.Request(
|
201
|
-
|
202
|
-
|
203
|
-
|
206
|
+
var connect = function(reconnect)
|
207
|
+
{ var request = new Ajax.Request([#{path.to_json},
|
208
|
+
new Number(new Date()).toString(32),
|
209
|
+
reconnect && '&__t__=rc'
|
210
|
+
].join(''),
|
211
|
+
{evalScript: true, onComplete: function(xhr){
|
212
|
+
setTimeout(function(){connect(true)},
|
204
213
|
xhr.getResponseHeader('Content-Type') ? 0 : 1000);
|
205
214
|
}});
|
206
215
|
var disconnect = function()
|
@@ -209,10 +218,12 @@ module ShootingStar
|
|
209
218
|
};
|
210
219
|
Event.observe(window, 'unload', disconnect);
|
211
220
|
};
|
212
|
-
setTimeout(connect, 0);
|
221
|
+
setTimeout(function(){connect(false)}, 0);
|
213
222
|
//]]>
|
214
223
|
</script></head><body></body></html>
|
215
224
|
EOH
|
225
|
+
rescue
|
226
|
+
ensure
|
216
227
|
write_and_close
|
217
228
|
end
|
218
229
|
end
|
data/lib/shooting_star.rb
CHANGED
@@ -7,13 +7,15 @@ require 'shooting_star/config'
|
|
7
7
|
require 'shooting_star/shooter'
|
8
8
|
|
9
9
|
module ShootingStar
|
10
|
-
VERSION = '1.0.
|
10
|
+
VERSION = '1.0.4'
|
11
11
|
CONFIG = Config.new(
|
12
12
|
:config => 'config/shooting_star.yml',
|
13
13
|
:pid_file => 'log/shooting_star.pid',
|
14
14
|
:log_file => 'log/shooting_star.log',
|
15
15
|
:daemon => false,
|
16
|
-
:slient => false
|
16
|
+
:slient => false,
|
17
|
+
:session_timeout => 10,
|
18
|
+
:sweep_timeout => 500_000)
|
17
19
|
|
18
20
|
def self.configure(options = {})
|
19
21
|
if @log_file
|
@@ -48,11 +50,10 @@ module ShootingStar
|
|
48
50
|
plugin_dir = File.join(base_dir, 'vendor/plugins')
|
49
51
|
`mkdir -p #{plugin_dir}` unless File.exist? plugin_dir
|
50
52
|
meteor_strike_dir = File.join(plugin_dir, 'meteor_strike')
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
end
|
53
|
+
src_dir = File.join(File.dirname(__FILE__),
|
54
|
+
'../vendor/plugins/meteor_strike')
|
55
|
+
`mkdir -p #{meteor_strike_dir}`
|
56
|
+
`cp -Rf #{src_dir}/* #{meteor_strike_dir}`
|
56
57
|
end
|
57
58
|
|
58
59
|
def self.start(&block)
|
@@ -23,6 +23,9 @@ class MeteorGenerator < Rails::Generator::NamedBase
|
|
23
23
|
m.template 'functional_test.rb',
|
24
24
|
File.join('test/functional', class_path, "#{controller_file}_test.rb")
|
25
25
|
|
26
|
+
m.template 'helper.rb',
|
27
|
+
File.join('app/helpers', class_path, "#{file_name}_helper.rb")
|
28
|
+
|
26
29
|
m.file 'view.rhtml',
|
27
30
|
File.join('app/views', class_path, "#{file_name}/strike.rhtml")
|
28
31
|
|
@@ -1,5 +1,7 @@
|
|
1
1
|
class MeteorController < ApplicationController
|
2
2
|
layout nil
|
3
|
+
caches_action :strike
|
4
|
+
after_filter :notify_execution, :only => [:strike]
|
3
5
|
|
4
6
|
def strike
|
5
7
|
@channel = params[:channel].split('/').map{|i| CGI.escape(i)}.join('/')
|
@@ -10,7 +12,6 @@ class MeteorController < ApplicationController
|
|
10
12
|
@javascript = %Q[setTimeout(function(){
|
11
13
|
meteorStrike.event[#{@channel.to_json}](#{params.to_json});}, 0);]
|
12
14
|
end
|
13
|
-
Meteor.shooter.executed(params[:sig], params[:id])
|
14
15
|
end
|
15
16
|
|
16
17
|
def update
|
@@ -21,5 +22,11 @@ class MeteorController < ApplicationController
|
|
21
22
|
|
22
23
|
def sweep
|
23
24
|
Meteor.shooter.sweep
|
25
|
+
render :nothing => true
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
def notify_execution
|
30
|
+
Meteor.shooter.executed(params[:sig], params[:id])
|
24
31
|
end
|
25
32
|
end
|
@@ -3,7 +3,21 @@ require 'md5'
|
|
3
3
|
|
4
4
|
module MeteorStrike
|
5
5
|
module Helper
|
6
|
+
def self.included(base)
|
7
|
+
base.class_eval do
|
8
|
+
alias_method_chain :form_tag, :timestamp
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
6
12
|
def meteor_strike(channel, options = {})
|
13
|
+
unless options[:cache] || @meteor_strike
|
14
|
+
cc = controller.headers['Cache-Control'] || ''
|
15
|
+
cc += ', ' unless cc.empty?
|
16
|
+
cc += 'no-store, no-cache, must-revalidate, max-age=0, '
|
17
|
+
cc += 'post-check=0, pre-check=0'
|
18
|
+
controller.headers['Cache-Control'] = cc
|
19
|
+
@meteor_strike = true
|
20
|
+
end
|
7
21
|
config = ActiveRecord::Base.configurations[RAILS_ENV]
|
8
22
|
shooting_star_uri = "#{config['shooting_star']['server']}/#{channel}"
|
9
23
|
uri = url_for(:only_path => false).split('/')[0..2].join('/')
|
@@ -44,7 +58,8 @@ module MeteorStrike
|
|
44
58
|
};
|
45
59
|
setTimeout(function(){
|
46
60
|
var iframe = document.createElement('iframe');
|
47
|
-
iframe.src = "#{base_uri}&uid=#{uid}&tag=#{tag}&sig=#{sig}"
|
61
|
+
iframe.src = ["#{base_uri}&uid=#{uid}&tag=#{tag}&sig=#{sig}",
|
62
|
+
'&__ts__=', new Number(new Date()).toString(32)].join('');
|
48
63
|
iframe.frameborder = "0";
|
49
64
|
iframe.width = "0";
|
50
65
|
iframe.height = "0";
|
@@ -57,5 +72,23 @@ module MeteorStrike
|
|
57
72
|
</script>
|
58
73
|
EOH
|
59
74
|
end
|
75
|
+
|
76
|
+
private
|
77
|
+
# Workaround for Safari's strange behaviour after back navigation.
|
78
|
+
# Safari never posts if form elements are not modified since back
|
79
|
+
# navigation. So we append timestamp before submitting.
|
80
|
+
def form_tag_with_timestamp(urlop = {}, options = {}, *arg, &block)
|
81
|
+
options = options.stringify_keys
|
82
|
+
(options['onsubmit'] ||= '').insert(0, %Q[
|
83
|
+
if(!this.__ts__){
|
84
|
+
var ts = document.createElement('input');
|
85
|
+
ts.name = ts.id = '__ts__';
|
86
|
+
ts.type = 'hidden';
|
87
|
+
this.appendChild(ts);
|
88
|
+
}
|
89
|
+
this.__ts__.value = new Number(new Date()).toString(32);
|
90
|
+
]) unless /^get$/i === options['method']
|
91
|
+
form_tag_without_timestamp(urlop, options, *arg, &block)
|
92
|
+
end
|
60
93
|
end
|
61
94
|
end
|
@@ -1,15 +1,23 @@
|
|
1
1
|
require 'test/unit'
|
2
|
+
require 'rubygems'
|
3
|
+
require 'active_support'
|
2
4
|
require File.join(File.dirname(__FILE__), '../lib/meteor_strike')
|
3
5
|
begin
|
4
|
-
require 'rubygems'
|
5
6
|
require 'redgreen'
|
6
7
|
rescue
|
7
8
|
end
|
8
9
|
|
9
10
|
class MeteorStrikeTest < Test::Unit::TestCase
|
10
|
-
include MeteorStrike::Helper
|
11
|
-
|
12
11
|
def test_meteor_strike
|
13
12
|
assert respond_to?(:meteor_strike)
|
13
|
+
form_tag
|
14
|
+
assert @form_tag
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
def form_tag(*arg, &block)
|
19
|
+
@form_tag = true
|
14
20
|
end
|
21
|
+
|
22
|
+
include MeteorStrike::Helper
|
15
23
|
end
|
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: 1.0.
|
7
|
-
date: 2007-03-
|
6
|
+
version: 1.0.4
|
7
|
+
date: 2007-03-29 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
|
@@ -64,6 +64,7 @@ files:
|
|
64
64
|
- vendor/plugins/meteor_strike/generators/meteor/templates
|
65
65
|
- vendor/plugins/meteor_strike/generators/meteor/templates/controller.rb
|
66
66
|
- vendor/plugins/meteor_strike/generators/meteor/templates/model.rb
|
67
|
+
- vendor/plugins/meteor_strike/generators/meteor/templates/helper.rb
|
67
68
|
- vendor/plugins/meteor_strike/generators/meteor/templates/view.rhtml
|
68
69
|
- vendor/plugins/meteor_strike/generators/meteor/templates/migration.rb
|
69
70
|
- vendor/plugins/meteor_strike/generators/meteor/templates/unit_test.rb
|