shooting_star 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/History.txt +42 -0
- data/Manifest.txt +38 -0
- data/README.txt +80 -0
- data/Rakefile +54 -0
- data/bin/shooting_star +70 -0
- data/ext/asteroid.c +262 -0
- data/ext/asteroid.h +4 -0
- data/ext/extconf.rb +9 -0
- data/lib/form_encoder.rb +23 -0
- data/lib/shooting_star/channel.rb +41 -0
- data/lib/shooting_star/config.rb +19 -0
- data/lib/shooting_star/server.rb +219 -0
- data/lib/shooting_star/shooter.rb +62 -0
- data/lib/shooting_star.rb +142 -0
- data/test/ext/asteroid_test.rb +60 -0
- data/test/lib/shooting_star_test.rb +55 -0
- data/test/lib/test_c10k_problem.c +47 -0
- data/test/test_helper.rb +33 -0
- data/test/test_shooting_star.rb +64 -0
- data/vendor/plugins/meteor_strike/README +50 -0
- data/vendor/plugins/meteor_strike/Rakefile +22 -0
- data/vendor/plugins/meteor_strike/generators/meteor/meteor_generator.rb +39 -0
- data/vendor/plugins/meteor_strike/generators/meteor/templates/controller.rb +25 -0
- data/vendor/plugins/meteor_strike/generators/meteor/templates/functional_test.rb +17 -0
- data/vendor/plugins/meteor_strike/generators/meteor/templates/migration.rb +13 -0
- data/vendor/plugins/meteor_strike/generators/meteor/templates/model.rb +15 -0
- data/vendor/plugins/meteor_strike/generators/meteor/templates/unit_test.rb +11 -0
- data/vendor/plugins/meteor_strike/generators/meteor/templates/view.rhtml +13 -0
- data/vendor/plugins/meteor_strike/init.rb +3 -0
- data/vendor/plugins/meteor_strike/lib/meteor_strike.rb +61 -0
- data/vendor/plugins/meteor_strike/test/meteor_strike_test.rb +15 -0
- metadata +100 -0
data/History.txt
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
*** 1.0.3 / 2007-03-22
|
2
|
+
+ 2 major enhancements:
|
3
|
+
+ Rails plugin is installed when shooting_star init.
|
4
|
+
+ Implemented logging.
|
5
|
+
+ 1 major bug fix:
|
6
|
+
+ Fixed execution queuing and flashing.
|
7
|
+
+ 3 minor enhancements:
|
8
|
+
+ Suppressed warnings reported while installing gem.
|
9
|
+
+ Added simple documentation.
|
10
|
+
+ Independent from pthread.
|
11
|
+
+ 1 minor bug fix:
|
12
|
+
+ encodeURIComponent() is used for escaping tag list instead of escape().
|
13
|
+
|
14
|
+
*** 1.0.2 / 2007-03-16
|
15
|
+
+ 1 major enhancement:
|
16
|
+
+ Separated notification by tag grouping.
|
17
|
+
|
18
|
+
*** 1.0.1 / 2007-03-12
|
19
|
+
+ 2 major enhancements:
|
20
|
+
+ Corrected management of sessions.
|
21
|
+
+ Added implementation using kqueue (Thank you Takanori Ishikawa!)
|
22
|
+
|
23
|
+
*** 1.0.0 / 2007-03-09
|
24
|
+
+ 1 major enhancement:
|
25
|
+
+ Supported tags in order to handle various events via single connection.
|
26
|
+
|
27
|
+
*** 1.0.0 / 2007-02-27
|
28
|
+
+ 2 major enhancements:
|
29
|
+
+ Added the ruby extension named Asteroid which is socket manager using epoll.
|
30
|
+
+ Merged gem and rails app for testing.
|
31
|
+
|
32
|
+
*** 0.0.3 / 2006-11-18
|
33
|
+
+ 2 major enhancements:
|
34
|
+
+ Added function to initialize directory.
|
35
|
+
+ Changed status code of execution response from 200 to 201.
|
36
|
+
+ 1 minor enhancements:
|
37
|
+
+ Exploited better algorithm for reconnection.
|
38
|
+
|
39
|
+
*** 0.0.2 / 2006-11-04
|
40
|
+
+ 2 major enhancements:
|
41
|
+
+ Supported install_gem task.
|
42
|
+
+ Converted Rakefile+gemspec to Hoe-based Rakefile.
|
data/Manifest.txt
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
History.txt
|
2
|
+
Manifest.txt
|
3
|
+
README.txt
|
4
|
+
Rakefile
|
5
|
+
bin/shooting_star
|
6
|
+
lib/shooting_star.rb
|
7
|
+
lib/shooting_star/server.rb
|
8
|
+
lib/shooting_star/shooter.rb
|
9
|
+
lib/shooting_star/channel.rb
|
10
|
+
lib/shooting_star/config.rb
|
11
|
+
lib/form_encoder.rb
|
12
|
+
ext/asteroid.c
|
13
|
+
ext/extconf.rb
|
14
|
+
ext/asteroid.h
|
15
|
+
test/test_helper.rb
|
16
|
+
test/lib/shooting_star_test.rb
|
17
|
+
test/lib/test_c10k_problem.c
|
18
|
+
test/ext/asteroid_test.rb
|
19
|
+
vendor
|
20
|
+
vendor/plugins
|
21
|
+
vendor/plugins/meteor_strike
|
22
|
+
vendor/plugins/meteor_strike/README
|
23
|
+
vendor/plugins/meteor_strike/Rakefile
|
24
|
+
vendor/plugins/meteor_strike/init.rb
|
25
|
+
vendor/plugins/meteor_strike/lib
|
26
|
+
vendor/plugins/meteor_strike/lib/meteor_strike.rb
|
27
|
+
vendor/plugins/meteor_strike/test
|
28
|
+
vendor/plugins/meteor_strike/test/meteor_strike_test.rb
|
29
|
+
vendor/plugins/meteor_strike/generators
|
30
|
+
vendor/plugins/meteor_strike/generators/meteor
|
31
|
+
vendor/plugins/meteor_strike/generators/meteor/meteor_generator.rb
|
32
|
+
vendor/plugins/meteor_strike/generators/meteor/templates
|
33
|
+
vendor/plugins/meteor_strike/generators/meteor/templates/controller.rb
|
34
|
+
vendor/plugins/meteor_strike/generators/meteor/templates/model.rb
|
35
|
+
vendor/plugins/meteor_strike/generators/meteor/templates/view.rhtml
|
36
|
+
vendor/plugins/meteor_strike/generators/meteor/templates/migration.rb
|
37
|
+
vendor/plugins/meteor_strike/generators/meteor/templates/unit_test.rb
|
38
|
+
vendor/plugins/meteor_strike/generators/meteor/templates/functional_test.rb
|
data/README.txt
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
shooting_star
|
2
|
+
by Genki Takiuchi <takiuchi@drecom.co.jp>
|
3
|
+
http://rails.office.drecom.jp/takiuchi/
|
4
|
+
http://labs.drecom.jp/
|
5
|
+
|
6
|
+
== DESCRIPTION:
|
7
|
+
|
8
|
+
Our goal is development of practical comet server which will be achieving
|
9
|
+
over 100,000 simultaneous connections per host. On this purpose, we abandon
|
10
|
+
portability and use system calls depending on particular OS such as epoll
|
11
|
+
and kqueue.
|
12
|
+
|
13
|
+
== FEATURES/PROBLEMS:
|
14
|
+
|
15
|
+
* Comet server
|
16
|
+
* Comet client implementation (Rails plugin)
|
17
|
+
|
18
|
+
== SYNOPSYS:
|
19
|
+
|
20
|
+
shooting_star {init|restart|start|stat|stop} [options]
|
21
|
+
|
22
|
+
-f <config file>
|
23
|
+
-g debug mode.
|
24
|
+
-s silent mode.
|
25
|
+
-v, --version Show version.
|
26
|
+
-h, --help Show this message.
|
27
|
+
|
28
|
+
Options for subcommand `init':
|
29
|
+
-d <base dir> ShootingStarize directory.
|
30
|
+
|
31
|
+
Options for subcommand `start':
|
32
|
+
-d daemon mode.
|
33
|
+
|
34
|
+
Options for subcommand `stat':
|
35
|
+
-u with uid.
|
36
|
+
-s with signature.
|
37
|
+
|
38
|
+
== REQUIREMENTS:
|
39
|
+
|
40
|
+
* Linux or xBSD OS having epoll or kqueue.
|
41
|
+
* Increase ulimit of nofile up to over 100,000.
|
42
|
+
(edit /etc/security/limits.conf file.)
|
43
|
+
* prototype.js 1.5.0+
|
44
|
+
* Ruby 1.8.5+
|
45
|
+
* Ruby on Rails 1.2.0+
|
46
|
+
|
47
|
+
== INSTALL:
|
48
|
+
|
49
|
+
$ sudo gem install shooting_star
|
50
|
+
$ cd /path/to/rails/root
|
51
|
+
$ shooting_star init
|
52
|
+
$ ./script/generate meteor
|
53
|
+
|
54
|
+
And then, you need to configure config/shooting_star.yml and database.yml.
|
55
|
+
See also vendor/plugins/meteor_strike/README.
|
56
|
+
|
57
|
+
== LICENSE:
|
58
|
+
|
59
|
+
(The MIT License)
|
60
|
+
|
61
|
+
Copyright (c) 2007 Genki Takiuchi
|
62
|
+
|
63
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
64
|
+
a copy of this software and associated documentation files (the
|
65
|
+
'Software'), to deal in the Software without restriction, including
|
66
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
67
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
68
|
+
permit persons to whom the Software is furnished to do so, subject to
|
69
|
+
the following conditions:
|
70
|
+
|
71
|
+
The above copyright notice and this permission notice shall be
|
72
|
+
included in all copies or substantial portions of the Software.
|
73
|
+
|
74
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
75
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
76
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
77
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
78
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
79
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
80
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
require(File.join(File.dirname(__FILE__), 'config', 'boot'))
|
2
|
+
|
3
|
+
require 'rake'
|
4
|
+
require 'rake/testtask'
|
5
|
+
require 'rake/rdoctask'
|
6
|
+
require 'tasks/rails'
|
7
|
+
|
8
|
+
namespace :gem do
|
9
|
+
require 'hoe'
|
10
|
+
|
11
|
+
$: << './lib'
|
12
|
+
$: << './ext'
|
13
|
+
require 'shooting_star'
|
14
|
+
|
15
|
+
Hoe.new('shooting_star', ShootingStar::VERSION) do |hoe|
|
16
|
+
hoe.author = 'Genki Takiuchi'
|
17
|
+
hoe.email = 'takiuchi@drecom.co.jp'
|
18
|
+
hoe.description = 'Comet server.'
|
19
|
+
hoe.rubyforge_name = 'shooting-star'
|
20
|
+
hoe.summary = hoe.paragraphs_of('README.txt', 2)[0]
|
21
|
+
hoe.description = hoe.paragraphs_of('README.txt', 2..5).join("\n\n")
|
22
|
+
hoe.url = hoe.paragraphs_of('README.txt', 0).first.split(/\n/)[1..-1]
|
23
|
+
hoe.changes = hoe.paragraphs_of('History.txt', 0..1).join("\n\n")
|
24
|
+
hoe.spec_extras = {
|
25
|
+
:extensions => 'ext/extconf.rb',
|
26
|
+
:rdoc_options => ['-S',
|
27
|
+
'--template', 'kilmer',
|
28
|
+
'--main', 'README.txt',
|
29
|
+
'--exclude', 'ext/asteroid',
|
30
|
+
'README.txt']
|
31
|
+
}
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
desc 'default gem task.'
|
36
|
+
task :gem => 'gem:default'
|
37
|
+
|
38
|
+
desc 'update generator template files.'
|
39
|
+
task 'update_generator_template_files' do
|
40
|
+
templates = 'vendor/plugins/meteor_strike/generators/meteor/templates'
|
41
|
+
cp 'app/controllers/meteor_controller.rb',
|
42
|
+
templates + '/controller.rb'
|
43
|
+
cp 'app/models/meteor.rb',
|
44
|
+
templates + '/model.rb'
|
45
|
+
cp 'app/views/meteor/strike.rhtml',
|
46
|
+
templates + '/view.rhtml'
|
47
|
+
cp 'test/unit/meteor_test.rb',
|
48
|
+
templates + '/unit_test.rb'
|
49
|
+
cp 'test/functional/meteor_controller_test.rb',
|
50
|
+
templates + '/functional_test.rb'
|
51
|
+
end
|
52
|
+
|
53
|
+
desc 'test all tests'
|
54
|
+
task 'test:all' => [:test, 'test:plugins', 'test:exts', 'test:libs']
|
data/bin/shooting_star
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'optparse'
|
3
|
+
|
4
|
+
$:.unshift File.join(File.dirname(__FILE__), '../lib')
|
5
|
+
$:.unshift File.join(File.dirname(__FILE__), '../ext')
|
6
|
+
require 'shooting_star'
|
7
|
+
|
8
|
+
# define options
|
9
|
+
$command_line = "#{$0} #{ARGV.join(' ')}"
|
10
|
+
OPT = {}
|
11
|
+
opt = OptionParser.new
|
12
|
+
opt.on('-f <config file>'){|v| OPT[:config] = v}
|
13
|
+
opt.on('-g', 'debug mode.'){$debug = true}
|
14
|
+
opt.on('-s', 'silent mode.'){OPT[:silent] = true}
|
15
|
+
subopt = Hash.new{|h,k| $stderr.puts "no such subcommand: #{k}."; exit 1}
|
16
|
+
subopt['init'] = OptionParser.new do |cmd|
|
17
|
+
cmd.banner = ['Usage:', cmd.program_name, 'init [options]'].join(' ')
|
18
|
+
cmd.on('-d <base dir>', 'ShootingStarize directory.'){|v| OPT[:directory] = v}
|
19
|
+
end
|
20
|
+
subopt['start'] = OptionParser.new do |cmd|
|
21
|
+
cmd.banner = ['Usage:', cmd.program_name, 'start [options]'].join(' ')
|
22
|
+
cmd.on('-d', 'daemon mode.'){OPT[:daemon] = true}
|
23
|
+
end
|
24
|
+
subopt['stop'] = OptionParser.new do |cmd| end
|
25
|
+
subopt['restart'] = OptionParser.new do |cmd| end
|
26
|
+
subopt['stat'] = OptionParser.new do |cmd|
|
27
|
+
cmd.banner = ['Usage:', cmd.program_name, 'stat [options]'].join(' ')
|
28
|
+
cmd.on('-u', 'with uid.'){OPT[:with_uid] = true}
|
29
|
+
cmd.on('-s', 'with signature.'){OPT[:with_sig] = true}
|
30
|
+
end
|
31
|
+
opt.on_tail('-v', '--version', 'Show version.'){puts opt.ver; exit}
|
32
|
+
opt.on_tail('-h', '--help', 'Show this message.') do
|
33
|
+
opt.banner = " " + ['Usage:', opt.program_name,
|
34
|
+
"{#{subopt.keys.sort.join('|')}}", '[options]'].join(' ') + "\n\n"
|
35
|
+
puts "#{'-'*opt.ver.length}\n#{opt.ver}\n#{'-'*opt.ver.length}"
|
36
|
+
puts opt.help
|
37
|
+
subopt.keys.sort.each do |key| cmd = subopt[key]
|
38
|
+
next if cmd.to_a.size <= 1
|
39
|
+
cmd.banner = "\n Options for subcommand `#{key}':\n"
|
40
|
+
puts cmd.help
|
41
|
+
end; exit
|
42
|
+
end
|
43
|
+
|
44
|
+
# parse options
|
45
|
+
Version = ShootingStar::VERSION
|
46
|
+
opt.order!(ARGV)
|
47
|
+
CMD = ARGV.shift
|
48
|
+
begin
|
49
|
+
subopt[CMD].parse!(ARGV) unless ARGV.empty?
|
50
|
+
rescue OptionParser::InvalidOption => e
|
51
|
+
puts e.message; exit
|
52
|
+
end
|
53
|
+
ShootingStar.configure(OPT)
|
54
|
+
OPT[:daemon] = true if CMD == 'restart'
|
55
|
+
|
56
|
+
# execute
|
57
|
+
case CMD
|
58
|
+
when "init"
|
59
|
+
ShootingStar.init
|
60
|
+
when "start"
|
61
|
+
ShootingStar.start
|
62
|
+
when "stop"
|
63
|
+
ShootingStar.stop
|
64
|
+
when "restart"
|
65
|
+
ShootingStar.restart
|
66
|
+
when "stat"
|
67
|
+
ShootingStar.report
|
68
|
+
else
|
69
|
+
puts `#{$0} --help`
|
70
|
+
end
|
data/ext/asteroid.c
ADDED
@@ -0,0 +1,262 @@
|
|
1
|
+
/* --------------------------------------------------------------------------
|
2
|
+
* Asteroid ruby extension.
|
3
|
+
* by Genki Takiuchi <takiuchi@drecom.co.jp>
|
4
|
+
* -------------------------------------------------------------------------- */
|
5
|
+
#include <ruby.h>
|
6
|
+
#include <rubysig.h>
|
7
|
+
#include <unistd.h>
|
8
|
+
#include <fcntl.h>
|
9
|
+
#include <errno.h>
|
10
|
+
#include <sys/types.h>
|
11
|
+
#include <sys/socket.h>
|
12
|
+
#include <netinet/in.h>
|
13
|
+
#include <arpa/inet.h>
|
14
|
+
#include "extconf.h"
|
15
|
+
#include "asteroid.h"
|
16
|
+
|
17
|
+
/* --------------------------------------------------------------------------
|
18
|
+
* epoll / kqueue
|
19
|
+
* - 2007/03/09 *BSD kqueue port, Mac OS X MSG_NOSIGNAL missing.
|
20
|
+
* (Takanori Ishikawa)
|
21
|
+
* -------------------------------------------------------------------------- */
|
22
|
+
#ifdef HAVE_SYS_EVENT_H
|
23
|
+
#include <sys/event.h>
|
24
|
+
#include <sys/time.h>
|
25
|
+
typedef int asteroid_pollfd_t;
|
26
|
+
typedef struct kevent asteroid_poll_event_t;
|
27
|
+
#endif
|
28
|
+
|
29
|
+
#ifdef HAVE_SYS_EPOLL_H
|
30
|
+
#include <sys/epoll.h>
|
31
|
+
typedef int asteroid_pollfd_t;
|
32
|
+
typedef struct epoll_event asteroid_poll_event_t;
|
33
|
+
#endif
|
34
|
+
|
35
|
+
static asteroid_pollfd_t asteroid_poll_create(int sizeHint) {
|
36
|
+
#ifdef HAVE_SYS_EVENT_H
|
37
|
+
return kqueue();
|
38
|
+
#endif
|
39
|
+
#ifdef HAVE_SYS_EPOLL_H
|
40
|
+
return epoll_create(sizeHint);
|
41
|
+
#endif
|
42
|
+
}
|
43
|
+
|
44
|
+
static int asteroid_poll_add(
|
45
|
+
asteroid_pollfd_t pollfd,
|
46
|
+
asteroid_poll_event_t *event,
|
47
|
+
int fd) {
|
48
|
+
#ifdef HAVE_SYS_EVENT_H
|
49
|
+
EV_SET(event, fd, EVFILT_READ, EV_ADD, 0, 0, NULL);
|
50
|
+
return kevent(pollfd, event, 1, NULL, 0, NULL);
|
51
|
+
#endif
|
52
|
+
#ifdef HAVE_SYS_EPOLL_H
|
53
|
+
event->events = (EPOLLIN | EPOLLPRI);
|
54
|
+
event->data.fd = fd;
|
55
|
+
return epoll_ctl(pollfd, EPOLL_CTL_ADD, fd, event);
|
56
|
+
#endif
|
57
|
+
}
|
58
|
+
|
59
|
+
static int asteroid_poll_remove(
|
60
|
+
asteroid_pollfd_t pollfd,
|
61
|
+
asteroid_poll_event_t *event,
|
62
|
+
int fd) {
|
63
|
+
#ifdef HAVE_SYS_EVENT_H
|
64
|
+
EV_SET(event, fd, EVFILT_READ, EV_DELETE, 0, 0, NULL);
|
65
|
+
return kevent(pollfd, event, 1, NULL, 0, NULL);
|
66
|
+
#endif
|
67
|
+
#ifdef HAVE_SYS_EPOLL_H
|
68
|
+
return epoll_ctl(pollfd, EPOLL_CTL_DEL, fd, event);
|
69
|
+
#endif
|
70
|
+
}
|
71
|
+
|
72
|
+
static int asteroid_poll_wait(
|
73
|
+
asteroid_pollfd_t pollfd,
|
74
|
+
asteroid_poll_event_t *events,
|
75
|
+
int maxevents,
|
76
|
+
int timeout) {
|
77
|
+
#ifdef HAVE_SYS_EVENT_H
|
78
|
+
struct timespec tv, *tvptr;
|
79
|
+
if (timeout < 0) {
|
80
|
+
tvptr = NULL;
|
81
|
+
}
|
82
|
+
else {
|
83
|
+
tv.tv_sec = (long) (timeout/1000);
|
84
|
+
tv.tv_nsec = (long) (timeout%1000)*1000;
|
85
|
+
tvptr = &tv;
|
86
|
+
}
|
87
|
+
return kevent(pollfd, NULL, 0, events, maxevents, tvptr);
|
88
|
+
#endif
|
89
|
+
#ifdef HAVE_SYS_EPOLL_H
|
90
|
+
return epoll_wait(pollfd, events, maxevents, timeout);
|
91
|
+
#endif
|
92
|
+
}
|
93
|
+
|
94
|
+
#ifdef HAVE_SYS_EVENT_H
|
95
|
+
#define AST_POLL_EVENT_SOCK(event) ((event)->ident)
|
96
|
+
#define AST_POLL_EVENT_CAN_READ(event) ((event)->filter == EVFILT_READ)
|
97
|
+
#endif
|
98
|
+
#ifdef HAVE_SYS_EPOLL_H
|
99
|
+
#define AST_POLL_EVENT_SOCK(event) ((event)->data.fd)
|
100
|
+
#define AST_POLL_EVENT_CAN_READ(event) ((event)->events & (EPOLLIN | EPOLLPRI))
|
101
|
+
#endif
|
102
|
+
|
103
|
+
#ifdef SO_NOSIGPIPE
|
104
|
+
#ifndef MSG_NOSIGNAL
|
105
|
+
#define MSG_NOSIGNAL 0
|
106
|
+
#endif
|
107
|
+
|
108
|
+
/**
|
109
|
+
* The preferred method on Mac OS X (10.2 and later) to prevent SIGPIPEs when
|
110
|
+
* sending data to a dead peer (instead of relying on the 4th argument to send
|
111
|
+
* being MSG_NOSIGNAL). Possibly also existing and in use on other BSD
|
112
|
+
* systems?
|
113
|
+
*
|
114
|
+
* curl-7.15.5/lib/connect.c
|
115
|
+
*/
|
116
|
+
static void nosigpipe(int sockfd) {
|
117
|
+
int one = 1;
|
118
|
+
setsockopt(sockfd, SOL_SOCKET, SO_NOSIGPIPE, (void *)&one, sizeof(one));
|
119
|
+
}
|
120
|
+
#else
|
121
|
+
#define nosigpipe(x)
|
122
|
+
#endif
|
123
|
+
|
124
|
+
#define MAX_CONNECTION (102400)
|
125
|
+
#define EVENT_BUF_SIZE (1024)
|
126
|
+
|
127
|
+
static VALUE Asteroid;
|
128
|
+
static VALUE clients;
|
129
|
+
static volatile int running = 0;
|
130
|
+
static asteroid_pollfd_t epoll_fd = 0;
|
131
|
+
static asteroid_poll_event_t events[EVENT_BUF_SIZE];
|
132
|
+
|
133
|
+
int dispatch();
|
134
|
+
void runtime_error();
|
135
|
+
|
136
|
+
void Init_asteroid(){
|
137
|
+
Asteroid = rb_define_module("Asteroid");
|
138
|
+
rb_define_singleton_method(Asteroid, "run", asteroid_s_run, 3);
|
139
|
+
rb_define_singleton_method(Asteroid, "stop", asteroid_s_stop, 0);
|
140
|
+
rb_define_class_variable(Asteroid, "@@clients", clients = rb_hash_new());
|
141
|
+
}
|
142
|
+
|
143
|
+
static VALUE close_socket_proc(VALUE Pair, VALUE Arg, VALUE Self) {
|
144
|
+
close(FIX2INT(RARRAY(Pair)->ptr[0]));
|
145
|
+
return Qnil;
|
146
|
+
}
|
147
|
+
|
148
|
+
static VALUE asteroid_s_run(VALUE Self, VALUE Host, VALUE Port, VALUE Module){
|
149
|
+
char *host = StringValuePtr(Host);
|
150
|
+
int port = FIX2INT(Port);
|
151
|
+
|
152
|
+
epoll_fd = asteroid_poll_create(1024);
|
153
|
+
if(epoll_fd == -1) runtime_error();
|
154
|
+
|
155
|
+
struct sockaddr_in addr;
|
156
|
+
addr.sin_family = AF_INET;
|
157
|
+
addr.sin_port = htons(port);
|
158
|
+
addr.sin_addr.s_addr = inet_addr(host);
|
159
|
+
int s = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP), c, one = 1;
|
160
|
+
if(s == -1) runtime_error();
|
161
|
+
fcntl(s, F_SETFL, fcntl(s, F_GETFL, 0) | O_NONBLOCK);
|
162
|
+
setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
|
163
|
+
nosigpipe(s);
|
164
|
+
if(bind(s, (struct sockaddr*)&addr, sizeof(addr)) != 0) runtime_error();
|
165
|
+
if(listen(s, MAX_CONNECTION) != 0) runtime_error();
|
166
|
+
if(rb_block_given_p()) rb_yield(Qnil);
|
167
|
+
|
168
|
+
VALUE Class = rb_define_class_under(Asteroid, "Server", rb_cObject);
|
169
|
+
rb_define_method(Class, "send_data",
|
170
|
+
asteroid_server_send_data, 1);
|
171
|
+
rb_define_method(Class, "write_and_close",
|
172
|
+
asteroid_server_write_and_close, 0);
|
173
|
+
rb_include_module(Class, Module);
|
174
|
+
// Mac OS X, Fedora needs explicit rb_thread_schedule call.
|
175
|
+
for(running = 1; running; rb_thread_schedule()){
|
176
|
+
socklen_t len = sizeof(addr);
|
177
|
+
while((c = accept(s, (struct sockaddr*)&addr, &len)) != -1){
|
178
|
+
fcntl(c, F_SETFL, fcntl(c, F_GETFL, 0) | O_NONBLOCK);
|
179
|
+
asteroid_poll_event_t event;
|
180
|
+
memset(&event, 0, sizeof(event));
|
181
|
+
if(asteroid_poll_add(epoll_fd, &event, c) == -1) runtime_error();
|
182
|
+
// instantiate server class which responds to client.
|
183
|
+
VALUE Server = rb_class_new_instance(0, NULL, Class);
|
184
|
+
rb_iv_set(Server, "@fd", rb_fix_new(c));
|
185
|
+
rb_hash_aset(clients, rb_fix_new(c), Server);
|
186
|
+
if(rb_respond_to(Server, rb_intern("post_init"))){
|
187
|
+
rb_funcall(Server, rb_intern("post_init"), 0);
|
188
|
+
}
|
189
|
+
}
|
190
|
+
if(dispatch() != 0) asteroid_s_stop(Asteroid);
|
191
|
+
// You must call them to give a chance for ruby to handle system events.
|
192
|
+
CHECK_INTS;
|
193
|
+
}
|
194
|
+
|
195
|
+
rb_iterate(rb_each, clients, close_socket_proc, Qnil);
|
196
|
+
rb_funcall(clients, rb_intern("clear"), 0);
|
197
|
+
close(s);
|
198
|
+
close(epoll_fd);
|
199
|
+
return Qnil;
|
200
|
+
}
|
201
|
+
|
202
|
+
static VALUE asteroid_s_stop(VALUE Self){
|
203
|
+
running = 0;
|
204
|
+
return Qnil;
|
205
|
+
}
|
206
|
+
|
207
|
+
static VALUE asteroid_server_send_data(VALUE Self, VALUE Data){
|
208
|
+
VALUE Fd = rb_iv_get(Self, "@fd");
|
209
|
+
int fd = FIX2INT(Fd);
|
210
|
+
char *data = StringValuePtr(Data);
|
211
|
+
send(fd, data, RSTRING(Data)->len, MSG_NOSIGNAL);
|
212
|
+
return Qnil;
|
213
|
+
}
|
214
|
+
|
215
|
+
static VALUE asteroid_server_write_and_close(VALUE Self){
|
216
|
+
VALUE Fd = rb_iv_get(Self, "@fd");
|
217
|
+
int fd = FIX2INT(Fd);
|
218
|
+
asteroid_poll_event_t event;
|
219
|
+
memset(&event, 0, sizeof(event));
|
220
|
+
asteroid_poll_remove(epoll_fd, &event, fd);
|
221
|
+
close(fd);
|
222
|
+
rb_hash_delete(clients, Fd);
|
223
|
+
return Qnil;
|
224
|
+
}
|
225
|
+
|
226
|
+
int dispatch(){
|
227
|
+
int i, s, len;
|
228
|
+
// We need to wait at least 1 millisecond to avoid busy loop.
|
229
|
+
while((s = asteroid_poll_wait(epoll_fd, events, EVENT_BUF_SIZE, 1)) > 0){
|
230
|
+
for(i = 0; i < s; ++i){
|
231
|
+
asteroid_poll_event_t event = events[i];
|
232
|
+
int fd = AST_POLL_EVENT_SOCK(&event);
|
233
|
+
VALUE Fd = rb_fix_new(fd);
|
234
|
+
VALUE Server = rb_hash_aref(clients, Fd);
|
235
|
+
if(AST_POLL_EVENT_CAN_READ(&event)){
|
236
|
+
VALUE Buf = rb_str_new("", 0);
|
237
|
+
char buf[1024];
|
238
|
+
while((len = read(fd, buf, 1023)) > 0){
|
239
|
+
buf[len] = '\0';
|
240
|
+
rb_str_concat(Buf, rb_str_new2(buf));
|
241
|
+
}
|
242
|
+
if(len == -1){
|
243
|
+
if(rb_respond_to(Server, rb_intern("receive_data"))){
|
244
|
+
rb_funcall(Server, rb_intern("receive_data"), 1, Buf);
|
245
|
+
}
|
246
|
+
}else{
|
247
|
+
if(rb_respond_to(Server, rb_intern("unbind"))){
|
248
|
+
rb_funcall(Server, rb_intern("unbind"), 0);
|
249
|
+
}
|
250
|
+
asteroid_poll_remove(epoll_fd, &event, fd);
|
251
|
+
rb_hash_delete(clients, Fd);
|
252
|
+
close(fd);
|
253
|
+
}
|
254
|
+
}
|
255
|
+
}
|
256
|
+
}
|
257
|
+
return 0;
|
258
|
+
}
|
259
|
+
|
260
|
+
void runtime_error(){
|
261
|
+
rb_raise(rb_eRuntimeError, strerror(errno));
|
262
|
+
}
|
data/ext/asteroid.h
ADDED
data/ext/extconf.rb
ADDED
data/lib/form_encoder.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
module FormEncoder
|
2
|
+
def self.encode(parameters, prefix = "")
|
3
|
+
case parameters
|
4
|
+
when Hash; encode_hash(parameters, prefix)
|
5
|
+
when Array; encode_array(parameters, prefix)
|
6
|
+
else "#{prefix}=#{CGI.escape(parameters.to_s)}"
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
def self.encode_hash(hash, prefix)
|
12
|
+
hash.inject([]) do |result, (key, value)|
|
13
|
+
key = CGI.escape(key.to_s)
|
14
|
+
result << encode(value, prefix.empty? ? key : "#{prefix}[#{key}]")
|
15
|
+
end.join('&')
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.encode_array(array, prefix)
|
19
|
+
array.inject([]) do |result, value|
|
20
|
+
result << encode(value, "#{prefix}[]")
|
21
|
+
end.join('&')
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
module ShootingStar
|
4
|
+
class Channel
|
5
|
+
class InvalidIdError < StandardError; end
|
6
|
+
attr_reader :path, :waiters
|
7
|
+
@@channels = {}
|
8
|
+
|
9
|
+
def initialize(path)
|
10
|
+
@path = path
|
11
|
+
@waiters = Hash.new
|
12
|
+
@event_id = 0
|
13
|
+
@@channels[path] = self
|
14
|
+
end
|
15
|
+
|
16
|
+
def transmit(id, params)
|
17
|
+
@waiters.each do |signature, server|
|
18
|
+
server.commit if server.respond(id, params)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def join(server)
|
23
|
+
@waiters[server.signature] = server
|
24
|
+
server.commit
|
25
|
+
end
|
26
|
+
|
27
|
+
def leave(server)
|
28
|
+
@waiters.delete(server.signature)
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.[](channel); @@channels[channel] end
|
32
|
+
def self.list; @@channels.keys end
|
33
|
+
def self.sweep; @@channels.delete_if{|k,v| v.waiters.empty?} end
|
34
|
+
|
35
|
+
def self.cleanup(channel)
|
36
|
+
result = @@channels[channel] && @@channels[channel].waiters.empty?
|
37
|
+
@@channels.delete(channel) if result
|
38
|
+
result
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module ShootingStar
|
2
|
+
class Config
|
3
|
+
def initialize(hash)
|
4
|
+
@hash = hash.keys.inject({}) do |new_hash, key|
|
5
|
+
new_hash[key.to_s] = hash[key]
|
6
|
+
new_hash
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
def merge!(hash)
|
11
|
+
hash.keys.each{|key| @hash[key.to_s] = hash[key]}; self
|
12
|
+
end
|
13
|
+
|
14
|
+
def method_missing(method, *arg, &block)
|
15
|
+
value = @hash[method.to_s]
|
16
|
+
value.is_a?(Hash) ? Config.new(value) : value
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|