Victor/http/add streams (#183)

* Adding body Stream for HTTP
* Adding libcurl session with custom send and receive
This commit is contained in:
Victor Vazquez 2020-06-18 21:43:00 -07:00 committed by GitHub
parent 5758bedc5c
commit 90e57362bf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 1756 additions and 503 deletions

View File

@ -1,16 +1,28 @@
{
"configurations": [
{
"name": "x64-Debug",
"generator": "Ninja",
"configurationType": "Debug",
"inheritEnvironments": [ "msvc_x64_x64" ],
"buildRoot": "${projectDir}\\out\\build\\${name}",
"installRoot": "${projectDir}\\out\\install\\${name}",
"cmakeCommandArgs": "-DINSTALL_GTEST=OFF -DBUILD_TESTING=OFF -DBUILD_CURL_TRANSPORT=ON",
"buildCommandArgs": "-v",
"ctestCommandArgs": "",
"variables": []
}
]
}
"configurations": [
{
"name": "x64-Debug",
"generator": "Ninja",
"configurationType": "Debug",
"inheritEnvironments": [ "msvc_x64_x64" ],
"buildRoot": "${projectDir}\\out\\build\\${name}",
"installRoot": "${projectDir}\\out\\install\\${name}",
"cmakeCommandArgs": "-DINSTALL_GTEST=OFF -DBUILD_TESTING=OFF -DBUILD_CURL_TRANSPORT=ON",
"buildCommandArgs": "-v",
"ctestCommandArgs": "",
"variables": []
},
{
"name": "x86-Debug",
"generator": "Ninja",
"configurationType": "Debug",
"buildRoot": "${projectDir}\\out\\build\\${name}",
"installRoot": "${projectDir}\\out\\install\\${name}",
"cmakeCommandArgs": "",
"buildCommandArgs": "",
"ctestCommandArgs": "",
"inheritEnvironments": [ "msvc_x86" ],
"variables": []
}
]
}

View File

