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 2018/09/18 15:14:51 UTC

qpid-proton git commit: PROTON-1935: [cpp] connection configuration and default connect()

Repository: qpid-proton
Updated Branches:
  refs/heads/master 348e9da89 -> b164d99c8


PROTON-1935: [cpp] connection configuration and default connect()

docs/connect-config.md: describes connection configuration JSON format.

container::connect() connects using the default configuration file

Additional API in proton::connect_config allows the user to parse configuration
and apply to a connection_options object for more flexible use.


Project: http://git-wip-us.apache.org/repos/asf/qpid-proton/repo
Commit: http://git-wip-us.apache.org/repos/asf/qpid-proton/commit/b164d99c
Tree: http://git-wip-us.apache.org/repos/asf/qpid-proton/tree/b164d99c
Diff: http://git-wip-us.apache.org/repos/asf/qpid-proton/diff/b164d99c

Branch: refs/heads/master
Commit: b164d99c80129a2a24ae7203846579569c9cf3b5
Parents: 348e9da
Author: Alan Conway <ac...@redhat.com>
Authored: Tue Sep 18 09:31:47 2018 -0400
Committer: Alan Conway <ac...@redhat.com>
Committed: Tue Sep 18 11:03:25 2018 -0400

----------------------------------------------------------------------
 INSTALL.md                            |   2 +
 c/include/proton/cproton.i            |   1 +
 c/include/proton/version.h.in         |   2 +
 cpp/CMakeLists.txt                    |  20 ++-
 cpp/docs/CMakeLists.txt               |   2 +-
 cpp/docs/user.doxygen.in              |   3 +-
 cpp/include/proton/connect_config.hpp |  49 +++++++
 cpp/include/proton/container.hpp      |   6 +
 cpp/src/connect_config.cpp            | 219 +++++++++++++++++++++++++++++
 cpp/src/connect_config_dummy.cpp      |  31 ++++
 cpp/src/connect_config_test.cpp       | 142 +++++++++++++++++++
 cpp/src/connection_options.cpp        |   1 +
 cpp/src/container.cpp                 |   4 +
 cpp/src/proactor_container_impl.cpp   |   7 +
 cpp/src/proactor_container_impl.hpp   |   1 +
 cpp/src/test_bits.hpp                 |   8 ++
 docs/connect_config.md                |  42 ++++++
 tools/cmake/Modules/FindJsonCpp.cmake |  70 +++++++++
 18 files changed, 607 insertions(+), 3 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/b164d99c/INSTALL.md
----------------------------------------------------------------------
diff --git a/INSTALL.md b/INSTALL.md
index 430d648..76e6af2 100644
--- a/INSTALL.md
+++ b/INSTALL.md
@@ -20,6 +20,7 @@ Linux dependencies
   - GCC 4.4+
   - Cyrus SASL 2.1+ (for SASL support)
   - OpenSSL 1.0+ (for SSL support)
+  - JsonCpp 1.8+ for C++ connection configuration file support
 
 Windows dependencies
 
@@ -46,6 +47,7 @@ language.
     $ yum install swig                                       # Required for all bindings
     $ yum install python-devel                               # Python
     $ yum install ruby-devel rubygem-minitest                # Ruby
+    $ yum install jsoncpp-devel                              # C++ optional config file
 
     # Dependencies needed to generate documentation
     $ yum install epydoc                                     # Python

http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/b164d99c/c/include/proton/cproton.i
----------------------------------------------------------------------
diff --git a/c/include/proton/cproton.i b/c/include/proton/cproton.i
index 430deca..c642438 100644
--- a/c/include/proton/cproton.i
+++ b/c/include/proton/cproton.i
@@ -37,6 +37,7 @@ typedef unsigned long int uintptr_t;
 %include "proton/import_export.h"
 
 %ignore _PROTON_VERSION_H;
+%ignore PN_INSTALL_PREFIX;
 %include "proton/version.h"
 
 /* We cannot safely just wrap pn_bytes_t but each language binding must have a typemap for it - presumably to a string type */

http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/b164d99c/c/include/proton/version.h.in
----------------------------------------------------------------------
diff --git a/c/include/proton/version.h.in b/c/include/proton/version.h.in
index 133b0bb..13ee411 100644
--- a/c/include/proton/version.h.in
+++ b/c/include/proton/version.h.in
@@ -26,4 +26,6 @@
 #define PN_VERSION_MINOR @PN_VERSION_MINOR@
 #define PN_VERSION_POINT @PN_VERSION_POINT@
 
