Refactoring for reading content

This commit is contained in:
yhirose 2019-07-17 08:56:49 -04:00
parent 3541fe8330
commit fccb84f5e8

265
httplib.h
View file

@ -60,6 +60,7 @@ typedef int socket_t;
#endif //_WIN32
#include <assert.h>
#include <atomic>
#include <fcntl.h>
#include <fstream>
#include <functional>
@ -70,7 +71,6 @@ typedef int socket_t;
#include <string>
#include <sys/stat.h>
#include <thread>
#include <atomic>
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
#include <openssl/err.h>
@ -98,7 +98,7 @@ inline const unsigned char *ASN1_STRING_get0_data(const ASN1_STRING *asn1) {
#define CPPHTTPLIB_READ_TIMEOUT_USECOND 0
#define CPPHTTPLIB_REQUEST_URI_MAX_LENGTH 8192
#define CPPHTTPLIB_PAYLOAD_MAX_LENGTH (std::numeric_limits<size_t>::max)()
#define CPPHTTPLIB_RECV_BUFSIZ 4096
#define CPPHTTPLIB_RECV_BUFSIZ size_t(4096u)
namespace httplib {
@ -445,6 +445,76 @@ private:
*/
namespace detail {
#ifdef CPPHTTPLIB_ZLIB_SUPPORT
inline bool can_compress(const std::string &content_type) {
return !content_type.find("text/") || content_type == "image/svg+xml" ||
content_type == "application/javascript" ||
content_type == "application/json" ||
content_type == "application/xml" ||
content_type == "application/xhtml+xml";
}
inline void compress(std::string &content) {
z_stream strm;
strm.zalloc = Z_NULL;
strm.zfree = Z_NULL;
strm.opaque = Z_NULL;
auto ret = deflateInit2(&strm, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 31, 8,
Z_DEFAULT_STRATEGY);
if (ret != Z_OK) { return; }
strm.avail_in = content.size();
strm.next_in = (Bytef *)content.data();
std::string compressed;
const auto bufsiz = 16384;
char buff[bufsiz];
do {
strm.avail_out = bufsiz;
strm.next_out = (Bytef *)buff;
deflate(&strm, Z_FINISH);
compressed.append(buff, bufsiz - strm.avail_out);
} while (strm.avail_out == 0);
content.swap(compressed);
deflateEnd(&strm);
}
inline void decompress(std::string &content) {
z_stream strm;
strm.zalloc = Z_NULL;
strm.zfree = Z_NULL;
strm.opaque = Z_NULL;
// 15 is the value of wbits, which should be at the maximum possible value to
// ensure that any gzip stream can be decoded. The offset of 16 specifies that
// the stream to decompress will be formatted with a gzip wrapper.
auto ret = inflateInit2(&strm, 16 + 15);
if (ret != Z_OK) { return; }
strm.avail_in = content.size();
strm.next_in = (Bytef *)content.data();
std::string decompressed;
const auto bufsiz = 16384;
char buff[bufsiz];
do {
strm.avail_out = bufsiz;
strm.next_out = (Bytef *)buff;
inflate(&strm, Z_NO_FLUSH);
decompressed.append(buff, bufsiz - strm.avail_out);
} while (strm.avail_out == 0);
content.swap(decompressed);
inflateEnd(&strm);
}
#endif
template <class Fn> void split(const char *b, const char *e, char d, Fn fn) {
int i = 0;
int beg = 0;
@ -869,14 +939,18 @@ inline bool read_headers(Stream &strm, Headers &headers) {
return true;
}
inline bool read_content_with_length(Stream &strm, std::string &out, size_t len,
Progress progress) {
out.assign(len, 0);
template <typename T>
inline bool read_content_with_length(Stream &strm, size_t len,
Progress progress, T callback) {
char buf[CPPHTTPLIB_RECV_BUFSIZ];
size_t r = 0;
while (r < len) {
auto n = strm.read(&out[r], len - r);
auto n = strm.read(buf, std::min((len - r), CPPHTTPLIB_RECV_BUFSIZ));
if (n <= 0) { return false; }
callback(buf, n);
r += n;
if (progress) {
@ -891,13 +965,14 @@ inline void skip_content_with_length(Stream &strm, size_t len) {
char buf[CPPHTTPLIB_RECV_BUFSIZ];
size_t r = 0;
while (r < len) {
auto n = strm.read(buf, CPPHTTPLIB_RECV_BUFSIZ);
auto n = strm.read(buf, std::min((len - r), CPPHTTPLIB_RECV_BUFSIZ));
if (n <= 0) { return; }
r += n;
}
}
inline bool read_content_without_length(Stream &strm, std::string &out) {
template <typename T>
inline bool read_content_without_length(Stream &strm, T callback) {
char buf[CPPHTTPLIB_RECV_BUFSIZ];
for (;;) {
auto n = strm.read(buf, CPPHTTPLIB_RECV_BUFSIZ);
@ -906,13 +981,14 @@ inline bool read_content_without_length(Stream &strm, std::string &out) {
} else if (n == 0) {
return true;
}
out.append(buf, n);
callback(buf, n);
}
return true;
}
inline bool read_content_chunked(Stream &strm, std::string &out) {
template <typename T>
inline bool read_content_chunked(Stream &strm, T callback) {
const auto bufsiz = 16;
char buf[bufsiz];
@ -923,8 +999,7 @@ inline bool read_content_chunked(Stream &strm, std::string &out) {
auto chunk_len = std::stoi(reader.ptr(), 0, 16);
while (chunk_len > 0) {
std::string chunk;
if (!read_content_with_length(strm, chunk, chunk_len, nullptr)) {
if (!read_content_with_length(strm, chunk_len, nullptr, callback)) {
return false;
}
@ -932,8 +1007,6 @@ inline bool read_content_chunked(Stream &strm, std::string &out) {
if (strcmp(reader.ptr(), "\r\n")) { break; }
out += chunk;
if (!reader.getline()) { return false; }
chunk_len = std::stoi(reader.ptr(), 0, 16);
@ -947,39 +1020,61 @@ inline bool read_content_chunked(Stream &strm, std::string &out) {
return true;
}
template <typename T>
bool read_content(Stream &strm, T &x, uint64_t payload_max_length,
bool &exceed_payload_max_length,
Progress progress = Progress()) {
if (has_header(x.headers, "Content-Length")) {
auto len = get_header_value_uint64(x.headers, "Content-Length", 0);
if (len == 0) {
const auto &encoding =
get_header_value(x.headers, "Transfer-Encoding", 0, "");
if (!strcasecmp(encoding, "chunked")) {
return read_content_chunked(strm, x.body);
}
inline bool is_chunked_transfer_encoding(const Headers &headers) {
return !strcasecmp(get_header_value(headers, "Transfer-Encoding", 0, ""),
"chunked");
}
template <typename T>
bool read_content(Stream &strm, T &x, uint64_t payload_max_length, int &status,
Progress progress) {
#ifndef CPPHTTPLIB_ZLIB_SUPPORT
if (x.get_header_value("Content-Encoding") == "gzip") {
status = 415;
return false;
}
#endif
auto callback = [&](const char *buf, size_t n) { x.body.append(buf, n); };
auto ret = true;
auto exceed_payload_max_length = false;
if (is_chunked_transfer_encoding(x.headers)) {
ret = read_content_chunked(strm, callback);
} else if (!has_header(x.headers, "Content-Length")) {
ret = read_content_without_length(strm, callback);
} else {
auto len = get_header_value_uint64(x.headers, "Content-Length", 0);
if (len > 0) {
if ((len > payload_max_length) ||
// For 32-bit platform
(sizeof(size_t) < sizeof(uint64_t) &&
len > std::numeric_limits<size_t>::max())) {
exceed_payload_max_length = true;
skip_content_with_length(strm, len);
return false;
ret = false;
} else {
// NOTE: We can remove it if it doesn't give us enough better
// performance.
x.body.reserve(len);
ret = read_content_with_length(strm, len, progress, callback);
}
}
}
return read_content_with_length(strm, x.body, len, progress);
if (ret) {
#ifdef CPPHTTPLIB_ZLIB_SUPPORT
if (x.get_header_value("Content-Encoding") == "gzip") {
detail::decompress(x.body);
}
#endif
} else {
const auto &encoding =
get_header_value(x.headers, "Transfer-Encoding", 0, "");
if (!strcasecmp(encoding, "chunked")) {
return read_content_chunked(strm, x.body);
status = exceed_payload_max_length ? 413 : 400;
}
return read_content_without_length(strm, x.body);
}
return true;
return ret;
}
template <typename T> inline void write_headers(Stream &strm, const T &info) {
@ -1252,76 +1347,6 @@ inline void make_range_header_core(std::string &field, uint64_t value1,
make_range_header_core(field, args...);
}
#ifdef CPPHTTPLIB_ZLIB_SUPPORT
inline bool can_compress(const std::string &content_type) {
return !content_type.find("text/") || content_type == "image/svg+xml" ||
content_type == "application/javascript" ||
content_type == "application/json" ||
content_type == "application/xml" ||
content_type == "application/xhtml+xml";
}
inline void compress(std::string &content) {
z_stream strm;
strm.zalloc = Z_NULL;
strm.zfree = Z_NULL;
strm.opaque = Z_NULL;
auto ret = deflateInit2(&strm, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 31, 8,
Z_DEFAULT_STRATEGY);
if (ret != Z_OK) { return; }
strm.avail_in = content.size();
strm.next_in = (Bytef *)content.data();
std::string compressed;
const auto bufsiz = 16384;
char buff[bufsiz];
do {
strm.avail_out = bufsiz;
strm.next_out = (Bytef *)buff;
deflate(&strm, Z_FINISH);
compressed.append(buff, bufsiz - strm.avail_out);
} while (strm.avail_out == 0);
content.swap(compressed);
deflateEnd(&strm);
}
inline void decompress(std::string &content) {
z_stream strm;
strm.zalloc = Z_NULL;
strm.zfree = Z_NULL;
strm.opaque = Z_NULL;
// 15 is the value of wbits, which should be at the maximum possible value to
// ensure that any gzip stream can be decoded. The offset of 16 specifies that
// the stream to decompress will be formatted with a gzip wrapper.
auto ret = inflateInit2(&strm, 16 + 15);
if (ret != Z_OK) { return; }
strm.avail_in = content.size();
strm.next_in = (Bytef *)content.data();
std::string decompressed;
const auto bufsiz = 16384;
char buff[bufsiz];
do {
strm.avail_out = bufsiz;
strm.next_out = (Bytef *)buff;
inflate(&strm, Z_NO_FLUSH);
decompressed.append(buff, bufsiz - strm.avail_out);
} while (strm.avail_out == 0);
content.swap(decompressed);
inflateEnd(&strm);
}
#endif
#ifdef _WIN32
class WSInit {
public:
@ -1878,26 +1903,14 @@ Server::process_request(Stream &strm, bool last_connection,
// Body
if (req.method == "POST" || req.method == "PUT" || req.method == "PATCH") {
bool exceed_payload_max_length = false;
if (!detail::read_content(strm, req, payload_max_length_,
exceed_payload_max_length)) {
res.status = exceed_payload_max_length ? 413 : 400;
if (!detail::read_content(strm, req, payload_max_length_, res.status,
Progress())) {
write_response(strm, last_connection, req, res);
return !exceed_payload_max_length;
return true;
}
const auto &content_type = req.get_header_value("Content-Type");
if (req.get_header_value("Content-Encoding") == "gzip") {
#ifdef CPPHTTPLIB_ZLIB_SUPPORT
detail::decompress(req.body);
#else
res.status = 415;
write_response(strm, last_connection, req, res);
return true;
#endif
}
if (!content_type.find("application/x-www-form-urlencoded")) {
detail::parse_query_text(req.body, req.params);
} else if (!content_type.find("multipart/form-data")) {
@ -2069,19 +2082,11 @@ inline bool Client::process_request(Stream &strm, Request &req, Response &res,
// Body
if (req.method != "HEAD") {
bool exceed_payload_max_length = false;
int dummy_status;
if (!detail::read_content(strm, res, std::numeric_limits<uint64_t>::max(),
exceed_payload_max_length, req.progress)) {
dummy_status, req.progress)) {
return false;
}
if (res.get_header_value("Content-Encoding") == "gzip") {
#ifdef CPPHTTPLIB_ZLIB_SUPPORT
detail::decompress(res.body);
#else
return false;
#endif
}
}
return true;