From c19f9e2b050702492686477c6f8905a76011242b Mon Sep 17 00:00:00 2001 From: Victor Vazquez Date: Wed, 20 May 2020 14:48:26 -0700 Subject: [PATCH] Adding writing response from curl to buffer (#64) * write headers and response code to Response --- sdk/core/azure-core/inc/http/http.hpp | 197 ++++++++++++------ sdk/core/azure-core/src/http/http.cpp | 1 - sdk/core/azure-core/src/http/request.cpp | 37 +--- sdk/core/azure-core/src/http/response.cpp | 66 ++++-- sdk/core/azure-core/test/main.cpp | 90 +++----- .../http_client/curl/inc/curl_client.hpp | 71 +++++-- .../curl/src/azure_transport_curl.cpp | 4 +- .../http_client/curl/src/curl_client.cpp | 125 ++++++++++- .../nohttp/src/azure_transport_nohttp.cpp | 3 +- .../curl/src/azure_core_with_curl.cpp | 27 ++- 10 files changed, 425 insertions(+), 196 deletions(-) diff --git a/sdk/core/azure-core/inc/http/http.hpp b/sdk/core/azure-core/inc/http/http.hpp index 823904d4f..069426508 100644 --- a/sdk/core/azure-core/inc/http/http.hpp +++ b/sdk/core/azure-core/inc/http/http.hpp @@ -4,14 +4,12 @@ #pragma once #include -#include +#include #include -#include +#include #include #include -#include - 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 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 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 mergeMaps( + static std::map MergeMaps( std::map left, std::map 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(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 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()) { } - Request(HttpMethod httpMethod, std::string const& url, BodyBuffer* bodyBuffer) + Request(HttpMethod httpMethod, std::string const& url, std::vector 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()) { } // 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 getHeaders(); - BodyStream* getBodyStream(); - BodyBuffer* getBodyBuffer(); + HttpMethod GetMethod(); + std::string GetEncodedUrl(); // should return encoded url + std::map GetHeaders(); + BodyStream* GetBodyStream(); + std::vector 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 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 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 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(), + 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 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 const& GetHeaders(); + std::vector& 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 Send(Request& request); }; - }}} // namespace Azure::Core::Http diff --git a/sdk/core/azure-core/src/http/http.cpp b/sdk/core/azure-core/src/http/http.cpp index 12dbb11aa..a6c9abf57 100644 --- a/sdk/core/azure-core/src/http/http.cpp +++ b/sdk/core/azure-core/src/http/http.cpp @@ -6,4 +6,3 @@ using namespace Azure::Core::Http; BodyStream* BodyStream::null = nullptr; -BodyBuffer* BodyBuffer::null = nullptr; diff --git a/sdk/core/azure-core/src/http/request.cpp b/sdk/core/azure-core/src/http/request.cpp index 4121e4f26..a4cac077d 100644 --- a/sdk/core/azure-core/src/http/request.cpp +++ b/sdk/core/azure-core/src/http/request.cpp @@ -1,20 +1,16 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // SPDX-License-Identifier: MIT +#include #include #include #include -#include - 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 Request::getHeaders() +std::map 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; } diff --git a/sdk/core/azure-core/src/http/response.cpp b/sdk/core/azure-core/src/http/response.cpp index df060f0f7..93d00379b 100644 --- a/sdk/core/azure-core/src/http/response.cpp +++ b/sdk/core/azure-core/src/http/response.cpp @@ -1,35 +1,57 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // SPDX-License-Identifier: MIT +#include +#include #include #include #include -#include - 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 const& Response::GetHeaders() { return this->m_headers; } + +std::vector& 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(name, value)); } -std::string const& Response::getReasonPhrase() +void Response::AppendBody(uint8_t* ptr, uint64_t size) { - return m_reasonPhrase; -} - -std::map 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); +} \ No newline at end of file diff --git a/sdk/core/azure-core/test/main.cpp b/sdk/core/azure-core/test/main.cpp index cabda9ccb..131e588d9 100644 --- a/sdk/core/azure-core/test/main.cpp +++ b/sdk/core/azure-core/test/main.cpp @@ -1,11 +1,9 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // SPDX-License-Identifier: MIT +#include "gtest/gtest.h" #include #include - -#include "gtest/gtest.h" - #include #include @@ -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); diff --git a/sdk/platform/http_client/curl/inc/curl_client.hpp b/sdk/platform/http_client/curl/inc/curl_client.hpp index eff90c07e..e3c3e1cb2 100644 --- a/sdk/platform/http_client/curl/inc/curl_client.hpp +++ b/sdk/platform/http_client/curl/inc/curl_client.hpp @@ -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 #include +#include 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 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 Send(); }; diff --git a/sdk/platform/http_client/curl/src/azure_transport_curl.cpp b/sdk/platform/http_client/curl/src/azure_transport_curl.cpp index 43eb46daa..93da784cf 100644 --- a/sdk/platform/http_client/curl/src/azure_transport_curl.cpp +++ b/sdk/platform/http_client/curl/src/azure_transport_curl.cpp @@ -7,9 +7,9 @@ using namespace Azure::Core::Http; // implement send method -Response Client::send(Request& request) +std::unique_ptr Client::Send(Request& request) { CurlClient client(request); // return request response - return client.send(); + return client.Send(); } diff --git a/sdk/platform/http_client/curl/src/curl_client.cpp b/sdk/platform/http_client/curl/src/curl_client.cpp index ebaa9e80d..8972d8475 100644 --- a/sdk/platform/http_client/curl/src/curl_client.cpp +++ b/sdk/platform/http_client/curl/src/curl_client.cpp @@ -5,13 +5,14 @@ #include #include +#include using namespace Azure::Core::Http; using namespace std; -Response CurlClient::send() +std::unique_ptr 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 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( + majorVersion, minorVersion, HttpStatusCode(statusCode), reasonPhrase); +} + +static void ParseHeader(std::string const& header, std::unique_ptr& 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(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(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; } diff --git a/sdk/platform/http_client/nohttp/src/azure_transport_nohttp.cpp b/sdk/platform/http_client/nohttp/src/azure_transport_nohttp.cpp index 3155d1d73..e04372a48 100644 --- a/sdk/platform/http_client/nohttp/src/azure_transport_nohttp.cpp +++ b/sdk/platform/http_client/nohttp/src/azure_transport_nohttp.cpp @@ -2,11 +2,12 @@ // SPDX-License-Identifier: MIT #include +#include using namespace Azure::Core::Http; // implement send method -Response Client::send(Request& request) +std::unique_ptr Client::Send(Request& request) { (void)request; throw; diff --git a/sdk/samples/http_client/curl/src/azure_core_with_curl.cpp b/sdk/samples/http_client/curl/src/azure_core_with_curl.cpp index c9fcaef6c..ba6e950c9 100644 --- a/sdk/samples/http_client/curl/src/azure_core_with_curl.cpp +++ b/sdk/samples/http_client/curl/src/azure_core_with_curl.cpp @@ -7,8 +7,8 @@ */ #include - #include +#include 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 response = Http::Client::Send(request); + + if (response == nullptr) + { + cout << "Error. Response returned as null"; + return 0; + } + + cout << static_cast::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) {