Connection pool for keep alive (#500)
* keep-alive. reuse same connection based on host
This commit is contained in:
parent
755085f25d
commit
70eeec5984
@ -10,6 +10,11 @@ option(BUILD_CURL_TRANSPORT "Build internal http transport implementation with C
|
||||
option(BUILD_TESTING "Build test cases" OFF)
|
||||
option(BUILD_DOCUMENTATION "Create HTML based API documentation (requires Doxygen)" OFF)
|
||||
|
||||
if(BUILD_TESTING)
|
||||
# define a symbol that enables some test hooks in code
|
||||
add_compile_definitions(TESTING_BUILD)
|
||||
endif()
|
||||
|
||||
# VCPKG Integration
|
||||
if(DEFINED ENV{VCPKG_ROOT} AND NOT DEFINED CMAKE_TOOLCHAIN_FILE)
|
||||
set(CMAKE_TOOLCHAIN_FILE "$ENV{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake"
|
||||
|
||||
@ -9,6 +9,9 @@
|
||||
#include "http/policy.hpp"
|
||||
|
||||
#include <curl/curl.h>
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
@ -17,8 +20,12 @@ namespace Azure { namespace Core { namespace Http {
|
||||
namespace Details {
|
||||
// libcurl CURL_MAX_WRITE_SIZE is 64k. Using same value for default uploading chunk size.
|
||||
// This can be customizable in the HttpRequest
|
||||
constexpr int64_t c_UploadDefaultChunkSize = 1024 * 64;
|
||||
constexpr auto c_LibcurlReaderSize = 1024;
|
||||
constexpr static int64_t c_DefaultUploadChunkSize = 1024 * 64;
|
||||
constexpr static auto c_DefaultLibcurlReaderSize = 1024;
|
||||
// Run time error template
|
||||
constexpr static const char* c_DefaultFailedToGetNewConnectionTemplate
|
||||
= "Fail to get a new connection for: ";
|
||||
constexpr static int c_DefaultMaxOpenNewConnectionIntentsAllowed = 10;
|
||||
} // namespace Details
|
||||
|
||||
/**
|
||||
@ -34,6 +41,35 @@ namespace Azure { namespace Core { namespace Http {
|
||||
* transporter to be re usuable in multiple pipelines while every call to network is unique.
|
||||
*/
|
||||
class CurlSession : public BodyStream {
|
||||
// connection handle. It will be taken from a pool
|
||||
class CurlConnection {
|
||||
private:
|
||||
CURL* m_handle;
|
||||
std::string m_host;
|
||||
|
||||
public:
|
||||
CurlConnection(std::string const& host) : m_handle(curl_easy_init()), m_host(host) {}
|
||||
|
||||
~CurlConnection() { curl_easy_cleanup(this->m_handle); }
|
||||
|
||||
CURL* GetHandle() { return this->m_handle; }
|
||||
|
||||
std::string GetHost() const { return this->m_host; }
|
||||
};
|
||||
|
||||
// TODO: Mutex for this code to access connectionPoolIndex
|
||||
/*
|
||||
* Keeps an unique key for each host and creates a connection pool for each key.
|
||||
* This way getting a connection for an specific host can be done in O(1) instead of looping a
|
||||
* single connection list to find the first connection for the required host.
|
||||
*
|
||||
* There might be multiple connections for each host.
|
||||
*/
|
||||
static std::map<std::string, std::list<std::unique_ptr<CurlConnection>>> s_connectionPoolIndex;
|
||||
static std::unique_ptr<CurlConnection> GetCurlConnection(Request& request);
|
||||
static void MoveConnectionBackToPool(std::unique_ptr<CurlSession::CurlConnection> connection);
|
||||
static std::mutex s_connectionPoolMutex;
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief Enum used by ResponseBufferParser to control the parsing internal state while building
|
||||
@ -174,11 +210,7 @@ namespace Azure { namespace Core { namespace Http {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief libcurl handle to be used in the session.
|
||||
*
|
||||
*/
|
||||
CURL* m_pCurl;
|
||||
std::unique_ptr<CurlConnection> m_connection;
|
||||
|
||||
/**
|
||||
* @brief libcurl socket abstraction used when working with streams.
|
||||
@ -257,7 +289,7 @@ namespace Azure { namespace Core { namespace Http {
|
||||
* provide their own buffer to copy from socket when reading the HTTP body using streams.
|
||||
*
|
||||
*/
|
||||
uint8_t m_readBuffer[Details::c_LibcurlReaderSize]; // to work with libcurl custom read.
|
||||
uint8_t m_readBuffer[Details::c_DefaultLibcurlReaderSize]; // to work with libcurl custom read.
|
||||
|
||||
/**
|
||||
* @brief convenient function that indicates when the HTTP Request will need to upload a payload
|
||||
@ -268,23 +300,6 @@ namespace Azure { namespace Core { namespace Http {
|
||||
*/
|
||||
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.
|
||||
*
|
||||
@ -367,6 +382,15 @@ namespace Azure { namespace Core { namespace Http {
|
||||
int64_t ReadSocketToBuffer(uint8_t* buffer, int64_t bufferSize);
|
||||
|
||||
public:
|
||||
#ifdef TESTING_BUILD
|
||||
// Makes possible to know the number of current connections in the connection pool
|
||||
static int64_t s_ConnectionsOnPool(std::string const& host)
|
||||
{
|
||||
auto& pool = s_connectionPoolIndex[host];
|
||||
return pool.size();
|
||||
};
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Construct a new Curl Session object. Init internal libcurl handler.
|
||||
*
|
||||
@ -374,18 +398,27 @@ namespace Azure { namespace Core { namespace Http {
|
||||
*/
|
||||
CurlSession(Request& request) : m_request(request)
|
||||
{
|
||||
this->m_pCurl = curl_easy_init();
|
||||
this->m_connection = GetCurlConnection(this->m_request);
|
||||
this->m_bodyStartInBuffer = -1;
|
||||
this->m_innerBufferSize = Details::c_LibcurlReaderSize;
|
||||
this->m_innerBufferSize = Details::c_DefaultLibcurlReaderSize;
|
||||
this->m_rawResponseEOF = false;
|
||||
this->m_isChunkedResponseType = false;
|
||||
this->m_uploadedBytes = 0;
|
||||
}
|
||||
|
||||
~CurlSession() override { curl_easy_cleanup(this->m_pCurl); }
|
||||
~CurlSession() override
|
||||
{
|
||||
// mark connection as reusable only if entire response was read
|
||||
// If not, connection can't be reused because next Read will start from what it is currently
|
||||
// in the wire. We leave the connection blocked until Server closes the connection
|
||||
if (this->m_rawResponseEOF)
|
||||
{
|
||||
MoveConnectionBackToPool(std::move(this->m_connection));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Function will use the HTTP request received in constutor to perform a network call
|
||||
* @brief Function will use the HTTP request received in constructor to perform a network call
|
||||
* based on the HTTP request configuration.
|
||||
*
|
||||
* @param context TBD
|
||||
|
||||
@ -12,8 +12,24 @@ std::unique_ptr<RawResponse> CurlTransport::Send(Context const& context, Request
|
||||
{
|
||||
// Create CurlSession to perform request
|
||||
auto session = std::make_unique<CurlSession>(request);
|
||||
CURLcode performing;
|
||||
|
||||
auto performing = session->Perform(context);
|
||||
// Try to send the request. If we get CURLE_UNSUPPORTED_PROTOCOL back it means the connection is
|
||||
// either closed or the socket is not usable any more. In that case, let the session be destroyed
|
||||
// and create a new session to get another connection from connection pool.
|
||||
// Prevent from trying forever by using c_DefaultMaxOpenNewConnectionIntentsAllowed.
|
||||
for (auto getConnectionOpenIntent = 0;
|
||||
getConnectionOpenIntent < Details::c_DefaultMaxOpenNewConnectionIntentsAllowed;
|
||||
getConnectionOpenIntent++)
|
||||
{
|
||||
performing = session->Perform(context);
|
||||
if (performing != CURLE_UNSUPPORTED_PROTOCOL)
|
||||
{
|
||||
break;
|
||||
}
|
||||
// Let session be destroyed and create a new one to get a new connection
|
||||
session = std::make_unique<CurlSession>(request);
|
||||
}
|
||||
|
||||
if (performing != CURLE_OK)
|
||||
{
|
||||
@ -41,15 +57,16 @@ CURLcode CurlSession::Perform(Context const& context)
|
||||
{
|
||||
AZURE_UNREFERENCED_PARAMETER(context);
|
||||
|
||||
// Working with Body Buffer. let Libcurl use the classic callback to read/write
|
||||
auto result = SetUrl();
|
||||
// Get the socket that libcurl is using from handle. Will use this to wait while reading/writing
|
||||
// into wire
|
||||
auto result = curl_easy_getinfo(
|
||||
this->m_connection->GetHandle(), CURLINFO_ACTIVESOCKET, &this->m_curlSocket);
|
||||
if (result != CURLE_OK)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
// Make sure host is set
|
||||
// TODO-> use isEqualNoCase here once it is merged
|
||||
// LibCurl settings after connection is open (headers)
|
||||
{
|
||||
auto headers = this->m_request.GetHeaders();
|
||||
auto hostHeader = headers.find("Host");
|
||||
@ -65,38 +82,15 @@ CURLcode CurlSession::Perform(Context const& context)
|
||||
}
|
||||
}
|
||||
|
||||
result = SetConnectOnly();
|
||||
if (result != CURLE_OK)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
// curl_easy_setopt(this->m_pCurl, CURLOPT_VERBOSE, 1L);
|
||||
// Set timeout to 24h. Libcurl will fail uploading on windows if timeout is:
|
||||
// timeout >= 25 days. Fails as soon as trying to upload any data
|
||||
// 25 days < timeout > 1 days. Fail on huge uploads ( > 1GB)
|
||||
curl_easy_setopt(this->m_pCurl, CURLOPT_TIMEOUT, 60L * 60L * 24L);
|
||||
|
||||
// use expect:100 for PUT requests. Server will decide if it can take our request
|
||||
if (this->m_request.GetMethod() == HttpMethod::Put)
|
||||
{
|
||||
this->m_request.AddHeader("expect", "100-continue");
|
||||
}
|
||||
|
||||
// establish connection only (won't send or receive anything yet)
|
||||
result = curl_easy_perform(this->m_pCurl);
|
||||
if (result != CURLE_OK)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
// Record socket to be used
|
||||
result = curl_easy_getinfo(this->m_pCurl, CURLINFO_ACTIVESOCKET, &this->m_curlSocket);
|
||||
if (result != CURLE_OK)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
// Send request
|
||||
// Send request. If the connection assigned to this curlSession is closed or the socket is
|
||||
// somehow lost, libcurl will return CURLE_UNSUPPORTED_PROTOCOL
|
||||
// (https://curl.haxx.se/libcurl/c/curl_easy_send.html). Return the error back.
|
||||
result = HttpRawSend(context);
|
||||
if (result != CURLE_OK)
|
||||
{
|
||||
@ -201,16 +195,6 @@ bool CurlSession::isUploadRequest()
|
||||
|| this->m_request.GetMethod() == HttpMethod::Post;
|
||||
}
|
||||
|
||||
CURLcode CurlSession::SetUrl()
|
||||
{
|
||||
return curl_easy_setopt(this->m_pCurl, CURLOPT_URL, this->m_request.GetEncodedUrl().data());
|
||||
}
|
||||
|
||||
CURLcode CurlSession::SetConnectOnly()
|
||||
{
|
||||
return curl_easy_setopt(this->m_pCurl, CURLOPT_CONNECT_ONLY, 1L);
|
||||
}
|
||||
|
||||
// Send buffer thru the wire
|
||||
CURLcode CurlSession::SendBuffer(uint8_t const* buffer, size_t bufferSize)
|
||||
{
|
||||
@ -220,7 +204,7 @@ CURLcode CurlSession::SendBuffer(uint8_t const* buffer, size_t bufferSize)
|
||||
{
|
||||
size_t sentBytesPerRequest = 0;
|
||||
sendResult = curl_easy_send(
|
||||
this->m_pCurl,
|
||||
this->m_connection->GetHandle(),
|
||||
buffer + sentBytesTotal,
|
||||
bufferSize - sentBytesTotal,
|
||||
&sentBytesPerRequest);
|
||||
@ -259,7 +243,7 @@ CURLcode CurlSession::UploadBody(Context const& context)
|
||||
if (uploadChunkSize <= 0)
|
||||
{
|
||||
// use default size
|
||||
uploadChunkSize = Details::c_UploadDefaultChunkSize;
|
||||
uploadChunkSize = Details::c_DefaultUploadChunkSize;
|
||||
}
|
||||
auto unique_buffer = std::make_unique<uint8_t[]>(static_cast<size_t>(uploadChunkSize));
|
||||
|
||||
@ -294,13 +278,6 @@ CURLcode CurlSession::HttpRawSend(Context const& context)
|
||||
return sendResult;
|
||||
}
|
||||
|
||||
auto streamBody = this->m_request.GetBodyStream();
|
||||
if (streamBody->Length() == 0)
|
||||
{
|
||||
// Finish request with no body (Head or PUT (expect:100;)
|
||||
uint8_t const endOfRequest = 0;
|
||||
return SendBuffer(&endOfRequest, 1); // need one more byte to end request
|
||||
}
|
||||
return this->UploadBody(context);
|
||||
}
|
||||
|
||||
@ -333,7 +310,7 @@ void CurlSession::ParseChunkSize()
|
||||
if (index + 1 == this->m_innerBufferSize)
|
||||
{ // on last index. Whatever we read is the BodyStart here
|
||||
this->m_innerBufferSize
|
||||
= ReadSocketToBuffer(this->m_readBuffer, Details::c_LibcurlReaderSize);
|
||||
= ReadSocketToBuffer(this->m_readBuffer, Details::c_DefaultLibcurlReaderSize);
|
||||
this->m_bodyStartInBuffer = 0;
|
||||
}
|
||||
else
|
||||
@ -348,7 +325,7 @@ void CurlSession::ParseChunkSize()
|
||||
if (keepPolling)
|
||||
{ // Read all internal buffer and \n was not found, pull from wire
|
||||
this->m_innerBufferSize
|
||||
= ReadSocketToBuffer(this->m_readBuffer, Details::c_LibcurlReaderSize);
|
||||
= ReadSocketToBuffer(this->m_readBuffer, Details::c_DefaultLibcurlReaderSize);
|
||||
this->m_bodyStartInBuffer = 0;
|
||||
}
|
||||
}
|
||||
@ -366,7 +343,7 @@ void CurlSession::ReadStatusLineAndHeadersFromRawResponse()
|
||||
{
|
||||
// Try to fill internal buffer from socket.
|
||||
// If response is smaller than buffer, we will get back the size of the response
|
||||
bufferSize = ReadSocketToBuffer(this->m_readBuffer, Details::c_LibcurlReaderSize);
|
||||
bufferSize = ReadSocketToBuffer(this->m_readBuffer, Details::c_DefaultLibcurlReaderSize);
|
||||
|
||||
// returns the number of bytes parsed up to the body Start
|
||||
auto bytesParsed = parser.Parse(this->m_readBuffer, static_cast<size_t>(bufferSize));
|
||||
@ -422,7 +399,7 @@ void CurlSession::ReadStatusLineAndHeadersFromRawResponse()
|
||||
if (this->m_bodyStartInBuffer == -1)
|
||||
{ // if nothing on inner buffer, pull from wire
|
||||
this->m_innerBufferSize
|
||||
= ReadSocketToBuffer(this->m_readBuffer, Details::c_LibcurlReaderSize);
|
||||
= ReadSocketToBuffer(this->m_readBuffer, Details::c_DefaultLibcurlReaderSize);
|
||||
this->m_bodyStartInBuffer = 0;
|
||||
}
|
||||
|
||||
@ -462,7 +439,7 @@ int64_t CurlSession::Read(Azure::Core::Context const& context, uint8_t* buffer,
|
||||
else
|
||||
{ // end of buffer, pull data from wire
|
||||
this->m_innerBufferSize
|
||||
= ReadSocketToBuffer(this->m_readBuffer, Details::c_LibcurlReaderSize);
|
||||
= ReadSocketToBuffer(this->m_readBuffer, Details::c_DefaultLibcurlReaderSize);
|
||||
this->m_bodyStartInBuffer = 1; // jump first char (could be \r or \n)
|
||||
}
|
||||
}
|
||||
@ -515,7 +492,8 @@ int64_t CurlSession::Read(Azure::Core::Context const& context, uint8_t* buffer,
|
||||
// Also if we have already read all contentLength
|
||||
if (this->m_sessionTotalRead == this->m_contentLength || this->m_rawResponseEOF)
|
||||
{
|
||||
// Read everything already
|
||||
// make sure EOF for response is set to true
|
||||
this->m_rawResponseEOF = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -538,7 +516,8 @@ int64_t CurlSession::ReadSocketToBuffer(uint8_t* buffer, int64_t bufferSize)
|
||||
size_t readBytes = 0;
|
||||
for (CURLcode readResult = CURLE_AGAIN; readResult == CURLE_AGAIN;)
|
||||
{
|
||||
readResult = curl_easy_recv(this->m_pCurl, buffer, static_cast<size_t>(bufferSize), &readBytes);
|
||||
readResult = curl_easy_recv(
|
||||
this->m_connection->GetHandle(), buffer, static_cast<size_t>(bufferSize), &readBytes);
|
||||
|
||||
switch (readResult)
|
||||
{
|
||||
@ -804,6 +783,88 @@ int64_t CurlSession::ResponseBufferParser::BuildHeader(
|
||||
|
||||
// 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
|
||||
// Parsing Headers will make sure to move one position
|
||||
return indexOfEndOfStatusLine + 1 - buffer;
|
||||
}
|
||||
|
||||
std::mutex CurlSession::s_connectionPoolMutex;
|
||||
std::map<std::string, std::list<std::unique_ptr<CurlSession::CurlConnection>>>
|
||||
CurlSession::s_connectionPoolIndex;
|
||||
|
||||
std::unique_ptr<CurlSession::CurlConnection> CurlSession::GetCurlConnection(Request& request)
|
||||
{
|
||||
std::string const& host = request.GetHost();
|
||||
|
||||
// Double-check locking. Check if there is any available connection before locking mutex
|
||||
auto& hostPoolFirstCheck = s_connectionPoolIndex[host];
|
||||
if (hostPoolFirstCheck.size() > 0)
|
||||
{
|
||||
// Critical section. Needs to own s_connectionPoolMutex before executing
|
||||
// Lock mutex to access connection pool. mutex is unlock as soon as lock is out of scope
|
||||
std::lock_guard<std::mutex> lock(s_connectionPoolMutex);
|
||||
|
||||
// get a ref to the pool from the map of pools
|
||||
auto& hostPool = s_connectionPoolIndex[host];
|
||||
if (hostPool.size() > 0)
|
||||
{
|
||||
// get ref to first connection
|
||||
auto fistConnectionIterator = hostPool.begin();
|
||||
// move the connection ref to temp ref
|
||||
auto connection = std::move(*fistConnectionIterator);
|
||||
// Remove the connection ref from list
|
||||
hostPool.erase(fistConnectionIterator);
|
||||
// return connection ref
|
||||
return connection;
|
||||
}
|
||||
}
|
||||
|
||||
// Creating a new connection is thread safe. No need to lock mutex here.
|
||||
// No available connection for the pool for the required host. Create one
|
||||
auto newConnection = std::make_unique<CurlConnection>(host);
|
||||
|
||||
// Libcurl setup before open connection (url, connet_only, timeout)
|
||||
auto result
|
||||
= curl_easy_setopt(newConnection->GetHandle(), CURLOPT_URL, request.GetEncodedUrl().data());
|
||||
if (result != CURLE_OK)
|
||||
{
|
||||
throw std::runtime_error(
|
||||
Details::c_DefaultFailedToGetNewConnectionTemplate + host + ". Could not set URL.");
|
||||
}
|
||||
|
||||
result = curl_easy_setopt(newConnection->GetHandle(), CURLOPT_CONNECT_ONLY, 1L);
|
||||
if (result != CURLE_OK)
|
||||
{
|
||||
throw std::runtime_error(
|
||||
Details::c_DefaultFailedToGetNewConnectionTemplate + host
|
||||
+ ". Could not set connect only ON.");
|
||||
}
|
||||
|
||||
// curl_easy_setopt(newConnection->GetHandle(), CURLOPT_VERBOSE, 1L);
|
||||
// Set timeout to 24h. Libcurl will fail uploading on windows if timeout is:
|
||||
// timeout >= 25 days. Fails as soon as trying to upload any data
|
||||
// 25 days < timeout > 1 days. Fail on huge uploads ( > 1GB)
|
||||
result = curl_easy_setopt(newConnection->GetHandle(), CURLOPT_TIMEOUT, 60L * 60L * 24L);
|
||||
if (result != CURLE_OK)
|
||||
{
|
||||
throw std::runtime_error(
|
||||
Details::c_DefaultFailedToGetNewConnectionTemplate + host + ". Could not set timeout.");
|
||||
}
|
||||
|
||||
result = curl_easy_perform(newConnection->GetHandle());
|
||||
if (result != CURLE_OK)
|
||||
{
|
||||
throw std::runtime_error(
|
||||
Details::c_DefaultFailedToGetNewConnectionTemplate + host + ". Could not open connection.");
|
||||
}
|
||||
return newConnection;
|
||||
}
|
||||
|
||||
// Move the connection back to the connection pool. Push it to the front so it becomes the first
|
||||
// connection to be picked next time some one ask for a connection to the pool (LIFO)
|
||||
void CurlSession::MoveConnectionBackToPool(std::unique_ptr<CurlSession::CurlConnection> connection)
|
||||
{
|
||||
// Lock mutex to access connection pool. mutex is unlock as soon as lock is out of scope
|
||||
std::lock_guard<std::mutex> lock(s_connectionPoolMutex);
|
||||
auto& hostPool = s_connectionPoolIndex[connection->GetHost()];
|
||||
hostPool.push_front(std::move(connection));
|
||||
}
|
||||
|
||||
@ -81,15 +81,16 @@ void doFileRequest(Context const& context, HttpPipeline& pipeline)
|
||||
cout << "Creating a Put From File request to" << endl << "Host: " << host << endl;
|
||||
|
||||
// Open a file that contains: {{"key":"value"}, {"key2":"value2"}, {"key3":"value3"}}
|
||||
int fd = open("/home/vagrant/workspace/a", O_RDONLY);
|
||||
int fd = open("/home/vivazqu/workspace/a", O_RDONLY);
|
||||
// Create Stream from file starting with offset 18 to 100
|
||||
auto requestBodyStream = FileBodyStream(fd, 18, 100);
|
||||
// Limit stream to read up to 17 postions ( {"key2","value2"} )
|
||||
// Limit stream to read up to 17 positions ( {"key2","value2"} )
|
||||
auto limitedStream = LimitBodyStream(&requestBodyStream, 17);
|
||||
|
||||
// Send request
|
||||
auto request = Http::Request(Http::HttpMethod::Put, host, &limitedStream);
|
||||
auto request = Http::Request(Http::HttpMethod::Put, host, &limitedStream, true);
|
||||
request.AddHeader("Content-Length", std::to_string(limitedStream.Length()));
|
||||
request.AddHeader("File", "fileeeeeeeeeee");
|
||||
|
||||
auto response = pipeline.Send(context, request);
|
||||
// File can be closed at this point
|
||||
@ -138,10 +139,10 @@ void doGetRequest(Context const& context, HttpPipeline& pipeline)
|
||||
cout << "Creating a GET request to" << endl << "Host: " << host << endl;
|
||||
|
||||
auto requestBodyStream = std::make_unique<MemoryBodyStream>(buffer.data(), buffer.size());
|
||||
auto request = Http::Request(Http::HttpMethod::Get, host, requestBodyStream.get());
|
||||
request.AddHeader("one", "header");
|
||||
request.AddHeader("other", "header2");
|
||||
request.AddHeader("header", "value");
|
||||
auto request = Http::Request(Http::HttpMethod::Get, host, requestBodyStream.get(), true);
|
||||
request.AddHeader("one", "GetHeader");
|
||||
request.AddHeader("other", "GetHeader2");
|
||||
request.AddHeader("header", "GetValue");
|
||||
request.AddHeader("Host", "httpbin.org");
|
||||
|
||||
cout << endl << "GET:";
|
||||
@ -163,10 +164,10 @@ void doPutRequest(Context const& context, HttpPipeline& pipeline)
|
||||
buffer[BufferSize - 1] = '}'; // set buffer to look like a Json `{"x":"xxx...xxx"}`
|
||||
|
||||
auto requestBodyStream = std::make_unique<MemoryBodyStream>(buffer.data(), buffer.size());
|
||||
auto request = Http::Request(Http::HttpMethod::Put, host, requestBodyStream.get());
|
||||
request.AddHeader("one", "header");
|
||||
request.AddHeader("other", "header2");
|
||||
request.AddHeader("header", "value");
|
||||
auto request = Http::Request(Http::HttpMethod::Put, host, requestBodyStream.get(), true);
|
||||
request.AddHeader("PUT", "header");
|
||||
request.AddHeader("PUT2", "header2");
|
||||
request.AddHeader("PUT3", "value");
|
||||
|
||||
request.AddHeader("Host", "httpbin.org");
|
||||
request.AddHeader("Content-Length", std::to_string(BufferSize));
|
||||
@ -214,8 +215,7 @@ void doPatchRequest(Context const& context, HttpPipeline& pipeline)
|
||||
string host("https://httpbin.org/patch");
|
||||
cout << "Creating an PATCH request to" << endl << "Host: " << host << endl;
|
||||
|
||||
auto request = Http::Request(Http::HttpMethod::Patch, host);
|
||||
request.AddHeader("Host", "httpbin.org");
|
||||
auto request = Http::Request(Http::HttpMethod::Patch, host, true);
|
||||
|
||||
cout << endl << "PATCH:";
|
||||
printRespose(pipeline.Send(context, request));
|
||||
@ -226,8 +226,8 @@ void doDeleteRequest(Context const& context, HttpPipeline& pipeline)
|
||||
string host("https://httpbin.org/delete");
|
||||
cout << "Creating an DELETE request to" << endl << "Host: " << host << endl;
|
||||
|
||||
auto request = Http::Request(Http::HttpMethod::Delete, host);
|
||||
request.AddHeader("Host", "httpbin.org");
|
||||
auto request = Http::Request(Http::HttpMethod::Delete, host, true);
|
||||
// request.AddHeader("deleteeeee", "httpbin.org");
|
||||
|
||||
cout << endl << "DELETE:";
|
||||
printRespose(pipeline.Send(context, request));
|
||||
@ -238,8 +238,8 @@ void doHeadRequest(Context const& context, HttpPipeline& pipeline)
|
||||
string host("https://httpbin.org/get");
|
||||
cout << "Creating an HEAD request to" << endl << "Host: " << host << endl;
|
||||
|
||||
auto request = Http::Request(Http::HttpMethod::Head, host);
|
||||
request.AddHeader("Host", "httpbin.org");
|
||||
auto request = Http::Request(Http::HttpMethod::Head, host, true);
|
||||
request.AddHeader("HEAD", "httpbin.org");
|
||||
|
||||
cout << endl << "HEAD:";
|
||||
printRespose(pipeline.Send(context, request));
|
||||
|
||||
@ -5,6 +5,7 @@
|
||||
#include <context.hpp>
|
||||
#include <response.hpp>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
|
||||
namespace Azure { namespace Core { namespace Test {
|
||||
|
||||
@ -42,6 +43,39 @@ namespace Azure { namespace Core { namespace Test {
|
||||
CheckBodyFromBuffer(*response, expectedResponseBodySize + 6 + 13);
|
||||
}
|
||||
|
||||
// multiThread test requires `s_ConnectionsOnPool` hook which is only available when building
|
||||
// TESTING_BUILD. This test cases are only built when that case is true.`
|
||||
TEST_F(TransportAdapter, getMultiThread)
|
||||
{
|
||||
std::string host("http://httpbin.org/get");
|
||||
|
||||
auto threadRoutine = [host]() {
|
||||
auto request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Get, host);
|
||||
auto response = pipeline.Send(context, request);
|
||||
checkResponseCode(response->GetStatusCode());
|
||||
auto expectedResponseBodySize = std::stoull(response->GetHeaders().at("content-length"));
|
||||
CheckBodyFromBuffer(*response, expectedResponseBodySize);
|
||||
};
|
||||
|
||||
std::thread t1(threadRoutine);
|
||||
std::thread t2(threadRoutine);
|
||||
t1.join();
|
||||
t2.join();
|
||||
auto connectionsNow = Http::CurlSession::s_ConnectionsOnPool("httpbin.org");
|
||||
// 2 connections must be available at this point
|
||||
EXPECT_EQ(connectionsNow, 2);
|
||||
|
||||
std::thread t3(threadRoutine);
|
||||
std::thread t4(threadRoutine);
|
||||
std::thread t5(threadRoutine);
|
||||
t3.join();
|
||||
t4.join();
|
||||
t5.join();
|
||||
connectionsNow = Http::CurlSession::s_ConnectionsOnPool("httpbin.org");
|
||||
// Two connections re-used plus one connection created
|
||||
EXPECT_EQ(connectionsNow, 3);
|
||||
}
|
||||
|
||||
TEST_F(TransportAdapter, get204)
|
||||
{
|
||||
std::string host("http://mt3.google.com/generate_204");
|
||||
@ -60,7 +94,7 @@ namespace Azure { namespace Core { namespace Test {
|
||||
auto request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Get, host);
|
||||
|
||||
// loop sending request
|
||||
for (auto i = 0; i < 20; i++)
|
||||
for (auto i = 0; i < 500; i++)
|
||||
{
|
||||
auto response = pipeline.Send(context, request);
|
||||
auto expectedResponseBodySize = std::stoull(response->GetHeaders().at("content-length"));
|
||||
@ -179,7 +213,7 @@ namespace Azure { namespace Core { namespace Test {
|
||||
auto request = Azure::Core::Http::Request(Azure::Core::Http::HttpMethod::Get, host, true);
|
||||
|
||||
// loop sending request
|
||||
for (auto i = 0; i < 20; i++)
|
||||
for (auto i = 0; i < 50; i++)
|
||||
{
|
||||
auto response = pipeline.Send(context, request);
|
||||
auto expectedResponseBodySize = std::stoull(response->GetHeaders().at("content-length"));
|
||||
|
||||
Loading…
Reference in New Issue
Block a user