123456789_123456789_123456789_123456789_123456789_

Class: Mongo::URI

Relationships & Source Files
Namespace Children
Classes:
Extension / Inclusion / Inheritance Descendants
Subclasses:
Super Chains via Extension / Inclusion / Inheritance
Instance Chain:
Inherits: Object
Defined in: lib/mongo/uri.rb,
lib/mongo/uri/options_mapper.rb,
lib/mongo/uri/srv_protocol.rb

Overview

The URI class provides a way for users to parse the MongoDB uri as defined in the connection string format spec.

www.mongodb.com/docs/manual/reference/connection-string/

Examples:

Use the uri string to make a client connection.

uri = Mongo::URI.new('mongodb://localhost:27017')
client = Mongo::Client.new(uri.servers, uri.options)
client.(uri.credentials)
client[uri.database]

Since:

  • 2.0.0

Constant Summary

Loggable - Included

PREFIX

Class Method Summary

Instance Attribute Summary

  • #options readonly

    The uri parser object options.

  • #servers readonly

    The servers specified in the uri.

  • #uri_options readonly

    Mongo::Options::Redacted of the options specified in the uri.

Instance Method Summary

Address::Validator - Included

#validate_address_str!

Takes an address string in ipv4/ipv6/hostname/socket path format and validates its format.

#validate_hostname!

Validates format of the hostname, in particular for further use as the origin in same origin verification.

#validate_port_str!

Loggable - Included

#log_debug

Convenience method to log debug messages with the standard prefix.

#log_error

Convenience method to log error messages with the standard prefix.

#log_fatal

Convenience method to log fatal messages with the standard prefix.

#log_info

Convenience method to log info messages with the standard prefix.

#log_warn

Convenience method to log warn messages with the standard prefix.

#logger

Get the logger instance.

#_mongo_log_prefix, #format_message

Constructor Details

.new(string, options = {}) ⇒ URI

Create the new uri from the provided string.

Examples:

Create the new URI.

URI.new('mongodb://localhost:27017')

Parameters:

  • string (String)

    The URI to parse.

  • options (Hash) (defaults to: {})

    The options.

Options Hash (options):

  • :logger (Logger)

    A custom logger to use.

Raises:

Since:

  • 2.0.0

[ GitHub ]

  
# File 'lib/mongo/uri.rb', line 284

def initialize(string, options = {})
  unless string
    raise Error::InvalidURI.new(string, 'URI must be a string, not nil.')
  end
  if string.empty?
    raise Error::InvalidURI.new(string, 'Cannot parse an empty URI.')
  end

  @string = string
  @options = options
  parsed_scheme, _, remaining = string.partition(SCHEME_DELIM)
  unless parsed_scheme == scheme
    raise_invalid_error!("Invalid scheme '#{parsed_scheme}'. Scheme must be '#{MONGODB_SCHEME}'. Use URI#get to parse SRV URIs.")
  end
  if remaining.empty?
    raise_invalid_error!('No hosts in the URI')
  end
  parse!(remaining)
  validate_uri_options!
end

Class Method Details

.get(string, opts = {}) ⇒ URI, URI::SRVProtocol

Get either a URI object or a URI::SRVProtocol URI object.

Examples:

Get the uri object.

URI.get(string)

Parameters:

  • string (String)

    The URI to parse.

  • opts (Hash) (defaults to: {})

    The options.

  • options (Hash)

    a customizable set of options

Returns:

Since:

  • 2.5.0

[ GitHub ]

  
# File 'lib/mongo/uri.rb', line 230

def self.get(string, opts = {})
  unless string
    raise Error::InvalidURI.new(string, 'URI must be a string, not nil.')
  end
  if string.empty?
    raise Error::InvalidURI.new(string, 'Cannot parse an empty URI.')
  end

  scheme, _, _ = string.partition(SCHEME_DELIM)
  case scheme
    when MONGODB_SCHEME
      URI.new(string, opts)
    when MONGODB_SRV_SCHEME
      SRVProtocol.new(string, opts)
    else
      raise Error::InvalidURI.new(string, "Invalid scheme '#{scheme}'. Scheme must be '#{MONGODB_SCHEME}' or '#{MONGODB_SRV_SCHEME}'")
  end
