Adding writing response from curl to buffer (#64)

* write headers and response code to Response
This commit is contained in:
Victor Vazquez 2020-05-20 14:48:26 -07:00 коммит произвёл GitHub
Родитель d3360abf2b
Коммит c19f9e2b05
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
10 изменённых файлов: 425 добавлений и 196 удалений

Просмотреть файл

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