cool.io 1.4.1-x64-mingw32

Sign up to get free protection for your applications and to get access to all the features.
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