cool.io 0.9.0
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 +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,17 @@
|
|
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 IOWatcher
|
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_readable, :on_writable
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,93 @@
|
|
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 'socket'
|
8
|
+
|
9
|
+
module Coolio
|
10
|
+
# Listeners wait for incoming connections. When a listener receives a
|
11
|
+
# connection it fires the on_connection event with the newly accepted
|
12
|
+
# socket as a parameter.
|
13
|
+
class Listener < IOWatcher
|
14
|
+
def initialize(listen_socket)
|
15
|
+
@listen_socket = listen_socket
|
16
|
+
super(@listen_socket)
|
17
|
+
end
|
18
|
+
|
19
|
+
# Returns an integer representing the underlying numeric file descriptor
|
20
|
+
def fileno
|
21
|
+
@listen_socket.fileno
|
22
|
+
end
|
23
|
+
|
24
|
+
# Close the listener
|
25
|
+
def close
|
26
|
+
detach if attached?
|
27
|
+
@listen_socket.close
|
28
|
+
end
|
29
|
+
|
30
|
+
# Called whenever the server receives a new connection
|
31
|
+
def on_connection(socket); end
|
32
|
+
event_callback :on_connection
|
33
|
+
|
34
|
+
#########
|
35
|
+
protected
|
36
|
+
#########
|
37
|
+
|
38
|
+
# Coolio callback for handling new connections
|
39
|
+
def on_readable
|
40
|
+
begin
|
41
|
+
on_connection @listen_socket.accept_nonblock
|
42
|
+
rescue Errno::EAGAIN, Errno::ECONNABORTED
|
43
|
+
# EAGAIN can be triggered here if the socket is shared between
|
44
|
+
# multiple processes and a thundering herd is woken up to accept
|
45
|
+
# one connection, only one process will get the connection and
|
46
|
+
# the others will be awoken.
|
47
|
+
# ECONNABORTED is documented in accept() manpages but modern TCP
|
48
|
+
# stacks with syncookies and/or accept()-filtering for DoS
|
49
|
+
# protection do not see it. In any case this error is harmless
|
50
|
+
# and we should instead spend our time with clients that follow
|
51
|
+
# through on connection attempts.
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
class TCPListener < Listener
|
57
|
+
DEFAULT_BACKLOG = 1024
|
58
|
+
|
59
|
+
# Create a new Coolio::TCPListener on the specified address and port.
|
60
|
+
# Accepts the following options:
|
61
|
+
#
|
62
|
+
# :backlog - Max size of the pending connection queue (default 1024)
|
63
|
+
# :reverse_lookup - Retain BasicSocket's reverse DNS functionality (default false)
|
64
|
+
#
|
65
|
+
# If the specified address is an TCPServer object, it will ignore
|
66
|
+
# the port and :backlog option and create a new Coolio::TCPListener out
|
67
|
+
# of the existing TCPServer object.
|
68
|
+
def initialize(addr, port = nil, options = {})
|
69
|
+
BasicSocket.do_not_reverse_lookup = true unless options[:reverse_lookup]
|
70
|
+
options[:backlog] ||= DEFAULT_BACKLOG
|
71
|
+
|
72
|
+
listen_socket = if ::TCPServer === addr
|
73
|
+
addr
|
74
|
+
else
|
75
|
+
raise ArgumentError, "port must be an integer" if nil == port
|
76
|
+
::TCPServer.new(addr, port)
|
77
|
+
end
|
78
|
+
listen_socket.instance_eval { listen(options[:backlog]) }
|
79
|
+
super(listen_socket)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
class UNIXListener < Listener
|
84
|
+
# Create a new Coolio::UNIXListener
|
85
|
+
#
|
86
|
+
# Accepts the same arguments as UNIXServer.new
|
87
|
+
# Optionally, it can also take anyn existing UNIXServer object
|
88
|
+
# and create a Coolio::UNIXListener out of it.
|
89
|
+
def initialize(*args)
|
90
|
+
super(::UNIXServer === args.first ? args.first : ::UNIXServer.new(*args))
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
data/lib/cool.io/loop.rb
ADDED
@@ -0,0 +1,130 @@
|
|
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 'thread'
|
8
|
+
|
9
|
+
# Monkeypatch Thread to include a method for obtaining the default Coolio::Loop
|
10
|
+
class Thread
|
11
|
+
def _coolio_loop
|
12
|
+
@_coolio_loop ||= Coolio::Loop.new
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
module Coolio
|
17
|
+
class Loop
|
18
|
+
# In Ruby 1.9 we want a Coolio::Loop per thread, but Ruby 1.8 is unithreaded
|
19
|
+
if RUBY_VERSION >= "1.9.0"
|
20
|
+
# Retrieve the default event loop for the current thread
|
21
|
+
def self.default
|
22
|
+
Thread.current._coolio_loop
|
23
|
+
end
|
24
|
+
else
|
25
|
+
# Retrieve the default event loop
|
26
|
+
def self.default
|
27
|
+
@@_coolio_loop ||= Coolio::Loop.new
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# Create a new Coolio::Loop
|
32
|
+
#
|
33
|
+
# Options:
|
34
|
+
#
|
35
|
+
# :skip_environment (boolean)
|
36
|
+
# Ignore the $LIBEV_FLAGS environment variable
|
37
|
+
#
|
38
|
+
# :fork_check (boolean)
|
39
|
+
# Enable autodetection of forks
|
40
|
+
#
|
41
|
+
# :backend
|
42
|
+
# Choose the default backend, one (or many in an array) of:
|
43
|
+
# :select (most platforms)
|
44
|
+
# :poll (most platforms except Windows)
|
45
|
+
# :epoll (Linux)
|
46
|
+
# :kqueue (BSD/Mac OS X)
|
47
|
+
# :port (Solaris 10)
|
48
|
+
#
|
49
|
+
def initialize(options = {})
|
50
|
+
@watchers = {}
|
51
|
+
@active_watchers = 0
|
52
|
+
|
53
|
+
flags = 0
|
54
|
+
|
55
|
+
options.each do |option, value|
|
56
|
+
case option
|
57
|
+
when :skip_environment
|
58
|
+
flags |= EVFLAG_NOEV if value
|
59
|
+
when :fork_check
|
60
|
+
flags |= EVFLAG_FORKCHECK if value
|
61
|
+
when :backend
|
62
|
+
value = [value] unless value.is_a? Array
|
63
|
+
value.each do |backend|
|
64
|
+
case backend
|
65
|
+
when :select then flags |= EVBACKEND_SELECT
|
66
|
+
when :poll then flags |= EVBACKEND_POLL
|
67
|
+
when :epoll then flags |= EVBACKEND_EPOLL
|
68
|
+
when :kqueue then flags |= EVBACKEND_KQUEUE
|
69
|
+
when :port then flags |= EVBACKEND_PORT
|
70
|
+
else raise ArgumentError, "no such backend: #{backend}"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
else raise ArgumentError, "no such option: #{option}"
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
@loop = ev_loop_new(flags)
|
78
|
+
end
|
79
|
+
|
80
|
+
# Attach a watcher to the loop
|
81
|
+
def attach(watcher)
|
82
|
+
watcher.attach self
|
83
|
+
end
|
84
|
+
|
85
|
+
# Run the event loop and dispatch events back to Ruby. If there
|
86
|
+
# are no watchers associated with the event loop it will return
|
87
|
+
# immediately. Otherwise, run will continue blocking and making
|
88
|
+
# event callbacks to watchers until all watchers associated with
|
89
|
+
# the loop have been disabled or detached. The loop may be
|
90
|
+
# explicitly stopped by calling the stop method on the loop object.
|
91
|
+
def run
|
92
|
+
raise RuntimeError, "no watchers for this loop" if @watchers.empty?
|
93
|
+
|
94
|
+
@running = true
|
95
|
+
while @running and not @active_watchers.zero?
|
96
|
+
run_once
|
97
|
+
end
|
98
|
+
@running = false
|
99
|
+
end
|
100
|
+
|
101
|
+
# Stop the event loop if it's running
|
102
|
+
def stop
|
103
|
+
raise RuntimeError, "loop not running" unless @running
|
104
|
+
@running = false
|
105
|
+
end
|
106
|
+
|
107
|
+
# Does the loop have any active watchers?
|
108
|
+
def has_active_watchers?
|
109
|
+
@active_watchers > 0
|
110
|
+
end
|
111
|
+
|
112
|
+
# All watchers attached to the current loop
|
113
|
+
def watchers
|
114
|
+
@watchers.keys
|
115
|
+
end
|
116
|
+
|
117
|
+
#######
|
118
|
+
private
|
119
|
+
#######
|
120
|
+
|
121
|
+
EVFLAG_NOENV = 0x1000000 # do NOT consult environment
|
122
|
+
EVFLAG_FORKCHECK = 0x2000000 # check for a fork in each iteration
|
123
|
+
|
124
|
+
EVBACKEND_SELECT = 0x00000001 # supported about anywhere
|
125
|
+
EVBACKEND_POLL = 0x00000002 # !win
|
126
|
+
EVBACKEND_EPOLL = 0x00000004 # linux
|
127
|
+
EVBACKEND_KQUEUE = 0x00000008 # bsd
|
128
|
+
EVBACKEND_PORT = 0x00000020 # solaris 10
|
129
|
+
end
|
130
|
+
end
|
data/lib/cool.io/meta.rb
ADDED
@@ -0,0 +1,49 @@
|
|
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
|
+
module Meta
|
9
|
+
# Use an alternate watcher with the attach/detach/enable/disable methods
|
10
|
+
# if it is presently assigned. This is useful if you are waiting for
|
11
|
+
# an event to occur before the current watcher can be used in earnest,
|
12
|
+
# such as making an outgoing TCP connection.
|
13
|
+
def watcher_delegate(proxy_var)
|
14
|
+
%w{attach detach enable disable}.each do |method|
|
15
|
+
module_eval <<-EOD
|
16
|
+
def #{method}(*args)
|
17
|
+
if #{proxy_var}
|
18
|
+
#{proxy_var}.#{method}(*args)
|
19
|
+
return self
|
20
|
+
end
|
21
|
+
|
22
|
+
super
|
23
|
+
end
|
24
|
+
EOD
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# Define callbacks whose behavior can be changed on-the-fly per instance.
|
29
|
+
# This is done by giving a block to the callback method, which is captured
|
30
|
+
# as a proc and stored for later. If the method is called without a block,
|
31
|
+
# the stored block is executed if present, otherwise it's a noop.
|
32
|
+
def event_callback(*methods)
|
33
|
+
methods.each do |method|
|
34
|
+
module_eval <<-EOD
|
35
|
+
def #{method}(*args, &block)
|
36
|
+
if block
|
37
|
+
@#{method}_callback = block
|
38
|
+
return
|
39
|
+
end
|
40
|
+
|
41
|
+
if @#{method}_callback
|
42
|
+
instance_exec(*args, &@#{method}_callback)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
EOD
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -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,224 @@
|
|
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
|
+
watcher_delegate :@_connector
|
21
|
+
|
22
|
+
def attach(evloop)
|
23
|
+
raise RuntimeError, "connection failed" if @_failed
|
24
|
+
|
25
|
+
if @_connector
|
26
|
+
@_connector.attach(evloop)
|
27
|
+
return self
|
28
|
+
end
|
29
|
+
|
30
|
+
super
|
31
|
+
end
|
32
|
+
|
33
|
+
# Called upon completion of a socket connection
|
34
|
+
def on_connect; end
|
35
|
+
event_callback :on_connect
|
36
|
+
|
37
|
+
# Called if a socket connection failed to complete
|
38
|
+
def on_connect_failed; end
|
39
|
+
event_callback :on_connect_failed
|
40
|
+
|
41
|
+
# Called if a hostname failed to resolve when connecting
|
42
|
+
# Defaults to calling on_connect_failed
|
43
|
+
def on_resolve_failed
|
44
|
+
on_connect_failed
|
45
|
+
end
|
46
|
+
|
47
|
+
#########
|
48
|
+
protected
|
49
|
+
#########
|
50
|
+
|
51
|
+
class Connector < IOWatcher
|
52
|
+
def initialize(coolio_socket, ruby_socket)
|
53
|
+
@coolio_socket, @ruby_socket = coolio_socket, ruby_socket
|
54
|
+
super(ruby_socket, :w)
|
55
|
+
end
|
56
|
+
|
57
|
+
def on_writable
|
58
|
+
evl = evloop
|
59
|
+
detach
|
60
|
+
|
61
|
+
if connect_successful?
|
62
|
+
@coolio_socket.instance_eval { @_connector = nil }
|
63
|
+
@coolio_socket.attach(evl)
|
64
|
+
@ruby_socket.setsockopt(::Socket::IPPROTO_TCP, ::Socket::TCP_NODELAY, [1].pack("l"))
|
65
|
+
@ruby_socket.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_KEEPALIVE, true)
|
66
|
+
|
67
|
+
@coolio_socket.__send__(:on_connect)
|
68
|
+
else
|
69
|
+
@coolio_socket.instance_eval { @_failed = true }
|
70
|
+
@coolio_socket.__send__(:on_connect_failed)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
#######
|
75
|
+
private
|
76
|
+
#######
|
77
|
+
|
78
|
+
def connect_successful?
|
79
|
+
@ruby_socket.getsockopt(::Socket::SOL_SOCKET, ::Socket::SO_ERROR).unpack('i').first == 0
|
80
|
+
rescue IOError
|
81
|
+
false
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
class TCPSocket < Socket
|
87
|
+
attr_reader :remote_host, :remote_addr, :remote_port, :address_family
|
88
|
+
watcher_delegate :@_resolver
|
89
|
+
|
90
|
+
# Similar to .new, but used in cases where the resulting object is in a
|
91
|
+
# "half-open" state. This is primarily used for when asynchronous
|
92
|
+
# DNS resolution is taking place. We don't actually have a handle to
|
93
|
+
# the socket we want to use to create the watcher yet, since we don't
|
94
|
+
# know the IP address to connect to.
|
95
|
+
def self.precreate(*args, &block)
|
96
|
+
obj = allocate
|
97
|
+
obj.__send__(:preinitialize, *args, &block)
|
98
|
+
obj
|
99
|
+
end
|
100
|
+
|
101
|
+
# Perform a non-blocking connect to the given host and port
|
102
|
+
# see examples/echo_client.rb
|
103
|
+
# addr is a string, can be an IP address or a hostname.
|
104
|
+
def self.connect(addr, port, *args)
|
105
|
+
family = nil
|
106
|
+
|
107
|
+
if (Resolv::IPv4.create(addr) rescue nil)
|
108
|
+
family = ::Socket::AF_INET
|
109
|
+
elsif(Resolv::IPv6.create(addr) rescue nil)
|
110
|
+
family = ::Socket::AF_INET6
|
111
|
+
end
|
112
|
+
|
113
|
+
if family
|
114
|
+
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
|
115
|
+
end
|
116
|
+
|
117
|
+
if host = Coolio::DNSResolver.hosts(addr)
|
118
|
+
return connect(host, port, *args) # calls this same function
|
119
|
+
end
|
120
|
+
|
121
|
+
precreate(addr, port, *args)
|
122
|
+
end
|
123
|
+
|
124
|
+
# Called by precreate during asyncronous DNS resolution
|
125
|
+
def preinitialize(addr, port, *args)
|
126
|
+
@_write_buffer = ::IO::Buffer.new # allow for writing BEFORE the DNS has resolved
|
127
|
+
@remote_host, @remote_addr, @remote_port = addr, addr, port
|
128
|
+
@_resolver = TCPConnectResolver.new(self, addr, port, *args)
|
129
|
+
end
|
130
|
+
|
131
|
+
private :preinitialize
|
132
|
+
|
133
|
+
def initialize(socket)
|
134
|
+
unless socket.is_a?(::TCPSocket) or socket.is_a?(TCPConnectSocket)
|
135
|
+
raise TypeError, "socket must be a TCPSocket"
|
136
|
+
end
|
137
|
+
|
138
|
+
super
|
139
|
+
|
140
|
+
@address_family, @remote_port, @remote_host, @remote_addr = socket.peeraddr
|
141
|
+
end
|
142
|
+
|
143
|
+
def peeraddr
|
144
|
+
[@address_family, @remote_port, @remote_host, @remote_addr]
|
145
|
+
end
|
146
|
+
|
147
|
+
#########
|
148
|
+
protected
|
149
|
+
#########
|
150
|
+
|
151
|
+
class TCPConnectSocket < ::Socket
|
152
|
+
def initialize(family, addr, port, host = addr)
|
153
|
+
@host, addr, @port = host, addr, port
|
154
|
+
@address_family = nil
|
155
|
+
|
156
|
+
@socket = super(family, ::Socket::SOCK_STREAM, 0)
|
157
|
+
begin
|
158
|
+
@socket.connect_nonblock(::Socket.sockaddr_in(port, addr))
|
159
|
+
rescue Errno::EINPROGRESS
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
def peeraddr
|
164
|
+
[
|
165
|
+
@address_family == ::Socket::AF_INET ? 'AF_INET' : 'AF_INET6',
|
166
|
+
@port,
|
167
|
+
@host,
|
168
|
+
@addr
|
169
|
+
]
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
class TCPConnectResolver < Coolio::DNSResolver
|
174
|
+
def initialize(socket, host, port, *args)
|
175
|
+
@sock, @host, @port, @args = socket, host, port, args
|
176
|
+
super(host)
|
177
|
+
end
|
178
|
+
|
179
|
+
def on_success(addr)
|
180
|
+
host, port, args = @host, @port, @args
|
181
|
+
|
182
|
+
@sock.instance_eval do
|
183
|
+
# DNSResolver only supports IPv4 so we can safely assume IPv4 address
|
184
|
+
begin
|
185
|
+
socket = TCPConnectSocket.new(::Socket::AF_INET, addr, port, host)
|
186
|
+
rescue Errno::ENETUNREACH
|
187
|
+
on_connect_failed
|
188
|
+
return
|
189
|
+
end
|
190
|
+
|
191
|
+
initialize(socket, *args)
|
192
|
+
@_connector = Socket::Connector.new(self, socket)
|
193
|
+
@_resolver = nil
|
194
|
+
end
|
195
|
+
@sock.attach(evloop)
|
196
|
+
end
|
197
|
+
|
198
|
+
def on_failure
|
199
|
+
@sock.__send__(:on_resolve_failed)
|
200
|
+
@sock.instance_eval do
|
201
|
+
@_resolver = nil
|
202
|
+
@_failed = true
|
203
|
+
end
|
204
|
+
return
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
class UNIXSocket < Socket
|
210
|
+
attr_reader :path, :address_family
|
211
|
+
|
212
|
+
# Connect to the given UNIX domain socket
|
213
|
+
def self.connect(path, *args)
|
214
|
+
new(::UNIXSocket.new(path), *args)
|
215
|
+
end
|
216
|
+
|
217
|
+
def initialize(socket)
|
218
|
+
raise ArgumentError, "socket must be a UNIXSocket" unless socket.is_a? ::UNIXSocket
|
219
|
+
|
220
|
+
super
|
221
|
+
@address_family, @path = socket.peeraddr
|
222
|
+
end
|
223
|
+
end
|
224
|
+
end
|