diff --git a/Modules/CppRestSdk/documentation/mitkCppRestSdk.dox b/Modules/CppRestSdk/documentation/mitkCppRestSdk.dox index 73a5fc37e0..8741d7c45f 100644 --- a/Modules/CppRestSdk/documentation/mitkCppRestSdk.dox +++ b/Modules/CppRestSdk/documentation/mitkCppRestSdk.dox @@ -1,194 +1,194 @@ /** \page CppRestSdkModule The MITK CppRestSdk Module \tableofcontents \section CppRestSdk_brief Description The MITK CppRestSdk Module is able to manage REST requests. The two main classes to use are the RESTManager and the RESTManageQt (in the CppRestSdkQt Module). -These are MicroServices which can be accessed via +These are MicroServices which can be accessed via \code{.cpp} auto *context = us::GetModuleContext(); auto managerRef = context->GetServiceReference(); if (managerRef) { auto managerService = context->GetService(managerRef); if (managerService) { //call the function you need from the service } } \endcode \subsection CppRestSdk_Technical Technical background - + The module uses the library CppRestSdk for REST mechanisms as well as JSON convertion and asynchronic prorgamming - + \section Use_CppRestSdk How to use the CppRestSdk Module -You can use the CppRestSdk from two different perspectives in MITK: +You can use the CppRestSdk from two different perspectives in MITK:
  1. The Server view (receive Requests from clients)
  2. The Client view (send Requests to servers)
The following sections will give you an introduction on how to use which of those roles: \subsection Server_Use Use from a Server perspective To act as a server, you need to implement the IRESTObserver, which has a Notify() method that has to be implemented. In this Notify() method you specify how you want to react to incoming requests and with which data you want to respond to the requests. -You can then start listening for requests from clients as shown below: +You can then start listening for requests from clients as shown below: \code{.cpp} auto *context = us::GetModuleContext(); auto managerRef = context->GetServiceReference(); if (managerRef) { auto managerService = context->GetService(managerRef); if (managerService) { managerService->ReceiveRequests(uri /*specify your uri which you want to receive requests for*/, this); } } \endcode If a client sends a request, the Notify method is called and a response is sent. By now, only Get-requests from clients are supported. -If you want to stop listening for requests you can do this by calling +If you want to stop listening for requests you can do this by calling \code{.cpp} auto *context = us::GetModuleContext(); auto managerRef = context->GetServiceReference(); if (managerRef) { auto managerService = context->GetService(managerRef); if (managerService) { managerService->HandleDeleteObserver(this, uri); } } \endcode You don't have to specify a uri in the HandleDeleteObserver method, if you only call managerService->HandleDeleteObserver(this);, all uris you receive requests for are deleted and you aren't listening to any requests anymore. \subsection Client_Use Use from a Client perspective The following example shows how to send requests from a client perspective: \code{.cpp} //Get the microservice auto *context = us::ModuleRegistry::GetModule(1)->GetModuleContext(); auto managerRef = context->GetServiceReference(); if (managerRef) { auto managerService = context->GetService(managerRef); if (managerService) { - //Call the send request method which starts the actual request + //Call the send request method which starts the actual request managerService - ->SendRequest(L"https://jsonplaceholder.typicode.com/posts/1") + ->SendRequest(_XPLATSTR("https://jsonplaceholder.typicode.com/posts/1")) .then([=](pplx::task resultTask)/*It is important to use task-based continuation*/ { try { //Get the result of the request - //This will throw an exception if the ascendent task threw an exception (e.g. invalid URI) + //This will throw an exception if the ascendent task threw an exception (e.g. invalid URI) web::json::value result = resultTask.get(); //Do something with the result (e.g. convert it to a QString to update an UI element) utility::string_t stringT = result.to_string(); std::string stringStd(stringT.begin(), stringT.end()); QString stringQ = QString::fromStdString(stringStd); - //Note: if you want to update your UI, do this by using signals and slots. + //Note: if you want to update your UI, do this by using signals and slots. //The UI can't be updated from a Thread different to the Qt main thread emit UpdateLabel(stringQ); } catch (const mitk::Exception &exception) { //Exceptions from ascendent tasks are catched here MITK_ERROR << exception.what(); return; } }); } } \endcode The steps you need to make are the following:
  1. Get the microservice. You can get the microservice via the module context. If you want to use the microservice within a plug-in, you need to get the module context from the us::ModuleRegistry.
  2. Call the SendRequest method. This will start the request itself and is performed asynchronously. As soon as the response is sent by the server, the .then(...) block is executed. -
  3. Choose parameters for .then(...) block. For exception handling, it is important to choose pplx::task . This is a task-based continuation. +
  4. Choose parameters for .then(...) block. For exception handling, it is important to choose pplx::task . This is a task-based continuation. For more information, visit https://docs.microsoft.com/en-us/cpp/parallel/concrt/exception-handling-in-the-concurrency-runtime?view=vs-2017.
  5. Get the result of the request. You can get the JSON-value of the result by callint .get(). At this point, an exception is thrown if something in the previous tasks threw an exception.
  6. Do something with the result. \note If you want to modify GUI elements within the .then(...) block, you need to do this by using signals and slots because GUI elements can only be modified by th Qt Main Thread. For more information, visit https://doc.qt.io/Qt-5/thread-basics.html#gui-thread-and-worker-thread
  7. Exception handling. Here you can define the behaviour if an exception is thrown, exceptions from ascendent tasks are also catched here.
