Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Using pImpl to avoid the usage of void* #2

Open
wants to merge 6 commits into
base: master
Choose a base branch
from

Conversation

afrizaloky
Copy link

  1. Using void* to hide the real implementation is not recommended because it will stop the compiler from being able to enforce type checking
  2. Add unittest using gtest

Reference

  1. https://en.cppreference.com/w/cpp/language/pimpl

@ammarfaizi2
Copy link
Member

ammarfaizi2 commented Aug 1, 2024

Please split the first commit into smaller manageable commits; each should focus on one thing.

This PR should at least contain 4 commits:

  1. Wrap the CURL pointer.
  2. Add CMake support.
  3. Add unit test.
  4. Add GitHub action.

Don't do too many things at once.

In addition to that:
When applied, each commit should not leave a known broken function. So, the order matters if a commit depends on a previous commit. For example, no (1) can be reordered freely, but (2) and (3) can't, as (2) must come first because the unit test needs CMake.

@@ -143,18 +142,15 @@ std::string Lokal::__curl(std::string path, std::string method,
curl_easy_setopt(ch, CURLOPT_HEADERDATA, &data);
curl_easy_setopt(ch, CURLOPT_USERAGENT, "Lokal Go - github.com/lokal-so/lokal-go");
curl_easy_setopt(ch, CURLOPT_VERBOSE, 0L);
curl_easy_setopt(ch, CURLOPT_HTTPHEADER, headers);
curl_easy_setopt(ch, CURLOPT_HTTPHEADER, headers.get());
Copy link
Member

@ammarfaizi2 ammarfaizi2 Aug 1, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This header wrapping thing is gruesome. Plus, I don't see the need to
do that. The slist_append and slist_free here are never touched by
the exception.

It costs extra complexity and an indirect call to the deleter without
code improvement.

If you really want to wrap it up, create the whole CURL class that
handles the header addition. Like:

class Lokal {
private:
	CURLImpl c_;
};


std::string Lokal::curl(/* params */)
{
	c_->setURL(url);
	c_->addHeader("Content-Type: application/json");
	c_->setOpt(/* ... */)
}

... and then when the Lokal object is destroyed, the CURLImpl will
also be destroyed cleanly withouth the need for slist_free and
slist_append from the caller (do those internally in CURLImpl).

Something like that is the expected improvement, not just blindly
wrapping everything with unique_ptr. Otherwise, keep it as is in
the C fashioned way.

@ammarfaizi2
Copy link
Member

ammarfaizi2 commented Aug 1, 2024

@afrizaloky if you want to wrap the CURL pointer. Please do the following requirements.

[ Just for fun, I asked ChatGPT and it yields a nice result. But I am not going to use ChatGPT's code. ]

Create a curl class named CurlImpl in C++ that wraps the libcurl
function from curl/curl.h header.

Key features:

  1. f: setURL
  2. f: addHeader
  3. f: setMethod
  4. f: setReqBody
  5. f: setUserAgent
  6. f: execute (throws an exception when error)
  7. f: getResHeader (returns vec<string, string>)
  8. f: getResBody (returns string)

Additional notes:

  1. The CurlImpl object must be reusable for multiple HTTP requests.

  2. Keep the connection alive as long as possible (only destroy CURL *
    pointer in the destructor).

  3. The default method is GET.

  4. Each execute() call must reset method, req body, and req headers
    (except for CURLOPT_USERAGENT, keep it as is).

[ Edit: Fix (7) and (8) names. ]

1. Using void* to hide the real implementation is not recommended because it will stop the compiler from being able to enforce type checking
2. Create CurlWrapper to wrap the implementation of cURL
3. Create CurlImpl to hide the implementation of CurlWrapper, so the main header doesnt need to include the header
Copy link
Member

@ammarfaizi2 ammarfaizi2 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please follow the coding style around the code.

Indentation: Tab
Tab width: 8

Comment on lines 6 to 16
namespace LokalSo {

static void ltrim(std::string &s) {
s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](unsigned char ch) {
return !std::isspace(ch);
}));
}

static void rtrim(std::string &s) {
s.erase(std::find_if(s.rbegin(), s.rend(), [](unsigned char ch) {
return !std::isspace(ch);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't add indentation inside the namespace.

Comment on lines 65 to 82

CurlWrapper::CurlWrapper() {
this->ch = curl_easy_init();
curl_easy_setopt(this->ch, CURLOPT_WRITEFUNCTION, &res_body_callback);
curl_easy_setopt(this->ch, CURLOPT_WRITEDATA, this);

curl_easy_setopt(this->ch, CURLOPT_HEADERFUNCTION, &res_hdr_callback);
curl_easy_setopt(this->ch, CURLOPT_HEADERDATA, this);

}
CurlWrapper::~CurlWrapper() {
if(this->ch) {
curl_easy_cleanup(this->ch);
}
if(this->sList) {
curl_slist_free_all(this->sList);
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fix the bad indentation and add an empty line between function definitions.

Comment on lines 165 to 173


curl_easy_reset(this->ch);
// curl_easy_setopt(this->ch, CURLOPT_URL, nullptr);
// curl_easy_setopt(this->ch, CURLOPT_POST, 0L);
// curl_easy_setopt(this->ch, CURLOPT_POSTFIELDSIZE, 0);
// curl_easy_setopt(this->ch, CURLOPT_POSTFIELDS, nullptr);
// curl_easy_setopt(ch, CURLOPT_HTTPHEADER, nullptr);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Kill these unnecessary comments.

Comment on lines 46 to 60
static size_t res_hdr_callback(void *contents, size_t size, size_t nmemb,
void *userp) {
CurlWrapper* mem = static_cast<CurlWrapper*>(userp);
size_t full_size = size * nmemb;

std::string headerStr(static_cast<char *>(contents), full_size);

auto colon = headerStr.find(':');
if (colon != std::string::npos) {
std::string key = headerStr.substr(0, colon);
std::string value = headerStr.substr(colon + 1);
// Trim whitespace from key and value
trim(key);
trim(value);
mem->addResponseHeader(key, value);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Apart from the trim, make the key lowercase (only for the response header).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants