Add new type answer to hold status, headers and body of a request.

This commit is contained in:
tastytea 2020-11-07 23:46:14 +01:00
parent 4a64bd5300
commit 63a14867c3
Signed by: tastytea
GPG Key ID: CFC39497F1B26E07
8 changed files with 222 additions and 22 deletions

View File

@ -10,6 +10,7 @@ project(curl_wrapper
LANGUAGES CXX)
find_package(CURL 7.52 REQUIRED)
find_package(Boost 1.54 REQUIRED COMPONENTS regex)
add_subdirectory(src)

View File

@ -18,5 +18,7 @@ else()
target_link_libraries(${PROJECT_NAME} PUBLIC ${CURL_LIBRARIES})
endif()
target_link_libraries(${PROJECT_NAME} PRIVATE Boost::regex)
target_include_directories(${PROJECT_NAME}
PUBLIC "$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/src>")

View File

@ -16,6 +16,8 @@
#include "curl_wrapper.hpp"
#include "types.hpp"
#include <curl/curl.h>
#include <atomic>
@ -92,7 +94,7 @@ void CURLWrapper::set_useragent(string_view useragent)
check(curl_easy_setopt(_connection, CURLOPT_USERAGENT, useragent.data()));
}
string CURLWrapper::make_http_request(http_method method, string_view uri)
answer CURLWrapper::make_http_request(http_method method, string_view uri)
{
_buffer_headers.clear();
_buffer_body.clear();
@ -171,10 +173,8 @@ string CURLWrapper::make_http_request(http_method method, string_view uri)
long http_status{0}; // NOLINT(google-runtime-int)
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
check(curl_easy_getinfo(_connection, CURLINFO_RESPONSE_CODE, &http_status));
// status code is in http_status, headers are in _buffer_headers, body is in
// _buffer_body.
// TODO: Communicate the above.
return _buffer_body;
return {static_cast<std::uint16_t>(http_status), _buffer_headers,
_buffer_body};
}
size_t CURLWrapper::writer_body(char *data, size_t size, size_t nmemb)

View File

@ -17,7 +17,9 @@
#ifndef CURL_WRAPPER_HPP
#define CURL_WRAPPER_HPP
#include "curl/curl.h"
#include "types.hpp"
#include <curl/curl.h>
#include <exception>
#include <string>
@ -29,16 +31,11 @@ namespace curl_wrapper
using std::string;
using std::string_view;
enum class http_method
{
DELETE,
GET,
HEAD,
PATCH,
POST,
PUT
};
/*!
* @brief Light wrapper atound libcurl.
*
* @since INSERT_VERSION
*/
class CURLWrapper
{
public:
@ -69,8 +66,9 @@ public:
/*!
* @brief Cleans up curl and connection.
*
* May call `curl_global_cleanup`, which is not thread-safe. For more
* information consult [curl_global_cleanup(3)]
* Calls `curl_global_cleanup`, which is not thread-safe, when the last
* instance of CURLWrapper is destroyed. For more information consult
* [curl_global_cleanup(3)]
* (https://curl.haxx.se/libcurl/c/curl_global_cleanup.html).
*
* @since INSERT_VERSION
@ -92,7 +90,7 @@ public:
*
* @since INSERT_VERSION
*/
inline CURL *get_curl_easy_handle()
[[nodiscard]] inline CURL *get_curl_easy_handle() const
{
return _connection;
}
@ -156,11 +154,11 @@ public:
* @param method The HTTP method.
* @param uri The full URI.
*
* @return The body of the page.
* @return The status code, headers and body of the page.
*
* @since INSERT_VERSION
*/
[[nodiscard]] string make_http_request(http_method method, string_view uri);
[[nodiscard]] answer make_http_request(http_method method, string_view uri);
private:
CURL *_connection;

43
src/types.cpp Normal file
View File

@ -0,0 +1,43 @@
/* This file is part of curl_wrapper.
* Copyright © 2020 tastytea <tastytea@tastytea.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "types.hpp"
#include <boost/regex.hpp>
#include <string>
#include <string_view>
namespace curl_wrapper
{
std::string answer::get_header(const std::string_view field) const
{
const boost::regex re_field(string("^") + field.data()
+ R"(:\s+([^\r\n]+))",
boost::regex::icase);
boost::cmatch match;
boost::regex_search(headers.c_str(), match, re_field);
if (match[1].matched)
{
return match[1].str();
}
return {};
}
} // namespace curl_wrapper

105
src/types.hpp Normal file
View File

@ -0,0 +1,105 @@
/* This file is part of curl_wrapper.
* Copyright © 2020 tastytea <tastytea@tastytea.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef CURL_WRAPPER_TYPES_HPP
#define CURL_WRAPPER_TYPES_HPP
#include <cstdint>
#include <ostream>
#include <string>
#include <string_view>
namespace curl_wrapper
{
using std::ostream;
using std::string;
using std::string_view;
/*!
* @brief The HTTP method.
*
* @since INSERT_VERSION
*/
enum class http_method
{
DELETE,
GET,
HEAD,
PATCH,
POST,
PUT
};
/*!
* @brief Return type for network requests.
*
* Currently only HTTP is considered.
*
* @since INSERT_VERSION
*/
struct answer
{
std::uint16_t status{0};
string headers;
string body;
/*!
* @brief Returns true if #status is 200.
*
* @since INSERT_VERSION
*/
[[nodiscard]] inline explicit operator bool() const
{
return (status == 200);
}
/*!
* @brief Returns #body as const std::string_view.
*
* @since INSERT_VERSION
*/
[[nodiscard]] inline explicit operator string_view() const
{
return body;
}
/*!
* @brief Returns #body as std::ostream.
*
* @since INSERT_VERSION
*/
inline friend ostream &operator<<(ostream &out, const answer &answer)
{
out << answer.body;
return out;
}
/*!
* @brief Returns the value of a header field.
*
* @param field Case insensitive, uses default locale.
*
* @return The value of the header field as std::string or {} if not found.
*
* @since INSERT_VERSION
*/
[[nodiscard]] string get_header(string_view field) const;
};
} // namespace curl_wrapper
#endif // CURL_WRAPPER_TYPES_HPP

View File

@ -22,7 +22,7 @@ SCENARIO("HTTP GET", "[http]")
try
{
CURLWrapper curl;
answer = curl.make_http_request(http_method::GET, uri);
answer = curl.make_http_request(http_method::GET, uri).body;
}
catch (const std::exception &e)
{

51
tests/test_headers.cpp Normal file
View File

@ -0,0 +1,51 @@
#include "types.hpp"
#include <catch.hpp>
#include <exception>
#include <string>
namespace curl_wrapper
{
using std::string;
SCENARIO("Extract header")
{
answer ret;
ret.headers = R"(HTTP/1.1 200 OK
Server: nginx/1.18.0
Date: Sat, 07 Nov 2020 22:26:13 GMT
Content-Type: application/rss+xml; charset=utf-8
Connection: keep-alive
Keep-Alive: timeout=20
Expires: Sat, 07 Nov 2020 22:56:13 GMT
Cache-Control: max-age=1800
X-Cache: HIT
)";
const string searchfor{"cache-control"};
bool exception = false;
string value;
WHEN("We search for " + searchfor)
{
try
{
value = ret.get_header(searchfor);
}
catch (const std::exception &e)
{
exception = true;
}
THEN("No exception is thrown")
AND_THEN("The value is successfully extracted")
{
REQUIRE_FALSE(exception);
REQUIRE(value == "max-age=1800");
}
}
}
} // namespace curl_wrapper