Code, which is followed by this codeblock shown above will be performed asynchronously while waiting for the result. Besides Get-Requests, you can also perform Put or Post requests by specifying a RequestType in the SendRequest method. The following example shows, how you can perform multiple tasks, encapsulated to one joined task. The steps are based on the example for one request and only the specific steps for encapsulation are described. \code{.cpp} //Get the microservice //Get microservice auto *context = us::ModuleRegistry::GetModule(1)->GetModuleContext(); auto managerRef = context->GetServiceReference(); if (managerRef) { auto managerService = context->GetService(managerRef); if (managerService) { //Create multiple tasks e.g. as shown below std::vector> tasks; for (int i = 0; i < 20; i++) { pplx::task singleTask = managerService->SendRequest(L"https://jsonplaceholder.typicode.com/posts/1") .then([=](pplx::task resultTask) { //Do something when a single task is done try { resultTask.get(); emit UpdateProgressBar(); } catch (const mitk::Exception &exception) { MITK_ERROR << exception.what(); return; } }); tasks.emplace_back(singleTask); } //Create a joinTask which includes all tasks you've created auto joinTask = pplx::when_all(begin(tasks), end(tasks)); //Run asynchonously - joinTask.then([=](pplx::task resultTask) { + joinTask.then([=](pplx::task resultTask) { //Do something when all tasks are finished try { resultTask.get(); emit UpdateLabel("All tasks finished"); } catch (const mitk::Exception &exception) { MITK_ERROR << exception.what(); return; } }); } \endcode The steps you need to make are the following:
  1. Get the microservice. See example above.
  2. Create multiple tasks. In this example, 20 identical tasks are created and are saved into a vector. In general, it is possible to place any tasks in that vector.
  3. Do something when a single task is done. Here, an action is performed if a single tasks is finished. In this example, a progress bar is loaded by a specific number of percent.
  4. Create a joinTask. Here, all small tasks are encapsulated in one big task.
  5. Run joinTask asynchonously. The then(...) of the joinTask is performed when all single tasks are finished.
  6. Do something when all tasks are finished. The handling of the end of a joinTask is equivalent to the end of a single tasks.
