cool.io 1.2.0-x86-mswin32-60
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/.gitignore +26 -0
- data/.rspec +3 -0
- data/.travis.yml +5 -0
- data/CHANGES.md +177 -0
- data/Gemfile +4 -0
- data/LICENSE +20 -0
- data/README.md +172 -0
- data/Rakefile +81 -0
- data/cool.io.gemspec +28 -0
- data/examples/dslified_echo_client.rb +34 -0
- data/examples/dslified_echo_server.rb +24 -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 +73 -0
- data/ext/cool.io/iowatcher.c +189 -0
- data/ext/cool.io/libev.c +8 -0
- data/ext/cool.io/loop.c +301 -0
- data/ext/cool.io/stat_watcher.c +269 -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/LICENSE +31 -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/iobuffer/extconf.rb +9 -0
- data/ext/iobuffer/iobuffer.c +765 -0
- data/ext/libev/Changes +388 -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 +4803 -0
- data/ext/libev/ev.h +845 -0
- data/ext/libev/ev_epoll.c +279 -0
- data/ext/libev/ev_kqueue.c +214 -0
- data/ext/libev/ev_poll.c +148 -0
- data/ext/libev/ev_port.c +185 -0
- data/ext/libev/ev_select.c +314 -0
- data/ext/libev/ev_vars.h +203 -0
- data/ext/libev/ev_win32.c +163 -0
- data/ext/libev/ev_wrap.h +200 -0
- data/ext/libev/test_libev_win32.c +123 -0
- data/lib/.gitignore +2 -0
- data/lib/cool.io.rb +32 -0
- data/lib/cool.io/async_watcher.rb +43 -0
- data/lib/cool.io/custom_require.rb +9 -0
- data/lib/cool.io/dns_resolver.rb +225 -0
- data/lib/cool.io/dsl.rb +135 -0
- data/lib/cool.io/eventmachine.rb +234 -0
- data/lib/cool.io/http_client.rb +427 -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 +230 -0
- data/lib/cool.io/timer_watcher.rb +17 -0
- data/lib/cool.io/version.rb +5 -0
- data/lib/coolio.rb +2 -0
- data/spec/async_watcher_spec.rb +57 -0
- data/spec/dns_spec.rb +39 -0
- data/spec/spec_helper.rb +12 -0
- data/spec/stat_watcher_spec.rb +77 -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 +200 -0
@@ -0,0 +1,74 @@
|
|
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
|
+
class Server < Listener
|
9
|
+
# Servers listen for incoming connections and create new connection objects
|
10
|
+
# whenever incoming connections are received. The default class for new
|
11
|
+
# connections is a Socket, but any subclass of IOWatcher is acceptable.
|
12
|
+
def initialize(listen_socket, klass = Socket, *args, &block)
|
13
|
+
# Ensure the provided class responds to attach
|
14
|
+
unless klass.allocate.is_a? IO
|
15
|
+
raise ArgumentError, "can't convert #{klass} to Coolio::IO"
|
16
|
+
end
|
17
|
+
|
18
|
+
# Verify the arity of the provided arguments
|
19
|
+
arity = klass.instance_method(:initialize).arity
|
20
|
+
expected = arity >= 0 ? arity : -(arity + 1)
|
21
|
+
|
22
|
+
if (arity >= 0 and args.size + 1 != expected) or (arity < 0 and args.size + 1 < expected)
|
23
|
+
raise ArgumentError, "wrong number of arguments for #{klass}#initialize (#{args.size+1} for #{expected})"
|
24
|
+
end
|
25
|
+
|
26
|
+
@klass, @args, @block = klass, args, block
|
27
|
+
super(listen_socket)
|
28
|
+
end
|
29
|
+
|
30
|
+
# Returns an integer representing the underlying numeric file descriptor
|
31
|
+
def fileno
|
32
|
+
@listen_socket.fileno
|
33
|
+
end
|
34
|
+
|
35
|
+
#########
|
36
|
+
protected
|
37
|
+
#########
|
38
|
+
|
39
|
+
def on_connection(socket)
|
40
|
+
connection = @klass.new(socket, *@args).attach(evloop)
|
41
|
+
connection.__send__(:on_connect)
|
42
|
+
@block.call(connection) if @block
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# TCP server class. Listens on the specified host and port and creates
|
47
|
+
# new connection objects of the given class. This is the most common server class.
|
48
|
+
# Note that the new connection objects will be bound by default to the same event loop that the server is attached to.
|
49
|
+
# Optionally, it can also take any existing core TCPServer object as
|
50
|
+
# +host+ and create a Coolio::TCPServer out of it.
|
51
|
+
class TCPServer < Server
|
52
|
+
def initialize(host, port = nil, klass = TCPSocket, *args, &block)
|
53
|
+
listen_socket = if ::TCPServer === host
|
54
|
+
host
|
55
|
+
else
|
56
|
+
raise ArgumentError, "port must be an integer" if nil == port
|
57
|
+
::TCPServer.new(host, port)
|
58
|
+
end
|
59
|
+
listen_socket.instance_eval { listen(1024) } # Change listen backlog to 1024
|
60
|
+
super(listen_socket, klass, *args, &block)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
# UNIX server class. Listens on the specified UNIX domain socket and
|
65
|
+
# creates new connection objects of the given class.
|
66
|
+
# Optionally, it can also take any existing core UNIXServer object as
|
67
|
+
# +path+ and create a Coolio::UNIXServer out of it.
|
68
|
+
class UNIXServer < Server
|
69
|
+
def initialize(path, klass = UNIXSocket, *args, &block)
|
70
|
+
s = ::UNIXServer === path ? path : ::UNIXServer.new(path)
|
71
|
+
super(s, klass, *args, &block)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,230 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (C)2007 Tony Arcieri
|
3
|
+
# You can redistribute this under the terms of the Ruby license
|
4
|
+
# See file LICENSE for details
|
5
|
+
#++
|
6
|
+
|
7
|
+
require 'socket'
|
8
|
+
require 'resolv'
|
9
|
+
|
10
|
+
module Coolio
|
11
|
+
class Socket < IO
|
12
|
+
def self.connect(socket, *args)
|
13
|
+
|
14
|
+
new(socket, *args).instance_eval do
|
15
|
+
@_connector = Connector.new(self, socket)
|
16
|
+
self
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# Just initializes some instance variables to avoid
|
21
|
+
# warnings and calls super().
|
22
|
+
def initialize *args
|
23
|
+
@_failed = nil
|
24
|
+
@_connector = nil
|
25
|
+
super
|
26
|
+
end
|
27
|
+
|
28
|
+
watcher_delegate :@_connector
|
29
|
+
|
30
|
+
def attach(evloop)
|
31
|
+
raise RuntimeError, "connection failed" if @_failed
|
32
|
+
|
33
|
+
if @_connector
|
34
|
+
@_connector.attach(evloop)
|
35
|
+
return self
|
36
|
+
end
|
37
|
+
|
38
|
+
super
|
39
|
+
end
|
40
|
+
|
41
|
+
# Called upon completion of a socket connection
|
42
|
+
def on_connect; end
|
43
|
+
event_callback :on_connect
|
44
|
+
|
45
|
+
# Called if a socket connection failed to complete
|
46
|
+
def on_connect_failed; end
|
47
|
+
event_callback :on_connect_failed
|
48
|
+
|
49
|
+
# Called if a hostname failed to resolve when connecting
|
50
|
+
# Defaults to calling on_connect_failed
|
51
|
+
alias_method :on_resolve_failed, :on_connect_failed
|
52
|
+
|
53
|
+
#########
|
54
|
+
protected
|
55
|
+
#########
|
56
|
+
|
57
|
+
class Connector < IOWatcher
|
58
|
+
def initialize(coolio_socket, ruby_socket)
|
59
|
+
@coolio_socket, @ruby_socket = coolio_socket, ruby_socket
|
60
|
+
super(ruby_socket, :w)
|
61
|
+
end
|
62
|
+
|
63
|
+
def on_writable
|
64
|
+
evl = evloop
|
65
|
+
detach
|
66
|
+
|
67
|
+
if connect_successful?
|
68
|
+
@coolio_socket.instance_eval { @_connector = nil }
|
69
|
+
@coolio_socket.attach(evl)
|
70
|
+
@ruby_socket.setsockopt(::Socket::IPPROTO_TCP, ::Socket::TCP_NODELAY, [1].pack("l"))
|
71
|
+
@ruby_socket.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_KEEPALIVE, true)
|
72
|
+
|
73
|
+
@coolio_socket.__send__(:on_connect)
|
74
|
+
else
|
75
|
+
@coolio_socket.instance_eval { @_failed = true }
|
76
|
+
@coolio_socket.__send__(:on_connect_failed)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
#######
|
81
|
+
private
|
82
|
+
#######
|
83
|
+
|
84
|
+
def connect_successful?
|
85
|
+
@ruby_socket.getsockopt(::Socket::SOL_SOCKET, ::Socket::SO_ERROR).unpack('i').first == 0
|
86
|
+
rescue IOError
|
87
|
+
false
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
class TCPSocket < Socket
|
93
|
+
attr_reader :remote_host, :remote_addr, :remote_port, :address_family
|
94
|
+
watcher_delegate :@_resolver
|
95
|
+
|
96
|
+
# Similar to .new, but used in cases where the resulting object is in a
|
97
|
+
# "half-open" state. This is primarily used for when asynchronous
|
98
|
+
# DNS resolution is taking place. We don't actually have a handle to
|
99
|
+
# the socket we want to use to create the watcher yet, since we don't
|
100
|
+
# know the IP address to connect to.
|
101
|
+
def self.precreate(*args, &block)
|
102
|
+
obj = allocate
|
103
|
+
obj.__send__(:preinitialize, *args, &block)
|
104
|
+
obj
|
105
|
+
end
|
106
|
+
|
107
|
+
# Perform a non-blocking connect to the given host and port
|
108
|
+
# see examples/echo_client.rb
|
109
|
+
# addr is a string, can be an IP address or a hostname.
|
110
|
+
def self.connect(addr, port, *args)
|
111
|
+
family = nil
|
112
|
+
|
113
|
+
if (Resolv::IPv4.create(addr) rescue nil)
|
114
|
+
family = ::Socket::AF_INET
|
115
|
+
elsif(Resolv::IPv6.create(addr) rescue nil)
|
116
|
+
family = ::Socket::AF_INET6
|
117
|
+
end
|
118
|
+
|
119
|
+
if family
|
120
|
+
return super(TCPConnectSocket.new(family, addr, port), *args) # this creates a 'real' write buffer so we're ok there with regards to already having a write buffer from the get go
|
121
|
+
end
|
122
|
+
|
123
|
+
if host = Coolio::DNSResolver.hosts(addr)
|
124
|
+
return connect(host, port, *args) # calls this same function
|
125
|
+
end
|
126
|
+
|
127
|
+
precreate(addr, port, *args)
|
128
|
+
end
|
129
|
+
|
130
|
+
# Called by precreate during asyncronous DNS resolution
|
131
|
+
def preinitialize(addr, port, *args)
|
132
|
+
@_write_buffer = ::IO::Buffer.new # allow for writing BEFORE DNS has resolved
|
133
|
+
@remote_host, @remote_addr, @remote_port = addr, addr, port
|
134
|
+
@_resolver = TCPConnectResolver.new(self, addr, port, *args)
|
135
|
+
end
|
136
|
+
|
137
|
+
private :preinitialize
|
138
|
+
|
139
|
+
def initialize(socket)
|
140
|
+
unless socket.is_a?(::TCPSocket) or socket.is_a?(TCPConnectSocket)
|
141
|
+
raise TypeError, "socket must be a TCPSocket"
|
142
|
+
end
|
143
|
+
|
144
|
+
super
|
145
|
+
|
146
|
+
@address_family, @remote_port, @remote_host, @remote_addr = socket.peeraddr
|
147
|
+
end
|
148
|
+
|
149
|
+
def peeraddr
|
150
|
+
[@address_family, @remote_port, @remote_host, @remote_addr]
|
151
|
+
end
|
152
|
+
|
153
|
+
#########
|
154
|
+
protected
|
155
|
+
#########
|
156
|
+
|
157
|
+
class TCPConnectSocket < ::Socket
|
158
|
+
def initialize(family, addr, port, host = addr)
|
159
|
+
@host, @addr, @port = host, addr, port
|
160
|
+
@address_family = nil
|
161
|
+
|
162
|
+
super(family, ::Socket::SOCK_STREAM, 0)
|
163
|
+
begin
|
164
|
+
connect_nonblock(::Socket.sockaddr_in(port, addr))
|
165
|
+
rescue Errno::EINPROGRESS
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
def peeraddr
|
170
|
+
[
|
171
|
+
@address_family == ::Socket::AF_INET ? 'AF_INET' : 'AF_INET6',
|
172
|
+
@port,
|
173
|
+
@host,
|
174
|
+
@addr
|
175
|
+
]
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
class TCPConnectResolver < Coolio::DNSResolver
|
180
|
+
def initialize(socket, host, port, *args)
|
181
|
+
@sock, @host, @port, @args = socket, host, port, args
|
182
|
+
super(host)
|
183
|
+
end
|
184
|
+
|
185
|
+
def on_success(addr)
|
186
|
+
host, port, args = @host, @port, @args
|
187
|
+
|
188
|
+
@sock.instance_eval do
|
189
|
+
# DNSResolver only supports IPv4 so we can safely assume IPv4 address
|
190
|
+
begin
|
191
|
+
socket = TCPConnectSocket.new(::Socket::AF_INET, addr, port, host)
|
192
|
+
rescue Errno::ENETUNREACH
|
193
|
+
on_connect_failed
|
194
|
+
return
|
195
|
+
end
|
196
|
+
|
197
|
+
initialize(socket, *args)
|
198
|
+
@_connector = Socket::Connector.new(self, socket)
|
199
|
+
@_resolver = nil
|
200
|
+
end
|
201
|
+
@sock.attach(evloop)
|
202
|
+
end
|
203
|
+
|
204
|
+
def on_failure
|
205
|
+
@sock.__send__(:on_resolve_failed)
|
206
|
+
@sock.instance_eval do
|
207
|
+
@_resolver = nil
|
208
|
+
@_failed = true
|
209
|
+
end
|
210
|
+
return
|
211
|
+
end
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
class UNIXSocket < Socket
|
216
|
+
attr_reader :path, :address_family
|
217
|
+
|
218
|
+
# Connect to the given UNIX domain socket
|
219
|
+
def self.connect(path, *args)
|
220
|
+
new(::UNIXSocket.new(path), *args)
|
221
|
+
end
|
222
|
+
|
223
|
+
def initialize(socket)
|
224
|
+
raise ArgumentError, "socket must be a UNIXSocket" unless socket.is_a? ::UNIXSocket
|
225
|
+
|
226
|
+
super
|
227
|
+
@address_family, @path = socket.peeraddr
|
228
|
+
end
|
229
|
+
end
|
230
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (C)2007 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
|
+
class TimerWatcher
|
9
|
+
# The actual implementation of this class resides in the C extension
|
10
|
+
# Here we metaprogram proper event_callbacks for the callback methods
|
11
|
+
# These can take a block and store it to be called when the event
|
12
|
+
# is actually fired.
|
13
|
+
|
14
|
+
extend Meta
|
15
|
+
event_callback :on_timer
|
16
|
+
end
|
17
|
+
end
|
data/lib/coolio.rb
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
require File.expand_path('../spec_helper', __FILE__)
|
2
|
+
require 'tempfile'
|
3
|
+
require 'fcntl'
|
4
|
+
|
5
|
+
describe Cool.io::AsyncWatcher, :env => :win do
|
6
|
+
|
7
|
+
it "does not signal on spurious wakeups" do
|
8
|
+
aw = Cool.io::AsyncWatcher.new
|
9
|
+
tmp = Tempfile.new('coolio_async_watcher_test')
|
10
|
+
nr_fork = 2 # must be at least two for spurious wakeups
|
11
|
+
|
12
|
+
# We have aetter chance of failing if this overflows the pipe buffer
|
13
|
+
# which POSIX requires >= 512 bytes, Linux 2.6 uses 4096 bytes
|
14
|
+
nr_signal = 4096 * 4
|
15
|
+
|
16
|
+
append = File.open(tmp.path, "ab")
|
17
|
+
append.sync = true
|
18
|
+
rd, wr = ::IO.pipe
|
19
|
+
|
20
|
+
aw.on_signal { append.syswrite("#$$\n") }
|
21
|
+
children = nr_fork.times.map do
|
22
|
+
fork do
|
23
|
+
trap(:TERM) { exit!(0) }
|
24
|
+
rloop = Cool.io::Loop.default
|
25
|
+
aw.attach(rloop)
|
26
|
+
wr.write '.' # signal to master that we're ready
|
27
|
+
rloop.run
|
28
|
+
exit!(1) # should not get here
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# ensure children are ready
|
33
|
+
nr_fork.times { rd.sysread(1).should == '.' }
|
34
|
+
|
35
|
+
# send our signals
|
36
|
+
nr_signal.times { aw.signal }
|
37
|
+
|
38
|
+
# wait for the pipe buffer to be consumed by the children
|
39
|
+
sleep 1 while tmp.stat.ctime >= (Time.now - 4)
|
40
|
+
|
41
|
+
children.each do |pid|
|
42
|
+
Process.kill(:TERM, pid)
|
43
|
+
_, status = Process.waitpid2(pid)
|
44
|
+
status.exitstatus.should == 0
|
45
|
+
end
|
46
|
+
|
47
|
+
# we should've written a line for every signal we sent
|
48
|
+
lines = tmp.readlines
|
49
|
+
lines.size.should == nr_signal
|
50
|
+
|
51
|
+
# theoretically a bad kernel scheduler could give us fewer...
|
52
|
+
lines.sort.uniq.size.should == nr_fork
|
53
|
+
|
54
|
+
tmp.close!
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
data/spec/dns_spec.rb
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
require File.expand_path('../spec_helper', __FILE__)
|
2
|
+
|
3
|
+
VALID_DOMAIN = "google.com"
|
4
|
+
INVALID_DOMAIN = "gibidigibigididibitidibigitibidigitidididi.com"
|
5
|
+
|
6
|
+
class ItWorked < StandardError; end
|
7
|
+
class WontResolve < StandardError; end
|
8
|
+
|
9
|
+
class ConnectorThingy < Cool.io::TCPSocket
|
10
|
+
def on_connect
|
11
|
+
raise ItWorked
|
12
|
+
end
|
13
|
+
|
14
|
+
def on_resolve_failed
|
15
|
+
raise WontResolve
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
describe "DNS" do
|
20
|
+
before :each do
|
21
|
+
@loop = Cool.io::Loop.new
|
22
|
+
end
|
23
|
+
|
24
|
+
it "connects to valid domains" do
|
25
|
+
ConnectorThingy.connect(VALID_DOMAIN, 80).attach(@loop)
|
26
|
+
|
27
|
+
proc do
|
28
|
+
@loop.run
|
29
|
+
end.should raise_error(ItWorked)
|
30
|
+
end
|
31
|
+
|
32
|
+
it "fires on_resolve_failed for invalid domains" do
|
33
|
+
ConnectorThingy.connect(INVALID_DOMAIN, 80).attach(@loop)
|
34
|
+
|
35
|
+
proc do
|
36
|
+
@loop.run
|
37
|
+
end.should raise_error(WontResolve)
|
38
|
+
end
|
39
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
$LOAD_PATH.unshift File.dirname(__FILE__)
|
2
|
+
$LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
|
3
|
+
|
4
|
+
require 'rspec'
|
5
|
+
require 'cool.io'
|
6
|
+
|
7
|
+
RSpec.configure do |c|
|
8
|
+
if RUBY_PLATFORM =~ /mingw|win32/
|
9
|
+
$stderr.puts "Skip some specs on Windows"
|
10
|
+
c.filter_run_excluding :env => :win
|
11
|
+
end
|
12
|
+
end
|