Add first work package for REST API implementation
[anna.git] / example / diameter / launcher / MyHandler.cpp
index 6638fc3..ed49f4f 100644 (file)
 #include <string>
 #include <sstream>
 #include <iomanip>
+#include <stdlib.h>
 
 // Project
 #include <anna/http/Request.hpp>
+#include <anna/core/functions.hpp>
 
 // Process
 #include <MyHandler.hpp>
 #include <Launcher.hpp>
 #include <EventOperation.hpp>
 
+void MyHandler::splitURI(const std::string &uri, std::string & operation, std::string & param1, std::string & param2) const {
+
+  std::string::size_type slash_pos = uri.find("/", 1u);
+  if (slash_pos == std::string::npos) {
+    operation = uri.substr(0);
+  }
+  else {
+    operation = uri.substr(0, slash_pos);
+    param1 = uri.substr(slash_pos + 1);
+
+    std::string::size_type slash2_pos = uri.find("/", slash_pos + 1);
+    if (slash2_pos != std::string::npos) {
+      param1 = uri.substr(slash_pos + 1, slash2_pos - slash_pos - 1);
+      param2 = uri.substr(slash2_pos + 1);
+    }
+  }
+}
+
+void MyHandler::sendResponse(anna::comm::ClientSocket& clientSocket, anna::http::Response *response)
+{
+  try {
+    clientSocket.send(*response);
+  } catch(Exception& ex) {
+    ex.trace();
+  }
+}
 
 void MyHandler::evRequest(anna::comm::ClientSocket& clientSocket, const anna::http::Request& request)
 throw(anna::RuntimeException) {
-  const anna::DataBlock& body = request.getBody();
-
-  if(body.getSize() == 0)
-    throw anna::RuntimeException("Missing operation parameters on HTTP request", ANNA_FILE_LOCATION);
 
-  LOGINFORMATION(
-    std::string msg("Received body: ");
-    msg += anna::functions::asString(body);
-    anna::Logger::information(msg, ANNA_FILE_LOCATION);
-  );
+  const anna::DataBlock& body = request.getBody();
+  anna::http::Method::Type::_v method = request.getMethod();
+  std::string uri = request.getURI();
+  bool isGET = (method == anna::http::Method::Type::Get);
+  bool isPOST = (method == anna::http::Method::Type::Post);
   std::string body_content;
-  body_content.assign(body.getData(), body.getSize());
-  auto json_body = nlohmann::json::parse(body_content);
+  nlohmann::json json_body;
+  std::stringstream ss;
 
-  // Operation:
-  std::string response_content;
+  anna::http::Response* response = allocateResponse();
+  response->setStatusCode(200);
 
-  bool opOk{};
+  if (isGET) {
+    if(body.getSize() != 0)
+       response->setStatusCode(400); // bad request
+  }
+  else if (isPOST) {
+    if(body.getSize() != 0) {
+      body_content.assign(body.getData(), body.getSize()); // assign body content:
 
-  try {
-    Launcher& my_app = static_cast <Launcher&>(anna::app::functions::getApp());
-    opOk = my_app.eventOperation(EventOperation::json2piped(json_body), response_content);
-  } catch(RuntimeException &ex) {
-    ex.trace();
-    opOk = false;
+      try {
+        json_body = nlohmann::json::parse(body_content);
+        LOGINFORMATION(
+          std::string msg("Json body received:\n\n");
+          msg += json_body.dump(4); // pretty print json body
+          anna::Logger::information(msg, ANNA_FILE_LOCATION);
+        );
+      }
+      catch (nlohmann::json::parse_error& e)
+      {
+        ss << "Json body parse error: " << e.what() << '\n'
+           << "exception id: " << e.id << '\n'
+           << "byte position of error: " << e.byte << std::endl;
+        anna::Logger::error(ss.str(), ANNA_FILE_LOCATION);
+      }
+    }
+  }
+  else {
+       response->setStatusCode(405); // method not allowed
   }
 
-  anna::http::Response* response = allocateResponse();
-  response->setStatusCode(200);  // http://en.wikipedia.org/wiki/List_of_HTTP_status_codes
-
-// EXAMPLES TO SET HEADERS:
-//   response->find(anna::http::Header::Type::Date)->setValue("Mon, 30 Jan 2006 14:36:18 GMT");
-//   anna::http::Header* keepAlive = response->find("Keep-Alive");
-//
-//   if (keepAlive == NULL)
-//      keepAlive = response->createHeader("Keep-Alive");
-//
-//   keepAlive->setValue("Verificacion del cambio 1.0.7");
-
-  // Content-Type header:
-  anna::http::Header* contentType = response->find("Content-Type");
-  if (contentType == NULL)
-    contentType = response->createHeader("Content-Type");
+  //////////////////////
+  // Prepare response //
+  //////////////////////
+
+  // Headers:
+  http::Header* contentType =response->find(http::Header::Type::ContentType);
+  if(contentType == NULL)
+    contentType = response->createHeader(http::Header::Type::ContentType);
   contentType->setValue("application/json");
 
-  // Json Body for response:
-  std::stringstream ss;
+  // Body:
+  std::string response_content = "Internal error (check ADML traces): ";
+  bool success = false;
+  if (isGET) {
+    success = doGET(uri, response_content);
+  }
+  else if (isPOST) {
+    try {
+      success = doPOST(uri, json_body, response_content);
+    }
+    catch(anna::RuntimeException &ex) {
+      ex.trace();
+      LOGINFORMATION(anna::Logger::information("XXXXXXXXXXXX EXCEPCION PENDIENTE DE CATCHEAR ABAJO !!! XXXXXXXXXXXXXXXXX", ANNA_FILE_LOCATION));
+    }
+  }
+
+  ss << R"({ "success":")" << (success ? "true":"false")
+     << R"(", "response": )" << std::quoted(response_content);
+  ss << R"( })";
 
