changeset 209:9960642f0f45

abstraction of RestApi
author Sebastien Jodogne <s.jodogne@gmail.com>
date Wed, 28 Nov 2012 17:22:07 +0100
parents de640de989b8
children 96b7918a6a18
files CMakeLists.txt Core/RestApi/RestApi.cpp Core/RestApi/RestApi.h Core/RestApi/RestApiOutput.cpp Core/RestApi/RestApiOutput.h Core/RestApi/RestApiPath.cpp Core/RestApi/RestApiPath.h UnitTests/RestApi.cpp UnitTests/ServerIndex.cpp
diffstat 9 files changed, 885 insertions(+), 619 deletions(-) [+]
line wrap: on
line diff
--- a/CMakeLists.txt	Wed Nov 28 17:00:46 2012 +0100
+++ b/CMakeLists.txt	Wed Nov 28 17:22:07 2012 +0100
@@ -111,6 +111,9 @@
   Core/HttpServer/MongooseServer.cpp
   Core/HttpServer/HttpFileSender.cpp
   Core/HttpServer/FilesystemHttpSender.cpp
+  Core/RestApi/RestApiPath.cpp
+  Core/RestApi/RestApiOutput.cpp
+  Core/RestApi/RestApi.cpp
   Core/MultiThreading/BagOfRunnablesBySteps.cpp
   Core/PngWriter.cpp
   Core/SQLite/Connection.cpp
@@ -163,13 +166,14 @@
   include(${CMAKE_SOURCE_DIR}/Resources/CMake/GoogleTestConfiguration.cmake)
   add_executable(UnitTests
     ${GTEST_SOURCES}
-    UnitTests/main.cpp
     UnitTests/MessageWithDestination.cpp
+    UnitTests/RestApi.cpp
     UnitTests/SQLite.cpp
     UnitTests/SQLiteChromium.cpp
+    UnitTests/ServerIndex.cpp
     UnitTests/Versions.cpp
     UnitTests/Zip.cpp
-    UnitTests/ServerIndex.cpp
+    UnitTests/main.cpp
     )
   target_link_libraries(UnitTests ServerLibrary CoreLibrary)
 endif()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/RestApi/RestApi.cpp	Wed Nov 28 17:22:07 2012 +0100