@ -8,9 +8,10 @@ project(${TARGET_NAME} LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD_REQUIRED True)
find_package(CURL CONFIG)
set(CURL_MIN_REQUIRED_VERSION 7.4)
find_package(CURL ${CURL_MIN_REQUIRED_VERSION} CONFIG)
if(NOT CURL_FOUND)
find_package(CURL REQUIRED)
find_package(CURL ${CURL_MIN_REQUIRED_VERSION} REQUIRED)
endif()
add_library (
@ -21,7 +22,9 @@ add_library (
src/http/policy.cpp
src/http/request.cpp
src/http/response.cpp
src/http/curl/curl_transport.cpp
src/http/stream.cpp
src/http/url.cpp
src/http/curl/curl.cpp
src/http/winhttp/win_http_transport.cpp
)

View File

@ -14,77 +14,459 @@
namespace Azure { namespace Core { namespace Http {
class CurlTransport : public HttpTransport {
constexpr auto UploadSstreamPageSize = 1024 * 64;
constexpr auto LibcurlReaderSize = 100;
/**
* @brief Statefull component that controls sending an HTTP Request with libcurl thru the wire and
* parsing and building an HTTP Response.
* This session supports the classic libcurl easy interface to send and receive bytes from network
* using callbacks.
* This session also supports working with the custom HTTP protocol option from libcurl to
* manually upload and download bytes using a network socket. This implementation is used when
* working with streams so customers can lazily pull data from netwok using an stream abstraction.
*
* @remarks This component is expected to be used by an HTTP Transporter to ensure that
* transporter to be re usuable in multiple pipelines while every call to network is unique.
*/
class CurlSession {
private:
CurlTransport(const CurlTransport&) = delete;
CurlTransport& operator=(const CurlTransport&) = delete;
/**
* @brief Enum used by ResponseBufferParser to control the parsing internal state while building
* the HTTP Response
*
*/
enum class ResponseParserState
{
StatusLine,
Headers,
EndOfHeaders,
};
// for every client instance, create a default response
std::unique_ptr<Azure::Core::Http::Response> m_response;
bool m_firstHeader;
/**
* @brief stateful component used to read and parse a buffer to construct a valid HTTP Response.
*
* It uses an internal string as buffers to accumulate a response token (version, code, header,
* etc) until the next delimiter is found. Then it uses this string to keep building the HTTP
* Response.
*
* @remark Only status line and headers are parsed and built. Body is ignored by this component.
* A libcurl session will use this component to build and return the HTTP Response with a body
* stream to the pipeline.
*/
class ResponseBufferParser {
private:
/**
* @brief Controls what the parser is expecting during the reading process
*
*/
ResponseParserState state;
/**
* @brief Unique prt to a response. Parser will create an Initial-valid HTTP Response and then
* it will append headers to it. This response is moved to a different owner once parsing is
* completed.
*
*/
std::unique_ptr<Response> m_response;
/**
* @brief Indicates if parser has found the end of the headers and there is nothing left for
* the HTTP Response.
*
*/
bool m_parseCompleted;
/**
* @brief This buffer is used when the parsed buffer doesn't contain a completed token. The
* content from the buffer will be appended to this buffer. Once that a delimiter is found,
* the token for the HTTP Response is taken from this internal sting if it contains data.
*
* @remark This buffer allows a libcurl session to use any size of buffer to read from a
* socket while constructing an initial valid HTTP Response. No matter if the response from
* wire contains hundreds of headers, we can use only one fixed size buffer to parse it all.
*
*/
std::string m_internalBuffer;
/**
* @brief This method is invoked by the Parsing process if the internal state is set to status
* code. Function will get the status-line expected tokens until finding the end of status
* line delimiter.
*
* @remark When the end of status line delimiter is found, this method will create the HTTP
* Response. The HTTP Response is constructed by default with body type as Stream.
*
* @param buffer Points to a memory address with all or some part of a HTTP status line.
* @param bufferSize Indicates the size of the buffer.
* @return Returns the index of the last parsed position from buffer.
*/
size_t BuildStatusCode(uint8_t const* const buffer, size_t const bufferSize);
/**
* @brief This method is invoked by the Parsing process if the internal state is set to
* headers. Function will keep adding headers to the HTTP Response created before while
* parsing an status line.
*
* @param buffer Points to a memory address with all or some part of a HTTP header.
* @param bufferSize Indicates the size of the buffer.
* @return Returns the index of the last parsed position from buffer. When the returned value
* is smaller than the body size, means there is part of the body response in the buffer.
*/
size_t BuildHeader(uint8_t const* const buffer, size_t const bufferSize);
public:
/**
* @brief Construct a new Response Buffer Parser object.
* Set the initial state and parsing completion.
*
*/
ResponseBufferParser()
{
state = ResponseParserState::StatusLine;
this->m_parseCompleted = false;
}
// Parse contents of buffer to construct HttpResponse. Returns the index of the last parsed
// possition. Return bufferSize when all buffer was used to parse
/**
* @brief Parses the content of a buffer to constuct a valid HTTP Response. This method is
* expected to be called over and over until it returns 0, indicating there is nothing more to
* parse to build the HTTP Response.
*
* @param buffer points to a memory area that contains, all or some part of an HTTP response.
* @param bufferSize Indicates the size of the buffer.
* @return Returns the index of the last parsed position. Returning a 0 means nothing was
* parsed and it is likely that the HTTP Response is completed. Returning the same value as
* the buffer size means all buffer was parsed and the HTTP might be completed or not.
* Returning a value smaller than the buffer size will likely indicate that the HTTP Response
* is completed and that the rest of the buffer contains part of the response body.
*/
size_t Parse(uint8_t const* const buffer, size_t const bufferSize);
/**
* @brief Indicates when the parser has completed parsing and building the HTTP Response.
*
* @return true if parsing is completed. Otherwise false.
*/
bool IsParseCompleted() const { return this->m_parseCompleted; }
/**
* @brief Moves the internal response to a different owner.
*
* @return Will move the response only if parsing is completed and if the HTTP Response was
* not moved before.
*/
std::unique_ptr<Response> GetResponse()
{
if (this->m_parseCompleted && this->m_response != nullptr)
{
return std::move(this->m_response);
}
return nullptr; // parse is not completed or response has been moved already.
}
};
/**
* @brief libcurl handle to be used in the session.
*
*/
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);
/**
* @brief libcurl socket abstraction used when working with streams.
*
*/
curl_socket_t m_curlSocket;
// setHeaders()
CURLcode SetUrl(Request& request)
{
return curl_easy_setopt(m_pCurl, CURLOPT_URL, request.GetEncodedUrl().c_str());
}
/**
* @brief unique ptr for the HTTP Response. The session is responsable for creating the response
* once that an HTTP status line is received.
*
*/
std::unique_ptr<Response> m_response;
CURLcode SetHeaders(Request& request)
{
auto headers = request.GetHeaders();
if (headers.size() == 0)
{
return CURLE_OK;
}
/**
* @brief The HTTP Request for to be used by the session.
*
*/
Request& m_request;
// creates a slist for bulding curl headers
struct curl_slist* headerList = NULL;
/**
* @brief Controls the progress of a body buffer upload when using libcurl callbacks. Woks like
* an offset to move the pointer to read the body from the HTTP Request on each callback.
*
*/
size_t uploadedBytes;
// 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());
}
/**
* @brief Control field that gets true as soon as there is no more data to read from network. A
* network socket will return 0 once we got the entire reponse.
*
*/
bool m_rawResponseEOF;
// set all headers from slist
return curl_easy_setopt(m_pCurl, CURLOPT_HTTPHEADER, headerList);
}
/**
* @brief Control field to handle the case when part of HTTP response body was copied to the
* inner buffer. When a libcurl stream tries to read part of the body, this field will help to
* decide how much data to take from the inner buffer before pulling more data from network.
*
*/
size_t m_bodyStartInBuffer;
CURLcode SetWriteResponse()
{
auto settingUp = curl_easy_setopt(m_pCurl, CURLOPT_HEADERFUNCTION, WriteHeadersCallBack);
if (settingUp != CURLE_OK)
{
return settingUp;
}
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;
}
/**
* @brief This is a copy of the value of an HTTP response header `content-length`. The value is
* received as string and parsed to size_t. This field avoid parsing the string header everytime
* from HTTP Response.
*
* @remark This value is also used to avoid trying to read more data from network than what we
* are expecting to.
*
*/
uint64_t m_contentLength;
return curl_easy_setopt(m_pCurl, CURLOPT_WRITEDATA, (void*)this);
}
/**
* @brief Internal buffer from a session used to read bytes from a socket. This buffer is only
* used while constructing an HTTP Response without adding a body to it. Customers would
* provide their own buffer to copy from socket when reading the HTTP body using streams.
*
*/
uint8_t m_readBuffer[LibcurlReaderSize]; // to work with libcurl custom read.
CURLcode Perform(Context& context, Request& request);
/**
* @brief convenient function that indicates when the HTTP Request will need to upload a payload
* or not.
*
* @return true if the HTTP Request will need to upload bytes to wire.
*
*/
bool isUploadRequest();
/**
* @brief Set up libcurl handle with a value for CURLOPT_URL.
*
* @return returns the libcurl result after setting up.
*/
CURLcode SetUrl();
/**
* @brief Set up libcurl handle with a value for CURLOPT_CONNECT_ONLY.
*
* @remark This configuration is required to enabled the custom upload/download from libcurl
* easy interface.
*
* @return returns the libcurl result after setting up.
*/
CURLcode SetConnectOnly();
/**
* @brief Set up libcurl handle to behave as an specific HTTP Method.
*
* @return returns the libcurl result after setting up.
*/
CURLcode SetMethod();
/**
* @brief Creates a list of libcurl headers and set it up to CURLOPT_HTTPHEADER.
*
* @remark For an HTTP Request that requires uploading bytes to network, this method will set
* the content-length header and will also set libcurl to avoid sending an expect; header to
* only ask server if it is OK to upload the body.
*
* @return returns the libcurl result after setting up.
*/
CURLcode SetHeaders();
/**
* @brief Set up libcurl callback functions for writing and user data. User data ptr for all
* callbacks is set to reference the session object.
*
* @return returns the libcurl result after setting up.
*/
CURLcode SetWriteResponse();
/**
* @brief Set up libcurl callback functions for reading and user data. User data ptr for all
* callbacks is set to reference the session object.
*
* @return returns the libcurl result after setting up.
*/
CURLcode SetReadRequest();
/**
* @brief Function used when working with Streams to manually write from the HTTP Request to the
* wire.
*
* @return CURL_OK when response is sent successfully.
*/
CURLcode HttpRawSend();
/**
* @brief This method will use libcurl socket to write all the bytes from buffer.
*
* @remarks Hardcoded timeout is used in case a socket stop responding.
*
* @param buffer ptr to the data to be sent to wire.
* @param bufferSize size of the buffer to send.
* @return CURL_OK when response is sent successfully.
*/
CURLcode SendBuffer(uint8_t* buffer, size_t bufferSize);
/**
* @brief This function is used after sending an HTTP request to the server to read the HTTP
* Response from wire until the end of headers only.
*
* @return CURL_OK when an HTTP response is created.
*/
CURLcode ReadStatusLineAndHeadersFromRawResponse();
/**
* @brief This function is used when working with streams to pull more data from the wire.
* Function will try to keep pulling data from socket until the buffer is all written or until
* there is no more data to get from the socket.
*
* @param buffer prt to buffer where to copy bytes from socket.
* @param bufferSize size of the buffer and the requested bytes to be pulled from wire.
* @return return the numbers of bytes pulled from socket. It can be less than what it was
* requested.
*/
uint64_t ReadSocketToBuffer(uint8_t* buffer, size_t bufferSize);
public:
CurlTransport();
~CurlTransport();
/**
* @brief Construct a new Curl Session object. Init internal libcurl handler.
*
* @param request reference to an HTTP Request.
*/
CurlSession(Request& request) : m_request(request)
{
this->m_pCurl = curl_easy_init();
this->m_bodyStartInBuffer = 0;
}
/**
* @brief Function will use the HTTP request received in constutor to perform a network call
* based on the HTTP request configuration.
*
* @param context TBD
* @return CURLE_OK when the network call is completed successfully.
*/
CURLcode Perform(Context& context);
/**
* @brief Moved the ownership of the HTTP Response out of the session.
*
* @return the unique ptr to the HTTP Response or null if the HTTP Response is not yet created
* or was moved before.
*/
std::unique_ptr<Azure::Core::Http::Response> GetResponse();
/**
* @brief Helper method for reading with a Stream. Function will figure it out where to get
* bytes from (either the libcurl socket of the internal buffer from session). The offset is
* how stream controls how much it was already read.
*
* @param buffer ptr to a buffer where to write bytes from HTTP Response body.
* @param bufferSize size of the buffer.
* @param offset the number of bytes previously read.
* @return the number of bytes read.
*/
uint64_t ReadWithOffset(uint8_t* buffer, uint64_t bufferSize, uint64_t offset);
};
/**
* @brief Concrete implementation of an HTTP Transport that uses libcurl.
*
*/
class CurlTransport : public HttpTransport {
public:
/**
* @brief Implements interface to send an HTTP Request and produce an HTTP Response
*
* @param context TBD
* @param request an HTTP Request to be send.
* @return unique ptr to an HTTP Response.
*/
std::unique_ptr<Response> Send(Context& context, Request& request) override;
};
/**
* @brief concrete implementation of a body stream to read bytes for the HTTP body using libcurl
* handler.
*/
class CurlBodyStream : public Azure::Core::Http::BodyStream {
private:
/**
* @brief length of the entire HTTP Response body.
*
*/
uint64_t m_length;
/**
* @brief reference to a Curl Session with all the configuration to be used to read from wire.
*
*/
CurlSession* m_curlSession;
/**
* @brief Numbers of bytes already read.
*
*/
uint64_t m_offset;
public:
/**
* @brief Construct a new Curl Body Stream object.
*
* @param length size of the HTTP Response body.
* @param curlSession reference to a libcurl session that contains the libcurl handler to be
* used.
*/
CurlBodyStream(uint64_t length, CurlSession* curlSession)
: m_length(length), m_curlSession(curlSession), m_offset(0)
{
}
~CurlBodyStream()
{
if (this->m_curlSession != nullptr)
{
delete this->m_curlSession; // Session Destructor will cleanup libcurl handle
}
}
/**
* @brief Gets the length of the HTTP Response body.
*
* @return uint64_t
*/
uint64_t Length() const override { return this->m_length; }
/**
* @brief Gets the number of bytes received on count from netwok. Copies the bytes to the
* buffer.
*
* @param buffer ptr to a buffer where to copy bytes from network.
* @param count number of bytes to copy from network into buffer.
* @return the number of read and copied bytes from network to buffer.
*/
uint64_t Read(uint8_t* buffer, uint64_t count) override
{
if (this->m_length == this->m_offset)
{
return 0;
}
// Read bytes from curl into buffer. As max as the length of Stream is allowed
auto readCount = this->m_curlSession->ReadWithOffset(buffer, count, this->m_offset);
this->m_offset += readCount;
return readCount;
}
/**
* @brief clean up heap. Removes the libcurl session and stream from the heap.
*
* @remark calling this method deletes the stream.
*
*/
void Close() override{};
};
}}} // namespace Azure::Core::Http

View File

