Support for gzip compression when using http 1.1; giving gzip precendence

This commit is contained in:
Dennis Luxen 2011-02-13 11:15:56 +00:00
parent d0547f3d69
commit 221080e281
4 changed files with 121 additions and 108 deletions

View File

@ -45,7 +45,7 @@ struct Header {
}; };
enum CompressionType { enum CompressionType {
none, noCompression,
gzipRFC1952, gzipRFC1952,
deflateRFC1951 deflateRFC1951
} Compression; } Compression;

View File

@ -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

View File

@ -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);

View File

@ -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;
} }