mirror of
https://github.com/yhirose/cpp-httplib
synced 2024-11-21 06:26:02 -07:00
Add gzip support. resolved #11
This commit is contained in:
parent
d1f903fc58
commit
1d5fbe6a5b
5 changed files with 128 additions and 14 deletions
14
README.md
14
README.md
|
@ -163,6 +163,20 @@ SSLServer svr("./cert.pem", "./key.pem");
|
|||
SSLClient cli("localhost", 8080);
|
||||
```
|
||||
|
||||
Zlib Support
|
||||
------------
|
||||
|
||||
'gzip' compression is available with `CPPHTTPLIB_ZLIB_SUPPORT`.
|
||||
|
||||
The server applies gzip compression to the following MIME type contents:
|
||||
|
||||
* all text types
|
||||
* image/svg+xml
|
||||
* application/javascript
|
||||
* application/json
|
||||
* application/xml
|
||||
* application/xhtml+xml
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
|
|
|
@ -2,23 +2,24 @@
|
|||
CC = clang++
|
||||
CFLAGS = -std=c++14 -I.. -Wall -Wextra -lpthread
|
||||
#OPENSSL_SUPPORT = -DCPPHTTPLIB_OPENSSL_SUPPORT -I/usr/local/opt/openssl/include -L/usr/local/opt/openssl/lib -lssl -lcrypto
|
||||
ZLIB_SUPPORT = -DCPPHTTPLIB_ZLIB_SUPPORT -lz
|
||||
|
||||
all: server client hello simplesvr benchmark
|
||||
|
||||
server : server.cc ../httplib.h Makefile
|
||||
$(CC) -o server $(CFLAGS) server.cc $(OPENSSL_SUPPORT)
|
||||
$(CC) -o server $(CFLAGS) server.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT)
|
||||
|
||||
client : client.cc ../httplib.h Makefile
|
||||
$(CC) -o client $(CFLAGS) client.cc $(OPENSSL_SUPPORT)
|
||||
$(CC) -o client $(CFLAGS) client.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT)
|
||||
|
||||
hello : hello.cc ../httplib.h Makefile
|
||||
$(CC) -o hello $(CFLAGS) hello.cc $(OPENSSL_SUPPORT)
|
||||
$(CC) -o hello $(CFLAGS) hello.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT)
|
||||
|
||||
simplesvr : simplesvr.cc ../httplib.h Makefile
|
||||
$(CC) -o simplesvr $(CFLAGS) simplesvr.cc $(OPENSSL_SUPPORT)
|
||||
$(CC) -o simplesvr $(CFLAGS) simplesvr.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT)
|
||||
|
||||
benchmark : benchmark.cc ../httplib.h Makefile
|
||||
$(CC) -o benchmark $(CFLAGS) benchmark.cc $(OPENSSL_SUPPORT)
|
||||
$(CC) -o benchmark $(CFLAGS) benchmark.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT)
|
||||
|
||||
pem:
|
||||
openssl genrsa 2048 > key.pem
|
||||
|
|
75
httplib.h
75
httplib.h
|
@ -63,6 +63,10 @@ typedef int socket_t;
|
|||
#include <openssl/ssl.h>
|
||||
#endif
|
||||
|
||||
#ifdef CPPHTTPLIB_ZLIB_SUPPORT
|
||||
#include <zlib.h>
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Configuration
|
||||
*/
|
||||
|
@ -597,19 +601,15 @@ inline std::string file_extension(const std::string& path)
|
|||
return std::string();
|
||||
}
|
||||
|
||||
inline const char* content_type(const std::string& path)
|
||||
inline const char* find_content_type(const std::string& path)
|
||||
{
|
||||
auto ext = file_extension(path);
|
||||
if (ext == "txt") {
|
||||
return "text/plain";
|
||||
} else if (ext == "html") {
|
||||
return "text/html";
|
||||
} else if (ext == "js") {
|
||||
return "text/javascript";
|
||||
} else if (ext == "css") {
|
||||
return "text/css";
|
||||
} else if (ext == "xml") {
|
||||
return "text/xml";
|
||||
} else if (ext == "jpeg" || ext == "jpg") {
|
||||
return "image/jpg";
|
||||
} else if (ext == "png") {
|
||||
|
@ -624,6 +624,10 @@ inline const char* content_type(const std::string& path)
|
|||
return "application/json";
|
||||
} else if (ext == "pdf") {
|
||||
return "application/pdf";
|
||||
} else if (ext == "js") {
|
||||
return "application/javascript";
|
||||
} else if (ext == "xml") {
|
||||
return "application/xml";
|
||||
} else if (ext == "xhtml") {
|
||||
return "application/xhtml+xml";
|
||||
}
|
||||
|
@ -774,7 +778,7 @@ bool read_content(Stream& strm, T& x, Progress progress = Progress())
|
|||
if (len) {
|
||||
return read_content_with_length(strm, x, len, progress);
|
||||
} else {
|
||||
auto encoding = get_header_value(x.headers, "Transfer-Encoding", "");
|
||||
const auto& encoding = get_header_value(x.headers, "Transfer-Encoding", "");
|
||||
|
||||
if (!strcmp(encoding, "chunked")) {
|
||||
return read_content_chunked(strm, x);
|
||||
|
@ -1070,6 +1074,59 @@ inline void make_range_header_core(std::string& field, uint64_t value1, uint64_t
|
|||
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(const Request& req, Response& res)
|
||||
{
|
||||
// TODO: Server version is HTTP/1.1 and 'Accpet-Encoding' has gzip, not gzip;q=0
|
||||
const auto& encodings = req.get_header_value("Accept-Encoding");
|
||||
if (encodings.find("gzip") == std::string::npos) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!can_compress(res.get_header_value("Content-Type"))) {
|
||||
return;
|
||||
}
|
||||
|
||||
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 = res.body.size();
|
||||
strm.next_in = (Bytef *)res.body.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);
|
||||
|
||||
res.set_header("Content-Encoding", "gzip");
|
||||
res.body.swap(compressed);
|
||||
|
||||
deflateEnd(&strm);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
class WSInit {
|
||||
public:
|
||||
|
@ -1375,6 +1432,10 @@ inline void Server::write_response(Stream& strm, bool last_connection, const Req
|
|||
}
|
||||
|
||||
if (!res.body.empty()) {
|
||||
#ifdef CPPHTTPLIB_ZLIB_SUPPORT
|
||||
detail::compress(req, res);
|
||||
#endif
|
||||
|
||||
if (!res.has_header("Content-Type")) {
|
||||
res.set_header("Content-Type", "text/plain");
|
||||
}
|
||||
|
@ -1407,7 +1468,7 @@ inline bool Server::handle_file_request(Request& req, Response& res)
|
|||
|
||||
if (detail::is_file(path)) {
|
||||
detail::read_file(path, res.body);
|
||||
auto type = detail::content_type(path);
|
||||
auto type = detail::find_content_type(path);
|
||||
if (type) {
|
||||
res.set_header("Content-Type", type);
|
||||
}
|
||||
|
|
|
@ -2,12 +2,13 @@
|
|||
CC = clang++
|
||||
CFLAGS = -std=c++11 -DGTEST_USE_OWN_TR1_TUPLE -I.. -I. -Wall -Wextra -lpthread
|
||||
#OPENSSL_SUPPORT = -DCPPHTTPLIB_OPENSSL_SUPPORT -I/usr/local/opt/openssl/include -L/usr/local/opt/openssl/lib -lssl -lcrypto
|
||||
#ZLIB_SUPPORT = -DCPPHTTPLIB_ZLIB_SUPPORT -lz
|
||||
|
||||
all : test
|
||||
./test
|
||||
|
||||
test : test.cc ../httplib.h Makefile
|
||||
$(CC) -o test $(CFLAGS) test.cc gtest/gtest-all.cc gtest/gtest_main.cc $(OPENSSL_SUPPORT)
|
||||
$(CC) -o test $(CFLAGS) test.cc gtest/gtest-all.cc gtest/gtest_main.cc $(OPENSSL_SUPPORT) $(ZLIB_SUPPORT)
|
||||
|
||||
pem:
|
||||
openssl genrsa 2048 > key.pem
|
||||
|
|
39
test/test.cc
39
test/test.cc
|
@ -267,7 +267,16 @@ protected:
|
|||
EXPECT_EQ("application/octet-stream", file.content_type);
|
||||
EXPECT_EQ(0u, file.length);
|
||||
}
|
||||
});
|
||||
})
|
||||
#ifdef CPPHTTPLIB_ZLIB_SUPPORT
|
||||
.get("/gzip", [&](const Request& /*req*/, Response& res) {
|
||||
res.set_content("1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890", "text/plain");
|
||||
})
|
||||
.get("/nogzip", [&](const Request& /*req*/, Response& res) {
|
||||
res.set_content("1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890", "application/octet-stream");
|
||||
})
|
||||
#endif
|
||||
;
|
||||
|
||||
persons_["john"] = "programmer";
|
||||
|
||||
|
@ -580,6 +589,34 @@ TEST_F(ServerTest, CaseInsensitiveHeaderName)
|
|||
EXPECT_EQ("Hello World!", res->body);
|
||||
}
|
||||
|
||||
#ifdef CPPHTTPLIB_ZLIB_SUPPORT
|
||||
TEST_F(ServerTest, Gzip)
|
||||
{
|
||||
Headers headers;
|
||||
headers.emplace("Accept-Encoding", "gzip, deflate");
|
||||
auto res = cli_.get("/gzip", headers);
|
||||
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
EXPECT_EQ("gzip", res->get_header_value("Content-Encoding"));
|
||||
EXPECT_EQ("text/plain", res->get_header_value("Content-Type"));
|
||||
EXPECT_EQ("33", res->get_header_value("Content-Length"));
|
||||
EXPECT_EQ(200, res->status);
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, NoGzip)
|
||||
{
|
||||
Headers headers;
|
||||
headers.emplace("Accept-Encoding", "gzip, deflate");
|
||||
auto res = cli_.get("/nogzip", headers);
|
||||
|
||||
ASSERT_TRUE(res != nullptr);
|
||||
EXPECT_EQ(false, res->has_header("Content-Encoding"));
|
||||
EXPECT_EQ("application/octet-stream", res->get_header_value("Content-Type"));
|
||||
EXPECT_EQ("100", res->get_header_value("Content-Length"));
|
||||
EXPECT_EQ(200, res->status);
|
||||
}
|
||||
#endif
|
||||
|
||||
class ServerTestWithAI_PASSIVE : public ::testing::Test {
|
||||
protected:
|
||||
ServerTestWithAI_PASSIVE()
|
||||
|
|
Loading…
Reference in a new issue