+#define PN_INSTALL_PREFIX "@CMAKE_INSTALL_PREFIX@"
+
 #endif /* version.h */

http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/b164d99c/cpp/CMakeLists.txt
----------------------------------------------------------------------
diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt
index d0b3cfb..35f5478 100644
--- a/cpp/CMakeLists.txt
+++ b/cpp/CMakeLists.txt
@@ -27,6 +27,16 @@ include(versions.cmake)
 
 set (BUILD_CPP_03 OFF CACHE BOOL "Compile the C++ binding as C++03 even when C++11 is available")
 
+# Check for JSON-CPP support for connection configuration
+find_package(JsonCpp)
+option(ENABLE_JSONCPP "Use jsoncpp parser for connection configuration" ${JsonCpp_FOUND})
+if (ENABLE_JSONCPP)
+  set(CONNECT_CONFIG_SRC src/connect_config.cpp)
+  set(CONNECT_CONFIG_LIBS ${JsonCpp_LIBRARY})
+else()
+  set(CONNECT_CONFIG_SRC src/connect_config_dummy.cpp)
+endif()
+
 # This effectively checks for cmake version 3.1 or later
 if (DEFINED CMAKE_CXX_COMPILE_FEATURES)
   if (BUILD_CPP_03)
@@ -150,6 +160,7 @@ set(qpid-proton-cpp-source
   src/uuid.cpp
   src/value.cpp
   src/work_queue.cpp
+  ${CONNECT_CONFIG_SRC}
   )
 
 set_source_files_properties (
@@ -167,7 +178,7 @@ if(BUILD_STATIC_LIBS)
   add_library(qpid-proton-cpp-static STATIC ${qpid-proton-cpp-source})
 endif(BUILD_STATIC_LIBS)
 
-target_link_libraries (qpid-proton-cpp LINK_PRIVATE ${PLATFORM_LIBS} qpid-proton-core qpid-proton-proactor)
+target_link_libraries (qpid-proton-cpp LINK_PRIVATE ${PLATFORM_LIBS} qpid-proton-core qpid-proton-proactor ${CONNECT_CONFIG_LIBS})
 
 set_target_properties (
   qpid-proton-cpp
@@ -258,3 +269,10 @@ add_cpp_test(container_test)
 add_cpp_test(url_test)
 add_cpp_test(reconnect_test)
 add_cpp_test(link_test)
+if (ENABLE_JSONCPP)
+  # Directories needed by connect_config tests
+  file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/testdata/.config/messaging")
+  add_cpp_test(connect_config_test)
+  set_tests_properties(cpp-connect_config_test PROPERTIES
+    WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}")
+endif()

http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/b164d99c/cpp/docs/CMakeLists.txt
----------------------------------------------------------------------
diff --git a/cpp/docs/CMakeLists.txt b/cpp/docs/CMakeLists.txt
index d512d15..690230d 100644
--- a/cpp/docs/CMakeLists.txt
+++ b/cpp/docs/CMakeLists.txt
@@ -24,7 +24,7 @@ if (DOXYGEN_FOUND)
     ${CMAKE_CURRENT_SOURCE_DIR}/user.doxygen.in
     ${CMAKE_CURRENT_BINARY_DIR}/user.doxygen)
 
-  file(GLOB_RECURSE headers ../include/proton/*.hpp)
+  file(GLOB_RECURSE sources ../include/proton/*.hpp ../../connect_config.md)
   add_custom_target (docs-cpp
     COMMAND ${CMAKE_COMMAND} -E remove_directory html # get rid of old files
     COMMAND ${DOXYGEN_EXECUTABLE} user.doxygen

http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/b164d99c/cpp/docs/user.doxygen.in
----------------------------------------------------------------------
diff --git a/cpp/docs/user.doxygen.in b/cpp/docs/user.doxygen.in
index 6bcd1bd..84375d8 100644
--- a/cpp/docs/user.doxygen.in
+++ b/cpp/docs/user.doxygen.in
@@ -55,7 +55,8 @@ WARNINGS                = YES
 
 INPUT                   = @CMAKE_SOURCE_DIR@/cpp/include \
                           @CMAKE_SOURCE_DIR@/cpp/docs \
-                          @CMAKE_SOURCE_DIR@/cpp/examples
+                          @CMAKE_SOURCE_DIR@/cpp/examples \
+                          @CMAKE_SOURCE_DIR@/docs/connect_config.md
 FILE_PATTERNS           = *.hpp *.md *.dox
 EXCLUDE_PATTERNS        = @CMAKE_SOURCE_DIR@/cpp/examples/*.?pp \
                           @CMAKE_SOURCE_DIR@/cpp/include/proton/internal/*.hpp

http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/b164d99c/cpp/include/proton/connect_config.hpp
----------------------------------------------------------------------
diff --git a/cpp/include/proton/connect_config.hpp b/cpp/include/proton/connect_config.hpp
new file mode 100644
index 0000000..be5c7ac
--- /dev/null
+++ b/cpp/include/proton/connect_config.hpp
@@ -0,0 +1,49 @@
+#ifndef CONNECT_CONFIG_HPP
+#define CONNECT_CONFIG_HPP
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#include <proton/connection_options.hpp>
+
+namespace proton {
+
+class connection_options;
+
+/// *Unsettled API*
+namespace connect_config {
+
+/// @return name of the default connection configuration file
+/// @throw proton::error if no default file is found
+PN_CPP_EXTERN std::string default_file();
+
+/// Parse configuration from @p is and update @p opts
+/// @param is input stream for configuration file/string
+/// @param opts [out] connection options to update
+/// @return address suitable for container::connect() from configuration
+PN_CPP_EXTERN std::string parse(std::istream& is, connection_options& opts);
+
+/// Parse configuration from default_file() and update @p opts
+/// @param opts [out] connection options to update
+/// @return address suitable for container::connect() from configuration
+PN_CPP_EXTERN std::string parse_default(connection_options& opts);
+
+}} // namespace proton::connect_config
+
+#endif // CONNECT_CONFIG_HPP

http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/b164d99c/cpp/include/proton/container.hpp
----------------------------------------------------------------------
diff --git a/cpp/include/proton/container.hpp b/cpp/include/proton/container.hpp
index 27ac498..362dba0 100644
--- a/cpp/include/proton/container.hpp
+++ b/cpp/include/proton/container.hpp
@@ -113,6 +113,12 @@ class PN_CPP_CLASS_EXTERN container {
     /// @copydetails returned
     PN_CPP_EXTERN returned<connection> connect(const std::string& conn_url);
 
+    /// Connect using the default @ref connect_config
+    /// FIXME aconway 2018-08-07: cmake - copy connect_config.md into C++ doc
+    ///
+    /// @copydetails returned
+    PN_CPP_EXTERN returned<connection> connect();
+
     /// Listen for new connections on `listen_url`.
     ///
     /// If the listener opens successfully, listen_handler::on_open() is called.

http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/b164d99c/cpp/src/connect_config.cpp
----------------------------------------------------------------------
diff --git a/cpp/src/connect_config.cpp b/cpp/src/connect_config.cpp
new file mode 100644
index 0000000..0dc577a
--- /dev/null
+++ b/cpp/src/connect_config.cpp
@@ -0,0 +1,219 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#include "msg.hpp"
+
+#include <proton/connect_config.hpp>
+#include <proton/error.hpp>
+#include <proton/ssl.hpp>
+
+#include <proton/version.h>
+
+#include <json/value.h>
+#include <json/reader.h>
+
+#include <cstdlib>
+#include <fstream>
+
+using namespace Json;
+using std::string;
+
+namespace {
+const char *type_name(ValueType t) {
+    switch (t) {
+      case nullValue: return "null";
+      case intValue: return "int";
+      case uintValue: return "uint";
+      case realValue: return "real";
+      case stringValue: return "string";
+      case booleanValue: return "boolean";
+      case arrayValue: return "array";
+      case objectValue: return "object";
+      default: return "unknown";
+    }
+}
+} // namespace
+
+namespace std {
+ostream& operator<<(ostream& o, ValueType t) { return o << type_name(t); }
+}
+
+namespace proton {
+namespace connect_config {
+
+namespace {
+
+void raise(const string& message) {
+    throw proton::error("connection configuration: " + message);
+}
+
+Value validate(ValueType t, const Value& v, const string& name) {
+    if (v.type() != t)
+        raise(msg() << " '" << name << "' expected " << t << ", found " << v.type());
+    return v;
+}
+
+Value get(ValueType t, const Value& obj, const char *key, const Value& dflt=Value()) {
+    Value v = obj ? obj.get(key, dflt) : dflt;
+    return validate(t, v, key);
+}
+
+bool get_bool(const Value& obj, const char *key, bool dflt) {
+    return get(booleanValue, obj, key, dflt).asBool();
+}
+
+string get_string(const Value& obj, const char *key, const string& dflt) {
+    return get(stringValue, obj, key, dflt).asString();
+}
+
+static const string HOME("HOME");
+static const string ENV_VAR("MESSAGING_CONNECT_FILE");
+static const string FILE_NAME("connect.json");
+static const string HOME_FILE_NAME("/.config/messaging/" + FILE_NAME);
+static const string ETC_FILE_NAME("/etc/messaging/" + FILE_NAME);
+
+bool exists(const string& name) { return std::ifstream(name.c_str()).good(); }
+
+void parse_sasl(Value root, connection_options& opts) {
+    Value sasl = root.get("sasl", Value());
+    opts.sasl_enabled(get_bool(sasl, "enable", true));
+    if (sasl) {
+        validate(objectValue, sasl, "sasl");
+        opts.sasl_allow_insecure_mechs(get_bool(sasl, "allow_insecure", false));
+        Value mechs = sasl.get("mechanisms", Value());
+        switch (mechs.type()) {
+          case nullValue:
+            break;
+          case stringValue:
+            opts.sasl_allowed_mechs(mechs.asString());
+            break;
+          case arrayValue: {
+              std::ostringstream s;
+              for (ArrayIndex i= 0; i < mechs.size(); ++i) {
+                  Value v = mechs.get(i, Value());
+                  if (v.type() != stringValue) {
+                      raise(msg() << "'sasl/mechanisms' expect string elements, found " << v.type());
+                  }
+                  if (i > 0) s << " ";
+                  s << v.asString();
+              }
+              opts.sasl_allowed_mechs(s.str().c_str());
+              break;
+          }
+          default:
+            raise(msg() << "'mechanisms' expected string or array, found " << mechs.type());
+        }
+    }
+}
+
+void parse_tls(const string& scheme, Value root, connection_options& opts) {
+    Value tls = root.get("tls", Value());
+    if (tls) {
+        validate(objectValue, tls, "tls");
+        if (scheme != "amqps") {
+            raise(msg() << "'tls' object is not allowed unless scheme is \"amqps\"");
+        }
+        string ca = get_string(tls, "ca", "");
+        bool verify = get_bool(tls, "verify", true);
+        Value cert = get(stringValue, tls, "cert");
+        ssl::verify_mode mode = verify ? ssl::VERIFY_PEER_NAME : ssl::ANONYMOUS_PEER;
+        if (cert) {
+            Value key = get(stringValue, tls, "key");
+            ssl_certificate cert2 = key ?
+                ssl_certificate(cert.asString(), key.asString()) :
+                ssl_certificate(cert.asString());
+            opts.ssl_client_options(ssl_client_options(cert2, ca, mode));
+        } else {
+            ssl_client_options(ssl_client_options(ca, mode));
+        }
+    }
+}
+
+} // namespace
+
+std::string parse(std::istream& is, connection_options& opts) {
+    Value root;
+    is >> root;
+
+    string scheme = get_string(root, "scheme", "amqps");
+    if (scheme != "amqp" && scheme != "amqps") {
+        raise(msg() << "'scheme' must be \"amqp\" or \"amqps\"");
+    }
+
+    string host = get_string(root, "host", "");
+    opts.virtual_host(host.c_str());
+
+    Value port = root.get("port", scheme);
+    if (!port.isIntegral() && !port.isString()) {
+        raise(msg() << "'port' expected string or integer, found " << port.type());
+    }
+
+    Value user = root.get("user", Value());
+    if (user) opts.user(validate(stringValue, user, "user").asString());
+    Value password = root.get("password", Value());
+    if (password) opts.password(validate(stringValue, password, "password").asString());
+
+    parse_sasl(root, opts);
+    parse_tls(scheme, root, opts);
+    return host + ":" + port.asString();
+}
+
+string default_file() {
+    /* Use environment variable if set */
+    const char *env_path = getenv(ENV_VAR.c_str());
+    if (env_path) return env_path;
+    /* current directory */
+    if (exists(FILE_NAME)) return FILE_NAME;
+    /* $HOME/.config/messaging/FILE_NAME */
+    const char *home = getenv(HOME.c_str());
+    if (home) {
+        string path = home + HOME_FILE_NAME;
+        if (exists(path)) return path;
+    }
+    /* INSTALL_PREFIX/etc/messaging/FILE_NAME */
+    if (PN_INSTALL_PREFIX && *PN_INSTALL_PREFIX) {
+        string path = PN_INSTALL_PREFIX + ETC_FILE_NAME;
+        if (exists(path)) return path;
+    }
+    /* /etc/messaging/FILE_NAME */
+    if (exists(ETC_FILE_NAME)) return ETC_FILE_NAME;
+    raise("no default configuration");
+    return "";                  // Never get here, keep compiler happy
+}
+
+string parse_default(connection_options& opts) {
+    string name = default_file();
+    std::ifstream f;
+    try {
+        f.exceptions(~std::ifstream::goodbit);
+        f.open(name);
+    } catch (const std::exception& e) {
+        raise(msg() << "error opening '" << name << "': " << e.what());
+    }
+    try {
+        return parse(f, opts);
+    } catch (const std::exception& e) {
+        raise(msg() << "error parsing '" << name << "': " << e.what());
+    } catch (...) {
+        raise(msg() << "error parsing '" << name);
+    }
+    return "";                  // Never get here, keep compiler happy
+}
+
+}} // namespace proton::connect_config