end

Instance Attribute Details

#options (readonly)

The uri parser object options.

Since:

  • 2.0.0

[ GitHub ]

  
# File 'lib/mongo/uri.rb', line 39

attr_reader :options

#servers (readonly)

The servers specified in the uri.

Since:

  • 2.0.0

[ GitHub ]

  
# File 'lib/mongo/uri.rb', line 49

attr_reader :servers

#uri_options (readonly)

Mongo::Options::Redacted of the options specified in the uri.

Since:

  • 2.1.0

[ GitHub ]

  
# File 'lib/mongo/uri.rb', line 44

attr_reader :uri_options

Instance Method Details

#client_optionsMongo::Options::Redacted

Gets the options hash that needs to be passed to a Client on instantiation, so we don’t have to merge the credentials and database in at that point - we only have a single point here.

Examples:

Get the client options.

uri.client_options

Returns:

Since:

  • 2.0.0

[ GitHub ]

  
# File 'lib/mongo/uri.rb', line 259

def client_options
  opts = uri_options.tap do |opts|
    opts[:database] = @database if @database
  end

  @user ? opts.merge(credentials) : opts
end

#credentialsHash

Get the credentials provided in the URI.

Examples:

Get the credentials.

uri.credentials

Returns:

  • (Hash)

    The credentials.

    • :user [ String ] The user.

    • :password [ String ] The provided password.

Since:

  • 2.0.0

[ GitHub ]

  
# File 'lib/mongo/uri.rb', line 315

def credentials
  { :user => @user, :password => @password }
end

#databaseString

Get the database provided in the URI.

Examples:

Get the database.

uri.database

Returns:

  • (String)

    The database.

Since:

  • 2.0.0

[ GitHub ]

  
# File 'lib/mongo/uri.rb', line 327

def database
  @database ? @database : Database::ADMIN
end

#decode(value) (private)

Since:

  • 2.0.0

[ GitHub ]

  
# File 'lib/mongo/uri.rb', line 481

def decode(value)
  ::URI::DEFAULT_PARSER.unescape(value)
end

#encode(value) (private)

Since:

  • 2.0.0

[ GitHub ]

  
# File 'lib/mongo/uri.rb', line 485

def encode(value)
  CGI.escape(value).gsub('+', '%20')
end

#options_mapper (private)

Since:

  • 2.0.0

[ GitHub ]

  
# File 'lib/mongo/uri.rb', line 418

def options_mapper
  @options_mapper ||= OptionsMapper.new(
    logger: @options[:logger],
  )
end

#parse!(remaining) (private)

Since:

  • 2.0.0

[ GitHub ]

  
# File 'lib/mongo/uri.rb', line 374

def parse!(remaining)
  hosts_and_db, options = remaining.split('?', 2)
  if options && options.index('?')
    raise_invalid_error!("Options contain an unescaped question mark (?), or the database name contains a question mark and was not escaped")
  end

  hosts, db = hosts_and_db.split('/', 2)
  if db && db.index('/')
    raise_invalid_error!("Database name contains an unescaped slash (/): #{db}")
  end

  if hosts.index('@')
    creds, hosts = hosts.split('@', 2)
    if hosts.empty?
      raise_invalid_error!("Empty hosts list")
    end
    if hosts.index('@')
      raise_invalid_error!("Unescaped @ in auth info")
    end
  end

  unless hosts.length > 0
    raise_invalid_error!("Missing host; at least one must be provided")
  end

  @servers = hosts.split(',').map do |host|
    if host.empty?
      raise_invalid_error!('Empty host given in the host list')
    end
    decode(host).tap do |host|
      validate_address_str!(host)
    end
  end

  @user = parse_user!(creds)
  @password = parse_password!(creds)
  @uri_options = Options::Redacted.new(parse_uri_options!(options))
  if db
    @database = parse_database!(db)
  end
rescue Error::InvalidAddress => e
  raise_invalid_error!(e.message)
end

#parse_database!(string) (private)

Since:

  • 2.0.0

[ GitHub ]

  
# File 'lib/mongo/uri.rb', line 468

def parse_database!(string)
  raise_invalid_error!(UNESCAPED_DATABASE) if string =~ UNSAFE
  decode(string) if string.length > 0
