lganzzzo vor 3 Jahren
Ursprung
Commit
0b3e2a18ed
53 geänderte Dateien mit 1486 neuen und 385 gelöschten Zeilen
  1. 2 2
      README.md
  2. 222 0
      changelog/1.2.5.md
  3. 4 3
      src/CMakeLists.txt
  4. 1 0
      src/oatpp-test/UnitTest.hpp
  5. 10 5
      src/oatpp-test/web/ClientServerTestRunner.hpp
  6. 28 3
      src/oatpp/codegen/api_controller/base_define.hpp
  7. 0 1
      src/oatpp/core/async/worker/IOEventWorker_kqueue.cpp
  8. 58 11
      src/oatpp/core/base/Environment.cpp
  9. 87 6
      src/oatpp/core/base/Environment.hpp
  10. 79 9
      src/oatpp/core/base/StrBuffer.cpp
  11. 24 9
      src/oatpp/core/base/StrBuffer.hpp
  12. 26 12
      src/oatpp/core/data/share/LazyStringMap.hpp
  13. 25 1
      src/oatpp/core/data/share/MemoryLabel.hpp
  14. 0 1
      src/oatpp/core/parser/Caret.cpp
  15. 0 2
      src/oatpp/encoding/Unicode.cpp
  16. 53 5
      src/oatpp/network/Server.cpp
  17. 14 2
      src/oatpp/network/Server.hpp
  18. 0 1
      src/oatpp/network/tcp/client/ConnectionProvider.cpp
  19. 0 9
      src/oatpp/web/client/HttpRequestExecutor.cpp
  20. 1 1
      src/oatpp/web/mime/multipart/Multipart.hpp
  21. 1 1
      src/oatpp/web/mime/multipart/Part.hpp
  22. 3 3
      src/oatpp/web/mime/multipart/StatefulParser.hpp
  23. 8 6
      src/oatpp/web/protocol/http/Http.cpp
  24. 1 1
      src/oatpp/web/protocol/http/Http.hpp
  25. 7 5
      src/oatpp/web/protocol/http/incoming/Request.cpp
  26. 6 2
      src/oatpp/web/protocol/http/incoming/Request.hpp
  27. 23 37
      src/oatpp/web/protocol/http/utils/CommunicationUtils.cpp
  28. 10 21
      src/oatpp/web/protocol/http/utils/CommunicationUtils.hpp
  29. 6 2
      src/oatpp/web/server/AsyncHttpConnectionHandler.cpp
  30. 15 6
      src/oatpp/web/server/AsyncHttpConnectionHandler.hpp
  31. 6 2
      src/oatpp/web/server/HttpConnectionHandler.cpp
  32. 12 8
      src/oatpp/web/server/HttpConnectionHandler.hpp
  33. 166 92
      src/oatpp/web/server/HttpProcessor.cpp
  34. 22 8
      src/oatpp/web/server/HttpProcessor.hpp
  35. 0 64
      src/oatpp/web/server/HttpRouter.cpp
  36. 41 15
      src/oatpp/web/server/HttpRouter.hpp
  37. 24 7
      src/oatpp/web/server/api/Endpoint.hpp
  38. 61 0
      src/oatpp/web/server/interceptor/AllowCorsGlobal.cpp
  39. 58 0
      src/oatpp/web/server/interceptor/AllowCorsGlobal.hpp
  40. 10 5
      src/oatpp/web/server/interceptor/RequestInterceptor.hpp
  41. 77 0
      src/oatpp/web/server/interceptor/ResponseInterceptor.hpp
  42. 4 0
      src/oatpp/web/url/mapping/Pattern.hpp
  43. 26 13
      src/oatpp/web/url/mapping/Router.hpp
  44. 2 0
      test/CMakeLists.txt
  45. 2 1
      test/oatpp/AllTestsMain.cpp
  46. 22 2
      test/oatpp/core/base/LoggerTest.cpp
  47. 1 0
      test/oatpp/core/base/LoggerTest.hpp
  48. 33 0
      test/oatpp/core/data/share/MemoryLabelTest.cpp
  49. 8 0
      test/oatpp/web/FullTest.cpp
  50. 1 0
      test/oatpp/web/app/Client.hpp
  51. 7 0
      test/oatpp/web/app/Controller.hpp
  52. 171 0
      test/oatpp/web/server/HttpRouterTest.cpp
  53. 18 1
      test/oatpp/web/server/HttpRouterTest.hpp

+ 2 - 2
README.md

@@ -19,8 +19,8 @@
 
 **News**  
 
