Support for gzip compression when using http 1.1; giving gzip precendence
This commit is contained in:
parent
d0547f3d69
commit
221080e281
@ -45,7 +45,7 @@ struct Header {
|
|||||||
};
|
};
|
||||||
|
|
||||||
enum CompressionType {
|
enum CompressionType {
|
||||||
none,
|
noCompression,
|
||||||
gzipRFC1952,
|
gzipRFC1952,
|
||||||
deflateRFC1951
|
deflateRFC1951
|
||||||
} Compression;
|
} Compression;
|
||||||
|
@ -42,126 +42,137 @@ namespace http {
|
|||||||
/// Represents a single connection from a client.
|
/// Represents a single connection from a client.
|
||||||
class Connection : public boost::enable_shared_from_this<Connection>, private boost::noncopyable {
|
class Connection : public boost::enable_shared_from_this<Connection>, private boost::noncopyable {
|
||||||
public:
|
public:
|
||||||
explicit Connection(boost::asio::io_service& io_service, RequestHandler& handler) : strand(io_service), TCPsocket(io_service), requestHandler(handler) {}
|
explicit Connection(boost::asio::io_service& io_service, RequestHandler& handler) : strand(io_service), TCPsocket(io_service), requestHandler(handler) {}
|
||||||
|
|
||||||
boost::asio::ip::tcp::socket& socket() {
|
boost::asio::ip::tcp::socket& socket() {
|
||||||
return TCPsocket;
|
return TCPsocket;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Start the first asynchronous operation for the connection.
|
/// Start the first asynchronous operation for the connection.
|
||||||
void start() {
|
void start() {
|
||||||
TCPsocket.async_read_some(boost::asio::buffer(incomingDataBuffer), strand.wrap( boost::bind(&Connection::handleRead, this->shared_from_this(), boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred)));
|
TCPsocket.async_read_some(boost::asio::buffer(incomingDataBuffer), strand.wrap( boost::bind(&Connection::handleRead, this->shared_from_this(), boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred)));
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void handleRead(const boost::system::error_code& e, std::size_t bytes_transferred) {
|
void handleRead(const boost::system::error_code& e, std::size_t bytes_transferred) {
|
||||||
if (!e) {
|
if (!e) {
|
||||||
CompressionType compressionType(none);
|
CompressionType compressionType(noCompression);
|
||||||
boost::tribool result;
|
boost::tribool result;
|
||||||
boost::tie(result, boost::tuples::ignore) = requestParser.Parse( request, incomingDataBuffer.data(), incomingDataBuffer.data() + bytes_transferred, &compressionType);
|
boost::tie(result, boost::tuples::ignore) = requestParser.Parse( request, incomingDataBuffer.data(), incomingDataBuffer.data() + bytes_transferred, &compressionType);
|
||||||
|
|
||||||
if (result) {
|
if (result) {
|
||||||
// std::cout << "----" << std::endl;
|
// std::cout << "----" << std::endl;
|
||||||
// if(compressionType == gzipRFC1952)
|
// if(compressionType == gzipRFC1952)
|
||||||
// std::cout << "[debug] supports gzip" << std::endl;
|
// std::cout << "[debug] using gzip" << std::endl;
|
||||||
// if(compressionType == deflateRFC1951)
|
// if(compressionType == deflateRFC1951)
|
||||||
// std::cout << "[debug] Supports deflate" << std::endl;
|
// std::cout << "[debug] using deflate" << std::endl;
|
||||||
|
// if(compressionType == noCompression)
|
||||||
|
// std::cout << "[debug] no compression" << std::endl;
|
||||||
|
|
||||||
requestHandler.handle_request(request, reply);
|
requestHandler.handle_request(request, reply);
|
||||||
|
|
||||||
if(compressionType == deflateRFC1951) {
|
Header compressionHeader;
|
||||||
Header compressionHeader;
|
std::vector<unsigned char> compressed;
|
||||||
compressionHeader.name = "Content-Encoding";
|
std::vector<boost::asio::const_buffer> outputBuffer;
|
||||||
compressionHeader.value = "deflate";
|
switch(compressionType) {
|
||||||
reply.headers.insert(reply.headers.begin(), compressionHeader); //push_back(compressionHeader);
|
case deflateRFC1951:
|
||||||
|
compressionHeader.name = "Content-Encoding";
|
||||||
|
compressionHeader.value = "deflate";
|
||||||
|
reply.headers.insert(reply.headers.begin(), compressionHeader); //push_back(compressionHeader);
|
||||||
|
compressCharArray(reply.content.c_str(), strlen(reply.content.c_str()), compressed, compressionType);
|
||||||
|
reply.setSize(compressed.size());
|
||||||
|
outputBuffer = reply.HeaderstoBuffers();
|
||||||
|
outputBuffer.push_back(boost::asio::buffer(compressed));
|
||||||
|
boost::asio::async_write(TCPsocket, outputBuffer, strand.wrap( boost::bind(&Connection::handleWrite, this->shared_from_this(), boost::asio::placeholders::error)));
|
||||||
|
break;
|
||||||
|
case gzipRFC1952:
|
||||||
|
compressionHeader.name = "Content-Encoding";
|
||||||
|
compressionHeader.value = "gzip";
|
||||||
|
reply.headers.insert(reply.headers.begin(), compressionHeader);
|
||||||
|
compressCharArray(reply.content.c_str(), strlen(reply.content.c_str()), compressed, compressionType);
|
||||||
|
reply.setSize(compressed.size());
|
||||||
|
outputBuffer = reply.HeaderstoBuffers();
|
||||||
|
outputBuffer.push_back(boost::asio::buffer(compressed));
|
||||||
|
boost::asio::async_write(TCPsocket, outputBuffer, strand.wrap( boost::bind(&Connection::handleWrite, this->shared_from_this(), boost::asio::placeholders::error)));break;
|
||||||
|
case noCompression:
|
||||||
|
boost::asio::async_write(TCPsocket, reply.toBuffers(), strand.wrap( boost::bind(&Connection::handleWrite, this->shared_from_this(), boost::asio::placeholders::error)));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
std::vector<unsigned char> compressed;
|
} else if (!result) {
|
||||||
// double time = get_timestamp();
|
reply = Reply::stockReply(Reply::badRequest);
|
||||||
compressCharArray(reply.content.c_str(), strlen(reply.content.c_str()), compressed);
|
boost::asio::async_write(TCPsocket, reply.toBuffers(), strand.wrap( boost::bind(&Connection::handleWrite, this->shared_from_this(), boost::asio::placeholders::error)));
|
||||||
// std::cout << "[info] compression took " << get_timestamp() - time << " seconds." << std::endl;
|
} else {
|
||||||
|
TCPsocket.async_read_some(boost::asio::buffer(incomingDataBuffer), strand.wrap( boost::bind(&Connection::handleRead, this->shared_from_this(), boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
reply.setSize(compressed.size());
|
/// Handle completion of a write operation.
|
||||||
std::vector<boost::asio::const_buffer> outputBuffer = reply.HeaderstoBuffers();
|
void handleWrite(const boost::system::error_code& e) {
|
||||||
outputBuffer.push_back(boost::asio::buffer(compressed));
|
if (!e) {
|
||||||
// std::cout << "[debug] outbuffer.size(): " << outputBuffer.size() << std::endl;
|
// Initiate graceful connection closure.
|
||||||
boost::asio::async_write(TCPsocket, outputBuffer, strand.wrap( boost::bind(&Connection::handleWrite, this->shared_from_this(), boost::asio::placeholders::error)));
|
boost::system::error_code ignoredEC;
|
||||||
|
TCPsocket.shutdown(boost::asio::ip::tcp::socket::shutdown_both, ignoredEC);
|
||||||
|
}
|
||||||
|
// No new asynchronous operations are started. This means that all shared_ptr
|
||||||
|
// references to the connection object will disappear and the object will be
|
||||||
|
// destroyed automatically after this handler returns. The connection class's
|
||||||
|
// destructor closes the socket.
|
||||||
|
}
|
||||||
|
|
||||||
} else {
|
void compressCharArray(const void *in_data, size_t in_data_size, std::vector<unsigned char> &buffer, CompressionType type) {
|
||||||
boost::asio::async_write(TCPsocket, reply.toBuffers(), strand.wrap( boost::bind(&Connection::handleWrite, this->shared_from_this(), boost::asio::placeholders::error)));
|
const size_t BUFSIZE = 128 * 1024;
|
||||||
}
|
unsigned char temp_buffer[BUFSIZE];
|
||||||
|
|
||||||
} else if (!result) {
|
z_stream strm;
|
||||||
reply = Reply::stockReply(Reply::badRequest);
|
strm.zalloc = 0;
|
||||||
boost::asio::async_write(TCPsocket, reply.toBuffers(), strand.wrap( boost::bind(&Connection::handleWrite, this->shared_from_this(), boost::asio::placeholders::error)));
|
strm.zfree = 0;
|
||||||
} else {
|
strm.next_in = (unsigned char *)(in_data);
|
||||||
TCPsocket.async_read_some(boost::asio::buffer(incomingDataBuffer), strand.wrap( boost::bind(&Connection::handleRead, this->shared_from_this(), boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred)));
|
strm.avail_in = in_data_size;
|
||||||
}
|
strm.next_out = temp_buffer;
|
||||||
}
|
strm.avail_out = BUFSIZE;
|
||||||
}
|
strm.data_type = Z_ASCII;
|
||||||
|
|
||||||
/// Handle completion of a write operation.
|
switch(type){
|
||||||
void handleWrite(const boost::system::error_code& e) {
|
case deflateRFC1951:
|
||||||
if (!e) {
|
deflateInit(&strm, Z_BEST_SPEED);
|
||||||
// Initiate graceful connection closure.
|
break;
|
||||||
boost::system::error_code ignoredEC;
|
case gzipRFC1952:
|
||||||
TCPsocket.shutdown(boost::asio::ip::tcp::socket::shutdown_both, ignoredEC);
|
/*
|
||||||
}
|
* Big thanks to deusty who explains how to have gzip compression turned on by the right call to deflateInit2():
|
||||||
// No new asynchronous operations are started. This means that all shared_ptr
|
* http://deusty.blogspot.com/2007/07/gzip-compressiondecompression.html
|
||||||
// references to the connection object will disappear and the object will be
|
*/
|
||||||
// destroyed automatically after this handler returns. The connection class's
|
deflateInit2(&strm, Z_DEFAULT_COMPRESSION, Z_DEFLATED, (15+16), 8, Z_DEFAULT_STRATEGY);
|
||||||
// destructor closes the socket.
|
break;
|
||||||
}
|
case noCompression:
|
||||||
|
std::cerr << "[error] contradicting compression request" << std::endl;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
void compressCharArray(const void *in_data, size_t in_data_size, std::vector<unsigned char> &buffer) {
|
int deflate_res = Z_OK;
|
||||||
// std::vector<unsigned char> buffer;
|
do {
|
||||||
|
if (strm.avail_out == 0) {
|
||||||
|
buffer.insert(buffer.end(), temp_buffer, temp_buffer + BUFSIZE);
|
||||||
|
strm.next_out = temp_buffer;
|
||||||
|
strm.avail_out = BUFSIZE;
|
||||||
|
}
|
||||||
|
deflate_res = deflate(&strm, Z_FINISH);
|
||||||
|
|
||||||
const size_t BUFSIZE = 128 * 1024;
|
} while (strm.avail_out == 0);
|
||||||
unsigned char temp_buffer[BUFSIZE];
|
|
||||||
|
|
||||||
z_stream strm;
|
assert(deflate_res == Z_STREAM_END);
|
||||||
strm.zalloc = 0;
|
buffer.insert(buffer.end(), temp_buffer, temp_buffer + BUFSIZE - strm.avail_out);
|
||||||
strm.zfree = 0;
|
deflateEnd(&strm);
|
||||||
strm.next_in = (unsigned char *)(in_data);
|
}
|
||||||
strm.avail_in = in_data_size;
|
|
||||||
strm.next_out = temp_buffer;
|
|
||||||
strm.avail_out = BUFSIZE;
|
|
||||||
|
|
||||||
deflateInit(&strm, Z_BEST_SPEED);
|
boost::asio::io_service::strand strand;
|
||||||
|
boost::asio::ip::tcp::socket TCPsocket;
|
||||||
while (strm.avail_in != 0) {
|
RequestHandler& requestHandler;
|
||||||
int res = deflate(&strm, Z_NO_FLUSH);
|
boost::array<char, 8192> incomingDataBuffer;
|
||||||
assert(res == Z_OK);
|
Request request;
|
||||||
if (strm.avail_out == 0) {
|
RequestParser requestParser;
|
||||||
buffer.insert(buffer.end(), temp_buffer, temp_buffer + BUFSIZE);
|
Reply reply;
|
||||||
strm.next_out = temp_buffer;
|
|
||||||
strm.avail_out = BUFSIZE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int deflate_res = Z_OK;
|
|
||||||
while (deflate_res == Z_OK) {
|
|
||||||
if (strm.avail_out == 0) {
|
|
||||||
buffer.insert(buffer.end(), temp_buffer, temp_buffer + BUFSIZE);
|
|
||||||
strm.next_out = temp_buffer;
|
|
||||||
strm.avail_out = BUFSIZE;
|
|
||||||
}
|
|
||||||
deflate_res = deflate(&strm, Z_FINISH);
|
|
||||||
}
|
|
||||||
|
|
||||||
assert(deflate_res == Z_STREAM_END);
|
|
||||||
buffer.insert(buffer.end(), temp_buffer, temp_buffer + BUFSIZE - strm.avail_out);
|
|
||||||
deflateEnd(&strm);
|
|
||||||
|
|
||||||
// out_data.swap(buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
boost::asio::io_service::strand strand;
|
|
||||||
boost::asio::ip::tcp::socket TCPsocket;
|
|
||||||
RequestHandler& requestHandler;
|
|
||||||
boost::array<char, 8192> incomingDataBuffer;
|
|
||||||
Request request;
|
|
||||||
RequestParser requestParser;
|
|
||||||
Reply reply;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace http
|
} // namespace http
|
||||||
|
@ -164,10 +164,11 @@ private:
|
|||||||
} else {
|
} else {
|
||||||
state_ = header_name;
|
state_ = header_name;
|
||||||
if(header.name == "Accept-Encoding") {
|
if(header.name == "Accept-Encoding") {
|
||||||
if(header.value.find("deflate") != std::string::npos)
|
/* giving gzip precedence over deflate */
|
||||||
|
if(header.value.find("deflate") != std::string::npos)
|
||||||
*compressionType = deflateRFC1951;
|
*compressionType = deflateRFC1951;
|
||||||
// if(header.value.find("gzip") != std::string::npos)
|
if(header.value.find("gzip") != std::string::npos)
|
||||||
// *compressionType = gzipRFC1952;
|
*compressionType = gzipRFC1952;
|
||||||
}
|
}
|
||||||
header.Clear();
|
header.Clear();
|
||||||
header.name.push_back(input);
|
header.name.push_back(input);
|
||||||
|
@ -75,6 +75,7 @@ struct ServerFactory {
|
|||||||
if(atoi(serverConfig.GetParameter("Threads").c_str()) != 0 && (unsigned)atoi(serverConfig.GetParameter("Threads").c_str()) <= threads)
|
if(atoi(serverConfig.GetParameter("Threads").c_str()) != 0 && (unsigned)atoi(serverConfig.GetParameter("Threads").c_str()) <= threads)
|
||||||
threads = atoi( serverConfig.GetParameter("Threads").c_str() );
|
threads = atoi( serverConfig.GetParameter("Threads").c_str() );
|
||||||
|
|
||||||
|
std::cout << "[info] http 1.1 compression handled by zlib version " << zlibVersion() << std::endl;
|
||||||
Server * server = new Server(serverConfig.GetParameter("IP"), serverConfig.GetParameter("Port"), threads);
|
Server * server = new Server(serverConfig.GetParameter("IP"), serverConfig.GetParameter("Port"), threads);
|
||||||
return server;
|
return server;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user