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()