-*/ \ No newline at end of file +*/ diff --git a/Modules/CppRestSdk/src/mitkRESTManager.cpp b/Modules/CppRestSdk/src/mitkRESTManager.cpp index 08cd5d367f..aee08ac086 100644 --- a/Modules/CppRestSdk/src/mitkRESTManager.cpp +++ b/Modules/CppRestSdk/src/mitkRESTManager.cpp @@ -1,218 +1,218 @@ #include "mitkRESTManager.h" #include #include mitk::RESTManager::RESTManager() {} mitk::RESTManager::~RESTManager() {} pplx::task mitk::RESTManager::SendRequest(const web::uri &uri, const RequestType &type, const web::json::value *content, const utility::string_t &filePath) { pplx::task answer; auto client = new RESTClient(); // according to the RequestType, different HTTP requests are made switch (type) { case RequestType::Get: - + if (filePath.empty()) { // no file path specified, starts a normal get request returning the normal json result answer = client->Get(uri); } else { // file path ist specified, the result of the get request ist stored in this file // and an empty json object is returned answer = client->Get(uri, filePath); } break; - + case RequestType::Post: - + if (nullptr == content) { // warning because normally you won't create an empty ressource MITK_WARN << "Content for put is empty, this will create an empty ressource"; } answer = client->Post(uri, content); break; - + case RequestType::Put: - + if (nullptr == content) { // warning because normally you won't empty a ressource MITK_WARN << "Content for put is empty, this will empty the ressource"; } answer = client->Put(uri, content); break; - + default: mitkThrow() << "Request Type not supported"; break; } return answer; } void mitk::RESTManager::ReceiveRequest(const web::uri &uri, mitk::IRESTObserver *observer) { // New instance of RESTServer in m_ServerMap, key is port of the request int port = uri.port(); // Checking if port is free to add a new Server if (0 == m_ServerMap.count(port)) { this->AddObserver(uri, observer); // creating server instance auto server = new RESTServer(uri.authority()); // add reference to server instance to map m_ServerMap[port] = server; // start Server server->OpenListener(); MITK_INFO << "new server " << mitk::RESTUtil::convertToUtf8(uri.authority().to_string()) << " at port " << port; } // If there is already a server under this port else { this->RequestForATakenPort(uri, observer); } } web::json::value mitk::RESTManager::Handle(const web::uri &uri, const web::json::value &body) { // Checking if there is an observer for the port and path std::pair key(uri.port(), uri.path()); if (0 != m_Observers.count(key)) { return m_Observers[key]->Notify(uri, body); } // No observer under this port, return null which results in status code 404 (s. RESTServer) else { MITK_WARN << "No Observer can handle the data"; - return NULL; + return web::json::value(); } } void mitk::RESTManager::HandleDeleteObserver(IRESTObserver *observer, const web::uri &uri) { for (auto it = m_Observers.begin(); it != m_Observers.end();) { mitk::IRESTObserver *obsMap = it->second; // Check wether observer is at this place in map if (observer == obsMap) { // Check wether it is the right uri to be deleted if (uri.is_empty() || uri.path() == it->first.second) { int port = it->first.first; bool noObserverForPort = this->DeleteObserver(it, uri); if (noObserverForPort) { // there isn't an observer at this port, delete m_ServerMap entry for this port // close listener m_ServerMap[port]->CloseListener(); delete m_ServerMap[port]; // delete server from map m_ServerMap.erase(port); } } else { ++it; } } else { ++it; } } } const std::map &mitk::RESTManager::GetServerMap() { return m_ServerMap; } std::map, mitk::IRESTObserver *> &mitk::RESTManager::GetObservers() { return m_Observers; } void mitk::RESTManager::AddObserver(const web::uri &uri, IRESTObserver *observer) { // new observer has to be added std::pair key(uri.port(), uri.path()); m_Observers[key] = observer; } void mitk::RESTManager::RequestForATakenPort(const web::uri &uri, IRESTObserver *observer) { // Same host, means new observer but not a new server instance if (uri.authority() == m_ServerMap[uri.port()]->GetUri()) { // new observer has to be added std::pair key(uri.port(), uri.path()); // only add a new observer if there isn't already an observer for this uri if (0 == m_Observers.count(key)) { this->AddObserver(uri, observer); // info output MITK_INFO << "started listening, no new server instance has been created"; } else { MITK_ERROR << "Threre is already a observer handeling this data"; } } // Error, since another server can't be added under this port else { MITK_ERROR << "There is already another server listening under this port"; } } bool mitk::RESTManager::DeleteObserver(std::map, IRESTObserver *>::iterator &it, const web::uri &uri) { // if yes // 1. store port and path in a temporary variable // (path is only needed to create a key for info output) int port = it->first.first; utility::string_t path = it->first.second; std::pair key(port, path); // 2. delete map entry it = m_Observers.erase(it); // 3. check, if there is another observer under this port in observer map (with bool flag) for (auto o : m_Observers) { if (port == o.first.first) { // there still exists an observer for this port return false; } } return true; } -void mitk::RESTManager::SetServerMap(const int port, RESTServer *server) +void mitk::RESTManager::SetServerMap(const int port, RESTServer *server) { m_ServerMap[port] = server; } -void mitk::RESTManager::DeleteFromServerMap(const int port) +void mitk::RESTManager::DeleteFromServerMap(const int port) { m_ServerMap.erase(port); } -void mitk::RESTManager::SetObservers(const std::pair key, IRESTObserver *observer) +void mitk::RESTManager::SetObservers(const std::pair key, IRESTObserver *observer) { m_Observers[key] = observer; } diff --git a/Modules/CppRestSdk/src/mitkRESTServer.cpp b/Modules/CppRestSdk/src/mitkRESTServer.cpp index 0d0dd28944..ce71c870a5 100644 --- a/Modules/CppRestSdk/src/mitkRESTServer.cpp +++ b/Modules/CppRestSdk/src/mitkRESTServer.cpp @@ -1,72 +1,72 @@ #include "mitkRESTServer.h" #include #include mitk::RESTServer::RESTServer(const web::uri &uri) { m_Uri = uri; } mitk::RESTServer::~RESTServer() { } void mitk::RESTServer::OpenListener() { //create listener m_Listener = MitkListener(m_Uri); //Connect incoming get requests with HandleGet method m_Listener.support(web::http::methods::GET, std::bind(&mitk::RESTServer::HandleGet, this, std::placeholders::_1)); //open listener m_Listener.open().wait(); } void mitk::RESTServer::CloseListener() { //close listener m_Listener.close().wait(); } -web::uri mitk::RESTServer::GetUri() +web::uri mitk::RESTServer::GetUri() { return m_Uri; } void mitk::RESTServer::HandleGet(const MitkRequest &request) { int port = m_Listener.uri().port(); //getting exact request uri has to be a parameter in handle function web::uri_builder build(m_Listener.uri()); build.append(request.absolute_uri()); auto uriStringT = build.to_uri().to_string(); - MITK_INFO << "Get Request for server at port " << port << " Exact request uri: " + MITK_INFO << "Get Request for server at port " << port << " Exact request uri: " << mitk::RESTUtil::convertToUtf8(uriStringT); - + web::json::value content; //get RESTManager as microservice to call th Handle method of the manager auto context = us::GetModuleContext(); auto managerRef = context->GetServiceReference(); if (managerRef) { auto managerService = context->GetService(managerRef); if (managerService) { web::json::value data = request.extract_json().get(); //call the handle method content = managerService->Handle(build.to_uri(), data); } } - if (content!=NULL) + if (!content.is_null()) { //content handled by observer request.reply(MitkRestStatusCodes::OK, content); } else { //no observer to handle data request.reply(MitkRestStatusCodes::NotFound); } -} \ No newline at end of file +}