PROTON-2092: Use default options if connect.json not found
- Previous behaviour was to throw an error.
diff --git a/cpp/include/proton/connect_config.hpp b/cpp/include/proton/connect_config.hpp
index d9d2c0f..76ac7a9 100644
--- a/cpp/include/proton/connect_config.hpp
+++ b/cpp/include/proton/connect_config.hpp
@@ -1,5 +1,5 @@
-#ifndef CONNECT_CONFIG_HPP
-#define CONNECT_CONFIG_HPP
+#ifndef PROTON_CONNECT_CONFIG_HPP
+#define PROTON_CONNECT_CONFIG_HPP
/*
* Licensed to the Apache Software Foundation (ASF) under one
@@ -20,7 +20,10 @@
* under the License.
*/
-#include <proton/connection_options.hpp>
+#include "./internal/export.hpp"
+
+#include <iosfwd>
+#include <string>
namespace proton {
diff --git a/cpp/src/connect_config.cpp b/cpp/src/connect_config.cpp
index 6bb11c5..712796c 100644
--- a/cpp/src/connect_config.cpp
+++ b/cpp/src/connect_config.cpp
@@ -17,12 +17,13 @@
* under the License.
*/
+#include <proton/connect_config.hpp>
+
#include "msg.hpp"
-#include <proton/connect_config.hpp>
+#include <proton/connection_options.hpp>
#include <proton/error.hpp>
#include <proton/ssl.hpp>
-
#include <proton/version.h>
#include <json/value.h>
@@ -57,7 +58,6 @@
}
namespace proton {
-namespace connect_config {
namespace {
@@ -90,8 +90,6 @@
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 = get(objectValue, root, "sasl");
opts.sasl_enabled(get_bool(sasl, "enable", true));
@@ -112,7 +110,7 @@
if (i > 0) s << " ";
s << v.asString();
}
- opts.sasl_allowed_mechs(s.str().c_str());
+ opts.sasl_allowed_mechs(s.str());
break;
}
default:
@@ -149,43 +147,106 @@
} // namespace
+std::string parse(Value root, connection_options& opts) {
+ std::ostringstream addr;
+
+ validate(objectValue, root, "configuration");
+
+ string scheme = get_string(root, "scheme", "amqps");
+ if (scheme != "amqp" && scheme != "amqps") {
+ throw err(msg() << "'scheme' must be \"amqp\" or \"amqps\"");
+ }
+
+ string host = get_string(root, "host", "localhost");
+ opts.virtual_host(host.c_str());
+ addr << host << ":";
+
+ Value port = root.get("port", scheme);
+ switch (port.type()) {
+ case stringValue:
+ addr << port.asString(); break;
+ case intValue:
+ case uintValue:
+ addr << port.asUInt(); break;
+ default:
+ throw err(msg() << "'port' expected string or uint, found " << port.type());
+ }
+
+ Value user = get(stringValue, root, "user");
+ if (!user.isNull()) opts.user(user.asString());
+ Value password = get(stringValue, root, "password");
+ if (!password.isNull()) opts.password(password.asString());
+
+ parse_sasl(root, opts);
+ parse_tls(scheme, root, opts);
+
+ return addr.str();
+}
+
+std::ifstream config_file(std::string& name) {
+ const char *env_path = getenv(ENV_VAR.c_str());
+ const char *home = getenv(HOME.c_str());
+
+ // Try environment variable if set
+ std::ifstream f;
+ if (env_path) {
+ name = env_path;
+ f.open(name);
+ return f;
+ }
+
+ std::vector<std::string> path;
+ // current directory
+ path.push_back(FILE_NAME);
+ // $HOME/.config/messaging/FILE_NAME
+ if (home) path.push_back(home + HOME_FILE_NAME);
+ // INSTALL_PREFIX/etc/messaging/FILE_NAME
+ if (PN_INSTALL_PREFIX && *PN_INSTALL_PREFIX) path.push_back(PN_INSTALL_PREFIX + ETC_FILE_NAME);
+
+ for (unsigned i = 0; i < path.size(); ++i) {
+ name = path[i];
+ f.open(name);
+ if (f.good()) return f;
+ f.close();
+ }
+
+ /* /etc/messaging/FILE_NAME */
+ name = ETC_FILE_NAME;
+ f.open(name);
+ return f;
+}
+
+string apply_config(connection_options& opts) {
+ std::string name;
+ std::ifstream f = config_file(name);
+ try {
+ Value root;
+ if (f.good()) {
+ f >> root;
+ f.close();
+ } else {
+ std::istringstream is("{}");
+ is >> root;
+ }
+ return parse(root, opts);
+ } catch (const std::ifstream::failure& e) {
+ throw err(msg() << "io error parsing '" << name << "': " << e.what());
+ } catch (const std::exception& e) {
+ throw err(msg() << "error parsing '" << name << "': " << e.what());
+ } catch (...) {
+ throw err(msg() << "error parsing '" << name << "'");
+ }
+}
+
+// Legacy Unsettled API
+
+namespace connect_config {
+
std::string parse(std::istream& is, connection_options& opts) {
try {
- std::ostringstream addr;
-
Value root;
is >> root;
- validate(objectValue, root, "configuration");
-
- string scheme = get_string(root, "scheme", "amqps");
- if (scheme != "amqp" && scheme != "amqps") {
- throw err(msg() << "'scheme' must be \"amqp\" or \"amqps\"");
- }
-
- string host = get_string(root, "host", "localhost");
- opts.virtual_host(host.c_str());
- addr << host << ":";
-
- Value port = root.get("port", scheme);
- switch (port.type()) {
- case stringValue:
- addr << port.asString(); break;
- case intValue:
- case uintValue:
- addr << port.asUInt(); break;
- default:
- throw err(msg() << "'port' expected string or uint, found " << port.type());
- }
-
- Value user = get(stringValue, root, "user");
- if (!user.isNull()) opts.user(user.asString());
- Value password = get(stringValue, root, "password");
- if (!password.isNull()) opts.password(password.asString());
-
- parse_sasl(root, opts);
- parse_tls(scheme, root, opts);
-
- return addr.str();
+ return parse(root, opts);
} catch (const std::exception& e) {
throw err(e.what());
} catch (...) {
@@ -194,38 +255,27 @@
}
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;
+ std::string name;
+ std::ifstream f = config_file(name);
+ bool good = f.good();
+ f.close();
+ if (good || name!=ETC_FILE_NAME) {
+ return name;
}
- /* 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;
- throw err("no default configuration");
+ throw err("no default configuration, last tried: " + name);
}
string parse_default(connection_options& opts) {
- string name = default_file();
- std::ifstream f;
- try {
- f.exceptions(std::ifstream::badbit|std::ifstream::failbit);
- f.open(name.c_str());
- } catch (const std::exception& e) {
- throw err(msg() << "error opening '" << name << "': " << e.what());
+ std::string name;
+ std::ifstream f = config_file(name);
+ if (!f.good()) {
+ throw err("no default configuration, last tried: " + name);
}
try {
- return parse(f, opts);
+ Value root;
+ f >> root;
+ f.close();
+ return parse(root, opts);
} catch (const std::ifstream::failure& e) {
throw err(msg() << "io error parsing '" << name << "': " << e.what());
} catch (const std::exception& e) {
diff --git a/cpp/src/connect_config.hpp b/cpp/src/connect_config.hpp
new file mode 100644
index 0000000..6219887
--- /dev/null
+++ b/cpp/src/connect_config.hpp
@@ -0,0 +1,32 @@
+#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 <string>
+
+namespace proton {
+
+class connection_options;
+
+std::string apply_config(connection_options& opts);
+
+}
+ #endif
diff --git a/cpp/src/connect_config_test.cpp b/cpp/src/connect_config_test.cpp
index 81dec87..541dcdc 100644
--- a/cpp/src/connect_config_test.cpp
+++ b/cpp/src/connect_config_test.cpp
@@ -40,6 +40,12 @@
#include <cstdio>
#include <cstdlib>
+// Used to inspect items from the proton-c level of the API
+#include "proton/internal/object.hpp"
+#include "proton/returned.hpp"
+#include <proton/transport.h>
+#include <proton/connection.h>
+
// Windows has a different set of APIs for setting the environment
#ifdef _WIN32
#include <string.h>
@@ -205,7 +211,7 @@
}
};
-class test_default_connect : public test_handler {
+class test_almost_default_connect : public test_handler {
public:
void on_listener_start(container& c) PN_CPP_OVERRIDE {
@@ -220,6 +226,43 @@
}
};
+// Hack to use protected pn_object member of proton::object
+template <class P, class T>
+class internal_access_of: public P {
+
+public:
+ internal_access_of(const P& t) : P(t) {}
+ T* pn_object() { return proton::internal::object<T>::pn_object(); }
+};
+
+class test_default_connect : public test_handler {
+ public:
+
+ void on_listener_start(container& c) PN_CPP_OVERRIDE {
+ c.connect();
+ }
+
+ // on_transport_open will be called whatever happens to the connection whether it's
+ // connected or rejected due to error.
+ //
+ // We use a hacky way to get to the underlying C objects to check what's in them
+ void on_transport_open(proton::transport& transport) PN_CPP_OVERRIDE {
+ proton::connection connection = transport.connection();
+ internal_access_of<proton::connection, pn_connection_t> ic(connection);
+ pn_connection_t* c = ic.pn_object();
+ ASSERT_EQUAL(std::string("localhost"), pn_connection_get_hostname(c));
+ }
+
+ void on_transport_error(proton::transport& t) PN_CPP_OVERRIDE {
+ ASSERT_SUBSTRING("localhost:5671", t.error().description());
+ t.connection().container().stop();
+ }
+
+ void run() {
+ container(*this).run();
+ }
+};
+
class test_host_user_pass : public test_handler {
public:
@@ -358,6 +401,7 @@
RUN_ARGV_TEST(failed, test_addr());
RUN_ARGV_TEST(failed, test_invalid());
RUN_ARGV_TEST(failed, test_default_connect().run());
+ RUN_ARGV_TEST(failed, test_almost_default_connect().run());
bool have_sasl = pn_sasl_extended() && getenv("PN_SASL_CONFIG_PATH");
pn_ssl_domain_t *have_ssl = pn_ssl_domain(PN_SSL_MODE_SERVER);
diff --git a/cpp/src/proactor_container_impl.cpp b/cpp/src/proactor_container_impl.cpp
index 8cab1c5..0f02891 100644
--- a/cpp/src/proactor_container_impl.cpp
+++ b/cpp/src/proactor_container_impl.cpp
@@ -20,7 +20,7 @@
#include "proactor_container_impl.hpp"
#include "proactor_work_queue_impl.hpp"
-#include "proton/connect_config.hpp"
+#include "connect_config.hpp"
#include "proton/error_condition.hpp"
#include "proton/listen_handler.hpp"
#include "proton/listener.hpp"
@@ -383,7 +383,7 @@
returned<connection> container::impl::connect() {
connection_options opts;
- std::string addr = connect_config::parse_default(opts);
+ std::string addr = apply_config(opts);
return connect(addr, opts);
}