parent
7a02ab023b
commit
9f1493af27
5
.gitignore
vendored
5
.gitignore
vendored
@ -332,7 +332,10 @@ ASALocalRun/
|
||||
# CMake
|
||||
build/
|
||||
/cmake-build-debug
|
||||
.vscode/settings.json
|
||||
|
||||
#Doxygen
|
||||
[Dd]ocs/
|
||||
|
||||
# vscode
|
||||
.vscode/
|
||||
.factorypath
|
||||
|
||||
@ -7,6 +7,7 @@ list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake-modules")
|
||||
# Compile Options
|
||||
option(WARNINGS_AS_ERRORS "Treat compiler warnings as errors" ON)
|
||||
option(BUILD_CURL_TRANSPORT "Build internal http transport implementation with CURL for HTTP Pipeline" OFF)
|
||||
option(BUILD_TESTING "Build test cases" OFF)
|
||||
|
||||
# VCPKG Integration
|
||||
if(DEFINED ENV{VCPKG_ROOT} AND NOT DEFINED CMAKE_TOOLCHAIN_FILE)
|
||||
@ -22,10 +23,16 @@ endif()
|
||||
|
||||
# Project definition
|
||||
project(az LANGUAGES CXX)
|
||||
set(CMAKE_CXX_STANDARD 11)
|
||||
set(CMAKE_CXX_STANDARD 14)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED True)
|
||||
|
||||
# compiler warning flags
|
||||
if(BUILD_TESTING)
|
||||
# tests
|
||||
enable_testing ()
|
||||
add_subdirectory(sdk/core/azure-core/test)
|
||||
endif()
|
||||
|
||||
# compiler warning flags globally
|
||||
include(global_compile_options)
|
||||
|
||||
# sub-projects
|
||||
|
||||
@ -1,16 +1,16 @@
|
||||
{
|
||||
"configurations": [
|
||||
{
|
||||
"name": "x64-Debug",
|
||||
"generator": "Ninja",
|
||||
"configurationType": "Debug",
|
||||
"inheritEnvironments": [ "msvc_x64_x64" ],
|
||||
"buildRoot": "${projectDir}\\out\\build\\${name}",
|
||||
"installRoot": "${projectDir}\\out\\install\\${name}",
|
||||
"cmakeCommandArgs": "",
|
||||
"buildCommandArgs": "-v",
|
||||
"ctestCommandArgs": "",
|
||||
"variables": []
|
||||
}
|
||||
]
|
||||
{
|
||||
"configurations": [
|
||||
{
|
||||
"name": "x64-Debug",
|
||||
"generator": "Ninja",
|
||||
"configurationType": "Debug",
|
||||
"inheritEnvironments": [ "msvc_x64_x64" ],
|
||||
"buildRoot": "${projectDir}\\out\\build\\${name}",
|
||||
"installRoot": "${projectDir}\\out\\install\\${name}",
|
||||
"cmakeCommandArgs": "-DINSTALL_GTEST=OFF -DBUILD_TESTING=ON",
|
||||
"buildCommandArgs": "-v",
|
||||
"ctestCommandArgs": "",
|
||||
"variables": []
|
||||
}
|
||||
]
|
||||
}
|
||||
98
cmake-modules/AddGoogleTest.cmake
Normal file
98
cmake-modules/AddGoogleTest.cmake
Normal file
@ -0,0 +1,98 @@
|
||||
#
|
||||
#
|
||||
# Downloads GTest and provides a helper macro to add tests. Add make check, as well, which
|
||||
# gives output on failed tests without having to set an environment variable.
|
||||
#
|
||||
#
|
||||
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
|
||||
|
||||
if(CMAKE_VERSION VERSION_LESS 3.11)
|
||||
set(UPDATE_DISCONNECTED_IF_AVAILABLE "UPDATE_DISCONNECTED 1")
|
||||
|
||||
include(DownloadProject)
|
||||
download_project(PROJ googletest
|
||||
GIT_REPOSITORY https://github.com/google/googletest.git
|
||||
GIT_TAG master
|
||||
UPDATE_DISCONNECTED 1
|
||||
QUIET
|
||||
)
|
||||
|
||||
# CMake warning suppression will not be needed in version 1.9
|
||||
set(CMAKE_SUPPRESS_DEVELOPER_WARNINGS 1 CACHE BOOL "")
|
||||
add_subdirectory(${googletest_SOURCE_DIR} ${googletest_SOURCE_DIR} EXCLUDE_FROM_ALL)
|
||||
unset(CMAKE_SUPPRESS_DEVELOPER_WARNINGS)
|
||||
else()
|
||||
include(FetchContent)
|
||||
FetchContent_Declare(googletest
|
||||
GIT_REPOSITORY https://github.com/google/googletest.git
|
||||
GIT_TAG master)
|
||||
FetchContent_GetProperties(googletest)
|
||||
if(NOT googletest_POPULATED)
|
||||
FetchContent_Populate(googletest)
|
||||
set(CMAKE_SUPPRESS_DEVELOPER_WARNINGS 1 CACHE BOOL "")
|
||||
add_subdirectory(${googletest_SOURCE_DIR} ${googletest_BINARY_DIR} EXCLUDE_FROM_ALL)
|
||||
unset(CMAKE_SUPPRESS_DEVELOPER_WARNINGS)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
|
||||
if(CMAKE_CONFIGURATION_TYPES)
|
||||
add_custom_target(check COMMAND ${CMAKE_CTEST_COMMAND}
|
||||
--force-new-ctest-process --output-on-failure
|
||||
--build-config "$<CONFIGURATION>")
|
||||
else()
|
||||
add_custom_target(check COMMAND ${CMAKE_CTEST_COMMAND}
|
||||
--force-new-ctest-process --output-on-failure)
|
||||
endif()
|
||||
set_target_properties(check PROPERTIES FOLDER "Scripts")
|
||||
|
||||
#include_directories(${gtest_SOURCE_DIR}/include)
|
||||
|
||||
# More modern way to do the last line, less messy but needs newish CMake:
|
||||
#target_include_directories(gtest INTERFACE ${gtest_SOURCE_DIR}/include)
|
||||
|
||||
|
||||
if(GOOGLE_TEST_INDIVIDUAL)
|
||||
if(NOT CMAKE_VERSION VERSION_LESS 3.9)
|
||||
include(GoogleTest)
|
||||
else()
|
||||
set(GOOGLE_TEST_INDIVIDUAL OFF)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Target must already exist
|
||||
macro(add_gtest TESTNAME)
|
||||
target_link_libraries(${TESTNAME} PUBLIC gtest gmock gtest_main)
|
||||
|
||||
if(GOOGLE_TEST_INDIVIDUAL)
|
||||
if(CMAKE_VERSION VERSION_LESS 3.10)
|
||||
gtest_add_tests(TARGET ${TESTNAME}
|
||||
TEST_PREFIX "${TESTNAME}."
|
||||
TEST_LIST TmpTestList)
|
||||
set_tests_properties(${TmpTestList} PROPERTIES FOLDER "Tests")
|
||||
else()
|
||||
gtest_discover_tests(${TESTNAME}
|
||||
TEST_PREFIX "${TESTNAME}."
|
||||
PROPERTIES FOLDER "Tests")
|
||||
endif()
|
||||
else()
|
||||
add_test(${TESTNAME} ${TESTNAME})
|
||||
set_target_properties(${TESTNAME} PROPERTIES FOLDER "Tests")
|
||||
endif()
|
||||
|
||||
endmacro()
|
||||
|
||||
mark_as_advanced(
|
||||
gmock_build_tests
|
||||
gtest_build_samples
|
||||
gtest_build_tests
|
||||
gtest_disable_pthreads
|
||||
gtest_force_shared_crt
|
||||
gtest_hide_internal_symbols
|
||||
BUILD_GMOCK
|
||||
BUILD_GTEST
|
||||
)
|
||||
|
||||
set_target_properties(gtest gtest_main gmock gmock_main
|
||||
PROPERTIES FOLDER "Extern")
|
||||
|
||||
@ -3,7 +3,8 @@ if(MSVC)
|
||||
set(WARNINGS_AS_ERRORS_FLAG "/WX")
|
||||
endif()
|
||||
|
||||
add_compile_options(/Wall ${WARNINGS_AS_ERRORS_FLAG} /wd5031 /wd4668 /wd4820 /wd4255 /wd4710 /analyze)
|
||||
#https://stackoverflow.com/questions/37527946/warning-unreferenced-inline-function-has-been-removed
|
||||
add_compile_options(/W4 ${WARNINGS_AS_ERRORS_FLAG} /wd5031 /wd4668 /wd4820 /wd4255 /wd4710 /analyze)
|
||||
elseif(CMAKE_C_COMPILER_ID MATCHES "Clang")
|
||||
if(WARNINGS_AS_ERRORS)
|
||||
set(WARNINGS_AS_ERRORS_FLAG "-Werror")
|
||||
|
||||
@ -28,6 +28,31 @@ jobs:
|
||||
vcpkg.deps: ''
|
||||
VCPKG_DEFAULT_TRIPLET: 'x64-osx'
|
||||
|
||||
# Unit testing ON
|
||||
Linux_x64_with_unit_test:
|
||||
vm.image: 'ubuntu-18.04'
|
||||
vcpkg.deps: ''
|
||||
VCPKG_DEFAULT_TRIPLET: 'x64-linux'
|
||||
build.args: ' -DBUILD_TESTING=ON'
|
||||
Win_x86_with_unit_test:
|
||||
vm.image: 'windows-2019'
|
||||
vcpkg.deps: ''
|
||||
VCPKG_DEFAULT_TRIPLET: 'x86-windows-static'
|
||||
CMAKE_GENERATOR: 'Visual Studio 16 2019'
|
||||
CMAKE_GENERATOR_PLATFORM: Win32
|
||||
build.args: ' -DBUILD_TESTING=ON'
|
||||
Win_x64_with_unit_test:
|
||||
vm.image: 'windows-2019'
|
||||
vcpkg.deps: ''
|
||||
VCPKG_DEFAULT_TRIPLET: 'x64-windows-static'
|
||||
CMAKE_GENERATOR: 'Visual Studio 16 2019'
|
||||
CMAKE_GENERATOR_PLATFORM: x64
|
||||
build.args: ' -DBUILD_TESTING=ON'
|
||||
MacOS_x64_with_unit_test:
|
||||
vm.image: 'macOS-10.14'
|
||||
vcpkg.deps: ''
|
||||
VCPKG_DEFAULT_TRIPLET: 'x64-osx'
|
||||
build.args: ' -DBUILD_TESTING=ON'
|
||||
pool:
|
||||
vmImage: $(vm.image)
|
||||
variables:
|
||||
|
||||
@ -4,17 +4,15 @@
|
||||
cmake_minimum_required (VERSION 3.12)
|
||||
|
||||
project (azure-core LANGUAGES CXX)
|
||||
set(CMAKE_CXX_STANDARD 11)
|
||||
set(CMAKE_CXX_STANDARD 14)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED True)
|
||||
|
||||
add_library (
|
||||
azure-core
|
||||
src/http/http_request
|
||||
src/http/http
|
||||
src/http/request
|
||||
)
|
||||
|
||||
# compiler warning flags
|
||||
include(global_compile_options)
|
||||
|
||||
target_include_directories (azure-core PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/inc> $<INSTALL_INTERFACE:include/az_core>)
|
||||
|
||||
# make sure that users can consume the project as a library.
|
||||
|
||||
@ -3,6 +3,10 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
#include <internal/contract.hpp>
|
||||
|
||||
namespace azure
|
||||
@ -12,18 +16,52 @@ namespace core
|
||||
namespace http
|
||||
{
|
||||
|
||||
class http_request
|
||||
// BodyStream is used to read data to/from a service
|
||||
class BodyStream
|
||||
{
|
||||
|
||||
private:
|
||||
/* data */
|
||||
public:
|
||||
http_request();
|
||||
~http_request();
|
||||
static BodyStream* null;
|
||||
|
||||
// Returns the length of the data; used with the HTTP Content-Length header
|
||||
virtual uint64_t Length() = 0;
|
||||
|
||||
// Resets the stream back to the beginning (for retries)
|
||||
// Derived classes that send data in an HTTP request MUST override this and implement it properly.
|
||||
virtual void Rewind()
|
||||
{
|
||||
throw "Not Implemented"; // TODO: Replace with best practice as defined by guideline
|
||||
};
|
||||
|
||||
// Reads more data; EOF if return < count; throws if error/canceled
|
||||
virtual uint64_t Read(/*Context& context, */ uint8_t* buffer, uint64_t offset, uint64_t count)
|
||||
= 0;
|
||||
|
||||
// Closes the stream; typically called after all data read or if an error occurs.
|
||||
virtual void Close() = 0;
|
||||
};
|
||||
|
||||
http_request::http_request() {}
|
||||
http_request::~http_request() {}
|
||||
class BodyBuffer
|
||||
{
|
||||
public:
|
||||
static BodyBuffer* null;
|
||||
|
||||
uint8_t const* _bodyBuffer;
|
||||
uint64_t _bodyBufferSize;
|
||||
BodyBuffer(uint8_t const* bodyBuffer, uint64_t bodyBufferSize)
|
||||
: _bodyBuffer(bodyBuffer), _bodyBufferSize(bodyBufferSize)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
enum class HttpMethod
|
||||
{
|
||||
GET,
|
||||
HEAD,
|
||||
POST,
|
||||
PUT,
|
||||
DELETE,
|
||||
PATCH,
|
||||
};
|
||||
|
||||
} // namespace http
|
||||
} // namespace core
|
||||
|
||||
147
sdk/core/azure-core/inc/http/request.hpp
Normal file
147
sdk/core/azure-core/inc/http/request.hpp
Normal file
@ -0,0 +1,147 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
#include <map>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <internal/contract.hpp>
|
||||
|
||||
namespace azure
|
||||
{
|
||||
namespace core
|
||||
{
|
||||
namespace http
|
||||
{
|
||||
|
||||
class Request
|
||||
{
|
||||
|
||||
private:
|
||||
Request(Request const&) = delete;
|
||||
void operator=(Request const&) = delete;
|
||||
|
||||
// query needs to be first or at least before url, since url might update it
|
||||
std::map<std::string, std::string> _queryParameters;
|
||||
|
||||
HttpMethod _method;
|
||||
std::string _url;
|
||||
std::map<std::string, std::string> _headers;
|
||||
std::map<std::string, std::string> _retryHeaders;
|
||||
std::map<std::string, std::string> _retryQueryParameters;
|
||||
// Request can contain no body, or either of next bodies (_bodyBuffer plus size or bodyStream)
|
||||
BodyStream* _bodyStream;
|
||||
BodyBuffer* _bodyBuffer;
|
||||
|
||||
// flag to know where to insert header
|
||||
bool _retryModeEnabled;
|
||||
|
||||
// returns left map plus all items in right
|
||||
// when duplicates, left items are preferred
|
||||
static std::map<std::string, std::string> mergeMaps(
|
||||
std::map<std::string, std::string> const& left,
|
||||
std::map<std::string, std::string> const& right)
|
||||
{
|
||||
auto result = std::map<std::string, std::string>(left);
|
||||
std::for_each(right.begin(), right.end(), [&](auto const& pair) { result.insert(pair); });
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an stream from source string. Then use getline and separator to get as many tokens as
|
||||
* possible and insert each one to result vector
|
||||
*/
|
||||
static std::vector<std::string> split(std::string source, char separator)
|
||||
{
|
||||
auto result = std::vector<std::string>();
|
||||
|
||||
auto stringAsStream = std::stringstream(source);
|
||||
auto token = std::string();
|
||||
|
||||
while (std::getline(stringAsStream, token, separator))
|
||||
{
|
||||
result.push_back(token);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Will check if there are any query parameter in url looking for symbol '?'
|
||||
* If it is found, it will insert query parameters to _queryParameters internal field
|
||||
* and remove it from url
|
||||
*/
|
||||
std::string parseUrl(std::string url)
|
||||
{
|
||||
auto position = url.find('?');
|
||||
|
||||
if (position == std::string::npos)
|
||||
{
|
||||
return url; // no query parameters. Nothing else to do
|
||||
}
|
||||
|
||||
// Get query parameters string and update
|
||||
auto queryParameters = url.substr(position + 1);
|
||||
url = url.substr(0, position);
|
||||
|
||||
// Split all query parameters (should be separated by &)
|
||||
auto queryParametersVector = split(queryParameters, '&');
|
||||
|
||||
// insert each query parameter to internal field
|
||||
std::for_each(queryParametersVector.begin(), queryParametersVector.end(), [&](auto query) {
|
||||
auto parameter = split(query, '=');
|
||||
// Will throw if parameter in query is not valid (i.e. arg:1)
|
||||
_queryParameters.insert(std::pair<std::string, std::string>(parameter[0], parameter[1]));
|
||||
});
|
||||
|
||||
return url;
|
||||
}
|
||||
|
||||
Request(
|
||||
HttpMethod httpMethod,
|
||||
std::string const& url,
|
||||
BodyStream* bodyStream,
|
||||
BodyBuffer* bodyBuffer)
|
||||
: _method(std::move(httpMethod)), _url(parseUrl(std::move(url))), _bodyStream(bodyStream),
|
||||
_bodyBuffer(bodyBuffer), _retryModeEnabled(false)
|
||||
{
|
||||
// TODO: parse url
|
||||
}
|
||||
|
||||
public:
|
||||
Request(HttpMethod httpMethod, std::string const& url)
|
||||
: Request(httpMethod, url, BodyStream::null, BodyBuffer::null)
|
||||
{
|
||||
}
|
||||
|
||||
Request(HttpMethod httpMethod, std::string const& url, BodyBuffer* bodyBuffer)
|
||||
: Request(httpMethod, url, BodyStream::null, bodyBuffer)
|
||||
{
|
||||
}
|
||||
|
||||
Request(HttpMethod httpMethod, std::string const& url, BodyStream* bodyStream)
|
||||
: Request(httpMethod, url, bodyStream, BodyBuffer::null)
|
||||
{
|
||||
}
|
||||
|
||||
// Methods used to build HTTP request
|
||||
void addPath(std::string const& path);
|
||||
void addQueryParameter(std::string const& name, std::string const& value);
|
||||
void addHeader(std::string const& name, std::string const& value);
|
||||
void startRetry(); // only called by retry policy
|
||||
|
||||
// Methods used by transport layer (and logger) to send request
|
||||
HttpMethod getMethod();
|
||||
std::string getEncodedUrl(); // should return encoded url
|
||||
std::map<std::string, std::string> getHeaders();
|
||||
BodyStream* getBodyStream();
|
||||
BodyBuffer* getBodyBuffer();
|
||||
};
|
||||
|
||||
} // namespace http
|
||||
} // namespace core
|
||||
} // namespace azure
|
||||
65
sdk/core/azure-core/inc/http/response.hpp
Normal file
65
sdk/core/azure-core/inc/http/response.hpp
Normal file
@ -0,0 +1,65 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
#include <internal/contract.hpp>
|
||||
|
||||
namespace azure
|
||||
{
|
||||
namespace core
|
||||
{
|
||||
namespace http
|
||||
{
|
||||
|
||||
class Response
|
||||
{
|
||||
|
||||
private:
|
||||
Response(Response const&) = delete;
|
||||
void operator=(Response const&) = delete;
|
||||
|
||||
uint16_t _statusCode;
|
||||
std::string _reasonPhrase;
|
||||
std::map<std::string, std::string> _headers;
|
||||
|
||||
// Response can contain no body, or either of next bodies (_bodyBuffer plus size or bodyStream)
|
||||
BodyBuffer& _bodyBuffer;
|
||||
BodyStream& _bodyStream;
|
||||
|
||||
Response(
|
||||
uint16_t statusCode,
|
||||
std::string reasonPhrase,
|
||||
BodyBuffer& const bodyBuffer,
|
||||
BodyStream& const BodyStream)
|
||||
: _statusCode(statusCode), _reasonPhrase(reasonPhrase), _bodyBuffer(bodyBuffer),
|
||||
_bodyStream(BodyStream)
|
||||
{
|
||||
}
|
||||
|
||||
public:
|
||||
Response(uint16_t statusCode, std::string reasonPhrase)
|
||||
: Response(statusCode, reasonPhrase, BodyBuffer::null, BodyStream::null)
|
||||
{
|
||||
}
|
||||
|
||||
// Methods used to build HTTP response
|
||||
void addHeader(std::string const& name, std::string const& value);
|
||||
void setBody(BodyBuffer& const bodyBuffer);
|
||||
void setBody(BodyStream& const bodyStream);
|
||||
|
||||
// Methods used by transport layer (and logger) to send response
|
||||
uint16_t const& getStatusCode();
|
||||
std::string const& getReasonPhrase();
|
||||
std::map<std::string, std::string> const& getHeaders();
|
||||
http::BodyStream& getBodyStream();
|
||||
http::BodyBuffer& getBodyBuffer();
|
||||
};
|
||||
|
||||
} // namespace http
|
||||
} // namespace core
|
||||
} // namespace azure
|
||||
@ -1,28 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include <contract.hpp>
|
||||
|
||||
namespace azure
|
||||
{
|
||||
namespace core
|
||||
{
|
||||
namespace http
|
||||
{
|
||||
class az_http_request
|
||||
{
|
||||
|
||||
private:
|
||||
/* data */
|
||||
public:
|
||||
az_http_request();
|
||||
~az_http_request();
|
||||
};
|
||||
|
||||
az_http_request::az_http_request() {}
|
||||
|
||||
az_http_request::~az_http_request() {}
|
||||
|
||||
} // namespace http
|
||||
} // namespace core
|
||||
} // namespace azure
|
||||
@ -3,13 +3,7 @@
|
||||
|
||||
#include <http/http.hpp>
|
||||
|
||||
namespace azure
|
||||
{
|
||||
namespace core
|
||||
{
|
||||
namespace http
|
||||
{
|
||||
using namespace azure::core::http;
|
||||
|
||||
} // namespace http
|
||||
} // namespace core
|
||||
} // namespace azure
|
||||
BodyStream* BodyStream::null = nullptr;
|
||||
BodyBuffer* BodyBuffer::null = nullptr;
|
||||
77
sdk/core/azure-core/src/http/request.cpp
Normal file
77
sdk/core/azure-core/src/http/request.cpp
Normal file
@ -0,0 +1,77 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <http/http.hpp>
|
||||
#include <http/request.hpp>
|
||||
|
||||
using namespace azure::core::http;
|
||||
|
||||
void Request::addPath(std::string const& path) { this->_url += "/" + path; }
|
||||
|
||||
void Request::addQueryParameter(std::string const& name, std::string const& value)
|
||||
{
|
||||
if (this->_retryModeEnabled)
|
||||
{
|
||||
// When retry mode is ON, any new value must override previous
|
||||
this->_retryQueryParameters[name] = value;
|
||||
}
|
||||
else
|
||||
{
|
||||
this->_queryParameters.insert(std::pair<std::string, std::string>(name, value));
|
||||
}
|
||||
}
|
||||
|
||||
void Request::addHeader(std::string const& name, std::string const& value)
|
||||
{
|
||||
if (this->_retryModeEnabled)
|
||||
{
|
||||
// When retry mode is ON, any new value must override previous
|
||||
this->_retryHeaders[name] = value;
|
||||
}
|
||||
else
|
||||
{
|
||||
this->_headers.insert(std::pair<std::string, std::string>(name, value));
|
||||
}
|
||||
}
|
||||
|
||||
void Request::startRetry()
|
||||
{
|
||||
this->_retryModeEnabled = true;
|
||||
this->_retryHeaders.clear();
|
||||
}
|
||||
|
||||
HttpMethod Request::getMethod() { return this->_method; }
|
||||
|
||||
std::string Request::getEncodedUrl()
|
||||
{
|
||||
if (this->_queryParameters.size() == 0 && this->_retryQueryParameters.size() == 0)
|
||||
{
|
||||
return _url; // no query parameters to add
|
||||
}
|
||||
|
||||
// remove query duplicates
|
||||
auto queryParameters = Request::mergeMaps(this->_retryQueryParameters, this->_queryParameters);
|
||||
// build url
|
||||
auto queryString = std::string("");
|
||||
for (auto pair : queryParameters)
|
||||
{
|
||||
queryString += (queryString.empty() ? "?" : "&") + pair.first + "=" + pair.second;
|
||||
}
|
||||
|
||||
return _url + queryString;
|
||||
}
|
||||
|
||||
std::map<std::string, std::string> Request::getHeaders()
|
||||
{
|
||||
// create map with retry headers witch are the most important and we don't want
|
||||
// to override them with any duplicate header
|
||||
return Request::mergeMaps(this->_retryHeaders, this->_headers);
|
||||
}
|
||||
|
||||
BodyStream* Request::getBodyStream() { return _bodyStream; }
|
||||
|
||||
BodyBuffer* Request::getBodyBuffer() { return _bodyBuffer; }
|
||||
24
sdk/core/azure-core/src/http/response.cpp
Normal file
24
sdk/core/azure-core/src/http/response.cpp
Normal file
@ -0,0 +1,24 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <http/http.hpp>
|
||||
#include <http/response.hpp>
|
||||
|
||||
using namespace azure::core::http;
|
||||
|
||||
BodyStream& BodyStream::null = *(BodyStream*)NULL;
|
||||
BodyBuffer& BodyBuffer::null = *(BodyBuffer*)NULL;
|
||||
|
||||
uint16_t const& Response::getStatusCode() { return _statusCode; }
|
||||
|
||||
std::string const& Response::getReasonPhrase() { return _reasonPhrase; }
|
||||
|
||||
std::map<std::string, std::string> const& Response::getHeaders() { return this->_headers; }
|
||||
|
||||
BodyStream& Response::getBodyStream() { return _bodyStream; }
|
||||
|
||||
BodyBuffer& Response::getBodyBuffer() { return _bodyBuffer; }
|
||||
19
sdk/core/azure-core/test/CMakeLists.txt
Normal file
19
sdk/core/azure-core/test/CMakeLists.txt
Normal file
@ -0,0 +1,19 @@
|
||||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
cmake_minimum_required (VERSION 3.12)
|
||||
|
||||
include(AddGoogleTest)
|
||||
|
||||
project (azure-core-test LANGUAGES CXX)
|
||||
set(CMAKE_CXX_STANDARD 14)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED True)
|
||||
|
||||
add_executable (
|
||||
azure-core-test
|
||||
main.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(azure-core-test PRIVATE azure-core)
|
||||
add_gtest(azure-core-test)
|
||||
|
||||
@ -1,16 +1,151 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include <azure.hpp>
|
||||
#include <http/http.hpp>
|
||||
#include <http/request.hpp>
|
||||
|
||||
#include <stdlib.h>
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
namespace azure
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
using namespace azure::core;
|
||||
|
||||
TEST(Http_Request, getters)
|
||||
{
|
||||
namespace core
|
||||
http::HttpMethod httpMethod = http::HttpMethod::GET;
|
||||
std::string url = "http://test.url.com";
|
||||
http::Request req(httpMethod, url);
|
||||
|
||||
// EXPECT_PRED works better than just EQ because it will print values in log
|
||||
EXPECT_PRED2(
|
||||
[](http::HttpMethod a, http::HttpMethod b) { return a == b; }, req.getMethod(), httpMethod);
|
||||
EXPECT_PRED2([](std::string a, std::string b) { return a == b; }, req.getEncodedUrl(), url);
|
||||
/* EXPECT_PRED2(
|
||||
[](std::string a, std::string b) { return a == b; },
|
||||
req.getBodyStream(),
|
||||
http::BodyStream::null);
|
||||
EXPECT_PRED2(
|
||||
[](std::string a, std::string b) { return a == b; },
|
||||
req.getBodyBuffer(),
|
||||
http::BodyBuffer::null); */
|
||||
|
||||
uint8_t buffer[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
|
||||
auto bufferBody = http::BodyBuffer(buffer, sizeof(buffer));
|
||||
http::Request requestWithBody(httpMethod, url, &bufferBody);
|
||||
|
||||
EXPECT_PRED2(
|
||||
[](http::HttpMethod a, http::HttpMethod b) { return a == b; },
|
||||
requestWithBody.getMethod(),
|
||||
httpMethod);
|
||||
EXPECT_PRED2(
|
||||
[](std::string a, std::string b) { return a == b; }, requestWithBody.getEncodedUrl(), url);
|
||||
/* EXPECT_PRED2(
|
||||
[](std::string a, std::string b) { return a == b; },
|
||||
requestWithBody.getBodyStream(),
|
||||
http::BodyStream::null); */
|
||||
|
||||
// body with buffer
|
||||
auto body = requestWithBody.getBodyBuffer();
|
||||
ASSERT_EQ(body->_bodyBufferSize, 10);
|
||||
for (auto i = 0; i < 10; i++)
|
||||
{
|
||||
ASSERT_EQ(body->_bodyBuffer[i], i);
|
||||
}
|
||||
|
||||
EXPECT_NO_THROW(req.addHeader("name", "value"));
|
||||
EXPECT_NO_THROW(req.addHeader("name2", "value2"));
|
||||
|
||||
auto headers = req.getHeaders();
|
||||
|
||||
EXPECT_TRUE(headers.count("name"));
|
||||
EXPECT_TRUE(headers.count("name2"));
|
||||
EXPECT_FALSE(headers.count("newHeader"));
|
||||
|
||||
auto value = headers.find("name");
|
||||
EXPECT_PRED2([](std::string a, std::string b) { return a == b; }, value->second, "value");
|
||||
auto value2 = headers.find("name2");
|
||||
EXPECT_PRED2([](std::string a, std::string b) { return a == b; }, value2->second, "value2");
|
||||
|
||||
// now add to retry headers
|
||||
req.startRetry();
|
||||
// same headers first, then one new
|
||||
EXPECT_NO_THROW(req.addHeader("name", "retryValue"));
|
||||
EXPECT_NO_THROW(req.addHeader("name2", "retryValue2"));
|
||||
EXPECT_NO_THROW(req.addHeader("newHeader", "new"));
|
||||
|
||||
headers = req.getHeaders();
|
||||
|
||||
EXPECT_TRUE(headers.count("name"));
|
||||
EXPECT_TRUE(headers.count("name2"));
|
||||
EXPECT_TRUE(headers.count("newHeader"));
|
||||
|
||||
value = headers.find("name");
|
||||
EXPECT_PRED2([](std::string a, std::string b) { return a == b; }, value->second, "retryValue");
|
||||
value2 = headers.find("name2");
|
||||
EXPECT_PRED2([](std::string a, std::string b) { return a == b; }, value2->second, "retryValue2");
|
||||
auto value3 = headers.find("newHeader");
|
||||
EXPECT_PRED2([](std::string a, std::string b) { return a == b; }, value3->second, "new");
|
||||
}
|
||||
|
||||
TEST(Http_Request, query_parameter)
|
||||
{
|
||||
http::HttpMethod httpMethod = http::HttpMethod::PUT;
|
||||
std::string url = "http://test.com";
|
||||
http::Request req(httpMethod, url);
|
||||
|
||||
int main() { return 0; }
|
||||
EXPECT_NO_THROW(req.addQueryParameter("query", "value"));
|
||||
EXPECT_PRED2(
|
||||
[](std::string a, std::string b) { return a == b; },
|
||||
req.getEncodedUrl(),
|
||||
url + "?query=value");
|
||||
|
||||
} // namespace core
|
||||
} // namespace azure
|
||||
std::string url_with_query = "http://test.com?query=1";
|
||||
http::Request req_with_query(httpMethod, url_with_query);
|
||||
|
||||
// ignore if adding same query parameter key that is already in url
|
||||
EXPECT_NO_THROW(req_with_query.addQueryParameter("query", "value"));
|
||||
EXPECT_PRED2(
|
||||
[](std::string a, std::string b) { return a == b; },
|
||||
req_with_query.getEncodedUrl(),
|
||||
url_with_query);
|
||||
|
||||
// retry query params testing
|
||||
req.startRetry();
|
||||
// same query parameter should override previous
|
||||
EXPECT_NO_THROW(req.addQueryParameter("query", "retryValue"));
|
||||
|
||||
EXPECT_PRED2(
|
||||
[](std::string a, std::string b) { return a == b; },
|
||||
req.getEncodedUrl(),
|
||||
url + "?query=retryValue");
|
||||
}
|
||||
|
||||
TEST(Http_Request, add_path)
|
||||
{
|
||||
http::HttpMethod httpMethod = http::HttpMethod::POST;
|
||||
std::string url = "http://test.com";
|
||||
http::Request req(httpMethod, url);
|
||||
|
||||
EXPECT_NO_THROW(req.addPath("path"));
|
||||
EXPECT_PRED2(
|
||||
[](std::string a, std::string b) { return a == b; }, req.getEncodedUrl(), url + "/path");
|
||||
|
||||
EXPECT_NO_THROW(req.addQueryParameter("query", "value"));
|
||||
EXPECT_PRED2(
|
||||
[](std::string a, std::string b) { return a == b; },
|
||||
req.getEncodedUrl(),
|
||||
url + "/path?query=value");
|
||||
|
||||
EXPECT_NO_THROW(req.addPath("path2"));
|
||||
EXPECT_PRED2(
|
||||
[](std::string a, std::string b) { return a == b; },
|
||||
req.getEncodedUrl(),
|
||||
url + "/path/path2?query=value");
|
||||
|
||||
EXPECT_NO_THROW(req.addPath("path3"));
|
||||
EXPECT_PRED2(
|
||||
[](std::string a, std::string b) { return a == b; },
|
||||
req.getEncodedUrl(),
|
||||
url + "/path/path2/path3?query=value");
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user