Skip to content
Snippets Groups Projects
RemoteJobManager.cpp 6.28 KiB
Newer Older
Miller, Ross's avatar
Miller, Ross committed
#include "MantidKernel/ConfigService.h"
#include "MantidKernel/Logger.h"
#include "MantidKernel/RemoteJobManager.h"

#include <Poco/Base64Encoder.h>
#include <Poco/Net/HTTPSClientSession.h>
#include <Poco/Net/HTTPRequest.h>
#include <Poco/Net/HTTPResponse.h>
#include <Poco/Net/HTTPCookie.h>
#include <Poco/Net/NameValueCollection.h>
#include <Poco/URI.h>

#include <Poco/DOM/Element.h>
#include <Poco/DOM/NodeList.h>
#include <Poco/DOM/Text.h>

#include <ostream>
#include <sstream>
#include <fstream>


namespace Mantid
{
namespace Kernel
{

// Get a reference to the logger
Logger& RemoteJobManager::g_log = Logger::get("RemoteJobManager");

RemoteJobManager::RemoteJobManager( const Poco::XML::Element* elem)
{
  m_displayName = elem->getAttribute("name");
  if (m_displayName.length() == 0)
  {
    g_log.error("Compute Resources must have a name attribute");
    throw std::runtime_error("Compute Resources must have a name attribute");
  }

  Poco::XML::NodeList* nl = elem->getElementsByTagName("baseURL");
  if (nl->length() != 1)
  {
    g_log.error("HTTP Compute Resources must have exactly one baseURL tag");
    throw std::runtime_error("HTTP Compute Resources must have exactly one baseURL tag");
  }
  else
  {
    nl = nl->item(0)->childNodes();
    if (nl->length() > 0)
    {
      Poco::XML::Text* txt = dynamic_cast<Poco::XML::Text*>(nl->item(0));
      if (txt)
      {
        m_serviceBaseUrl = txt->getData();

      }
    }
  }

  // Make sure this is always either NULL or a valid pointer.
  m_session = NULL;
}

std::istream & RemoteJobManager::httpGet( const std::string &path, const std::string &query_str,
                                          const std::string &username, const std::string &password)
{
  Poco::Net::HTTPRequest req;
  initGetRequest( req, path, query_str);

  if (username.length() > 0)
  {
    // Set the Authorization header (base64 encoded)
    std::ostringstream encodedAuth;
    Poco::Base64Encoder encoder( encodedAuth);
    encoder << username << ":" << password;
    encoder.close();
    req.setCredentials( "Basic", encodedAuth.str());
  }

  m_session->sendRequest( req);

  std::istream &respStream = m_session->receiveResponse( m_response);
  m_response.getCookies( m_cookies);

  return respStream;
}

std::istream & RemoteJobManager::httpPost(const std::string &path, const PostDataMap &postData,
                                          const std::string &username, const std::string &password)
{
  Poco::Net::HTTPRequest req;
  initPostRequest( req, path);

  if (username.length() > 0)
  {
    // Set the Authorization header (base64 encoded)
    std::ostringstream encodedAuth;
    Poco::Base64Encoder encoder( encodedAuth);
    encoder << username << ":" << password;
    encoder.close();
    req.setCredentials( "Basic", encodedAuth.str());
  }


  // We have to do a POST with multipart MIME encoding. MIME is rather picky about
  // how the parts are delimited. See RFC 2045 & 2046 for details.

  char httpLineEnd[3] = { 0x0d, 0x0a, 0x00 }; // HTTP uses CRLF for its line endings

  // boundary can be almost anything (again, see RFC 2046). The important part is that it
  // cannot appear anywhere in the actual data
  std::string boundary = "112233MantidHTTPBoundary44556677";
  std::string boundaryLine = "--" + boundary + httpLineEnd;
  std::string finalBoundaryLine = "--" + boundary + "--" + httpLineEnd;

  req.setContentType( "multipart/form-data; boundary=" + boundary);

  // Need to be able to specify the content length, so build up the post body here.
  std::ostringstream postBody;
  PostDataMap::const_iterator it = postData.begin();
  while (it != postData.end())
  {
    postBody << boundaryLine;
    postBody <<"Content-Disposition: form-data; name=\"" << (*it).first << "\"";
    postBody << httpLineEnd;
    postBody << (*it).second;
    postBody << httpLineEnd;
  }

  postBody << finalBoundaryLine;

  req.setContentLength( postBody.str().size());

  std::ostream &postStream = m_session->sendRequest( req);

  // upload the actual HTTP body
  postStream << postBody.rdbuf() << std::flush;

  std::istream &respStream = m_session->receiveResponse( m_response);
  m_response.getCookies( m_cookies);
  return respStream;
}


// Wrappers for a lot of the boilerplate code needed to perform an HTTPS GET or POST
void RemoteJobManager::initHTTPRequest( Poco::Net::HTTPRequest &req, const std::string &method,
                                            std::string extraPath, std::string queryString)
{
  // Set up the session object
  if (m_session)
  {
    delete m_session;
    m_session = NULL;
  }

  if (Poco::URI( m_serviceBaseUrl).getScheme() == "https")
  {
    // Create an HTTPS session
    // TODO: Notice that we've set the context to VERIFY_NONE. I think that means we're not checking the SSL certificate that the server
    // sends to us. That's BAD!!
    Poco::Net::Context::Ptr context = new Poco::Net::Context(Poco::Net::Context::CLIENT_USE, "", "", "", Poco::Net::Context::VERIFY_NONE, 9, false, "ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH");
     m_session = new Poco::Net::HTTPSClientSession( Poco::URI(m_serviceBaseUrl).getHost(), Poco::URI(m_serviceBaseUrl).getPort(), context);
  }
  else
  {
    // Create a regular HTTP client session.  (NOTE: Using unencrypted HTTP is a really bad idea! We'll be sending passwords in the clear!)
    m_session = new Poco::Net::HTTPClientSession( Poco::URI(m_serviceBaseUrl).getHost(), Poco::URI(m_serviceBaseUrl).getPort());
  }

  Poco::URI uri(m_serviceBaseUrl);
  std::string path = uri.getPath();
  // Path should be something like "/mws/rest", append extraPath to it.
  path += extraPath;

  uri.setPath( path);
  if (method == Poco::Net::HTTPRequest::HTTP_GET &&
      queryString.size() > 0)
  {
    uri.setQuery(queryString);
  }

  req.setVersion(Poco::Net::HTTPRequest::HTTP_1_1);
  req.setMethod( method);
  req.setURI(uri.toString());

  // Attach any cookies we've got from previous responses
  req.setCookies( getCookies());

  return;
}

// Converts the vector of HTTPCookie objects into a NameValueCollection
Poco::Net::NameValueCollection RemoteJobManager::getCookies()
{
  Poco::Net::NameValueCollection nvc;
  std::vector<Poco::Net::HTTPCookie>::const_iterator it = m_cookies.begin();
  while (it != m_cookies.end())
  {
    nvc.add( (*it).getName(), (*it).getValue());
    ++it;
  }
  return nvc;
}


} // end of namespace Kernel
} // end of namespace Mantid