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 req
* @param timeout In seconds. If the response is not received within the * @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. * The zero value by default disables the timeout.
* *
* @return internal::HttpRespAwaiter. Await on it to get the response * @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 #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( inline void internal::HttpRespAwaiter::await_suspend(
std::coroutine_handle<> handle) std::coroutine_handle<> handle)
{ {
@ -353,17 +376,11 @@ inline void internal::HttpRespAwaiter::await_suspend(
assert(req_ != nullptr); assert(req_ != nullptr);
client_->sendRequest( client_->sendRequest(
req_, req_,
[handle = std::move(handle), this](ReqResult result, [handle, this](ReqResult result, const HttpResponsePtr &resp) {
const HttpResponsePtr &resp) {
if (result == ReqResult::Ok) if (result == ReqResult::Ok)
setValue(resp); setValue(resp);
else else
{ setException(std::make_exception_ptr(HttpException(result)));
std::stringstream ss;
ss << result;
setException(
std::make_exception_ptr(std::runtime_error(ss.str())));
}
handle.resume(); handle.resume();
}, },
timeout_); 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 /// Create a normal response with a status code of 200ok and a content type
/// of text/html. /// of text/html.
static HttpResponsePtr newHttpResponse(); 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. /// Create a response which returns a 404 page.
static HttpResponsePtr newNotFoundResponse(); static HttpResponsePtr newNotFoundResponse();
/// Create a response which returns a json object. Its content type is set /// 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 /// Determine if the string is an integer
DROGON_EXPORT bool isInteger(const std::string &str); 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 /// Generate random a string
/** /**
* @param length The string length * @param length The string length
@ -98,15 +101,46 @@ DROGON_EXPORT std::set<std::string> splitStringToSet(
/// Get UUID string. /// Get UUID string.
DROGON_EXPORT std::string getUuid(); 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. /// Encode the string to base64 format.
DROGON_EXPORT std::string base64Encode(const unsigned char *bytes_to_encode, DROGON_EXPORT std::string base64Encode(const unsigned char *bytes_to_encode,
unsigned int in_len, 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. /// 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( DROGON_EXPORT std::vector<char> base64DecodeToVector(
const std::string &encoded_string); string_view encoded_string);
/// Check if the string need decoding /// Check if the string need decoding
DROGON_EXPORT bool needUrlDecoding(const char *begin, const char *end); DROGON_EXPORT bool needUrlDecoding(const char *begin, const char *end);

View File

@ -71,6 +71,14 @@ HttpResponsePtr HttpResponse::newHttpResponse()
return res; 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) HttpResponsePtr HttpResponse::newHttpJsonResponse(const Json::Value &data)
{ {
auto res = std::make_shared<HttpResponseImpl>(k200OK, CT_APPLICATION_JSON); auto res = std::make_shared<HttpResponseImpl>(k200OK, CT_APPLICATION_JSON);

View File

@ -149,6 +149,14 @@ bool isInteger(const std::string &str)
return true; return true;
} }
bool isBase64(const std::string &str)
{
for (auto c : str)
if (!isBase64(c))
return false;
return true;
}
std::string genRandomString(int length) std::string genRandomString(int length)
{ {
static const char char_space[] = static const char char_space[] =
@ -387,11 +395,18 @@ std::string getUuid()
#endif #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, std::string base64Encode(const unsigned char *bytes_to_encode,
unsigned int in_len, unsigned int in_len,
bool url_safe) bool url_safe,
bool padded)
{ {
std::string ret; std::string ret;
ret.reserve(base64EncodedLength(in_len, padded));
int i = 0; int i = 0;
unsigned char char_array_3[3]; unsigned char char_array_3[3];
unsigned char char_array_4[4]; 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_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
char_array_4[3] = char_array_3[2] & 0x3f; 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]]; ret += charSet[char_array_4[j]];
while ((i++ < 3)) if (padded)
ret += '='; while ((++i < 4))
ret += '=';
} }
return 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(); auto in_len = encoded_string.size();
int i = 0; int i = 0;
int in_{0}; int in_{0};
char char_array_4[4], char_array_3[3]; char char_array_4[4], char_array_3[3];
std::vector<char> ret; std::vector<char> ret;
ret.reserve(in_len); ret.reserve(base64DecodedLength(in_len));
while (in_len-- && (encoded_string[in_] != '=') && while (in_len-- && (encoded_string[in_] != '='))
isBase64(encoded_string[in_]))
{ {
if (!isBase64(encoded_string[in_]))
{
++in_;
continue;
}
char_array_4[i++] = encoded_string[in_]; char_array_4[i++] = encoded_string[in_];
++in_; ++in_;
if (i == 4) 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_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3]; 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]); ret.push_back(char_array_3[j]);
} }
return ret; return ret;
} }
std::string base64Decode(const std::string &encoded_string) std::string base64Decode(string_view encoded_string)
{ {
auto in_len = encoded_string.size(); auto in_len = encoded_string.size();
int i = 0; int i = 0;
int in_{0}; int in_{0};
unsigned char char_array_4[4], char_array_3[3]; unsigned char char_array_4[4], char_array_3[3];
std::string ret; std::string ret;
ret.reserve(base64DecodedLength(in_len));
while (in_len-- && (encoded_string[in_] != '=') && while (in_len-- && (encoded_string[in_] != '='))
isBase64(encoded_string[in_]))
{ {
if (!isBase64(encoded_string[in_]))
{
++in_;
continue;
}
char_array_4[i++] = encoded_string[in_]; char_array_4[i++] = encoded_string[in_];
++in_; ++in_;
if (i == 4) 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_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3]; 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]; ret += char_array_3[j];
} }

View File

@ -5,12 +5,34 @@
DROGON_TEST(Base64) DROGON_TEST(Base64)
{ {
std::string in{"drogon framework"}; std::string in{"drogon framework"};
auto encoded = drogon::utils::base64Encode((const unsigned char *)in.data(), auto encoded = drogon::utils::base64Encode(in);
(unsigned int)in.length());
auto decoded = drogon::utils::base64Decode(encoded); auto decoded = drogon::utils::base64Decode(encoded);
CHECK(encoded == "ZHJvZ29uIGZyYW1ld29yaw=="); CHECK(encoded == "ZHJvZ29uIGZyYW1ld29yaw==");
CHECK(decoded == in); 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) SUBSECTION(LongString)
{ {
std::string in; std::string in;
@ -19,12 +41,9 @@ DROGON_TEST(Base64)
{ {
in.append(1, char(i)); in.append(1, char(i));
} }
auto out = drogon::utils::base64Encode((const unsigned char *)in.data(), auto out = drogon::utils::base64Encode(in);
(unsigned int)in.length());
auto out2 = drogon::utils::base64Decode(out); auto out2 = drogon::utils::base64Decode(out);
auto encoded = auto encoded = drogon::utils::base64Encode(in);
drogon::utils::base64Encode((const unsigned char *)in.data(),
(unsigned int)in.length());
auto decoded = drogon::utils::base64Decode(encoded); auto decoded = drogon::utils::base64Decode(encoded);
CHECK(decoded == in); CHECK(decoded == in);
} }
@ -32,15 +51,21 @@ DROGON_TEST(Base64)
SUBSECTION(URLSafe) SUBSECTION(URLSafe)
{ {
std::string in{"drogon framework"}; std::string in{"drogon framework"};
auto encoded = auto encoded = drogon::utils::base64Encode(in, true);
drogon::utils::base64Encode((const unsigned char *)in.data(),
(unsigned int)in.length(),
true);
auto decoded = drogon::utils::base64Decode(encoded); auto decoded = drogon::utils::base64Decode(encoded);
CHECK(encoded == "ZHJvZ29uIGZyYW1ld29yaw=="); CHECK(encoded == "ZHJvZ29uIGZyYW1ld29yaw==");
CHECK(decoded == in); 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) SUBSECTION(LongURLSafe)
{ {
std::string in; std::string in;
@ -49,10 +74,7 @@ DROGON_TEST(Base64)
{ {
in.append(1, char(i)); in.append(1, char(i));
} }
auto encoded = auto encoded = drogon::utils::base64Encode(in, true);
drogon::utils::base64Encode((const unsigned char *)in.data(),
(unsigned int)in.length(),
true);
auto decoded = drogon::utils::base64Decode(encoded); auto decoded = drogon::utils::base64Decode(encoded);
CHECK(decoded == in); CHECK(decoded == in);
} }