http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/b164d99c/cpp/src/connect_config_dummy.cpp
----------------------------------------------------------------------
diff --git a/cpp/src/connect_config_dummy.cpp b/cpp/src/connect_config_dummy.cpp
new file mode 100644
index 0000000..4d46726
--- /dev/null
+++ b/cpp/src/connect_config_dummy.cpp
@@ -0,0 +1,31 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#include <proton/connect_config.hpp>
+#include <proton/error.hpp>
+
+namespace proton {
+namespace connect_config {
+namespace { const error nope("connection configuration is not supported"); }
+
+std::string default_file() { throw nope; }
+std::string parse(std::istream& is, connection_options& opts)  { throw nope; }
+std::string parse_default(proton::connection_options&)  { throw nope; }
+
+}} // namespace proton::connect_config

http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/b164d99c/cpp/src/connect_config_test.cpp
----------------------------------------------------------------------
diff --git a/cpp/src/connect_config_test.cpp b/cpp/src/connect_config_test.cpp
new file mode 100644
index 0000000..17f0c4b
--- /dev/null
+++ b/cpp/src/connect_config_test.cpp
@@ -0,0 +1,142 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#include "test_bits.hpp"
+
+#include "proton/connect_config.hpp"
+#include "proton/connection.hpp"
+#include "proton/connection_options.hpp"
+#include "proton/container.hpp"
+#include "proton/error_condition.hpp"
+#include "proton/listener.hpp"
+#include "proton/messaging_handler.hpp"
+#include "proton/transport.hpp"
+
+#include <sstream>
+#include <fstream>
+#include <cstdio>
+
+#include <stdlib.h>
+
+namespace {
+
+using namespace std;
+using namespace proton;
+using proton::error_condition;
+
+string configure(connection_options& opts, const string& config) {
+    istringstream is(config);
+    return connect_config::parse(is, opts);
+}
+
+void test_default_file() {
+    // Default file locations in order of preference.
+    ::setenv("MESSAGING_CONNECT_FILE", "environment", 1);
+    ofstream("connect.json") << "{ \"host\": \"current\" }" << endl;
+    ::setenv("HOME", "testdata", 1);
+    ofstream("testdata/.config/messaging/connect.json") << "{ \"host\": \".config\" }" << endl;
+    ASSERT_EQUAL("environment", connect_config::default_file());
+    ::unsetenv("MESSAGING_CONNECT_FILE");
+    ASSERT_EQUAL("connect.json", connect_config::default_file());
+    remove("connect.json");
+    ASSERT_EQUAL("testdata/.config/messaging/connect.json", connect_config::default_file());
+    remove("testdata/.config/messaging/connect.json");
+
+    // We can't fully test prefix and /etc locations, we have no control.
+    try {
+        ASSERT_SUBSTRING("/etc/messaging", connect_config::default_file());
+    } catch (...) {}            // OK if not there
+}
+
+void test_addr() {
+    connection_options opts;
+    ASSERT_EQUAL("foo:bar", configure(opts, "{ \"host\":\"foo\", \"port\":\"bar\" }"));
+    ASSERT_EQUAL("foo:1234", configure(opts, "{ \"host\":\"foo\", \"port\":\"1234\" }"));
+    ASSERT_EQUAL(":amqps", configure(opts, "{}"));
+    ASSERT_EQUAL(":amqp", configure(opts, "{\"scheme\":\"amqp\"}"));
+    ASSERT_EQUAL("foo:bar", configure(opts, "{ \"host\":\"foo\", /* inline comment */\"port\":\"bar\" // end of line comment\n}"));
+
+    ASSERT_THROWS_MSG(error, "'scheme' must be", configure(opts, "{\"scheme\":\"bad\"}"));
+    ASSERT_THROWS_MSG(error, "'scheme' expected string, found boolean", configure(opts, "{\"scheme\":true}"));
+    ASSERT_THROWS_MSG(error, "'port' expected string or integer, found boolean", configure(opts, "{\"port\":true}"));
+    ASSERT_THROWS_MSG(error, "'host' expected string, found boolean", configure(opts, "{\"host\":true}"));
+}
+
+class test_handler : public messaging_handler {
+  protected:
+    string config_;
+    listener listener_;
+    bool opened_;
+    proton::error_condition error_;
+
+  public:
+
+    void on_container_start(container& c) PN_CPP_OVERRIDE {
+        listener_ = c.listen("//:0");
+    }
+
+    virtual void check_connection(connection& c) {}
+
+    void on_connection_open(connection& c) PN_CPP_OVERRIDE {
+        if (!c.active()) {      // Server side
+            opened_ = true;
+            check_connection(c);
+            listener_.stop();
+            c.close();
+        }
+    }
+
+    void on_error(const error_condition& e) PN_CPP_OVERRIDE {
+        FAIL("unexpected error " << e);
+    }
+
+    void run(const string& config) {
+        config_ = config;
+        container(*this).run();
+    }
+};
+
+class test_default_connect : public test_handler {
+  public:
+
+    void on_container_start(container& c) PN_CPP_OVERRIDE {
+        test_handler::on_container_start(c);
+        ofstream os("connect.json");
+        ASSERT(os << "{ \"port\": " << listener_.port() << "}" << endl);
+        os.close();
+        c.connect();
+    }
+
+    void run() {
+        container(*this).run();
+        ASSERT(opened_);
+        ASSERT(!error_);
+    }
+};
+
+} // namespace
+
+
+int main(int argc, char** argv) {
+    int failed = 0;
+    RUN_ARGV_TEST(failed, test_default_file());
+    RUN_ARGV_TEST(failed, test_addr());
+    RUN_ARGV_TEST(failed, test_default_connect().run());
+    return failed;
+}

