cool.io 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +25 -0
- data/CHANGES +199 -0
- data/LICENSE +20 -0
- data/README.markdown +4 -0
- data/Rakefile +98 -0
- data/VERSION +1 -0
- data/examples/echo_client.rb +38 -0
- data/examples/echo_server.rb +27 -0
- data/examples/google.rb +9 -0
- data/examples/httpclient.rb +38 -0
- data/ext/cool.io/.gitignore +5 -0
- data/ext/cool.io/cool.io.h +58 -0
- data/ext/cool.io/cool.io_ext.c +25 -0
- data/ext/cool.io/ev_wrap.h +8 -0
- data/ext/cool.io/extconf.rb +69 -0
- data/ext/cool.io/iowatcher.c +189 -0
- data/ext/cool.io/libev.c +8 -0
- data/ext/cool.io/loop.c +303 -0
- data/ext/cool.io/stat_watcher.c +191 -0
- data/ext/cool.io/timer_watcher.c +219 -0
- data/ext/cool.io/utils.c +122 -0
- data/ext/cool.io/watcher.c +264 -0
- data/ext/cool.io/watcher.h +71 -0
- data/ext/http11_client/.gitignore +5 -0
- data/ext/http11_client/ext_help.h +14 -0
- data/ext/http11_client/extconf.rb +6 -0
- data/ext/http11_client/http11_client.c +300 -0
- data/ext/http11_client/http11_parser.c +403 -0
- data/ext/http11_client/http11_parser.h +48 -0
- data/ext/http11_client/http11_parser.rl +173 -0
- data/ext/libev/Changes +364 -0
- data/ext/libev/LICENSE +36 -0
- data/ext/libev/README +58 -0
- data/ext/libev/README.embed +3 -0
- data/ext/libev/ev.c +3867 -0
- data/ext/libev/ev.h +826 -0
- data/ext/libev/ev_epoll.c +234 -0
- data/ext/libev/ev_kqueue.c +198 -0
- data/ext/libev/ev_poll.c +148 -0
- data/ext/libev/ev_port.c +164 -0
- data/ext/libev/ev_select.c +307 -0
- data/ext/libev/ev_vars.h +197 -0
- data/ext/libev/ev_win32.c +153 -0
- data/ext/libev/ev_wrap.h +186 -0
- data/ext/libev/test_libev_win32.c +123 -0
- data/ext/libev/update_ev_wrap +19 -0
- data/lib/.gitignore +2 -0
- data/lib/cool.io.rb +30 -0
- data/lib/cool.io/async_watcher.rb +43 -0
- data/lib/cool.io/dns_resolver.rb +220 -0
- data/lib/cool.io/eventmachine.rb +234 -0
- data/lib/cool.io/http_client.rb +419 -0
- data/lib/cool.io/io.rb +174 -0
- data/lib/cool.io/iowatcher.rb +17 -0
- data/lib/cool.io/listener.rb +93 -0
- data/lib/cool.io/loop.rb +130 -0
- data/lib/cool.io/meta.rb +49 -0
- data/lib/cool.io/server.rb +74 -0
- data/lib/cool.io/socket.rb +224 -0
- data/lib/cool.io/timer_watcher.rb +17 -0
- data/lib/coolio.rb +2 -0
- data/lib/rev.rb +4 -0
- data/spec/async_watcher_spec.rb +57 -0
- data/spec/possible_tests/schedules_other_threads.rb +48 -0
- data/spec/possible_tests/test_on_resolve_failed.rb +9 -0
- data/spec/possible_tests/test_resolves.rb +27 -0
- data/spec/possible_tests/test_write_during_resolve.rb +27 -0
- data/spec/possible_tests/works_straight.rb +71 -0
- data/spec/spec_helper.rb +5 -0
- data/spec/timer_watcher_spec.rb +55 -0
- data/spec/unix_listener_spec.rb +25 -0
- data/spec/unix_server_spec.rb +25 -0
- metadata +184 -0
@@ -0,0 +1,123 @@
|
|
1
|
+
// a single header file is required
|
2
|
+
#include <ev.h>
|
3
|
+
#include <stdio.h>
|
4
|
+
#include <io.h>
|
5
|
+
|
6
|
+
// every watcher type has its own typedef'd struct
|
7
|
+
// with the name ev_TYPE
|
8
|
+
ev_io stdin_watcher;
|
9
|
+
ev_timer timeout_watcher;
|
10
|
+
|
11
|
+
// all watcher callbacks have a similar signature
|
12
|
+
// this callback is called when data is readable on stdin
|
13
|
+
static void
|
14
|
+
stdin_cb (EV_P_ ev_io *w, int revents)
|
15
|
+
{
|
16
|
+
puts ("stdin ready or done or something");
|
17
|
+
// for one-shot events, one must manually stop the watcher
|
18
|
+
// with its corresponding stop function.
|
19
|
+
//ev_io_stop (EV_A_ w);
|
20
|
+
|
21
|
+
// this causes all nested ev_loop's to stop iterating
|
22
|
+
//ev_unloop (EV_A_ EVUNLOOP_ALL);
|
23
|
+
}
|
24
|
+
|
25
|
+
// another callback, this time for a time-out
|
26
|
+
static void
|
27
|
+
timeout_cb (EV_P_ ev_timer *w, int revents)
|
28
|
+
{
|
29
|
+
puts ("timeout");
|
30
|
+
// this causes the innermost ev_loop to stop iterating
|
31
|
+
ev_unloop (EV_A_ EVUNLOOP_ONE);
|
32
|
+
}
|
33
|
+
|
34
|
+
|
35
|
+
|
36
|
+
#include <winsock.h>
|
37
|
+
|
38
|
+
#include <stdlib.h>
|
39
|
+
#include <iostream>
|
40
|
+
int get_server_fd()
|
41
|
+
{
|
42
|
+
|
43
|
+
//----------------------
|
44
|
+
// Initialize Winsock.
|
45
|
+
WSADATA wsaData;
|
46
|
+
int iResult = WSAStartup(MAKEWORD(2,2), &wsaData);
|
47
|
+
if (iResult != NO_ERROR) {
|
48
|
+
printf("Error at WSAStartup()\n");
|
49
|
+
return 1;
|
50
|
+
}
|
51
|
+
|
52
|
+
//----------------------
|
53
|
+
// Create a SOCKET for listening for
|
54
|
+
// incoming connection requests.
|
55
|
+
SOCKET ListenSocket;
|
56
|
+
ListenSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
|
57
|
+
if (ListenSocket == INVALID_SOCKET) {
|
58
|
+
printf("Error at socket(): %ld\n", WSAGetLastError());
|
59
|
+
WSACleanup();
|
60
|
+
return 1;
|
61
|
+
}
|
62
|
+
printf("socket returned %d\n", ListenSocket);
|
63
|
+
|
64
|
+
//----------------------
|
65
|
+
// The sockaddr_in structure specifies the address family,
|
66
|
+
// IP address, and port for the socket that is being bound.
|
67
|
+
sockaddr_in service;
|
68
|
+
service.sin_family = AF_INET;
|
69
|
+
service.sin_addr.s_addr = inet_addr("127.0.0.1");
|
70
|
+
service.sin_port = htons(4444);
|
71
|
+
|
72
|
+
if (bind( ListenSocket,
|
73
|
+
(SOCKADDR*) &service,
|
74
|
+
sizeof(service)) == SOCKET_ERROR) {
|
75
|
+
printf("bind() failed.\n");
|
76
|
+
closesocket(ListenSocket);
|
77
|
+
WSACleanup();
|
78
|
+
return 1;
|
79
|
+
}
|
80
|
+
|
81
|
+
//----------------------
|
82
|
+
// Listen for incoming connection requests.
|
83
|
+
// on the created socket
|
84
|
+
if (listen( ListenSocket, 1 ) == SOCKET_ERROR) {
|
85
|
+
printf("Error listening on socket.\n");
|
86
|
+
closesocket(ListenSocket);
|
87
|
+
WSACleanup();
|
88
|
+
return 1;
|
89
|
+
}
|
90
|
+
|
91
|
+
|
92
|
+
printf("sock and osf handle are %d %d, error is \n", ListenSocket, _get_osfhandle (ListenSocket)); // -1 is invalid file handle: http://msdn.microsoft.com/en-us/library/ks2530z6.aspx
|
93
|
+
printf("err was %d\n", WSAGetLastError());
|
94
|
+
//----------------------
|
95
|
+
return ListenSocket;
|
96
|
+
}
|
97
|
+
|
98
|
+
|
99
|
+
int
|
100
|
+
main (void)
|
101
|
+
{
|
102
|
+
struct ev_loop *loopy = ev_default_loop(0);
|
103
|
+
int fd = get_server_fd();
|
104
|
+
int fd_real = _open_osfhandle(fd, NULL);
|
105
|
+
int conv = _get_osfhandle(fd_real);
|
106
|
+
printf("got server fd %d, loop %d, fd_real %d, that converted %d\n", fd, loopy, fd_real, conv);
|
107
|
+
// accept(fd, NULL, NULL);
|
108
|
+
// initialise an io watcher, then start it
|
109
|
+
// this one will watch for stdin to become readable
|
110
|
+
ev_io_init (&stdin_watcher, stdin_cb, /*STDIN_FILENO*/ conv, EV_READ);
|
111
|
+
ev_io_start (loopy, &stdin_watcher);
|
112
|
+
|
113
|
+
// initialise a timer watcher, then start it
|
114
|
+
// simple non-repeating 5.5 second timeout
|
115
|
+
//ev_timer_init (&timeout_watcher, timeout_cb, 15.5, 0.);
|
116
|
+
//ev_timer_start (loopy, &timeout_watcher);
|
117
|
+
printf("starting loop\n");
|
118
|
+
// now wait for events to arrive
|
119
|
+
ev_loop (loopy, 0);
|
120
|
+
|
121
|
+
// unloop was called, so exit
|
122
|
+
return 0;
|
123
|
+
}
|
@@ -0,0 +1,19 @@
|
|
1
|
+
#!/bin/sh
|
2
|
+
|
3
|
+
(
|
4
|
+
echo '#define VAR(name,decl) name'
|
5
|
+
echo '#define EV_GENWRAP 1'
|
6
|
+
cat ev_vars.h
|
7
|
+
) | cc -E -o - - | perl -ne '
|
8
|
+
while (<>) {
|
9
|
+
push @syms, $1 if /(^\w+)/;
|
10
|
+
}
|
11
|
+
print "/* DO NOT EDIT, automatically generated by update_ev_wrap */\n",
|
12
|
+
"#ifndef EV_WRAP_H\n",
|
13
|
+
"#define EV_WRAP_H\n",
|
14
|
+
(map "#define $_ ((loop)->$_)\n", @syms),
|
15
|
+
"#else\n",
|
16
|
+
"#undef EV_WRAP_H\n",
|
17
|
+
(map "#undef $_\n", @syms),
|
18
|
+
"#endif\n";
|
19
|
+
' >ev_wrap.h
|
data/lib/.gitignore
ADDED
data/lib/cool.io.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (C)2007-10 Tony Arcieri
|
3
|
+
# You can redistribute this under the terms of the Ruby license
|
4
|
+
# See file LICENSE for details
|
5
|
+
#++
|
6
|
+
|
7
|
+
require 'iobuffer'
|
8
|
+
|
9
|
+
require "cool.io_ext"
|
10
|
+
require "cool.io/loop"
|
11
|
+
require "cool.io/meta"
|
12
|
+
require "cool.io/io"
|
13
|
+
require "cool.io/iowatcher"
|
14
|
+
require "cool.io/timer_watcher"
|
15
|
+
require "cool.io/async_watcher"
|
16
|
+
require "cool.io/listener"
|
17
|
+
require "cool.io/dns_resolver"
|
18
|
+
require "cool.io/socket"
|
19
|
+
require "cool.io/server"
|
20
|
+
require "cool.io/http_client"
|
21
|
+
|
22
|
+
module Coolio
|
23
|
+
VERSION = File.read(File.expand_path('../../VERSION', __FILE__)).strip
|
24
|
+
def self.version; VERSION; end
|
25
|
+
end
|
26
|
+
|
27
|
+
module Cool
|
28
|
+
# Allow Coolio module to be referenced as Cool.io
|
29
|
+
def self.io; Coolio; end
|
30
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (C)2007-10 Tony Arcieri
|
3
|
+
# You can redistribute this under the terms of the Ruby license
|
4
|
+
# See file LICENSE for details
|
5
|
+
#++
|
6
|
+
|
7
|
+
module Coolio
|
8
|
+
# The AsyncWatcher lets you signal another thread to wake up. Its
|
9
|
+
# intended use is notifying another thread of events.
|
10
|
+
class AsyncWatcher < IOWatcher
|
11
|
+
def initialize
|
12
|
+
@reader, @writer = ::IO.pipe
|
13
|
+
super(@reader)
|
14
|
+
end
|
15
|
+
|
16
|
+
# Signal the async watcher. This call is thread safe.
|
17
|
+
def signal
|
18
|
+
# Write a byte to the pipe. What we write is meaningless, it
|
19
|
+
# merely signals an event has occurred for each byte written.
|
20
|
+
@writer.write "\0"
|
21
|
+
end
|
22
|
+
|
23
|
+
# Called whenever a signal is received
|
24
|
+
def on_signal; end
|
25
|
+
event_callback :on_signal
|
26
|
+
|
27
|
+
#########
|
28
|
+
protected
|
29
|
+
#########
|
30
|
+
|
31
|
+
def on_readable
|
32
|
+
# Read a byte from the pipe. This clears readability, unless
|
33
|
+
# another signal is pending
|
34
|
+
begin
|
35
|
+
@reader.read_nonblock 1
|
36
|
+
rescue Errno::EAGAIN
|
37
|
+
# in case there are spurious wakeups from forked processs
|
38
|
+
return
|
39
|
+
end
|
40
|
+
on_signal
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,220 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (C)2007-10 Tony Arcieri
|
3
|
+
# You can redistribute this under the terms of the Ruby license
|
4
|
+
# See file LICENSE for details
|
5
|
+
#
|
6
|
+
# Gimpy hacka asynchronous DNS resolver
|
7
|
+
#
|
8
|
+
# Word to the wise: I don't know what I'm doing here. This was cobbled together
|
9
|
+
# as best I could with extremely limited knowledge of the DNS format. There's
|
10
|
+
# obviously a ton of stuff it doesn't support (like IPv6 and TCP).
|
11
|
+
#
|
12
|
+
# If you do know what you're doing with DNS, feel free to improve this!
|
13
|
+
# A good starting point my be this EventMachine Net::DNS-based asynchronous
|
14
|
+
# resolver:
|
15
|
+
#
|
16
|
+
# http://gist.github.com/663299
|
17
|
+
#
|
18
|
+
#++
|
19
|
+
|
20
|
+
module Coolio
|
21
|
+
# A non-blocking DNS resolver. It provides interfaces for querying both
|
22
|
+
# /etc/hosts and nameserves listed in /etc/resolv.conf, or nameservers of
|
23
|
+
# your choosing.
|
24
|
+
#
|
25
|
+
# Presently the client only supports UDP requests against your nameservers
|
26
|
+
# and cannot resolve anything with records larger than 512-bytes. Also,
|
27
|
+
# IPv6 is not presently supported.
|
28
|
+
#
|
29
|
+
# DNSResolver objects are one-shot. Once they resolve a domain name they
|
30
|
+
# automatically detach themselves from the event loop and cannot be used
|
31
|
+
# again.
|
32
|
+
class DNSResolver < IOWatcher
|
33
|
+
#--
|
34
|
+
# TODO check if it's caching right
|
35
|
+
RESOLV_CONF = '/etc/resolv.conf'
|
36
|
+
HOSTS = '/etc/hosts'
|
37
|
+
DNS_PORT = 53
|
38
|
+
DATAGRAM_SIZE = 512
|
39
|
+
TIMEOUT = 3 # Retry timeout for each datagram sent
|
40
|
+
RETRIES = 4 # Number of retries to attempt
|
41
|
+
# so currently total is 12s before it will err due to timeouts
|
42
|
+
# if it errs due to inability to reach the DNS server [Errno::EHOSTUNREACH], same
|
43
|
+
|
44
|
+
# Query /etc/hosts (or the specified hostfile) for the given host
|
45
|
+
def self.hosts(host, hostfile = HOSTS)
|
46
|
+
hosts = {}
|
47
|
+
File.open(hostfile).each_line do |host_entry|
|
48
|
+
entries = host_entry.gsub(/#.*$/, '').gsub(/\s+/, ' ').split(' ')
|
49
|
+
addr = entries.shift
|
50
|
+
entries.each { |e| hosts[e] ||= addr }
|
51
|
+
end
|
52
|
+
|
53
|
+
hosts[host]
|
54
|
+
end
|
55
|
+
|
56
|
+
# Create a new Coolio::Watcher descended object to resolve the
|
57
|
+
# given hostname. If you so desire you can also specify a
|
58
|
+
# list of nameservers to query. By default the resolver will
|
59
|
+
# use nameservers listed in /etc/resolv.conf
|
60
|
+
def initialize(hostname, *nameservers)
|
61
|
+
if nameservers.empty?
|
62
|
+
nameservers = File.read(RESOLV_CONF).scan(/^\s*nameserver\s+([0-9.:]+)/).flatten # TODO could optimize this to just read once
|
63
|
+
raise RuntimeError, "no nameservers found in #{RESOLV_CONF}" if nameservers.empty? # TODO just call resolve_failed, not raise [also handle Errno::ENOENT)]
|
64
|
+
end
|
65
|
+
|
66
|
+
@nameservers = nameservers
|
67
|
+
@question = request_question hostname
|
68
|
+
|
69
|
+
@socket = UDPSocket.new
|
70
|
+
@timer = Timeout.new(self)
|
71
|
+
|
72
|
+
super(@socket)
|
73
|
+
end
|
74
|
+
|
75
|
+
# Attach the DNSResolver to the given event loop
|
76
|
+
def attach(evloop)
|
77
|
+
send_request
|
78
|
+
@timer.attach(evloop)
|
79
|
+
super
|
80
|
+
end
|
81
|
+
|
82
|
+
# Detach the DNSResolver from the given event loop
|
83
|
+
def detach
|
84
|
+
@timer.detach if @timer.attached?
|
85
|
+
super
|
86
|
+
end
|
87
|
+
|
88
|
+
# Called when the name has successfully resolved to an address
|
89
|
+
def on_success(address); end
|
90
|
+
event_callback :on_success
|
91
|
+
|
92
|
+
# Called when we receive a response indicating the name didn't resolve
|
93
|
+
def on_failure; end
|
94
|
+
event_callback :on_failure
|
95
|
+
|
96
|
+
# Called if we don't receive a response, defaults to calling on_failure
|
97
|
+
def on_timeout
|
98
|
+
on_failure
|
99
|
+
end
|
100
|
+
|
101
|
+
#########
|
102
|
+
protected
|
103
|
+
#########
|
104
|
+
|
105
|
+
# Send a request to the DNS server
|
106
|
+
def send_request
|
107
|
+
nameserver = @nameservers.shift
|
108
|
+
@nameservers << nameserver # rotate them
|
109
|
+
@socket.connect @nameservers.first, DNS_PORT
|
110
|
+
begin
|
111
|
+
@socket.send request_message, 0
|
112
|
+
rescue Errno::EHOSTUNREACH # TODO figure out why it has to be wrapper here, when the other wrapper should be wrapping this one!
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
# Called by the subclass when the DNS response is available
|
117
|
+
def on_readable
|
118
|
+
datagram = nil
|
119
|
+
begin
|
120
|
+
datagram = @socket.recvfrom_nonblock(DATAGRAM_SIZE).first
|
121
|
+
rescue Errno::ECONNREFUSED
|
122
|
+
end
|
123
|
+
|
124
|
+
address = response_address datagram rescue nil
|
125
|
+
address ? on_success(address) : on_failure
|
126
|
+
detach
|
127
|
+
end
|
128
|
+
|
129
|
+
def request_question(hostname)
|
130
|
+
raise ArgumentError, "hostname cannot be nil" if hostname.nil?
|
131
|
+
|
132
|
+
# Query name
|
133
|
+
message = hostname.split('.').map { |s| [s.size].pack('C') << s }.join + "\0"
|
134
|
+
|
135
|
+
# Host address query
|
136
|
+
qtype = 1
|
137
|
+
|
138
|
+
# Internet query
|
139
|
+
qclass = 1
|
140
|
+
|
141
|
+
message << [qtype, qclass].pack('nn')
|
142
|
+
end
|
143
|
+
|
144
|
+
def request_message
|
145
|
+
# Standard query header
|
146
|
+
message = [2, 1, 0].pack('nCC')
|
147
|
+
|
148
|
+
# One entry
|
149
|
+
qdcount = 1
|
150
|
+
|
151
|
+
# No answer, authority, or additional records
|
152
|
+
ancount = nscount = arcount = 0
|
153
|
+
|
154
|
+
message << [qdcount, ancount, nscount, arcount].pack('nnnn')
|
155
|
+
message << @question
|
156
|
+
end
|
157
|
+
|
158
|
+
def response_address(message)
|
159
|
+
# Confirm the ID field
|
160
|
+
id = message[0..1].unpack('n').first.to_i
|
161
|
+
return unless id == 2
|
162
|
+
|
163
|
+
# Check the QR value and confirm this message is a response
|
164
|
+
qr = message[2..2].unpack('B1').first.to_i
|
165
|
+
return unless qr == 1
|
166
|
+
|
167
|
+
# Check the RCODE (lower nibble) and ensure there wasn't an error
|
168
|
+
rcode = message[3..3].unpack('B8').first[4..7].to_i(2)
|
169
|
+
return unless rcode == 0
|
170
|
+
|
171
|
+
# Extract the question and answer counts
|
172
|
+
qdcount, ancount = message[4..7].unpack('nn').map { |n| n.to_i }
|
173
|
+
|
174
|
+
# We only asked one question
|
175
|
+
return unless qdcount == 1
|
176
|
+
message.slice!(0, 12)
|
177
|
+
|
178
|
+
# Make sure it's the same question
|
179
|
+
return unless message[0..(@question.size-1)] == @question
|
180
|
+
message.slice!(0, @question.size)
|
181
|
+
|
182
|
+
# Extract the RDLENGTH
|
183
|
+
while not message.empty?
|
184
|
+
type = message[2..3].unpack('n').first.to_i
|
185
|
+
rdlength = message[10..11].unpack('n').first.to_i
|
186
|
+
rdata = message[12..(12 + rdlength - 1)]
|
187
|
+
message.slice!(0, 12 + rdlength)
|
188
|
+
|
189
|
+
# Only IPv4 supported
|
190
|
+
next unless rdlength == 4
|
191
|
+
|
192
|
+
# If we got an Internet address back, return it
|
193
|
+
return rdata.unpack('CCCC').join('.') if type == 1
|
194
|
+
end
|
195
|
+
|
196
|
+
nil
|
197
|
+
end
|
198
|
+
|
199
|
+
class Timeout < TimerWatcher
|
200
|
+
def initialize(resolver)
|
201
|
+
@resolver = resolver
|
202
|
+
@attempts = 0
|
203
|
+
super(TIMEOUT, true)
|
204
|
+
end
|
205
|
+
|
206
|
+
def on_timer
|
207
|
+
@attempts += 1
|
208
|
+
if @attempts <= RETRIES
|
209
|
+
begin
|
210
|
+
return @resolver.__send__(:send_request)
|
211
|
+
rescue Errno::EHOSTUNREACH # if the DNS is toast try again after the timeout occurs again
|
212
|
+
return nil
|
213
|
+
end
|
214
|
+
end
|
215
|
+
@resolver.__send__(:on_timeout)
|
216
|
+
@resolver.detach
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
@@ -0,0 +1,234 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (C)2007-10 Tony Arcieri, Roger Pack
|
3
|
+
# You can redistribute this under the terms of the Ruby license
|
4
|
+
# See file LICENSE for details
|
5
|
+
#++
|
6
|
+
|
7
|
+
require 'cool.io'
|
8
|
+
|
9
|
+
# EventMachine emulation for Cool.io:
|
10
|
+
#
|
11
|
+
# require 'coolio/eventmachine'
|
12
|
+
#
|
13
|
+
# Drawbacks: slightly slower than EM.
|
14
|
+
# Benefits: timers are more accurate using libev than using EM
|
15
|
+
# TODO: some things like connection timeouts aren't implemented yet
|
16
|
+
# DONE: timers and normal socket functions are implemented.
|
17
|
+
module EventMachine
|
18
|
+
class << self
|
19
|
+
# Start the Reactor loop
|
20
|
+
def run
|
21
|
+
yield if block_given?
|
22
|
+
Coolio::Loop.default.run
|
23
|
+
end
|
24
|
+
|
25
|
+
# Stop the Reactor loop
|
26
|
+
def stop_event_loop
|
27
|
+
Coolio::Loop.default.stop
|
28
|
+
end
|
29
|
+
|
30
|
+
class OneShotEMTimer < Coolio::TimerWatcher
|
31
|
+
def setup(proc)
|
32
|
+
@proc = proc
|
33
|
+
end
|
34
|
+
|
35
|
+
def on_timer
|
36
|
+
@proc.call
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# ltodo: use Coolio's PeriodicTimer to wrap EM's two similar to it
|
41
|
+
# todo: close all connections on 'stop', I believe
|
42
|
+
|
43
|
+
def add_timer(interval, proc = nil, &block)
|
44
|
+
block ||= proc
|
45
|
+
t = OneShotEMTimer.new(interval, false) # non repeating
|
46
|
+
t.setup(block)
|
47
|
+
|
48
|
+
# fire 'er off ltodo: do we keep track of these timers in memory?
|
49
|
+
t.attach(Coolio::Loop.default)
|
50
|
+
t
|
51
|
+
end
|
52
|
+
|
53
|
+
def cancel_timer(t)
|
54
|
+
# guess there's a case where EM you can say 'cancel' but it's already fired?
|
55
|
+
# kind of odd but it happens
|
56
|
+
t.detach if t.attached?
|
57
|
+
end
|
58
|
+
|
59
|
+
def set_comm_inactivity_timeout(*args); end # TODO
|
60
|
+
|
61
|
+
# Make an outgoing connection
|
62
|
+
def connect(addr, port, handler = Connection, *args, &block)
|
63
|
+
block = args.pop if Proc === args[-1]
|
64
|
+
|
65
|
+
# make sure we're a 'real' class here
|
66
|
+
klass = if (handler and handler.is_a?(Class))
|
67
|
+
handler
|
68
|
+
else
|
69
|
+
Class.new( Connection ) {handler and include handler}
|
70
|
+
end
|
71
|
+
|
72
|
+
wrapped_child = CallsBackToEM.connect(addr, port, *args) # ltodo: args? what? they're used? also TODOC TODO FIX
|
73
|
+
conn = klass.new(wrapped_child) # ltodo [?] addr, port, *args)
|
74
|
+
wrapped_child.attach(Coolio::Loop.default) # necessary
|
75
|
+
conn.heres_your_socket(wrapped_child)
|
76
|
+
wrapped_child.call_back_to_this(conn) # calls post_init for us
|
77
|
+
yield conn if block_given?
|
78
|
+
end
|
79
|
+
|
80
|
+
# Start a TCP server on the given address and port
|
81
|
+
def start_server(addr, port, handler = Connection, *args, &block)
|
82
|
+
# make sure we're a 'real' class here
|
83
|
+
klass = if (handler and handler.is_a?(Class))
|
84
|
+
handler
|
85
|
+
else
|
86
|
+
Class.new( Connection ) {handler and include handler}
|
87
|
+
end
|
88
|
+
|
89
|
+
server = Coolio::TCPServer.new(addr, port, CallsBackToEM, *args) do |wrapped_child|
|
90
|
+
conn = klass.new(wrapped_child)
|
91
|
+
conn.heres_your_socket(wrapped_child) # ideally NOT have this :)
|
92
|
+
wrapped_child.call_back_to_this(conn)
|
93
|
+
block.call(conn) if block
|
94
|
+
end
|
95
|
+
|
96
|
+
server.attach(Coolio::Loop.default)
|
97
|
+
end
|
98
|
+
|
99
|
+
def stop_server(server)
|
100
|
+
server.close
|
101
|
+
end
|
102
|
+
|
103
|
+
# Set the maximum number of descriptors available to this process
|
104
|
+
def set_descriptor_table_size(nfds)
|
105
|
+
Coolio::Utils.maxfds = nfds
|
106
|
+
end
|
107
|
+
|
108
|
+
# Compatibility noop. Handled automatically by libev
|
109
|
+
def epoll; end
|
110
|
+
|
111
|
+
# Compatibility noop. Handled automatically by libev
|
112
|
+
def kqueue; end
|
113
|
+
end
|
114
|
+
|
115
|
+
class CallsBackToEM < Coolio::TCPSocket
|
116
|
+
class ConnectTimer < Coolio::TimerWatcher
|
117
|
+
attr_accessor :parent
|
118
|
+
def on_timer
|
119
|
+
@parent.connection_has_timed_out
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def call_back_to_this parent
|
124
|
+
@call_back_to_this = parent
|
125
|
+
parent.post_init
|
126
|
+
end
|
127
|
+
|
128
|
+
def on_connect
|
129
|
+
# @connection_timer.detach if @connection_timer
|
130
|
+
# won't need that anymore :) -- with server connecteds we don't have it, anyway
|
131
|
+
|
132
|
+
# TODO should server accepted's call this? They don't currently
|
133
|
+
# [and can't, since on_connect gets called basically in the initializer--needs some code love for that to happen :)
|
134
|
+
@call_back_to_this.connection_completed if @call_back_to_this
|
135
|
+
end
|
136
|
+
|
137
|
+
def connection_has_timed_out
|
138
|
+
return if closed?
|
139
|
+
|
140
|
+
# wonder if this works when you're within a half-connected phase.
|
141
|
+
# I think it does. What about TCP state?
|
142
|
+
close unless closed?
|
143
|
+
@call_back_to_this.unbind
|
144
|
+
end
|
145
|
+
|
146
|
+
def on_write_complete
|
147
|
+
close if @should_close_after_writing
|
148
|
+
end
|
149
|
+
|
150
|
+
def should_close_after_writing
|
151
|
+
@should_close_after_writing = true;
|
152
|
+
end
|
153
|
+
|
154
|
+
def on_close
|
155
|
+
@call_back_to_this.unbind # about the same ltodo check if they ARE the same here
|
156
|
+
end
|
157
|
+
|
158
|
+
def on_resolve_failed
|
159
|
+
fail
|
160
|
+
end
|
161
|
+
|
162
|
+
def on_connect_failed
|
163
|
+
fail
|
164
|
+
end
|
165
|
+
|
166
|
+
def on_read(data)
|
167
|
+
@call_back_to_this.receive_data data
|
168
|
+
end
|
169
|
+
|
170
|
+
def fail
|
171
|
+
#@connection_timer.detch if @connection_timer
|
172
|
+
@call_back_to_this.unbind
|
173
|
+
end
|
174
|
+
|
175
|
+
def self.connect(*args)
|
176
|
+
a = super *args
|
177
|
+
# the connect timer currently kills TCPServer classes. I'm not sure why.
|
178
|
+
#@connection_timer = ConnectTimer.new(14) # needs to be at least higher than 12 :)
|
179
|
+
#@connection_timer.parent = a
|
180
|
+
#@connection_timer.attach(Coolio::Loop.default)
|
181
|
+
a
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
class Connection
|
186
|
+
def self.new(*args)
|
187
|
+
allocate#.instance_eval do
|
188
|
+
# initialize *args
|
189
|
+
#end
|
190
|
+
end
|
191
|
+
|
192
|
+
# we will need to call 'their functions' appropriately -- the commented out ones, here
|
193
|
+
#
|
194
|
+
# Callback fired when connection is created
|
195
|
+
def post_init
|
196
|
+
# I thought we were 'overriding' EM's existing methods, here.
|
197
|
+
# Huh? Why do we have to define these then?
|
198
|
+
end
|
199
|
+
|
200
|
+
# Callback fired when connection is closed
|
201
|
+
def unbind; end
|
202
|
+
|
203
|
+
# Callback fired when data is received
|
204
|
+
# def receive_data(data); end
|
205
|
+
def heres_your_socket(instantiated_coolio_socket)
|
206
|
+
instantiated_coolio_socket.call_back_to_this self
|
207
|
+
@wrapped_coolio = instantiated_coolio_socket
|
208
|
+
end
|
209
|
+
|
210
|
+
# Send data to the current connection -- called by them
|
211
|
+
def send_data(data)
|
212
|
+
@wrapped_coolio.write data
|
213
|
+
end
|
214
|
+
|
215
|
+
# Close the connection, optionally after writing
|
216
|
+
def close_connection(after_writing = false)
|
217
|
+
return close_connection_after_writing if after_writing
|
218
|
+
@wrapped_coolio.close
|
219
|
+
end
|
220
|
+
|
221
|
+
# Close the connection after all data has been written
|
222
|
+
def close_connection_after_writing
|
223
|
+
@wrapped_coolio.output_buffer_size.zero? ? @wrapped_coolio.close : @wrapped_coolio.should_close_after_writing
|
224
|
+
end
|
225
|
+
|
226
|
+
def get_peername
|
227
|
+
family, port, host_name, host_ip = @wrapped_coolio.peeraddr
|
228
|
+
Socket.pack_sockaddr_in(port, host_ip) # pack it up :)
|
229
|
+
end
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
# Shortcut constant
|
234
|
+
EM = EventMachine
|