You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@qpid.apache.org by ac...@apache.org on 2008/11/20 19:29:21 UTC

svn commit: r719317 - in /incubator/qpid/trunk/qpid/cpp/src: qpid/Address.cpp qpid/Address.h qpid/Url.cpp qpid/Url.h tests/Url.cpp

Author: aconway
Date: Thu Nov 20 10:29:20 2008
New Revision: 719317

URL: http://svn.apache.org/viewvc?rev=719317&view=rev
Log:
Replaced boost.spirit-based URL parser with simple recursive descent parser.

boost.spirit has some known thread-safety issues and appears to be causing
qpid clients to crash in several scenarios. 

The new parser is trivially thread safe and relatively easy to extend.
It's a simple recursive descent parser, sufficient for simple grammars
like those used in URL formats. It's not intended to be a
full-featured parser framework like spirit.

Modified:
    incubator/qpid/trunk/qpid/cpp/src/qpid/Address.cpp
    incubator/qpid/trunk/qpid/cpp/src/qpid/Address.h
    incubator/qpid/trunk/qpid/cpp/src/qpid/Url.cpp
    incubator/qpid/trunk/qpid/cpp/src/qpid/Url.h
    incubator/qpid/trunk/qpid/cpp/src/tests/Url.cpp

Modified: incubator/qpid/trunk/qpid/cpp/src/qpid/Address.cpp
URL: http://svn.apache.org/viewvc/incubator/qpid/trunk/qpid/cpp/src/qpid/Address.cpp?rev=719317&r1=719316&r2=719317&view=diff
==============================================================================
--- incubator/qpid/trunk/qpid/cpp/src/qpid/Address.cpp (original)
+++ incubator/qpid/trunk/qpid/cpp/src/qpid/Address.cpp Thu Nov 20 10:29:20 2008
@@ -18,17 +18,41 @@
 
 #include "Address.h"
 