-  ss << R"({ "result":")" << (opOk ? "true":"false") << R"(", "data": )" << std::quoted(response_content) << R"( })";
   anna::DataBlock db_content(true);
   db_content = ss.str();
   response->setBody(db_content);
 
-  try {
-    clientSocket.send(*response);
-  } catch(Exception& ex) {
-    ex.trace();
+  sendResponse(clientSocket, response);
+}
+
+bool MyHandler::doGET(const std::string &uri, std::string &response) {
+
+  bool result = false;
+
+  EventOperation eop(true /* is HTTP */);
+
+  // Snapshots
+  if (uri == "/show-oam") {
+    result = eop.show_oam(response);
+  }
+  else if (uri == "/show-stats") {
+    result = eop.show_stats(response);
+  }
+
+  return result;
+}
+
+bool MyHandler::doPOST(const std::string &uri, const nlohmann::json &j, std::string &response) {
+
+  bool result = false;
+
+  EventOperation eop(true /* is HTTP */);
+
+  std::string opType{}, param1{}, param2{};
+  splitURI(uri, opType, param1, param2);
+
+  // Node management
+  if (opType == "/node") {
+    auto it = j.find("name");
+    if (it != j.end())
+      result = eop.node(response, *it);
+    else
+      response += "missing 'name' string field";
+  }
+  else if (opType == "/node-auto") {
+    result = eop.node_auto(response);
+  }
+
+  // Parsing operations
+  else if (opType == "/code") {
+    auto it = j.find("diameterJson");
+    if (it != j.end())
+      result = eop.code(response, it->dump(4)); // get the object as string (always indentation = 4)
+    else
+      response += "missing 'diameterJson' object field";
+  }
+  else if (opType == "/decode") {
+    auto it = j.find("diameterHex");
+    if (it != j.end())
+      result = eop.decode(response, *it);
+    else
+      response += "missing 'diameterHex' string field";
+  }
+  else if (opType == "/loadmsg") {
+    auto it = j.find("diameterJson");
+    if (it != j.end())
+      result = eop.loadmsg(response, it->dump(4)); // get the object as string (always indentation = 4)
+    else
+      response += "missing 'diameterJson' object field";
+  }
+
+  // Hot changes
+  else if (opType == "/services") {
+    auto it = j.find("servicesJson");
+    if (it != j.end()) {
+      result = eop.services(response, it->dump(4)); // get the object as string (always indentation = 4)
+    }
+    else
+      response += "missing 'servicesJson' object field";
+  }
+  else if (opType == "/diameterServerSessions") {
+    auto it = j.find("sessions");
+    if (it != j.end())
+      result = eop.diameterServerSessions(response, it->get<int>());
+    else
+      response += "missing 'session' integer field";
+  }
+  else if (opType == "/change-dir") {
+    auto it = j.find("directory");
+    std::string directory = (it != j.end()) ? *it : ""; // default is: restore initial directory
+    result = eop.change_dir(response, directory);
+  }
+
+  // Client sessions visibility
+  else if (opType == "/visibility") {
+    auto it = j.find("action");
+    if (it != j.end()) {
+      std::string action = *it;
+
+      it = j.find("addressPort");
+      std::string addressPort = (it != j.end()) ? *it : "";
+
+      it = j.find("socket");
+      int socket = (it != j.end()) ? it->get<int>() : -1;
+
+      result = eop.visibility(response, action, addressPort, socket);
+    }
+    else
+      response += "missing 'action' string field";
+  }
+
+  // Snapshots
+  else if (opType == "/collect") {
+    result = eop.collect(response);
   }
+  else if (opType == "/context") {
+    auto it = j.find("targetFile");
+    std::string targetFile = (it != j.end()) ? *it : "";
+    result = eop.context(response, targetFile);
+  }
+  else if (opType == "/forceCountersRecord") {
+    result = eop.forceCountersRecord(response);
+  }
+  else if (opType == "/log-statistics-samples") {
+    auto it = j.find("list");
+    if (it != j.end())
+      result = eop.log_statistics_samples(response, *it);
+    else
+      response += "missing 'list' string field";
+  }
+
+  // Flow operations
+  else if ((opType == "/sendmsg2e")||(opType == "/sendmsg2c")) {
+    auto it = j.find("diameterJson");
+    if (it != j.end())
+      if (opType == "/sendmsg2e")
+        result = eop.sendmsg2e(response, it->dump(4)); // get the object as string (always indentation = 4)
+      else
+        result = eop.sendmsg2c(response, it->dump(4)); // get the object as string (always indentation = 4)
+    else
+      response += "missing 'diameterJson' object field";
+  }
+  else if ((opType == "/answermsg2e")||(opType == "/answermsg2c")) {
+    auto itJ = j.find("diameterJson");
+    auto itA = j.find("action");
+    bool hasJ = (itJ != j.end());
+    bool hasA = (itA != j.end());
+
+    if (hasJ != hasA) { // XOR
+
+      if (opType == "/answermsg2e") {
+        if (hasJ)
+          result = eop.answermsg2e(response, itJ->dump(4)); // get the object as string (always indentation = 4)
+        else
+          result = eop.answermsg2e_action(response, *itA);
+      }
+      else {
+        if (hasJ)
+          result = eop.answermsg2c(response, itJ->dump(4)); // get the object as string (always indentation = 4)
+        else
+          result = eop.answermsg2c_action(response, *itA);
+      }
+    }
+    else
+      response += "missing 'diameterJson' object or 'action' string field (only one accepted)";
+  }
+  else if ((opType == "/sendhex2e")||(opType == "/sendhex2c")) {
+    auto it = j.find("diameterHex");
+    if (it != j.end())
+      if (opType == "/sendhex2e")
+        result = eop.sendhex2e(response, *it);
+      else
+        result = eop.sendhex2c(response, *it);
+    else
+      response += "missing 'diameterHex' string field";
+  }
+
+  // FSM testing
+  // test_id__<command>
+  else if (opType == "/testid-description") {
+    auto it = j.find("description");
+    if (it != j.end())
+      result = eop.test_id__description(response, atoi(param1.c_str()), *it);
+    else
+      response += "missing 'description' string field";
+  }
+  else if (opType == "/testid-ip-limit") {
+    auto it = j.find("amount");
+    int amount = (it != j.end()) ? it->get<int>() : 1;
+    result = eop.test_id__ip_limit(response, atoi(param1.c_str()), amount);
+  }
+  else if (opType == "/testid-timeout") {
+    auto it = j.find("msecs");
+    if (it != j.end())
+      result = eop.test_id__timeout(response, atoi(param1.c_str()), it->get<int>());
+    else
+      response += "missing 'msecs' integer field";
+  }
+  else if ((opType == "/testid-sendmsg2e")||(opType == "/testid-sendmsg2c")) {
+    auto it = j.find("diameterJson");
+    if (it != j.end()) {
+
+      auto itS = j.find("stepNumber");
+      int stepNumber = (itS != j.end()) ? itS->get<int>() : -1;
+
+      if (opType == "/testid-sendmsg2e")
+        result = eop.test_id__sendmsg2e(response, atoi(param1.c_str()), it->dump(4), stepNumber); // get the object as string (always indentation = 4)
+      else
+        result = eop.test_id__sendmsg2c(response, atoi(param1.c_str()), it->dump(4), stepNumber); // get the object as string (always indentation = 4)
+    }
+    else
+      response += "missing 'diameterJson' object field";
+  }
+  else if (opType == "/testid-delay") {
+    auto it = j.find("msecs");
+    if (it != j.end())
+      result = eop.test_id__delay(response, atoi(param1.c_str()), it->get<int>());
+    else
+      response += "missing 'msecs' integer field";
+  }
+  else if (opType == "/testid-sh-command") {
+    auto it = j.find("script");
+    if (it != j.end())
+      result = eop.test_id__sh_command(response, atoi(param1.c_str()), *it);
+    else
+      response += "missing 'script' string field";
+  }
+  else if ((opType == "/testid-waitfe-hex")||(opType == "/testid-waitfc-hex")) {
+    auto it = j.find("hex");
+    if (it != j.end()) {
+
+      auto itS = j.find("strict");
+      bool strict = (itS != j.end()) ? (*itS == "true") : false;
+
+      if (opType == "/testid-waitfe-hex")
+        result = eop.test_id__waitfe_hex(response, atoi(param1.c_str()), *it, strict);
+      else
+        result = eop.test_id__waitfc_hex(response, atoi(param1.c_str()), *it, strict);
+    }
+    else
+      response += "missing 'hex' string field";
+  }
+  else if ((opType == "/testid-waitfe-msg")||(opType == "/testid-waitfc-msg")) {
+    auto it = j.find("diameterJson");
+    if (it != j.end()) {
+
+      auto itS = j.find("strict");
+      bool strict = (itS != j.end()) ? (*itS == "true") : false;
+
+      if (opType == "/testid-waitfe-msg")
+        result = eop.test_id__waitfe_msg(response, atoi(param1.c_str()), it->dump(4), strict); // get the object as string (always indentation = 4)
+      else
+        result = eop.test_id__waitfc_msg(response, atoi(param1.c_str()), it->dump(4), strict); // get the object as string (always indentation = 4)
+    }
+    else
+      response += "missing 'diameterJson' object field";
+  }
+  else if ((opType == "/testid-waitfe")||(opType == "/testid-waitfc")) {
+    auto it = j.find("condition");
+    if (it != j.end()) {
+      // [code]|[bitR]|[hopByHop]|[applicationId]|[sessionId]|[resultCode]|[msisdn]|[imsi]|[serviceContextId]
+      auto it_code = it->find("code");
+      auto it_bitR = it->find("bitR");
+      auto it_hopByHop = it->find("hopByHop");
+      auto it_applicationId = it->find("applicationId");
+      auto it_sessionId = it->find("sessionId");
+      auto it_resultCode = it->find("resultCode");
+      auto it_msisdn = it->find("msisdn");
+      auto it_imsi = it->find("imsi");
+      auto it_serviceContextId = it->find("serviceContextId");
+
+      std::string condition;
+      condition += (it_code != it->end()) ? *it_code : ""; condition += "|";
+      condition += (it_bitR != it->end()) ? *it_bitR : ""; condition += "|";
+      condition += (it_hopByHop != it->end()) ? *it_hopByHop : ""; condition += "|";
+      condition += (it_applicationId != it->end()) ? *it_applicationId : ""; condition += "|";
+      condition += (it_sessionId != it->end()) ? *it_sessionId : ""; condition += "|";
+      condition += (it_resultCode != it->end()) ? *it_resultCode : ""; condition += "|";
+      condition += (it_msisdn != it->end()) ? *it_msisdn : ""; condition += "|";
+      condition += (it_imsi != it->end()) ? *it_imsi : ""; condition += "|";
+      condition += (it_serviceContextId != it->end()) ? *it_serviceContextId : "";
+
+      if (opType == "/testid-waitfe")
+        result = eop.test_id__waitfe(response, atoi(param1.c_str()), condition);
+      else
+        result = eop.test_id__waitfc(response, atoi(param1.c_str()), condition);
+    }
+    else
+      response += "missing 'condition' object field";
+  }
+
+  // Testcases execution
+  // test__<command>
+  else if (opType == "/test-ttps") {
+    auto it = j.find("amount");
+    if (it != j.end())
+      result = eop.test__ttps(response, it->get<int>());
+    else
+      response += "missing 'amount' integer field";
+  }
+  else if (opType == "/test-next") {
+    auto it = j.find("syncAmount");
+    int syncAmount = (it != j.end()) ? it->get<int>() : 1;
+    result = eop.test__next(response, syncAmount);
+  }
+  else if (opType == "/test-ip-limit") {
+    auto it = j.find("amount");
+    int amount = (it != j.end()) ? it->get<int>() : -2; // default is: show current ip-limit and in-progress test cases amount
+    result = eop.test__ip_limit(response, amount);
+  }
+  else if (opType == "/test-goto") {
+    auto it = j.find("id");
+    if (it != j.end())
+      result = eop.test__goto(response, it->get<int>());
+    else
+      response += "missing 'id' integer field";
+  }
+  else if (opType == "/test-run") {
+    auto it = j.find("id");
+    if (it != j.end())
+      result = eop.test__run(response, it->get<int>());
+    else
+      response += "missing 'id' integer field";
+  }
+  else if (opType == "/test-look") {
+    auto it = j.find("id");
+    int id = (it != j.end()) ? it->get<int>() : -1; // default is: current
+    result = eop.test__look(response, id);
+  }
+  else if (opType == "/test-state") {
+    auto it = j.find("id");
+    int id = (it != j.end()) ? it->get<int>() : -1; // default is: current
+    result = eop.test__state(response, id);
+  }
+  else if (opType == "/test-interact") {
+    auto it = j.find("amount");
+    if (it != j.end()) {
+
+      auto itI = j.find("id");
+      int id = (itI != j.end()) ? itI->get<int>() : -1; // default is: current
+
+      result = eop.test__interact(response, it->get<int>(), id);
+    }
+    else
+      response += "missing 'amount' integer field";
+  }
+  else if (opType == "/test-reset") {
+    auto it = j.find("type");
+    if (it != j.end()) {
+
+      auto itI = j.find("id");
+      int id = (itI != j.end()) ? itI->get<int>() : -2; // default is: apply to all the tests
+
+      if ((*it == "soft") || (*it == "hard")) {
+        result = eop.test__reset(response, (*it == "soft"), id);
+      }
+      else
+        response += "invalid 'type' string field (allowed: soft|hard)";
+    }
+    else
+      response += "missing 'type' string field";
+  }
+  else if (opType == "/test-repeats") {
+    auto it = j.find("amount");
+    if (it != j.end())
+      result = eop.test__repeats(response, it->get<int>());
+    else
+      response += "missing 'amount' integer field";
+  }
+  else if (opType == "/test-auto-reset") {
+    auto it = j.find("type");
+    if (it != j.end()) {
+
+      if ((*it == "soft") || (*it == "hard")) {
+        result = eop.test__auto_reset(response, (*it == "soft"));
+      }
+      else
+        response += "invalid 'type' string field (allowed: soft|hard)";
+    }
+    else
+      response += "missing 'type' string field";
+  }
+  else if (opType == "/test-initialized") {
+    result = eop.test__initialized(response);
+  }
+  else if (opType == "/test-finished") {
+    result = eop.test__finished(response);
+  }
+  else if (opType == "/test-clear") {
+    result = eop.test__clear(response);
+  }
+  else if (opType == "/test-junit") {
+    auto it = j.find("targetFile");
+    std::string targetFile = (it != j.end()) ? *it : ""; // default is: get junit on response instead dumping on file
+    result = eop.test__junit(response, targetFile);
+  }
+  else if (opType == "/test-summary-counts") {
+    result = eop.test__summary_counts(response);
+  }
+  else if (opType == "/test-summary-states") {
+    result = eop.test__summary_states(response);
+  }
+  else if (opType == "/test-summary") {
+    result = eop.test__summary(response);
+  }
+  else if (opType == "/test-report") {
+    auto it = j.find("state");
+    std::string state = (it != j.end()) ? *it : "all"; // initialized|in-progress|failed|success|[all]|none
+
+    auto itA = j.find("action");
+    std::string action = (itA != j.end()) ? *itA : "enable"; // default is: enable
+
+    if ((action == "enable") || (action == "disable")) {
+      result = eop.test__report(response, state, (action == "enable"));
+    }
+    else
+      response += "invalid 'action' string field (allowed: enable|disable)";
+  }
+  else if ((opType == "/test-report-hex")||(opType == "/test-dump_stdout")) {
+
+    auto itA = j.find("action");
+    std::string action = (itA != j.end()) ? *itA : "enable"; // default is: enable
+
+    if ((action == "enable") || (action == "disable")) {
+      bool enable = (action == "enable");
+
+      if (opType == "/test-report-hex")
+        result = eop.test__report_hex(response, enable);
+      else
+        result = eop.test__dump_stdout(response, enable);
+    }
+    else
+      response += "invalid 'action' string field (allowed: enable|disable)";
+  }
+
+
+  return result;
 }
+