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.
Files changed (73) hide show
  1. data/.gitignore +25 -0
  2. data/CHANGES +199 -0
  3. data/LICENSE +20 -0
  4. data/README.markdown +4 -0
  5. data/Rakefile +98 -0
  6. data/VERSION +1 -0
  7. data/examples/echo_client.rb +38 -0
  8. data/examples/echo_server.rb +27 -0
  9. data/examples/google.rb +9 -0
  10. data/examples/httpclient.rb +38 -0
  11. data/ext/cool.io/.gitignore +5 -0
  12. data/ext/cool.io/cool.io.h +58 -0
  13. data/ext/cool.io/cool.io_ext.c +25 -0
  14. data/ext/cool.io/ev_wrap.h +8 -0
  15. data/ext/cool.io/extconf.rb +69 -0
  16. data/ext/cool.io/iowatcher.c +189 -0
  17. data/ext/cool.io/libev.c +8 -0
  18. data/ext/cool.io/loop.c +303 -0
  19. data/ext/cool.io/stat_watcher.c +191 -0
  20. data/ext/cool.io/timer_watcher.c +219 -0
  21. data/ext/cool.io/utils.c +122 -0
  22. data/ext/cool.io/watcher.c +264 -0
  23. data/ext/cool.io/watcher.h +71 -0
  24. data/ext/http11_client/.gitignore +5 -0
  25. data/ext/http11_client/ext_help.h +14 -0
  26. data/ext/http11_client/extconf.rb +6 -0
  27. data/ext/http11_client/http11_client.c +300 -0
  28. data/ext/http11_client/http11_parser.c +403 -0
  29. data/ext/http11_client/http11_parser.h +48 -0
  30. data/ext/http11_client/http11_parser.rl +173 -0
  31. data/ext/libev/Changes +364 -0
  32. data/ext/libev/LICENSE +36 -0
  33. data/ext/libev/README +58 -0
  34. data/ext/libev/README.embed +3 -0
  35. data/ext/libev/ev.c +3867 -0
  36. data/ext/libev/ev.h +826 -0
  37. data/ext/libev/ev_epoll.c +234 -0
  38. data/ext/libev/ev_kqueue.c +198 -0
  39. data/ext/libev/ev_poll.c +148 -0
  40. data/ext/libev/ev_port.c +164 -0
  41. data/ext/libev/ev_select.c +307 -0
  42. data/ext/libev/ev_vars.h +197 -0
  43. data/ext/libev/ev_win32.c +153 -0
  44. data/ext/libev/ev_wrap.h +186 -0
  45. data/ext/libev/test_libev_win32.c +123 -0
  46. data/ext/libev/update_ev_wrap +19 -0
  47. data/lib/.gitignore +2 -0
  48. data/lib/cool.io.rb +30 -0
  49. data/lib/cool.io/async_watcher.rb +43 -0
  50. data/lib/cool.io/dns_resolver.rb +220 -0
  51. data/lib/cool.io/eventmachine.rb +234 -0
  52. data/lib/cool.io/http_client.rb +419 -0
  53. data/lib/cool.io/io.rb +174 -0
  54. data/lib/cool.io/iowatcher.rb +17 -0
  55. data/lib/cool.io/listener.rb +93 -0
  56. data/lib/cool.io/loop.rb +130 -0
  57. data/lib/cool.io/meta.rb +49 -0
  58. data/lib/cool.io/server.rb +74 -0
  59. data/lib/cool.io/socket.rb +224 -0
  60. data/lib/cool.io/timer_watcher.rb +17 -0
  61. data/lib/coolio.rb +2 -0
  62. data/lib/rev.rb +4 -0
  63. data/spec/async_watcher_spec.rb +57 -0
  64. data/spec/possible_tests/schedules_other_threads.rb +48 -0
  65. data/spec/possible_tests/test_on_resolve_failed.rb +9 -0
  66. data/spec/possible_tests/test_resolves.rb +27 -0
  67. data/spec/possible_tests/test_write_during_resolve.rb +27 -0
  68. data/spec/possible_tests/works_straight.rb +71 -0
  69. data/spec/spec_helper.rb +5 -0
  70. data/spec/timer_watcher_spec.rb +55 -0
  71. data/spec/unix_listener_spec.rb +25 -0
  72. data/spec/unix_server_spec.rb +25 -0
  73. 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
@@ -0,0 +1,2 @@
1
+ *.so
2
+ *.bundle
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