Compare commits

...

3 Commits

Author SHA1 Message Date
Muhammad
61073b4f74
Base64 improvements (#1635) 2023-06-24 18:09:08 +08:00
Nitromelon
eea916315e
Throw custom exception in HttpClient (#1641) 2023-06-24 18:05:39 +08:00
fantasy-peak
8e4474bf4c
Add overload function of newHttpResponse (#1646) 2023-06-24 18:04:38 +08:00
6 changed files with 165 additions and 40 deletions

View File

@ -151,7 +151,7 @@ class DROGON_EXPORT HttpClient : public trantor::NonCopyable
*
* @param req
* @param timeout In seconds. If the response is not received within the
* timeout, A `std::runtime_error` with the message "Timeout" is thrown.
* timeout, A `drogon::HttpException` with `ReqResult::Timeout` is thrown.
* The zero value by default disables the timeout.
*
* @return internal::HttpRespAwaiter. Await on it to get the response
@ -346,6 +346,29 @@ class DROGON_EXPORT HttpClient : public trantor::NonCopyable
};
#ifdef __cpp_impl_coroutine
class HttpException : public std::exception
{
public:
HttpException() = delete;
explicit HttpException(ReqResult res)
: resultCode_(res), message_(to_string_view(res))
{
}
const char *what() const noexcept override
{
return message_.data();
}
ReqResult code() const
{
return resultCode_;
}
private:
ReqResult resultCode_;
std::string_view message_;
};
inline void internal::HttpRespAwaiter::await_suspend(
std::coroutine_handle<> handle)
{
@ -353,17 +376,11 @@ inline void internal::HttpRespAwaiter::await_suspend(
assert(req_ != nullptr);
client_->sendRequest(
req_,
[handle = std::move(handle), this](ReqResult result,
const HttpResponsePtr &resp) {
[handle, this](ReqResult result, const HttpResponsePtr &resp) {
if (result == ReqResult::Ok)
setValue(resp);
else
{
std::stringstream ss;
ss << result;
setException(
std::make_exception_ptr(std::runtime_error(ss.str())));
}
setException(std::make_exception_ptr(HttpException(result)));
handle.resume();
},
timeout_);

View File

@ -353,6 +353,9 @@ class DROGON_EXPORT HttpResponse
/// Create a normal response with a status code of 200ok and a content type
/// of text/html.
static HttpResponsePtr newHttpResponse();
/// Create a response with a status code and a content type
static HttpResponsePtr newHttpResponse(HttpStatusCode code,
ContentType type);
/// Create a response which returns a 404 page.
static HttpResponsePtr newNotFoundResponse();
/// Create a response which returns a json object. Its content type is set

View File

@ -60,6 +60,9 @@ namespace utils
/// Determine if the string is an integer
DROGON_EXPORT bool isInteger(const std::string &str);
/// Determine if the string is base64 encoded
DROGON_EXPORT bool isBase64(const std::string &str);
/// Generate random a string
/**
* @param length The string length
@ -98,15 +101,46 @@ DROGON_EXPORT std::set<std::string> splitStringToSet(
/// Get UUID string.
DROGON_EXPORT std::string getUuid();
/// Get the encoded length of base64.
DROGON_EXPORT size_t base64EncodedLength(unsigned int in_len,
bool padded = true);
/// Encode the string to base64 format.
DROGON_EXPORT std::string base64Encode(const unsigned char *bytes_to_encode,
unsigned int in_len,
bool url_safe = false);
bool url_safe = false,
bool padded = true);
/// Encode the string to base64 format.
inline std::string base64Encode(string_view data,
bool url_safe = false,
bool padded = true)
{
return base64Encode((unsigned char *)data.data(),
data.size(),
url_safe,
padded);
}
/// Encode the string to base64 format with no padding.
DROGON_EXPORT std::string base64EncodeUnpadded(
const unsigned char *bytes_to_encode,
unsigned int in_len,
bool url_safe = false);
/// Encode the string to base64 format with no padding.
inline std::string base64EncodeUnpadded(string_view data, bool url_safe = false)
{
return base64Encode(data, url_safe, false);
}
/// Get the decoded length of base64.
DROGON_EXPORT size_t base64DecodedLength(unsigned int in_len);
/// Decode the base64 format string.
DROGON_EXPORT std::string base64Decode(const std::string &encoded_string);
DROGON_EXPORT std::string base64Decode(string_view encoded_string);
DROGON_EXPORT std::vector<char> base64DecodeToVector(
const std::string &encoded_string);
string_view encoded_string);
/// Check if the string need decoding
DROGON_EXPORT bool needUrlDecoding(const char *begin, const char *end);

View File

@ -71,6 +71,14 @@ HttpResponsePtr HttpResponse::newHttpResponse()
return res;
}
HttpResponsePtr HttpResponse::newHttpResponse(HttpStatusCode code,
ContentType type)
{
auto res = std::make_shared<HttpResponseImpl>(code, type);
doResponseCreateAdvices(res);
return res;
}
HttpResponsePtr HttpResponse::newHttpJsonResponse(const Json::Value &data)
{
auto res = std::make_shared<HttpResponseImpl>(k200OK, CT_APPLICATION_JSON);

View File

@ -149,6 +149,14 @@ bool isInteger(const std::string &str)
return true;
}
bool isBase64(const std::string &str)
{
for (auto c : str)
if (!isBase64(c))
return false;
return true;
}
std::string genRandomString(int length)
{
static const char char_space[] =
@ -387,11 +395,18 @@ std::string getUuid()
#endif
}
size_t base64EncodedLength(unsigned int in_len, bool padded)
{
return padded ? ((in_len + 3 - 1) / 3) * 4 : (in_len * 8 + 6 - 1) / 6;
}
std::string base64Encode(const unsigned char *bytes_to_encode,
unsigned int in_len,
bool url_safe)
bool url_safe,
bool padded)
{
std::string ret;
ret.reserve(base64EncodedLength(in_len, padded));
int i = 0;
unsigned char char_array_3[3];
unsigned char char_array_4[4];
@ -428,27 +443,45 @@ std::string base64Encode(const unsigned char *bytes_to_encode,
((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
char_array_4[3] = char_array_3[2] & 0x3f;
for (int j = 0; (j < i + 1); ++j)
for (int j = 0; (j <= i); ++j)
ret += charSet[char_array_4[j]];
while ((i++ < 3))
ret += '=';
if (padded)
while ((++i < 4))
ret += '=';
}
return ret;
}
std::vector<char> base64DecodeToVector(const std::string &encoded_string)
std::string base64EncodeUnpadded(const unsigned char *bytes_to_encode,
unsigned int in_len,
bool url_safe)
{
return base64Encode(bytes_to_encode, in_len, url_safe, false);
}
size_t base64DecodedLength(unsigned int in_len)
{
return (in_len * 3) / 4;
}
std::vector<char> base64DecodeToVector(string_view encoded_string)
{
auto in_len = encoded_string.size();
int i = 0;
int in_{0};
char char_array_4[4], char_array_3[3];
std::vector<char> ret;
ret.reserve(in_len);
ret.reserve(base64DecodedLength(in_len));
while (in_len-- && (encoded_string[in_] != '=') &&
isBase64(encoded_string[in_]))
while (in_len-- && (encoded_string[in_] != '='))
{
if (!isBase64(encoded_string[in_]))
{
++in_;
continue;
}
char_array_4[i++] = encoded_string[in_];
++in_;
if (i == 4)
@ -486,24 +519,31 @@ std::vector<char> base64DecodeToVector(const std::string &encoded_string)
((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];
for (int j = 0; (j < i - 1); ++j)
--i;
for (int j = 0; (j < i); ++j)
ret.push_back(char_array_3[j]);
}
return ret;
}
std::string base64Decode(const std::string &encoded_string)
std::string base64Decode(string_view encoded_string)
{
auto in_len = encoded_string.size();
int i = 0;
int in_{0};
unsigned char char_array_4[4], char_array_3[3];
std::string ret;
ret.reserve(base64DecodedLength(in_len));
while (in_len-- && (encoded_string[in_] != '=') &&
isBase64(encoded_string[in_]))
while (in_len-- && (encoded_string[in_] != '='))
{
if (!isBase64(encoded_string[in_]))
{
++in_;
continue;
}
char_array_4[i++] = encoded_string[in_];
++in_;
if (i == 4)
@ -540,7 +580,8 @@ std::string base64Decode(const std::string &encoded_string)
((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];
for (int j = 0; (j < i - 1); ++j)
--i;
for (int j = 0; (j < i); ++j)
ret += char_array_3[j];
}

View File

@ -5,12 +5,34 @@
DROGON_TEST(Base64)
{
std::string in{"drogon framework"};
auto encoded = drogon::utils::base64Encode((const unsigned char *)in.data(),
(unsigned int)in.length());
auto encoded = drogon::utils::base64Encode(in);
auto decoded = drogon::utils::base64Decode(encoded);
CHECK(encoded == "ZHJvZ29uIGZyYW1ld29yaw==");
CHECK(decoded == in);
SUBSECTION(InvalidChars)
{
auto decoded =
drogon::utils::base64Decode("ZHJvZ2*9uIGZy**YW1ld2***9yaw*=*=");
CHECK(decoded == in);
}
SUBSECTION(InvalidCharsNoPadding)
{
auto decoded =
drogon::utils::base64Decode("ZHJvZ2*9uIGZy**YW1ld2***9yaw**");
CHECK(decoded == in);
}
SUBSECTION(Unpadded)
{
std::string in{"drogon framework"};
auto encoded = drogon::utils::base64EncodeUnpadded(in);
auto decoded = drogon::utils::base64Decode(encoded);
CHECK(encoded == "ZHJvZ29uIGZyYW1ld29yaw");
CHECK(decoded == in);
}
SUBSECTION(LongString)
{
std::string in;
@ -19,12 +41,9 @@ DROGON_TEST(Base64)
{
in.append(1, char(i));
}
auto out = drogon::utils::base64Encode((const unsigned char *)in.data(),
(unsigned int)in.length());
auto out = drogon::utils::base64Encode(in);
auto out2 = drogon::utils::base64Decode(out);
auto encoded =
drogon::utils::base64Encode((const unsigned char *)in.data(),
(unsigned int)in.length());
auto encoded = drogon::utils::base64Encode(in);
auto decoded = drogon::utils::base64Decode(encoded);
CHECK(decoded == in);
}
@ -32,15 +51,21 @@ DROGON_TEST(Base64)
SUBSECTION(URLSafe)
{
std::string in{"drogon framework"};
auto encoded =
drogon::utils::base64Encode((const unsigned char *)in.data(),
(unsigned int)in.length(),
true);
auto encoded = drogon::utils::base64Encode(in, true);
auto decoded = drogon::utils::base64Decode(encoded);
CHECK(encoded == "ZHJvZ29uIGZyYW1ld29yaw==");
CHECK(decoded == in);
}
SUBSECTION(UnpaddedURLSafe)
{
std::string in{"drogon framework"};
auto encoded = drogon::utils::base64EncodeUnpadded(in, true);
auto decoded = drogon::utils::base64Decode(encoded);
CHECK(encoded == "ZHJvZ29uIGZyYW1ld29yaw");
CHECK(decoded == in);
}
SUBSECTION(LongURLSafe)
{
std::string in;
@ -49,10 +74,7 @@ DROGON_TEST(Base64)
{
in.append(1, char(i));
}
auto encoded =
drogon::utils::base64Encode((const unsigned char *)in.data(),
(unsigned int)in.length(),
true);
auto encoded = drogon::utils::base64Encode(in, true);
auto decoded = drogon::utils::base64Decode(encoded);
CHECK(decoded == in);
}