@ -3,6 +3,8 @@
#pragma once
#include "stream.hpp"
#include <algorithm>
#include <internal/contract.hpp>
#include <map>
@ -14,33 +16,9 @@ namespace Azure { namespace Core { namespace Http {
enum class TransportKind
{
//TODO move this to Factory
Curl,
WinHttp
};
// BodyStream is used to read data to/from a service
class BodyStream {
public:
static BodyStream* null;
// Returns the length of the data; used with the HTTP Content-Length header
virtual uint64_t Length() = 0;
// Resets the stream back to the beginning (for retries)
// Derived classes that send data in an HTTP request MUST override this and implement it
// properly.
virtual void Rewind()
{
throw "Not Implemented"; // TODO: Replace with best practice as defined by guideline
};
// Reads more data; EOF if return < count; throws if error/canceled
virtual uint64_t Read(/*Context& context, */ uint8_t* buffer, uint64_t offset, uint64_t count)
= 0;
// Closes the stream; typically called after all data read or if an error occurs.
virtual void Close() = 0;
// TODO move this to Factory
Curl,
WinHttp
};
enum class HttpStatusCode
@ -150,40 +128,28 @@ namespace Azure { namespace Core { namespace Http {
}
}
class Request {
enum class BodyType
{
Buffer,
Stream,
};
// parses full url into protocol, host, port, path and query.
// Authority is not currently supported.
class URL {
private:
// query needs to be first or at least before url, since url might update it
std::string m_scheme;
std::string m_host;
std::string m_port;
std::string m_path;
std::map<std::string, std::string> m_queryParameters;
HttpMethod _method;
std::string _url;
std::map<std::string, std::string> m_headers;
std::map<std::string, std::string> m_retryHeaders;
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;
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(
std::map<std::string, std::string> left,
std::map<std::string, std::string> const& right)
{
left.insert(right.begin(), right.end());
return left;
}
/**
* Will check if there are any query parameter in url looking for symbol '?'
* If it is found, it will insert query parameters to m_queryParameters internal field
* and remove it from url
*/
const std::string parseUrl(std::string const& url)
const std::string SaveAndRemoveQueryParameter(std::string const& url)
{
const auto firstPosition = std::find(url.begin(), url.end(), '?');
@ -206,7 +172,7 @@ namespace Azure { namespace Core { namespace Http {
// 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>(
this->m_queryParameters.insert(std::pair<std::string, std::string>(
std::string(position, equalChar), std::string(valueStart, nextPosition)));
position = nextPosition;
@ -215,45 +181,87 @@ namespace Azure { namespace Core { namespace Http {
return std::string(url.begin(), firstPosition);
}
Request(
HttpMethod httpMethod,
std::string const& url,
BodyStream* bodyStream,
std::vector<uint8_t> bodyBuffer)
: _method(std::move(httpMethod)), _url(parseUrl(url)), m_bodyStream(bodyStream),
m_bodyBuffer(std::move(bodyBuffer)), m_retryModeEnabled(false)
public:
URL(std::string const& url);
void AppendPath(std::string const& path)
{
// TODO: parse url
this->m_path += "/" + path; // TODO: check if the path already finish with a /
}
std::string ToString() const
{
auto port = this->m_port.size() > 0 ? ":" + this->m_port : "";
return this->m_scheme + "://" + this->m_host + port + this->m_path; // TODO: add query params
}
std::string GetPath() const { return this->m_path; }
std::map<std::string, std::string> GetQueryParameters() const
{
return this->m_queryParameters;
}
void AddQueryParameter(std::string const& name, std::string const& value)
{
this->m_queryParameters.insert(std::pair<std::string, std::string>(name, value));
}
};
class Request {
private:
HttpMethod m_method;
URL m_url;
std::map<std::string, std::string> m_headers;
std::map<std::string, std::string> m_retryHeaders;
std::map<std::string, std::string> m_retryQueryParameters;
// Work only with streams
BodyStream* m_bodyStream;
// 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(
std::map<std::string, std::string> left,
std::map<std::string, std::string> const& right)
{
left.insert(right.begin(), right.end());
return left;
}
std::string GetQueryString() const;
public:
Request(HttpMethod httpMethod, std::string const& url)
: Request(httpMethod, url, BodyStream::null, std::vector<uint8_t>())
{
}
Request(HttpMethod httpMethod, std::string const& url, std::vector<uint8_t> bodyBuffer)
: Request(httpMethod, url, BodyStream::null, std::move(bodyBuffer))
{
}
Request(HttpMethod httpMethod, std::string const& url, BodyStream* bodyStream)
: Request(httpMethod, url, bodyStream, std::vector<uint8_t>())
: m_method(std::move(httpMethod)), m_url(url), m_bodyStream(bodyStream),
m_retryModeEnabled(false)
{
}
// Typically used for GET with no request body.
Request(HttpMethod httpMethod, std::string const& url)
: Request(httpMethod, url, BodyStream::null)
{
}
~Request()
{
if (this->m_bodyStream != BodyStream::null)
{
delete this->m_bodyStream;
}
}
// Methods used to build HTTP request
void AddPath(std::string const& path);
void AppendPath(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() const;
std::string GetEncodedUrl() const; // should return encoded url
std::string GetEncodedUrl() const; // should call URL encode
std::map<std::string, std::string> GetHeaders() const;
BodyStream* GetBodyStream();
std::vector<uint8_t> const& GetBodyBuffer();
std::string GetHTTPMessagePreBody() const;
};
/*
@ -278,60 +286,62 @@ namespace Azure { namespace Core { namespace Http {
class Response {
private:
uint16_t m_majorVersion;
uint16_t m_minorVersion;
int32_t m_majorVersion;
int32_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 (m_bodyBuffer or
// bodyStream)
std::vector<uint8_t> m_bodyBuffer;
Http::BodyStream* m_bodyStream;
BodyStream* m_bodyStream;
Response(
int16_t majorVersion,
uint16_t minorVersion,
int32_t majorVersion,
int32_t minorVersion,
HttpStatusCode statusCode,
std::string const& reasonPhrase,
std::vector<uint8_t> const& bodyBuffer,
BodyStream* const BodyStream)
: m_majorVersion(majorVersion), m_minorVersion(minorVersion), m_statusCode(statusCode),
m_reasonPhrase(reasonPhrase), m_bodyBuffer(bodyBuffer), m_bodyStream(BodyStream)
m_reasonPhrase(reasonPhrase), m_bodyStream(BodyStream)
{
}
public:
Response(
uint16_t majorVersion,
uint16_t minorVersion,
int32_t majorVersion,
int32_t minorVersion,
HttpStatusCode statusCode,
std::string const& reasonPhrase)
: Response(
majorVersion,
minorVersion,
statusCode,
reasonPhrase,
std::vector<uint8_t>(),
Http::BodyStream::null)
: Response(majorVersion, minorVersion, statusCode, reasonPhrase, BodyStream::null)
{
}
~Response()
{
if (this->m_bodyStream != BodyStream::null)
{
delete this->m_bodyStream;
}
}
// Methods used to build HTTP response
void AddHeader(std::string const& name, std::string const& value);
void AppendBody(uint8_t* ptr, uint64_t size);
// 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();
// rfc form header-name: OWS header-value OWS
void AddHeader(std::string const& header);
void SetBodyStream(BodyStream* stream);
// 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; }
int32_t GetMajorVersion() const { return this->m_majorVersion; }
int32_t GetMinorVersion() const { return this->m_minorVersion; }
HttpStatusCode GetStatusCode() const;
std::string const& GetReasonPhrase();
std::map<std::string, std::string> const& GetHeaders();
BodyStream* GetBodyStream() { return this->m_bodyStream; }
// Allocates a buffer in heap and reads and copy stream content into it.
// util for any API that needs to get the content from stream as a buffer
static std::unique_ptr<std::vector<uint8_t>> ConstructBodyBufferFromStream(
BodyStream* const stream);
};
}}} // namespace Azure::Core::Http

View File

@ -0,0 +1,128 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// SPDX-License-Identifier: MIT
#pragma once
#include <algorithm>
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <vector>
namespace Azure { namespace Core { namespace Http {
// BodyStream is used to read data to/from a service
class BodyStream {
public:
static BodyStream* null;
// Returns the length of the data; used with the HTTP Content-Length header
virtual uint64_t Length() const = 0;
// Resets the stream back to the beginning (for retries)
// Derived classes that send data in an HTTP request MUST override this and implement it
// properly.
virtual void Rewind()
{
throw "Not Implemented"; // TODO: Replace with best practice as defined by guideline
};
// Reads more data; throws if error/canceled
// return copied size
virtual uint64_t Read(/*Context& context, */ uint8_t* buffer, uint64_t count) = 0;
// Closes the stream; typically called after all data read or if an error occurs.
virtual void Close() = 0;
// Desstructor. Enables derived classes to call its destructor
virtual ~BodyStream() = 0;
};
class MemoryBodyStream : public BodyStream {
private:
std::vector<uint8_t> m_buffer;
uint64_t m_offset = 0;
public:
MemoryBodyStream(std::vector<uint8_t> buffer) : m_buffer(std::move(buffer)) {}
// cast as vector from ptr and length
MemoryBodyStream(uint8_t* ptr, uint64_t length) : m_buffer(ptr, ptr + length) {}
uint64_t Length() const override { return this->m_buffer.size(); }
uint64_t Read(uint8_t* buffer, uint64_t count) override
{
uint64_t copy_length = std::min(count, (this->m_buffer.size() - m_offset));
// Copy what's left or just the count
std::memcpy(buffer, m_buffer.data() + m_offset, (size_t)copy_length);
// move position
m_offset += copy_length;
return copy_length;
}
void Rewind() override { m_offset = 0; }
void Close() override {}
};
/*
TODO: fix file to work multi-platform
class FileBodyStream : public BodyStream {
private:
FILE* stream;
uint64_t length;
public:
FileBodyStream(FILE* stream)
{
// set internal fields
this->stream = stream;
// calculate size seeking end...
this->length = fseeko64(stream, 0, SEEK_END);
// seek back to beggin
this->Rewind();
}
// Rewind seek back to 0
void Rewind() { rewind(this->stream); }
uint64_t Read( uint8_t* buffer, uint64_t count)
{
// do static cast here?
return (uint64_t)fread(buffer, 1, count, this->stream);
}
// close does nothing opp
void Close() { fclose(this->stream); }
}; */
class LimitBodyStream : public BodyStream {
BodyStream* m_inner;
uint64_t m_length;
uint64_t m_bytesRead = 0;
LimitBodyStream(BodyStream* inner, uint64_t max_length)
: m_inner(inner), m_length(std::min(inner->Length(), max_length))
{
}
uint64_t Length() const override { return this->m_length; }
void Rewind() override
{
this->m_inner->Rewind();
this->m_bytesRead = 0;
}
uint64_t Read(uint8_t* buffer, uint64_t count) override
{
// Read up to count or whatever length is remaining; whichever is less
uint64_t bytesRead
= m_inner->Read(buffer, std::min(count, this->m_length - this->m_bytesRead));
this->m_bytesRead += bytesRead;
return bytesRead;
}
void Close() override { this->m_inner->Close(); }
};
}}} // namespace Azure::Core::Http

View File

@ -0,0 +1,521 @@
#include "http/curl/curl.hpp"
#include "azure.hpp"
#include "http/http.hpp"
#include <string>
using namespace Azure::Core::Http;
std::unique_ptr<Response> CurlTransport::Send(Context& context, Request& request)
{
// Create CurlSession in heap. We will point to it from response's stream to keep it alive
CurlSession* session = new CurlSession(request);
auto performing = session->Perform(context);
if (performing != CURLE_OK)
{
switch (performing)
{
case CURLE_COULDNT_RESOLVE_HOST: {
throw Azure::Core::Http::CouldNotResolveHostException();
}
case CURLE_WRITE_ERROR: {
throw Azure::Core::Http::ErrorWhileWrittingResponse();
}
default: {
throw Azure::Core::Http::TransportException();
}
}
}
return session->GetResponse();
}
CURLcode CurlSession::Perform(Context& context)
{
AZURE_UNREFERENCED_PARAMETER(context);
// Working with Body Buffer. let Libcurl use the classic callback to read/write
auto settingUp = SetUrl();
if (settingUp != CURLE_OK)
{
return settingUp;
}
settingUp = SetConnectOnly();
if (settingUp != CURLE_OK)
{
return settingUp;
}
// stablish connection only (won't send or receive nothing yet)
settingUp = curl_easy_perform(this->m_pCurl);
if (settingUp != CURLE_OK)
{
return settingUp;
}
// Record socket to be used
settingUp = curl_easy_getinfo(this->m_pCurl, CURLINFO_ACTIVESOCKET, &this->m_curlSocket);
if (settingUp != CURLE_OK)
{
return settingUp;
}
// Send request
settingUp = HttpRawSend();
if (settingUp != CURLE_OK)
{
return settingUp;
}
this->m_rawResponseEOF = false; // Control EOF for response;
return ReadStatusLineAndHeadersFromRawResponse();
}
// Creates an HTTP Response with specific bodyType
static std::unique_ptr<Response> CreateHTTPResponse(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
end = std::find(start, header.end(), '\r');
auto reasonPhrase = std::string(start, end); // remove \r
// 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>(
(uint16_t)majorVersion, (uint16_t)minorVersion, HttpStatusCode(statusCode), reasonPhrase);
}
// To wait for a socket to be ready to be read/write
static int WaitForSocketReady(curl_socket_t sockfd, int for_recv, long timeout_ms)
{
struct timeval tv;
fd_set infd, outfd, errfd;
int res;
tv.tv_sec = timeout_ms / 1000;
tv.tv_usec = (timeout_ms % 1000) * 1000;
FD_ZERO(&infd);
FD_ZERO(&outfd);
FD_ZERO(&errfd);
FD_SET(sockfd, &errfd); /* always check for error */
if (for_recv)
{
FD_SET(sockfd, &infd);
}
else
{
FD_SET(sockfd, &outfd);
}
/* select() returns the number of signalled sockets or -1 */
res = select((int)sockfd + 1, &infd, &outfd, &errfd, &tv);
return res;
}
bool CurlSession::isUploadRequest()
{
return this->m_request.GetMethod() == HttpMethod::Put
|| this->m_request.GetMethod() == HttpMethod::Post;
}
CURLcode CurlSession::SetUrl()
{
return curl_easy_setopt(this->m_pCurl, CURLOPT_URL, this->m_request.GetEncodedUrl().c_str());
}
CURLcode CurlSession::SetConnectOnly()
{
return curl_easy_setopt(this->m_pCurl, CURLOPT_CONNECT_ONLY, 1L);
}
// Send buffer thru the wire
CURLcode CurlSession::SendBuffer(uint8_t* buffer, size_t bufferSize)
{
if (bufferSize <= 0)
{
return CURLE_OK;
}
size_t sentBytesTotal = 0;
CURLcode sendResult;
do
{
size_t sentBytesPerRequest;
do
{
sentBytesPerRequest = 0;
auto sendFrom = buffer + sentBytesTotal;
auto remainingBytes = bufferSize - sentBytesTotal;
sendResult = curl_easy_send(this->m_pCurl, sendFrom, remainingBytes, &sentBytesPerRequest);
sentBytesTotal += sentBytesPerRequest;
if (sendResult == CURLE_AGAIN && !WaitForSocketReady(this->m_curlSocket, 0, 60000L))
{
throw;
}
} while (sendResult == CURLE_AGAIN);
if (sendResult != CURLE_OK)
{
return sendResult;
}
} while (sentBytesTotal < bufferSize);
return CURLE_OK;
}
// custom sending to wire an http request
CURLcode CurlSession::HttpRawSend()
{
auto rawRequest = this->m_request.GetHTTPMessagePreBody();
uint64_t rawRequestLen = rawRequest.size();
CURLcode sendResult = SendBuffer((uint8_t*)rawRequest.data(), (size_t)rawRequestLen);
if (this->m_request.GetMethod() == HttpMethod::Get)
{
uint8_t endOfRequest[] = "0";
return SendBuffer(endOfRequest, 1); // need one more byte to end request
}
auto streamBody = this->m_request.GetBodyStream();
// Send body 64k at a time (libcurl default)
// NOTE: if stream is on top a contiguous memory, we can avoid allocating this copying buffer
std::unique_ptr<uint8_t[]> unique_buffer(new uint8_t[UploadSstreamPageSize]);
auto buffer = unique_buffer.get();
while (rawRequestLen > 0)
{
rawRequestLen = streamBody->Read(buffer, UploadSstreamPageSize);
sendResult = SendBuffer(buffer, (size_t)rawRequestLen);
}
return sendResult;
}
// Read status line plus headers to create a response with no body
CURLcode CurlSession::ReadStatusLineAndHeadersFromRawResponse()
{
auto parser = ResponseBufferParser();
// Keep reading until all headers were read
while (!parser.IsParseCompleted())
{
// Try to fill internal buffer from socket.
// If response is smaller than buffer, we will get back the size of the response
auto bufferSize = ReadSocketToBuffer(this->m_readBuffer, LibcurlReaderSize);
// parse from buffer to create response
auto bytesParsed = parser.Parse(this->m_readBuffer, (size_t)bufferSize);
// if end of headers is reach before the end of response, that's where body start
if (bytesParsed < bufferSize)
{
this->m_bodyStartInBuffer = bytesParsed + 1; // Set the start of body (skip \n)
}
}
this->m_response = parser.GetResponse();
// TODO: tolower ContentLength
auto headers = this->m_response->GetHeaders();
auto bodySize = atoi(headers.at("Content-Length").data());
// content-length is used later by session and session won't have access to the response any more
// (unique_ptr), so we save this value
this->m_contentLength = bodySize;
// Move session to live inside the stream from response.
this->m_response->SetBodyStream(new CurlBodyStream(bodySize, this));
return CURLE_OK;
}
uint64_t CurlSession::ReadWithOffset(uint8_t* buffer, uint64_t bufferSize, uint64_t offset)
{
if (bufferSize <= 0)
{
return 0;
}
// calculate where to start reading from inner buffer
auto fixedOffset
= offset == 0 ? offset + 1 : offset; // advance the last '\n' from headers end on first read
auto innerBufferStart = this->m_bodyStartInBuffer + fixedOffset;
// total size from content-length header less any bytes already read
auto remainingBodySize = this->m_contentLength - fixedOffset;
// set ptr for writting
auto writePosition = buffer;
// Set the max to be written as the size of remaining body size
auto bytesToWrite = std::min<>(bufferSize, remainingBodySize);
// If bodyStartInBuffer is set and while innerBufferStart is less than the buffer, it means there
// is still data at innerbuffer for the body that is not yet read
if (this->m_bodyStartInBuffer > 0 && LibcurlReaderSize > innerBufferStart)
{
// Calculate the right size of innerBuffer:
// It can be smaller than the total body, in that case we will take as much as the size of
// buffer
// It can be bugger than the total body, in that case we will take as much as the size of the
// body
auto innerBufferWithBodyContent = LibcurlReaderSize - innerBufferStart;
auto innerbufferSize = remainingBodySize < innerBufferWithBodyContent
? remainingBodySize
: innerBufferWithBodyContent;
// Requested less data than what we have at inner buffer, take it from innerBuffer
if (bufferSize <= innerbufferSize)
{
std::memcpy(writePosition, this->m_readBuffer + innerBufferStart, (size_t)bytesToWrite);
return bytesToWrite;
}
// Requested more data than what we have at innerbuffer. Take all from inner buffer and continue
std::memcpy(writePosition, this->m_readBuffer + innerBufferStart, (size_t)innerbufferSize);
// Return if all body was read and theres not need to read socket
if (innerbufferSize == remainingBodySize)
{
// libcurl handle can be clean now. We won't request more data
curl_easy_cleanup(this->m_pCurl);
return innerbufferSize;
}
// next write will be done after reading from socket, move ptr to where to write and how many
// to write
writePosition += innerbufferSize;
bytesToWrite -= innerbufferSize;
}
// read from socket the remaining requested bytes
auto bytesRead = ReadSocketToBuffer(writePosition, (size_t)bytesToWrite);
if (remainingBodySize - bytesRead == 0)
{
// No more to read from socket
curl_easy_cleanup(this->m_pCurl);
}
return bytesRead;
}
// Read from socket until buffer is full or until socket has no more data
uint64_t CurlSession::ReadSocketToBuffer(uint8_t* buffer, size_t bufferSize)
{
CURLcode readResult;
size_t readBytes = 0;
size_t totalRead = 0;
auto pendingToRead = bufferSize;
while (!this->m_rawResponseEOF && pendingToRead > 0)
{
do // try to read from socket until response is OK
{
readResult = curl_easy_recv(this->m_pCurl, buffer + totalRead, pendingToRead, &readBytes);
// socket not ready. Wait or fail on timeout
if (readResult == CURLE_AGAIN && !WaitForSocketReady(this->m_curlSocket, 1, 60000L))
{
throw;
}
} while (readResult == CURLE_AGAIN); // Keep trying to read until result is not CURLE_AGAIN
// At this point we read, update counters
totalRead += readBytes;
pendingToRead -= readBytes;
if (readBytes == 0)
{
// set socket as nothing more to read
this->m_rawResponseEOF = true;
break;
}
}
return totalRead;
}
std::unique_ptr<Azure::Core::Http::Response> CurlSession::GetResponse()
{
if (this->m_response != nullptr)
{
return std::move(this->m_response);
}
return nullptr;
}
size_t CurlSession::ResponseBufferParser::Parse(
uint8_t const* const buffer,
size_t const bufferSize)
{
if (this->m_parseCompleted)
{
return 0;
}
switch (this->state)
{
case ResponseParserState::StatusLine: {
auto parsedBytes = BuildStatusCode(buffer, bufferSize);
if (parsedBytes < bufferSize) // status code is built and buffer can be still parsed
{
// Can keep parsing, Control have moved to headers
return parsedBytes + Parse(buffer + parsedBytes, bufferSize - parsedBytes);
}
return parsedBytes;
}
case ResponseParserState::Headers: {
auto parsedBytes = BuildHeader(buffer, bufferSize);
if (!this->m_parseCompleted
&& parsedBytes < bufferSize) // status code is built and buffer can be still parsed
{
// Can keep parsing, Control have moved to headers
return parsedBytes + Parse(buffer + parsedBytes, bufferSize - parsedBytes);
}
return parsedBytes;
}
case ResponseParserState::EndOfHeaders:
default: {
return 0;
}
}
}
// Finds delimiter '\r' as the end of the
size_t CurlSession::ResponseBufferParser::BuildStatusCode(
uint8_t const* const buffer,
size_t const bufferSize)
{
if (this->state != ResponseParserState::StatusLine)
{
return 0; // Wrong internal state to call this method.
}
uint8_t endOfStatusLine = '\r';
auto endOfBuffer = buffer + bufferSize;
// Look for the end of status line in buffer
auto indexOfEndOfStatusLine = std::find(buffer, endOfBuffer, endOfStatusLine);
if (indexOfEndOfStatusLine == endOfBuffer)
{
// did not find the delimiter yet, copy to internal buffer
this->m_internalBuffer.append(buffer, endOfBuffer);
return bufferSize; // all buffer read and requesting for more
}
// Delimiter found, check if there is data in the internal buffer
if (this->m_internalBuffer.size() > 0)
{
// If the index is same as buffer it means delimiter is at position 0, meaning that
// internalBuffer containst the status line and we don't need to add anything else
if (indexOfEndOfStatusLine > buffer)
{
// Append and build response minus the delimiter
this->m_internalBuffer.append(buffer, indexOfEndOfStatusLine);
}
this->m_response = CreateHTTPResponse(this->m_internalBuffer);
}
else
{
// Internal Buffer was not required, create response directly from buffer
this->m_response = CreateHTTPResponse(std::string(buffer, indexOfEndOfStatusLine));
}
// update control
this->state = ResponseParserState::Headers;
this->m_internalBuffer.clear();
// Return the index of the next char to read after delimiter
// No need to advance one more char ('\n') (since we might be at the end of the array)
// Parsing Headers will make sure to move one possition
return indexOfEndOfStatusLine + 1 - buffer;
}
// Finds delimiter '\r' as the end of the
size_t CurlSession::ResponseBufferParser::BuildHeader(
uint8_t const* const buffer,
size_t const bufferSize)
{
if (this->state != ResponseParserState::Headers)
{
return 0; // can't run this if state is not Headers.
}
uint8_t delimiter = '\r';
auto start = buffer;
auto endOfBuffer = buffer + bufferSize;
if (bufferSize == 1 && buffer[0] == '\n')
{
// rare case of using buffer of size 1 to read. In this case, if the char is next value after
// headers or previous header, just consider it as read and return
return bufferSize;
}
else if (bufferSize > 1 && this->m_internalBuffer.size() == 0) // only if nothing in buffer,
// advance
{
// move offset one possition. This is because readStatusLine and readHeader will read up to
// '\r' then next delimeter is '\n' and we don't care
start = buffer + 1;
}
// Look for the end of status line in buffer
auto indexOfEndOfStatusLine = std::find(start, endOfBuffer, delimiter);
if (indexOfEndOfStatusLine == start && this->m_internalBuffer.size() == 0)
{
// \r found at the start means the end of headers
this->m_internalBuffer.clear();
this->m_parseCompleted = true;
return 1; // can't return more than the found delimiter. On read remaining we need to also
// remove first char
}
if (indexOfEndOfStatusLine == endOfBuffer)
{
// did not find the delimiter yet, copy to internal buffer
this->m_internalBuffer.append(start, endOfBuffer);
return bufferSize; // all buffer read and requesting for more
}
// Delimiter found, check if there is data in the internal buffer
if (this->m_internalBuffer.size() > 0)
{
// If the index is same as buffer it means delimiter is at position 0, meaning that
// internalBuffer containst the status line and we don't need to add anything else
if (indexOfEndOfStatusLine > buffer)
{
// Append and build response minus the delimiter
this->m_internalBuffer.append(start, indexOfEndOfStatusLine);
}
this->m_response->AddHeader(this->m_internalBuffer);
}
else
{
// Internal Buffer was not required, create response directly from buffer
this->m_response->AddHeader(std::string(start, indexOfEndOfStatusLine));
}
// reuse buffer
this->m_internalBuffer.clear();
// Return the index of the next char to read after delimiter
// No need to advance one more char ('\n') (since we might be at the end of the array)
// Parsing Headers will make sure to move one possition
return indexOfEndOfStatusLine + 1 - buffer;
}

View File

@ -1,185 +0,0 @@
#include "azure.hpp"
#include "http/curl/curl.hpp"
#include "http/http.hpp"
#include <string>
using namespace Azure::Core::Http;
CurlTransport::CurlTransport() : HttpTransport() { m_pCurl = curl_easy_init(); }
CurlTransport::~CurlTransport() { curl_easy_cleanup(m_pCurl); }
std::unique_ptr<Response> CurlTransport::Send(Context& context, Request& request)
{
auto performing = Perform(context, request);
if (performing != CURLE_OK)
{
switch (performing)
{
case CURLE_COULDNT_RESOLVE_HOST: {
throw Azure::Core::Http::CouldNotResolveHostException();
}
case CURLE_WRITE_ERROR: {
throw Azure::Core::Http::ErrorWhileWrittingResponse();
}
default: {
throw Azure::Core::Http::TransportException();
}
}
}
return std::move(m_response);
}
CURLcode CurlTransport::Perform(Context& context, Request& request)
{
AZURE_UNREFERENCED_PARAMETER(context);
m_firstHeader = true;
auto settingUp = SetUrl(request);
if (settingUp != CURLE_OK)
{
return settingUp;
}
if (request.GetMethod() == HttpMethod::Get)
{
settingUp = curl_easy_setopt(m_pCurl, CURLOPT_HTTPGET, 1L);
}
else if (request.GetMethod() == HttpMethod::Put)
{
settingUp = curl_easy_setopt(m_pCurl, CURLOPT_PUT, 1L);
}
else
{
settingUp
= curl_easy_setopt(m_pCurl, CURLOPT_CUSTOMREQUEST, HttpMethodToString(request.GetMethod()).data());
}
if (settingUp != CURLE_OK)
{
return settingUp;
}
if (request.GetMethod() == HttpMethod::Head)
{
settingUp = curl_easy_setopt(m_pCurl, CURLOPT_NOBODY, 1L);
if (settingUp != CURLE_OK)
{
return settingUp;
}
}
settingUp = SetHeaders(request);
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>(
(uint16_t)majorVersion, (uint16_t)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
while (start < header.end() && (*start == ' ' || *start == '\t'))
{
++start;
}
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 CurlTransport::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
CurlTransport* client = static_cast<CurlTransport*>(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 CurlTransport::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
CurlTransport* client = static_cast<CurlTransport*>(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;
}

View File

@ -8,7 +8,7 @@
using namespace Azure::Core::Http;
void Request::AddPath(std::string const& path) { this->_url += "/" + path; }
void Request::AppendPath(std::string const& path) { this->m_url.AppendPath(path); }
void Request::AddQueryParameter(std::string const& name, std::string const& value)
{
@ -19,7 +19,7 @@ void Request::AddQueryParameter(std::string const& name, std::string const& valu
}
else
{
this->m_queryParameters.insert(std::pair<std::string, std::string>(name, value));
this->m_url.AddQueryParameter(name, value);
}
}
@ -42,17 +42,13 @@ void Request::StartRetry()
this->m_retryHeaders.clear();
}
HttpMethod Request::GetMethod() const { return this->_method; }
HttpMethod Request::GetMethod() const { return this->m_method; }
std::string Request::GetEncodedUrl() const
std::string Request::GetQueryString() const
{
if (this->m_queryParameters.size() == 0 && this->m_retryQueryParameters.size() == 0)
{
return _url; // no query parameters to add
}
// remove query duplicates
auto queryParameters = Request::MergeMaps(this->m_retryQueryParameters, this->m_queryParameters);
auto queryParameters
= Request::MergeMaps(this->m_retryQueryParameters, this->m_url.GetQueryParameters());
// build url
auto queryString = std::string("");
for (auto pair : queryParameters)
@ -60,7 +56,17 @@ std::string Request::GetEncodedUrl() const
queryString += (queryString.empty() ? "?" : "&") + pair.first + "=" + pair.second;
}
return _url + queryString;
return queryString;
}
std::string Request::GetEncodedUrl() const
{
if (this->m_url.GetQueryParameters().size() == 0 && this->m_retryQueryParameters.size() == 0)
{
return m_url.ToString(); // no query parameters to add
}
return m_url.ToString() + GetQueryString();
}
std::map<std::string, std::string> Request::GetHeaders() const
@ -71,3 +77,25 @@ std::map<std::string, std::string> Request::GetHeaders() const
}
BodyStream* Request::GetBodyStream() { return m_bodyStream; }
// Writes an HTTP request with RFC2730 without the body (head line and headers)
// https://tools.ietf.org/html/rfc7230#section-3.1.1
std::string Request::GetHTTPMessagePreBody() const
{
std::string httpRequest(HttpMethodToString(this->m_method));
// origin-form. TODO: parse URL to split host from path and use it here instead of empty
// HTTP version harcoded to 1.0
httpRequest += " " + this->m_url.GetPath() + GetQueryString() + " HTTP/1.1\r\n";
// headers
for (auto header : this->GetHeaders())
{
httpRequest += header.first;
httpRequest += ": ";
httpRequest += header.second;
httpRequest += "\r\n";
}
// end of headers
httpRequest += "\r\n";
return httpRequest;
}

View File

@ -9,49 +9,59 @@
using namespace Azure::Core::Http;
HttpStatusCode Response::GetStatusCode() { return m_statusCode; }
HttpStatusCode Response::GetStatusCode() const { 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)
void Response::AddHeader(std::string const& header)
{
auto alen = a.length();
auto blen = b.length();
// get name and value from header
auto start = header.begin();
auto end = std::find(start, header.end(), ':');
if (alen != blen)
if (end == header.end())
{
return false;
return; // not a valid header or end of headers symbol reached
}
for (size_t index = 0; index < alen; index++)
auto headerName = std::string(start, end);
start = end + 1; // start value
while (start < header.end() && (*start == ' ' || *start == '\t'))
{
// 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;
}
++start;
}
return true;
end = std::find(start, header.end(), '\r');
auto headerValue = std::string(start, end); // remove \r
AddHeader(headerName, headerValue);
}
} // 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));
}
// TODO: make sure the Content-Length header is insterted as "Content-Length" no mather the case
// We currently assume we receive it like it and expected to be there from all HTTP
// Responses.
this->m_headers.insert(std::pair<std::string, std::string>(name, value));
}
void Response::AppendBody(uint8_t* ptr, uint64_t size)
void Response::SetBodyStream(BodyStream* stream) { this->m_bodyStream = stream; }
std::unique_ptr<std::vector<uint8_t>> Response::ConstructBodyBufferFromStream(
BodyStream* const stream)
{
m_bodyBuffer.insert(m_bodyBuffer.end(), ptr, ptr + size);
}
if (stream == nullptr)
{
return nullptr;
}
uint8_t const bodySize = (uint8_t)stream->Length();
std::unique_ptr<std::vector<uint8_t>> unique_buffer(new std::vector<uint8_t>(bodySize));
auto buffer = unique_buffer.get()->data();
stream->Read(buffer, bodySize);
return unique_buffer;
}

