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.
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
@@ -0,0 +1,21 @@
1
+ ## MAC OS
2
+ .DS_Store
3
+
4
+ ## TEXTMATE
5
+ *.tmproj
6
+ tmtags
7
+
8
+ ## EMACS
9
+ *~
10
+ \#*
11
+ .\#*
12
+
13
+ ## VIM
14
+ *.swp
15
+
16
+ ## PROJECT::GENERAL
17
+ coverage
18
+ rdoc
19
+ pkg
20
+
21
+ ## PROJECT::SPECIFIC
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.
@@ -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
@@ -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
@@ -0,0 +1,9 @@
1
+ require 'rubygems'
2
+ require 'test/unit'
3
+ require 'shoulda'
4
+ require 'mocha'
5
+
6
+ require 'em-resolv-replace'
7
+
8
+ class Test::Unit::TestCase
9
+ end
@@ -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