em-resolv-replace 1.0.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/.document +5 -0
- data/.gitignore +21 -0
- data/LICENSE +20 -0
- data/README.rdoc +32 -0
- data/Rakefile +53 -0
- data/VERSION +1 -0
- data/lib/em-dns-resolver.rb +162 -0
- data/lib/em-resolv-replace.rb +41 -0
- data/test/helper.rb +9 -0
- data/test/test_em-resolv-replace.rb +43 -0
- metadata +85 -0
data/.document
ADDED
data/.gitignore
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 Mike Perham
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
= em-resolv-replace
|
2
|
+
|
3
|
+
EventMachine-aware DNS lookup for Ruby.
|
4
|
+
|
5
|
+
Ruby's stock DNS resolution, by default, blocks the entire Ruby VM from processing while
|
6
|
+
the lookup is happening, because it calls out to the native libc resolver code. A slow
|
7
|
+
DNS server can cause your entire Ruby process to grind to a halt. Ruby comes with a pure
|
8
|
+
Ruby replacement that is not loaded by default:
|
9
|
+
|
10
|
+
require 'resolv'
|
11
|
+
require 'resolv-replace'
|
12
|
+
|
13
|
+
'resolv' is the pure Ruby DNS resolver. 'resolv-replace' monkeypatches the various Ruby
|
14
|
+
Socket objects to use resolv. This gem monkeypatches the monkeypatch so that the Socket
|
15
|
+
classes will use an EventMachine-aware resolver, em-dns-resolver.
|
16
|
+
|
17
|
+
== Usage
|
18
|
+
|
19
|
+
Just require em-resolv-replace when initializing your application:
|
20
|
+
|
21
|
+
require 'em-resolv-replace'
|
22
|
+
|
23
|
+
The code will use the EM-aware resolver if EventMachine is running.
|
24
|
+
|
25
|
+
== Credits
|
26
|
+
|
27
|
+
em-dns-resolver.rb is taken from the em-dns project, with a connection bugfix applied by myself,
|
28
|
+
and was written by Aman Gupta and Stephan Maka.
|
29
|
+
|
30
|
+
== Author
|
31
|
+
|
32
|
+
Mike Perham, @mperham, mperham AT gmail.com, http://github.com/mperham
|
data/Rakefile
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
gem.name = "em-resolv-replace"
|
8
|
+
gem.summary = %Q{EventMachine-aware DNS lookup for Ruby}
|
9
|
+
gem.email = "mperham@gmail.com"
|
10
|
+
gem.homepage = "http://github.com/mperham/em-resolv-replace"
|
11
|
+
gem.authors = ["Mike Perham"]
|
12
|
+
gem.add_development_dependency "shoulda", ">= 2.10.3"
|
13
|
+
gem.add_development_dependency "mocha", ">= 0.9.8"
|
14
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
15
|
+
end
|
16
|
+
Jeweler::GemcutterTasks.new
|
17
|
+
rescue LoadError
|
18
|
+
puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
|
19
|
+
end
|
20
|
+
|
21
|
+
require 'rake/testtask'
|
22
|
+
Rake::TestTask.new(:test) do |test|
|
23
|
+
test.libs << 'lib' << 'test'
|
24
|
+
test.pattern = 'test/**/test_*.rb'
|
25
|
+
test.verbose = true
|
26
|
+
end
|
27
|
+
|
28
|
+
begin
|
29
|
+
require 'rcov/rcovtask'
|
30
|
+
Rcov::RcovTask.new do |test|
|
31
|
+
test.libs << 'test'
|
32
|
+
test.pattern = 'test/**/test_*.rb'
|
33
|
+
test.verbose = true
|
34
|
+
end
|
35
|
+
rescue LoadError
|
36
|
+
task :rcov do
|
37
|
+
abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
task :test => :check_dependencies
|
42
|
+
|
43
|
+
task :default => :test
|
44
|
+
|
45
|
+
require 'rake/rdoctask'
|
46
|
+
Rake::RDocTask.new do |rdoc|
|
47
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
48
|
+
|
49
|
+
rdoc.rdoc_dir = 'rdoc'
|
50
|
+
rdoc.title = "em-resolv-replace #{version}"
|
51
|
+
rdoc.rdoc_files.include('README*')
|
52
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
53
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
1.0.0
|
@@ -0,0 +1,162 @@
|
|
1
|
+
require 'eventmachine'
|
2
|
+
require 'resolv'
|
3
|
+
|
4
|
+
module EventMachine
|
5
|
+
module DnsResolver
|
6
|
+
##
|
7
|
+
# Global interface
|
8
|
+
##
|
9
|
+
|
10
|
+
def self.resolve(hostname)
|
11
|
+
Request.new(socket, hostname)
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.socket
|
15
|
+
if defined?(@socket) && @socket.connected?
|
16
|
+
@socket
|
17
|
+
else
|
18
|
+
@socket = DnsSocket.open
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.nameserver=(ns)
|
23
|
+
@nameserver = ns
|
24
|
+
end
|
25
|
+
def self.nameserver
|
26
|
+
unless defined?(@nameserver)
|
27
|
+
IO::readlines('/etc/resolv.conf').each do |line|
|
28
|
+
if line =~ /^nameserver (.+)$/
|
29
|
+
@nameserver = $1.split(/\s+/).first
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
@nameserver
|
34
|
+
end
|
35
|
+
|
36
|
+
##
|
37
|
+
# Socket stuff
|
38
|
+
##
|
39
|
+
|
40
|
+
class RequestIdAlreadyUsed < RuntimeError
|
41
|
+
end
|
42
|
+
|
43
|
+
class DnsSocket < EM::Connection
|
44
|
+
def self.open
|
45
|
+
EM::open_datagram_socket('0.0.0.0', 0, self)
|
46
|
+
end
|
47
|
+
def post_init
|
48
|
+
@requests = {}
|
49
|
+
@connected = true
|
50
|
+
EM.add_periodic_timer(0.1, &method(:tick))
|
51
|
+
end
|
52
|
+
# Periodically called each second to fire request retries
|
53
|
+
def tick
|
54
|
+
@requests.each do |id,req|
|
55
|
+
req.tick
|
56
|
+
end
|
57
|
+
end
|
58
|
+
def register_request(id, req)
|
59
|
+
if @requests.has_key?(id)
|
60
|
+
raise RequestIdAlreadyUsed
|
61
|
+
else
|
62
|
+
@requests[id] = req
|
63
|
+
end
|
64
|
+
end
|
65
|
+
def send_packet(pkt)
|
66
|
+
send_datagram(pkt, nameserver, 53)
|
67
|
+
end
|
68
|
+
def nameserver=(ns)
|
69
|
+
@nameserver = ns
|
70
|
+
end
|
71
|
+
def nameserver
|
72
|
+
@nameserver ||= DnsResolver.nameserver
|
73
|
+
end
|
74
|
+
# Decodes the packet, looks for the request and passes the
|
75
|
+
# response over to the requester
|
76
|
+
def receive_data(data)
|
77
|
+
msg = nil
|
78
|
+
begin
|
79
|
+
msg = Resolv::DNS::Message.decode data
|
80
|
+
rescue
|
81
|
+
else
|
82
|
+
req = @requests[msg.id]
|
83
|
+
if req
|
84
|
+
@requests.delete(msg.id)
|
85
|
+
req.receive_answer(msg)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
def connected?; @connected; end
|
90
|
+
def unbind
|
91
|
+
@connected = false
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
##
|
96
|
+
# Request
|
97
|
+
##
|
98
|
+
|
99
|
+
class Request
|
100
|
+
include Deferrable
|
101
|
+
attr_accessor :retry_interval
|
102
|
+
attr_accessor :max_tries
|
103
|
+
def initialize(socket, hostname)
|
104
|
+
@socket = socket
|
105
|
+
@hostname = hostname
|
106
|
+
@tries = 0
|
107
|
+
@last_send = Time.at(0)
|
108
|
+
@retry_interval = 3
|
109
|
+
@max_tries = 5
|
110
|
+
EM.next_tick { tick }
|
111
|
+
end
|
112
|
+
def tick
|
113
|
+
# Break early if nothing to do
|
114
|
+
return if @last_send + @retry_interval > Time.now
|
115
|
+
|
116
|
+
if @tries < @max_tries
|
117
|
+
send
|
118
|
+
else
|
119
|
+
fail 'retries exceeded'
|
120
|
+
end
|
121
|
+
end
|
122
|
+
# Called by DnsSocket#receive_data
|
123
|
+
def receive_answer(msg)
|
124
|
+
addrs = []
|
125
|
+
msg.each_answer do |name,ttl,data|
|
126
|
+
if data.kind_of?(Resolv::DNS::Resource::IN::A) ||
|
127
|
+
data.kind_of?(Resolv::DNS::Resource::IN::AAAA)
|
128
|
+
addrs << data.address.to_s
|
129
|
+
end
|
130
|
+
end
|
131
|
+
if addrs.empty?
|
132
|
+
fail "rcode=#{msg.rcode}"
|
133
|
+
else
|
134
|
+
succeed addrs
|
135
|
+
end
|
136
|
+
end
|
137
|
+
private
|
138
|
+
def send
|
139
|
+
@socket.send_packet(packet.encode)
|
140
|
+
@tries += 1
|
141
|
+
@last_send = Time.now
|
142
|
+
end
|
143
|
+
def id
|
144
|
+
begin
|
145
|
+
@id = rand(65535)
|
146
|
+
@socket.register_request(@id, self)
|
147
|
+
rescue RequestIdAlreadyUsed
|
148
|
+
retry
|
149
|
+
end unless defined?(@id)
|
150
|
+
|
151
|
+
@id
|
152
|
+
end
|
153
|
+
def packet
|
154
|
+
msg = Resolv::DNS::Message.new
|
155
|
+
msg.id = id
|
156
|
+
msg.rd = 1
|
157
|
+
msg.add_question @hostname, Resolv::DNS::Resource::IN::A
|
158
|
+
msg
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# Standard libc DNS resolution
|
2
|
+
require 'resolv'
|
3
|
+
# Override with pure Ruby DNS resolution
|
4
|
+
require 'resolv-replace'
|
5
|
+
|
6
|
+
require 'em-dns-resolver'
|
7
|
+
require 'fiber'
|
8
|
+
|
9
|
+
# Now override the override with EM-aware functions
|
10
|
+
class Resolv
|
11
|
+
|
12
|
+
alias :orig_getaddress :getaddress
|
13
|
+
|
14
|
+
def getaddress(host)
|
15
|
+
EM.reactor_running? ? em_getaddress(host)[0] : orig_getaddress(host)
|
16
|
+
end
|
17
|
+
|
18
|
+
alias :orig_getaddresses :getaddresses
|
19
|
+
|
20
|
+
def getaddresses(host)
|
21
|
+
EM.reactor_running? ? em_getaddress(host) : orig_getaddresses(host)
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def em_getaddress(host)
|
27
|
+
fiber = Fiber.current
|
28
|
+
df = EM::DnsResolver.resolve(host)
|
29
|
+
df.callback do |a|
|
30
|
+
fiber.resume(a)
|
31
|
+
end
|
32
|
+
df.errback do |*a|
|
33
|
+
fiber.resume(ResolvError.new(a.inspect))
|
34
|
+
end
|
35
|
+
result = Fiber.yield
|
36
|
+
if result.is_a?(StandardError)
|
37
|
+
raise result
|
38
|
+
end
|
39
|
+
result
|
40
|
+
end
|
41
|
+
end
|
data/test/helper.rb
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
class TestEmResolvReplace < Test::Unit::TestCase
|
4
|
+
|
5
|
+
IPv4 = /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/
|
6
|
+
|
7
|
+
should "resolve address without eventmachine" do
|
8
|
+
Resolv.any_instance.expects(:em_getaddress).times(0)
|
9
|
+
|
10
|
+
assert_match IPv4, Resolv.getaddress('www.google.com')
|
11
|
+
end
|
12
|
+
|
13
|
+
should "resolve address with eventmachine" do
|
14
|
+
Resolv.any_instance.expects(:orig_getaddress).times(0)
|
15
|
+
|
16
|
+
EM.run do
|
17
|
+
Fiber.new do
|
18
|
+
assert_match IPv4, Resolv.getaddress('www.google.com')
|
19
|
+
EM.stop
|
20
|
+
end.resume
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
should "resolve addresses without eventmachine" do
|
25
|
+
Resolv.any_instance.expects(:em_getaddress).times(0)
|
26
|
+
|
27
|
+
results = Resolv.getaddresses('www.google.com')
|
28
|
+
assert_match IPv4, results.first
|
29
|
+
end
|
30
|
+
|
31
|
+
should "resolve addresses with eventmachine" do
|
32
|
+
Resolv.any_instance.expects(:orig_getaddresses).times(0)
|
33
|
+
|
34
|
+
EM.run do
|
35
|
+
Fiber.new do
|
36
|
+
results = Resolv.getaddresses('www.google.com')
|
37
|
+
assert_match IPv4, results.first
|
38
|
+
EM.stop
|
39
|
+
end.resume
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
metadata
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: em-resolv-replace
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Mike Perham
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2010-02-10 00:00:00 -06:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: shoulda
|
17
|
+
type: :development
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 2.10.3
|
24
|
+
version:
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: mocha
|
27
|
+
type: :development
|
28
|
+
version_requirement:
|
29
|
+
version_requirements: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 0.9.8
|
34
|
+
version:
|
35
|
+
description:
|
36
|
+
email: mperham@gmail.com
|
37
|
+
executables: []
|
38
|
+
|
39
|
+
extensions: []
|
40
|
+
|
41
|
+
extra_rdoc_files:
|
42
|
+
- LICENSE
|
43
|
+
- README.rdoc
|
44
|
+
files:
|
45
|
+
- .document
|
46
|
+
- .gitignore
|
47
|
+
- LICENSE
|
48
|
+
- README.rdoc
|
49
|
+
- Rakefile
|
50
|
+
- VERSION
|
51
|
+
- lib/em-dns-resolver.rb
|
52
|
+
- lib/em-resolv-replace.rb
|
53
|
+
- test/helper.rb
|
54
|
+
- test/test_em-resolv-replace.rb
|
55
|
+
has_rdoc: true
|
56
|
+
homepage: http://github.com/mperham/em-resolv-replace
|
57
|
+
licenses: []
|
58
|
+
|
59
|
+
post_install_message:
|
60
|
+
rdoc_options:
|
61
|
+
- --charset=UTF-8
|
62
|
+
require_paths:
|
63
|
+
- lib
|
64
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: "0"
|
69
|
+
version:
|
70
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - ">="
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: "0"
|
75
|
+
version:
|
76
|
+
requirements: []
|
77
|
+
|
78
|
+
rubyforge_project:
|
79
|
+
rubygems_version: 1.3.5
|
80
|
+
signing_key:
|
81
|
+
specification_version: 3
|
82
|
+
summary: EventMachine-aware DNS lookup for Ruby
|
83
|
+
test_files:
|
84
|
+
- test/helper.rb
|
85
|
+
- test/test_em-resolv-replace.rb
|