View File

@ -0,0 +1,8 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// SPDX-License-Identifier: MIT
#include <http/stream.hpp>
using namespace Azure::Core::Http;
BodyStream::~BodyStream() {}

View File

@ -0,0 +1,57 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// SPDX-License-Identifier: MIT
#include <http/http.hpp>
using namespace Azure::Core::Http;
URL::URL(std::string const& url)
{
if (url.size() == 0)
{
return; // nothing to set
}
// Remove Query Parameters from url
auto noQueryParamsUrl = SaveAndRemoveQueryParameter(url);
auto endOfUrl = noQueryParamsUrl.end();
auto start = noQueryParamsUrl.begin();
// Protocol
auto protocolEnd = std::find(start, endOfUrl, ':');
if (protocolEnd != endOfUrl)
{
auto protocolDelimiter = std::string(protocolEnd, endOfUrl);
// Check protocol delimiter is there ://, otherwise it can be a port
if (protocolDelimiter.size() >= 3 && protocolDelimiter[1] == '/' && protocolDelimiter[2] == '/')
{
this->m_scheme = std::string(start, protocolEnd);
start = protocolEnd + 3;
}
}
// Host
auto endOfHost = std::find(start, endOfUrl, '/');
auto startOfPort = std::find(start, endOfUrl, ':');
if (startOfPort < endOfHost)
{
this->m_port = std::string(startOfPort + 1, endOfHost);
}
this->m_host = std::string(start, std::min(startOfPort, endOfHost));
// finish if there is nothing more ahead
if (endOfHost == endOfUrl)
{
return;
}
// Advance to path
start = endOfHost + 1;
// Path
this->m_path = std::string(start, endOfUrl);
if (this->m_path.size() > 0)
{
this->m_path = "/" + this->m_path;
}
}

