Mercurial > hg > orthanc
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();*/ -}