PageRenderTime 39ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/mongo/address.rb

http://github.com/mongodb/mongo-ruby-driver
Ruby | 252 lines | 85 code | 20 blank | 147 comment | 10 complexity | 1c918a0ed73dc2e27404966d70e9528d MD5 | raw file
Possible License(s): Apache-2.0
  1. # Copyright (C) 2014-2020 MongoDB Inc.
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the 'License');
  4. # you may not use this file except in compliance with the License.
  5. # You may obtain a copy of the License at
  6. #
  7. # http://www.apache.org/licenses/LICENSE-2.0
  8. #
  9. # Unless required by applicable law or agreed to in writing, software
  10. # distributed under the License is distributed on an 'AS IS' BASIS,
  11. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. # See the License for the specific language governing permissions and
  13. # limitations under the License.
  14. require 'mongo/address/ipv4'
  15. require 'mongo/address/ipv6'
  16. require 'mongo/address/unix'
  17. require 'mongo/address/validator'
  18. module Mongo
  19. # Represents an address to a server, either with an IP address or socket
  20. # path.
  21. #
  22. # @since 2.0.0
  23. class Address
  24. extend Forwardable
  25. # Mapping from socket family to resolver class.
  26. #
  27. # @since 2.0.0
  28. FAMILY_MAP = {
  29. ::Socket::PF_UNIX => Unix,
  30. ::Socket::AF_INET6 => IPv6,
  31. ::Socket::AF_INET => IPv4
  32. }.freeze
  33. # The localhost constant.
  34. #
  35. # @since 2.1.0
  36. LOCALHOST = 'localhost'.freeze
  37. # Initialize the address.
  38. #
  39. # @example Initialize the address with a DNS entry and port.
  40. # Mongo::Address.new("app.example.com:27017")
  41. #
  42. # @example Initialize the address with a DNS entry and no port.
  43. # Mongo::Address.new("app.example.com")
  44. #
  45. # @example Initialize the address with an IPV4 address and port.
  46. # Mongo::Address.new("127.0.0.1:27017")
  47. #
  48. # @example Initialize the address with an IPV4 address and no port.
  49. # Mongo::Address.new("127.0.0.1")
  50. #
  51. # @example Initialize the address with an IPV6 address and port.
  52. # Mongo::Address.new("[::1]:27017")
  53. #
  54. # @example Initialize the address with an IPV6 address and no port.
  55. # Mongo::Address.new("[::1]")
  56. #
  57. # @example Initialize the address with a unix socket.
  58. # Mongo::Address.new("/path/to/socket.sock")
  59. #
  60. # @param [ String ] seed The provided address.
  61. # @param [ Hash ] options The address options.
  62. #
  63. # @option options [ Float ] :connect_timeout Connect timeout.
  64. #
  65. # @since 2.0.0
  66. def initialize(seed, options = {})
  67. if seed.nil?
  68. raise ArgumentError, "address must be not nil"
  69. end
  70. @seed = seed
  71. @host, @port = parse_host_port
  72. @options = options
  73. end
  74. # @return [ String ] seed The seed address.
  75. attr_reader :seed
  76. # @return [ String ] host The original host name.
  77. attr_reader :host
  78. # @return [ Integer ] port The port.
  79. attr_reader :port
  80. # @api private
  81. attr_reader :options
  82. # Check equality of the address to another.
  83. #
  84. # @example Check address equality.
  85. # address == other
  86. #
  87. # @param [ Object ] other The other object.
  88. #
  89. # @return [ true, false ] If the objects are equal.
  90. #
  91. # @since 2.0.0
  92. def ==(other)
  93. return false unless other.is_a?(Address)
  94. host == other.host && port == other.port
  95. end
  96. # Check equality for hashing.
  97. #
  98. # @example Check hashing equality.
  99. # address.eql?(other)
  100. #
  101. # @param [ Object ] other The other object.
  102. #
  103. # @return [ true, false ] If the objects are equal.
  104. #
  105. # @since 2.2.0
  106. def eql?(other)
  107. self == other
  108. end
  109. # Calculate the hash value for the address.
  110. #
  111. # @example Calculate the hash value.
  112. # address.hash
  113. #
  114. # @return [ Integer ] The hash value.
  115. #
  116. # @since 2.0.0
  117. def hash
  118. [ host, port ].hash
  119. end
  120. # Get a pretty printed address inspection.
  121. #
  122. # @example Get the address inspection.
  123. # address.inspect
  124. #
  125. # @return [ String ] The nice inspection string.
  126. #
  127. # @since 2.0.0
  128. def inspect
  129. "#<Mongo::Address:0x#{object_id} address=#{to_s}>"
  130. end
  131. # Get a socket for the address stored in this object, given the options.
  132. #
  133. # If the address stored in this object looks like a Unix path, this method
  134. # returns a Unix domain socket for this path.
  135. #
  136. # Otherwise, this method attempts to resolve the address stored in
  137. # this object to IPv4 and IPv6 addresses using +Socket#getaddrinfo+, then
  138. # connects to the resulting addresses and returns the socket of the first
  139. # successful connection. The order in which address families (IPv4/IPV6)
  140. # are tried is the same order in which the addresses are returned by
  141. # +getaddrinfo+, and is determined by the host system.
  142. #
  143. # Name resolution is performed on each +socket+ call. This is done so that
  144. # any changes to which addresses the host names used as seeds or in
  145. # server configuration resolve to are immediately noticed by the driver,
  146. # even if a socket has been connected to the affected host name/address
  147. # before. However, note that DNS TTL values may still affect when a change
  148. # to a host address is noticed by the driver.
  149. #
  150. # This method propagates any exceptions raised during DNS resolution and
  151. # subsequent connection attempts. In case of a host name resolving to
  152. # multiple IP addresses, the error raised by the last attempt is propagated
  153. # to the caller. This method does not map exceptions to Mongo::Error
  154. # subclasses, and may raise any subclass of Exception.
  155. #
  156. # @example Get a socket.
  157. # address.socket(5, :ssl => true)
  158. #
  159. # @param [ Float ] socket_timeout The socket timeout.
  160. # @param [ Hash ] ssl_options SSL options.
  161. # @param [ Hash ] options The options.
  162. #
  163. # @option options [ Float ] :connect_timeout Connect timeout.
  164. #
  165. # @return [ Mongo::Socket::SSL | Mongo::Socket::TCP | Mongo::Socket::Unix ]
  166. # The socket.
  167. #
  168. # @raise [ Exception ] If network connection failed.
  169. #
  170. # @since 2.0.0
  171. def socket(socket_timeout, ssl_options = {}, options = {})
  172. if seed.downcase =~ Unix::MATCH
  173. specific_address = Unix.new(seed.downcase)
  174. return specific_address.socket(socket_timeout, ssl_options, options)
  175. end
  176. options = {
  177. connect_timeout: Server::CONNECT_TIMEOUT,
  178. }.update(options)
  179. # When the driver connects to "localhost", it only attempts IPv4
  180. # connections. When the driver connects to other hosts, it will
  181. # attempt both IPv4 and IPv6 connections.
  182. family = (host == LOCALHOST) ? ::Socket::AF_INET : ::Socket::AF_UNSPEC
  183. error = nil
  184. # Sometimes Socket#getaddrinfo returns the same info more than once
  185. # (multiple identical items in the returned array). It does not make
  186. # sense to try to connect to the same address more than once, thus
  187. # eliminate duplicates here.
  188. infos = ::Socket.getaddrinfo(host, nil, family, ::Socket::SOCK_STREAM)
  189. results = infos.map do |info|
  190. [info[4], info[3]]
  191. end.uniq
  192. results.each do |family, address_str|
  193. begin
  194. specific_address = FAMILY_MAP[family].new(address_str, port, host)
  195. socket = specific_address.socket(socket_timeout, ssl_options, options)
  196. return socket
  197. rescue IOError, SystemCallError, Error::SocketTimeoutError, Error::SocketError => e
  198. error = e
  199. end
  200. end
  201. raise error
  202. end
  203. # Get the address as a string.
  204. #
  205. # @example Get the address as a string.
  206. # address.to_s
  207. #
  208. # @return [ String ] The nice string.
  209. #
  210. # @since 2.0.0
  211. def to_s
  212. if port
  213. if host.include?(':')
  214. "[#{host}]:#{port}"
  215. else
  216. "#{host}:#{port}"
  217. end
  218. else
  219. host
  220. end
  221. end
  222. private
  223. def parse_host_port
  224. address = seed.downcase
  225. case address
  226. when Unix::MATCH then Unix.parse(address)
  227. when IPv6::MATCH then IPv6.parse(address)
  228. else IPv4.parse(address)
  229. end
  230. end
  231. end
  232. end