View File

@ -94,7 +94,7 @@ 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.AppendPath("path"));
EXPECT_PRED2(
[](std::string a, std::string b) { return a == b; }, req.GetEncodedUrl(), url + "/path");
@ -104,13 +104,13 @@ TEST(Http_Request, add_path)
req.GetEncodedUrl(),
url + "/path?query=value");
EXPECT_NO_THROW(req.AddPath("path2"));
EXPECT_NO_THROW(req.AppendPath("path2"));
EXPECT_PRED2(
[](std::string a, std::string b) { return a == b; },
req.GetEncodedUrl(),
url + "/path/path2?query=value");
EXPECT_NO_THROW(req.AddPath("path3"));
EXPECT_NO_THROW(req.AppendPath("path3"));
EXPECT_PRED2(
[](std::string a, std::string b) { return a == b; },
req.GetEncodedUrl(),

View File

@ -5,6 +5,7 @@ if (BUILD_CURL_TRANSPORT)
cmake_minimum_required (VERSION 3.12)
set(TARGET_NAME "azure_core_with_curl")
set(TARGET_NAME_STREAM "azure_core_with_curl_stream")
project(${TARGET_NAME} LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 14)
@ -12,9 +13,15 @@ set(CMAKE_CXX_STANDARD_REQUIRED True)
add_executable (
${TARGET_NAME}
src/azure_core_with_curl
src/azure_core_with_curl_bodyBuffer
)
add_executable (
${TARGET_NAME_STREAM}
src/azure_core_with_curl_bodyStream
)
target_link_libraries(${TARGET_NAME} PRIVATE azure-core)
target_link_libraries(${TARGET_NAME_STREAM} PRIVATE azure-core)
endif()

View File

@ -1,78 +0,0 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// SPDX-License-Identifier: MIT
/**
* @brief Simulates customer application that is linked with azure-core and azure-transport-curl
*
*/
#include <http/http.hpp>
#include "http/pipeline.hpp"
#include <http/curl/curl.hpp>
#include <iostream>
#include <memory>
using namespace Azure::Core;
using namespace Azure::Core::Http;
using namespace std;
int main()
{
string host("https://httpbin.org/get");
cout << "testing curl from transport" << endl << "Host: " << host << endl;
try
{
auto request = Http::Request(Http::HttpMethod::Get, host);
request.AddHeader("one", "header");
request.AddHeader("other", "header2");
request.AddHeader("header", "value");
//Create the Transport
std::shared_ptr<HttpTransport> transport = std::make_unique<CurlTransport>();
std::vector<std::unique_ptr<HttpPolicy>> policies;
policies.push_back(std::make_unique<RequestIdPolicy>());
RetryOptions retryOptions;
policies.push_back(std::make_unique<RetryPolicy>(retryOptions));
// Add the transport policy
policies.push_back(std::make_unique<TransportPolicy>(std::move(transport)));
auto httpPipeline = Http::HttpPipeline(policies);
auto context = Context();
std::shared_ptr<Http::Response> response = httpPipeline.Send(context, 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)
{
cout << e.what() << endl;
}
catch (Http::TransportException& e)
{
cout << e.what() << endl;
}
return 0;
}

View File

@ -0,0 +1,127 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// SPDX-License-Identifier: MIT
/**
* @brief Simulates customer application that is linked with azure-core and azure-transport-curl
*
*/
#include "http/pipeline.hpp"
#include <http/curl/curl.hpp>
#include <http/http.hpp>
#include <iostream>
#include <memory>
using namespace Azure::Core;
using namespace Azure::Core::Http;
using namespace std;
constexpr auto BufferSize = 50;
std::vector<uint8_t> buffer(BufferSize);
Http::Request createGetRequest();
Http::Request createPutRequest();
void printRespose(std::unique_ptr<Http::Response> response);
int main()
{
try
{
// Both requests uses a body buffer to be uploaded that would produce responses with bodyBuffer
auto getRequest = createGetRequest();
auto putRequest = createPutRequest();
// Create the Transport
std::shared_ptr<HttpTransport> transport = std::make_unique<CurlTransport>();
std::vector<std::unique_ptr<HttpPolicy>> policies;
policies.push_back(std::make_unique<RequestIdPolicy>());
RetryOptions retryOptions;
policies.push_back(std::make_unique<RetryPolicy>(retryOptions));
// Add the transport policy
policies.push_back(std::make_unique<TransportPolicy>(std::move(transport)));
auto httpPipeline = Http::HttpPipeline(policies);
auto context = Context();
std::unique_ptr<Http::Response> getResponse = httpPipeline.Send(context, getRequest);
std::unique_ptr<Http::Response> putResponse = httpPipeline.Send(context, putRequest);
printRespose(std::move(getResponse));
printRespose(std::move(putResponse));
}
catch (Http::CouldNotResolveHostException& e)
{
cout << e.what() << endl;
}
catch (Http::TransportException& e)
{
cout << e.what() << endl;
}
return 0;
}
Http::Request createGetRequest()
{
string host("https://httpbin.org/get");
cout << "Creating a GET request to" << endl << "Host: " << host << endl;
auto request = Http::Request(Http::HttpMethod::Get, host, new Http::MemoryBodyStream(buffer));
request.AddHeader("one", "header");
request.AddHeader("other", "header2");
request.AddHeader("header", "value");
request.AddHeader("Host", "httpbin.org");
return request;
}
Http::Request createPutRequest()
{
string host("https://httpbin.org/put");
cout << "Creating a PUT request to" << endl << "Host: " << host << endl;
std::fill(buffer.begin(), buffer.end(), 'x');
buffer[0] = '{';
buffer[1] = '\"';
buffer[3] = '\"';
buffer[4] = ':';
buffer[5] = '\"';
buffer[BufferSize - 2] = '\"';
buffer[BufferSize - 1] = '}'; // set buffer to look like a Json `{"x":"xxx...xxx"}`
auto request
= Http::Request(Http::HttpMethod::Put, host, new Http::MemoryBodyStream(std::move(buffer)));
request.AddHeader("one", "header");
request.AddHeader("other", "header2");
request.AddHeader("header", "value");
request.AddHeader("Host", "httpbin.org");
request.AddHeader("Content-Length", std::to_string(BufferSize));
return request;
}
void printRespose(std::unique_ptr<Http::Response> response)
{
if (response == nullptr)
{
cout << "Error. Response returned as null";
throw;
}
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 = *Http::Response::ConstructBodyBufferFromStream(response->GetBodyStream()).get();
cout << std::string(bodyVector.begin(), bodyVector.end());
return;
}

View File

@ -0,0 +1,196 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// SPDX-License-Identifier: MIT
/**
* @brief Simulates customer application that is linked with azure-core and azure-transport-curl
* with Stream Body
*
*/
#include "http/pipeline.hpp"
#include <http/curl/curl.hpp>
#include <http/http.hpp>
#include <iostream>
#include <memory>
#include <vector>
#include <array>
using namespace Azure::Core;
using namespace Azure::Core::Http;
using namespace std;
// For bodyBuffer
constexpr auto BufferSize = 50;
std::vector<uint8_t> buffer(BufferSize);
// For StreamBody
constexpr auto StreamSize = 200;
std::array<uint8_t, StreamSize> bufferStream;
Http::Request createGetRequest();
Http::Request createPutRequest();
Http::Request createPutStreamRequest();
void printStream(std::unique_ptr<Http::Response> response);
int main()
{
try
{
// GetRequest. No body, produces stream
auto getRequest = createGetRequest();
// PutRequest. buffer body, produces stream
auto putRequest = createPutRequest();
// PutRequest. Stream body, produces stream
auto putStreamRequest = createPutStreamRequest();
// Create the Transport
std::shared_ptr<HttpTransport> transport = std::make_unique<CurlTransport>();
std::vector<std::unique_ptr<HttpPolicy>> policies;
policies.push_back(std::make_unique<RequestIdPolicy>());
RetryOptions retryOptions;
policies.push_back(std::make_unique<RetryPolicy>(retryOptions));
// Add the transport policy
policies.push_back(std::make_unique<TransportPolicy>(std::move(transport)));
auto httpPipeline = Http::HttpPipeline(policies);
std::unique_ptr<Http::Response> response;
auto context = Context();
response = httpPipeline.Send(context, getRequest);
printStream(std::move(response));
response = httpPipeline.Send(context, putRequest);
printStream(std::move(response));
response = httpPipeline.Send(context, putStreamRequest);
printStream(std::move(response));
}
catch (Http::CouldNotResolveHostException& e)
{
cout << e.what() << endl;
}
catch (Http::TransportException& e)
{
cout << e.what() << endl;
}
return 0;
}
// Request GET with no body that produces stream response
Http::Request createGetRequest()
{
string host("https://httpbin.org/get?arg=1&arg2=2");
cout << "Creating a GET request to" << endl << "Host: " << host << endl;
auto request = Http::Request(Http::HttpMethod::Get, host);
request.AddHeader("one", "header");
request.AddHeader("other", "header2");
request.AddHeader("header", "value");
request.AddHeader("Host", "httpbin.org");
request.AddQueryParameter("dinamicArg", "3");
request.AddQueryParameter("dinamicArg2", "4");
return request;
}
// Put Request with bodyBufferBody that produces stream
Http::Request createPutRequest()
{
string host("https://httpbin.org/put?a=1");
cout << "Creating a PUT request to" << endl << "Host: " << host << endl;
std::fill(buffer.begin(), buffer.end(), 'x');
buffer[0] = '{';
buffer[1] = '\"';
buffer[3] = '\"';
buffer[4] = ':';
buffer[5] = '\"';
buffer[BufferSize - 2] = '\"';
buffer[BufferSize - 1] = '}'; // set buffer to look like a Json `{"x":"xxx...xxx"}`
auto request = Http::Request(Http::HttpMethod::Put, host, new Http::MemoryBodyStream(buffer));
request.AddHeader("one", "header");
request.AddHeader("other", "header2");
request.AddHeader("header", "value");
request.AddHeader("Host", "httpbin.org");
request.AddHeader("Content-Length", std::to_string(BufferSize));
return request;
}
// Put Request with stream body that produces stream
Http::Request createPutStreamRequest()
{
string host("https://httpbin.org/put");
cout << "Creating a PUT request to" << endl << "Host: " << host << endl;
bufferStream.fill('1');
bufferStream[0] = '{';
bufferStream[1] = '\"';
bufferStream[3] = '\"';
bufferStream[4] = ':';
bufferStream[5] = '\"';
bufferStream[StreamSize - 2] = '\"';
bufferStream[StreamSize - 1] = '}'; // set buffer to look like a Json `{"1":"111...111"}`
auto request = Http::Request(
Http::HttpMethod::Put, host, new MemoryBodyStream(bufferStream.data(), StreamSize));
request.AddHeader("one", "header");
request.AddHeader("other", "header2");
request.AddHeader("header", "value");
request.AddHeader("Host", "httpbin.org");
request.AddHeader("Content-Length", std::to_string(StreamSize));
request.AddQueryParameter("dinamicArg", "1");
request.AddQueryParameter("dinamicArg2", "1");
request.AddQueryParameter("dinamicArg3", "1");
return request;
}
void printStream(std::unique_ptr<Http::Response> response)
{
if (response == nullptr)
{
cout << "Error. Response returned as null";
std::cin.ignore();
return;
}
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 (stream):" << endl;
uint8_t b[100];
auto bodyStream = response->GetBodyStream();
uint64_t readCount;
do
{
readCount = bodyStream->Read(b, 10);
cout << std::string(b, b + readCount);
} while (readCount > 0);
cout << endl << "Press any key to continue..." << endl;
std::cin.ignore();
bodyStream->Close();
return;
}

View File

@ -1036,9 +1036,11 @@ namespace Azure { namespace Storage { namespace Blobs {
{
throw std::runtime_error("HTTP status code " + std::to_string(http_status_code));
}
XmlReader reader(
reinterpret_cast<const char*>(http_response.GetBodyBuffer().data()),
http_response.GetBodyBuffer().size());
auto stream = http_response.GetBodyStream();
auto unique_buffer = Core::Http::Response::ConstructBodyBufferFromStream(stream);
XmlReader reader(reinterpret_cast<const char*>(unique_buffer.get()), (size_t)stream->Length());
response = ListContainersSegmentFromXml(reader);
response.Version = http_response.GetHeaders().at("x-ms-version");
response.Date = http_response.GetHeaders().at("Date");
@ -1076,11 +1078,14 @@ namespace Azure { namespace Storage { namespace Blobs {
XmlWriter writer;
GetUserDelegationKeyOptionsToXml(writer, options);
std::string xml_body = writer.GetDocument();
std::vector<uint8_t> body_buffer(xml_body.begin(), xml_body.end());
uint64_t body_buffer_length = body_buffer.size();
auto bodyBuffer = std::vector<uint8_t>(xml_body.begin(), xml_body.end());
auto request = Azure::Core::Http::Request(
Azure::Core::Http::HttpMethod::Post, url, std::move(body_buffer));
request.AddHeader("Content-Length", std::to_string(body_buffer_length));
Azure::Core::Http::HttpMethod::Post,
url,
new Azure::Core::Http::MemoryBodyStream(std::move(bodyBuffer)));
request.AddHeader("Content-Length", std::to_string(bodyBuffer.size()));
request.AddQueryParameter("restype", "service");
request.AddQueryParameter("comp", "userdelegationkey");
request.AddHeader("x-ms-version", "2019-07-07");
@ -1099,9 +1104,11 @@ namespace Azure { namespace Storage { namespace Blobs {
{
throw std::runtime_error("HTTP status code " + std::to_string(http_status_code));
}
XmlReader reader(
reinterpret_cast<const char*>(http_response.GetBodyBuffer().data()),
http_response.GetBodyBuffer().size());
auto stream = http_response.GetBodyStream();
auto unique_buffer = Core::Http::Response::ConstructBodyBufferFromStream(stream);
XmlReader reader(reinterpret_cast<const char*>(unique_buffer.get()), (size_t)stream->Length());
response = UserDelegationKeyFromXml(reader);
response.Version = http_response.GetHeaders().at("x-ms-version");
response.Date = http_response.GetHeaders().at("Date");
@ -1896,9 +1903,11 @@ namespace Azure { namespace Storage { namespace Blobs {
{
throw std::runtime_error("HTTP status code " + std::to_string(http_status_code));
}
XmlReader reader(
reinterpret_cast<const char*>(http_response.GetBodyBuffer().data()),
http_response.GetBodyBuffer().size());
auto stream = http_response.GetBodyStream();
auto unique_buffer = Core::Http::Response::ConstructBodyBufferFromStream(stream);
XmlReader reader(reinterpret_cast<const char*>(unique_buffer.get()), (size_t)stream->Length());
response = BlobsFlatSegmentFromXml(reader);
response.Version = http_response.GetHeaders().at("x-ms-version");
response.Date = http_response.GetHeaders().at("Date");
@ -2553,7 +2562,11 @@ namespace Azure { namespace Storage { namespace Blobs {
response.CommittedBlockCount = std::stoull(response_committedblockcount_iterator->second);
}
response.BlobType = BlobTypeFromString(http_response.GetHeaders().at("x-ms-blob-type"));
response.BodyBuffer = http_response.GetBodyBuffer();
auto stream = http_response.GetBodyStream();
response.BodyBuffer
= std::move(*Core::Http::Response::ConstructBodyBufferFromStream(stream).get());
return response;
}
@ -3441,7 +3454,9 @@ namespace Azure { namespace Storage { namespace Blobs {
const UploadOptions& options)
{
auto request = Azure::Core::Http::Request(
Azure::Core::Http::HttpMethod::Put, url, *options.BodyBuffer);
Azure::Core::Http::HttpMethod::Put,
url,
new Azure::Core::Http::MemoryBodyStream(*options.BodyBuffer));
request.AddHeader("Content-Length", std::to_string(options.BodyBuffer->size()));
request.AddHeader("x-ms-version", "2019-07-07");
if (!options.EncryptionKey.empty())
@ -3596,7 +3611,9 @@ namespace Azure { namespace Storage { namespace Blobs {
const StageBlockOptions& options)
{
auto request = Azure::Core::Http::Request(
Azure::Core::Http::HttpMethod::Put, url, *options.BodyBuffer);
Azure::Core::Http::HttpMethod::Put,
url,
new Azure::Core::Http::MemoryBodyStream(*options.BodyBuffer));
request.AddHeader("Content-Length", std::to_string(options.BodyBuffer->size()));
request.AddQueryParameter("comp", "block");
request.AddQueryParameter("blockid", options.BlockId);
@ -3853,7 +3870,9 @@ namespace Azure { namespace Storage { namespace Blobs {
std::vector<uint8_t> body_buffer(xml_body.begin(), xml_body.end());
uint64_t body_buffer_length = body_buffer.size();
auto request = Azure::Core::Http::Request(
Azure::Core::Http::HttpMethod::Put, url, std::move(body_buffer));
Azure::Core::Http::HttpMethod::Put,
url,
new Azure::Core::Http::MemoryBodyStream(std::move(body_buffer)));
request.AddHeader("Content-Length", std::to_string(body_buffer_length));
request.AddQueryParameter("comp", "blocklist");
request.AddHeader("x-ms-version", "2019-07-07");
@ -4024,9 +4043,11 @@ namespace Azure { namespace Storage { namespace Blobs {
{
throw std::runtime_error("HTTP status code " + std::to_string(http_status_code));
}
auto stream = http_response.GetBodyStream();
auto unique_buffer = Core::Http::Response::ConstructBodyBufferFromStream(stream);
XmlReader reader(
reinterpret_cast<const char*>(http_response.GetBodyBuffer().data()),
http_response.GetBodyBuffer().size());
reinterpret_cast<const char*>(unique_buffer.get()), (size_t)stream->Length());
response = BlobBlockListInfoFromXml(reader);
response.Version = http_response.GetHeaders().at("x-ms-version");
response.Date = http_response.GetHeaders().at("Date");
@ -4380,7 +4401,9 @@ namespace Azure { namespace Storage { namespace Blobs {
const UploadPagesOptions& options)
{
auto request = Azure::Core::Http::Request(
Azure::Core::Http::HttpMethod::Put, url, *options.BodyBuffer);
Azure::Core::Http::HttpMethod::Put,
url,
new Azure::Core::Http::MemoryBodyStream(*options.BodyBuffer));
request.AddHeader("Content-Length", std::to_string(options.BodyBuffer->size()));
request.AddQueryParameter("comp", "page");
request.AddHeader("x-ms-version", "2019-07-07");
@ -4967,9 +4990,11 @@ namespace Azure { namespace Storage { namespace Blobs {
{
throw std::runtime_error("HTTP status code " + std::to_string(http_status_code));
}
auto stream = http_response.GetBodyStream();
auto unique_buffer = Core::Http::Response::ConstructBodyBufferFromStream(stream);
XmlReader reader(
reinterpret_cast<const char*>(http_response.GetBodyBuffer().data()),
http_response.GetBodyBuffer().size());
reinterpret_cast<const char*>(unique_buffer.get()), (size_t)stream->Length());
response = PageRangesInfoFromXml(reader);
response.Version = http_response.GetHeaders().at("x-ms-version");
response.Date = http_response.GetHeaders().at("Date");
@ -5409,7 +5434,9 @@ namespace Azure { namespace Storage { namespace Blobs {
const AppendBlockOptions& options)
{
auto request = Azure::Core::Http::Request(
Azure::Core::Http::HttpMethod::Put, url, *options.BodyBuffer);
Azure::Core::Http::HttpMethod::Put,
url,
new Azure::Core::Http::MemoryBodyStream(*options.BodyBuffer));
request.AddHeader("Content-Length", std::to_string(options.BodyBuffer->size()));
request.AddQueryParameter("comp", "appendblock");
request.AddHeader("x-ms-version", "2019-07-07");