finclip-app-manager/vendor/gitlab.finogeeks.club/finclip-backend/apm/mongo_utils.go

215 lines
5.9 KiB
Go

package apm
import (
"errors"
"fmt"
"net"
"net/url"
"strconv"
"strings"
"time"
)
type urlInfo struct {
addrs []string
user string
pass string
db string
options map[string]string
}
func isOptSep(c rune) bool {
return c == ';' || c == '&'
}
func extractURL(s string) (*urlInfo, error) {
if strings.HasPrefix(s, "mongodb://") {
s = s[10:]
}
info := &urlInfo{options: make(map[string]string)}
if c := strings.Index(s, "?"); c != -1 {
for _, pair := range strings.FieldsFunc(s[c+1:], isOptSep) {
l := strings.SplitN(pair, "=", 2)
if len(l) != 2 || l[0] == "" || l[1] == "" {
return nil, errors.New("connection option must be key=value: " + pair)
}
info.options[l[0]] = l[1]
}
s = s[:c]
}
if c := strings.Index(s, "@"); c != -1 {
pair := strings.SplitN(s[:c], ":", 2)
if len(pair) > 2 || pair[0] == "" {
return nil, errors.New("credentials must be provided as user:pass@host")
}
var err error
info.user, err = url.QueryUnescape(pair[0])
if err != nil {
return nil, fmt.Errorf("cannot unescape username in URL: %q", pair[0])
}
if len(pair) > 1 {
info.pass, err = url.QueryUnescape(pair[1])
if err != nil {
return nil, fmt.Errorf("cannot unescape password in URL")
}
}
s = s[c+1:]
}
if c := strings.Index(s, "/"); c != -1 {
info.db = s[c+1:]
s = s[:c]
}
info.addrs = strings.Split(s, ",")
return info, nil
}
func ParseMongoURL(url string) (scheme string, host string, path string) {
dialInfo, err := doParseMongoURL(url)
if err != nil {
return "", "", ""
}
scheme = "mongodb"
if dialInfo.Addrs == nil {
host = ""
} else {
host = strings.Join(dialInfo.Addrs, ",")
}
path = dialInfo.Database
return scheme, host, path
}
// ParseURL parses a MongoDB URL as accepted by the Dial function and returns
// a value suitable for providing into DialWithInfo.
//
// See Dial for more details on the format of url.
func doParseMongoURL(url string) (*DialInfo, error) {
uinfo, err := extractURL(url)
if err != nil {
return nil, err
}
direct := false
mechanism := ""
service := ""
source := ""
setName := ""
poolLimit := 0
for k, v := range uinfo.options {
switch k {
case "authSource":
source = v
case "authMechanism":
mechanism = v
case "gssapiServiceName":
service = v
case "replicaSet":
setName = v
case "maxPoolSize":
poolLimit, err = strconv.Atoi(v)
if err != nil {
return nil, errors.New("bad value for maxPoolSize: " + v)
}
case "connect":
if v == "direct" {
direct = true
break
}
if v == "replicaSet" {
break
}
fallthrough
default:
return nil, errors.New("unsupported connection URL option: " + k + "=" + v)
}
}
info := DialInfo{
Addrs: uinfo.addrs,
Direct: direct,
Database: uinfo.db,
Username: uinfo.user,
Password: uinfo.pass,
Mechanism: mechanism,
Service: service,
Source: source,
PoolLimit: poolLimit,
ReplicaSetName: setName,
}
return &info, nil
}
// DialInfo holds options for establishing a session with a MongoDB cluster.
// To use a URL, see the Dial function.
type DialInfo struct {
// Addrs holds the addresses for the seed servers.
Addrs []string
// Direct informs whether to establish connections only with the
// specified seed servers, or to obtain information for the whole
// cluster and establish connections with further servers too.
Direct bool
// Timeout is the amount of time to wait for a server to respond when
// first connecting and on follow up operations in the session. If
// timeout is zero, the call may block forever waiting for a connection
// to be established. Timeout does not affect logic in DialServer.
Timeout time.Duration
// FailFast will cause connection and query attempts to fail faster when
// the server is unavailable, instead of retrying until the configured
// timeout period. Note that an unavailable server may silently drop
// packets instead of rejecting them, in which case it's impossible to
// distinguish it from a slow server, so the timeout stays relevant.
FailFast bool
// Database is the default database name used when the Session.DB method
// is called with an empty name, and is also used during the initial
// authentication if Source is unset.
Database string
// ReplicaSetName, if specified, will prevent the obtained session from
// communicating with any server which is not part of a replica set
// with the given name. The default is to communicate with any server
// specified or discovered via the servers contacted.
ReplicaSetName string
// Source is the database used to establish credentials and privileges
// with a MongoDB server. Defaults to the value of Database, if that is
// set, or "admin" otherwise.
Source string
// Service defines the service name to use when authenticating with the GSSAPI
// mechanism. Defaults to "mongodb".
Service string
// ServiceHost defines which hostname to use when authenticating
// with the GSSAPI mechanism. If not specified, defaults to the MongoDB
// server's address.
ServiceHost string
// Mechanism defines the protocol for credential negotiation.
// Defaults to "MONGODB-CR".
Mechanism string
// Username and Password inform the credentials for the initial authentication
// done on the database defined by the Source field. See Session.Login.
Username string
Password string
// PoolLimit defines the per-server socket pool limit. Defaults to 4096.
// See Session.SetPoolLimit for details.
PoolLimit int
// DialServer optionally specifies the dial function for establishing
// connections with the MongoDB servers.
DialServer func(addr *ServerAddr) (net.Conn, error)
// WARNING: This field is obsolete. See DialServer above.
Dial func(addr net.Addr) (net.Conn, error)
}
// ServerAddr represents the address for establishing a connection to an
// individual MongoDB server.
type ServerAddr struct {
str string
tcp *net.TCPAddr
}