http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/b164d99c/cpp/src/connection_options.cpp
----------------------------------------------------------------------
diff --git a/cpp/src/connection_options.cpp b/cpp/src/connection_options.cpp
index 1903536..fdc770e 100644
--- a/cpp/src/connection_options.cpp
+++ b/cpp/src/connection_options.cpp
@@ -217,4 +217,5 @@ void connection_options::apply_unbound_client(pn_transport_t *t) const { impl_->
 void connection_options::apply_unbound_server(pn_transport_t *t) const { impl_->apply_sasl(t); impl_->apply_ssl(t, false); impl_->apply_transport(t); }
 
 messaging_handler* connection_options::handler() const { return impl_->handler.value; }
+
 } // namespace proton

http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/b164d99c/cpp/src/container.cpp
----------------------------------------------------------------------
diff --git a/cpp/src/container.cpp b/cpp/src/container.cpp
index cfc0196..013fb4b 100644
--- a/cpp/src/container.cpp
+++ b/cpp/src/container.cpp
@@ -83,6 +83,10 @@ returned<connection> container::connect(const std::string& url, const connection
     return impl_->connect(url, opts);
 }
 
+returned<connection> container::connect() {
+    return impl_->connect();
+}
+
 listener container::listen(const std::string& url, listen_handler& l) { return impl_->listen(url, l); }
 
 void container::run() { impl_->run(1); }