end

#parse_password!(string) (private)

Since:

  • 2.0.0

[ GitHub ]

  
# File 'lib/mongo/uri.rb', line 455

def parse_password!(string)
  if (string && pwd = string.partition(AUTH_USER_PWD_DELIM)[2])
    if pwd.length > 0
      raise_invalid_error!(UNESCAPED_USER_PWD) if pwd =~ UNSAFE
      pwd_decoded = decode(pwd)
      if pwd_decoded =~ PERCENT_CHAR && encode(pwd_decoded) != pwd
        raise_invalid_error!(UNESCAPED_USER_PWD)
      end
      pwd_decoded
    end
  end
end

#parse_uri_options!(string) (private)

Since:

  • 2.0.0

[ GitHub ]

  
# File 'lib/mongo/uri.rb', line 424

def parse_uri_options!(string)
  uri_options = {}
  unless string
    return uri_options
  end
  string.split('&').each do |option_str|
    if option_str.empty?
      next
    end
    key, value = option_str.split('=', 2)
    if value.nil?
      raise_invalid_error!("Option #{key} has no value")
    end
    key = decode(key)
    value = decode(value)
    options_mapper.add_uri_option(key, value, uri_options)
  end
  uri_options
end

#parse_user!(string) (private)

Since:

  • 2.0.0

[ GitHub ]

  
# File 'lib/mongo/uri.rb', line 444

def parse_user!(string)
  if (string && user = string.partition(AUTH_USER_PWD_DELIM)[0])
    raise_invalid_error!(UNESCAPED_USER_PWD) if user =~ UNSAFE
    user_decoded = decode(user)
    if user_decoded =~ PERCENT_CHAR && encode(user_decoded) != user
      raise_invalid_error!(UNESCAPED_USER_PWD)
    end
    user_decoded
  end
end

#raise_invalid_error!(details) (private)

Raises:

Since:

  • 2.0.0

[ GitHub ]

  
# File 'lib/mongo/uri.rb', line 473

def raise_invalid_error!(details)
  raise Error::InvalidURI.new(@string, details, FORMAT)
end

#raise_invalid_error_no_fmt!(details) (private)

Raises:

Since:

  • 2.0.0

[ GitHub ]

  
# File 'lib/mongo/uri.rb', line 477

def raise_invalid_error_no_fmt!(details)
  raise Error::InvalidURI.new(@string, details)
end

#reconstruct_uriString (private)

Reconstruct the URI from its parts. Invalid options are dropped and options are converted to camelCase.

Returns:

  • (String)

    the uri.

Since:

  • 2.0.0

[ GitHub ]

  
# File 'lib/mongo/uri.rb', line 347

def reconstruct_uri
  servers = @servers.join(',')
  options = options_mapper.ruby_to_string(@uri_options).map do |k, vs|
    unless vs.nil?
      if vs.is_a?(Array)
        vs.map { |v| "#{k}=#{v}" }.join('&')
      else
        "#{k}=#{vs}"
      end
    end
  end.compact.join('&')

  uri = "#{scheme}#{SCHEME_DELIM}"
  uri += @user.to_s if @user
  uri += "#{AUTH_USER_PWD_DELIM}#{@password}" if @password
  uri += "@" if @user || @password
  uri += @query_hostname || servers
  uri += "/" if @database || !options.empty?
  uri += @database.to_s if @database
  uri += "?#{options}" unless options.empty?
  uri
end

#scheme (private)

Since:

  • 2.0.0

[ GitHub ]

  
# File 'lib/mongo/uri.rb', line 370

def scheme
  MONGODB_SCHEME
end

#srv_records

Since:

  • 2.0.0

[ GitHub ]

  
# File 'lib/mongo/uri.rb', line 267

def srv_records
  nil
end

#to_sString

Get the uri as a string.

Examples:

Get the uri as a string.

uri.to_s

Returns:

  • (String)

    The uri string.

Since:

  • 2.0.0

[ GitHub ]

  
# File 'lib/mongo/uri.rb', line 337

def to_s
  reconstruct_uri
end

#validate_uri_options! (private)

Since:

  • 2.0.0

