/*************************************************************************** * GVOClient.cpp - VO client class * * ----------------------------------------------------------------------- * * copyright (C) 2013-2015 by Juergen Knoedlseder * * ----------------------------------------------------------------------- * * * * This program is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, either version 3 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * * * ***************************************************************************/ /** * @file GVOClient.cpp * @brief VO client class implementation * @author Juergen Knoedlseder */ /* __ Includes ___________________________________________________________ */ #ifdef HAVE_CONFIG_H #include #endif #include // std::getenv() function #include // std::fopen(), etc. functions #include // std::memset() function #include // close() function #include // getaddrinfo() function #include // socket(), connect() functions #include "GVOClient.hpp" #include "GVOHub.hpp" #include "GException.hpp" #include "GTools.hpp" /* __ Method name definitions ____________________________________________ */ #define G_FIND_HUB "GVOClient::find_hub()" #define G_REQUIRE_HUB "GVOClient::require_hub()" #define G_REGISTER_TO_HUB "GVOClient::register_to_hub()" /* __ Macros _____________________________________________________________ */ /* __ Coding definitions _________________________________________________ */ /* __ Debug definitions __________________________________________________ */ //#define G_SHOW_MESSAGE //!< Show posted and received messages /*========================================================================== = = = Constructors/destructors = = = ==========================================================================*/ /***********************************************************************//** * @brief Void constructor ***************************************************************************/ GVOClient::GVOClient(void) { // Initialise members init_members(); // Find Hub find_hub(); // Return return; } /***********************************************************************//** * @brief Copy constructor * * @param[in] client VO client. ***************************************************************************/ GVOClient::GVOClient(const GVOClient& client) { // Initialise members init_members(); // Copy members copy_members(client); // Return return; } /***********************************************************************//** * @brief Destructor ***************************************************************************/ GVOClient::~GVOClient(void) { // Free members free_members(); // Return return; } /*========================================================================== = = = Operators = = = ==========================================================================*/ /***********************************************************************//** * @brief Assignment operator * * @param[in] client VO client. * @return VO client. ***************************************************************************/ GVOClient& GVOClient::operator=(const GVOClient& client) { // Execute only if object is not identical if (this != &client) { // Free members free_members(); // Initialise members init_members(); // Copy members copy_members(client); } // endif: object was not identical // Return return *this; } /*========================================================================== = = = Public methods = = = ==========================================================================*/ /***********************************************************************//** * @brief Clear object. * * Reset object to a clean initial state. ***************************************************************************/ void GVOClient::clear(void) { // Free memory free_members(); // Initialise members init_members(); // Return return; } /***********************************************************************//** * @brief Clone object ***************************************************************************/ GVOClient* GVOClient::clone(void) const { // Clone client return new GVOClient(*this); } /***********************************************************************//** * @brief Register client at VO Hub * * Registers a client at the VO Hub. The method will search for a VO Hub, * launch it's own VO Hub if no Hub was found, and the register the client * at the Hub, including sending of metadata. ***************************************************************************/ void GVOClient::connect(void) { // Require a Hub require_hub(); // Continue only if we have a Hub if (has_hub()) { // Register to Hub register_to_hub(); // Send meta data send_metadata(); } // endif: we had a Hub // Return return; } /***********************************************************************//** * @brief Unregister client from VO Hub * * Disconnects the VO client from the VO Hub. The client is unregistered. ***************************************************************************/ void GVOClient::disconnect(void) { // Continue only if we have a Hub if (has_hub()) { // Unregister from Hub unregister_from_hub(); // Close socket if (m_socket != -1) { close(m_socket); m_socket = -1; } } // endif: we had a Hub // Return return; } /***********************************************************************//** * @brief Signals if client has VO Hub information * * @return True if client has VO Hub information, false otherwise. * * Checks if all mandatory VO Hub tokens are non-empty. ***************************************************************************/ bool GVOClient::has_hub(void) const { // Return Hub information status return (!m_secret.empty() && !m_hub_url.empty() && !m_version.empty()); } /***********************************************************************//** * @brief Signals if client is connected to VO Hub * * @return True if client is connected to VO Hub, false otherwise. * * Checks if the clients has non-empty client key, Hub ID and client ID. ***************************************************************************/ bool GVOClient::is_connected(void) const { // Return Hub information status return (!m_client_key.empty() && !m_hub_id.empty() && !m_client_id.empty()); } /***********************************************************************//** * @brief Ping VO Hub * * @return True if VO Hub is alive, false otherwise. ***************************************************************************/ bool GVOClient::ping_hub(void) const { // Initialise ping result bool result = false; // Declare request std::string request = ""; // Set request request.append("\n"); request.append("\n"); request.append(" samp.hub.ping\n"); request.append(" \n"); request.append(" \n"); request.append(" \n"); request.append("\n"); // Execute request GXml xml = execute(request); // If we got a valid response then set ping result flag to true if (response_is_valid(xml)) { result = true; } // Return ping result return result; } /***********************************************************************//** * @brief Shutdown VO Hub ***************************************************************************/ void GVOClient::shutdown_hub(void) const { // Declare request std::string request = ""; // Set request request.append("\n"); request.append("\n"); request.append(" samp.hub.shutdown\n"); request.append(" \n"); request.append(" \n"); request.append(" \n"); request.append("\n"); // Execute request execute(request); // Return return; } /***********************************************************************//** * @brief Execute function on server * * @param[in] request XML request string * @return XML response ***************************************************************************/ GXml GVOClient::execute(const std::string& request) const { // Initialise response GXml xml; // Connect to hub connect_to_hub(); // Continue only if connection has been established if (m_socket != -1) { // Post request post_string(request); // Receive response std::string response = receive_string(); // Find start of XML text size_t start = response.find("\n"); request.append("\n"); request.append(" samp.hub.register\n"); request.append(" \n"); request.append(" "+m_secret+"\n"); request.append(" \n"); request.append("\n"); // Execute request GXml xml = execute(request); // If no error occured, extract hub and client identifiers if (response_is_valid(xml)) { m_client_key = get_response_value(xml, "samp.private-key"); m_hub_id = get_response_value(xml, "samp.hub-id"); m_client_id = get_response_value(xml, "samp.self-id"); } // ... otherwise signal an error else { std::string msg = "Unable to connect to Hub (" + response_error_message(xml) + ", code=" + gammalib::str(response_error_code(xml)) + ")."; throw GException::runtime_error(G_REGISTER_TO_HUB, msg); } // Return return; } /***********************************************************************//** * @brief Unregister client from SAMP Hub ***************************************************************************/ void GVOClient::unregister_from_hub(void) { // Unregister only if we were registered if (is_connected()) { // Declare request std::string request; // Set metadata header request.append("\n"); request.append("\n"); request.append(" samp.hub.unregister\n"); request.append(" \n"); request.append(" "+m_client_key+"\n"); request.append(" \n"); request.append("\n"); // Execute request execute(request); } // endif: we were registered // Reset Hub and client identifiers m_client_key.clear(); m_hub_id.clear(); m_client_id.clear(); // Return return; } /***********************************************************************//** * @brief Send client metadata to SAMP Hub ***************************************************************************/ void GVOClient::send_metadata(void) const { // Declare request std::string request; // Set metadata header request.append("\n"); request.append("\n"); request.append("samp.hub.declareMetadata\n"); request.append("\n"); request.append(" "+m_client_key+"\n"); request.append(" \n"); // Set SAMP name request.append(" \n"); request.append(" samp.name\n"); request.append(" "+m_name+"\n"); request.append(" \n"); // Set SAMP description text request.append(" \n"); request.append(" samp.description.text\n"); request.append(" GammaLib client\n"); request.append(" \n"); // Set SAMP icon URL request.append(" \n"); request.append(" samp.icon.url\n"); request.append(" http://a.fsdn.com/allura/p/gammalib/icon\n"); request.append(" \n"); // Set author affiliation request.append(" \n"); request.append(" author.affiliation\n"); request.append(" IRAP, Toulouse, France\n"); request.append(" \n"); // Set author e-mail request.append(" \n"); request.append(" author.email\n"); request.append(" jurgen.knodlseder@irap.omp.eu\n"); request.append(" \n"); // Set author name request.append(" \n"); request.append(" author.name\n"); request.append(" Juergen Knoedlseder\n"); request.append(" \n"); // Set metadata trailer request.append(" \n"); request.append("\n"); request.append("\n"); // Execute request execute(request); // Return return; } /*========================================================================== = = = Low-level private methods = = = ==========================================================================*/ /***********************************************************************//** * @brief Connect to SAMP Hub * * Connects to Hub by creating a socket and connecting to this socket. The * method expects that a Hub has already been found. If no Hub has been found * (i.e. has_hub() returns false), the method does nothing. * * The method extracts the hostname and the port from the Hub endpoint URL. * If Hub connection fails, any created socket is closed. * * If the method is successful, m_socket will contain on exit a non-negative * number. If any failure occurs, m_socket will be set to -1. ***************************************************************************/ void GVOClient::connect_to_hub(void) const { // Close any existing socket if (m_socket != -1) { close(m_socket); m_socket = -1; } // Continue only if host and port information was found if (!m_hub_host.empty() && !m_hub_port.empty()) { // Set hints struct addrinfo hints; std::memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_INET; hints.ai_socktype = SOCK_STREAM; // Get server information struct addrinfo* servinfo; if (getaddrinfo(m_hub_host.c_str(), m_hub_port.c_str(), &hints, &servinfo) == 0) { // Loop through all the results and connect to the first // we can for (struct addrinfo* ptr = servinfo; ptr != NULL; ptr = ptr->ai_next) { // Create socket m_socket = socket(ptr->ai_family, ptr->ai_socktype, ptr->ai_protocol); // Connect to socket if socket is valid if (m_socket != -1) { if (::connect(m_socket, ptr->ai_addr, ptr->ai_addrlen) == -1) { close(m_socket); m_socket = -1; } else { // Socket connection successful, break now break; } } } // endfor: looped through all results } // endif: server information was valid } // endif: host and port information found // Return return; } /***********************************************************************//** * @brief Post string content to Hub * * @param[in] content String content to post * * Posts the content of a string to the Hub. * * The method does nothing if no Hub connection has been established. ***************************************************************************/ void GVOClient::post_string(const std::string& content) const { // Continue only if Hub connection has been established if (m_socket != -1) { // Determine content length int length = content.length(); // Set prefix std::string prefix = "POST /"+m_hub_path+" HTTP/1.0\n" "User-Agent: GammaLib\n" "Content-Type: text/xml\n" "Content-Length: "+gammalib::str(length)+"\n\n"; // Build post string std::string post = prefix + content; // Debug option: show posted message #if defined(G_SHOW_MESSAGE) std::cout << post << std::endl; #endif // Send content to socket bool done = false; do { int length = post.length(); int sent_length = send(m_socket, post.c_str(), length, 0); if (sent_length < length) { post = post.substr(sent_length, std::string::npos); } else { done = true; } } while (!done); } // endif: Hub connection had been established // Return return; } /***********************************************************************//** * @brief Receive string content from Hub * * @return String received from Hub. * * Reads information sent by Hub into a string. * * The method does nothing if no Hub connection has been established. ***************************************************************************/ std::string GVOClient::receive_string(void) const { // Initialise empty string std::string result = ""; // Continue only if Hub connection has been established if (m_socket != -1) { // Define buffer char buffer[1001]; // Read buffer until it is empty int timeout = 2000; // Initial timeout is 2 sec int n = 0; do { n = gammalib::recv(m_socket, buffer, 1000, 0, timeout); if (n > 0) { buffer[n] = '\0'; result.append(std::string(buffer)); } timeout = 10; // The timeout now is 0.01 sec } while (n > 0); // Debug option: show received message #if defined(G_SHOW_MESSAGE) std::cout << result << std::endl; #endif } // endif: Hub connection had been established // Return result return result; } /***********************************************************************//** * @brief Returns value for a SAMP Hub response parameter * * @param[in] xml Hub response XML document. * @param[in] name Parameter name. * @return Parameter value. * * Returns value for a SAMP Hub response parameter. If the specified * parameter was not found or if the response structure is not compliant, * an empty string is returned. ***************************************************************************/ std::string GVOClient::get_response_value(const GXml& xml, const std::string& name) const { // Declare value std::string value = ""; // Search for value of specified member const GXmlNode* node = xml.element("methodResponse > params > param > value > struct"); if (node != NULL) { int num = node->elements("member"); for (int i = 0; i < num; ++i) { const GXmlNode* member = node->element("member", i); std::string one_name; std::string one_value; get_name_value_pair(member, one_name, one_value); if (one_name == name) { value = one_value; break; } } } // Return value return value; } /***********************************************************************//** * @brief Extract name / value pair from XML node * * @param[in] node Pointer to XML node. * @param[out] name Name string. * @param[out] value Value string. * * Extracts a name / value pair from a XML node. If the XML node pointer is * NULL, the name and value strings will be empty. ***************************************************************************/ void GVOClient::get_name_value_pair(const GXmlNode* node, std::string& name, std::string& value) const { // Clear name and value strings name.clear(); value.clear(); // Continue only if node is valid if (node != NULL) { // Get name node and extract text content const GXmlNode* ptr = node->element("name", 0); if (ptr != NULL) { const GXmlText* text = static_cast((*ptr)[0]); if (text != NULL) { name = text->text(); } } // Get value node and extract text content ptr = node->element("value", 0); if (ptr != NULL) { const GXmlText* text = static_cast((*ptr)[0]); if (text != NULL) { value = text->text(); } } } // Return return; } /***********************************************************************//** * @brief Returns SAMP Hub lockfile URL * * @return SAMP Hub lockfile URL (empty if no lockfile was found). * * Implements IVOA standard REC-SAMP-1.3-20120411. ***************************************************************************/ std::string GVOClient::get_hub_lockfile(void) const { // Initialise result std::string url = ""; // Check for existence of the SAMP_HUB environment variable first char* hub_ptr = std::getenv("SAMP_HUB"); if (hub_ptr != NULL) { // Check for mandatory std-lockurl: prefix (no other prefix is // supported so far) std::string lockurl = std::string(hub_ptr); if (lockurl.compare(0, 12, "std-lockurl:") == 0) { // Extract URL url = lockurl.substr(12, std::string::npos); } // endif: std-lockurl: prefix found } // endif: SAMP_HUB environment variable found // ... otherwise the lockfile should be $HOME/.samp else { // Get user's HOME directory path as the prefix of the full // path. If the HOME environment variable is not set we // expect that .samp is in the local directory. This is non // standard, but prevents for creating an exception here. std::string prefix = ""; char* home_ptr = std::getenv("HOME"); if (home_ptr != NULL) { prefix = std::string(home_ptr) + "/"; } // Set filename url = prefix + ".samp"; } // endelse: no SAMP_HUB environment variable found // Return URL return url; } /***********************************************************************//** * @brief Checks if response is valid * * @return True if response is valid, false otherwise. ***************************************************************************/ bool GVOClient::response_is_valid(const GXml& xml) const { // Initialise validity flag bool valid = true; // Check for data const GXmlNode* node = xml.element("methodResponse > params > param > value"); if (node == NULL) { valid = false; } // Return validity return valid; } /***********************************************************************//** * @brief Return response error code * * @return Response error core (0 if no error occured). ***************************************************************************/ int GVOClient::response_error_code(const GXml& xml) const { // Initialise error code int error_code = 0; // Extract error code const GXmlNode* node = xml.element("methodResponse > fault > value > struct"); if (node != NULL) { const GXmlNode* code = node->element("member[0] > value > int"); if (code != NULL) { error_code = gammalib::toint(static_cast((*code)[0])->text()); } } // Return error code return error_code; } /***********************************************************************//** * @brief Return response error message * * @return Response error message (blank if no error occured). ***************************************************************************/ std::string GVOClient::response_error_message(const GXml& xml) const { // Initialise error message std::string error_message; // Extract error message const GXmlNode* node = xml.element("methodResponse > fault > value > struct"); if (node != NULL) { const GXmlNode* msg = node->element("member[1] > value"); if (msg != NULL) { error_message = static_cast((*msg)[0])->text(); } } // Return error message return error_message; }