cool.io 1.4.1-x64-mingw32

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 (76) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +29 -0
  3. data/.rspec +3 -0
  4. data/.travis.yml +13 -0
  5. data/CHANGES.md +229 -0
  6. data/Gemfile +4 -0
  7. data/LICENSE +20 -0
  8. data/README.md +166 -0
  9. data/Rakefile +79 -0
  10. data/cool.io.gemspec +29 -0
  11. data/examples/callbacked_echo_server.rb +24 -0
  12. data/examples/dslified_echo_client.rb +34 -0
  13. data/examples/dslified_echo_server.rb +24 -0
  14. data/examples/echo_client.rb +38 -0
  15. data/examples/echo_server.rb +27 -0
  16. data/examples/google.rb +9 -0
  17. data/ext/cool.io/.gitignore +5 -0
  18. data/ext/cool.io/cool.io.h +59 -0
  19. data/ext/cool.io/cool.io_ext.c +25 -0
  20. data/ext/cool.io/ev_wrap.h +10 -0
  21. data/ext/cool.io/extconf.rb +61 -0
  22. data/ext/cool.io/iowatcher.c +189 -0
  23. data/ext/cool.io/libev.c +8 -0
  24. data/ext/cool.io/loop.c +261 -0
  25. data/ext/cool.io/stat_watcher.c +269 -0
  26. data/ext/cool.io/timer_watcher.c +219 -0
  27. data/ext/cool.io/utils.c +122 -0
  28. data/ext/cool.io/watcher.c +264 -0
  29. data/ext/cool.io/watcher.h +71 -0
  30. data/ext/iobuffer/extconf.rb +9 -0
  31. data/ext/iobuffer/iobuffer.c +767 -0
  32. data/ext/libev/Changes +507 -0
  33. data/ext/libev/LICENSE +37 -0
  34. data/ext/libev/README +58 -0
  35. data/ext/libev/README.embed +3 -0
  36. data/ext/libev/ev.c +5054 -0
  37. data/ext/libev/ev.h +853 -0
  38. data/ext/libev/ev_epoll.c +282 -0
  39. data/ext/libev/ev_kqueue.c +214 -0
  40. data/ext/libev/ev_poll.c +148 -0
  41. data/ext/libev/ev_port.c +185 -0
  42. data/ext/libev/ev_select.c +362 -0
  43. data/ext/libev/ev_vars.h +204 -0
  44. data/ext/libev/ev_win32.c +163 -0
  45. data/ext/libev/ev_wrap.h +200 -0
  46. data/ext/libev/ruby_gil.patch +97 -0
  47. data/ext/libev/test_libev_win32.c +123 -0
  48. data/ext/libev/win_select.patch +115 -0
  49. data/lib/.gitignore +2 -0
  50. data/lib/cool.io.rb +34 -0
  51. data/lib/cool.io/async_watcher.rb +43 -0
  52. data/lib/cool.io/custom_require.rb +9 -0
  53. data/lib/cool.io/dns_resolver.rb +219 -0
  54. data/lib/cool.io/dsl.rb +139 -0
  55. data/lib/cool.io/io.rb +194 -0
  56. data/lib/cool.io/iowatcher.rb +17 -0
  57. data/lib/cool.io/listener.rb +99 -0
  58. data/lib/cool.io/loop.rb +122 -0
  59. data/lib/cool.io/meta.rb +49 -0
  60. data/lib/cool.io/server.rb +75 -0
  61. data/lib/cool.io/socket.rb +230 -0
  62. data/lib/cool.io/timer_watcher.rb +17 -0
  63. data/lib/cool.io/version.rb +7 -0
  64. data/lib/coolio.rb +2 -0
  65. data/spec/async_watcher_spec.rb +57 -0
  66. data/spec/dns_spec.rb +43 -0
  67. data/spec/iobuffer_spec.rb +147 -0
  68. data/spec/spec_helper.rb +19 -0
  69. data/spec/stat_watcher_spec.rb +77 -0
  70. data/spec/tcp_server_spec.rb +225 -0
  71. data/spec/tcp_socket_spec.rb +185 -0
  72. data/spec/timer_watcher_spec.rb +59 -0
  73. data/spec/udp_socket_spec.rb +58 -0
  74. data/spec/unix_listener_spec.rb +25 -0
  75. data/spec/unix_server_spec.rb +27 -0
  76. metadata +182 -0