[ GitHub ]

  
# File 'lib/mongo/uri.rb', line 489

def validate_uri_options!
  # The URI options spec requires that we raise an error if there are conflicting values of
  # 'tls' and 'ssl'. In order to fulfill this, we parse the values of each instance into an
  # array; assuming all values in the array are the same, we replace the array with that value.
  unless uri_options[:ssl].nil? || uri_options[:ssl].empty?
    unless uri_options[:ssl].uniq.length == 1
      raise_invalid_error_no_fmt!("all instances of 'tls' and 'ssl' must have the same value")
    end

    uri_options[:ssl] = uri_options[:ssl].first
  end

  # Check for conflicting TLS insecure options.
  unless uri_options[:ssl_verify].nil?
    unless uri_options[:ssl_verify_certificate].nil?
      raise_invalid_error_no_fmt!("'tlsInsecure' and 'tlsAllowInvalidCertificates' cannot both be specified")
    end

    unless uri_options[:ssl_verify_hostname].nil?
      raise_invalid_error_no_fmt!("tlsInsecure' and 'tlsAllowInvalidHostnames' cannot both be specified")
    end

    unless uri_options[:ssl_verify_ocsp_endpoint].nil?
      raise_invalid_error_no_fmt!("tlsInsecure' and 'tlsDisableOCSPEndpointCheck' cannot both be specified")
    end
  end

  unless uri_options[:ssl_verify_certificate].nil?
    unless uri_options[:ssl_verify_ocsp_endpoint].nil?
      raise_invalid_error_no_fmt!("tlsAllowInvalidCertificates' and 'tlsDisableOCSPEndpointCheck' cannot both be specified")
    end
  end

  # Since we know that the only URI option that sets :ssl_cert is
  # "tlsCertificateKeyFile", any value set for :ssl_cert must also be set
  # for :ssl_key.
  if uri_options[:ssl_cert]
    uri_options[:ssl_key] = uri_options[:ssl_cert]
  end

  if uri_options[:write_concern] && !uri_options[:write_concern].empty?
    begin
      WriteConcern.get(uri_options[:write_concern])
    rescue Error::InvalidWriteConcern => e
      raise_invalid_error_no_fmt!("#{e.class}: #{e}")
    end
  end

  if uri_options[:direct_connection]
    if uri_options[:connect] && uri_options[:connect].to_s != 'direct'
      raise_invalid_error_no_fmt!("directConnection=true cannot be used with connect=#{uri_options[:connect]}")
    end
    if servers.length > 1
      raise_invalid_error_no_fmt!("directConnection=true cannot be used with multiple seeds")
    end
  elsif uri_options[:direct_connection] == false && uri_options[:connect].to_s == 'direct'
    raise_invalid_error_no_fmt!("directConnection=false cannot be used with connect=direct")
  end

  if uri_options[:load_balanced]
    if servers.length > 1
      raise_invalid_error_no_fmt!("loadBalanced=true cannot be used with multiple seeds")
    end

    if uri_options[:direct_connection]
      raise_invalid_error_no_fmt!("directConnection=true cannot be used with loadBalanced=true")
    end

    if uri_options[:connect] && uri_options[:connect].to_sym == :direct
      raise_invalid_error_no_fmt!("connect=direct cannot be used with loadBalanced=true")
    end

    if uri_options[:replica_set]
      raise_invalid_error_no_fmt!("loadBalanced=true cannot be used with replicaSet option")
    end
  end

  unless self.is_a?(URI::SRVProtocol)
    if uri_options[:srv_max_hosts]
      raise_invalid_error_no_fmt!("srvMaxHosts cannot be used on non-SRV URI")
    end

    if uri_options[:srv_service_name]
      raise_invalid_error_no_fmt!("srvServiceName cannot be used on non-SRV URI")
    end
  end

  if uri_options[:srv_max_hosts] && uri_options[:srv_max_hosts] > 0
    if uri_options[:replica_set]
      raise_invalid_error_no_fmt!("srvMaxHosts > 0 cannot be used with replicaSet option")
    end

    if options[:load_balanced]
      raise_invalid_error_no_fmt!("srvMaxHosts > 0 cannot be used with loadBalanced=true")
    end
  end
end