Use MemoryLabel to parse URL query params. Network/UrlTest.

This commit is contained in:
lganzzzo 2019-02-24 20:40:51 +02:00
parent f0bf82e695
commit 7de960d456
9 changed files with 361 additions and 35 deletions

View File

@ -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:

View File

@ -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::Parameters> 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::Parameters> 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);
}
}}

View File

@ -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 <unordered_map>
namespace oatpp { namespace network {
class Url : public oatpp::base::Controllable {
public:
typedef oatpp::collection::ListMap<oatpp::String, oatpp::String> Parameters;
typedef oatpp::data::share::StringKeyLabel StringKeyLabel;
public:
typedef std::unordered_map<oatpp::String, oatpp::String> Parameters;
typedef std::unordered_map<StringKeyLabel, StringKeyLabel> 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 "?<paramName>=<paramValue>&<paramName>=<paramValue>..." referred by ParsingCaret
*/
static std::shared_ptr<Url::Parameters> parseQueryParams(oatpp::parser::Caret& caret);
static Url::Parameters parseQueryParams(oatpp::parser::Caret& caret);
/**
* parse query params in form of "?<paramName>=<paramValue>&<paramName>=<paramValue>..." referred by str
*/
static std::shared_ptr<Url::Parameters> 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<StringKeyLabel, StringKeyLabel>
*/
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<Parameters> queryParams;
Parameters queryParams;
};

View File

@ -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

View File

@ -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);

View File

@ -0,0 +1,183 @@
/***************************************************************************
*
* 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 "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"] == "");
}
}
}}}

View File

@ -0,0 +1,43 @@
/***************************************************************************
*
* 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_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

View File

@ -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<app::TestDto>(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<app::TestDto>(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<app::TestDto>(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();

View File

@ -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<app::TestDto>(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<app::TestDto>(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<app::TestDto>(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);