http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/b164d99c/cpp/src/proactor_container_impl.cpp
----------------------------------------------------------------------
diff --git a/cpp/src/proactor_container_impl.cpp b/cpp/src/proactor_container_impl.cpp
index 01aea77..e0057cd 100644
--- a/cpp/src/proactor_container_impl.cpp
+++ b/cpp/src/proactor_container_impl.cpp
@@ -20,6 +20,7 @@
 #include "proactor_container_impl.hpp"
 #include "proactor_work_queue_impl.hpp"
 
+#include "proton/connect_config.hpp"
 #include "proton/error_condition.hpp"
 #include "proton/listener.hpp"
 #include "proton/listen_handler.hpp"
@@ -350,6 +351,12 @@ returned<connection> container::impl::connect(
     return make_returned<proton::connection>(pnc);
 }
 
+returned<connection> container::impl::connect() {
+    connection_options opts;
+    std::string addr = connect_config::parse_default(opts);
+    return connect(addr, opts);
+}
+
 returned<sender> container::impl::open_sender(const std::string &urlstr, const proton::sender_options &o1, const connection_options &o2)
 {
     proton::url url(urlstr);

http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/b164d99c/cpp/src/proactor_container_impl.hpp
----------------------------------------------------------------------
diff --git a/cpp/src/proactor_container_impl.hpp b/cpp/src/proactor_container_impl.hpp
index dcc9381..912a036 100644
--- a/cpp/src/proactor_container_impl.hpp
+++ b/cpp/src/proactor_container_impl.hpp
@@ -71,6 +71,7 @@ class container::impl {
     impl(container& c, const std::string& id, messaging_handler* = 0);
     ~impl();
     std::string id() const { return id_; }
+    returned<connection> connect();
     returned<connection> connect(const std::string&, const connection_options&);
     returned<sender> open_sender(
         const std::string&, const proton::sender_options &, const connection_options &);

http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/b164d99c/cpp/src/test_bits.hpp
----------------------------------------------------------------------
diff --git a/cpp/src/test_bits.hpp b/cpp/src/test_bits.hpp
index 28271f0..50ea2ee 100644
--- a/cpp/src/test_bits.hpp
+++ b/cpp/src/test_bits.hpp
@@ -52,6 +52,11 @@ inline void assert_equalish(T want, T got, T delta, const std::string& what)
         throw fail(MSG(what << " " << want << " !=~ " << got));
 }
 
+void assert_substring(const std::string& want, const std::string& got, const std::string& what) {
+    if (got.find(want) == std::string::npos)
+        throw fail(MSG(what << " '" << want << "' not found in '" << got << "'"));
+}
+
 #define FAIL_MSG(WHAT) (MSG(__FILE__ << ":" << __LINE__ << ": " << WHAT).str())
 #define FAIL(WHAT) throw test::fail(FAIL_MSG(WHAT))
 #define ASSERT(TEST) do { if (!(TEST)) FAIL("failed ASSERT(" #TEST ")"); } while(false)
@@ -59,7 +64,10 @@ inline void assert_equalish(T want, T got, T delta, const std::string& what)
     test::assert_equal((WANT), (GOT), FAIL_MSG("failed ASSERT_EQUAL(" #WANT ", " #GOT ")"))
 #define ASSERT_EQUALISH(WANT, GOT, DELTA) \
     test::assert_equalish((WANT), (GOT), (DELTA), FAIL_MSG("failed ASSERT_EQUALISH(" #WANT ", " #GOT ")"))
+#define ASSERT_SUBSTRING(WANT, GOT) \
+    test::assert_substring((WANT), (GOT), FAIL_MSG("failed ASSERT_SUBSTRING(" #WANT ", " #GOT ")"))
 #define ASSERT_THROWS(WANT, EXPR) do { try { EXPR; FAIL("Expected " #WANT); } catch(const WANT&) {} } while(0)
+#define ASSERT_THROWS_MSG(CLASS, MSG, EXPR) do { try { EXPR; FAIL("Expected " #CLASS); } catch(const CLASS& e) { ASSERT_SUBSTRING((MSG), e.what()); } } while(0)
 
 #define RUN_TEST(BAD_COUNT, TEST)                                       \
     do {                                                                \

http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/b164d99c/docs/connect_config.md
----------------------------------------------------------------------
diff --git a/docs/connect_config.md b/docs/connect_config.md
new file mode 100644
index 0000000..f0419cc
--- /dev/null
+++ b/docs/connect_config.md
@@ -0,0 +1,42 @@
+# Connection Configuration
+
+Proton clients can read default connection information from a
+configuration file.
+
+If the environment variable `MESSAGING_CONNECT_FILE` is set, it is the
+path to the file. Otherwise the client looks for a file named
+`connect.json` in the following locations, using the first one found:
+
+* Current working directory of client process.
+* `$HOME/.config/messaging/` - $HOME is user's home directory.
+* `$PREFIX/etc/messaging/` - $PREFIX is the prefix where the proton library is installed
+* `/etc/messaging/`
+
+The configuration file is in JSON object format. Comments are allowed,
+as defined by the [JavaScript Minifier](https://www.crockford.com/javascript/jsmin.html)
+
+The file format is as follows. Properties are shown with their default
+values, all properties are optional.
+
+    {
+      "scheme": "amqps",   // [string] "amqp" (no TLS) or "amqps"
+      "host": "",          // [string] DNS or IP address for connection. Defaults to local host.
+      "port": "amqps",     // [string] "amqp", "amqps" or port number. Defaults to value of 'scheme'.
+      "user": null,        // [string] Authentication user name
+      "password": null,    // [string] Authentication password
+
+      "sasl": {
+        "enable": true,         // [bool] Enable or disable SASL
+        "mechanisms": null,     // [list] List of allowed SASL mechanism names.
+                                // If null the library determines the default list.
+        "allow_insecure": false // [boolean] Allow mechanisms that send clear-text passwords
+      },
+
+      // Note: it is an error to have a "tls" object unless scheme="amqps"
+      "tls": {
+        "cert": null,   // [string] name of client certificate or database
+        "key": null     // [string] private key or identity for client certificate
+        "ca": null,     // [string] name of CA certificate or database
+        "verify": true, // [bool] if true, require a valid cert with matching host name
+      }
+    }

http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/b164d99c/tools/cmake/Modules/FindJsonCpp.cmake
----------------------------------------------------------------------
diff --git a/tools/cmake/Modules/FindJsonCpp.cmake b/tools/cmake/Modules/FindJsonCpp.cmake
new file mode 100644
index 0000000..083d3fc
--- /dev/null
+++ b/tools/cmake/Modules/FindJsonCpp.cmake
@@ -0,0 +1,70 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+#.rst
+# FindJsonCpp
+#----------
+#
+# Find jsoncpp include directories and libraries.
+#
+# Sets the following variables:
+#
+#   JsonCpp_FOUND            - True if headers and requested libraries were found
+#   JsonCpp_INCLUDE_DIRS     - JsonCpp include directories
+#   JsonCpp_LIBRARIES        - Link these to use jsoncpp.
+#
+# This module reads hints about search locations from variables::
+#   JSONCPP_ROOT             - Preferred installation prefix
+#   JSONCPP_INCLUDEDIR       - Preferred include directory e.g. <prefix>/include
+#   JSONCPP_LIBRARYDIR       - Preferred library directory e.g. <prefix>/lib
+
+find_package (PkgConfig)
+pkg_check_modules (PC_JsonCpp QUIET jsoncpp)
+
+find_library(JsonCpp_LIBRARY NAMES jsoncpp libjsoncpp
+  HINTS ${JSONCPP_LIBRARYDIR} ${JSONCPP_ROOT}/lib ${CMAKE_INSTALL_PREFIX}/lib
+  PATHS ${PC_JsonCpp_LIBRARY_DIRS})
+
+find_path(JsonCpp_INCLUDE_DIR NAMES json/json.h json/value.h
+  HINTS ${JSONCPP_INCLUDEDIR} ${JSONCPP_ROOT}/include ${CMAKE_INSTALL_PREFIX}/include
+  PATHS /usr/include ${PC_JsonCpp_INCLUDE_DIRS})
+
+set(JsonCpp_VERSION ${PC_JsonCpp_VERSION})
+
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args(JsonCpp
+  REQUIRED_VARS JsonCpp_LIBRARY JsonCpp_INCLUDE_DIR
+  VERSION_VAR JsonCpp_VERSION)
+
+if (JsonCpp_FOUND)
+  set(JsonCpp_INCLUDE_DIRS ${JsonCpp_INCLUDE_DIR})
+  set(JsonCpp_LIBRARIES ${JsonCpp_LIBRARY})
+
+  if (NOT TARGET JsonCpp::JsonCpp)
+    add_library(JsonCpp::JsonCpp UNKNOWN IMPORTED)
+    set_target_properties(JsonCpp::JsonCpp
+      PROPERTIES
+        IMPORTED_LOCATION "${JsonCpp_LIBRARY}"
+        INTERFACE_INCLUDE_DIRECTORIES "${JsonCpp_INCLUDE_DIR}"
+    )
+  endif ()
+
+endif ()
+
+mark_as_advanced (JsonCpp_LIBRARY JsonCpp_INCLUDE_DIR)


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@qpid.apache.org
For additional commands, e-mail: commits-help@qpid.apache.org