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 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 | EPOLLPRI))
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, RSTRING(Data)->len, MSG_NOSIGNAL);
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
- result = @@channels[channel] && @@channels[channel].waiters.empty?
37
- @@channels.delete(channel) if result
38
- result
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
@@ -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
- @query += "&" +FormEncoder.encode(params) if params
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
- iframe.onload = function(){setTimeout(remove, 0)};
184
- iframe.src = '#{@params['execute']}/#{id}?#{@query}';
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='text/javascript'
195
- src='http://alphastars.drecom.jp/javascripts/prototype.js'
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
- #{path.to_json}, {method: 'post', evalScript: true,
202
- onComplete: function(xhr){
203
- setTimeout(connect,
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.3'
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
- unless File.exist?(meteor_strike_dir)
52
- src_dir = File.join(File.dirname(__FILE__),
53
- '../vendor/plugins/meteor_strike')
54
- `cp -R #{src_dir} #{meteor_strike_dir}`
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
@@ -0,0 +1,2 @@
1
+ module MeteorHelper
2
+ 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.3
7
- date: 2007-03-23 00:00:00 +09:00
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