+#include <ostream>
+
+using namespace std;
+
 namespace qpid {
 
-std::ostream& operator<<(std::ostream& os, const Address& addr) {
-    const TcpAddress *t = addr.get<TcpAddress>();
-    if (t)
-        os << *t;
-    return os;
+TcpAddress::TcpAddress(const std::string& h, uint16_t p): host(h), port(p) {}
+
+struct AddressOstreamVisitor : public boost::static_visitor<ostream&> {
+    ostream& out;
+    AddressOstreamVisitor(ostream& o) : out(o) {}
+    template <class T> ostream& operator()(const T& data) { return out << data; }
+};
+
+ostream& operator<<(ostream& os, const Address& addr) {
+    AddressOstreamVisitor visitor(os);
+    return boost::apply_visitor(visitor, addr.value);
+}
+
+bool operator==(const TcpAddress& x, const TcpAddress& y) {
+    return y.host==x.host && y.port == x.port;
 }
 
-std::ostream& operator<<(std::ostream& os, const TcpAddress& a) {
+ostream& operator<<(ostream& os, const TcpAddress& a) {
     return os << "tcp:" << a.host << ":" << a.port;
 }
 
+ExampleAddress::ExampleAddress(const char c) : data(c) {}
+
+bool operator==(const ExampleAddress& x, const ExampleAddress& y) {
+    return x.data == y.data;
+}
+
+ostream& operator<<(ostream& os, const ExampleAddress& ex) {
+    return os << "example:" << ex.data;
+}
+
 } // namespace qpid

Modified: incubator/qpid/trunk/qpid/cpp/src/qpid/Address.h
URL: http://svn.apache.org/viewvc/incubator/qpid/trunk/qpid/cpp/src/qpid/Address.h?rev=719317&r1=719316&r2=719317&view=diff
==============================================================================
--- incubator/qpid/trunk/qpid/cpp/src/qpid/Address.h (original)
+++ incubator/qpid/trunk/qpid/cpp/src/qpid/Address.h Thu Nov 20 10:29:20 2008
@@ -22,7 +22,7 @@
 #include "qpid/sys/IntegerTypes.h"
 
 #include <boost/variant.hpp>
-#include <ostream>
+#include <iosfwd>
 #include <string>
 #include <vector>
 
@@ -31,39 +31,54 @@
 /** TCP address of a broker - host:port */
 struct TcpAddress {
     static const uint16_t DEFAULT_PORT=5672;
-    explicit TcpAddress(const std::string& host_=std::string(),
-                        uint16_t port_=DEFAULT_PORT)
-        : host(host_), port(port_) {}
+    explicit TcpAddress(const std::string& host_=std::string(),uint16_t port_=DEFAULT_PORT);
     std::string host;
     uint16_t port;
 };
-
-inline bool operator==(const TcpAddress& x, const TcpAddress& y) {
-    return y.host==x.host && y.port == x.port;
-}
-
+bool operator==(const TcpAddress& x, const TcpAddress& y);
 std::ostream& operator<<(std::ostream& os, const TcpAddress& a);
 
+/**@internal Not a real address type, this is a placeholder to
+ * demonstrate and validate multi-protocol Urls for unit tests and
+ * developer education only. An example address holds just a char.
+ */
+struct ExampleAddress {
+    explicit ExampleAddress(char data);
+    char data;
+};
+bool operator==(const ExampleAddress& x, const ExampleAddress& y);
+std::ostream& operator<<(std::ostream& os, const ExampleAddress& a);
+
 /**
- * Address is a variant of all address types, more coming in future.
- *
- * Address wraps a boost::variant rather than be defined in terms of
- * boost::variant to prevent users from having to deal directly with
- * boost.
+ * Contains the address of an AMQP broker. Can any supported type of
+ * broker address.  Currently only TcpAddress is supported.
  */
 struct Address  {
 public:
     Address(const Address& a) : value(a.value) {}
     Address(const TcpAddress& tcp) : value(tcp) {}
-    template <class T> Address& operator=(const T& t) { value=t; return *this; }
-    template <class T> T* get() { return boost::get<T>(&value); }
-    template <class T> const T* get() const { return boost::get<T>(&value); }
+    Address(const ExampleAddress& eg) : value(eg) {} ///<@internal
+
+    template <class AddressType> Address& operator=(const AddressType& t) { value=t; return *this; }
+
+    /** Get the address of type AddressType.
+     *@return AddressType* pointing to the contained address or 0 if
+     *contained address is not of type AddressType.  
+     */
+    template <class AddressType> AddressType* get() { return boost::get<AddressType>(&value); }
+
+    /** Get the address of type AddressType.
+     *@return AddressType* pointing to the contained address or 0 if
+     *contained address is not of type AddressType.  
+     */
+    template <class AddressType> const AddressType* get() const { return boost::get<AddressType>(&value); }
 
 private:
-    boost::variant<TcpAddress> value;
+    boost::variant<TcpAddress,ExampleAddress> value;
+  friend std::ostream& operator<<(std::ostream& os, const Address& addr);
 };
 
-std::ostream& operator<<(std::ostream& os, const Address& addr);
+
 
 } // namespace qpid
 

Modified: incubator/qpid/trunk/qpid/cpp/src/qpid/Url.cpp
URL: http://svn.apache.org/viewvc/incubator/qpid/trunk/qpid/cpp/src/qpid/Url.cpp?rev=719317&r1=719316&r2=719317&view=diff
==============================================================================
--- incubator/qpid/trunk/qpid/cpp/src/qpid/Url.cpp (original)
+++ incubator/qpid/trunk/qpid/cpp/src/qpid/Url.cpp Thu Nov 20 10:29:20 2008
@@ -27,21 +27,28 @@
 
 #include <boost/spirit.hpp>
 #include <boost/spirit/actor.hpp>
+#include <boost/lexical_cast.hpp>
 
 #include <sstream>
+#include <map>
+#include <algorithm>
+#include <limits>
 
 #include <stdio.h>
 #include <errno.h>
 
 using namespace boost::spirit;
 using namespace std;
+using boost::lexical_cast;
 
 namespace qpid {
 
+Url::Invalid::Invalid(const string& s) : Exception(s) {}
+
 Url Url::getHostNameUrl(uint16_t port) {
     TcpAddress address(std::string(), port);
     if (!sys::SystemInfo::getLocalHostname(address))
-        throw InvalidUrl(QPID_MSG("Cannot get host name: " << qpid::sys::strError(errno)));
+        throw Url::Invalid(QPID_MSG("Cannot get host name: " << qpid::sys::strError(errno)));
     return Url(address);
 }
 
@@ -71,6 +78,120 @@
     return os;
 }
 
+
+/** Simple recursive-descent parser for url grammar in AMQP 0-10 spec:
+
+        amqp_url          = "amqp:" prot_addr_list
+        prot_addr_list    = [prot_addr ","]* prot_addr
+        prot_addr         = tcp_prot_addr | tls_prot_addr
+
+        tcp_prot_addr     = tcp_id tcp_addr
+        tcp_id            = "tcp:" | ""
+        tcp_addr          = [host [":" port] ]
+        host              = <as per http://www.ietf.org/rfc/rfc3986.txt>
+        port              = number]]>
+*/
+class UrlParser {
+  public:
+    UrlParser(Url& u, const char* s) : url(u), text(s), end(s+strlen(s)), i(s) {}
+    bool parse() { return literal("amqp:") && list(&UrlParser::protAddr, &UrlParser::comma) && i == end; }
+
+  private:
+    typedef bool (UrlParser::*Rule)();
+
+    bool comma() { return literal(","); }
+
+    //  NOTE: tcpAddr must be last since it is allowed to omit it's tcp: tag.
+    bool protAddr() { return exampleAddr() || tcpAddr(); }
+
+    bool tcpAddr() {
+        TcpAddress addr;
+        literal("tcp:");        // Don't check result, allowed to be absent.
+        return addIf(host(addr.host) && (literal(":") ? port(addr.port) : true), addr);
+    }
+    
+    // Placeholder address type till we have multiple address types. Address is a single char.
+    bool exampleAddr () {
+        if (literal("example:") && i < end) {
+            ExampleAddress ex(*i++);
+            url.push_back(ex);
+            return true;
+        }
+        return false;
+    }
+
+    // FIXME aconway 2008-11-20: this does not implement http://www.ietf.org/rfc/rfc3986.txt.
+    // Works for DNS names and ipv4 literals but won't handle ipv6.
+    bool host(string& h) {
+        const char* start=i;
+        while (unreserved() || pctEncoded())
+            ;
+        if (start == i) h = LOCALHOST; // Default
+        else h.assign(start, i);
+        return true;
+    }
+
+    bool unreserved() { return (::isalnum(*i) || ::strchr("-._~", *i)) && advance(); }
+
+    bool pctEncoded() { return literal("%") && hexDigit() && hexDigit(); }
+
+    bool hexDigit() { return ::strchr("01234567890abcdefABCDEF", *i) && advance(); }
+    
+    bool port(uint16_t& p) { return decimalInt(p); }
+
+    template <class AddrType> bool addIf(bool ok, const AddrType& addr) { if (ok) url.push_back(addr); return ok; }
+    
+    template <class IntType> bool decimalInt(IntType& n) {
+        const char* start = i;
+        while (::isdigit(*i)) ++i;
+        try {
+            n = lexical_cast<IntType>(string(start, i)); 
+            return true;
+        } catch(...) { return false; }
+    }
+
+    bool literal(const char* s) {
+        int n = ::strlen(s);
+        if (n <= end-i && equal(s, s+n, i)) return advance(n);
+        return false;
+    };
+
+    bool noop() { return true; }
+
+    /** List of item, separated by separator, with min & max bounds. */
+    bool list(Rule item, Rule separator, size_t min=0, size_t max=UNLIMITED) {
+        assert(max > 0);
+        assert(max >= min);
+        if (!(this->*item)()) return min == 0; // Empty list.
+        size_t n = 1;
+        while (n < max && i < end) {
+            if (!(this->*separator)()) break;
+            if (i == end || !(this->*item)()) return false; // Separator with no item.
+            ++n;
+        }
+        return n >= min;
+    }
+
+    /** List of items with no separator */
+    bool list(Rule item, size_t min=0, size_t max=UNLIMITED) { return list(item, &UrlParser::noop, min, max); }
+
+    bool advance(size_t n=1) {
+        if (i+n > end) return false;
+        i += n;
+        return true;
+    }
+
+    static const size_t UNLIMITED = size_t(~1);
+    static const std::string LOCALHOST;
+
+    Url& url;
+    const char* text;
+    const char* end;
+    const char* i;
+};
+
+const string UrlParser::LOCALHOST("127.0.0.1");
+
 // Addition to boost::spirit parsers: accept any character from a
 // string. Vastly more compile-time-efficient than long rules of the
 // form: ch_p('x') | ch_p('y') |...
@@ -129,20 +250,20 @@
 };
 
 void Url::parse(const char* url) {
-    cache.clear();
-    if (!boost::spirit::parse(url, UrlGrammar(*this)).full)
-        throw InvalidUrl(string("Invalid AMQP url: ")+url);
+    parseNoThrow(url);
+    if (empty())
+        throw Url::Invalid(QPID_MSG("Invalid URL: " << url));
 }
 
 void Url::parseNoThrow(const char* url) {
     cache.clear();
-    if (!boost::spirit::parse(url, UrlGrammar(*this)).full)
+    if (!UrlParser(*this, url).parse())
         clear();
 }
 
 void Url::throwIfEmpty() const {
     if (empty())
-        throw InvalidUrl("URL contains no addresses");
+        throw Url::Invalid("URL contains no addresses");
 }
 
 std::istream& operator>>(std::istream& is, Url& url) {

Modified: incubator/qpid/trunk/qpid/cpp/src/qpid/Url.h
URL: http://svn.apache.org/viewvc/incubator/qpid/trunk/qpid/cpp/src/qpid/Url.h?rev=719317&r1=719316&r2=719317&view=diff
==============================================================================
--- incubator/qpid/trunk/qpid/cpp/src/qpid/Url.h (original)
+++ incubator/qpid/trunk/qpid/cpp/src/qpid/Url.h Thu Nov 20 10:29:20 2008
@@ -40,9 +40,7 @@
      * on a multi-homed host. */
     static Url getIpAddressesUrl(uint16_t port);
 
-    struct InvalidUrl : public Exception {
-        InvalidUrl(const std::string& s) : Exception(s) {}
-    };
+    struct Invalid : public Exception { Invalid(const std::string& s); };
 
     /** Convert to string form. */
     std::string str() const;
@@ -53,22 +51,22 @@
     /** URL containing a single address */
     explicit Url(const Address& addr) { push_back(addr); }
 
-    /** Parse url, throw InvalidUrl if invalid. */
+    /** Parse url, throw Invalid if invalid. */
     explicit Url(const std::string& url) { parse(url.c_str()); }
 
-    /** Parse url, throw InvalidUrl if invalid. */
+    /** Parse url, throw Invalid if invalid. */
     explicit Url(const char* url) { parse(url); }
 
     Url& operator=(const Url& u) { this->std::vector<Address>::operator=(u); cache=u.cache; return *this; }
     Url& operator=(const char* s) { parse(s); return *this; }
     Url& operator=(const std::string& s) { parse(s); return *this; }
     
-    /** Throw InvalidUrl if the URL does not contain any addresses. */
+    /** Throw Invalid if the URL does not contain any addresses. */
     void throwIfEmpty() const;
 
     /** Replace contents with parsed URL as defined in
      * https://wiki.108.redhat.com/jira/browse/AMQP-95
-     *@exception InvalidUrl if the url is invalid.
+     *@exception Invalid if the url is invalid.
      */
     void parse(const char* url);
     void parse(const std::string& url) { parse(url.c_str()); }

Modified: incubator/qpid/trunk/qpid/cpp/src/tests/Url.cpp
URL: http://svn.apache.org/viewvc/incubator/qpid/trunk/qpid/cpp/src/tests/Url.cpp?rev=719317&r1=719316&r2=719317&view=diff
==============================================================================
--- incubator/qpid/trunk/qpid/cpp/src/tests/Url.cpp (original)
+++ incubator/qpid/trunk/qpid/cpp/src/tests/Url.cpp Thu Nov 20 10:29:20 2008
@@ -28,37 +28,40 @@
 
 QPID_AUTO_TEST_SUITE(UrlTestSuite)
 
-QPID_AUTO_TEST_CASE(testUrl_str) {
-    Url url;
-    url.push_back(TcpAddress("foo.com"));
-    url.push_back(TcpAddress("bar.com", 6789));
-    BOOST_CHECK_EQUAL("amqp:tcp:foo.com:5672,tcp:bar.com:6789", url.str());
-    BOOST_CHECK(Url().str().empty());
-}
+#define URL_CHECK_STR(STR) BOOST_CHECK_EQUAL(Url(STR).str(), STR)
+#define URL_CHECK_INVALID(STR) BOOST_CHECK_THROW(Url(STR), Url::Invalid)
+
+QPID_AUTO_TEST_CASE(TestParseTcp) {
+    URL_CHECK_STR("amqp:tcp:host:42");
+    URL_CHECK_STR("amqp:tcp:host-._~%ff%23:42"); // unreserved chars and pct encoded hex.
+
+    // Check defaults
+    BOOST_CHECK_EQUAL(Url("amqp:host:42").str(), "amqp:tcp:host:42");
+    BOOST_CHECK_EQUAL(Url("amqp:tcp:host").str(), "amqp:tcp:host:5672");
+    BOOST_CHECK_EQUAL(Url("amqp:tcp:").str(), "amqp:tcp:127.0.0.1:5672");
+    BOOST_CHECK_EQUAL(Url("amqp:").str(), "amqp:tcp:127.0.0.1:5672");
+    BOOST_CHECK_EQUAL(Url("amqp::42").str(), "amqp:tcp:127.0.0.1:42");
 
+    URL_CHECK_INVALID("amqp::badHost!#$#");
+    URL_CHECK_INVALID("amqp::host:badPort");
+}
 
-QPID_AUTO_TEST_CASE(testUrl_parse) {
-    Url url;
-    url.parse("amqp:foo.com,tcp:bar.com:1234");
-    BOOST_CHECK_EQUAL(2u, url.size());
-    BOOST_CHECK_EQUAL("foo.com", url[0].get<TcpAddress>()->host);
-    BOOST_CHECK_EQUAL("amqp:tcp:foo.com:5672,tcp:bar.com:1234", url.str());
-
-    url.parse("amqp:foo/ignorethis");
-    BOOST_CHECK_EQUAL("amqp:tcp:foo:5672", url.str());
-
-    url.parse("amqp:");
-    BOOST_CHECK_EQUAL("amqp:tcp::5672", url.str());
-
-    try {
-        url.parse("invalid url");
-        BOOST_FAIL("Expected InvalidUrl exception");
-    }
-    catch (const Url::InvalidUrl&) {}
+QPID_AUTO_TEST_CASE(TestParseExample) {
+    URL_CHECK_STR("amqp:example:x");
+    URL_CHECK_INVALID("amqp:example:badExample");
+}
 
-    url.parseNoThrow("invalid url");
-    BOOST_CHECK(url.empty());
+QPID_AUTO_TEST_CASE(TestParseMultiAddress) {
+    URL_CHECK_STR("amqp:tcp:host:0,example:y,tcp:foo:0,example:1");
+    URL_CHECK_STR("amqp:example:z,tcp:foo:0");
+    URL_CHECK_INVALID("amqp:tcp:h:0,");
+    URL_CHECK_INVALID(",amqp:tcp:h");
 }
 
 
+QPID_AUTO_TEST_CASE(TestInvalidAddress) {
+    URL_CHECK_INVALID("xxxx");
+    URL_CHECK_INVALID("");
+}
+
 QPID_AUTO_TEST_SUITE_END()