Skip to content

Commit

Permalink
Allowing post for multiple times
Browse files Browse the repository at this point in the history
  • Loading branch information
COM8 committed Oct 21, 2023
1 parent 5a78cb2 commit d9a1baf
Show file tree
Hide file tree
Showing 3 changed files with 155 additions and 67 deletions.
177 changes: 112 additions & 65 deletions cpr/session.cpp
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
#include "cpr/session.h"

#include <algorithm>
#include <cassert>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <functional>
#include <iostream>
#include <optional>
#include <stdexcept>
#include <string>

Expand Down Expand Up @@ -37,7 +39,7 @@ CURLcode Session::DoEasyPerform() {
return curl_easy_perform(curl_->handle);
}

void Session::SetHeaderInternal() {
void Session::prepareHeader() {
curl_slist* chunk = nullptr;
for (const std::pair<const std::string, std::string>& item : header_) {
std::string header_string = item.first;
Expand Down Expand Up @@ -97,7 +99,7 @@ Session::Session() : curl_(new CurlHolder()) {
curl_easy_setopt(curl_->handle, CURLOPT_NOSIGNAL, 1L);
#endif

#if LIBCURL_VERSION_NUM >= 0x071900 // 7.25.0
#if LIBCURL_VERSION_NUM >= 0x071900 // 7.25.0
curl_easy_setopt(curl_->handle, CURLOPT_TCP_KEEPALIVE, 1L);
#endif
}
Expand All @@ -115,8 +117,11 @@ Response Session::makeDownloadRequest() {
void Session::prepareCommon() {
assert(curl_->handle);

// Set Content:
prepareBodyPayloadOrMultipart();

// Set Header:
SetHeaderInternal();
prepareHeader();

const std::string parametersContent = parameters_.GetContent(*curl_);
if (!parametersContent.empty()) {
Expand Down Expand Up @@ -191,7 +196,7 @@ void Session::prepareCommonDownload() {
assert(curl_->handle);

// Set Header:
SetHeaderInternal();
prepareHeader();

const std::string parametersContent = parameters_.GetContent(*curl_);
if (!parametersContent.empty()) {
Expand Down Expand Up @@ -348,17 +353,19 @@ void Session::SetUserAgent(const UserAgent& ua) {
}

void Session::SetPayload(const Payload& payload) {
hasBodyOrPayload_ = true;
const std::string content = payload.GetContent(*curl_);
curl_easy_setopt(curl_->handle, CURLOPT_POSTFIELDSIZE_LARGE, static_cast<curl_off_t>(content.length()));
curl_easy_setopt(curl_->handle, CURLOPT_COPYPOSTFIELDS, content.c_str());
payload_ = payload;

// Either a body, multipart or a payload is allowed.
body_ = std::nullopt;
multipart_ = std::nullopt;
}

void Session::SetPayload(Payload&& payload) {
hasBodyOrPayload_ = true;
const std::string content = payload.GetContent(*curl_);
curl_easy_setopt(curl_->handle, CURLOPT_POSTFIELDSIZE_LARGE, static_cast<curl_off_t>(content.length()));
curl_easy_setopt(curl_->handle, CURLOPT_COPYPOSTFIELDS, content.c_str());
payload_ = std::move(payload);

// Either a body, multipart or a payload is allowed.
body_ = std::nullopt;
multipart_ = std::nullopt;
}

void Session::SetProxies(const Proxies& proxies) {
Expand All @@ -378,51 +385,19 @@ void Session::SetProxyAuth(const ProxyAuthentication& proxy_auth) {
}

void Session::SetMultipart(const Multipart& multipart) {
// Make sure, we have a empty multipart to start with:
if (curl_->multipart) {
curl_mime_free(curl_->multipart);
}
curl_->multipart = curl_mime_init(curl_->handle);

// Add all multipart pieces:
for (const Part& part : multipart.parts) {
if (part.is_file) {
for (const File& file : part.files) {
curl_mimepart* mimePart = curl_mime_addpart(curl_->multipart);
if (!part.content_type.empty()) {
curl_mime_type(mimePart, part.content_type.c_str());
}

curl_mime_filedata(mimePart, file.filepath.c_str());
curl_mime_name(mimePart, part.name.c_str());

if (file.hasOverridenFilename()) {
curl_mime_filename(mimePart, file.overriden_filename.c_str());
}
}
} else {
curl_mimepart* mimePart = curl_mime_addpart(curl_->multipart);
if (!part.content_type.empty()) {
curl_mime_type(mimePart, part.content_type.c_str());
}
if (part.is_buffer) {
// Do not use formdata, to prevent having to use reinterpreter_cast:
curl_mime_name(mimePart, part.name.c_str());
curl_mime_data(mimePart, part.data, part.datalen);
curl_mime_filename(mimePart, part.value.c_str());
} else {
curl_mime_name(mimePart, part.name.c_str());
curl_mime_data(mimePart, part.value.c_str(), CURL_ZERO_TERMINATED);
}
}
}
multipart_ = multipart;

curl_easy_setopt(curl_->handle, CURLOPT_MIMEPOST, curl_->multipart);
hasBodyOrPayload_ = true;
// Either a body, multipart or a payload is allowed.
body_ = std::nullopt;
payload_ = std::nullopt;
}

void Session::SetMultipart(Multipart&& multipart) {
SetMultipart(multipart);
multipart_ = std::move(multipart);

// Either a body, multipart or a payload is allowed.
body_ = std::nullopt;
payload_ = std::nullopt;
}

void Session::SetRedirect(const Redirect& redirect) {
Expand Down Expand Up @@ -450,15 +425,19 @@ void Session::SetCookies(const Cookies& cookies) {
}

void Session::SetBody(const Body& body) {
hasBodyOrPayload_ = true;
curl_easy_setopt(curl_->handle, CURLOPT_POSTFIELDSIZE_LARGE, static_cast<curl_off_t>(body.str().length()));
curl_easy_setopt(curl_->handle, CURLOPT_POSTFIELDS, body.c_str());
body_ = body;

// Either a body, multipart or a payload is allowed.
payload_ = std::nullopt;
multipart_ = std::nullopt;
}

void Session::SetBody(Body&& body) {
hasBodyOrPayload_ = true;
curl_easy_setopt(curl_->handle, CURLOPT_POSTFIELDSIZE_LARGE, static_cast<curl_off_t>(body.str().length()));
curl_easy_setopt(curl_->handle, CURLOPT_COPYPOSTFIELDS, body.c_str());
body_ = std::move(body);

// Either a body, multipart or a payload is allowed.
payload_ = std::nullopt;
multipart_ = std::nullopt;
}

void Session::SetLowSpeed(const LowSpeed& low_speed) {
Expand Down Expand Up @@ -788,7 +767,7 @@ void Session::PrepareDelete() {
void Session::PrepareGet() {
// In case there is a body or payload for this request, we create a custom GET-Request since a
// GET-Request with body is based on the HTTP RFC **not** a leagal request.
if (hasBodyOrPayload_) {
if (hasBodyOrPayload()) {
curl_easy_setopt(curl_->handle, CURLOPT_NOBODY, 0L);
curl_easy_setopt(curl_->handle, CURLOPT_CUSTOMREQUEST, "GET");
} else {
Expand Down Expand Up @@ -821,7 +800,7 @@ void Session::PreparePost() {
curl_easy_setopt(curl_->handle, CURLOPT_NOBODY, 0L);

// In case there is no body or payload set it to an empty post:
if (hasBodyOrPayload_) {
if (hasBodyOrPayload()) {
curl_easy_setopt(curl_->handle, CURLOPT_CUSTOMREQUEST, nullptr);
} else {
curl_easy_setopt(curl_->handle, CURLOPT_POSTFIELDS, cbs_->readcb_.callback ? nullptr : "");
Expand All @@ -832,7 +811,7 @@ void Session::PreparePost() {

void Session::PreparePut() {
curl_easy_setopt(curl_->handle, CURLOPT_NOBODY, 0L);
if (!hasBodyOrPayload_ && cbs_->readcb_.callback) {
if (!hasBodyOrPayload() && cbs_->readcb_.callback) {
/**
* Yes, this one has to be CURLOPT_POSTFIELDS even if we are performing a PUT request.
* In case we don't set this one, performing a POST-request with PUT won't work.
Expand Down Expand Up @@ -871,9 +850,6 @@ Response Session::Complete(CURLcode curl_error) {
Cookies cookies = util::parseCookies(raw_cookies);
curl_slist_free_all(raw_cookies);

// Reset the has no body property:
hasBodyOrPayload_ = false;

std::string errorMsg = curl_->error.data();
return Response(curl_, std::move(response_string_), std::move(header_string_), std::move(cookies), Error(curl_error, std::move(errorMsg)));
}
Expand Down Expand Up @@ -909,6 +885,77 @@ Response Session::intercept() {
return interceptor->intercept(*this);
}

void Session::prepareBodyPayloadOrMultipart() const {
// Either a body, multipart or a payload is allowed.

if (payload_) {
assert(!body_);
assert(!multipart_);

const std::string content = payload_->GetContent(*curl_);
curl_easy_setopt(curl_->handle, CURLOPT_POSTFIELDSIZE_LARGE, static_cast<curl_off_t>(content.length()));
curl_easy_setopt(curl_->handle, CURLOPT_COPYPOSTFIELDS, content.c_str());
}

if (body_) {
assert(!payload_);
assert(!multipart_);

curl_easy_setopt(curl_->handle, CURLOPT_POSTFIELDSIZE_LARGE, static_cast<curl_off_t>(body_->str().length()));
curl_easy_setopt(curl_->handle, CURLOPT_COPYPOSTFIELDS, body_->c_str());
}

if (multipart_) {
assert(!payload_);
assert(!body_);

// Make sure, we have a empty multipart to start with:
if (curl_->multipart) {
curl_mime_free(curl_->multipart);
}
curl_->multipart = curl_mime_init(curl_->handle);

// Add all multipart pieces:
for (const Part& part : multipart_->parts) {
if (part.is_file) {
for (const File& file : part.files) {
curl_mimepart* mimePart = curl_mime_addpart(curl_->multipart);
if (!part.content_type.empty()) {
curl_mime_type(mimePart, part.content_type.c_str());
}

curl_mime_filedata(mimePart, file.filepath.c_str());
curl_mime_name(mimePart, part.name.c_str());

if (file.hasOverridenFilename()) {
curl_mime_filename(mimePart, file.overriden_filename.c_str());
}
}
} else {
curl_mimepart* mimePart = curl_mime_addpart(curl_->multipart);
if (!part.content_type.empty()) {
curl_mime_type(mimePart, part.content_type.c_str());
}
if (part.is_buffer) {
// Do not use formdata, to prevent having to use reinterpreter_cast:
curl_mime_name(mimePart, part.name.c_str());
curl_mime_data(mimePart, part.data, part.datalen);
curl_mime_filename(mimePart, part.value.c_str());
} else {
curl_mime_name(mimePart, part.name.c_str());
curl_mime_data(mimePart, part.value.c_str(), CURL_ZERO_TERMINATED);
}
}
}

curl_easy_setopt(curl_->handle, CURLOPT_MIMEPOST, curl_->multipart);
}
}

[[nodiscard]] bool Session::hasBodyOrPayload() const {
return payload_ || body_;
}

// clang-format off
void Session::SetOption(const Resolve& resolve) { SetResolve(resolve); }
void Session::SetOption(const std::vector<Resolve>& resolves) { SetResolves(resolves); }
Expand Down
9 changes: 7 additions & 2 deletions include/cpr/session.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include <functional>
#include <future>
#include <memory>
#include <optional>
#include <queue>

#include "cpr/accept_encoding.h"
Expand Down Expand Up @@ -230,8 +231,10 @@ class Session : public std::enable_shared_from_this<Session> {
friend MultiPerform;


bool hasBodyOrPayload_{false};
bool chunkedTransferEncoding_{false};
std::optional<cpr::Payload> payload_;
std::optional<cpr::Body> body_;
std::optional<cpr::Multipart> multipart_;
std::shared_ptr<CurlHolder> curl_;
Url url_;
Parameters parameters_;
Expand Down Expand Up @@ -269,9 +272,11 @@ class Session : public std::enable_shared_from_this<Session> {
Response intercept();
void prepareCommon();
void prepareCommonDownload();
void SetHeaderInternal();
void prepareHeader();
std::shared_ptr<Session> GetSharedPtrFromThis();
CURLcode DoEasyPerform();
void prepareBodyPayloadOrMultipart() const;
[[nodiscard]] bool hasBodyOrPayload() const;
};

template <typename Then>
Expand Down
36 changes: 36 additions & 0 deletions test/session_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,42 @@ bool write_data(std::string /*data*/, intptr_t /*userdata*/) {
return true;
}

TEST(SessionGetTests, GetMultipleTimes) {
Url url{server->GetBaseUrl() + "/hello.html"};
Session session;
session.SetUrl(url);
std::string expected_text{"Hello world!"};

for (size_t i = 0; i < 100; i++) {
Response response = session.Get();
EXPECT_EQ(expected_text, response.text);
EXPECT_EQ(url, response.url);
EXPECT_EQ(std::string{"text/html"}, response.header["content-type"]);
EXPECT_EQ(200, response.status_code);
EXPECT_EQ(ErrorCode::OK, response.error.code);
}
}

TEST(SessionPostTests, PostMultipleTimes) {
Url url{server->GetBaseUrl() + "/url_post.html"};
Session session;
session.SetUrl(url);
session.SetPayload({{"x", "5"}});
std::string expected_text{
"{\n"
" \"x\": 5\n"
"}"};

for (size_t i = 0; i < 100; i++) {
Response response = session.Post();
EXPECT_EQ(expected_text, response.text);
EXPECT_EQ(url, response.url);
EXPECT_EQ(std::string{"application/json"}, response.header["content-type"]);
EXPECT_EQ(201, response.status_code);
EXPECT_EQ(ErrorCode::OK, response.error.code);
}
}

TEST(RedirectTests, TemporaryDefaultRedirectTest) {
Url url{server->GetBaseUrl() + "/temporary_redirect.html"};
Session session;
Expand Down

0 comments on commit d9a1baf

Please sign in to comment.