// Copyright (c) 2012, Sean Treadway, SoundCloud Ltd. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // Source code and contact info at http://github.com/streadway/amqp package amqp import ( "errors" "net" "net/url" "strconv" "strings" ) var errURIScheme = errors.New("AMQP scheme must be either 'amqp://' or 'amqps://'") var errURIWhitespace = errors.New("URI must not contain whitespace") var schemePorts = map[string]int{ "amqp": 5672, "amqps": 5671, } var defaultURI = URI{ Scheme: "amqp", Host: "localhost", Port: 5672, Username: "guest", Password: "guest", Vhost: "/", } // URI represents a parsed AMQP URI string. type URI struct { Scheme string Host string Port int Username string Password string Vhost string } // ParseURI attempts to parse the given AMQP URI according to the spec. // See http://www.rabbitmq.com/uri-spec.html. // // Default values for the fields are: // // Scheme: amqp // Host: localhost // Port: 5672 // Username: guest // Password: guest // Vhost: / // func ParseURI(uri string) (URI, error) { builder := defaultURI if strings.Contains(uri, " ") == true { return builder, errURIWhitespace } u, err := url.Parse(uri) if err != nil { return builder, err } defaultPort, okScheme := schemePorts[u.Scheme] if okScheme { builder.Scheme = u.Scheme } else { return builder, errURIScheme } host := u.Hostname() port := u.Port() if host != "" { builder.Host = host } if port != "" { port32, err := strconv.ParseInt(port, 10, 32) if err != nil { return builder, err } builder.Port = int(port32) } else { builder.Port = defaultPort } if u.User != nil { builder.Username = u.User.Username() if password, ok := u.User.Password(); ok { builder.Password = password } } if u.Path != "" { if strings.HasPrefix(u.Path, "/") { if u.Host == "" && strings.HasPrefix(u.Path, "///") { // net/url doesn't handle local context authorities and leaves that up // to the scheme handler. In our case, we translate amqp:/// into the // default host and whatever the vhost should be if len(u.Path) > 3 { builder.Vhost = u.Path[3:] } } else if len(u.Path) > 1 { builder.Vhost = u.Path[1:] } } else { builder.Vhost = u.Path } } return builder, nil } // PlainAuth returns a PlainAuth structure based on the parsed URI's // Username and Password fields. func (uri URI) PlainAuth() *PlainAuth { return &PlainAuth{ Username: uri.Username, Password: uri.Password, } } // AMQPlainAuth returns a PlainAuth structure based on the parsed URI's // Username and Password fields. func (uri URI) AMQPlainAuth() *AMQPlainAuth { return &AMQPlainAuth{ Username: uri.Username, Password: uri.Password, } } func (uri URI) String() string { authority, err := url.Parse("") if err != nil { return err.Error() } authority.Scheme = uri.Scheme if uri.Username != defaultURI.Username || uri.Password != defaultURI.Password { authority.User = url.User(uri.Username) if uri.Password != defaultURI.Password { authority.User = url.UserPassword(uri.Username, uri.Password) } } authority.Host = net.JoinHostPort(uri.Host, strconv.Itoa(uri.Port)) if defaultPort, found := schemePorts[uri.Scheme]; !found || defaultPort != uri.Port { authority.Host = net.JoinHostPort(uri.Host, strconv.Itoa(uri.Port)) } else { // JoinHostPort() automatically add brackets to the host if it's // an IPv6 address. // // If not port is specified, JoinHostPort() return an IP address in the // form of "[::1]:", so we use TrimSuffix() to remove the extra ":". authority.Host = strings.TrimSuffix(net.JoinHostPort(uri.Host, ""), ":") } if uri.Vhost != defaultURI.Vhost { // Make sure net/url does not double escape, e.g. // "%2F" does not become "%252F". authority.Path = uri.Vhost authority.RawPath = url.QueryEscape(uri.Vhost) } else { authority.Path = "/" } return authority.String() }