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 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
- // We need to wait at least 1 millisecond to avoid busy loop.
243
- while((s = asteroid_poll_wait(epoll_fd, events, EVENT_BUF_SIZE, 1)) > 0){
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
@@ -7,7 +7,7 @@ require 'shooting_star/config'
7
7
  require 'shooting_star/shooter'
8
8
 
9
9
  module ShootingStar
10
- VERSION = '1.0.5'
10
+ VERSION = '2.0.0'
11
11
  CONFIG = Config.new(
12
12
  :config => 'config/shooting_star.yml',
13
13
  :pid_file => 'log/shooting_star.pid',
@@ -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
- response if @data[-4..-1] == "\r\n\r\n"
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
- # an accessor which maps signatures to servers.
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 an event to clients.
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(){clearTimeout(timer); setTimeout(remove, 0)};
189
- document.body.appendChild(iframe);
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 request = new Ajax.Request([#{path.to_json},
208
- new Number(new Date()).toString(32),
209
- reconnect && '&__t__=rc'
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
@@ -42,7 +42,7 @@ module ShootingStar
42
42
 
43
43
  def executed(sig, id)
44
44
  ::ShootingStar::Server[sig].executed(id)
45
- rescue
45
+ rescue Exception
46
46
  end
47
47
 
48
48
  private
@@ -10,7 +10,7 @@ class MeteorController < ApplicationController
10
10
  @javascript = meteor.javascript
11
11
  else
12
12
  @javascript = %Q[setTimeout(function(){
13
- meteorStrike.event[#{@channel.to_json}](#{params.to_json});}, 0);]
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 ||= DRbObject.new_with_uri(
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 = true
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
- meteorStrike.execute = function(js){eval(js)};
43
- meteorStrike.event = meteorStrike.event || $H();
44
- meteorStrike.event[channel] = function(params){#{options[:event]}};
45
- meteorStrike.update = function(uid, tags){
46
- new Ajax.Request([#{update_uri.to_json},
47
- '?channel=', channel,
48
- '&uid=', uid || UID,
49
- '&tag=', encodeTags(tags || TAGS),
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
- meteorStrike.tuneIn = function(tags){
54
- meteorStrike.update(UID, TAGS.concat(tags).uniq());
68
+ ms.tuneIn = function(tags){
69
+ ms.update(UID, TAGS.concat(tags || []).uniq());
55
70
  };
56
- meteorStrike.tuneOut = function(tags){
57
- meteorStrike.update(UID, Array.prototype.without.apply(TAGS, tags));
71
+ ms.tuneOut = function(tags){
72
+ ms.update(UID, Array.prototype.without.apply(TAGS, tags));
58
73
  };
59
74
  setTimeout(function(){
60
- var iframe = document.createElement('iframe');
61
- iframe.src = ["#{base_uri}&uid=#{uid}&tag=#{tag}&sig=#{sig}",
62
- '&__ts__=', new Number(new Date()).toString(32)].join('');
63
- iframe.frameborder = "0";
64
- iframe.width = "0";
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: 1.0.5
7
- date: 2007-04-17 00:00:00 +09:00
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