Adding writing response from curl to buffer (#64)
* write headers and response code to Response
This commit is contained in:
Родитель
d3360abf2b
Коммит
c19f9e2b05
|
@ -4,14 +4,12 @@
|
|||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
#include <internal/contract.hpp>
|
||||
#include <map>
|
||||
#include <sstream>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <internal/contract.hpp>
|
||||
|
||||
namespace Azure { namespace Core { namespace Http {
|
||||
|
||||
// BodyStream is used to read data to/from a service
|
||||
|
@ -38,16 +36,80 @@ namespace Azure { namespace Core { namespace Http {
|
|||
virtual void Close() = 0;
|
||||
};
|
||||
|
||||
class BodyBuffer {
|
||||
public:
|
||||
static BodyBuffer* null;
|
||||
enum class HttpStatusCode
|
||||
{
|
||||
None = 0,
|
||||
|
||||
uint8_t const* _bodyBuffer;
|
||||
uint64_t _bodyBufferSize;
|
||||
BodyBuffer(uint8_t const* bodyBuffer, uint64_t bodyBufferSize)
|
||||
: _bodyBuffer(bodyBuffer), _bodyBufferSize(bodyBufferSize)
|
||||
{
|
||||
}
|
||||
// 1xx (information) Status Codes:
|
||||
Continue = 100,
|
||||
SwitchingProtocols = 101,
|
||||
Processing = 102,
|
||||
EarlyHints = 103,
|
||||
|
||||
// 2xx (successful) Status Codes:
|
||||
Ok = 200,
|
||||
Created = 201,
|
||||
Accepted = 202,
|
||||
NonAuthoritativeInformation = 203,
|
||||
NoContent = 204,
|
||||
ResetContent = 205,
|
||||
PartialContent = 206,
|
||||
MultiStatus = 207,
|
||||
AlreadyReported = 208,
|
||||
ImUsed = 226,
|
||||
|
||||
// 3xx (redirection) Status Codes:
|
||||
MultipleChoices = 300,
|
||||
MovedPermanently = 301,
|
||||
Found = 302,
|
||||
SeeOther = 303,
|
||||
NotModified = 304,
|
||||
UseProxy = 305,
|
||||
TemporaryRedirect = 307,
|
||||
PermanentRedirect = 308,
|
||||
|
||||
// 4xx (client error) Status Codes:
|
||||
BadRequest = 400,
|
||||
Unauthorized = 401,
|
||||
PaymentRequired = 402,
|
||||
Forbidden = 403,
|
||||
NotFound = 404,
|
||||
MethodNotAllowed = 405,
|
||||
NotAcceptable = 406,
|
||||
ProxyAuthenticationRequired = 407,
|
||||
RequestTimeout = 408,
|
||||
Conflict = 409,
|
||||
Gone = 410,
|
||||
LengthRequired = 411,
|
||||
PreconditionFailed = 412,
|
||||
PayloadTooLarge = 413,
|
||||
UriTooLong = 414,
|
||||
UnsupportedMediaType = 415,
|
||||
RangeNotSatisfiable = 416,
|
||||
ExpectationFailed = 417,
|
||||
MisdirectedRequest = 421,
|
||||
UnprocessableEntity = 422,
|
||||
Locked = 423,
|
||||
FailedDependency = 424,
|
||||
TooEarly = 425,
|
||||
UpgradeRequired = 426,
|
||||
PreconditionRequired = 428,
|
||||
TooManyRequests = 429,
|
||||
RequestHeaderFieldsTooLarge = 431,
|
||||
UnavailableForLegalReasons = 451,
|
||||
|
||||
// 5xx (server error) Status Codes:
|
||||
InternalServerError = 500,
|
||||
NotImplemented = 501,
|
||||
BadGateway = 502,
|
||||
ServiceUnavailable = 503,
|
||||
GatewayTimeout = 504,
|
||||
HttpVersionNotSupported = 505,
|
||||
VariantAlsoNegotiates = 506,
|
||||
InsufficientStorage = 507,
|
||||
LoopDetected = 508,
|
||||
NotExtended = 510,
|
||||
NetworkAuthenticationRequired = 511,
|
||||
};
|
||||
|
||||
enum class HttpMethod
|
||||
|
@ -73,14 +135,14 @@ namespace Azure { namespace Core { namespace Http {
|
|||
std::map<std::string, std::string> m_retryQueryParameters;
|
||||
// Request can contain no body, or either of next bodies (_bodyBuffer plus size or bodyStream)
|
||||
BodyStream* m_bodyStream;
|
||||
BodyBuffer* m_bodyBuffer;
|
||||
std::vector<uint8_t> m_bodyBuffer;
|
||||
|
||||
// flag to know where to insert header
|
||||
bool m_retryModeEnabled;
|
||||
|
||||
// returns left map plus all items in right
|
||||
// when duplicates, left items are preferred
|
||||
static std::map<std::string, std::string> mergeMaps(
|
||||
static std::map<std::string, std::string> MergeMaps(
|
||||
std::map<std::string, std::string> left,
|
||||
std::map<std::string, std::string> const& right)
|
||||
{
|
||||
|
@ -114,8 +176,8 @@ namespace Azure { namespace Core { namespace Http {
|
|||
++valueStart; // skip = symbol
|
||||
}
|
||||
|
||||
// Note: if there is another = symbol before nextPosition, it will be part of the paramenter
|
||||
// value. And if there is not a ? symbol, we add empty string as value
|
||||
// Note: if there is another = symbol before nextPosition, it will be part of the
|
||||
// paramenter value. And if there is not a ? symbol, we add empty string as value
|
||||
m_queryParameters.insert(std::pair<std::string, std::string>(
|
||||
std::string(position, equalChar), std::string(valueStart, nextPosition)));
|
||||
|
||||
|
@ -129,7 +191,7 @@ namespace Azure { namespace Core { namespace Http {
|
|||
HttpMethod httpMethod,
|
||||
std::string const& url,
|
||||
BodyStream* bodyStream,
|
||||
BodyBuffer* bodyBuffer)
|
||||
std::vector<uint8_t> const& bodyBuffer)
|
||||
: _method(std::move(httpMethod)), _url(parseUrl(url)), m_bodyStream(bodyStream),
|
||||
m_bodyBuffer(bodyBuffer), m_retryModeEnabled(false)
|
||||
{
|
||||
|
@ -138,32 +200,32 @@ namespace Azure { namespace Core { namespace Http {
|
|||
|
||||
public:
|
||||
Request(HttpMethod httpMethod, std::string const& url)
|
||||
: Request(httpMethod, url, BodyStream::null, BodyBuffer::null)
|
||||
: Request(httpMethod, url, BodyStream::null, std::vector<uint8_t>())
|
||||
{
|
||||
}
|
||||
|
||||
Request(HttpMethod httpMethod, std::string const& url, BodyBuffer* bodyBuffer)
|
||||
Request(HttpMethod httpMethod, std::string const& url, std::vector<uint8_t> const& bodyBuffer)
|
||||
: Request(httpMethod, url, BodyStream::null, bodyBuffer)
|
||||
{
|
||||
}
|
||||
|
||||
Request(HttpMethod httpMethod, std::string const& url, BodyStream* bodyStream)
|
||||
: Request(httpMethod, url, bodyStream, BodyBuffer::null)
|
||||
: Request(httpMethod, url, bodyStream, std::vector<uint8_t>())
|
||||
{
|
||||
}
|
||||
|
||||
// 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
|
||||
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();
|
||||
HttpMethod GetMethod();
|
||||
std::string GetEncodedUrl(); // should return encoded url
|
||||
std::map<std::string, std::string> GetHeaders();
|
||||
BodyStream* GetBodyStream();
|
||||
std::vector<uint8_t> const& GetBodyBuffer();
|
||||
};
|
||||
|
||||
/*
|
||||
|
@ -171,70 +233,81 @@ namespace Azure { namespace Core { namespace Http {
|
|||
*/
|
||||
struct CouldNotResolveHostException : public std::exception
|
||||
{
|
||||
const char* what() const throw()
|
||||
{
|
||||
return "couldnt resolve host";
|
||||
}
|
||||
const char* what() const throw() { return "couldnt resolve host"; }
|
||||
};
|
||||
|
||||
struct ErrorWhileWrittingResponse : public std::exception
|
||||
{
|
||||
const char* what() const throw()
|
||||
{
|
||||
return "couldnt write response";
|
||||
}
|
||||
const char* what() const throw() { return "couldnt write response"; }
|
||||
};
|
||||
|
||||
// Any other excpetion from transport layer without an specific exception defined above
|
||||
struct TransportException : public std::exception
|
||||
{
|
||||
const char* what() const throw()
|
||||
{
|
||||
return "Error on transport layer while sending request";
|
||||
}
|
||||
const char* what() const throw() { return "Error on transport layer while sending request"; }
|
||||
};
|
||||
|
||||
class Response {
|
||||
|
||||
private:
|
||||
uint16_t m_statusCode;
|
||||
uint16_t m_majorVersion;
|
||||
uint16_t m_minorVersion;
|
||||
HttpStatusCode m_statusCode;
|
||||
std::string m_reasonPhrase;
|
||||
std::map<std::string, std::string> m_headers;
|
||||
|
||||
// Response can contain no body, or either of next bodies (_bodyBuffer plus size or bodyStream)
|
||||
Http::BodyBuffer* m_bodyBuffer;
|
||||
// Response can contain no body, or either of next bodies (m_bodyBuffer or
|
||||
// bodyStream)
|
||||
std::vector<uint8_t> m_bodyBuffer;
|
||||
Http::BodyStream* m_bodyStream;
|
||||
|
||||
Response(
|
||||
uint16_t statusCode,
|
||||
int16_t majorVersion,
|
||||
uint16_t minorVersion,
|
||||
HttpStatusCode statusCode,
|
||||
std::string const& reasonPhrase,
|
||||
BodyBuffer* const bodyBuffer,
|
||||
std::vector<uint8_t> const& bodyBuffer,
|
||||
BodyStream* const BodyStream)
|
||||
: m_statusCode(statusCode), m_reasonPhrase(reasonPhrase), m_bodyBuffer(bodyBuffer),
|
||||
m_bodyStream(BodyStream)
|
||||
: m_majorVersion(majorVersion), m_minorVersion(minorVersion), m_statusCode(statusCode),
|
||||
m_reasonPhrase(reasonPhrase), m_bodyBuffer(bodyBuffer), m_bodyStream(BodyStream)
|
||||
{
|
||||
}
|
||||
|
||||
public:
|
||||
Response(uint16_t statusCode, std::string const& reasonPhrase)
|
||||
: Response(statusCode, reasonPhrase, Http::BodyBuffer::null, Http::BodyStream::null)
|
||||
Response(
|
||||
uint16_t majorVersion,
|
||||
uint16_t minorVersion,
|
||||
HttpStatusCode statusCode,
|
||||
std::string const& reasonPhrase)
|
||||
: Response(
|
||||
majorVersion,
|
||||
minorVersion,
|
||||
statusCode,
|
||||
reasonPhrase,
|
||||
std::vector<uint8_t>(),
|
||||
Http::BodyStream::null)
|
||||
{
|
||||
}
|
||||
|
||||
// Methods used to build HTTP response
|
||||
void addHeader(std::string const& name, std::string const& value);
|
||||
void setBody(BodyBuffer* bodyBuffer);
|
||||
void setBody(BodyStream* bodyStream);
|
||||
void AddHeader(std::string const& name, std::string const& value);
|
||||
void AppendBody(uint8_t* ptr, uint64_t size);
|
||||
|
||||
// Methods used by transport layer (and logger) to send response
|
||||
uint16_t getStatusCode();
|
||||
std::string const& getReasonPhrase();
|
||||
std::map<std::string, std::string> const& getHeaders();
|
||||
Http::BodyStream* getBodyStream();
|
||||
Http::BodyBuffer* getBodyBuffer();
|
||||
// Util methods for customers to read response/parse an http response
|
||||
HttpStatusCode GetStatusCode();
|
||||
std::string const& GetReasonPhrase();
|
||||
std::map<std::string, std::string> const& GetHeaders();
|
||||
std::vector<uint8_t>& GetBodyBuffer();
|
||||
|
||||
// adding getters for version and stream body. Clang will complain on Mac if we have unused
|
||||
// fields in a class
|
||||
uint16_t GetmajorVersion() { return m_majorVersion; }
|
||||
uint16_t GetMinorVersion() { return m_minorVersion; }
|
||||
Http::BodyStream* GetBodyStream() { return m_bodyStream; }
|
||||
};
|
||||
|
||||
class Client {
|
||||
public:
|
||||
static Response send(Request& request);
|
||||
static std::unique_ptr<Response> Send(Request& request);
|
||||
};
|
||||
|
||||
}}} // namespace Azure::Core::Http
|
||||
|
|
|
@ -6,4 +6,3 @@
|
|||
using namespace Azure::Core::Http;
|
||||
|
||||
BodyStream* BodyStream::null = nullptr;
|
||||
BodyBuffer* BodyBuffer::null = nullptr;
|
||||
|
|
|
@ -1,20 +1,16 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include <http/http.hpp>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <http/http.hpp>
|
||||
|
||||
using namespace Azure::Core::Http;
|
||||
|
||||
void Request::addPath(std::string const& path)
|
||||
{
|
||||
this->_url += "/" + path;
|
||||
}
|
||||
void Request::AddPath(std::string const& path) { this->_url += "/" + path; }
|
||||
|
||||
void Request::addQueryParameter(std::string const& name, std::string const& value)
|
||||
void Request::AddQueryParameter(std::string const& name, std::string const& value)
|
||||
{
|
||||
if (this->m_retryModeEnabled)
|
||||
{
|
||||
|
@ -27,7 +23,7 @@ void Request::addQueryParameter(std::string const& name, std::string const& valu
|
|||
}
|
||||
}
|
||||
|
||||
void Request::addHeader(std::string const& name, std::string const& value)
|
||||
void Request::AddHeader(std::string const& name, std::string const& value)
|
||||
{
|
||||
if (this->m_retryModeEnabled)
|
||||
{
|
||||
|
@ -40,18 +36,15 @@ void Request::addHeader(std::string const& name, std::string const& value)
|
|||
}
|
||||
}
|
||||
|
||||
void Request::startRetry()
|
||||
void Request::StartRetry()
|
||||
{
|
||||
this->m_retryModeEnabled = true;
|
||||
this->m_retryHeaders.clear();
|
||||
}
|
||||
|
||||
HttpMethod Request::getMethod()
|
||||
{
|
||||
return this->_method;
|
||||
}
|
||||
HttpMethod Request::GetMethod() { return this->_method; }
|
||||
|
||||
std::string Request::getEncodedUrl()
|
||||
std::string Request::GetEncodedUrl()
|
||||
{
|
||||
if (this->m_queryParameters.size() == 0 && this->m_retryQueryParameters.size() == 0)
|
||||
{
|
||||
|
@ -59,7 +52,7 @@ std::string Request::getEncodedUrl()
|
|||
}
|
||||
|
||||
// remove query duplicates
|
||||
auto queryParameters = Request::mergeMaps(this->m_retryQueryParameters, this->m_queryParameters);
|
||||
auto queryParameters = Request::MergeMaps(this->m_retryQueryParameters, this->m_queryParameters);
|
||||
// build url
|
||||
auto queryString = std::string("");
|
||||
for (auto pair : queryParameters)
|
||||
|
@ -70,19 +63,11 @@ std::string Request::getEncodedUrl()
|
|||
return _url + queryString;
|
||||
}
|
||||
|
||||
std::map<std::string, std::string> Request::getHeaders()
|
||||
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->m_retryHeaders, this->m_headers);
|
||||
return Request::MergeMaps(this->m_retryHeaders, this->m_headers);
|
||||
}
|
||||
|
||||
BodyStream* Request::getBodyStream()
|
||||
{
|
||||
return m_bodyStream;
|
||||
}
|
||||
|
||||
BodyBuffer* Request::getBodyBuffer()
|
||||
{
|
||||
return m_bodyBuffer;
|
||||
}
|
||||
BodyStream* Request::GetBodyStream() { return m_bodyStream; }
|
||||
|
|
|
@ -1,35 +1,57 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include <cctype>
|
||||
#include <http/http.hpp>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <http/http.hpp>
|
||||
|
||||
using namespace Azure::Core::Http;
|
||||
|
||||
uint16_t Response::getStatusCode()
|
||||
HttpStatusCode Response::GetStatusCode() { return m_statusCode; }
|
||||
|
||||
std::string const& Response::GetReasonPhrase() { return m_reasonPhrase; }
|
||||
|
||||
std::map<std::string, std::string> const& Response::GetHeaders() { return this->m_headers; }
|
||||
|
||||
std::vector<uint8_t>& Response::GetBodyBuffer() { return m_bodyBuffer; }
|
||||
|
||||
namespace {
|
||||
inline bool IsStringEqualsIgnoreCase(std::string const& a, std::string const& b)
|
||||
{
|
||||
return m_statusCode;
|
||||
auto alen = a.length();
|
||||
auto blen = b.length();
|
||||
|
||||
if (alen != blen)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
for (size_t index = 0; index < alen; index++)
|
||||
{
|
||||
// TODO: tolower is bad for some charsets, see if this can be enhanced
|
||||
if (std::tolower(a.at(index)) != std::tolower(b.at(index)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void Response::AddHeader(std::string const& name, std::string const& value)
|
||||
{
|
||||
if (IsStringEqualsIgnoreCase("Content-Length", name))
|
||||
{
|
||||
// whenever this header is found, we reserve the value of it to be pre-allocated to write
|
||||
// response
|
||||
m_bodyBuffer.reserve(std::stol(value));
|
||||
}
|
||||
this->m_headers.insert(std::pair<std::string, std::string>(name, value));
|
||||
}
|
||||
|
||||
std::string const& Response::getReasonPhrase()
|
||||
void Response::AppendBody(uint8_t* ptr, uint64_t size)
|
||||
{
|
||||
return m_reasonPhrase;
|
||||
}
|
||||
|
||||
std::map<std::string, std::string> const& Response::getHeaders()
|
||||
{
|
||||
return this->m_headers;
|
||||
}
|
||||
|
||||
BodyStream* Response::getBodyStream()
|
||||
{
|
||||
return m_bodyStream;
|
||||
}
|
||||
|
||||
BodyBuffer* Response::getBodyBuffer()
|
||||
{
|
||||
return m_bodyBuffer;
|
||||
}
|
||||
m_bodyBuffer.insert(m_bodyBuffer.end(), ptr, ptr + size);
|
||||
}
|
|
@ -1,11 +1,9 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include <http/http.hpp>
|
||||
#include <internal/credentials_internal.hpp>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
|
@ -19,44 +17,13 @@ TEST(Http_Request, getters)
|
|||
|
||||
// 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); */
|
||||
[](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);
|
||||
|
||||
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_NO_THROW(req.AddHeader("name", "value"));
|
||||
EXPECT_NO_THROW(req.AddHeader("name2", "value2"));
|
||||
|
||||
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();
|
||||
auto headers = req.GetHeaders();
|
||||
|
||||
EXPECT_TRUE(headers.count("name"));
|
||||
EXPECT_TRUE(headers.count("name2"));
|
||||
|
@ -68,13 +35,13 @@ TEST(Http_Request, getters)
|
|||
EXPECT_PRED2([](std::string a, std::string b) { return a == b; }, value2->second, "value2");
|
||||
|
||||
// now add to retry headers
|
||||
req.startRetry();
|
||||
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"));
|
||||
EXPECT_NO_THROW(req.AddHeader("name", "retryValue"));
|
||||
EXPECT_NO_THROW(req.AddHeader("name2", "retryValue2"));
|
||||
EXPECT_NO_THROW(req.AddHeader("newHeader", "new"));
|
||||
|
||||
headers = req.getHeaders();
|
||||
headers = req.GetHeaders();
|
||||
|
||||
EXPECT_TRUE(headers.count("name"));
|
||||
EXPECT_TRUE(headers.count("name2"));
|
||||
|
@ -94,30 +61,30 @@ TEST(Http_Request, query_parameter)
|
|||
std::string url = "http://test.com";
|
||||
Http::Request req(httpMethod, url);
|
||||
|
||||
EXPECT_NO_THROW(req.addQueryParameter("query", "value"));
|
||||
EXPECT_NO_THROW(req.AddQueryParameter("query", "value"));
|
||||
EXPECT_PRED2(
|
||||
[](std::string a, std::string b) { return a == b; },
|
||||
req.getEncodedUrl(),
|
||||
req.GetEncodedUrl(),
|
||||
url + "?query=value");
|
||||
|
||||
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_NO_THROW(req_with_query.AddQueryParameter("query", "value"));
|
||||
EXPECT_PRED2(
|
||||
[](std::string a, std::string b) { return a == b; },
|
||||
req_with_query.getEncodedUrl(),
|
||||
req_with_query.GetEncodedUrl(),
|
||||
url_with_query);
|
||||
|
||||
// retry query params testing
|
||||
req.startRetry();
|
||||
req.StartRetry();
|
||||
// same query parameter should override previous
|
||||
EXPECT_NO_THROW(req.addQueryParameter("query", "retryValue"));
|
||||
EXPECT_NO_THROW(req.AddQueryParameter("query", "retryValue"));
|
||||
|
||||
EXPECT_PRED2(
|
||||
[](std::string a, std::string b) { return a == b; },
|
||||
req.getEncodedUrl(),
|
||||
req.GetEncodedUrl(),
|
||||
url + "?query=retryValue");
|
||||
}
|
||||
|
||||
|
@ -127,31 +94,30 @@ TEST(Http_Request, add_path)
|
|||
std::string url = "http://test.com";
|
||||
Http::Request req(httpMethod, url);
|
||||
|
||||
EXPECT_NO_THROW(req.addPath("path"));
|
||||
EXPECT_NO_THROW(req.AddPath("path"));
|
||||
EXPECT_PRED2(
|
||||
[](std::string a, std::string b) { return a == b; }, req.getEncodedUrl(), url + "/path");
|
||||
[](std::string a, std::string b) { return a == b; }, req.GetEncodedUrl(), url + "/path");
|
||||
|
||||
EXPECT_NO_THROW(req.addQueryParameter("query", "value"));
|
||||
EXPECT_NO_THROW(req.AddQueryParameter("query", "value"));
|
||||
EXPECT_PRED2(
|
||||
[](std::string a, std::string b) { return a == b; },
|
||||
req.getEncodedUrl(),
|
||||
req.GetEncodedUrl(),
|
||||
url + "/path?query=value");
|
||||
|
||||
EXPECT_NO_THROW(req.addPath("path2"));
|
||||
EXPECT_NO_THROW(req.AddPath("path2"));
|
||||
EXPECT_PRED2(
|
||||
[](std::string a, std::string b) { return a == b; },
|
||||
req.getEncodedUrl(),
|
||||
req.GetEncodedUrl(),
|
||||
url + "/path/path2?query=value");
|
||||
|
||||
EXPECT_NO_THROW(req.addPath("path3"));
|
||||
EXPECT_NO_THROW(req.AddPath("path3"));
|
||||
EXPECT_PRED2(
|
||||
[](std::string a, std::string b) { return a == b; },
|
||||
req.getEncodedUrl(),
|
||||
req.GetEncodedUrl(),
|
||||
url + "/path/path2/path3?query=value");
|
||||
}
|
||||
|
||||
class Azure::Core::Credentials::Details::CredentialTest : public ClientSecretCredential
|
||||
{
|
||||
class Azure::Core::Credentials::Details::CredentialTest : public ClientSecretCredential {
|
||||
public:
|
||||
CredentialTest(
|
||||
std::string const& tenantId,
|
||||
|
@ -300,7 +266,6 @@ TEST(Credential, ClientSecretCredential)
|
|||
EXPECT_NE(scopesPtr, scopesCopyPtr);
|
||||
EXPECT_EQ(scopes, scopesCopy);
|
||||
}
|
||||
|
||||
|
||||
Credentials::Credential::Internal::SetScopes(clientSecretCredential, scopesCopy);
|
||||
|
||||
|
@ -339,7 +304,6 @@ TEST(Credential, ClientSecretCredential)
|
|||
Credentials::Credential::Internal::SetScopes(
|
||||
clientSecretCredential, std::string(anotherScopes));
|
||||
|
||||
|
||||
EXPECT_EQ(clientSecretCredential.GetTenantId(), tenantId);
|
||||
EXPECT_EQ(clientSecretCredential.GetClientId(), clientId);
|
||||
EXPECT_EQ(clientSecretCredential.GetClientSecret(), clientSecret);
|
||||
|
|
|
@ -1,41 +1,86 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Copyright (c) `Microsoft Corporation. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <curl/curl.h>
|
||||
#include <http/http.hpp>
|
||||
#include <memory>
|
||||
|
||||
class CurlClient {
|
||||
private:
|
||||
CurlClient(const CurlClient&) = delete;
|
||||
CurlClient& operator=(const CurlClient&) = delete;
|
||||
|
||||
Azure::Core::Http::Request& m_request;
|
||||
CURL* m_p_curl;
|
||||
// for every client instance, create a default response
|
||||
std::unique_ptr<Azure::Core::Http::Response> m_response;
|
||||
bool m_firstHeader;
|
||||
|
||||
CURL* m_pCurl;
|
||||
|
||||
static size_t WriteHeadersCallBack(void* contents, size_t size, size_t nmemb, void* userp);
|
||||
static size_t WriteBodyCallBack(void* contents, size_t size, size_t nmemb, void* userp);
|
||||
|
||||
// setHeaders()
|
||||
CURLcode setUrl()
|
||||
CURLcode SetUrl()
|
||||
{
|
||||
return curl_easy_setopt(m_p_curl, CURLOPT_URL, this->m_request.getEncodedUrl().c_str());
|
||||
return curl_easy_setopt(m_pCurl, CURLOPT_URL, this->m_request.GetEncodedUrl().c_str());
|
||||
}
|
||||
CURLcode perform()
|
||||
|
||||
CURLcode SetHeaders()
|
||||
{
|
||||
auto settingUp = setUrl();
|
||||
auto headers = this->m_request.GetHeaders();
|
||||
if (headers.size() == 0)
|
||||
{
|
||||
return CURLE_OK;
|
||||
}
|
||||
|
||||
// creates a slist for bulding curl headers
|
||||
struct curl_slist* headerList = NULL;
|
||||
|
||||
// insert headers
|
||||
for (auto header : headers)
|
||||
{
|
||||
// TODO: check result is not null or trow
|
||||
headerList = curl_slist_append(headerList, (header.first + ":" + header.second).c_str());
|
||||
}
|
||||
|
||||
// set all headers from slist
|
||||
return curl_easy_setopt(m_pCurl, CURLOPT_HTTPHEADER, headerList);
|
||||
}
|
||||
|
||||
CURLcode SetWriteResponse()
|
||||
{
|
||||
auto settingUp = curl_easy_setopt(m_pCurl, CURLOPT_HEADERFUNCTION, WriteHeadersCallBack);
|
||||
if (settingUp != CURLE_OK)
|
||||
{
|
||||
return settingUp;
|
||||
}
|
||||
return curl_easy_perform(m_p_curl);
|
||||
settingUp = curl_easy_setopt(m_pCurl, CURLOPT_HEADERDATA, (void*)this);
|
||||
if (settingUp != CURLE_OK)
|
||||
{
|
||||
return settingUp;
|
||||
}
|
||||
// TODO: set up cache size. user should be able to set it up
|
||||
settingUp = curl_easy_setopt(m_pCurl, CURLOPT_WRITEFUNCTION, WriteBodyCallBack);
|
||||
if (settingUp != CURLE_OK)
|
||||
{
|
||||
return settingUp;
|
||||
}
|
||||
|
||||
return curl_easy_setopt(m_pCurl, CURLOPT_WRITEDATA, (void*)this);
|
||||
}
|
||||
|
||||
CURLcode Perform();
|
||||
|
||||
public:
|
||||
CurlClient(Azure::Core::Http::Request& request) : m_request(request)
|
||||
{
|
||||
m_p_curl = curl_easy_init();
|
||||
m_pCurl = curl_easy_init();
|
||||
}
|
||||
// client curl struct on destruct
|
||||
~CurlClient()
|
||||
{
|
||||
curl_easy_cleanup(m_p_curl);
|
||||
}
|
||||
~CurlClient() { curl_easy_cleanup(m_pCurl); }
|
||||
|
||||
Azure::Core::Http::Response send();
|
||||
std::unique_ptr<Azure::Core::Http::Response> Send();
|
||||
};
|
||||
|
|
|
@ -7,9 +7,9 @@
|
|||
using namespace Azure::Core::Http;
|
||||
|
||||
// implement send method
|
||||
Response Client::send(Request& request)
|
||||
std::unique_ptr<Response> Client::Send(Request& request)
|
||||
{
|
||||
CurlClient client(request);
|
||||
// return request response
|
||||
return client.send();
|
||||
return client.Send();
|
||||
}
|
||||
|
|
|
@ -5,13 +5,14 @@
|
|||
#include <http/http.hpp>
|
||||
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
|
||||
using namespace Azure::Core::Http;
|
||||
using namespace std;
|
||||
|
||||
Response CurlClient::send()
|
||||
std::unique_ptr<Response> CurlClient::Send()
|
||||
{
|
||||
auto performing = perform();
|
||||
auto performing = Perform();
|
||||
|
||||
if (performing != CURLE_OK)
|
||||
{
|
||||
|
@ -32,5 +33,123 @@ Response CurlClient::send()
|
|||
}
|
||||
}
|
||||
|
||||
return Response(200, "OK\n");
|
||||
return move(this->m_response);
|
||||
}
|
||||
|
||||
CURLcode CurlClient::Perform()
|
||||
{
|
||||
m_firstHeader = true;
|
||||
|
||||
auto settingUp = SetUrl();
|
||||
if (settingUp != CURLE_OK)
|
||||
{
|
||||
return settingUp;
|
||||
}
|
||||
|
||||
settingUp = SetHeaders();
|
||||
if (settingUp != CURLE_OK)
|
||||
{
|
||||
return settingUp;
|
||||
}
|
||||
|
||||
settingUp = SetWriteResponse();
|
||||
if (settingUp != CURLE_OK)
|
||||
{
|
||||
return settingUp;
|
||||
}
|
||||
|
||||
return curl_easy_perform(m_pCurl);
|
||||
}
|
||||
|
||||
static std::unique_ptr<Response> ParseAndSetFirstHeader(std::string const& header)
|
||||
{
|
||||
// set response code, http version and reason phrase (i.e. HTTP/1.1 200 OK)
|
||||
auto start = header.begin() + 5; // HTTP = 4, / = 1, moving to 5th place for version
|
||||
auto end = std::find(start, header.end(), '.');
|
||||
auto majorVersion = std::stoi(std::string(start, end));
|
||||
|
||||
start = end + 1; // start of minor version
|
||||
end = std::find(start, header.end(), ' ');
|
||||
auto minorVersion = std::stoi(std::string(start, end));
|
||||
|
||||
start = end + 1; // start of status code
|
||||
end = std::find(start, header.end(), ' ');
|
||||
auto statusCode = std::stoi(std::string(start, end));
|
||||
|
||||
start = end + 1; // start of reason phrase
|
||||
auto reasonPhrase = std::string(start, header.end() - 2); // remove \r and \n from the end
|
||||
|
||||
// allocate the instance of response to heap with shared ptr
|
||||
// So this memory gets delegated outside Curl Transport as a shared ptr so memory will be
|
||||
// eventually released
|
||||
return std::make_unique<Response>(
|
||||
majorVersion, minorVersion, HttpStatusCode(statusCode), reasonPhrase);
|
||||
}
|
||||
|
||||
static void ParseHeader(std::string const& header, std::unique_ptr<Response>& response)
|
||||
{
|
||||
// get name and value from header
|
||||
auto start = header.begin();
|
||||
auto end = std::find(start, header.end(), ':');
|
||||
|
||||
if (end == header.end())
|
||||
{
|
||||
return; // not a valid header or end of headers symbol reached
|
||||
}
|
||||
|
||||
auto headerName = std::string(start, end);
|
||||
start = end + 1; // start value
|
||||
|
||||
auto headerValue = std::string(start, header.end() - 2); // remove \r and \n from the end
|
||||
|
||||
response->AddHeader(headerName, headerValue);
|
||||
}
|
||||
|
||||
// Callback function for curl. This is called for every header that curl get from network
|
||||
size_t CurlClient::WriteHeadersCallBack(void* contents, size_t size, size_t nmemb, void* userp)
|
||||
{
|
||||
// No need to check for overflow, Curl already allocated this size internally for contents
|
||||
size_t const expected_size = size * nmemb;
|
||||
|
||||
// cast client
|
||||
CurlClient* client = static_cast<CurlClient*>(userp);
|
||||
// convert response to standard string
|
||||
std::string const& response = std::string((char*)contents, expected_size);
|
||||
|
||||
if (client->m_firstHeader)
|
||||
{
|
||||
// first header is expected to be the status code, version and reasonPhrase
|
||||
client->m_response = ParseAndSetFirstHeader(response);
|
||||
client->m_firstHeader = false;
|
||||
return expected_size;
|
||||
}
|
||||
|
||||
if (client->m_response != nullptr) // only if a response has been created
|
||||
{
|
||||
// parse all next headers and add them
|
||||
ParseHeader(response, client->m_response);
|
||||
}
|
||||
|
||||
// This callback needs to return the response size or curl will consider it as it failed
|
||||
return expected_size;
|
||||
}
|
||||
|
||||
// callback function for libcurl. It would be called as many times as need to ready a body from
|
||||
// network
|
||||
size_t CurlClient::WriteBodyCallBack(void* contents, size_t size, size_t nmemb, void* userp)
|
||||
{
|
||||
// No need to check for overflow, Curl already allocated this size internally for contents
|
||||
size_t const expected_size = size * nmemb;
|
||||
|
||||
// cast client
|
||||
CurlClient* client = static_cast<CurlClient*>(userp);
|
||||
|
||||
if (client->m_response != nullptr) // only if a response has been created
|
||||
{
|
||||
// TODO: check if response is to be written to buffer or to Stream
|
||||
client->m_response->AppendBody((uint8_t*)contents, expected_size);
|
||||
}
|
||||
|
||||
// This callback needs to return the response size or curl will consider it as it failed
|
||||
return expected_size;
|
||||
}
|
||||
|
|
|
@ -2,11 +2,12 @@
|
|||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include <http/http.hpp>
|
||||
#include <memory>
|
||||
|
||||
using namespace Azure::Core::Http;
|
||||
|
||||
// implement send method
|
||||
Response Client::send(Request& request)
|
||||
std::unique_ptr<Response> Client::Send(Request& request)
|
||||
{
|
||||
(void)request;
|
||||
throw;
|
||||
|
|
|
@ -7,8 +7,8 @@
|
|||
*/
|
||||
|
||||
#include <http/http.hpp>
|
||||
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
|
||||
using namespace Azure::Core;
|
||||
using namespace std;
|
||||
|
@ -19,11 +19,32 @@ int main()
|
|||
cout << "testing curl from transport" << endl << "Host: " << host << endl;
|
||||
|
||||
auto request = Http::Request(Http::HttpMethod::Get, host);
|
||||
request.AddHeader("one", "header");
|
||||
request.AddHeader("other", "header2");
|
||||
request.AddHeader("header", "value");
|
||||
|
||||
try
|
||||
{
|
||||
auto response = Http::Client::send(request);
|
||||
cout << response.getReasonPhrase();
|
||||
std::shared_ptr<Http::Response> response = Http::Client::Send(request);
|
||||
|
||||
if (response == nullptr)
|
||||
{
|
||||
cout << "Error. Response returned as null";
|
||||
return 0;
|
||||
}
|
||||
|
||||
cout << static_cast<typename std::underlying_type<Http::HttpStatusCode>::type>(
|
||||
response->GetStatusCode())
|
||||
<< endl;
|
||||
cout << response->GetReasonPhrase() << endl;
|
||||
cout << "headers:" << endl;
|
||||
for (auto header : response->GetHeaders())
|
||||
{
|
||||
cout << header.first << " : " << header.second << endl;
|
||||
}
|
||||
cout << "Body (buffer):" << endl;
|
||||
auto bodyVector = response->GetBodyBuffer();
|
||||
cout << std::string(bodyVector.begin(), bodyVector.end());
|
||||
}
|
||||
catch (Http::CouldNotResolveHostException& e)
|
||||
{
|
||||
|
|
Загрузка…
Ссылка в новой задаче