@@ -0,0 +1,97 @@
1
+ diff --git a/ext/libev/ev.c b/ext/libev/ev.c
2
+ index e5bd5ab..10f6ff2 100644
3
+ --- a/ext/libev/ev.c
4
+ +++ b/ext/libev/ev.c
5
+ @@ -37,6 +37,10 @@
6
+ * either the BSD or the GPL.
7
+ */
8
+
9
+ +/* ########## COOLIO PATCHERY HO! ########## */
10
+ +#include "ruby.h"
11
+ +/* ######################################## */
12
+ +
13
+ /* this big block deduces configuration from config.h */
14
+ #ifndef EV_STANDALONE
15
+ # ifdef EV_CONFIG_H
16
+ @@ -3237,9 +3241,27 @@ time_update (EV_P_ ev_tstamp max_block)
17
+ }
18
+ }
19
+
20
+ +/* ########## COOLIO PATCHERY HO! ########## */
21
+ +#if defined(HAVE_RB_THREAD_BLOCKING_REGION)
22
+ +static
23
+ +VALUE ev_backend_poll(void **args)
24
+ +{
25
+ + struct ev_loop *loop = (struct ev_loop *)args[0];
26
+ + ev_tstamp waittime = *(ev_tstamp *)args[1];
27
+ + backend_poll (EV_A_ waittime);
28
+ +}
29
+ +#endif
30
+ +/* ######################################## */
31
+ +
32
+ int
33
+ ev_run (EV_P_ int flags)
34
+ {
35
+ +/* ########## COOLIO PATCHERY HO! ########## */
36
+ +#if defined(HAVE_RB_THREAD_BLOCKING_REGION)
37
+ + void *poll_args[2];
38
+ +#endif
39
+ +/* ######################################## */
40
+ +
41
+ #if EV_FEATURE_API
42
+ ++loop_depth;
43
+ #endif
44
+ @@ -3357,7 +3379,53 @@ ev_run (EV_P_ int flags)
45
+ ++loop_count;
46
+ #endif
47
+ assert ((loop_done = EVBREAK_RECURSE, 1)); /* assert for side effect */
48
+ +
49
+ +/*
50
+ +########################## COOLIO PATCHERY HO! ##########################
51
+ +
52
+ +The original patch file is made by Tony Arcieri.
53
+ +https://github.com/celluloid/nio4r/blob/680143345726c5a64bb22376ca8fc3c6857019ae/ext/libev/ruby_gil.patch.
54
+ +
55
+ +According to the grandwizards of Ruby, locking and unlocking of the global
56
+ +interpreter lock are apparently too powerful a concept for a mere mortal to
57
+ +wield (although redefining what + and - do to numbers is totally cool).
58
+ +And so it came to pass that the only acceptable way to release the global
59
+ +interpreter lock is through a convoluted callback system that thakes a
60
+ +function pointer. While the grandwizard of libev foresaw this sort of scenario,
61
+ +he too attempted to place an API with callbacks on it, one that runs before
62
+ +the system call, and one that runs immediately after.
63
+ +
64
+ +And so it came to pass that trying to wrap everything up in callbacks created
65
+ +two incompatible APIs, Ruby's which releases the global interpreter lock and
66
+ +reacquires it when the callback returns, and libev's, which wants two
67
+ +callbacks, one which runs before the polling operation starts, and one which
68
+ +runs after it finishes.
69
+ +
70
+ +These two systems are incompatible as they both want to use callbacks to
71
+ +solve the same problem, however libev wants to use before/after callbacks,
72
+ +and Ruby wants to use an "around" callback. This presents a significant
73
+ +problem as these two patterns of callbacks are diametrical opposites of each
74
+ +other and thus cannot be composed.
75
+ +
76
+ +And thus we are left with no choice but to patch the internals of libev in
77
+ +order to release a mutex at just the precise moment.
78
+ +
79
+ +Let this be a lesson to the all: CALLBACKS FUCKING BLOW
80
+ +
81
+ +#######################################################################
82
+ +*/
83
+ +
84
+ +#if defined(HAVE_RB_THREAD_BLOCKING_REGION)
85
+ + poll_args[0] = (void *)loop;
86
+ + poll_args[1] = (void *)&waittime;
87
+ + rb_thread_blocking_region(ev_backend_poll, (void *)&poll_args, RUBY_UBF_IO, 0);
88
+ +#else
89
+ backend_poll (EV_A_ waittime);
90
+ +#endif
91
+ +/*
92
+ +############################# END PATCHERY ############################
93
+ +*/
94
+ +
95
+ assert ((loop_done = EVBREAK_CANCEL, 1)); /* assert for side effect */
96
+
97
+ pipe_write_wanted = 0; /* just an optimisation, no fence needed */
@@ -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,115 @@
1
+ diff --git a/ext/libev/ev_select.c b/ext/libev/ev_select.c
2
+ index f38d6ca..3a32642 100644
3
+ --- a/ext/libev/ev_select.c
4
+ +++ b/ext/libev/ev_select.c
5
+ @@ -67,6 +67,53 @@
6
+
7
+ #include <string.h>
8
+
9
+ +#ifdef _WIN32
10
+ +/*
11
+ +########## COOLIO PATCHERY HO! ##########
12
+ +
13
+ +Ruby undefs FD_* utilities for own implementation.
14
+ +It converts fd argument into socket handle internally on Windows,
15
+ +so libev should not use Ruby's FD_* utilities.
16
+ +
17
+ +Following FD_* utilities come from MinGW.
18
+ +RubyInstaller is built by MinGW so this should work.
19
+ +*/
20
+ +int PASCAL __WSAFDIsSet(SOCKET,fd_set*);
21
+ +#define EV_WIN_FD_CLR(fd,set) do { u_int __i;\
22
+ +for (__i = 0; __i < ((fd_set *)(set))->fd_count ; __i++) {\
23
+ + if (((fd_set *)(set))->fd_array[__i] == (fd)) {\
24
+ + while (__i < ((fd_set *)(set))->fd_count-1) {\
25
+ + ((fd_set*)(set))->fd_array[__i] = ((fd_set*)(set))->fd_array[__i+1];\
26
+ + __i++;\
27
+ + }\
28
+ + ((fd_set*)(set))->fd_count--;\
29
+ + break;\
30
+ + }\
31
+ +}\
32
+ +} while (0)
33
+ +#define EV_WIN_FD_SET(fd, set) do { u_int __i;\
34
+ +for (__i = 0; __i < ((fd_set *)(set))->fd_count ; __i++) {\
35
+ + if (((fd_set *)(set))->fd_array[__i] == (fd)) {\
36
+ + break;\
37
+ + }\
38
+ +}\
39
+ +if (__i == ((fd_set *)(set))->fd_count) {\
40
+ + if (((fd_set *)(set))->fd_count < FD_SETSIZE) {\
41
+ + ((fd_set *)(set))->fd_array[__i] = (fd);\
42
+ + ((fd_set *)(set))->fd_count++;\
43
+ + }\
44
+ +}\
45
+ +} while(0)
46
+ +#define EV_WIN_FD_ZERO(set) (((fd_set *)(set))->fd_count=0)
47
+ +#define EV_WIN_FD_ISSET(fd, set) __WSAFDIsSet((SOCKET)(fd), (fd_set *)(set))
48
+ +/* ######################################## */
49
+ +#else
50
+ +#define EV_WIN_FD_CLR FD_CLR
51
+ +#define EV_WIN_FD_SET FD_SET
52
+ +#define EV_WIN_FD_ZERO FD_ZERO
53
+ +#define EV_WIN_FD_ISSET FD_ISSET
54
+ +#endif
55
+ +
56
+ static void
57
+ select_modify (EV_P_ int fd, int oev, int nev)
58
+ {
59
+ @@ -91,17 +138,17 @@ select_modify (EV_P_ int fd, int oev, int nev)
60
+ if ((oev ^ nev) & EV_READ)
61
+ #endif
62
+ if (nev & EV_READ)
63
+ - FD_SET (handle, (fd_set *)vec_ri);
64
+ + EV_WIN_FD_SET (handle, (fd_set *)vec_ri);
65
+ else
66
+ - FD_CLR (handle, (fd_set *)vec_ri);
67
+ + EV_WIN_FD_CLR (handle, (fd_set *)vec_ri);
68
+
69
+ #if EV_SELECT_IS_WINSOCKET
70
+ if ((oev ^ nev) & EV_WRITE)
71
+ #endif
72
+ if (nev & EV_WRITE)
73
+ - FD_SET (handle, (fd_set *)vec_wi);
74
+ + EV_WIN_FD_SET (handle, (fd_set *)vec_wi);
75
+ else
76
+ - FD_CLR (handle, (fd_set *)vec_wi);
77
+ + EV_WIN_FD_CLR (handle, (fd_set *)vec_wi);
78
+
79
+ #else
80
+
81
+ @@ -136,6 +183,8 @@ select_modify (EV_P_ int fd, int oev, int nev)
82
+ }
83
+ }
84
+
85
+ +#undef socket
86
+ +
87
+ static void
88
+ select_poll (EV_P_ ev_tstamp timeout)
89
+ {
90
+ @@ -230,10 +279,10 @@ select_poll (EV_P_ ev_tstamp timeout)
91
+ int handle = fd;
92
+ #endif
93
+
94
+ - if (FD_ISSET (handle, (fd_set *)vec_ro)) events |= EV_READ;
95
+ - if (FD_ISSET (handle, (fd_set *)vec_wo)) events |= EV_WRITE;
96
+ + if (EV_WIN_FD_ISSET (handle, (fd_set *)vec_ro)) events |= EV_READ;
97
+ + if (EV_WIN_FD_ISSET (handle, (fd_set *)vec_wo)) events |= EV_WRITE;
98
+ #ifdef _WIN32
99
+ - if (FD_ISSET (handle, (fd_set *)vec_eo)) events |= EV_WRITE;
100
+ + if (EV_WIN_FD_ISSET (handle, (fd_set *)vec_eo)) events |= EV_WRITE;
101
+ #endif
102
+
103
+ if (expect_true (events))
104
+ @@ -279,9 +328,9 @@ select_init (EV_P_ int flags)
105
+ backend_poll = select_poll;
106
+
107
+ #if EV_SELECT_USE_FD_SET
108
+ - vec_ri = ev_malloc (sizeof (fd_set)); FD_ZERO ((fd_set *)vec_ri);
109
+ + vec_ri = ev_malloc (sizeof (fd_set)); EV_WIN_FD_ZERO ((fd_set *)vec_ri);
110
+ vec_ro = ev_malloc (sizeof (fd_set));
111
+ - vec_wi = ev_malloc (sizeof (fd_set)); FD_ZERO ((fd_set *)vec_wi);
112
+ + vec_wi = ev_malloc (sizeof (fd_set)); EV_WIN_FD_ZERO ((fd_set *)vec_wi);
113
+ vec_wo = ev_malloc (sizeof (fd_set));
114
+ #ifdef _WIN32
115
+ vec_eo = ev_malloc (sizeof (fd_set));
@@ -0,0 +1,2 @@
1
+ *.so
2
+ *.bundle
@@ -0,0 +1,34 @@
1
+ #--
2
+ # Copyright (C)2011 Tony Arcieri
3
+ # You can redistribute this under the terms of the Ruby license
4
+ # See file LICENSE for details
5
+ #++
6
+
7
+ require "cool.io/version"
8
+ require "cool.io/custom_require"
9
+ cool_require "iobuffer_ext"
10
+ cool_require "cool.io_ext"
11
+
12
+ require "cool.io/loop"
13
+ require "cool.io/meta"
14
+ require "cool.io/io"
15
+ require "cool.io/iowatcher"
16
+ require "cool.io/timer_watcher"
17
+ require "cool.io/async_watcher"
18
+ require "cool.io/listener"
19
+ require "cool.io/dns_resolver"
20
+ require "cool.io/socket"
21
+ require "cool.io/server"
22
+
23
+ module Coolio
24
+ def self.inspect
25
+ "Cool.io"
26
+ end
27
+ end
28
+
29
+ module Cool
30
+ # Allow Coolio module to be referenced as Cool.io
31
+ def self.io
32
+ Coolio
33
+ end
34
+ 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,9 @@
1
+ def cool_require(gem)
2
+ begin
3
+ m = /(\d+.\d+)/.match(RUBY_VERSION)
4
+ ver = m[1]
5
+ require "#{ver}/#{gem}.so"
6
+ rescue LoadError
7
+ require gem
8
+ end
9
+ end
@@ -0,0 +1,219 @@
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
+ require 'resolv'
21
+
22
+ module Coolio
23
+ # A non-blocking DNS resolver. It provides interfaces for querying both
24
+ # /etc/hosts and nameserves listed in /etc/resolv.conf, or nameservers of
25
+ # your choosing.
26
+ #
27
+ # Presently the client only supports UDP requests against your nameservers
28
+ # and cannot resolve anything with records larger than 512-bytes. Also,
29
+ # IPv6 is not presently supported.
30
+ #
31
+ # DNSResolver objects are one-shot. Once they resolve a domain name they
32
+ # automatically detach themselves from the event loop and cannot be used
33
+ # again.
34
+ class DNSResolver < IOWatcher
35
+ #--
36
+ DNS_PORT = 53
37
+ DATAGRAM_SIZE = 512
38
+ TIMEOUT = 3 # Retry timeout for each datagram sent
39
+ RETRIES = 4 # Number of retries to attempt
40
+ # so currently total is 12s before it will err due to timeouts
41
+ # if it errs due to inability to reach the DNS server [Errno::EHOSTUNREACH], same
42
+ # Query /etc/hosts (or the specified hostfile) for the given host
43
+ def self.hosts(host, hostfile = Resolv::Hosts::DefaultFileName)
44
+ hosts = {}
45
+ File.open(hostfile) do |f|
46
+ f.each_line do |host_entry|
47
+ entries = host_entry.gsub(/#.*$/, '').gsub(/\s+/, ' ').split(' ')
48
+ addr = entries.shift
49
+ entries.each { |e| hosts[e] ||= addr }
50
+ end
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 = Resolv::DNS::Config.default_config_hash[:nameserver]
63
+ raise RuntimeError, "no nameservers found" 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
+ begin
110
+ @socket.send request_message, 0, @nameservers.first, DNS_PORT
111
+ rescue Errno::EHOSTUNREACH # TODO figure out why it has to be wrapper here, when the other wrapper should be wrapping this one!
112
+ end
113
+ end
114
+
115
+ # Called by the subclass when the DNS response is available
116
+ def on_readable
117
+ datagram = nil
118
+ begin
119
+ datagram = @socket.recvfrom_nonblock(DATAGRAM_SIZE).first
120
+ rescue Errno::ECONNREFUSED
121
+ end
122
+
123
+ address = response_address datagram rescue nil
124
+ address ? on_success(address) : on_failure
125
+ detach
126
+ end
127
+
128
+ def request_question(hostname)
129
+ raise ArgumentError, "hostname cannot be nil" if hostname.nil?
130
+
131
+ # Query name
132
+ message = hostname.split('.').map { |s| [s.size].pack('C') << s }.join + "\0"
133
+
134
+ # Host address query
135
+ qtype = 1
136
+
137
+ # Internet query
138
+ qclass = 1
139
+
140
+ message << [qtype, qclass].pack('nn')
141
+ end
142
+
143
+ def request_message
144
+ # Standard query header
145
+ message = [2, 1, 0].pack('nCC')
146
+
147
+ # One entry
148
+ qdcount = 1
149
+
150
+ # No answer, authority, or additional records
151
+ ancount = nscount = arcount = 0
152
+
153
+ message << [qdcount, ancount, nscount, arcount].pack('nnnn')
154
+ message << @question
155
+ end
156
+
157
+ def response_address(message)
158
+ # Confirm the ID field
159
+ id = message[0..1].unpack('n').first.to_i
160
+ return unless id == 2
161
+
162
+ # Check the QR value and confirm this message is a response
163
+ qr = message[2..2].unpack('B1').first.to_i
164
+ return unless qr == 1
165
+
166
+ # Check the RCODE (lower nibble) and ensure there wasn't an error
167
+ rcode = message[3..3].unpack('B8').first[4..7].to_i(2)
168
+ return unless rcode == 0
169
+
170
+ # Extract the question and answer counts
171
+ qdcount, ancount = message[4..7].unpack('nn').map { |n| n.to_i }
172
+
173
+ # We only asked one question
174
+ return unless qdcount == 1
175
+ message.slice!(0, 12)
176
+
177
+ # Make sure it's the same question
178
+ return unless message[0..(@question.size-1)] == @question
179
+ message.slice!(0, @question.size)
180
+
181
+ # Extract the RDLENGTH
182
+ while not message.empty?
183
+ type = message[2..3].unpack('n').first.to_i
184
+ rdlength = message[10..11].unpack('n').first.to_i
185
+ rdata = message[12..(12 + rdlength - 1)]
186
+ message.slice!(0, 12 + rdlength)
187
+
188
+ # Only IPv4 supported
189
+ next unless rdlength == 4
190
+
191
+ # If we got an Internet address back, return it
192
+ return rdata.unpack('CCCC').join('.') if type == 1
193
+ end
194
+
195
+ nil
196
+ end
197
+
198
+ class Timeout < TimerWatcher
199
+ def initialize(resolver)
200
+ @resolver = resolver
201
+ @attempts = 0
202
+ super(TIMEOUT, true)
203
+ end
204
+
205
+ def on_timer
206
+ @attempts += 1
207
+ if @attempts <= RETRIES
208
+ begin
209
+ return @resolver.__send__(:send_request)
210
+ rescue Errno::EHOSTUNREACH # if the DNS is toast try again after the timeout occurs again
211
+ return nil
212
+ end
213
+ end
214
+ @resolver.__send__(:on_timeout)
215
+ @resolver.detach
216
+ end
217
+ end
218
+ end
219
+ end