-- The new `1.2.0` version is now released. See [changelog](changelog/1.2.0.md) for details.  
-- Check out the new oatpp ORM - read more [here](https://oatpp.io/docs/components/orm/#declare-dbclient).  
+- Hey, meet the new oatpp version `1.2.5`! See the [changelog](changelog/1.2.5.md) for details.  
+- Check out the new oatpp ORM - read more [here](https://oatpp.io/docs/components/orm/).  
 
 ---
 

+ 222 - 0
changelog/1.2.5.md

@@ -0,0 +1,222 @@
+# Oat++ 1.2.5
+
+Previous release - [1.2.0](1.2.0.md)
+
+Feel free to ask questions - [Chat on Gitter!](https://gitter.im/oatpp-framework/Lobby)
+
+Contents:
+
+- [Introduce ResponseInterceptor](#introduce-responseinterceptor)
+- [Enable Global CORS](#enable-global-cors)
+- [Headers Multimap](#headers-multimap)
+- [Better Router API](#better-router-api)
+- [ORM Clean Section](#orm-clean-section)
+- [ORM PostgreSQL - Arrays Support](#orm-postgresql---arrays-support)
+- [Swagger-UI Example Values](#swagger-ui-example-values)
+- [New Modules](#new-modules)
+
+## Introduce ResponseInterceptor
+
+### Declare Response Interceptor
+
+```cpp
+#include "oatpp/web/server/interceptor/ResponseInterceptor.hpp"
+
+class MyResponseInterceptor : public ResponseInterceptor {
+public:
+
+  std::shared_ptr<OutgoingResponse> intercept(const std::shared_ptr<IncomingRequest>& request,
+                                              const std::shared_ptr<OutgoingResponse>& response) override 
+  {
+    // TODO modify response or create a new one
+    return response; // return modified response
+                     // returning nullptr will result in an error
+  }
+
+};
+```
+
+### Register global request interceptor
+
+```cpp
+  OATPP_CREATE_COMPONENT(std::shared_ptr<oatpp::network::ConnectionHandler>, serverConnectionHandler)([] {
+
+    OATPP_COMPONENT(std::shared_ptr<oatpp::web::server::HttpRouter>, router); 
+
+    auto connectionHandler = oatpp::web::server::HttpConnectionHandler::createShared(router);
+
+    /* Add MyResponseInterceptor */
+    connectionHandler->addResponseInterceptor(std::make_shared<MyResponseInterceptor>());
+
+    return connectionHandler;
+
+  }());
+```
+
+## Enable Global CORS
+
+To enable global CORS for all endpoints:
+
+- Add **Request** Interceptor - `oatpp::web::server::interceptor::AllowOptionsGlobal` to `ConnectionHandler`.
+- Add **Response** Interceptor - `atpp::web::server::interceptor::AllowCorsGlobal` to `ConnectionHandler`.
+
+```cpp
+#include "oatpp/web/server/interceptor/AllowCorsGlobal.hpp"
+
+...
+
+  OATPP_CREATE_COMPONENT(std::shared_ptr<oatpp::network::ConnectionHandler>, serverConnectionHandler)([] {
+
+    OATPP_COMPONENT(std::shared_ptr<oatpp::web::server::HttpRouter>, router); // get Router component
+
+    auto connectionHandler = oatpp::web::server::HttpConnectionHandler::createShared(router);
+
+    /* Add CORS-enabling interceptors */
+    connectionHandler->addRequestInterceptor(std::make_shared<oatpp::web::server::interceptor::AllowOptionsGlobal>());
+    connectionHandler->addResponseInterceptor(std::make_shared<oatpp::web::server::interceptor::AllowCorsGlobal>());
+
+    return connectionHandler;
+
+  }());
+```
+
+## Headers Multimap
+
+Now headers are stored using [std::multimap](https://en.cppreference.com/w/cpp/container/multimap) and can store multiple entries with the same key.
+
+Put multiple headers:
+
+```cpp
+auto response = createResponse(Status::CODE_200, "");
+response->putHeader("Set-Cookie", "...");
+response->putHeader("Set-Cookie", "...");
+return response;
+```
+
+Log all "Set-Cookie" headers:
+
+```cpp
+  const auto& map = headers.getAll();
+  auto itlow = map.lower_bound("Set-Cookie");
+  auto itup = map.upper_bound("Set-Cookie");
+
+  for(auto it = itlow; it != itup; it ++) {
+    oatpp::String value = it->second.toString();
+    OATPP_LOGD("Header", "Set-Cookie: %s", value->c_str());
+  }
+```
+
+## Better Router API
+
+Now Router class is a template and can store any value-types and not only `RequestHandler`s.
+
+Example use-case - check if endpoint should require authorization:
+
+**Add Routs**
+
+```cpp
+oatpp::web::server::HttpRouterTemplate<bool> authEndpoints;
+
+authEndpoint.route("POST", "login", false); // DO NOT require auth for /login path
+authEndpoint.route("POST", "auth", false);  // DO NOT require auth for /auth path
+
+authEndpoint.route("GET", "*", true);       // require auth for all GET
+authEndpoint.route("POST", "*", true);      // require auth for all POST
+
+authEndpoint.route("OPTIONS", "*", false);  // DO NOT require auth for OPTIONS
+```
+
+**Check Auth**
+
+```cpp
+auto r = authEndpoints.getRoute(request->getStartingLine().method, request->getStartingLine().path);
+if(r && r.getEndpoint() == true) {
+  // check auth
+}
+```
+
+## ORM Clean Section
+
+For modules:
+
+- [oatpp-sqlite](https://github.com/oatpp/oatpp-sqlite)
+- [oatpp-postgresql](https://github.com/oatpp/oatpp-postgresql)
+
+Now it's possible to declare a "clean section" - a section that is untouched by DSL processor.
+
+Clean section begins with `<!!` and ends with `!!>`. 
+**Note:** `<!!` and `!!>` char sequences are ignored inside string. 
+
+### Example
+
+Such query:
+
+```cpp
+QUERY(selectUserName, 
+      "SELECT <!! name::varchar !!> FROM users WHERE userId=:userId", 
+      PARAM(String, userId))
+```
+
+Will be processed as follows:
+
+```sql
+SELECT  name::varchar  FROM users WHERE userId="<user-id-value>"
+```
+
+Note: unlike the `:userId` the `:varchar` char-sequence wasn't interpreted as a template parameter (unlike the `:userId`).
+
+## ORM PostgreSQL - Arrays Support
+
+[oatpp-postgresql](https://github.com/oatpp/oatpp-postgresql) now supports arrays.  
+More about PostgreSQL arrays - read [here](https://www.postgresql.org/docs/13/arrays.html)
+
+## Swagger-UI Example Values
+
+Now it's possible to add example-values to `RequestBody`, `Response`, and `Parameters` (Path, Headers, Queries)
+
+### Add Consumes Examples
+
+```cpp
+ENDPOINT_INFO(myEndpoint) {
+
+  info->addConsumes<Object<MyDto>>("application/json")
+    .addExample("example_1", MyDto::createShared(... /* params here */ ))
+    .addExample("example_2", MyDto::createShared(... /* params here */ ))
+    .addExample("example_3", MyDto::createShared(... /* params here */ ));
+
+}
+```
+
+### Add Response Examples 
+
+```cpp
+ENDPOINT_INFO(myEndpoint) {
+
+  info->addResponse<Object<MyDto>>(Status::CODE_200, "application/json")
+    .addExample("Successful Response_1", MyDto::createShared(... /* params */ ));
+
+  info->addResponse<Object<ErrorDto>>(Status::CODE_404, "application/json")
+    .addExample("Error - Not found", ErrorDto::createShared(404, "Not Found"));
+
+  info->addResponse<Object<ErrorDto>>(Status::CODE_500, "application/json")
+    .addExample("Error - DB Connection", ErrorDto::createShared(500, "Can't connect to DB"))
+    .addExample("Error - Unknown", ErrorDto::createShared(500, "Unknown Error"));
+
+}
+```
+
+### Add Parameter Examples
+
+```cpp
+ENDPOINT_INFO(myEndpoint) {
+
+  info->pathParams["userRole"]
+    .addExample("Admin", oatpp::Enum<UserRole>(UserRole::ADMIN))
+    .addExample("Guest", oatpp::Enum<UserRole>(UserRole::GUEST));
+
+}
+```
+
+## New Modules
+
+- [oatpp-openssl](https://github.com/oatpp/oatpp-openssl) - TLS adaptor for OpenSSL (Recommended to use).

+ 4 - 3
src/CMakeLists.txt

@@ -254,7 +254,6 @@ add_library(oatpp
         oatpp/web/server/HttpProcessor.cpp
         oatpp/web/server/HttpProcessor.hpp
         oatpp/web/server/HttpRequestHandler.hpp
-        oatpp/web/server/HttpRouter.cpp
         oatpp/web/server/HttpRouter.hpp
         oatpp/web/server/api/ApiController.cpp
         oatpp/web/server/api/ApiController.hpp
@@ -264,8 +263,10 @@ add_library(oatpp
         oatpp/web/server/handler/AuthorizationHandler.hpp
         oatpp/web/server/handler/ErrorHandler.cpp
         oatpp/web/server/handler/ErrorHandler.hpp
-        oatpp/web/server/handler/Interceptor.cpp
-        oatpp/web/server/handler/Interceptor.hpp
+        oatpp/web/server/interceptor/AllowCorsGlobal.cpp
+        oatpp/web/server/interceptor/AllowCorsGlobal.hpp
+        oatpp/web/server/interceptor/RequestInterceptor.hpp
+        oatpp/web/server/interceptor/ResponseInterceptor.hpp
         oatpp/web/url/mapping/Pattern.cpp
         oatpp/web/url/mapping/Pattern.hpp
         oatpp/web/url/mapping/Router.hpp

+ 1 - 0
src/oatpp-test/UnitTest.hpp

@@ -25,6 +25,7 @@
 #ifndef oatpp_test_UnitTest_hpp
 #define oatpp_test_UnitTest_hpp
 
+#include <functional>
 #include "oatpp/core/base/Environment.hpp"
 
 namespace oatpp { namespace test {

+ 10 - 5
src/oatpp-test/web/ClientServerTestRunner.hpp

@@ -89,21 +89,26 @@ public:
     std::atomic<bool> running(true);
     std::mutex timeoutMutex;
     std::condition_variable timeoutCondition;
+    bool runConditionForLambda = true;
 
     m_server = std::make_shared<oatpp::network::Server>(m_connectionProvider, m_connectionHandler);
     OATPP_LOGD("\033[1;34mClientServerTestRunner\033[0m", "\033[1;34mRunning server on port %s. Timeout %lld(micro)\033[0m",
                m_connectionProvider->getProperty("port").toString()->c_str(),
                timeout.count());
 
-    std::thread serverThread([this]{
-      m_server->run();
+    std::function<bool()> condition = [&runConditionForLambda](){
+        return runConditionForLambda;
+    };
+
+    std::thread serverThread([&condition, this]{
+      m_server->run(condition);
     });
 
-    std::thread clientThread([this, &lambda]{
+    std::thread clientThread([&runConditionForLambda, this, &lambda]{
 
       lambda();
-
-      m_server->stop();
+//      m_server->stop();
+      runConditionForLambda = false;
       m_connectionHandler->stop();
       m_connectionProvider->stop();
 

+ 28 - 3
src/oatpp/codegen/api_controller/base_define.hpp

@@ -186,6 +186,17 @@ if(!__param_validation_check_##NAME){ \
                                     "'. Expected type is '" #TYPE "'"); \
 }
 
+#define OATPP_MACRO_API_CONTROLLER_QUERY_3(TYPE, NAME, QUALIFIER, DEFAULT) \
+const auto& __param_str_val_##NAME = __request->getQueryParameter(QUALIFIER, DEFAULT); \
+bool __param_validation_check_##NAME; \
+const auto& NAME = ApiController::TypeInterpretation<TYPE>::fromString(#TYPE, __param_str_val_##NAME, __param_validation_check_##NAME); \
+if(!__param_validation_check_##NAME){ \
+  return ApiController::handleError(Status::CODE_400, \
+                                    oatpp::String("Invalid QUERY parameter '") + \
+                                    QUALIFIER + \
+                                    "'. Expected type is '" #TYPE "'"); \
+}
+
 #define OATPP_MACRO_API_CONTROLLER_QUERY(TYPE, PARAM_LIST) \
 OATPP_MACRO_API_CONTROLLER_MACRO_SELECTOR(OATPP_MACRO_API_CONTROLLER_QUERY_, TYPE, OATPP_MACRO_UNFOLD_VA_ARGS PARAM_LIST)
 
@@ -197,6 +208,9 @@ info->queryParams.add(#NAME, TYPE::Class::getType());
 #define OATPP_MACRO_API_CONTROLLER_QUERY_INFO_2(TYPE, NAME, QUALIFIER) \
 info->queryParams.add(QUALIFIER, TYPE::Class::getType());
 
+#define OATPP_MACRO_API_CONTROLLER_QUERY_INFO_3(TYPE, NAME, QUALIFIER, DEFAULT) \
+info->queryParams.add(QUALIFIER, TYPE::Class::getType());
+
 #define OATPP_MACRO_API_CONTROLLER_QUERY_INFO(TYPE, PARAM_LIST) \
 OATPP_MACRO_API_CONTROLLER_MACRO_SELECTOR(OATPP_MACRO_API_CONTROLLER_QUERY_INFO_, TYPE, OATPP_MACRO_UNFOLD_VA_ARGS PARAM_LIST)
 
@@ -209,11 +223,18 @@ const auto& OATPP_MACRO_FIRSTARG PARAM_LIST = __request->readBodyToString();
 
 #define OATPP_MACRO_API_CONTROLLER_BODY_STRING_INFO(TYPE, PARAM_LIST) \
 info->body.name = OATPP_MACRO_FIRSTARG_STR PARAM_LIST; \
-info->body.type = oatpp::data::mapping::type::__class::String::getType();
+info->body.required = true; \
+info->body.type = oatpp::data::mapping::type::__class::String::getType(); \
+if(getDefaultObjectMapper()) { \
+  info->bodyContentType = getDefaultObjectMapper()->getInfo().http_content_type; \
+}
 
 // BODY_DTO MACRO // ------------------------------------------------------
 
 #define OATPP_MACRO_API_CONTROLLER_BODY_DTO(TYPE, PARAM_LIST) \
+if(!getDefaultObjectMapper()) { \
+  return ApiController::handleError(Status::CODE_500, "ObjectMapper was NOT set. Can't deserialize the request body."); \
+} \
 TYPE OATPP_MACRO_FIRSTARG PARAM_LIST; \
 try { \
   OATPP_MACRO_FIRSTARG PARAM_LIST = __request->readBodyToDto<TYPE>(getDefaultObjectMapper().get()); \
@@ -224,7 +245,7 @@ try { \
   oatpp::data::stream::BufferOutputStream buffer(pe.getMessage()->getSize() + 20); \
   buffer.writeSimple(pe.getMessage()); \
   buffer.writeSimple(" (Position: "); \
-  buffer.writeAsString(pe.getPosition()); \
+  buffer.writeAsString((v_int64) pe.getPosition()); \
   buffer.writeSimple(")"); \
   return ApiController::handleError(Status::CODE_400, buffer.toString()); \
 } catch (const std::runtime_error &re) {                     \
@@ -234,7 +255,11 @@ try { \
 
 #define OATPP_MACRO_API_CONTROLLER_BODY_DTO_INFO(TYPE, PARAM_LIST) \
 info->body.name = OATPP_MACRO_FIRSTARG_STR PARAM_LIST; \
-info->body.type = TYPE::Class::getType();
+info->body.required = true; \
+info->body.type = TYPE::Class::getType(); \
+if(getDefaultObjectMapper()) { \
+  info->bodyContentType = getDefaultObjectMapper()->getInfo().http_content_type; \
+}
 
 // FOR EACH // ------------------------------------------------------
 

+ 0 - 1
src/oatpp/core/async/worker/IOEventWorker_kqueue.cpp

@@ -31,7 +31,6 @@
 
 #include "oatpp/core/async/Processor.hpp"
 
-#include <unistd.h>
 #include <sys/event.h>
 
 namespace oatpp { namespace async { namespace worker {

+ 58 - 11
src/oatpp/core/base/Environment.cpp

@@ -30,15 +30,22 @@
 #include <iostream>
 #include <cstring>
 #include <ctime>
-#include <stdarg.h>
+#include <cstdarg>
 
 #if defined(WIN32) || defined(_WIN32)
-#include <WinSock2.h>
+	#include <WinSock2.h>
+#endif
 
+#if (defined(WIN32) || defined(_WIN32)) && defined(_WIN64)
 struct tm* localtime_r(time_t *_clock, struct tm *_result) {
     _localtime64_s(_result, _clock);
     return _result;
 }
+#elif (defined(WIN32) || defined(_WIN32)) && not defined(_WIN64)
+struct tm* localtime_r(time_t *_clock, struct tm *_result) {
+    _localtime32_s(_result, _clock);
+    return _result;
+}
 #endif
 
 namespace oatpp { namespace base {
@@ -126,10 +133,16 @@ void DefaultLogger::log(v_uint32 priority, const std::string& tag, const std::st
 }
 
 void DefaultLogger::enablePriority(v_uint32 priority) {
+  if (priority > PRIORITY_E) {
+    return;
+  }
   m_config.logMask |= (1 << priority);
 }
 
 void DefaultLogger::disablePriority(v_uint32 priority) {
+  if (priority > PRIORITY_E) {
+    return;
+  }
   m_config.logMask &= ~(1 << priority);
 }
 
@@ -140,6 +153,27 @@ bool DefaultLogger::isLogPriorityEnabled(v_uint32 priority) {
   return m_config.logMask & (1 << priority);
 }
 
+void LogCategory::enablePriority(v_uint32 priority) {
+  if (priority > Logger::PRIORITY_E) {
+    return;
+  }
+  enabledPriorities |= (1 << priority);
+}
+
+void LogCategory::disablePriority(v_uint32 priority) {
+  if (priority > Logger::PRIORITY_E) {
+    return;
+  }
+  enabledPriorities &= ~(1 << priority);
+}
+
+bool LogCategory::isLogPriorityEnabled(v_uint32 priority) {
+  if (priority > Logger::PRIORITY_E) {
+    return true;
+  }
+  return enabledPriorities & (1 << priority);
+}
+
 void Environment::init() {
   init(std::make_shared<DefaultLogger>());
 }
@@ -287,13 +321,30 @@ void Environment::printCompilationConfig() {
 
 }
 
-void Environment::log(v_int32 priority, const std::string& tag, const std::string& message) {
+void Environment::log(v_uint32 priority, const std::string& tag, const std::string& message) {
   if(m_logger != nullptr) {
     m_logger->log(priority, tag, message);
   }
 }
 
-void Environment::logFormatted(v_int32 priority, const std::string& tag, const char* message, ...) {
+
+void Environment::logFormatted(v_uint32 priority, const LogCategory& category, const char* message, ...) {
+  if (category.categoryEnabled && (category.enabledPriorities & (1 << priority))) {
+    va_list args;
+    va_start(args, message);
+    vlogFormatted(priority, category.tag, message, args);
+    va_end(args);
+  }
+}
+
+void Environment::logFormatted(v_uint32 priority, const std::string& tag, const char* message, ...) {
+    va_list args;
+    va_start(args, message);
+    vlogFormatted(priority, tag, message, args);
+    va_end(args);
+}
+
+void Environment::vlogFormatted(v_uint32 priority, const std::string& tag, const char* message, va_list args) {
   // do we have a logger and the priority is enabled?
   if (m_logger == nullptr || !m_logger->isLogPriorityEnabled(priority)) {
     return;
@@ -304,10 +355,9 @@ void Environment::logFormatted(v_int32 priority, const std::string& tag, const c
     return;
   }
   // check how big our buffer has to be
-  va_list args;
-  va_start(args, message);
-  v_buff_size allocsize = vsnprintf(nullptr, 0, message, args) + 1;
-  va_end(args);
+  va_list argscpy;
+  va_copy(argscpy, args);
+  v_buff_size allocsize = vsnprintf(nullptr, 0, message, argscpy) + 1;
   // alloc the buffer (or the max size)
   if (allocsize > m_logger->getMaxFormattingBufferSize()) {
     allocsize = m_logger->getMaxFormattingBufferSize();
@@ -315,12 +365,9 @@ void Environment::logFormatted(v_int32 priority, const std::string& tag, const c
   auto buffer = std::unique_ptr<char[]>(new char[allocsize]);
   memset(buffer.get(), 0, allocsize);
   // actually format
-  va_start(args, message);
   vsnprintf(buffer.get(), allocsize, message, args);
   // call (user) providen log function
   log(priority, tag, buffer.get());
-  // cleanup
-  va_end(args);
 }
 
 void Environment::registerComponent(const std::string& typeName, const std::string& componentName, void* component) {

+ 87 - 6
src/oatpp/core/base/Environment.hpp

@@ -28,16 +28,16 @@
 
 #include "./Config.hpp"
 
-#include <stdio.h>
+#include <cstdio>
 #include <atomic>
 #include <mutex>
 #include <string>
 #include <unordered_map>
 #include <memory>
 #include <stdexcept>
-#include <stdlib.h>
+#include <cstdlib>
 
-#define OATPP_VERSION "1.2.0"
+#define OATPP_VERSION "1.2.5"
 
 typedef unsigned char v_char8;
 typedef v_char8 *p_char8;
@@ -114,6 +114,7 @@ public:
    * Log priority E-error.
    */
   static constexpr v_uint32 PRIORITY_E = 4;
+
 public:
   /**
    * Virtual Destructor.
@@ -146,6 +147,58 @@ public:
   }
 };
 
+/**
+ * Describes a logging category (i.e. a logging "namespace")
+ */
+class LogCategory {
+ public:
+  /**
+   * Constructs a logging category.
+   * @param pTag - Tag of this logging category
+   * @param pCategoryEnabled - Enable or disable the category completely
+   * @param pEnabledPriorities - Bitmap of initially active logging categories.
+   */
+  LogCategory(std::string pTag, bool pCategoryEnabled, v_uint32 pEnabledPriorities = ((1<<Logger::PRIORITY_V) | (1<<Logger::PRIORITY_D) | (1<<Logger::PRIORITY_I) | (1<<Logger::PRIORITY_W) | (1<<Logger::PRIORITY_E)))
+    : tag(std::move(pTag))
+    , categoryEnabled(pCategoryEnabled)
+    , enabledPriorities(pEnabledPriorities)
+  {};
+
+  /**
+   * The tag for this category
+   */
+  const std::string tag;
+
+  /**
+   * Generally enable or disable this category
+   */
+  bool categoryEnabled;
+
+  /**
+   * Priorities to print that are logged in this category
+   */
+  v_uint32 enabledPriorities;
+
+  /**
+   * Enables logging of a priorities for this category
+   * @param priority - the priority level to enable
+   */
+  void enablePriority(v_uint32 priority);
+
+  /**
+   * Disabled logging of a priorities for this category
+   * @param priority - the priority level to disable
+   */
+  void disablePriority(v_uint32 priority);
+
+  /**
+   * Returns wether or not a priority of this category should be logged/printed
+   * @param priority
+   * @return - true if given priority should be logged
+   */
+  bool isLogPriorityEnabled(v_uint32 priority);
+};
+
 /**
  * Default Logger implementation.
  */
@@ -301,6 +354,7 @@ public:
 private:
   static void registerComponent(const std::string& typeName, const std::string& componentName, void* component);
   static void unregisterComponent(const std::string& typeName, const std::string& componentName);
+  static void vlogFormatted(v_uint32 priority, const std::string& tag, const char* message, va_list args);
 public:
 
   /**
@@ -384,7 +438,7 @@ public:
    * @param tag - tag of the log message.
    * @param message - message.
    */
-  static void log(v_int32 priority, const std::string& tag, const std::string& message);
+  static void log(v_uint32 priority, const std::string& tag, const std::string& message);
 
   /**
    * Format message and call `Logger::log()`<br>
@@ -394,7 +448,17 @@ public:
    * @param message - message.
    * @param ... - format arguments.
    */
-  static void logFormatted(v_int32 priority, const std::string& tag, const char* message, ...);
+  static void logFormatted(v_uint32 priority, const std::string& tag, const char* message, ...);
+
+  /**
+   * Format message and call `Logger::log()`<br>
+   * Message is formatted using `vsnprintf` method.
+   * @param priority - log-priority channel of the message.
+   * @param category - category of the log message.
+   * @param message - message.
+   * @param ... - format arguments.
+   */
+  static void logFormatted(v_uint32 priority, const LogCategory& category, const char* message, ...);
 
   /**
    * Get component object by typeName.
@@ -428,7 +492,24 @@ if(!(EXP)) { \
   OATPP_LOGE("\033[1mASSERT\033[0m[\033[1;31mFAILED\033[0m]", #EXP); \
   exit(EXIT_FAILURE); \
 }
-  
+
+/**
+ * Convenience macro to declare a logging category directly in a class header.
+ * @param NAME - variable-name of the category which is later used to reference the category.
+ */
+#define OATPP_DECLARE_LOG_CATEGORY(NAME) \
+  static oatpp::base::LogCategory NAME;
+
+/**
+ * Convenience macro to implement a logging category directly in a class header.
+ * @param NAME - variable-name of the category which is later used to reference the category.
+ * @param TAG - tag printed with each message printed usig this category.
+ * @param ENABLED - enable or disable a category (bool).
+ */
+#define OATPP_LOG_CATEGORY(NAME, TAG, ENABLED) \
+  oatpp::base::LogCategory NAME = oatpp::base::LogCategory(TAG, ENABLED);
+
+
 #ifndef OATPP_DISABLE_LOGV
 
   /**

+ 79 - 9
src/oatpp/core/base/StrBuffer.cpp

@@ -211,19 +211,89 @@ bool StrBuffer::startsWith(StrBuffer* data) const {
 
 // static
 
-v_buff_size StrBuffer::compare(const void* data1, const void* data2, v_buff_size size) {
-  return std::memcmp(data1, data2, size);
+v_buff_size StrBuffer::compare(const void* data1, v_buff_size size1, const void* data2, v_buff_size size2) {
+
+  if(data1 == data2) return 0;
+  if(data1 == nullptr) return -1;
+  if(data2 == nullptr) return 1;
+
+  if(size1 < size2) {
+    auto res = std::memcmp(data1, data2, size1);
+    if(res == 0) return -1;
+    return res;
+  }
+
+  if(size1 > size2) {
+    auto res = std::memcmp(data1, data2, size2);
+    if(res == 0) return 1;
+    return res;
+  }
+
+  return std::memcmp(data1, data2, size1);
+
 }
 
-v_buff_size StrBuffer::compare(StrBuffer* str1, StrBuffer* str2) {
-  if(str1 == str2) {
-    return 0;
+v_buff_size StrBuffer::compareCI(const void* data1, v_buff_size size1, const void* data2, v_buff_size size2) {
+
+  if(data1 == data2) return 0;
+  if(data1 == nullptr) return -1;
+  if(data2 == nullptr) return 1;
+
+  auto d1 = (p_char8) data1;
+  auto d2 = (p_char8) data2;
+
+  v_buff_size size = size1;
+  if(size2 < size1) size = size2;
+
+  for(v_buff_size i = 0; i < size; i ++) {
+
+    v_char8 a = d1[i];
+    v_char8 b = d2[i];
+
+    if(a >= 'A' && a <= 'Z') a |= 32;
+    if(b >= 'A' && b <= 'Z') b |= 32;
+
+    if(a != b) {
+      return (int) a - (int) b;
+    }
+
   }
-  if(str1->m_size < str2->m_size) {
-    return compare(str1->m_data, str2->m_data, str1->m_size);
-  } else {
-    return compare(str1->m_data, str2->m_data, str2->m_size);
+
+  if(size1 < size2) return -1;
+  if(size1 > size2) return  1;
+
+  return 0;
+
+}
+
+v_buff_size StrBuffer::compareCI_FAST(const void* data1, v_buff_size size1, const void* data2, v_buff_size size2) {
+
+  if(data1 == data2) return 0;
+  if(data1 == nullptr) return -1;
+  if(data2 == nullptr) return 1;
+
+  auto d1 = (p_char8) data1;
+  auto d2 = (p_char8) data2;
+
+  v_buff_size size = size1;
+  if(size2 < size1) size = size2;
+
+  for(v_buff_size i = 0; i < size; i ++) {
+
+    v_char8 a = d1[i] | 32;
+    v_char8 b = d2[i] | 32;
+
+    if(a != b) {
+      return (int) a - (int) b;
+    }
+
   }
+
+  if(size1 < size2) return -1;
+  if(size1 > size2) return  1;
+
+  return 0;
+
 }
 
 bool StrBuffer::equals(const void* data1, const void* data2, v_buff_size size) {

+ 24 - 9
src/oatpp/core/base/StrBuffer.hpp

@@ -28,7 +28,7 @@
 #include "memory/ObjectPool.hpp"
 #include "./Countable.hpp"
 
-#include <cstring> // c
+#include <cstring>
 
 namespace oatpp { namespace base {
 
@@ -254,23 +254,38 @@ public:
   /**
    * Compare data1, data2 using `std::memcmp`.
    * @param data1 - pointer to data1.
+   * @param size1 - size of data1.
    * @param data2 - pointer to data2.
-   * @param size - number of characters to compare.
+   * @param size2 - size of data2.
    * @return - Negative value if the first differing byte (reinterpreted as unsigned char) in data1 is less than the corresponding byte in data2.<br>
-   * 0 if all count bytes of data1 and data2 are equal.<br>
+   * 0 if all count bytes of data1 and data2 are equal.<br>
    * Positive value if the first differing byte in data1 is greater than the corresponding byte in data2.
    */
-  static v_buff_size compare(const void* data1, const void* data2, v_buff_size size);
+  static v_buff_size compare(const void* data1, v_buff_size size1, const void* data2, v_buff_size size2);
 
   /**
-   * Compare data1, data2 using `std::memcmp`.
-   * @param data1 - data1 as `StrBuffer`.
-   * @param data2 - data2 as `StrBuffer`.
+   * Compare data1, data2 - case insensitive.
+   * @param data1 - pointer to data1.
+   * @param size1 - size of data1.
+   * @param data2 - pointer to data2.
+   * @param size2 - size of data2.
+   * @return - Negative value if the first differing byte (reinterpreted as unsigned char) in data1 is less than the corresponding byte in data2.<br>
+   * 0 if all count bytes of data1 and data2 are equal.<br>
+   * Positive value if the first differing byte in data1 is greater than the corresponding byte in data2.
+   */
+  static v_buff_size compareCI(const void* data1, v_buff_size size1, const void* data2, v_buff_size size2);
+
+  /**
+   * Compare data1, data2 - case insensitive (ASCII only, correct compare if one of strings contains letters only).
+   * @param data1 - pointer to data1.
+   * @param size1 - size of data1.
+   * @param data2 - pointer to data2.
+   * @param size2 - size of data2.s
    * @return - Negative value if the first differing byte (reinterpreted as unsigned char) in data1 is less than the corresponding byte in data2.<br>
-   * ​0​ if all count bytes of data1 and data2 are equal.<br>
+   * 0 if all count bytes of data1 and data2 are equal.<br>
    * Positive value if the first differing byte in data1 is greater than the corresponding byte in data2.
    */
-  static v_buff_size compare(StrBuffer* data1, StrBuffer* data2);
+  static v_buff_size compareCI_FAST(const void* data1, v_buff_size size1, const void* data2, v_buff_size size2);
 
   /**
    * Check string equality of data1 to data2.

+ 26 - 12
src/oatpp/core/data/share/LazyStringMap.hpp

@@ -27,6 +27,8 @@
 
 #include "./MemoryLabel.hpp"
 #include "oatpp/core/concurrency/SpinLock.hpp"
+
+#include <map>
 #include <unordered_map>
 
 namespace oatpp { namespace data { namespace share {
@@ -37,20 +39,20 @@ namespace oatpp { namespace data { namespace share {
  * @tparam Key - one of: &id:oatpp::data::share::MemoryLabel;, &id:oatpp::data::share::StringKeyLabel;, &id:oatpp::data::share::StringKeyLabelCI;,
  * &id:oatpp::data::share::StringKeyLabelCI_FAST;.
  */
-template<class Key>
-class LazyStringMap {
+template<typename Key, typename MapType>
+class LazyStringMapTemplate {
 public:
   typedef oatpp::data::mapping::type::String String;
 private:
   mutable concurrency::SpinLock m_lock;
   mutable bool m_fullyInitialized;
-  std::unordered_map<Key, StringKeyLabel> m_map;
+  MapType m_map;
 public:
 
   /**
    * Constructor.
    */
-  LazyStringMap()
+  LazyStringMapTemplate()
     : m_fullyInitialized(true)
   {}
 
@@ -58,12 +60,12 @@ public:
    * Copy-constructor.
    * @param other
    */
-  LazyStringMap(const LazyStringMap& other) {
+  LazyStringMapTemplate(const LazyStringMapTemplate& other) {
 
     std::lock_guard<concurrency::SpinLock> otherLock(other.m_lock);
 
     m_fullyInitialized = other.m_fullyInitialized;
-    m_map = std::unordered_map<Key, StringKeyLabel>(other.m_map);
+    m_map = MapType(other.m_map);
 
   }
 
@@ -71,7 +73,7 @@ public:
    * Move constructor.
    * @param other
    */
-  LazyStringMap(LazyStringMap&& other) {
+  LazyStringMapTemplate(LazyStringMapTemplate&& other) {
 
     std::lock_guard<concurrency::SpinLock> otherLock(other.m_lock);
 
@@ -80,7 +82,7 @@ public:
 
   }
 
-  LazyStringMap& operator = (const LazyStringMap& other) {
+  LazyStringMapTemplate& operator = (const LazyStringMapTemplate& other) {
 
     if(this != &other) {
 
@@ -88,7 +90,7 @@ public:
       std::lock_guard<concurrency::SpinLock> otherLock(other.m_lock);
 
       m_fullyInitialized = other.m_fullyInitialized;
-      m_map = std::unordered_map<Key, StringKeyLabel>(other.m_map);
+      m_map = MapType(other.m_map);
 
     }
 
@@ -96,7 +98,7 @@ public:
 
   }
 
-  LazyStringMap& operator = (LazyStringMap&& other) {
+  LazyStringMapTemplate& operator = (LazyStringMapTemplate&& other) {
 
     if(this != &other) {
 
@@ -249,7 +251,7 @@ public:
    * Get map of all values.
    * @return
    */
-  const std::unordered_map<Key, StringKeyLabel>& getAll() const {
+  const MapType& getAll() const {
 
     std::lock_guard<concurrency::SpinLock> lock(m_lock);
 
@@ -271,7 +273,7 @@ public:
    * Get map of all values without allocating memory for those keys/values.
    * @return
    */
-  const std::unordered_map<Key, StringKeyLabel>& getAll_Unsafe() const {
+  const MapType& getAll_Unsafe() const {
     return m_map;
   }
 
@@ -286,6 +288,18 @@ public:
 
 };
 
+/**
+ * Convenience template for &l:LazyStringMapTemplate;. Based on `std::unordered_map`.
+ */
+template<typename Key, typename Value = StringKeyLabel>
+using LazyStringMap = LazyStringMapTemplate<Key, std::unordered_map<Key, Value>>;
+
+/**
+ * Convenience template for &l:LazyStringMapTemplate;. Based on `std::unordered_map`.
+ */
+template<typename Key, typename Value = StringKeyLabel>
+using LazyStringMultimap = LazyStringMapTemplate<Key, std::multimap<Key, Value>>;
+
 }}}
 
 #endif //oatpp_data_share_LazyStringMap_hpp

+ 25 - 1
src/oatpp/core/data/share/MemoryLabel.hpp

@@ -213,7 +213,15 @@ public:
   inline bool operator!=(const StringKeyLabel &other) const {
     return !(m_size == other.m_size && base::StrBuffer::equals(m_data, other.m_data, m_size));
   }
-  
+
+  inline bool operator < (const StringKeyLabel &other) const {
+    return base::StrBuffer::compare(m_data, m_size, other.m_data, other.m_size) < 0;
+  }
+
+  inline bool operator > (const StringKeyLabel &other) const {
+    return base::StrBuffer::compare(m_data, m_size, other.m_data, other.m_size) > 0;
+  }
+
 };
 
 /**
@@ -268,6 +276,14 @@ public:
     return !(m_size == other.m_size && base::StrBuffer::equalsCI(m_data, other.m_data, m_size));
   }
 
+  inline bool operator < (const StringKeyLabelCI &other) const {
+    return base::StrBuffer::compareCI(m_data, m_size, other.m_data, other.m_size) < 0;
+  }
+
+  inline bool operator > (const StringKeyLabelCI &other) const {
+    return base::StrBuffer::compareCI(m_data, m_size, other.m_data, other.m_size) > 0;
+  }
+
 };
 
 /**
@@ -323,6 +339,14 @@ public:
   inline bool operator!=(const StringKeyLabelCI_FAST &other) const {
     return !(m_size == other.m_size && base::StrBuffer::equalsCI_FAST(m_data, other.m_data, m_size));
   }
+
+  inline bool operator < (const StringKeyLabelCI_FAST &other) const {
+    return base::StrBuffer::compareCI_FAST(m_data, m_size, other.m_data, other.m_size) < 0;
+  }
+
+  inline bool operator > (const StringKeyLabelCI_FAST &other) const {
+    return base::StrBuffer::compareCI_FAST(m_data, m_size, other.m_data, other.m_size) > 0;
+  }
   
 };
   

+ 0 - 1
src/oatpp/core/parser/Caret.cpp

@@ -24,7 +24,6 @@
 
 #include "Caret.hpp"
 
-#include <stdlib.h>
 #include <cstdlib>
 #include <algorithm>
 

+ 0 - 2
src/oatpp/encoding/Unicode.cpp

@@ -24,8 +24,6 @@
 
 #include "Unicode.hpp"
 
-#include "./Hex.hpp"
-
 #if defined(WIN32) || defined(_WIN32)
   #include <Winsock2.h>
 #else

+ 53 - 5
src/oatpp/network/Server.cpp

@@ -26,6 +26,7 @@
 
 #include <thread>
 #include <chrono>
+#include <utility>
 
 namespace oatpp { namespace network {
 
@@ -42,14 +43,38 @@ Server::Server(const std::shared_ptr<ServerConnectionProvider> &connectionProvid
     , m_connectionHandler(connectionHandler)
     , m_threaded(false) {}
 
-void Server::mainLoop(Server *instance) {
+// This isn't implemented as static since threading is dropped and therefore static isn't needed anymore.
+void Server::conditionalMainLoop() {
+  setStatus(STATUS_STARTING, STATUS_RUNNING);
+  std::shared_ptr<const std::unordered_map<oatpp::String, oatpp::String>> params;
 
-  instance->setStatus(STATUS_STARTING, STATUS_RUNNING);
+  while (getStatus() == STATUS_RUNNING) {
+    if (m_condition()) {
+      auto connection = m_connectionProvider->get();
+
+      if (connection) {
+        if (getStatus() == STATUS_RUNNING) {
+          if (m_condition()) {
+            m_connectionHandler->handleConnection(connection, params /* null params */);
+          } else {
+            setStatus(STATUS_STOPPING);
+          }
+        } else {
+          OATPP_LOGD("[oatpp::network::server::mainLoop()]", "Error. Server already stopped - closing connection...");
+        }
+      }
+    } else {
+      setStatus(STATUS_STOPPING);
+    }
+  }
+  setStatus(STATUS_DONE);
+}
 
+void Server::mainLoop(Server *instance) {
+  instance->setStatus(STATUS_STARTING, STATUS_RUNNING);
   std::shared_ptr<const std::unordered_map<oatpp::String, oatpp::String>> params;
 
-  while (instance->getStatus() == STATUS_RUNNING) {
-
+ while (instance->getStatus() == STATUS_RUNNING) {
     auto connection = instance->m_connectionProvider->get();
 
     if (connection) {
@@ -59,15 +84,38 @@ void Server::mainLoop(Server *instance) {
         OATPP_LOGD("[oatpp::network::server::mainLoop()]", "Error. Server already stopped - closing connection...");
       }
     }
-
   }
 
+
   instance->setStatus(STATUS_DONE);
 
 }
 
+void Server::run(std::function<bool()> conditional) {
+  std::unique_lock<std::mutex> ul(m_mutex);
+  switch (getStatus()) {
+    case STATUS_STARTING:
+      throw std::runtime_error("[oatpp::network::server::run()] Error. Server already starting");
+    case STATUS_RUNNING:
+      throw std::runtime_error("[oatpp::network::server::run()] Error. Server already started");
+  }
+
+  m_threaded = false;
+  setStatus(STATUS_CREATED, STATUS_STARTING);
+
+  if (conditional) {
+    m_condition = std::move(conditional);
+    ul.unlock(); // early unlock
+    conditionalMainLoop();
+  } else {
+    ul.unlock();
+    mainLoop(this);
+  }
+}
+
 void Server::run(bool startAsNewThread) {
   std::unique_lock<std::mutex> ul(m_mutex);
+  OATPP_LOGW("[oatpp::network::server::run(bool)]", "Using oatpp::network::server::run(bool) is deprecated and will be removed in the next release. Please implement your own threading.")
   switch (getStatus()) {
     case STATUS_STARTING:
       throw std::runtime_error("[oatpp::network::server::run()] Error. Server already starting");

+ 14 - 2
src/oatpp/network/Server.hpp

@@ -35,6 +35,7 @@
 
 #include <atomic>
 #include <thread>
+#include <functional>
 
 namespace oatpp { namespace network {
 
@@ -46,13 +47,15 @@ class Server : public base::Countable {
 private:
 
   static void mainLoop(Server *instance);
-  
+  void conditionalMainLoop();
+
   bool setStatus(v_int32 expectedStatus, v_int32 newStatus);
   void setStatus(v_int32 status);
 
 private:
 
   std::atomic<v_int32> m_status;
+  std::function<bool()> m_condition;
   std::thread m_thread;
   std::mutex m_mutex;
 
@@ -111,12 +114,21 @@ public:
     return std::make_shared<Server>(connectionProvider, connectionHandler);
   }
 
+  /**
+   * Call &id:oatpp::network::ConnectionProvider::getConnection; in the loop and passes obtained Connection
+   * to &id:oatpp::network::ConnectionHandler;.
+   * @param conditional - Function that is called every mainloop iteration to check if the server should continue to run <br>
+   * Return true to let the server continue, false to shut it down.
+   */
+  void run(std::function<bool()> conditional = nullptr);
+
   /**
    * Call &id:oatpp::network::ConnectionProvider::getConnection; in the loop and passes obtained Connection
    * to &id:oatpp::network::ConnectionHandler;.
    * @param startAsNewThread - Start the server blocking (thread of callee) or non-blocking (own thread)
+   * @deprecated Deprecated since 1.3.0, will be removed in the next release.
    */
-  void run(bool startAsNewThread = false);
+  void run(bool startAsNewThread);
 
   /**
    * Break server loop.

+ 0 - 1
src/oatpp/network/tcp/client/ConnectionProvider.cpp

@@ -25,7 +25,6 @@
 #include "./ConnectionProvider.hpp"
 
 #include "oatpp/network/tcp/Connection.hpp"
-#include "oatpp/core/data/stream/ChunkedBuffer.hpp"
 #include "oatpp/core/utils/ConversionUtils.hpp"
 
 #include <fcntl.h>

+ 0 - 9
src/oatpp/web/client/HttpRequestExecutor.cpp

@@ -26,21 +26,12 @@
 
 #include "oatpp/web/protocol/http/incoming/ResponseHeadersReader.hpp"
 #include "oatpp/web/protocol/http/outgoing/Request.hpp"
-#include "oatpp/web/protocol/http/outgoing/BufferBody.hpp"
 
 #include "oatpp/network/tcp/Connection.hpp"
-#include "oatpp/core/data/stream/ChunkedBuffer.hpp"
 #include "oatpp/core/data/stream/StreamBufferedProxy.hpp"
 
-#include <stdlib.h>
-#include <sys/types.h>
 #if defined(WIN32) || defined(_WIN32)
 #include <io.h>
-#else
-#include <sys/socket.h>
-#include <netdb.h>
-#include <netinet/in.h>
-#include <unistd.h>
 #endif
 
 namespace oatpp { namespace web { namespace client {

+ 1 - 1
src/oatpp/web/mime/multipart/Multipart.hpp

@@ -34,7 +34,7 @@ namespace oatpp { namespace web { namespace mime { namespace multipart {
  * Typedef for headers map. Headers map key is case-insensitive.
  * For more info see &id:oatpp::data::share::LazyStringMap;.
  */
-typedef oatpp::data::share::LazyStringMap<oatpp::data::share::StringKeyLabelCI_FAST> Headers;
+typedef oatpp::data::share::LazyStringMultimap<oatpp::data::share::StringKeyLabelCI_FAST> Headers;
 
 /**
  * Abstract Multipart.

+ 1 - 1
src/oatpp/web/mime/multipart/Part.hpp

@@ -39,7 +39,7 @@ public:
    * Typedef for headers map. Headers map key is case-insensitive.
    * For more info see &id:oatpp::data::share::LazyStringMap;.
    */
-  typedef oatpp::data::share::LazyStringMap<oatpp::data::share::StringKeyLabelCI_FAST> Headers;
+  typedef oatpp::data::share::LazyStringMultimap<oatpp::data::share::StringKeyLabelCI_FAST> Headers;
 private:
   oatpp::String m_name;
   oatpp::String m_filename;

+ 3 - 3
src/oatpp/web/mime/multipart/StatefulParser.hpp

@@ -51,7 +51,7 @@ private:
    * Typedef for headers map. Headers map key is case-insensitive.
    * For more info see &id:oatpp::data::share::LazyStringMap;.
    */
-  typedef oatpp::data::share::LazyStringMap<oatpp::data::share::StringKeyLabelCI_FAST> Headers;
+  typedef oatpp::data::share::LazyStringMultimap<oatpp::data::share::StringKeyLabelCI_FAST> Headers;
 public:
 
   /**
@@ -63,7 +63,7 @@ public:
      * Typedef for headers map. Headers map key is case-insensitive.
      * For more info see &id:oatpp::data::share::LazyStringMap;.
      */
-    typedef oatpp::data::share::LazyStringMap<oatpp::data::share::StringKeyLabelCI_FAST> Headers;
+    typedef oatpp::data::share::LazyStringMultimap<oatpp::data::share::StringKeyLabelCI_FAST> Headers;
   public:
 
     /**
@@ -100,7 +100,7 @@ public:
      * Typedef for headers map. Headers map key is case-insensitive.
      * For more info see &id:oatpp::data::share::LazyStringMap;.
      */
-    typedef oatpp::data::share::LazyStringMap<oatpp::data::share::StringKeyLabelCI_FAST> Headers;
+    typedef oatpp::data::share::LazyStringMultimap<oatpp::data::share::StringKeyLabelCI_FAST> Headers;
   public:
 
     /**

+ 8 - 6
src/oatpp/web/protocol/http/Http.cpp

@@ -334,9 +334,10 @@ void Parser::parseResponseStartingLine(ResponseStartingLine& line,
 }
   
 void Parser::parseOneHeader(Headers& headers,
-                              const std::shared_ptr<oatpp::base::StrBuffer>& headersText,
-                              oatpp::parser::Caret& caret,
-                              Status& error) {
+                            const std::shared_ptr<oatpp::base::StrBuffer>& headersText,
+                            oatpp::parser::Caret& caret,
+                            Status& error)
+{
   caret.skipChar(' ');
   auto name = parseHeaderNameLabel(headersText, caret);
   if(name.getData() != nullptr) {
@@ -357,9 +358,10 @@ void Parser::parseOneHeader(Headers& headers,
 }
 
 void Parser::parseHeaders(Headers& headers,
-                            const std::shared_ptr<oatpp::base::StrBuffer>& headersText,
-                            oatpp::parser::Caret& caret,
-                            Status& error) {
+                          const std::shared_ptr<oatpp::base::StrBuffer>& headersText,
+                          oatpp::parser::Caret& caret,
+                          Status& error)
+{
   
   while (!caret.isAtRN()) {
     parseOneHeader(headers, headersText, caret, error);

+ 1 - 1
src/oatpp/web/protocol/http/Http.hpp

@@ -43,7 +43,7 @@ namespace oatpp { namespace web { namespace protocol { namespace http {
  * Typedef for headers map. Headers map key is case-insensitive.
  * For more info see &id:oatpp::data::share::LazyStringMap;.
  */
-typedef oatpp::data::share::LazyStringMap<oatpp::data::share::StringKeyLabelCI_FAST> Headers;
+typedef oatpp::data::share::LazyStringMultimap<oatpp::data::share::StringKeyLabelCI_FAST> Headers;
 
 /**
  * Typedef for query parameters map.

+ 7 - 5
src/oatpp/web/protocol/http/incoming/Request.cpp

@@ -28,13 +28,11 @@ namespace oatpp { namespace web { namespace protocol { namespace http { namespac
 
 Request::Request(const std::shared_ptr<oatpp::data::stream::IOStream>& connection,
                  const http::RequestStartingLine& startingLine,
-                 const url::mapping::Pattern::MatchMap& pathVariables,
                  const http::Headers& headers,
                  const std::shared_ptr<oatpp::data::stream::InputStream>& bodyStream,
                  const std::shared_ptr<const http::incoming::BodyDecoder>& bodyDecoder)
   : m_connection(connection)
   , m_startingLine(startingLine)
-  , m_pathVariables(pathVariables)
   , m_headers(headers)
   , m_bodyStream(bodyStream)
   , m_bodyDecoder(bodyDecoder)
@@ -43,11 +41,11 @@ Request::Request(const std::shared_ptr<oatpp::data::stream::IOStream>& connectio
 
 std::shared_ptr<Request> Request::createShared(const std::shared_ptr<oatpp::data::stream::IOStream>& connection,
                                                const http::RequestStartingLine& startingLine,
-                                               const url::mapping::Pattern::MatchMap& pathVariables,
                                                const http::Headers& headers,
                                                const std::shared_ptr<oatpp::data::stream::InputStream>& bodyStream,
-                                               const std::shared_ptr<const http::incoming::BodyDecoder>& bodyDecoder) {
-  return Shared_Incoming_Request_Pool::allocateShared(connection, startingLine, pathVariables, headers, bodyStream, bodyDecoder);
+                                               const std::shared_ptr<const http::incoming::BodyDecoder>& bodyDecoder)
+{
+  return Shared_Incoming_Request_Pool::allocateShared(connection, startingLine, headers, bodyStream, bodyDecoder);
 }
 
 std::shared_ptr<oatpp::data::stream::IOStream> Request::getConnection() {
@@ -58,6 +56,10 @@ const http::RequestStartingLine& Request::getStartingLine() const {
   return m_startingLine;
 }
 
+void Request::setPathVariables(const url::mapping::Pattern::MatchMap& pathVariables) {
+  m_pathVariables = pathVariables;
+}
+
 const url::mapping::Pattern::MatchMap& Request::getPathVariables() const {
   return m_pathVariables;
 }

+ 6 - 2
src/oatpp/web/protocol/http/incoming/Request.hpp

@@ -60,7 +60,6 @@ public:
   
   Request(const std::shared_ptr<oatpp::data::stream::IOStream>& connection,
           const http::RequestStartingLine& startingLine,
-          const url::mapping::Pattern::MatchMap& pathVariables,
           const http::Headers& headers,
           const std::shared_ptr<oatpp::data::stream::InputStream>& bodyStream,
           const std::shared_ptr<const http::incoming::BodyDecoder>& bodyDecoder);
@@ -68,7 +67,6 @@ public:
   
   static std::shared_ptr<Request> createShared(const std::shared_ptr<oatpp::data::stream::IOStream>& connection,
                                                const http::RequestStartingLine& startingLine,
-                                               const url::mapping::Pattern::MatchMap& pathVariables,
                                                const http::Headers& headers,
                                                const std::shared_ptr<oatpp::data::stream::InputStream>& bodyStream,
                                                const std::shared_ptr<const http::incoming::BodyDecoder>& bodyDecoder);
@@ -108,6 +106,12 @@ public:
    */
   const http::RequestStartingLine& getStartingLine() const;
 
+  /**
+   * Set request path variables.
+   * @param pathVariables - &id:oatpp::web::url::mapping::Pattern::MatchMap;.
+   */
+  void setPathVariables(const url::mapping::Pattern::MatchMap& pathVariables);
+
   /**
    * Get path variables according to path-pattern. <br>
    * Ex. given request path="/sum/19/1" for path-pattern="/sum/{a}/{b}" <br>

+ 23 - 37
src/oatpp/web/protocol/http/utils/CommunicationUtils.cpp

@@ -31,29 +31,29 @@ bool CommunicationUtils::headerEqualsCI_FAST(const oatpp::data::share::MemoryLab
   return size == headerValue.getSize() && oatpp::base::StrBuffer::equalsCI_FAST(headerValue.getData(), value, size);
 }
   
-v_int32 CommunicationUtils::considerConnectionState(const std::shared_ptr<protocol::http::incoming::Request>& request,
-                                                    const std::shared_ptr<protocol::http::outgoing::Response>& response){
-  
+void CommunicationUtils::considerConnectionState(const std::shared_ptr<protocol::http::incoming::Request>& request,
+                                                 const std::shared_ptr<protocol::http::outgoing::Response>& response,
+                                                 ConnectionState& connectionState)
+{
+
+  if(connectionState != ConnectionState::ALIVE) {
+    return;
+  }
+
   auto outState = response->getHeaders().getAsMemoryLabel<oatpp::data::share::StringKeyLabelCI_FAST>(Header::CONNECTION);
   if(outState && outState == Header::Value::CONNECTION_UPGRADE) {
-    return CONNECTION_STATE_UPGRADE;
+    connectionState = ConnectionState::DELEGATED;
+    return;
   }
   
   if(request) {
-    /* Set keep-alive to value specified in the client's request, if no Connection header present in response. */
-    /* Set keep-alive to value specified in response otherwise */
+    /* If the connection header is present in the request and its value isn't keep-alive, then close */
     auto connection = request->getHeaders().getAsMemoryLabel<oatpp::data::share::StringKeyLabelCI_FAST>(Header::CONNECTION);
-    if(connection && connection == Header::Value::CONNECTION_KEEP_ALIVE) {
-      if(outState) {
-        if(outState == Header::Value::CONNECTION_KEEP_ALIVE) {
-          return CONNECTION_STATE_KEEP_ALIVE;
-        } else {
-          return CONNECTION_STATE_CLOSE;
-        }
-      } else {
-        response->putHeader(Header::CONNECTION, Header::Value::CONNECTION_KEEP_ALIVE);
-        return CONNECTION_STATE_KEEP_ALIVE;
+    if(connection) {
+      if(connection != Header::Value::CONNECTION_KEEP_ALIVE) {
+        connectionState = ConnectionState::CLOSING;
       }
+      return;
     }
     
     /* If protocol == HTTP/1.1 */
@@ -61,35 +61,21 @@ v_int32 CommunicationUtils::considerConnectionState(const std::shared_ptr<protoc
     /* Set keep-alive to value specified in response otherwise */
     auto& protocol = request->getStartingLine().protocol;
     if(protocol && headerEqualsCI_FAST(protocol, "HTTP/1.1")) {
-      if(outState) {
-        if(outState == Header::Value::CONNECTION_KEEP_ALIVE) {
-          return CONNECTION_STATE_KEEP_ALIVE;
-        } else {
-          return CONNECTION_STATE_CLOSE;
-        }
-      } else {
-        response->putHeader(Header::CONNECTION, Header::Value::CONNECTION_KEEP_ALIVE);
-        return CONNECTION_STATE_KEEP_ALIVE;
+      if(outState && outState != Header::Value::CONNECTION_KEEP_ALIVE) {
+        connectionState = ConnectionState::CLOSING;
       }
+      return;
     }
-    
   }
   
   /* If protocol != HTTP/1.1 */
   /* Set default Connection header value (Close), if no Connection header present in response. */
   /* Set keep-alive to value specified in response otherwise */
-  if(outState) {
-    if(outState == Header::Value::CONNECTION_KEEP_ALIVE) {
-      return CONNECTION_STATE_KEEP_ALIVE;
-    } else {
-      return CONNECTION_STATE_CLOSE;
-    }
-  } else {
-    response->putHeader(Header::CONNECTION, Header::Value::CONNECTION_CLOSE);
-    return CONNECTION_STATE_CLOSE;
+  if(!outState || outState != Header::Value::CONNECTION_KEEP_ALIVE) {
+    connectionState = ConnectionState::CLOSING;
   }
-  
-  return CONNECTION_STATE_CLOSE;
+
+  return;
   
 }
 

+ 10 - 21
src/oatpp/web/protocol/http/utils/CommunicationUtils.hpp

@@ -36,20 +36,14 @@ namespace oatpp { namespace web { namespace protocol { namespace http { namespac
  */
 class CommunicationUtils {
 public:
-  /**
-   * Connection state - close.
-   */
-  static constexpr v_int32 CONNECTION_STATE_CLOSE = 0;
 
-  /**
-   * Connection state - keep alive.
-   */
-  static constexpr v_int32 CONNECTION_STATE_KEEP_ALIVE = 1;
+  enum class ConnectionState : int {
+    ALIVE = 0, // Continue processing connection.
+    DELEGATED = 1, // Stop current connection processing as connection was delegated to other processor.
+    CLOSING = 2, // Move connection to "closing" pool.
+    DEAD = 3 // Drop immediately
+  };
 
-  /**
-   * Connection state - upgrade.
-   */
-  static constexpr v_int32 CONNECTION_STATE_UPGRADE = 2;
 private:
   static bool headerEqualsCI_FAST(const oatpp::data::share::MemoryLabel& headerValue, const char* value);
 public:
@@ -57,18 +51,13 @@ public:
   /**
    * Consider keep connection alive taking into account request headers, response headers and protocol version.<br>
    * Corresponding header will be set to response if not existed before. <br>
-   * return one of (CONNECTION_STATE_CLOSE, CONNECTION_STATE_KEEP_ALIVE, CONNECTION_STATE_UPGRADE).
    * @param request - `std::shared_ptr` to &id:oatpp::web::protocol::http::incoming::Request;
    * @param response - `std::shared_ptr` to &id:oatpp::web::protocol::http::outgoing::Response;
-   * @return - one of values:
-   * <ul>
-   *   <li>&l:CommunicationUtils::CONNECTION_STATE_CLOSE;</li>
-   *   <li>&l:CommunicationUtils::CONNECTION_STATE_KEEP_ALIVE;</li>
-   *   <li>&l:CommunicationUtils::CONNECTION_STATE_UPGRADE;</li>
-   * </ul>
+   * @param connectionState
    */
-  static v_int32 considerConnectionState(const std::shared_ptr<protocol::http::incoming::Request>& request,
-                                         const std::shared_ptr<protocol::http::outgoing::Response>& response);
+  static void considerConnectionState(const std::shared_ptr<protocol::http::incoming::Request>& request,
+                                      const std::shared_ptr<protocol::http::outgoing::Response>& response,
+                                      ConnectionState& connectionState);
 
   static std::shared_ptr<encoding::EncoderProvider> selectEncoder(const std::shared_ptr<http::incoming::Request>& request,
                                                                   const std::shared_ptr<http::encoding::ProviderCollection>& providers);

+ 6 - 2
src/oatpp/web/server/AsyncHttpConnectionHandler.cpp

@@ -55,8 +55,12 @@ void AsyncHttpConnectionHandler::setErrorHandler(const std::shared_ptr<handler::
   }
 }
 
-void AsyncHttpConnectionHandler::addRequestInterceptor(const std::shared_ptr<handler::RequestInterceptor>& interceptor) {
-  m_components->requestInterceptors->pushBack(interceptor);
+void AsyncHttpConnectionHandler::addRequestInterceptor(const std::shared_ptr<interceptor::RequestInterceptor>& interceptor) {
+  m_components->requestInterceptors.push_back(interceptor);
+}
+
+void AsyncHttpConnectionHandler::addResponseInterceptor(const std::shared_ptr<interceptor::ResponseInterceptor>& interceptor) {
+  m_components->responseInterceptors.push_back(interceptor);
 }
 
 void AsyncHttpConnectionHandler::handleConnection(const std::shared_ptr<IOStream>& connection,

+ 15 - 6
src/oatpp/web/server/AsyncHttpConnectionHandler.hpp

@@ -26,10 +26,6 @@
 #define oatpp_web_server_AsyncHttpConnectionHandler_hpp
 
 #include "oatpp/web/server/HttpProcessor.hpp"
-#include "oatpp/web/server/HttpRouter.hpp"
-#include "oatpp/web/server/handler/ErrorHandler.hpp"
-#include "oatpp/web/server/handler/Interceptor.hpp"
-
 #include "oatpp/network/ConnectionHandler.hpp"
 #include "oatpp/core/async/Executor.hpp"
 
@@ -110,8 +106,21 @@ public:
                                                                   const std::shared_ptr<oatpp::async::Executor>& executor);
   
   void setErrorHandler(const std::shared_ptr<handler::ErrorHandler>& errorHandler);
-  
-  void addRequestInterceptor(const std::shared_ptr<handler::RequestInterceptor>& interceptor);
+
+  /**
+   * Add request interceptor. Request interceptors are called before routing happens.
+   * If multiple interceptors set then the order of interception is the same as the order of calls to `addRequestInterceptor`.
+   * @param interceptor - &id:oatpp::web::server::interceptor::RequestInterceptor;.
+   */
+  void addRequestInterceptor(const std::shared_ptr<interceptor::RequestInterceptor>& interceptor);
+
+  /**
+   * Add response interceptor.
+   * If multiple interceptors set then the order of interception is the same as the order of calls to `addResponseInterceptor`.
+   * @param interceptor - &id:oatpp::web::server::interceptor::RequestInterceptor;.
+   */
+  void addResponseInterceptor(const std::shared_ptr<interceptor::ResponseInterceptor>& interceptor);
+
   
   void handleConnection(const std::shared_ptr<IOStream>& connection, const std::shared_ptr<const ParameterMap>& params) override;
 

+ 6 - 2
src/oatpp/web/server/HttpConnectionHandler.cpp

@@ -52,8 +52,12 @@ void HttpConnectionHandler::setErrorHandler(const std::shared_ptr<handler::Error
   }
 }
 
-void HttpConnectionHandler::addRequestInterceptor(const std::shared_ptr<handler::RequestInterceptor>& interceptor) {
-  m_components->requestInterceptors->pushBack(interceptor);
+void HttpConnectionHandler::addRequestInterceptor(const std::shared_ptr<interceptor::RequestInterceptor>& interceptor) {
+  m_components->requestInterceptors.push_back(interceptor);
+}
+
+void HttpConnectionHandler::addResponseInterceptor(const std::shared_ptr<interceptor::ResponseInterceptor>& interceptor) {
+  m_components->responseInterceptors.push_back(interceptor);
 }
   
 void HttpConnectionHandler::handleConnection(const std::shared_ptr<oatpp::data::stream::IOStream>& connection,

+ 12 - 8
src/oatpp/web/server/HttpConnectionHandler.hpp

@@ -25,12 +25,8 @@
 #ifndef oatpp_web_server_HttpConnectionHandler_hpp
 #define oatpp_web_server_HttpConnectionHandler_hpp
 
-#include "./HttpProcessor.hpp"
-#include "./handler/ErrorHandler.hpp"
-#include "./HttpRouter.hpp"
-
+#include "oatpp/web/server/HttpProcessor.hpp"
 #include "oatpp/network/ConnectionHandler.hpp"
-#include "oatpp/network/tcp/Connection.hpp"
 
 namespace oatpp { namespace web { namespace server {
 
@@ -84,10 +80,18 @@ public:
   void setErrorHandler(const std::shared_ptr<handler::ErrorHandler>& errorHandler);
 
   /**
-   * Set request interceptor. Request intercepted after route is resolved but before corresponding route endpoint is called.
-   * @param interceptor - &id:oatpp::web::server::handler::RequestInterceptor;.
+   * Add request interceptor. Request interceptors are called before routing happens.
+   * If multiple interceptors set then the order of interception is the same as the order of calls to `addRequestInterceptor`.
+   * @param interceptor - &id:oatpp::web::server::interceptor::RequestInterceptor;.
+   */
+  void addRequestInterceptor(const std::shared_ptr<interceptor::RequestInterceptor>& interceptor);
+
+  /**
+   * Add response interceptor.
+   * If multiple interceptors set then the order of interception is the same as the order of calls to `addResponseInterceptor`.
+   * @param interceptor - &id:oatpp::web::server::interceptor::RequestInterceptor;.
    */
-  void addRequestInterceptor(const std::shared_ptr<handler::RequestInterceptor>& interceptor);
+  void addResponseInterceptor(const std::shared_ptr<interceptor::ResponseInterceptor>& interceptor);
 
   /**
    * Implementation of &id:oatpp::network::ConnectionHandler::handleConnection;.

+ 166 - 92
src/oatpp/web/server/HttpProcessor.cpp

@@ -25,6 +25,7 @@
 #include "HttpProcessor.hpp"
 
 #include "oatpp/web/protocol/http/incoming/SimpleBodyDecoder.hpp"
+#include "oatpp/core/data/stream/BufferStream.hpp"
 
 namespace oatpp { namespace web { namespace server {
 
@@ -35,13 +36,15 @@ HttpProcessor::Components::Components(const std::shared_ptr<HttpRouter>& pRouter
                                       const std::shared_ptr<protocol::http::encoding::ProviderCollection>& pContentEncodingProviders,
                                       const std::shared_ptr<const oatpp::web::protocol::http::incoming::BodyDecoder>& pBodyDecoder,
                                       const std::shared_ptr<handler::ErrorHandler>& pErrorHandler,
-                                      const std::shared_ptr<RequestInterceptors>& pRequestInterceptors,
+                                      const RequestInterceptors& pRequestInterceptors,
+                                      const ResponseInterceptors& pResponseInterceptors,
                                       const std::shared_ptr<Config>& pConfig)
   : router(pRouter)
   , contentEncodingProviders(pContentEncodingProviders)
   , bodyDecoder(pBodyDecoder)
   , errorHandler(pErrorHandler)
   , requestInterceptors(pRequestInterceptors)
+  , responseInterceptors(pResponseInterceptors)
   , config(pConfig)
 {}
 
@@ -50,7 +53,8 @@ HttpProcessor::Components::Components(const std::shared_ptr<HttpRouter>& pRouter
                nullptr,
                std::make_shared<oatpp::web::protocol::http::incoming::SimpleBodyDecoder>(),
                handler::DefaultErrorHandler::createShared(),
-               std::make_shared<RequestInterceptors>(),
+               {},
+               {},
                std::make_shared<Config>())
 {}
 
@@ -59,7 +63,8 @@ HttpProcessor::Components::Components(const std::shared_ptr<HttpRouter>& pRouter
                nullptr,
                std::make_shared<oatpp::web::protocol::http::incoming::SimpleBodyDecoder>(),
                handler::DefaultErrorHandler::createShared(),
-               std::make_shared<RequestInterceptors>(),
+               {},
+               {},
                pConfig)
 {}
 
@@ -76,99 +81,137 @@ HttpProcessor::ProcessingResources::ProcessingResources(const std::shared_ptr<Co
   , inStream(data::stream::InputStreamBufferedProxy::createShared(connection, base::StrBuffer::createShared(data::buffer::IOBuffer::BUFFER_SIZE)))
 {}
 
-bool HttpProcessor::processNextRequest(ProcessingResources& resources) {
-
-  oatpp::web::protocol::http::HttpError::Info error;
-  auto headersReadResult = resources.headersReader.readHeaders(resources.inStream.get(), error);
-
-  if(error.status.code != 0) {
-    auto response = resources.components->errorHandler->handleError(error.status, "Invalid request headers");
-    response->send(resources.connection.get(), &resources.headersOutBuffer, nullptr);
-    return false;
-  }
-
-  if(error.ioStatus <= 0) {
-    return false; // connection is in invalid state. should be dropped
-  }
-
-  auto route = resources.components->router->getRoute(headersReadResult.startingLine.method, headersReadResult.startingLine.path);
-
-  if(!route) {
-    auto response = resources.components->errorHandler->handleError(protocol::http::Status::CODE_404, "Current url has no mapping");
-    response->send(resources.connection.get(), &resources.headersOutBuffer, nullptr);
-    return false;
-  }
-
-  auto request = protocol::http::incoming::Request::createShared(resources.connection,
-                                                                 headersReadResult.startingLine,
-                                                                 route.matchMap,
-                                                                 headersReadResult.headers,
-                                                                 resources.inStream,
-                                                                 resources.components->bodyDecoder);
+std::shared_ptr<protocol::http::outgoing::Response>
+HttpProcessor::processNextRequest(ProcessingResources& resources,
+                                  const std::shared_ptr<protocol::http::incoming::Request>& request,
+                                  ConnectionState& connectionState)
+{
 
   std::shared_ptr<protocol::http::outgoing::Response> response;
 
   try{
 
-    auto currInterceptor = resources.components->requestInterceptors->getFirstNode();
-    while (currInterceptor != nullptr) {
-      response = currInterceptor->getData()->intercept(request);
+    for(auto& interceptor : resources.components->requestInterceptors) {
+      response = interceptor->intercept(request);
       if(response) {
-        break;
+        return response;
       }
-      currInterceptor = currInterceptor->getNext();
     }
 
-    if(!response) {
-      response = route.getEndpoint()->handle(request);
+    auto route = resources.components->router->getRoute(request->getStartingLine().method, request->getStartingLine().path);
+
+    if(!route) {
+
+      data::stream::BufferOutputStream ss;
+      ss << "No mapping for HTTP-method: '" << request->getStartingLine().method.toString()
+      << "', URL: '" << request->getStartingLine().path.toString() << "'";
+
+      connectionState = ConnectionState::CLOSING;
+      return resources.components->errorHandler->handleError(protocol::http::Status::CODE_404, ss.toString());
+
     }
 
-  } catch (oatpp::web::protocol::http::HttpError& error) {
+    request->setPathVariables(route.getMatchMap());
+    return route.getEndpoint()->handle(request);
 
+  } catch (oatpp::web::protocol::http::HttpError& error) {
     response = resources.components->errorHandler->handleError(error.getInfo().status, error.getMessage(), error.getHeaders());
-    response->send(resources.connection.get(), &resources.headersOutBuffer, nullptr);
-    return false;
-
+    connectionState = ConnectionState::CLOSING;
   } catch (std::exception& error) {
-
     response = resources.components->errorHandler->handleError(protocol::http::Status::CODE_500, error.what());
-    response->send(resources.connection.get(), &resources.headersOutBuffer, nullptr);
-    return false;
-
+    connectionState = ConnectionState::CLOSING;
   } catch (...) {
-    response = resources.components->errorHandler->handleError(protocol::http::Status::CODE_500, "Unknown error");
-    response->send(resources.connection.get(), &resources.headersOutBuffer, nullptr);
-    return false;
+    response = resources.components->errorHandler->handleError(protocol::http::Status::CODE_500, "Unhandled Error");
+    connectionState = ConnectionState::CLOSING;
   }
 
-  response->putHeaderIfNotExists(protocol::http::Header::SERVER, protocol::http::Header::Value::SERVER);
-  auto connectionState = protocol::http::utils::CommunicationUtils::considerConnectionState(request, response);
+  return response;
 
-  auto contentEncoderProvider =
-    protocol::http::utils::CommunicationUtils::selectEncoder(request, resources.components->contentEncodingProviders);
+}
 
-  response->send(resources.connection.get(), &resources.headersOutBuffer, contentEncoderProvider.get());
+HttpProcessor::ConnectionState HttpProcessor::processNextRequest(ProcessingResources& resources) {
 
-  switch(connectionState) {
+  oatpp::web::protocol::http::HttpError::Info error;
+  auto headersReadResult = resources.headersReader.readHeaders(resources.inStream.get(), error);
 
-    case protocol::http::utils::CommunicationUtils::CONNECTION_STATE_KEEP_ALIVE: return true;
+  if(error.ioStatus <= 0) {
+    return ConnectionState::DEAD;
+  }
 
-    case protocol::http::utils::CommunicationUtils::CONNECTION_STATE_UPGRADE: {
+  ConnectionState connectionState = ConnectionState::ALIVE;
+  std::shared_ptr<protocol::http::incoming::Request> request;
+  std::shared_ptr<protocol::http::outgoing::Response> response;
 
-      auto handler = response->getConnectionUpgradeHandler();
-      if(handler) {
-        handler->handleConnection(resources.connection, response->getConnectionUpgradeParameters());
-      } else {
-        OATPP_LOGW("[oatpp::web::server::HttpProcessor::processNextRequest()]", "Warning. ConnectionUpgradeHandler not set!");
+  if(error.status.code != 0) {
+    response = resources.components->errorHandler->handleError(error.status, "Invalid Request Headers");
+    connectionState = ConnectionState::CLOSING;
+  } else {
+
+    request = protocol::http::incoming::Request::createShared(resources.connection,
+                                                              headersReadResult.startingLine,
+                                                              headersReadResult.headers,
+                                                              resources.inStream,
+                                                              resources.components->bodyDecoder);
+
+    response = processNextRequest(resources, request, connectionState);
+
+    try {
+
+      for (auto& interceptor : resources.components->responseInterceptors) {
+        response = interceptor->intercept(request, response);
+        if (!response) {
+          response = resources.components->errorHandler->handleError(
+            protocol::http::Status::CODE_500,
+            "Response Interceptor returned an Invalid Response - 'null'"
+          );
+          connectionState = ConnectionState::CLOSING;
+        }
       }
 
-      return false;
+    } catch (...) {
+      response = resources.components->errorHandler->handleError(
+        protocol::http::Status::CODE_500,
+        "Unhandled Error in Response Interceptor"
+      );
+      connectionState = ConnectionState::CLOSING;
+    }
+
+    response->putHeaderIfNotExists(protocol::http::Header::SERVER, protocol::http::Header::Value::SERVER);
+    protocol::http::utils::CommunicationUtils::considerConnectionState(request, response, connectionState);
+
+    switch(connectionState) {
+
+      case ConnectionState::ALIVE :
+        response->putHeaderIfNotExists(protocol::http::Header::CONNECTION, protocol::http::Header::Value::CONNECTION_KEEP_ALIVE);
+        break;
+
+      case ConnectionState::CLOSING:
+      case ConnectionState::DEAD:
+        response->putHeaderIfNotExists(protocol::http::Header::CONNECTION, protocol::http::Header::Value::CONNECTION_CLOSE);
+        break;
+
+      case ConnectionState::DELEGATED: {
+        auto handler = response->getConnectionUpgradeHandler();
+        if(handler) {
+          handler->handleConnection(resources.connection, response->getConnectionUpgradeParameters());
+          connectionState = ConnectionState::DELEGATED;
+        } else {
+          OATPP_LOGW("[oatpp::web::server::HttpProcessor::processNextRequest()]", "Warning. ConnectionUpgradeHandler not set!");
+          connectionState = ConnectionState::CLOSING;
+        }
+        break;
+      }
 
     }
 
   }
 
-  return false;
+  auto contentEncoderProvider =
+    protocol::http::utils::CommunicationUtils::selectEncoder(request, resources.components->contentEncodingProviders);
+
+  response->send(resources.connection.get(), &resources.headersOutBuffer, contentEncoderProvider.get());
+
+  return connectionState;
 
 }
 
@@ -187,15 +230,15 @@ void HttpProcessor::Task::run(){
 
   ProcessingResources resources(m_components, m_connection);
 
-  bool wantContinue;
+  ConnectionState connectionState;
 
   try {
 
     do {
 
-      wantContinue = HttpProcessor::processNextRequest(resources);
+      connectionState = HttpProcessor::processNextRequest(resources);
 
-    } while (wantContinue);
+    } while (connectionState == ConnectionState::ALIVE);
 
   } catch (...) {
     // DO NOTHING
@@ -214,7 +257,7 @@ HttpProcessor::Coroutine::Coroutine(const std::shared_ptr<Components>& component
   , m_headersReader(&m_headersInBuffer, components->config->headersReaderChunkSize, components->config->headersReaderMaxSize)
   , m_headersOutBuffer(std::make_shared<oatpp::data::stream::BufferOutputStream>(components->config->headersOutBufferInitial))
   , m_inStream(data::stream::InputStreamBufferedProxy::createShared(m_connection, base::StrBuffer::createShared(data::buffer::IOBuffer::BUFFER_SIZE)))
-  , m_connectionState(oatpp::web::protocol::http::utils::CommunicationUtils::CONNECTION_STATE_KEEP_ALIVE)
+  , m_connectionState(ConnectionState::ALIVE)
 {}
 
 HttpProcessor::Coroutine::Action HttpProcessor::Coroutine::act() {
@@ -227,29 +270,33 @@ HttpProcessor::Coroutine::Action HttpProcessor::Coroutine::parseHeaders() {
 
 oatpp::async::Action HttpProcessor::Coroutine::onHeadersParsed(const RequestHeadersReader::Result& headersReadResult) {
 
-  m_currentRoute = m_components->router->getRoute(headersReadResult.startingLine.method.toString(), headersReadResult.startingLine.path.toString());
-
-  if(!m_currentRoute) {
-    m_currentResponse = m_components->errorHandler->handleError(protocol::http::Status::CODE_404, "Current url has no mapping");
-    return yieldTo(&HttpProcessor::Coroutine::onResponseFormed);
-  }
-
   m_currentRequest = protocol::http::incoming::Request::createShared(m_connection,
                                                                      headersReadResult.startingLine,
-                                                                     m_currentRoute.matchMap,
                                                                      headersReadResult.headers,
                                                                      m_inStream,
                                                                      m_components->bodyDecoder);
 
-  auto currInterceptor = m_components->requestInterceptors->getFirstNode();
-  while (currInterceptor != nullptr) {
-    m_currentResponse = currInterceptor->getData()->intercept(m_currentRequest);
+  for(auto& interceptor : m_components->requestInterceptors) {
+    m_currentResponse = interceptor->intercept(m_currentRequest);
     if(m_currentResponse) {
       return yieldTo(&HttpProcessor::Coroutine::onResponseFormed);
     }
-    currInterceptor = currInterceptor->getNext();
   }
 
+  m_currentRoute = m_components->router->getRoute(headersReadResult.startingLine.method.toString(), headersReadResult.startingLine.path.toString());
+
+  if(!m_currentRoute) {
+
+    data::stream::BufferOutputStream ss;
+    ss << "No mapping for HTTP-method: '" << headersReadResult.startingLine.method.toString()
+       << "', URL: '" << headersReadResult.startingLine.path.toString() << "'";
+    m_currentResponse = m_components->errorHandler->handleError(protocol::http::Status::CODE_404, ss.toString());
+    m_connectionState = ConnectionState::CLOSING;
+    return yieldTo(&HttpProcessor::Coroutine::onResponseFormed);
+  }
+
+  m_currentRequest->setPathVariables(m_currentRoute.getMatchMap());
+
   return yieldTo(&HttpProcessor::Coroutine::onRequestFormed);
 
 }
@@ -265,8 +312,43 @@ HttpProcessor::Coroutine::Action HttpProcessor::Coroutine::onResponse(const std:
   
 HttpProcessor::Coroutine::Action HttpProcessor::Coroutine::onResponseFormed() {
 
+  for(auto& interceptor : m_components->responseInterceptors) {
+    m_currentResponse = interceptor->intercept(m_currentRequest, m_currentResponse);
+    if(!m_currentResponse) {
+      m_currentResponse = m_components->errorHandler->handleError(
+        protocol::http::Status::CODE_500,
+        "Response Interceptor returned an Invalid Response - 'null'"
+      );
+    }
+  }
+
   m_currentResponse->putHeaderIfNotExists(protocol::http::Header::SERVER, protocol::http::Header::Value::SERVER);
-  m_connectionState = oatpp::web::protocol::http::utils::CommunicationUtils::considerConnectionState(m_currentRequest, m_currentResponse);
+  oatpp::web::protocol::http::utils::CommunicationUtils::considerConnectionState(m_currentRequest, m_currentResponse, m_connectionState);
+
+  switch(m_connectionState) {
+
+    case ConnectionState::ALIVE :
+      m_currentResponse->putHeaderIfNotExists(protocol::http::Header::CONNECTION, protocol::http::Header::Value::CONNECTION_KEEP_ALIVE);
+      break;
+
+    case ConnectionState::CLOSING:
+    case ConnectionState::DEAD:
+      m_currentResponse->putHeaderIfNotExists(protocol::http::Header::CONNECTION, protocol::http::Header::Value::CONNECTION_CLOSE);
+      break;
+
+    case ConnectionState::DELEGATED: {
+      auto handler = m_currentResponse->getConnectionUpgradeHandler();
+      if(handler) {
+        handler->handleConnection(m_connection, m_currentResponse->getConnectionUpgradeParameters());
+        m_connectionState = ConnectionState::DELEGATED;
+      } else {
+        OATPP_LOGW("[oatpp::web::server::HttpProcessor::Coroutine::onResponseFormed()]", "Warning. ConnectionUpgradeHandler not set!");
+        m_connectionState = ConnectionState::CLOSING;
+      }
+      break;
+    }
+
+  }
 
   auto contentEncoderProvider =
     protocol::http::utils::CommunicationUtils::selectEncoder(m_currentRequest, m_components->contentEncodingProviders);
@@ -278,20 +360,12 @@ HttpProcessor::Coroutine::Action HttpProcessor::Coroutine::onResponseFormed() {
   
 HttpProcessor::Coroutine::Action HttpProcessor::Coroutine::onRequestDone() {
   
-  if(m_connectionState == oatpp::web::protocol::http::utils::CommunicationUtils::CONNECTION_STATE_KEEP_ALIVE) {
+  if(m_connectionState == ConnectionState::ALIVE) {
     return yieldTo(&HttpProcessor::Coroutine::parseHeaders);
   }
   
-  if(m_connectionState == oatpp::web::protocol::http::utils::CommunicationUtils::CONNECTION_STATE_UPGRADE) {
-    auto handler = m_currentResponse->getConnectionUpgradeHandler();
-    if(handler) {
-      handler->handleConnection(m_connection, m_currentResponse->getConnectionUpgradeParameters());
-    } else {
-      OATPP_LOGW("[oatpp::web::server::HttpProcessor::Coroutine::onRequestDone()]", "Warning. ConnectionUpgradeHandler not set!");
-    }
-  }
-  
   return finish();
+
 }
   
 HttpProcessor::Coroutine::Action HttpProcessor::Coroutine::handleError(Error* error) {

+ 22 - 8
src/oatpp/web/server/HttpProcessor.hpp

@@ -27,7 +27,8 @@
 
 #include "./HttpRouter.hpp"
 
-#include "./handler/Interceptor.hpp"
+#include "./interceptor/RequestInterceptor.hpp"
+#include "./interceptor/ResponseInterceptor.hpp"
 #include "./handler/ErrorHandler.hpp"
 
 #include "oatpp/web/protocol/http/encoding/ProviderCollection.hpp"
@@ -48,8 +49,10 @@ namespace oatpp { namespace web { namespace server {
  */
 class HttpProcessor {
 public:
-  typedef oatpp::collection::LinkedList<std::shared_ptr<oatpp::web::server::handler::RequestInterceptor>> RequestInterceptors;
-  typedef oatpp::web::protocol::http::incoming::RequestHeadersReader RequestHeadersReader;
+  typedef std::list<std::shared_ptr<web::server::interceptor::RequestInterceptor>> RequestInterceptors;
+  typedef std::list<std::shared_ptr<web::server::interceptor::ResponseInterceptor>> ResponseInterceptors;
+  typedef web::protocol::http::incoming::RequestHeadersReader RequestHeadersReader;
+  typedef protocol::http::utils::CommunicationUtils::ConnectionState ConnectionState;
 public:
 
   /**
@@ -99,7 +102,8 @@ public:
                const std::shared_ptr<protocol::http::encoding::ProviderCollection>& pContentEncodingProviders,
                const std::shared_ptr<const oatpp::web::protocol::http::incoming::BodyDecoder>& pBodyDecoder,
                const std::shared_ptr<handler::ErrorHandler>& pErrorHandler,
-               const std::shared_ptr<RequestInterceptors>& pRequestInterceptors,
+               const RequestInterceptors& pRequestInterceptors,
+               const ResponseInterceptors& pResponseInterceptors,
                const std::shared_ptr<Config>& pConfig);
 
     /**
@@ -136,9 +140,14 @@ public:
     std::shared_ptr<handler::ErrorHandler> errorHandler;
 
     /**
-     * Collection of request interceptors. &id:oatpp::web::server::handler::RequestInterceptor;.
+     * Collection of request interceptors. &id:oatpp::web::server::interceptor::RequestInterceptor;.
      */
-    std::shared_ptr<RequestInterceptors> requestInterceptors;
+    RequestInterceptors requestInterceptors;
+
+    /**
+     * Collection of request interceptors. &id:oatpp::web::server::interceptor::ResponseInterceptor;.
+     */
+    ResponseInterceptors responseInterceptors;
 
     /**
      * Resource allocation config. &l:HttpProcessor::Config;.
@@ -163,7 +172,12 @@ private:
 
   };
 
-  static bool processNextRequest(ProcessingResources& resources);
+  static
+  std::shared_ptr<protocol::http::outgoing::Response>
+  processNextRequest(ProcessingResources& resources,
+                     const std::shared_ptr<protocol::http::incoming::Request>& request,
+                     ConnectionState& connectionState);
+  static ConnectionState processNextRequest(ProcessingResources& resources);
 
 public:
 
@@ -207,7 +221,7 @@ public:
     RequestHeadersReader m_headersReader;
     std::shared_ptr<oatpp::data::stream::BufferOutputStream> m_headersOutBuffer;
     std::shared_ptr<oatpp::data::stream::InputStreamBufferedProxy> m_inStream;
-    v_int32 m_connectionState;
+    ConnectionState m_connectionState;
   private:
     oatpp::web::server::HttpRouter::BranchRouter::Route m_currentRoute;
     std::shared_ptr<protocol::http::incoming::Request> m_currentRequest;

+ 0 - 64
src/oatpp/web/server/HttpRouter.cpp

@@ -1,64 +0,0 @@
-/***************************************************************************
- *
- * Project         _____    __   ____   _      _
- *                (  _  )  /__\ (_  _)_| |_  _| |_
- *                 )(_)(  /(__)\  )( (_   _)(_   _)
- *                (_____)(__)(__)(__)  |_|    |_|
- *
- *
- * Copyright 2018-present, Leonid Stryzhevskyi <lganzzzo@gmail.com>
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- ***************************************************************************/
-
-#include "HttpRouter.hpp"
-
-namespace oatpp { namespace web { namespace server {
-
-HttpRouter::HttpRouter()
-{}
-
-const std::shared_ptr<HttpRouter::BranchRouter>& HttpRouter::getBranch(const StringKeyLabel& name){
-  auto it = m_branchMap.find(name);
-  if(it == m_branchMap.end()){
-    m_branchMap[name] = BranchRouter::createShared();
-  }
-  return m_branchMap[name];
-}
-
-std::shared_ptr<HttpRouter> HttpRouter::createShared() {
-  return std::make_shared<HttpRouter>();
-}
-
-void HttpRouter::route(const oatpp::String& method,
-                               const oatpp::String& pathPattern,
-                               const std::shared_ptr<HttpRequestHandler>& handler) {
-  getBranch(method)->route(pathPattern, handler);
-}
-
-HttpRouter::BranchRouter::Route HttpRouter::getRoute(const StringKeyLabel& method, const StringKeyLabel& path){
-  auto it = m_branchMap.find(method);
-  if(it != m_branchMap.end()) {
-    return m_branchMap[method]->getRoute(path);
-  }
-  return BranchRouter::Route();
-}
-
-void HttpRouter::logRouterMappings() {
-  for(auto it : m_branchMap) {
-    it.second->logRouterMappings(it.first);
-  }
-}
-
-}}}

+ 41 - 15
src/oatpp/web/server/HttpRouter.hpp

@@ -33,18 +33,19 @@ namespace oatpp { namespace web { namespace server {
 /**
  * HttpRouter is responsible for routing http requests by method and path-pattern.
  */
-class HttpRouter : public oatpp::base::Countable {
+template<typename RouterEndpoint>
+class HttpRouterTemplate : public oatpp::base::Countable {
 private:
   /**
    * Convenience typedef for &id:oatpp::data::share::StringKeyLabel;.
    */
-  typedef oatpp::data::share::StringKeyLabel StringKeyLabel;
+  typedef data::share::StringKeyLabel StringKeyLabel;
 public:
 
   /**
-   * &id:oatpp::web::url::mapping::Router; of &id:oatpp::web::server::HttpRequestHandler;.
+   * &id:oatpp::web::url::mapping::Router;
    */
-  typedef oatpp::web::url::mapping::Router<HttpRequestHandler> BranchRouter;
+  typedef web::url::mapping::Router<RouterEndpoint> BranchRouter;
 
   /**
    * Http method to &l:HttpRouter::BranchRouter; map.
@@ -54,29 +55,39 @@ public:
 protected:
   BranchMap m_branchMap;
 protected:
-  
-  const std::shared_ptr<BranchRouter>& getBranch(const StringKeyLabel& name);
-  
+
+  const std::shared_ptr<BranchRouter>& getBranch(const StringKeyLabel& name){
+    auto it = m_branchMap.find(name);
+    if(it == m_branchMap.end()){
+      m_branchMap[name] = BranchRouter::createShared();
+    }
+    return m_branchMap[name];
+  }
+
 public:
 
   /**
-   * Constructor.
+   * Default Constructor.
    */
-  HttpRouter();
+  HttpRouterTemplate() = default;
 
   /**
    * Create shared HttpRouter.
    * @return - `std::shared_ptr` to HttpRouter.
    */
-  static std::shared_ptr<HttpRouter> createShared();
+  static std::shared_ptr<HttpRouterTemplate> createShared() {
+    return std::make_shared<HttpRouterTemplate>();
+  }
 
   /**
-   * Route URL to Handler by method, and pathPattern.
+   * Route URL to Endpoint by method, and pathPattern.
    * @param method - http method like ["GET", "POST", etc.].
    * @param pathPattern - url path pattern. ex.: `"/path/to/resource/with/{param1}/{param2}"`.
-   * @param handler - &id:oatpp::web::server::HttpRequestHandler;.
+   * @param endpoint - router endpoint.
    */
-  void route(const oatpp::String& method, const oatpp::String& pathPattern, const std::shared_ptr<HttpRequestHandler>& handler);
+  void route(const oatpp::String& method, const oatpp::String& pathPattern, const RouterEndpoint& endpoint) {
+    getBranch(method)->route(pathPattern, endpoint);
+  }
 
   /**
    * Resolve http method and path to &id:oatpp::web::url::mapping::Router::Route;
@@ -84,14 +95,29 @@ public:
    * @param url - url path. "Path" part of url only.
    * @return - &id:oatpp::web::url::mapping::Router::Route;.
    */
-  BranchRouter::Route getRoute(const StringKeyLabel& method, const StringKeyLabel& path);
+  typename BranchRouter::Route getRoute(const StringKeyLabel& method, const StringKeyLabel& path){
+    auto it = m_branchMap.find(method);
+    if(it != m_branchMap.end()) {
+      return m_branchMap[method]->getRoute(path);
+    }
+    return typename BranchRouter::Route();
+  }
 
   /**
    * Print out all router mapping.
    */
-  void logRouterMappings();
+  void logRouterMappings() {
+    for(auto it : m_branchMap) {
+      it.second->logRouterMappings(it.first);
+    }
+  }
   
 };
+
+/**
+ * Default HttpRouter.
+ */
+typedef HttpRouterTemplate<std::shared_ptr<HttpRequestHandler>> HttpRouter;
   
 }}}
 

+ 24 - 7
src/oatpp/web/server/api/Endpoint.hpp

@@ -67,7 +67,13 @@ public:
       oatpp::Boolean required = true;
       oatpp::Boolean deprecated = false;
       oatpp::Boolean allowEmptyValue;
-      
+      std::list<std::pair<oatpp::String, oatpp::Any>> examples;
+
+      Param& addExample(const oatpp::String& title, const oatpp::Any& example) {
+        examples.push_back({title, example});
+        return *this;
+      }
+
     };
 
     /**
@@ -111,10 +117,16 @@ public:
     /**
      * Hints about the response (content-type, schema, description, ...)
      */
-    struct ResponseHints {
+    struct ContentHints {
       oatpp::String contentType;
       oatpp::data::mapping::type::Type* schema;
       oatpp::String description;
+      std::list<std::pair<oatpp::String, oatpp::Any>> examples;
+
+      ContentHints& addExample(const oatpp::String& title, const oatpp::Any& example) {
+        examples.push_back({title, example});
+        return *this;
+      }
     };
     
   public:
@@ -183,7 +195,7 @@ public:
     /**
      * Consumes.
      */
-    std::list<ResponseHints> consumes;
+    std::list<ContentHints> consumes;
 
     /**
      * Security Requirements
@@ -209,7 +221,7 @@ public:
      *  ResponseCode to {ContentType, Type} mapping.
      *  Example responses[Status::CODE_200] = {"application/json", MyDto::ObjectWrapper::Class::getType()};
      */
-    std::unordered_map<oatpp::web::protocol::http::Status, ResponseHints> responses;
+    std::unordered_map<oatpp::web::protocol::http::Status, ContentHints> responses;
     
     oatpp::String toString();
 
@@ -219,8 +231,9 @@ public:
      * @param contentType
      */
     template<class Wrapper>
-    void addConsumes(const oatpp::String& contentType, const oatpp::String& description = oatpp::String()) {
+    ContentHints& addConsumes(const oatpp::String& contentType, const oatpp::String& description = oatpp::String()) {
       consumes.push_back({contentType, Wrapper::Class::getType(), description});
+      return consumes.back();
     }
 
     /**
@@ -231,8 +244,12 @@ public:
      * @param responseDescription
      */
     template<class Wrapper>
-    void addResponse(const oatpp::web::protocol::http::Status& status, const oatpp::String& contentType, const oatpp::String& responseDescription = oatpp::String()) {
-      responses[status] = {contentType, Wrapper::Class::getType(), responseDescription.get() == nullptr ? status.description : responseDescription};
+    ContentHints& addResponse(const oatpp::web::protocol::http::Status& status, const oatpp::String& contentType, const oatpp::String& responseDescription = oatpp::String()) {
+      auto& hint = responses[status];
+      hint.contentType = contentType;
+      hint.description = responseDescription.get() == nullptr ? status.description : responseDescription;
+      hint.schema = Wrapper::Class::getType();
+      return hint;
     }
 
     /**

+ 61 - 0
src/oatpp/web/server/interceptor/AllowCorsGlobal.cpp

@@ -0,0 +1,61 @@
+/***************************************************************************
+ *
+ * Project         _____    __   ____   _      _
+ *                (  _  )  /__\ (_  _)_| |_  _| |_
+ *                 )(_)(  /(__)\  )( (_   _)(_   _)
+ *                (_____)(__)(__)(__)  |_|    |_|
+ *
+ *
+ * Copyright 2018-present, Leonid Stryzhevskyi <lganzzzo@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ ***************************************************************************/
+
+#include "AllowCorsGlobal.hpp"
+
+namespace oatpp { namespace web { namespace server { namespace interceptor {
+
+std::shared_ptr<protocol::http::outgoing::Response> AllowOptionsGlobal::intercept(const std::shared_ptr<IncomingRequest> &request) {
+
+  const auto &line = request->getStartingLine();
+
+  if (line.method == "OPTIONS") {
+    return OutgoingResponse::createShared(protocol::http::Status::CODE_204, nullptr);
+  }
+
+  return nullptr;
+
+}
+
+AllowCorsGlobal::AllowCorsGlobal(const oatpp::String &origin,
+                                 const oatpp::String &methods,
+                                 const oatpp::String &headers,
+                                 const oatpp::String &maxAge)
+  : m_origin(origin)
+  , m_methods(methods)
+  , m_headers(headers)
+  , m_maxAge(maxAge)
+{}
+
+std::shared_ptr<protocol::http::outgoing::Response> AllowCorsGlobal::intercept(const std::shared_ptr<IncomingRequest>& request,
+                                                                               const std::shared_ptr<OutgoingResponse>& response)
+{
+  response->putHeaderIfNotExists(protocol::http::Header::CORS_ORIGIN, m_origin);
+  response->putHeaderIfNotExists(protocol::http::Header::CORS_METHODS, m_methods);
+  response->putHeaderIfNotExists(protocol::http::Header::CORS_HEADERS, m_headers);
+  response->putHeaderIfNotExists(protocol::http::Header::CORS_MAX_AGE, m_maxAge);
+  return response;
+}
+
+}}}}

+ 58 - 0
src/oatpp/web/server/interceptor/AllowCorsGlobal.hpp

@@ -0,0 +1,58 @@
+/***************************************************************************
+ *
+ * Project         _____    __   ____   _      _
+ *                (  _  )  /__\ (_  _)_| |_  _| |_
+ *                 )(_)(  /(__)\  )( (_   _)(_   _)
+ *                (_____)(__)(__)(__)  |_|    |_|
+ *
+ *
+ * Copyright 2018-present, Leonid Stryzhevskyi <lganzzzo@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ ***************************************************************************/
+
+#ifndef oatpp_web_server_interceptor_AllowCorsGlobal_hpp
+#define oatpp_web_server_interceptor_AllowCorsGlobal_hpp
+
+#include "oatpp/web/server/interceptor/ResponseInterceptor.hpp"
+#include "oatpp/web/server/interceptor/RequestInterceptor.hpp"
+
+namespace oatpp { namespace web { namespace server { namespace interceptor {
+
+class AllowOptionsGlobal : public RequestInterceptor {
+public:
+  std::shared_ptr<OutgoingResponse> intercept(const std::shared_ptr<IncomingRequest>& request) override;
+};
+
+class AllowCorsGlobal : public ResponseInterceptor {
+private:
+  oatpp::String m_origin;
+  oatpp::String m_methods;
+  oatpp::String m_headers;
+  oatpp::String m_maxAge;
+public:
+
+  AllowCorsGlobal(const oatpp::String &origin = "*",
+                  const oatpp::String &methods = "GET, POST, OPTIONS",
+                  const oatpp::String &headers = "DNT, User-Agent, X-Requested-With, If-Modified-Since, Cache-Control, Content-Type, Range, Authorization",
+                  const oatpp::String &maxAge = "1728000");
+
+  std::shared_ptr<OutgoingResponse> intercept(const std::shared_ptr<IncomingRequest>& request,
+                                              const std::shared_ptr<OutgoingResponse>& response) override;
+
+};
+
+}}}}
+
+#endif // oatpp_web_server_interceptor_AllowCorsGlobal_hpp

+ 10 - 5
src/oatpp/web/server/handler/Interceptor.hpp → src/oatpp/web/server/interceptor/RequestInterceptor.hpp

@@ -22,14 +22,14 @@
  *
  ***************************************************************************/
 
-#ifndef oatpp_web_server_handler_Interceptor_hpp
-#define oatpp_web_server_handler_Interceptor_hpp
+#ifndef oatpp_web_server_interceptor_RequestInterceptor_hpp
+#define oatpp_web_server_interceptor_RequestInterceptor_hpp
 
 #include "oatpp/web/protocol/http/outgoing/Response.hpp"
 #include "oatpp/web/protocol/http/incoming/Request.hpp"
 #include "oatpp/web/protocol/http/Http.hpp"
 
-namespace oatpp { namespace web { namespace server { namespace handler {
+namespace oatpp { namespace web { namespace server { namespace interceptor {
 
 /**
  * RequestInterceptor.
@@ -47,6 +47,11 @@ public:
   typedef oatpp::web::protocol::http::outgoing::Response OutgoingResponse;
 public:
   
+  /**
+   * Default virtual destructor.
+   */
+  virtual ~RequestInterceptor() = default;
+  
   /**
    *
    *  This method should not do any "heavy" nor I/O operations
@@ -59,10 +64,10 @@ public:
    *  possible usage ex: return 301 - redirect if needed
    *
    */
-  virtual std::shared_ptr<OutgoingResponse> intercept(std::shared_ptr<IncomingRequest>& request) = 0;
+  virtual std::shared_ptr<OutgoingResponse> intercept(const std::shared_ptr<IncomingRequest>& request) = 0;
   
 };
   
 }}}}
 
-#endif /* oatpp_web_server_handler_Interceptor_hpp */
+#endif /* oatpp_web_server_interceptor_RequestInterceptor_hpp */

+ 77 - 0
src/oatpp/web/server/interceptor/ResponseInterceptor.hpp

@@ -0,0 +1,77 @@
+/***************************************************************************
+ *
+ * Project         _____    __   ____   _      _
+ *                (  _  )  /__\ (_  _)_| |_  _| |_
+ *                 )(_)(  /(__)\  )( (_   _)(_   _)
+ *                (_____)(__)(__)(__)  |_|    |_|
+ *
+ *
+ * Copyright 2018-present, Leonid Stryzhevskyi <lganzzzo@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ ***************************************************************************/
+
+#ifndef oatpp_web_server_interceptor_ResponseInterceptor_hpp
+#define oatpp_web_server_interceptor_ResponseInterceptor_hpp
+
+#include "oatpp/web/protocol/http/incoming/Request.hpp"
+#include "oatpp/web/protocol/http/outgoing/Response.hpp"
+#include "oatpp/web/protocol/http/Http.hpp"
+
+namespace oatpp { namespace web { namespace server { namespace interceptor {
+
+/**
+ * ResponseInterceptor.
+ */
+class ResponseInterceptor {
+public:
+  /**
+   * Convenience typedef for &id:oatpp::web::protocol::http::incoming::Request;.
+   */
+  typedef oatpp::web::protocol::http::incoming::Request IncomingRequest;
+
+  /**
+   * Convenience typedef for &id:oatpp::web::protocol::http::outgoing::Response;.
+   */
+  typedef oatpp::web::protocol::http::outgoing::Response OutgoingResponse;
+public:
+
+  /**
+   * Default virtual destructor.
+   */
+  virtual ~ResponseInterceptor() = default;
+
+  /**
+   *
+   * This method should not do any "heavy" nor I/O operations <br>
+   * as it is used for both "Simple" and "Async" API <br>
+   * NOT FOR I/O operations!!! <br>
+   * <br>
+   * - return the same response, or the new one. <br>
+   * - do **NOT** return `nullptr`.
+   * <br><br>
+   * possible usage ex: add extra headers to the response.
+   *
+   * @param request - the corresponding request.
+   * @param response - response to the request
+   * @return - &id:oatpp::web::protocol::http::outgoing::Response;.
+   */
+  virtual std::shared_ptr<OutgoingResponse> intercept(const std::shared_ptr<IncomingRequest>& request,
+                                                      const std::shared_ptr<OutgoingResponse>& response) = 0;
+
+};
+
+}}}}
+
+#endif /* oatpp_web_server_interceptor_ResponseInterceptor_hpp */

+ 4 - 0
src/oatpp/web/url/mapping/Pattern.hpp

@@ -67,6 +67,10 @@ public:
     oatpp::String getTail() const {
       return m_tail.toString();
     }
+
+    const Variables& getVariables() const {
+      return m_variables;
+    }
     
   };
   

+ 26 - 13
src/oatpp/web/url/mapping/Router.hpp

@@ -38,14 +38,14 @@ namespace oatpp { namespace web { namespace url { namespace mapping {
  * Class responsible to map "Path" to "Route" by "Path-Pattern".
  * @tparam Endpoint - endpoint of the route.
  */
-template<class Endpoint>
+template<typename Endpoint>
 class Router : public base::Countable {
 private:
 
   /**
    * Pair &id:oatpp::web::url::mapping::Pattern; to Endpoint.
    */
-  typedef std::pair<std::shared_ptr<Pattern>, std::shared_ptr<Endpoint>> Pair;
+  typedef std::pair<std::shared_ptr<Pattern>, Endpoint> Pair;
 
   /**
    * Convenience typedef &id:oatpp::data::share::StringKeyLabel;.
@@ -58,14 +58,16 @@ public:
    */
   class Route {
   private:
-    Endpoint* m_endpoint;
+    bool m_valid;
+    Endpoint m_endpoint;
+    Pattern::MatchMap m_matchMap;
   public:
 
     /**
      * Default constructor.
      */
     Route()
-      : m_endpoint(nullptr)
+      : m_valid(false)
     {}
 
     /**
@@ -73,25 +75,36 @@ public:
      * @param pEndpoint - route endpoint.
      * @param pMatchMap - Match map of resolved path containing resolved path variables.
      */
-    Route(Endpoint* endpoint, const Pattern::MatchMap& pMatchMap)
-      : m_endpoint(endpoint)
-      , matchMap(pMatchMap)
+    Route(const Endpoint& endpoint, Pattern::MatchMap&& matchMap)
+      : m_valid(true)
+      , m_endpoint(endpoint)
+      , m_matchMap(matchMap)
     {}
 
     /**
-     * Get endpoint of the route.
+     * Get Endpoint.
      */
-    Endpoint* getEndpoint() {
+    const Endpoint& getEndpoint() {
       return m_endpoint;
     }
 
     /**
      * Match map of resolved path containing resolved path variables.
      */
-    Pattern::MatchMap matchMap;
+    const Pattern::MatchMap& getMatchMap() {
+      return m_matchMap;
+    }
+
+    /**
+     * Check if route is valid.
+     * @return
+     */
+    bool isValid() {
+      return m_valid;
+    }
     
     explicit operator bool() const {
-      return m_endpoint != nullptr;
+      return m_valid;
     }
     
   };
@@ -109,7 +122,7 @@ public:
    * @param pathPattern - path pattern for endpoint.
    * @param endpoint - route endpoint.
    */
-  void route(const oatpp::String& pathPattern, const std::shared_ptr<Endpoint>& endpoint) {
+  void route(const oatpp::String& pathPattern, const Endpoint& endpoint) {
     auto pattern = Pattern::parse(pathPattern);
     m_endpointsByPattern.push_back({pattern, endpoint});
   }
@@ -124,7 +137,7 @@ public:
     for(auto& pair : m_endpointsByPattern) {
       Pattern::MatchMap matchMap;
       if(pair.first->match(path, matchMap)) {
-        return Route(pair.second.get(), matchMap);
+        return Route(pair.second, std::move(matchMap));
       }
     }
 

+ 2 - 0
test/CMakeLists.txt

@@ -87,6 +87,8 @@ add_executable(oatppAllTests
         oatpp/web/server/api/ApiControllerTest.hpp
         oatpp/web/server/handler/AuthorizationHandlerTest.cpp
         oatpp/web/server/handler/AuthorizationHandlerTest.hpp
+        oatpp/web/server/HttpRouterTest.cpp
+        oatpp/web/server/HttpRouterTest.hpp
         oatpp/web/ClientRetryTest.cpp
         oatpp/web/ClientRetryTest.hpp
         oatpp/web/PipelineTest.cpp

+ 2 - 1
test/oatpp/AllTestsMain.cpp

@@ -8,6 +8,7 @@
 #include "oatpp/web/protocol/http/encoding/ChunkedTest.hpp"
 #include "oatpp/web/server/api/ApiControllerTest.hpp"
 #include "oatpp/web/server/handler/AuthorizationHandlerTest.hpp"
+#include "oatpp/web/server/HttpRouterTest.hpp"
 #include "oatpp/web/mime/multipart/StatefulParserTest.hpp"
 
 #include "oatpp/network/virtual_/PipeTest.hpp"
@@ -135,8 +136,8 @@ void runTests() {
 
   OATPP_RUN_TEST(oatpp::test::web::mime::multipart::StatefulParserTest);
 
+  OATPP_RUN_TEST(oatpp::test::web::server::HttpRouterTest);
   OATPP_RUN_TEST(oatpp::test::web::server::api::ApiControllerTest);
-
   OATPP_RUN_TEST(oatpp::test::web::server::handler::AuthorizationHandlerTest);
 
   {

+ 22 - 2
test/oatpp/core/base/LoggerTest.cpp

@@ -27,6 +27,8 @@
 
 namespace oatpp { namespace test { namespace base {
 
+OATPP_LOG_CATEGORY(LoggerTest::TESTCATEGORY, "LogCategory", true);
+
 void LoggerTest::onRun() {
 
   auto logger = std::static_pointer_cast<oatpp::base::DefaultLogger>(oatpp::base::Environment::getLogger());
@@ -37,8 +39,9 @@ void LoggerTest::onRun() {
   OATPP_LOGW("LoggerTest", "Warning Log");
   OATPP_LOGE("LoggerTest", "Error Log");
 
-  OATPP_LOGV("LoggerTest", "Disabling Debug Log");
+  OATPP_LOGI("LoggerTest", " --- Disabling Debug Log");
   logger->disablePriority(oatpp::base::DefaultLogger::PRIORITY_D);
+  OATPP_ASSERT(!logger->isLogPriorityEnabled(oatpp::base::DefaultLogger::PRIORITY_D))
 
   OATPP_LOGV("LoggerTest", "Verbose Log");
   OATPP_LOGD("LoggerTest", "Debug Log");
@@ -46,8 +49,9 @@ void LoggerTest::onRun() {
   OATPP_LOGW("LoggerTest", "Warning Log");
   OATPP_LOGE("LoggerTest", "Error Log");
 
-  OATPP_LOGV("LoggerTest", "Enabling Debug Log again");
+  OATPP_LOGI("LoggerTest", " --- Enabling Debug Log again");
   logger->enablePriority(oatpp::base::DefaultLogger::PRIORITY_D);
+  OATPP_ASSERT(logger->isLogPriorityEnabled(oatpp::base::DefaultLogger::PRIORITY_D))
 
   OATPP_LOGV("LoggerTest", "Verbose Log");
   OATPP_LOGD("LoggerTest", "Debug Log");
@@ -55,6 +59,22 @@ void LoggerTest::onRun() {
   OATPP_LOGW("LoggerTest", "Warning Log");
   OATPP_LOGE("LoggerTest", "Error Log");
 
+  OATPP_LOGI(TESTCATEGORY, " --- Log-Test with category");
+  OATPP_LOGV(TESTCATEGORY, "Verbose Log");
+  OATPP_LOGD(TESTCATEGORY, "Debug Log");
+  OATPP_LOGI(TESTCATEGORY, "Info Log");
+  OATPP_LOGW(TESTCATEGORY, "Warning Log");
+  OATPP_LOGE(TESTCATEGORY, "Error Log");
+
+  OATPP_LOGI(TESTCATEGORY, " --- Disabling Debug Log for category");
+  TESTCATEGORY.disablePriority(oatpp::base::DefaultLogger::PRIORITY_D);
+  OATPP_ASSERT(!TESTCATEGORY.isLogPriorityEnabled(oatpp::base::DefaultLogger::PRIORITY_D))
+  OATPP_LOGV(TESTCATEGORY, "Verbose Log");
+  OATPP_LOGD(TESTCATEGORY, "Debug Log");
+  OATPP_LOGI(TESTCATEGORY, "Info Log");
+  OATPP_LOGW(TESTCATEGORY, "Warning Log");
+  OATPP_LOGE(TESTCATEGORY, "Error Log");
+
 }
 
 }}}

+ 1 - 0
test/oatpp/core/base/LoggerTest.hpp

@@ -36,6 +36,7 @@ class LoggerTest : public UnitTest{
   LoggerTest():UnitTest("TEST[base::LoggerTest]"){}
   void onRun() override;
 
+  OATPP_DECLARE_LOG_CATEGORY(TESTCATEGORY);
 };
 
 }}}

+ 33 - 0
test/oatpp/core/data/share/MemoryLabelTest.cpp

@@ -319,6 +319,39 @@ void MemoryLabelTest::onRun() {
 
   }
 
+  {
+
+    v_int32 iterationsCount = 100;
+
+    oatpp::String headersText =
+      "header0: value0\r\n"
+      "header0: value1\r\n"
+      "header1: value2\r\n"
+      "header1: value3\r\n"
+      "header2: value4\r\n"
+      "header2: value5\r\n"
+      "header3: value6\r\n"
+      "header3: value7\r\n"
+      "header4: value8\r\n"
+      "header4: value9\r\n"
+      "\r\n";
+
+    oatpp::parser::Caret caret(headersText);
+    oatpp::web::protocol::http::Status status;
+    oatpp::web::protocol::http::Headers headers;
+    oatpp::web::protocol::http::Parser::parseHeaders(headers, headersText.getPtr(), caret, status);
+
+    OATPP_ASSERT(status.code == 0);
+    OATPP_ASSERT(headers.getSize() == 10);
+
+    for(auto& h : headers.getAll()) {
+      auto key = h.first.toString();
+      auto val = h.second.toString();
+      OATPP_LOGD(TAG, "'%s': '%s'", key->c_str(), val->c_str());
+    }
+
+  }
+
 }
   
 }}}}}

+ 8 - 0
test/oatpp/web/FullTest.cpp

@@ -264,6 +264,14 @@ void FullTest::onRun() {
         OATPP_ASSERT(dto->testValue == "name=oatpp&age=1");
       }
 
+      { // test GET with optional query parameters
+        auto response = client->getWithOptQueries("oatpp", connection);
+        OATPP_ASSERT(response->getStatusCode() == 200);
+        auto dto = response->readBodyToDto<oatpp::Object<app::TestDto>>(objectMapper.get());
+        OATPP_ASSERT(dto);
+        OATPP_ASSERT(dto->testValue == "name=oatpp&age=101");
+      }
+
       { // test GET with query parameters
         auto response = client->getWithQueriesMap("value1", 32, 0.32f, connection);
         OATPP_ASSERT(response->getStatusCode() == 200);

+ 1 - 0
test/oatpp/web/app/Client.hpp

@@ -52,6 +52,7 @@ public:
   API_CALL("GET", "/cors-origin-methods-headers", getCorsOriginMethodsHeader)
   API_CALL("GET", "params/{param}", getWithParams, PATH(String, param))
   API_CALL("GET", "queries", getWithQueries, QUERY(String, name), QUERY(Int32, age))
+  API_CALL("GET", "queries/optional", getWithOptQueries, QUERY(String, name))
   API_CALL("GET", "queries/map", getWithQueriesMap, QUERY(String, key1), QUERY(Int32, key2), QUERY(Float32, key3))
   API_CALL("GET", "headers", getWithHeaders, HEADER(String, param, "X-TEST-HEADER"))
   API_CALL("POST", "body", postBody, BODY_STRING(String, body))

+ 7 - 0
test/oatpp/web/app/Controller.hpp

@@ -117,6 +117,13 @@ public:
     return createDtoResponse(Status::CODE_200, dto);
   }
 
+  ENDPOINT("GET", "queries/optional", getWithOptQueries,
+           QUERY(String, name, "name", "Default"), QUERY(Int32, age, "age", "101")) {
+    auto dto = TestDto::createShared();
+    dto->testValue = "name=" + name + "&age=" + oatpp::utils::conversion::int32ToStr(*age);
+    return createDtoResponse(Status::CODE_200, dto);
+  }
+
   ENDPOINT("GET", "queries/map", getWithQueriesMap,
            QUERIES(QueryParams, queries)) {
     auto dto = TestDto::createShared();

+ 171 - 0
test/oatpp/web/server/HttpRouterTest.cpp

@@ -0,0 +1,171 @@
+/***************************************************************************
+ *
+ * Project         _____    __   ____   _      _
+ *                (  _  )  /__\ (_  _)_| |_  _| |_
+ *                 )(_)(  /(__)\  )( (_   _)(_   _)
+ *                (_____)(__)(__)(__)  |_|    |_|
+ *
+ *
+ * Copyright 2018-present, Leonid Stryzhevskyi <lganzzzo@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ ***************************************************************************/
+
+#include "HttpRouterTest.hpp"
+
+#include "oatpp/web/server/HttpRouter.hpp"
+#include "oatpp/core/Types.hpp"
+
+namespace oatpp { namespace test { namespace web { namespace server {
+
+namespace {
+
+typedef oatpp::web::server::HttpRouterTemplate<v_int32> NumRouter;
+
+}
+
+void HttpRouterTest::onRun() {
+
+  NumRouter router;
+  router.route("GET", "ints/1", 1);
+  router.route("GET", "ints/2", 2);
+  router.route("GET", "ints/all/{value}", -1);
+
+  router.route("POST", "ints/1", 1);
+  router.route("POST", "ints/2", 2);
+  router.route("POST", "ints/{value}", 3);
+  router.route("POST", "ints/*", 4);
+  router.route("POST", "*", -100);
+
+  {
+    OATPP_LOGI(TAG, "Case 1");
+    auto r = router.getRoute("GET", "ints/1");
+    OATPP_ASSERT(r.isValid());
+    OATPP_ASSERT(r);
+    OATPP_ASSERT(r.getEndpoint() == 1);
+  }
+
+  {
+    OATPP_LOGI(TAG, "Case 2");
+    auto r = router.getRoute("GET", "/ints/1");
+    OATPP_ASSERT(r.isValid());
+    OATPP_ASSERT(r);
+    OATPP_ASSERT(r.getEndpoint() == 1);
+  }
+
+  {
+    OATPP_LOGI(TAG, "Case 3");
+    auto r = router.getRoute("GET", "ints/1//");
+    OATPP_ASSERT(r.isValid());
+    OATPP_ASSERT(r);
+    OATPP_ASSERT(r.getEndpoint() == 1);
+  }
+
+  {
+    OATPP_LOGI(TAG, "Case 4");
+    auto r = router.getRoute("GET", "//ints///1//");
+    OATPP_ASSERT(r.isValid());
+    OATPP_ASSERT(r);
+    OATPP_ASSERT(r.getEndpoint() == 1);
+  }
+
+  {
+    OATPP_LOGI(TAG, "Case 5");
+    auto r = router.getRoute("GET", "ints/1/*");
+    OATPP_ASSERT(r.isValid() == false);
+    OATPP_ASSERT(!r);
+  }
+
+  {
+    OATPP_LOGI(TAG, "Case 6");
+    auto r = router.getRoute("GET", "ints/2");
+    OATPP_ASSERT(r.isValid());
+    OATPP_ASSERT(r);
+    OATPP_ASSERT(r.getEndpoint() == 2);
+  }
+
+  {
+    OATPP_LOGI(TAG, "Case 7");
+    auto r = router.getRoute("GET", "ints/all/10");
+    OATPP_ASSERT(r.isValid());
+    OATPP_ASSERT(r);
+    OATPP_ASSERT(r.getEndpoint() == -1);
+    OATPP_ASSERT(r.getMatchMap().getVariables().size() == 1);
+    OATPP_ASSERT(r.getMatchMap().getVariable("value") == "10");
+  }
+
+  {
+    OATPP_LOGI(TAG, "Case 8");
+    auto r = router.getRoute("GET", "//ints//all//10//");
+    OATPP_ASSERT(r.isValid());
+    OATPP_ASSERT(r);
+    OATPP_ASSERT(r.getEndpoint() == -1);
+    OATPP_ASSERT(r.getMatchMap().getVariables().size() == 1);
+    OATPP_ASSERT(r.getMatchMap().getVariable("value") == "10");
+  }
+
+  {
+    OATPP_LOGI(TAG, "Case 9");
+    auto r = router.getRoute("GET", "//ints//all//10//*");
+    OATPP_ASSERT(r.isValid() == false);
+    OATPP_ASSERT(!r);
+  }
+
+  {
+    OATPP_LOGI(TAG, "Case 10");
+    auto r = router.getRoute("POST", "ints/1");
+    OATPP_ASSERT(r.isValid());
+    OATPP_ASSERT(r);
+    OATPP_ASSERT(r.getEndpoint() == 1);
+  }
+
+  {
+    OATPP_LOGI(TAG, "Case 11");
+    auto r = router.getRoute("POST", "ints/2");
+    OATPP_ASSERT(r.isValid());
+    OATPP_ASSERT(r);
+    OATPP_ASSERT(r.getEndpoint() == 2);
+  }
+
+  {
+    OATPP_LOGI(TAG, "Case 12");
+    auto r = router.getRoute("POST", "ints/3");
+    OATPP_ASSERT(r.isValid());
+    OATPP_ASSERT(r);
+    OATPP_ASSERT(r.getEndpoint() == 3);
+    OATPP_ASSERT(r.getMatchMap().getVariables().size() == 1);
+    OATPP_ASSERT(r.getMatchMap().getVariable("value") == "3");
+  }
+
+  {
+    OATPP_LOGI(TAG, "Case 13");
+    auto r = router.getRoute("POST", "ints/3/10");
+    OATPP_ASSERT(r.isValid());
+    OATPP_ASSERT(r);
+    OATPP_ASSERT(r.getEndpoint() == 4);
+    OATPP_ASSERT(r.getMatchMap().getTail() == "3/10");
+  }
+
+  {
+    OATPP_LOGI(TAG, "Case 14");
+    auto r = router.getRoute("POST", "abc");
+    OATPP_ASSERT(r.isValid());
+    OATPP_ASSERT(r);
+    OATPP_ASSERT(r.getEndpoint() == -100);
+    OATPP_ASSERT(r.getMatchMap().getTail() == "abc");
+  }
+
+}
+
+}}}}

+ 18 - 1
src/oatpp/web/server/handler/Interceptor.cpp → test/oatpp/web/server/HttpRouterTest.hpp

@@ -22,4 +22,21 @@
  *
  ***************************************************************************/
 
-#include "Interceptor.hpp"
+#ifndef oatpp_test_web_server_HttpRouterTest_hpp
+#define oatpp_test_web_server_HttpRouterTest_hpp
+
+#include "oatpp-test/UnitTest.hpp"
+
+namespace oatpp { namespace test { namespace web { namespace server {
+
+class HttpRouterTest : public UnitTest {
+public:
+
+  HttpRouterTest():UnitTest("TEST[web::server::HttpRouterTest]"){}
+  void onRun() override;
+
+};
+
+}}}}
+
+#endif /* oatpp_test_web_server_HttpRouterTest_hpp */