@@ -0,0 +1,276 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Belgium
+ *
+ * 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.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * 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 <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "RestApi.h"
+
+namespace Orthanc
+{
+  bool RestApi::IsGetAccepted(const UriComponents& uri)
+  {
+    for (GetHandlers::const_iterator it = getHandlers_.begin();
+         it != getHandlers_.end(); it++)
+    {
+      if (it->first->Match(uri))
+      {
+        return true;
+      }
+    }
+
+    return false;
+  }
+
+  bool RestApi::IsPutAccepted(const UriComponents& uri)
+  {
+    for (PutHandlers::const_iterator it = putHandlers_.begin();
+         it != putHandlers_.end(); it++)
+    {
+      if (it->first->Match(uri))
+      {
+        return true;
+      }
+    }
+
+    return false;
+  }
+
+  bool RestApi::IsPostAccepted(const UriComponents& uri)
+  {
+    for (PostHandlers::const_iterator it = postHandlers_.begin();
+         it != postHandlers_.end(); it++)
+    {
+      if (it->first->Match(uri))
+      {
+        return true;
+      }
+    }
+
+    return false;
+  }
+
+  bool RestApi::IsDeleteAccepted(const UriComponents& uri)
+  {
+    for (DeleteHandlers::const_iterator it = deleteHandlers_.begin();
+         it != deleteHandlers_.end(); it++)
+    {
+      if (it->first->Match(uri))
+      {
+        return true;
+      }
+    }
+
+    return false;
+  }
+
+  static void AddMethod(std::string& target,
+                        const std::string& method)
+  {
+    if (target.size() > 0)
+      target += "," + method;
+    else
+      target = method;
+  }
+
+  std::string  RestApi::GetAcceptedMethods(const UriComponents& uri)
+  {
+    std::string s;
+
+    if (IsGetAccepted(uri))
+      AddMethod(s, "GET");
+
+    if (IsPutAccepted(uri))
+      AddMethod(s, "PUT");
+
+    if (IsPostAccepted(uri))
+      AddMethod(s, "POST");
+
+    if (IsDeleteAccepted(uri))
+      AddMethod(s, "DELETE");
+
+    return s;
+  }
+
+  RestApi::~RestApi()
+  {
+    for (GetHandlers::iterator it = getHandlers_.begin(); 
+         it != getHandlers_.end(); it++)
+    {
+      delete it->first;
+    } 
+
+    for (PutHandlers::iterator it = putHandlers_.begin(); 
+         it != putHandlers_.end(); it++)
+    {
+      delete it->first;
+    } 
+
+    for (PostHandlers::iterator it = postHandlers_.begin(); 
+         it != postHandlers_.end(); it++)
+    {
+      delete it->first;
+    } 
+
+    for (DeleteHandlers::iterator it = deleteHandlers_.begin(); 
+         it != deleteHandlers_.end(); it++)
+    {
+      delete it->first;
+    } 
+  }
+
+  bool RestApi::IsServedUri(const UriComponents& uri)
+  {
+    return (IsGetAccepted(uri) ||
+            IsPutAccepted(uri) ||
+            IsPostAccepted(uri) ||
+            IsDeleteAccepted(uri));
+  }
+
+  void RestApi::Handle(HttpOutput& output,
+                       const std::string& method,
+                       const UriComponents& uri,
+                       const Arguments& headers,
+                       const Arguments& getArguments,
+                       const std::string& postData)
+  {
+    bool ok = false;
+    RestApiOutput restOutput(output);
+    RestApiPath::Components components;
+    UriComponents trailing;
+               
+    if (method == "GET")
+    {
+      for (GetHandlers::const_iterator it = getHandlers_.begin();
+           it != getHandlers_.end(); it++)
+      {
+        if (it->first->Match(components, trailing, uri))
+        {
+          ok = true;
+          GetCall call;
+          call.output_ = &restOutput;
+          call.context_ = context_.get();
+          call.httpHeaders_ = &headers;
+          call.uriComponents_ = &components;
+          call.trailing_ = &trailing;
+           
+          call.getArguments_ = &getArguments;
+          it->second(call);
+        }
+      }
+    }
+    else if (method == "PUT")
+    {
+      for (PutHandlers::const_iterator it = putHandlers_.begin();
+           it != putHandlers_.end(); it++)
+      {
+        if (it->first->Match(components, trailing, uri))
+        {
+          ok = true;
+          PutCall call;
+          call.output_ = &restOutput;
+          call.context_ = context_.get();
+          call.httpHeaders_ = &headers;
+          call.uriComponents_ = &components;
+          call.trailing_ = &trailing;
+           
+          call.data_ = &postData;
+          it->second(call);
+        }
+      }
+    }
+    else if (method == "POST")
+    {
+      for (PostHandlers::const_iterator it = postHandlers_.begin();
+           it != postHandlers_.end(); it++)
+      {
+        if (it->first->Match(components, trailing, uri))
+        {
+          ok = true;
+          PostCall call;
+          call.output_ = &restOutput;
+          call.context_ = context_.get();
+          call.httpHeaders_ = &headers;
+          call.uriComponents_ = &components;
+          call.trailing_ = &trailing;
+           
+          call.data_ = &postData;
+          it->second(call);
+        }
+      }
+    }
+    else if (method == "DELETE")
+    {
+      for (DeleteHandlers::const_iterator it = deleteHandlers_.begin();
+           it != deleteHandlers_.end(); it++)
+      {
+        if (it->first->Match(components, trailing, uri))
+        {
+          ok = true;
+          DeleteCall call;
+          call.output_ = &restOutput;
+          call.context_ = context_.get();
+          call.httpHeaders_ = &headers;
+          call.uriComponents_ = &components;
+          call.trailing_ = &trailing;
+          it->second(call);
+        }
+      }
+    }
+
+    if (!ok)
+    {
+      output.SendMethodNotAllowedError(GetAcceptedMethods(uri));
+    }
+  }
+
+  void RestApi::Register(const std::string& path,
+                         GetHandler handler)
+  {
+    getHandlers_.push_back(std::make_pair(new RestApiPath(path), handler));
+  }
+
+  void RestApi::Register(const std::string& path,
+                         PutHandler handler)
+  {
+    putHandlers_.push_back(std::make_pair(new RestApiPath(path), handler));
+  }
+
+  void RestApi::Register(const std::string& path,
+                         PostHandler handler)
+  {
+    postHandlers_.push_back(std::make_pair(new RestApiPath(path), handler));
+  }
+
+  void RestApi::Register(const std::string& path,
+                         DeleteHandler handler)
+  {
+    deleteHandlers_.push_back(std::make_pair(new RestApiPath(path), handler));
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/RestApi/RestApi.h	Wed Nov 28 17:22:07 2012 +0100
@@ -0,0 +1,200 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Belgium
+ *
+ * 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.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * 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 <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "../IDynamicObject.h"
+#include "../HttpServer/HttpHandler.h"
+#include "RestApiPath.h"
+#include "RestApiOutput.h"
+
+namespace Orthanc
+{
+  class RestApi : public HttpHandler
+  {
+  private:
+    class SharedCall
+    {
+      friend class RestApi;
+
+    private:
+      RestApiOutput* output_;
+      IDynamicObject* context_;
+      const HttpHandler::Arguments* httpHeaders_;
+      const RestApiPath::Components* uriComponents_;
+      const UriComponents* trailing_;
+
+    public:
+      RestApiOutput& GetOutput()
+      {
+        return *output_;
+      }
+
+      IDynamicObject* GetContext()
+      {
+        return context_;
+      }
+    
+      const HttpHandler::Arguments& GetHttpHeaders() const
+      {
+        return *httpHeaders_;
+      }
+
+      const RestApiPath::Components& GetUriComponents() const
+      {
+        return *uriComponents_;
+      }
+
+      const UriComponents& GetTrailing() const
+      {
+        return *trailing_;
+      }
+
+      std::string GetUriComponent(const std::string& name,
+                                  const std::string& defaultValue)
+      {
+        return HttpHandler::GetArgument(*uriComponents_, name, defaultValue);
+      }
+    };
+
+ 
+  public:
+    class GetCall : public SharedCall
+    {
+      friend class RestApi;
+
+    private:
+      const HttpHandler::Arguments* getArguments_;
+
+    public:
+      std::string GetArgument(const std::string& name,
+                              const std::string& defaultValue)
+      {
+        return HttpHandler::GetArgument(*getArguments_, name, defaultValue);
+      }
+    };
+
+    class PutCall : public SharedCall
+    {
+      friend class RestApi;
+
+    private:
+      const std::string* data_;
+
+    public:
+      const std::string& GetData()
+      {
+        return *data_;
+      }
+    };
+
+    class PostCall : public SharedCall
+    {
+      friend class RestApi;
+
+    private:
+      const std::string* data_;
+
+    public:
+      const std::string& GetData()
+      {
+        return *data_;
+      }
+    };
+
+    class DeleteCall : public SharedCall
+    {
+    };
+
+    typedef void (*GetHandler) (GetCall& call);
+    
+    typedef void (*DeleteHandler) (DeleteCall& call);
+    
+    typedef void (*PutHandler) (PutCall& call);
+    
+    typedef void (*PostHandler) (PostCall& call);
+    
+  private:
+    typedef std::list< std::pair<RestApiPath*, GetHandler> > GetHandlers;
+    typedef std::list< std::pair<RestApiPath*, PutHandler> > PutHandlers;
+    typedef std::list< std::pair<RestApiPath*, PostHandler> > PostHandlers;
+    typedef std::list< std::pair<RestApiPath*, DeleteHandler> > DeleteHandlers;
+
+    // CAUTION: PLEASE INTRODUCE MUTEX BETWEEN CONTEXTS !!!
+    std::auto_ptr<IDynamicObject> context_;
+
+    GetHandlers  getHandlers_;
+    PutHandlers  putHandlers_;
+    PostHandlers  postHandlers_;
+    DeleteHandlers  deleteHandlers_;
+
+    bool IsGetAccepted(const UriComponents& uri);
+    bool IsPutAccepted(const UriComponents& uri);
+    bool IsPostAccepted(const UriComponents& uri);
+    bool IsDeleteAccepted(const UriComponents& uri);
+
+    std::string  GetAcceptedMethods(const UriComponents& uri);
+
+  public:
+    RestApi()
+    {
+    }
+
+    void SetContext(IDynamicObject* context)  // This takes the ownership
+    {
+      context_.reset(context);
+    }
+
+    ~RestApi();
+
+    virtual bool IsServedUri(const UriComponents& uri);
+
+    virtual void Handle(HttpOutput& output,
+                        const std::string& method,
+                        const UriComponents& uri,
+                        const Arguments& headers,
+                        const Arguments& getArguments,
+                        const std::string& postData);
+
+    void Register(const std::string& path,
+                  GetHandler handler);
+
+    void Register(const std::string& path,
+                  PutHandler handler);
+
+    void Register(const std::string& path,
+                  PostHandler handler);
+
+    void Register(const std::string& path,
+                  DeleteHandler handler);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/RestApi/RestApiOutput.cpp	Wed Nov 28 17:22:07 2012 +0100
@@ -0,0 +1,59 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Belgium
+ *
+ * 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.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * 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 <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "RestApiOutput.h"
+
+namespace Orthanc
+{
+  void RestApiOutput::AnswerFile(HttpFileSender& sender)
+  {
+    sender.Send(output_);
+  }
+
+  void RestApiOutput::AnswerJson(const Json::Value& value)
+  {
+    Json::StyledWriter writer;
+    std::string s = writer.write(value);
+    output_.AnswerBufferWithContentType(s, "application/json");
+  }
+
+  void RestApiOutput::AnswerBuffer(const std::string& buffer,
+                                   const std::string& contentType)
+  {
+    output_.AnswerBufferWithContentType(buffer, contentType);
+  }
+
+  void RestApiOutput::Redirect(const char* path)
+  {
+    output_.Redirect(path);
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/RestApi/RestApiOutput.h	Wed Nov 28 17:22:07 2012 +0100
@@ -0,0 +1,61 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Belgium
+ *
+ * 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.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * 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 <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "../HttpServer/HttpOutput.h"
+#include "../HttpServer/HttpFileSender.h"
+
+#include <json/json.h>
+
+namespace Orthanc
+{
+  class RestApiOutput
+  {
+  private:
+    HttpOutput& output_;
+
+  public:
+    RestApiOutput(HttpOutput& output) : output_(output)
+    {
+    }
+
+    void AnswerFile(HttpFileSender& sender);
+
+    void AnswerJson(const Json::Value& value);
+
+    void AnswerBuffer(const std::string& buffer,
+                      const std::string& contentType);
+
+    void Redirect(const char* path);
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/RestApi/RestApiPath.cpp	Wed Nov 28 17:22:07 2012 +0100
@@ -0,0 +1,136 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Belgium
+ *
+ * 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.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * 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 <http://www.gnu.org/licenses/>.
+ **/
+
+
+#include "RestApiPath.h"
+
+#include <cassert>
+
+namespace Orthanc
+{
+  RestApiPath::RestApiPath(const std::string& uri)
+  {
+    Toolbox::SplitUriComponents(uri_, uri);
+
+    if (uri_.size() == 0)
+    {
+      return;
+    }
+
+    if (uri_.back() == "*")
+    {
+      hasTrailing_ = true;
+      uri_.pop_back();
+    }
+    else
+    {
+      hasTrailing_ = false;
+    }
+
+    components_.resize(uri_.size());
+    for (size_t i = 0; i < uri_.size(); i++)
+    {
+      size_t s = uri_[i].size();
+      assert(s > 0);
+
+      if (uri_[i][0] == '{' && 
+          uri_[i][s - 1] == '}')
+      {
+        components_[i] = uri_[i].substr(1, s - 2);
+        uri_[i] = "";
+      }
+      else
+      {
+        components_[i] = "";
+      }
+    }
+  }
+
+  bool RestApiPath::Match(Components& components,
+                          UriComponents& trailing,
+                          const std::string& uriRaw) const
+  {
+    UriComponents uri;
+    Toolbox::SplitUriComponents(uri, uriRaw);
+    return Match(components, trailing, uri);
+  }
+
+  bool RestApiPath::Match(Components& components,
+                          UriComponents& trailing,
+                          const UriComponents& uri) const
+  {
+    if (uri.size() < uri_.size())
+    {
+      return false;
+    }
+
+    if (!hasTrailing_ && uri.size() > uri_.size())
+    {
+      return false;
+    }
+
+    components.clear();
+    trailing.clear();
+
+    assert(uri_.size() <= uri.size());
+    for (size_t i = 0; i < uri_.size(); i++)
+    {
+      if (components_[i].size() == 0)
+      {
+        // This URI component is not a free parameter
+        if (uri_[i] != uri[i])
+        {
+          return false;
+        }
+      }
+      else
+      {
+        // This URI component is a free parameter
+        components[components_[i]] = uri[i];
+      }
+    }
+
+    if (hasTrailing_)
+    {
+      trailing.assign(uri.begin() + uri_.size(), uri.end());
+    }
+
+    return true;
+  }
+
+
+  bool RestApiPath::Match(const UriComponents& uri) const
+  {
+    Components components;
+    UriComponents trailing;
+    return Match(components, trailing, uri);
+  }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Core/RestApi/RestApiPath.h	Wed Nov 28 17:22:07 2012 +0100
@@ -0,0 +1,63 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012 Medical Physics Department, CHU of Liege,
+ * Belgium
+ *
+ * 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.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * 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 <http://www.gnu.org/licenses/>.
+ **/
+
+
+#pragma once
+
+#include "../Toolbox.h"
+#include <map>
+
+namespace Orthanc
+{
+  class RestApiPath
+  {
+  private:
+    UriComponents uri_;
+    bool hasTrailing_;
+    std::vector<std::string> components_;
+
+  public:
+    typedef std::map<std::string, std::string> Components;
+
+    RestApiPath(const std::string& uri);
+
+    // This version is slower
+    bool Match(Components& components,
+               UriComponents& trailing,
+               const std::string& uriRaw) const;
+
+    bool Match(Components& components,
+               UriComponents& trailing,
+               const UriComponents& uri) const;
+
+    bool Match(const UriComponents& uri) const;
+  };
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/UnitTests/RestApi.cpp	Wed Nov 28 17:22:07 2012 +0100
@@ -0,0 +1,84 @@
+#include "gtest/gtest.h"
+
+#include <ctype.h>
+#include <glog/logging.h>
+
+#include "../Core/RestApi/RestApi.h"
+
+using namespace Orthanc;
+
+TEST(RestApi, RestApiPath)
+{
+  RestApiPath::Components args;
+  UriComponents trail;
+
+  {
+    RestApiPath uri("/coucou/{abc}/d/*");
+    ASSERT_TRUE(uri.Match(args, trail, "/coucou/moi/d/e/f/g"));
+    ASSERT_EQ(1u, args.size());
+    ASSERT_EQ(3u, trail.size());
+    ASSERT_EQ("moi", args["abc"]);
+    ASSERT_EQ("e", trail[0]);
+    ASSERT_EQ("f", trail[1]);
+    ASSERT_EQ("g", trail[2]);
+
+    ASSERT_FALSE(uri.Match(args, trail, "/coucou/moi/f"));
+    ASSERT_TRUE(uri.Match(args, trail, "/coucou/moi/d/"));
+    ASSERT_FALSE(uri.Match(args, trail, "/a/moi/d"));
+    ASSERT_FALSE(uri.Match(args, trail, "/coucou/moi"));
+  }
+
+  {
+    RestApiPath uri("/coucou/{abc}/d");
+    ASSERT_FALSE(uri.Match(args, trail, "/coucou/moi/d/e/f/g"));
+    ASSERT_TRUE(uri.Match(args, trail, "/coucou/moi/d"));
+    ASSERT_EQ(1u, args.size());
+    ASSERT_EQ(0u, trail.size());
+    ASSERT_EQ("moi", args["abc"]);
+  }
+
+  {
+    RestApiPath uri("/*");
+    ASSERT_TRUE(uri.Match(args, trail, "/a/b/c"));
+    ASSERT_EQ(0u, args.size());
+    ASSERT_EQ(3u, trail.size());
+    ASSERT_EQ("a", trail[0]);
+    ASSERT_EQ("b", trail[1]);
+    ASSERT_EQ("c", trail[2]);
+  }
+}
+
+
+
+
+#include "../Core/HttpServer/MongooseServer.h"
+
+struct Tutu : public IDynamicObject
+{
+  static void Toto(RestApi::GetCall& call)
+  {
+    printf("DONE\n");
+    Json::Value a = Json::objectValue;
+    a["Tutu"] = "Toto";
+    a["Youpie"] = call.GetArgument("coucou", "nope");
+    a["Toto"] = call.GetUriComponent("test", "nope");
+    call.GetOutput().AnswerJson(a);
+  }
+};
+
+
+
+TEST(RestApi, Tutu)
+{
+  MongooseServer httpServer;
+  httpServer.SetPortNumber(8042);
+  httpServer.Start();
+
+  RestApi* api = new RestApi;
+  httpServer.RegisterHandler(api);
+  api->Register("/coucou/{test}/a/*", Tutu::Toto);
+
+  httpServer.Start();
+  /*LOG(WARNING) << "REST has started";
+    Toolbox::ServerBarrier();*/
+}
--- a/UnitTests/ServerIndex.cpp	Wed Nov 28 17:00:46 2012 +0100
+++ b/UnitTests/ServerIndex.cpp	Wed Nov 28 17:22:07 2012 +0100
@@ -257,620 +257,3 @@
   index.DeleteResource(a[6]);
   ASSERT_EQ("", listener.ancestorId_);  // No more ancestor
 }
-
-
-
-
-#include "../Core/HttpServer/FilesystemHttpSender.h"
-
-#include "../Core/Toolbox.h"
-#include "../Core/HttpServer/HttpOutput.h"
-#include "../Core/HttpServer/HttpHandler.h"
-
-#include "../Core/HttpServer/HttpFileSender.h"
-
-
-namespace Orthanc
-{
-  class RestApiPath
-  {
-  private:
-    UriComponents uri_;
-    bool hasTrailing_;
-    std::vector<std::string> components_;
-
-  public:
-    typedef std::map<std::string, std::string> Components;
-
-    RestApiPath(const std::string& uri)
-    {
-      Toolbox::SplitUriComponents(uri_, uri);
-
-      if (uri_.size() == 0)
-      {
-        return;
-      }
-
-      if (uri_.back() == "*")
-      {
-        hasTrailing_ = true;
-        uri_.pop_back();
-      }
-      else
-      {
-        hasTrailing_ = false;
-      }
-
-      components_.resize(uri_.size());
-      for (size_t i = 0; i < uri_.size(); i++)
-      {
-        size_t s = uri_[i].size();
-        assert(s > 0);
-
-        if (uri_[i][0] == '{' && 
-            uri_[i][s - 1] == '}')
-        {
-          components_[i] = uri_[i].substr(1, s - 2);
-          uri_[i] = "";
-        }
-        else
-        {
-          components_[i] = "";
-        }
-      }
-    }
-
-    // This version is slower
-    bool Match(Components& components,
-               UriComponents& trailing,
-               const std::string& uriRaw) const
-    {
-      UriComponents uri;
-      Toolbox::SplitUriComponents(uri, uriRaw);
-      return Match(components, trailing, uri);
-    }
-
-    bool Match(Components& components,
-               UriComponents& trailing,
-               const UriComponents& uri) const
-    {
-      if (uri.size() < uri_.size())
-      {
-        return false;
-      }
-
-      if (!hasTrailing_ && uri.size() > uri_.size())
-      {
-        return false;
-      }
-
-      components.clear();
-      trailing.clear();
-
-      assert(uri_.size() <= uri.size());
-      for (size_t i = 0; i < uri_.size(); i++)
-      {
-        if (components_[i].size() == 0)
-        {
-          // This URI component is not a free parameter
-          if (uri_[i] != uri[i])
-          {
-            return false;
-          }
-        }
-        else
-        {
-          // This URI component is a free parameter
-          components[components_[i]] = uri[i];
-        }
-      }
-
-      if (hasTrailing_)
-      {
-        trailing.assign(uri.begin() + uri_.size(), uri.end());
-      }
-
-      return true;
-    }
-
-    bool Match(const UriComponents& uri) const
-    {
-      Components components;
-      UriComponents trailing;
-      return Match(components, trailing, uri);
-    }
-  };
-
-
-  class RestApiOutput
-  {
-  private:
-    HttpOutput& output_;
-
-  public:
-    RestApiOutput(HttpOutput& output) : output_(output)
-    {
-    }
-
-    void AnswerFile(HttpFileSender& sender)
-    {
-      sender.Send(output_);
-    }
-
-    void AnswerJson(const Json::Value& value)
-    {
-      Json::StyledWriter writer;
-      std::string s = writer.write(value);
-      output_.AnswerBufferWithContentType(s, "application/json");
-    }
-
-    void AnswerBuffer(const std::string& buffer,
-                      const std::string& contentType)
-    {
-      output_.AnswerBufferWithContentType(buffer, contentType);
-    }
-
-    void Redirect(const char* path)
-    {
-      output_.Redirect(path);
-    }
-  };
-
-
-  class RestApiSharedCall
-  {
-  protected:
-    RestApiOutput* output_;
-    IDynamicObject* context_;
-    const HttpHandler::Arguments* httpHeaders_;
-    const RestApiPath::Components* uriComponents_;
-    const UriComponents* trailing_;
-
-  public:
-    RestApiOutput& GetOutput()
-    {
-      return *output_;
-    }
-
-    IDynamicObject* GetContext()
-    {
-      return context_;
-    }
-    
-    const HttpHandler::Arguments& GetHttpHeaders() const
-    {
-      return *httpHeaders_;
-    }
-
-    const RestApiPath::Components& GetUriComponents() const
-    {
-      return *uriComponents_;
-    }
-
-    const UriComponents& GetTrailing() const
-    {
-      return *trailing_;
-    }
-
-    std::string GetUriComponent(const std::string& name,
-                                const std::string& defaultValue)
-    {
-      return HttpHandler::GetArgument(*uriComponents_, name, defaultValue);
-    }
-  };
-
- 
-  class RestApiPutCall : public RestApiSharedCall
-  {
-    friend class RestApi;
-
-  private:
-    const std::string* data_;
-
-  public:
-    const std::string& GetData()
-    {
-      return *data_;
-    }
-  };
-
-
-  class RestApiPostCall : public RestApiSharedCall
-  {
-    friend class RestApi;
-
-  private:
-    const std::string* data_;
-
-  public:
-    const std::string& GetData()
-    {
-      return *data_;
-    }
-  };
-
-
- 
-  class RestApiDeleteCall : public RestApiSharedCall
-  {
-    friend class RestApi;
-  };
-
-
-
-
-  class RestApiGetCall : public RestApiSharedCall
-  {
-    friend class RestApi;
-
-  private:
-    const HttpHandler::Arguments* getArguments_;
-
-  public:
-    std::string GetArgument(const std::string& name,
-                            const std::string& defaultValue)
-    {
-      return HttpHandler::GetArgument(*getArguments_, name, defaultValue);
-    }
-  };
-
-
-
-  class RestApi : public HttpHandler
-  {
-  public:
-    typedef void (*GetHandler) (RestApiGetCall& call);
-    
-    typedef void (*DeleteHandler) (RestApiDeleteCall& call);
-    
-    typedef void (*PutHandler) (RestApiPutCall& call);
-    
-    typedef void (*PostHandler) (RestApiPostCall& call);
-    
-  private:
-    typedef std::list< std::pair<RestApiPath*, GetHandler> > GetHandlers;
-    typedef std::list< std::pair<RestApiPath*, PutHandler> > PutHandlers;
-    typedef std::list< std::pair<RestApiPath*, PostHandler> > PostHandlers;
-    typedef std::list< std::pair<RestApiPath*, DeleteHandler> > DeleteHandlers;
-
-    // TODO MUTEX BETWEEN CONTEXTS !!!
-    std::auto_ptr<IDynamicObject> context_;
-
-    GetHandlers  getHandlers_;
-    PutHandlers  putHandlers_;
-    PostHandlers  postHandlers_;
-    DeleteHandlers  deleteHandlers_;
-
-    bool IsGetAccepted(const UriComponents& uri)
-    {
-      for (GetHandlers::const_iterator it = getHandlers_.begin();
-           it != getHandlers_.end(); it++)
-      {
-        if (it->first->Match(uri))
-        {
-          return true;
-        }
-      }
-
-      return false;
-    }
-
-    bool IsPutAccepted(const UriComponents& uri)
-    {
-      for (PutHandlers::const_iterator it = putHandlers_.begin();
-           it != putHandlers_.end(); it++)
-      {
-        if (it->first->Match(uri))
-        {
-          return true;
-        }
-      }
-
-      return false;
-    }
-
-    bool IsPostAccepted(const UriComponents& uri)
-    {
-      for (PostHandlers::const_iterator it = postHandlers_.begin();
-           it != postHandlers_.end(); it++)
-      {
-        if (it->first->Match(uri))
-        {
-          return true;
-        }
-      }
-
-      return false;
-    }
-
-    bool IsDeleteAccepted(const UriComponents& uri)
-    {
-      for (DeleteHandlers::const_iterator it = deleteHandlers_.begin();
-           it != deleteHandlers_.end(); it++)
-      {
-        if (it->first->Match(uri))
-        {
-          return true;
-        }
-      }
-
-      return false;
-    }
-
-    void AddMethod(std::string& target,
-                   const std::string& method) const
-    {
-      if (target.size() > 0)
-        target += "," + method;
-      else
-        target = method;
-    }
-
-    std::string  GetAcceptedMethods(const UriComponents& uri)
-    {
-      std::string s;
-
-      if (IsGetAccepted(uri))
-        AddMethod(s, "GET");
-
-      if (IsPutAccepted(uri))
-        AddMethod(s, "PUT");
-
-      if (IsPostAccepted(uri))
-        AddMethod(s, "POST");
-
-      if (IsDeleteAccepted(uri))
-        AddMethod(s, "DELETE");
-
-      return s;
-    }
-
-  public:
-    RestApi()
-    {
-    }
-
-    void SetContext(IDynamicObject* context)  // This takes the ownership
-    {
-      context_.reset(context);
-    }
-
-    ~RestApi()
-    {
-      for (GetHandlers::iterator it = getHandlers_.begin(); 
-           it != getHandlers_.end(); it++)
-      {
-        delete it->first;
-      } 
-
-      for (PutHandlers::iterator it = putHandlers_.begin(); 
-           it != putHandlers_.end(); it++)
-      {
-        delete it->first;
-      } 
-
-      for (PostHandlers::iterator it = postHandlers_.begin(); 
-           it != postHandlers_.end(); it++)
-      {
-        delete it->first;
-      } 
-
-      for (DeleteHandlers::iterator it = deleteHandlers_.begin(); 
-           it != deleteHandlers_.end(); it++)
-      {
-        delete it->first;
-      } 
-    }
-
-    virtual bool IsServedUri(const UriComponents& uri)
-    {
-      return (IsGetAccepted(uri) ||
-              IsPutAccepted(uri) ||
-              IsPostAccepted(uri) ||
-              IsDeleteAccepted(uri));
-    }
-
-    virtual void Handle(HttpOutput& output,
-                        const std::string& method,
-                        const UriComponents& uri,
-                        const Arguments& headers,
-                        const Arguments& getArguments,
-                        const std::string& postData)
-    {
-      bool ok = false;
-      RestApiOutput restOutput(output);
-      RestApiPath::Components components;
-      UriComponents trailing;
-               
-      if (method == "GET")
-      {
-        for (GetHandlers::const_iterator it = getHandlers_.begin();
-             it != getHandlers_.end(); it++)
-        {
-          if (it->first->Match(components, trailing, uri))
-          {
-            ok = true;
-            RestApiGetCall call;
-            call.output_ = &restOutput;
-            call.context_ = context_.get();
-            call.httpHeaders_ = &headers;
-            call.uriComponents_ = &components;
-            call.trailing_ = &trailing;
-           
-            call.getArguments_ = &getArguments;
-            it->second(call);
-          }
-        }
-      }
-      else if (method == "PUT")
-      {
-        for (PutHandlers::const_iterator it = putHandlers_.begin();
-             it != putHandlers_.end(); it++)
-        {
-          if (it->first->Match(components, trailing, uri))
-          {
-            ok = true;
-            RestApiPutCall call;
-            call.output_ = &restOutput;
-            call.context_ = context_.get();
-            call.httpHeaders_ = &headers;
-            call.uriComponents_ = &components;
-            call.trailing_ = &trailing;
-           
-            call.data_ = &postData;
-            it->second(call);
-          }
-        }
-      }
-      else if (method == "POST")
-      {
-        for (PostHandlers::const_iterator it = postHandlers_.begin();
-             it != postHandlers_.end(); it++)
-        {
-          if (it->first->Match(components, trailing, uri))
-          {
-            ok = true;
-            RestApiPostCall call;
-            call.output_ = &restOutput;
-            call.context_ = context_.get();
-            call.httpHeaders_ = &headers;
-            call.uriComponents_ = &components;
-            call.trailing_ = &trailing;
-           
-            call.data_ = &postData;
-            it->second(call);
-          }
-        }
-      }
-      else if (method == "DELETE")
-      {
-        for (DeleteHandlers::const_iterator it = deleteHandlers_.begin();
-             it != deleteHandlers_.end(); it++)
-        {
-          if (it->first->Match(components, trailing, uri))
-          {
-            ok = true;
-            RestApiDeleteCall call;
-            call.output_ = &restOutput;
-            call.context_ = context_.get();
-            call.httpHeaders_ = &headers;
-            call.uriComponents_ = &components;
-            call.trailing_ = &trailing;
-            it->second(call);
-          }
-        }
-      }
-
-      if (!ok)
-      {
-        output.SendMethodNotAllowedError(GetAcceptedMethods(uri));
-      }
-    }
-
-    void Register(const std::string& path,
-                  GetHandler handler)
-    {
-      getHandlers_.push_back(std::make_pair(new RestApiPath(path), handler));
-    }
-
-
-    void Register(const std::string& path,
-                  PutHandler handler)
-    {
-      putHandlers_.push_back(std::make_pair(new RestApiPath(path), handler));
-    }
-
-
-    void Register(const std::string& path,
-                  PostHandler handler)
-    {
-      postHandlers_.push_back(std::make_pair(new RestApiPath(path), handler));
-    }
-
-
-    void Register(const std::string& path,
-                  DeleteHandler handler)
-    {
-      deleteHandlers_.push_back(std::make_pair(new RestApiPath(path), handler));
-    }
-
-  };
-
-}
-
-
-TEST(RestApi, RestApiPath)
-{
-  RestApiPath::Components args;
-  UriComponents trail;
-
-  {
-    RestApiPath uri("/coucou/{abc}/d/*");
-    ASSERT_TRUE(uri.Match(args, trail, "/coucou/moi/d/e/f/g"));
-    ASSERT_EQ(1u, args.size());
-    ASSERT_EQ(3u, trail.size());
-    ASSERT_EQ("moi", args["abc"]);
-    ASSERT_EQ("e", trail[0]);
-    ASSERT_EQ("f", trail[1]);
-    ASSERT_EQ("g", trail[2]);
-
-    ASSERT_FALSE(uri.Match(args, trail, "/coucou/moi/f"));
-    ASSERT_TRUE(uri.Match(args, trail, "/coucou/moi/d/"));
-    ASSERT_FALSE(uri.Match(args, trail, "/a/moi/d"));
-    ASSERT_FALSE(uri.Match(args, trail, "/coucou/moi"));
-  }
-
-  {
-    RestApiPath uri("/coucou/{abc}/d");
-    ASSERT_FALSE(uri.Match(args, trail, "/coucou/moi/d/e/f/g"));
-    ASSERT_TRUE(uri.Match(args, trail, "/coucou/moi/d"));
-    ASSERT_EQ(1u, args.size());
-    ASSERT_EQ(0u, trail.size());
-    ASSERT_EQ("moi", args["abc"]);
-  }
-
-  {
-    RestApiPath uri("/*");
-    ASSERT_TRUE(uri.Match(args, trail, "/a/b/c"));
-    ASSERT_EQ(0u, args.size());
-    ASSERT_EQ(3u, trail.size());
-    ASSERT_EQ("a", trail[0]);
-    ASSERT_EQ("b", trail[1]);
-    ASSERT_EQ("c", trail[2]);
-  }
-}
-
-
-
-
-#include "../Core/HttpServer/MongooseServer.h"
-
-struct Tutu : public IDynamicObject
-{
-  static void Toto(RestApiGetCall& call)
-  {
-    printf("DONE\n");
-    Json::Value a = Json::objectValue;
-    a["Tutu"] = "Toto";
-    a["Youpie"] = call.GetArgument("coucou", "nope");
-    a["Toto"] = call.GetUriComponent("test", "nope");
-    call.GetOutput().AnswerJson(a);
-  }
-};
-
-
-
-TEST(RestApi, Tutu)
-{
-  MongooseServer httpServer;
-  httpServer.SetPortNumber(8042);
-  httpServer.Start();
-
-  RestApi* api = new RestApi;
-  httpServer.RegisterHandler(api);
-  api->Register("/coucou/{test}/a/*", Tutu::Toto);
-
-  httpServer.Start();
-  /*LOG(WARNING) << "REST has started";
-    Toolbox::ServerBarrier();*/
-}