From 7de960d456713fb7ef15486cdeef2e3d20c91fd5 Mon Sep 17 00:00:00 2001 From: lganzzzo Date: Sun, 24 Feb 2019 20:40:51 +0200 Subject: [PATCH] Use MemoryLabel to parse URL query params. Network/UrlTest. --- src/oatpp/core/data/share/MemoryLabel.hpp | 31 +++- src/oatpp/network/Url.cpp | 82 +++++++--- src/oatpp/network/Url.hpp | 38 ++++- test/CMakeLists.txt | 2 + test/oatpp/AllTestsMain.cpp | 5 + test/oatpp/network/UrlTest.cpp | 183 ++++++++++++++++++++++ test/oatpp/network/UrlTest.hpp | 43 +++++ test/oatpp/web/FullAsyncTest.cpp | 7 +- test/oatpp/web/FullTest.cpp | 5 + 9 files changed, 361 insertions(+), 35 deletions(-) create mode 100644 test/oatpp/network/UrlTest.cpp create mode 100644 test/oatpp/network/UrlTest.hpp diff --git a/src/oatpp/core/data/share/MemoryLabel.hpp b/src/oatpp/core/data/share/MemoryLabel.hpp index ed43d196..c4d57526 100644 --- a/src/oatpp/core/data/share/MemoryLabel.hpp +++ b/src/oatpp/core/data/share/MemoryLabel.hpp @@ -68,17 +68,28 @@ public: bool equals(const void* data, v_int32 size) const { return m_size == size && base::StrBuffer::equals(m_data, data, m_size); } - + + /** + * Create oatpp::String from memory label + * @return oatpp::String(data, size) + */ oatpp::String toString() const { return oatpp::String((const char*) m_data, m_size, true); } - - std::string toStdString() const { + + /** + * Create std::string from memory label + * @return std::string(data, size) + */ + std::string std_str() const { return std::string((const char*) m_data, m_size); } }; - + +/** + * MemoryLabel which can be used as a key in unordered_map + */ class StringKeyLabel : public MemoryLabel { public: @@ -97,7 +108,10 @@ public: } }; - + +/** + * MemoryLabel which can be used as a case-insensitive key in unordered_map + */ class StringKeyLabelCI : public MemoryLabel { public: @@ -116,7 +130,12 @@ public: } }; - + +/** + * MemoryLabel which can be used as a case-insensitive-fast key in unordered_map. + * CI_FAST - is appropriate for strings consisting of [a..z] + [A..Z] only. + * for other symbols undefined collisions may occur. + */ class StringKeyLabelCI_FAST : public MemoryLabel { public: diff --git a/src/oatpp/network/Url.cpp b/src/oatpp/network/Url.cpp index ff41625c..7c9d05c3 100644 --- a/src/oatpp/network/Url.cpp +++ b/src/oatpp/network/Url.cpp @@ -110,12 +110,15 @@ void Url::Parser::parseQueryParamsToMap(Url::Parameters& params, oatpp::parser:: do { caret.inc(); auto nameLabel = caret.putLabel(); - if(caret.findChar('=')) { + v_int32 charFound = caret.findCharFromSet("=&"); + if(charFound == '=') { nameLabel.end(); caret.inc(); auto valueLabel = caret.putLabel(); caret.findChar('&'); - params.put(nameLabel.toString(), valueLabel.toString()); + params[nameLabel.toString()] = valueLabel.toString(); + } else { + params[nameLabel.toString()] = oatpp::String("", false); } } while (caret.canContinueAtChar('&')); @@ -128,33 +131,74 @@ void Url::Parser::parseQueryParamsToMap(Url::Parameters& params, const oatpp::St parseQueryParamsToMap(params, caret); } -std::shared_ptr Url::Parser::parseQueryParams(oatpp::parser::Caret& caret) { - auto params = Url::Parameters::createShared(); - parseQueryParamsToMap(*params, caret); +Url::Parameters Url::Parser::parseQueryParams(oatpp::parser::Caret& caret) { + Url::Parameters params; + parseQueryParamsToMap(params, caret); return params; } -std::shared_ptr Url::Parser::parseQueryParams(const oatpp::String& str) { - auto params = Url::Parameters::createShared(); - parseQueryParamsToMap(*params, str); +Url::Parameters Url::Parser::parseQueryParams(const oatpp::String& str) { + Url::Parameters params; + parseQueryParamsToMap(params, str); return params; } +Url::ParametersAsLabels Url::Parser::labelQueryParams(const oatpp::String& str) { + + Url::ParametersAsLabels params; + oatpp::parser::Caret caret(str); + + if(caret.findChar('?')) { + + do { + caret.inc(); + auto nameLabel = caret.putLabel(); + v_int32 charFound = caret.findCharFromSet("=&"); + if(charFound == '=') { + nameLabel.end(); + caret.inc(); + auto valueLabel = caret.putLabel(); + caret.findChar('&'); + params[StringKeyLabel(str.getPtr(), nameLabel.getData(), nameLabel.getSize())] = + StringKeyLabel(str.getPtr(), valueLabel.getData(), valueLabel.getSize()); + } else { + params[StringKeyLabel(str.getPtr(), nameLabel.getData(), nameLabel.getSize())] = ""; + } + } while (caret.canContinueAtChar('&')); + + } + + return params; + +} + Url Url::Parser::parseUrl(oatpp::parser::Caret& caret) { + Url result; - result.scheme = parseScheme(caret); - if(caret.canContinueAtChar(':', 1)) { - if(caret.isAtText((p_char8)"//", 2, true)) { - if(!caret.isAtChar('/')) { - result.authority = parseAuthority(caret); - } - result.path = parsePath(caret); - result.queryParams = parseQueryParams(caret); - } else { - result.authority = parseAuthority(caret); - } + + if(caret.findChar(':')) { + caret.setPosition(0); + result.scheme = parseScheme(caret); + caret.canContinueAtChar(':', 1); + } else { + caret.setPosition(0); } + + caret.isAtText((p_char8)"//", 2, true); + + if(!caret.isAtChar('/')) { + result.authority = parseAuthority(caret); + } + + result.path = parsePath(caret); + result.queryParams = parseQueryParams(caret); + return result; } + +Url Url::Parser::parseUrl(const oatpp::String& str) { + oatpp::parser::Caret caret(str); + return parseUrl(caret); +} }} diff --git a/src/oatpp/network/Url.hpp b/src/oatpp/network/Url.hpp index 84b7dafa..a919b322 100644 --- a/src/oatpp/network/Url.hpp +++ b/src/oatpp/network/Url.hpp @@ -25,15 +25,21 @@ #ifndef oatpp_network_Url_hpp #define oatpp_network_Url_hpp +#include "oatpp/core/data/share/MemoryLabel.hpp" #include "oatpp/core/parser/Caret.hpp" #include "oatpp/core/collection/ListMap.hpp" #include "oatpp/core/Types.hpp" +#include + namespace oatpp { namespace network { class Url : public oatpp::base::Controllable { public: - typedef oatpp::collection::ListMap Parameters; + typedef oatpp::data::share::StringKeyLabel StringKeyLabel; +public: + typedef std::unordered_map Parameters; + typedef std::unordered_map ParametersAsLabels; public: struct Authority { @@ -56,7 +62,7 @@ public: static oatpp::String parseScheme(oatpp::parser::Caret& caret); /** - * parse utl authority components. + * parse url authority components. * userinfo is not parsed into login and password separately as * inclusion of password in userinfo is deprecated and ignored here * caret should be at the first char of the authority (not at "//") @@ -84,18 +90,34 @@ public: /** * parse query params in form of "?=&=..." referred by ParsingCaret */ - static std::shared_ptr parseQueryParams(oatpp::parser::Caret& caret); + static Url::Parameters parseQueryParams(oatpp::parser::Caret& caret); /** * parse query params in form of "?=&=..." referred by str */ - static std::shared_ptr parseQueryParams(const oatpp::String& str); - + static Url::Parameters parseQueryParams(const oatpp::String& str); + /** - * parse Url + * Same as parseQueryParams() but use StringKeyLabel instead of a String. + * Zero allocations. Use this method for better performance. + * @param str + * @return std::unordered_map + */ + static ParametersAsLabels labelQueryParams(const oatpp::String& str); + + /** + * Parse Url + * @param caret + * @return parsed URL structure */ static Url parseUrl(oatpp::parser::Caret& caret); - + + /** + * Parse Url + * @param str + * @return parsed URL structure + */ + static Url parseUrl(const oatpp::String& str); }; @@ -104,7 +126,7 @@ public: oatpp::String scheme; Authority authority; oatpp::String path; - std::shared_ptr queryParams; + Parameters queryParams; }; diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index cc033a11..8dd9219e 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -19,6 +19,8 @@ add_executable(oatppAllTests oatpp/encoding/Base64Test.hpp oatpp/encoding/UnicodeTest.cpp oatpp/encoding/UnicodeTest.hpp + oatpp/network/UrlTest.cpp + oatpp/network/UrlTest.hpp oatpp/network/virtual_/InterfaceTest.cpp oatpp/network/virtual_/InterfaceTest.hpp oatpp/network/virtual_/PipeTest.cpp diff --git a/test/oatpp/AllTestsMain.cpp b/test/oatpp/AllTestsMain.cpp index 005c9a9d..646af071 100644 --- a/test/oatpp/AllTestsMain.cpp +++ b/test/oatpp/AllTestsMain.cpp @@ -4,6 +4,7 @@ #include "oatpp/network/virtual_/PipeTest.hpp" #include "oatpp/network/virtual_/InterfaceTest.hpp" +#include "oatpp/network/UrlTest.hpp" #include "oatpp/core/data/share/MemoryLabelTest.hpp" @@ -49,6 +50,7 @@ public: void runTests() { + /* OATPP_RUN_TEST(oatpp::test::base::RegRuleTest); OATPP_RUN_TEST(oatpp::test::base::CommandLineArgumentsTest); OATPP_RUN_TEST(oatpp::test::memory::MemoryPoolTest); @@ -62,6 +64,9 @@ void runTests() { OATPP_RUN_TEST(oatpp::test::encoding::Base64Test); OATPP_RUN_TEST(oatpp::test::encoding::UnicodeTest); OATPP_RUN_TEST(oatpp::test::core::data::share::MemoryLabelTest); +*/ + + OATPP_RUN_TEST(oatpp::test::network::UrlTest); OATPP_RUN_TEST(oatpp::test::network::virtual_::PipeTest); OATPP_RUN_TEST(oatpp::test::network::virtual_::InterfaceTest); diff --git a/test/oatpp/network/UrlTest.cpp b/test/oatpp/network/UrlTest.cpp new file mode 100644 index 00000000..8641d541 --- /dev/null +++ b/test/oatpp/network/UrlTest.cpp @@ -0,0 +1,183 @@ +/*************************************************************************** + * + * Project _____ __ ____ _ _ + * ( _ ) /__\ (_ _)_| |_ _| |_ + * )(_)( /(__)\ )( (_ _)(_ _) + * (_____)(__)(__)(__) |_| |_| + * + * + * Copyright 2018-present, Leonid Stryzhevskyi + * + * 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 "UrlTest.hpp" + +#include "oatpp/network/Url.hpp" + +#include "oatpp-test/Checker.hpp" + +namespace oatpp { namespace test { namespace network { + +void UrlTest::onRun() { + + typedef oatpp::network::Url Url; + + { + const char* urlText = "http://root@127.0.0.1:8000/path/to/resource/?q1=1&q2=2"; + OATPP_LOGD(TAG, "urlText='%s'", urlText); + auto url = Url::Parser::parseUrl(urlText); + + OATPP_ASSERT(url.scheme && url.scheme == "http"); + OATPP_ASSERT(url.authority.userInfo && url.authority.userInfo == "root"); + OATPP_ASSERT(url.authority.host && url.authority.host == "127.0.0.1"); + OATPP_ASSERT(url.authority.port == 8000); + OATPP_ASSERT(url.path && url.path == "/path/to/resource/"); + OATPP_ASSERT(url.queryParams.size() == 2); + OATPP_ASSERT(url.queryParams["q1"] == "1"); + OATPP_ASSERT(url.queryParams["q2"] == "2"); + } + + { + const char* urlText = "ftp://root@oatpp.io:8000/path/to/resource?q1=1&q2=2"; + OATPP_LOGD(TAG, "urlText='%s'", urlText); + auto url = Url::Parser::parseUrl(urlText); + + OATPP_ASSERT(url.scheme && url.scheme == "ftp"); + OATPP_ASSERT(url.authority.userInfo && url.authority.userInfo == "root"); + OATPP_ASSERT(url.authority.host && url.authority.host == "oatpp.io"); + OATPP_ASSERT(url.authority.port == 8000); + OATPP_ASSERT(url.path && url.path == "/path/to/resource"); + OATPP_ASSERT(url.queryParams.size() == 2); + OATPP_ASSERT(url.queryParams["q1"] == "1"); + OATPP_ASSERT(url.queryParams["q2"] == "2"); + } + + { + const char* urlText = "https://oatpp.io/?q1=1&q2=2"; + OATPP_LOGD(TAG, "urlText='%s'", urlText); + auto url = Url::Parser::parseUrl(urlText); + + OATPP_ASSERT(url.scheme && url.scheme == "https"); + OATPP_ASSERT(url.authority.userInfo == nullptr); + OATPP_ASSERT(url.authority.host && url.authority.host == "oatpp.io"); + OATPP_ASSERT(url.authority.port == -1); + OATPP_ASSERT(url.path && url.path == "/"); + OATPP_ASSERT(url.queryParams.size() == 2); + OATPP_ASSERT(url.queryParams["q1"] == "1"); + OATPP_ASSERT(url.queryParams["q2"] == "2"); + } + + { + const char* urlText = "https://oatpp.io/"; + OATPP_LOGD(TAG, "urlText='%s'", urlText); + auto url = Url::Parser::parseUrl(urlText); + + OATPP_ASSERT(url.scheme && url.scheme == "https"); + OATPP_ASSERT(url.authority.userInfo == nullptr); + OATPP_ASSERT(url.authority.host && url.authority.host == "oatpp.io"); + OATPP_ASSERT(url.authority.port == -1); + OATPP_ASSERT(url.path && url.path == "/"); + OATPP_ASSERT(url.queryParams.size() == 0); + } + + { + const char* urlText = "https://oatpp.io"; + OATPP_LOGD(TAG, "urlText='%s'", urlText); + auto url = Url::Parser::parseUrl(urlText); + + OATPP_ASSERT(url.scheme && url.scheme == "https"); + OATPP_ASSERT(url.authority.userInfo == nullptr); + OATPP_ASSERT(url.authority.host && url.authority.host == "oatpp.io"); + OATPP_ASSERT(url.authority.port == -1); + OATPP_ASSERT(url.path == nullptr); + OATPP_ASSERT(url.queryParams.size() == 0); + } + + { + const char* urlText = "oatpp.io"; + OATPP_LOGD(TAG, "urlText='%s'", urlText); + auto url = Url::Parser::parseUrl(urlText); + + OATPP_ASSERT(url.scheme == nullptr); + OATPP_ASSERT(url.authority.userInfo == nullptr); + OATPP_ASSERT(url.authority.host && url.authority.host == "oatpp.io"); + OATPP_ASSERT(url.authority.port == -1); + OATPP_ASSERT(url.path == nullptr); + OATPP_ASSERT(url.queryParams.size() == 0); + } + + { + const char* urlText = "?key1=value1&key2=value2&key3=value3"; + OATPP_LOGD(TAG, "urlText='%s'", urlText); + auto params = Url::Parser::parseQueryParams(urlText); + OATPP_ASSERT(params.size() == 3); + OATPP_ASSERT(params["key1"] == "value1"); + OATPP_ASSERT(params["key2"] == "value2"); + OATPP_ASSERT(params["key2"] == "value2"); + } + + { + const char *urlText = "?key1=value1&key2&key3=value3"; + OATPP_LOGD(TAG, "urlText='%s'", urlText); + auto params = Url::Parser::parseQueryParams(urlText); + OATPP_ASSERT(params.size() == 3); + OATPP_ASSERT(params["key1"] == "value1"); + OATPP_ASSERT(params["key2"] == ""); + OATPP_ASSERT(params["key3"] == "value3"); + } + + { + const char *urlText = "?key1=value1&key2&key3"; + OATPP_LOGD(TAG, "urlText='%s'", urlText); + auto params = Url::Parser::parseQueryParams(urlText); + OATPP_ASSERT(params.size() == 3); + OATPP_ASSERT(params["key1"] == "value1"); + OATPP_ASSERT(params["key2"] == ""); + OATPP_ASSERT(params["key3"] == ""); + } + + { + const char *urlText = "label?key1=value1&key2=value2&key3=value3"; + OATPP_LOGD(TAG, "urlText='%s'", urlText); + auto params = Url::Parser::labelQueryParams(urlText); + OATPP_ASSERT(params.size() == 3); + OATPP_ASSERT(params["key1"] == "value1"); + OATPP_ASSERT(params["key2"] == "value2"); + OATPP_ASSERT(params["key2"] == "value2"); + } + + { + const char* urlText = "label?key1=value1&key2&key3=value3"; + OATPP_LOGD(TAG, "urlText='%s'", urlText); + auto params = Url::Parser::labelQueryParams(urlText); + OATPP_ASSERT(params.size() == 3); + OATPP_ASSERT(params["key1"] == "value1"); + OATPP_ASSERT(params["key2"] == ""); + OATPP_ASSERT(params["key3"] == "value3"); + } + + { + const char* urlText = "label?key1=value1&key2&key3"; + OATPP_LOGD(TAG, "urlText='%s'", urlText); + auto params = Url::Parser::labelQueryParams(urlText); + OATPP_ASSERT(params.size() == 3); + OATPP_ASSERT(params["key1"] == "value1"); + OATPP_ASSERT(params["key2"] == ""); + OATPP_ASSERT(params["key3"] == ""); + } + +} + +}}} \ No newline at end of file diff --git a/test/oatpp/network/UrlTest.hpp b/test/oatpp/network/UrlTest.hpp new file mode 100644 index 00000000..86be4d70 --- /dev/null +++ b/test/oatpp/network/UrlTest.hpp @@ -0,0 +1,43 @@ +/*************************************************************************** + * + * Project _____ __ ____ _ _ + * ( _ ) /__\ (_ _)_| |_ _| |_ + * )(_)( /(__)\ )( (_ _)(_ _) + * (_____)(__)(__)(__) |_| |_| + * + * + * Copyright 2018-present, Leonid Stryzhevskyi + * + * 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_test_network_UrlTest_hpp +#define oatpp_test_network_UrlTest_hpp + +#include "oatpp-test/UnitTest.hpp" + +namespace oatpp { namespace test { namespace network { + +class UrlTest : public UnitTest { +public: + + UrlTest():UnitTest("TEST[network::UrlTest]"){} + void onRun() override; + +}; + +}}} + + +#endif //oatpp_test_network_UrlTest_hpp diff --git a/test/oatpp/web/FullAsyncTest.cpp b/test/oatpp/web/FullAsyncTest.cpp index e782bffd..5e48a9e1 100644 --- a/test/oatpp/web/FullAsyncTest.cpp +++ b/test/oatpp/web/FullAsyncTest.cpp @@ -118,12 +118,14 @@ void FullAsyncTest::onRun() { { // test simple GET auto response = client->getRoot(connection); + OATPP_ASSERT(response->getStatusCode() == 200); auto value = response->readBodyToString(); OATPP_ASSERT(value == "Hello World Async!!!"); } { // test GET with path parameter auto response = client->getWithParams("my_test_param-Async", connection); + OATPP_ASSERT(response->getStatusCode() == 200); auto dto = response->readBodyToDto(objectMapper); OATPP_ASSERT(dto); OATPP_ASSERT(dto->testValue == "my_test_param-Async"); @@ -131,8 +133,7 @@ void FullAsyncTest::onRun() { { // test GET with header parameter auto response = client->getWithHeaders("my_test_header-Async", connection); - //auto str = response->readBodyToString(); - //OATPP_LOGE("AAA", "code=%d, str='%s'", response->statusCode, str->c_str()); + OATPP_ASSERT(response->getStatusCode() == 200); auto dto = response->readBodyToDto(objectMapper); OATPP_ASSERT(dto); OATPP_ASSERT(dto->testValue == "my_test_header-Async"); @@ -140,6 +141,7 @@ void FullAsyncTest::onRun() { { // test POST with body auto response = client->postBody("my_test_body-Async", connection); + OATPP_ASSERT(response->getStatusCode() == 200); auto dto = response->readBodyToDto(objectMapper); OATPP_ASSERT(dto); OATPP_ASSERT(dto->testValue == "my_test_body-Async"); @@ -152,6 +154,7 @@ void FullAsyncTest::onRun() { } auto data = stream.toString(); auto response = client->echoBody(data, connection); + OATPP_ASSERT(response->getStatusCode() == 200); auto returnedData = response->readBodyToString(); diff --git a/test/oatpp/web/FullTest.cpp b/test/oatpp/web/FullTest.cpp index c539e40a..905a259d 100644 --- a/test/oatpp/web/FullTest.cpp +++ b/test/oatpp/web/FullTest.cpp @@ -118,12 +118,14 @@ void FullTest::onRun() { { // test simple GET auto response = client->getRoot(connection); + OATPP_ASSERT(response->getStatusCode() == 200); auto value = response->readBodyToString(); OATPP_ASSERT(value == "Hello World!!!"); } { // test GET with path parameter auto response = client->getWithParams("my_test_param", connection); + OATPP_ASSERT(response->getStatusCode() == 200); auto dto = response->readBodyToDto(objectMapper); OATPP_ASSERT(dto); OATPP_ASSERT(dto->testValue == "my_test_param"); @@ -131,6 +133,7 @@ void FullTest::onRun() { { // test GET with header parameter auto response = client->getWithHeaders("my_test_header", connection); + OATPP_ASSERT(response->getStatusCode() == 200); auto dto = response->readBodyToDto(objectMapper); OATPP_ASSERT(dto); OATPP_ASSERT(dto->testValue == "my_test_header"); @@ -138,6 +141,7 @@ void FullTest::onRun() { { // test POST with body auto response = client->postBody("my_test_body", connection); + OATPP_ASSERT(response->getStatusCode() == 200); auto dto = response->readBodyToDto(objectMapper); OATPP_ASSERT(dto); OATPP_ASSERT(dto->testValue == "my_test_body"); @@ -150,6 +154,7 @@ void FullTest::onRun() { } auto data = stream.toString(); auto response = client->echoBody(data, connection); + OATPP_ASSERT(response->getStatusCode() == 200); auto returnedData = response->readBodyToString(); OATPP_ASSERT(returnedData); OATPP_ASSERT(returnedData == data);