shooting_star 1.0.3
Sign up to get free protection for your applications and to get access to all the features.
- 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
|