Support http 1.1 deflate compression
This commit is contained in:
parent
e48b47f1ec
commit
d0547f3d69
@ -20,7 +20,6 @@ or see http://www.gnu.org/licenses/agpl.txt.
|
|||||||
|
|
||||||
#ifndef BASIC_DATASTRUCTURES_H
|
#ifndef BASIC_DATASTRUCTURES_H
|
||||||
#define BASIC_DATASTRUCTURES_H
|
#define BASIC_DATASTRUCTURES_H
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <boost/lexical_cast.hpp>
|
#include <boost/lexical_cast.hpp>
|
||||||
|
|
||||||
@ -39,8 +38,18 @@ const char crlf[] = { '\r', '\n' };
|
|||||||
struct Header {
|
struct Header {
|
||||||
std::string name;
|
std::string name;
|
||||||
std::string value;
|
std::string value;
|
||||||
|
void Clear() {
|
||||||
|
name.clear();
|
||||||
|
value.clear();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum CompressionType {
|
||||||
|
none,
|
||||||
|
gzipRFC1952,
|
||||||
|
deflateRFC1951
|
||||||
|
} Compression;
|
||||||
|
|
||||||
struct Request {
|
struct Request {
|
||||||
std::string uri;
|
std::string uri;
|
||||||
};
|
};
|
||||||
@ -56,7 +65,18 @@ struct Reply {
|
|||||||
std::vector<Header> headers;
|
std::vector<Header> headers;
|
||||||
std::string content;
|
std::string content;
|
||||||
std::vector<boost::asio::const_buffer> toBuffers();
|
std::vector<boost::asio::const_buffer> toBuffers();
|
||||||
|
std::vector<boost::asio::const_buffer> HeaderstoBuffers();
|
||||||
static Reply stockReply(status_type status);
|
static Reply stockReply(status_type status);
|
||||||
|
void setSize(unsigned size) {
|
||||||
|
for (std::size_t i = 0; i < headers.size(); ++i) {
|
||||||
|
Header& h = headers[i];
|
||||||
|
if("Content-Length" == h.name) {
|
||||||
|
std::stringstream sizeString;
|
||||||
|
sizeString << size;
|
||||||
|
h.value = sizeString.str();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
boost::asio::const_buffer ToBuffer(Reply::status_type status) {
|
boost::asio::const_buffer ToBuffer(Reply::status_type status) {
|
||||||
@ -96,6 +116,21 @@ std::vector<boost::asio::const_buffer> Reply::toBuffers(){
|
|||||||
return buffers;
|
return buffers;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::vector<boost::asio::const_buffer> Reply::HeaderstoBuffers(){
|
||||||
|
std::vector<boost::asio::const_buffer> buffers;
|
||||||
|
buffers.push_back(ToBuffer(status));
|
||||||
|
for (std::size_t i = 0; i < headers.size(); ++i) {
|
||||||
|
Header& h = headers[i];
|
||||||
|
// std::cout << h.name << ": " << h.value << std::endl;
|
||||||
|
buffers.push_back(boost::asio::buffer(h.name));
|
||||||
|
buffers.push_back(boost::asio::buffer(seperators));
|
||||||
|
buffers.push_back(boost::asio::buffer(h.value));
|
||||||
|
buffers.push_back(boost::asio::buffer(crlf));
|
||||||
|
}
|
||||||
|
buffers.push_back(boost::asio::buffer(crlf));
|
||||||
|
return buffers;
|
||||||
|
}
|
||||||
|
|
||||||
Reply Reply::stockReply(Reply::status_type status) {
|
Reply Reply::stockReply(Reply::status_type status) {
|
||||||
Reply rep;
|
Reply rep;
|
||||||
rep.status = status;
|
rep.status = status;
|
||||||
|
@ -29,10 +29,14 @@ or see http://www.gnu.org/licenses/agpl.txt.
|
|||||||
#include <boost/noncopyable.hpp>
|
#include <boost/noncopyable.hpp>
|
||||||
#include <boost/shared_ptr.hpp>
|
#include <boost/shared_ptr.hpp>
|
||||||
#include <boost/enable_shared_from_this.hpp>
|
#include <boost/enable_shared_from_this.hpp>
|
||||||
|
|
||||||
|
#include "../DataStructures/Util.h"
|
||||||
#include "BasicDatastructures.h"
|
#include "BasicDatastructures.h"
|
||||||
#include "RequestHandler.h"
|
#include "RequestHandler.h"
|
||||||
#include "RequestParser.h"
|
#include "RequestParser.h"
|
||||||
|
|
||||||
|
#include "zlib.h"
|
||||||
|
|
||||||
namespace http {
|
namespace http {
|
||||||
|
|
||||||
/// Represents a single connection from a client.
|
/// Represents a single connection from a client.
|
||||||
@ -52,12 +56,40 @@ public:
|
|||||||
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);
|
||||||
boost::tribool result;
|
boost::tribool result;
|
||||||
boost::tie(result, boost::tuples::ignore) = requestParser.Parse( request, incomingDataBuffer.data(), incomingDataBuffer.data() + bytes_transferred);
|
boost::tie(result, boost::tuples::ignore) = requestParser.Parse( request, incomingDataBuffer.data(), incomingDataBuffer.data() + bytes_transferred, &compressionType);
|
||||||
|
|
||||||
if (result) {
|
if (result) {
|
||||||
|
// std::cout << "----" << std::endl;
|
||||||
|
// if(compressionType == gzipRFC1952)
|
||||||
|
// std::cout << "[debug] supports gzip" << std::endl;
|
||||||
|
// if(compressionType == deflateRFC1951)
|
||||||
|
// std::cout << "[debug] Supports deflate" << std::endl;
|
||||||
|
|
||||||
requestHandler.handle_request(request, reply);
|
requestHandler.handle_request(request, reply);
|
||||||
|
|
||||||
|
if(compressionType == deflateRFC1951) {
|
||||||
|
Header compressionHeader;
|
||||||
|
compressionHeader.name = "Content-Encoding";
|
||||||
|
compressionHeader.value = "deflate";
|
||||||
|
reply.headers.insert(reply.headers.begin(), compressionHeader); //push_back(compressionHeader);
|
||||||
|
|
||||||
|
std::vector<unsigned char> compressed;
|
||||||
|
// double time = get_timestamp();
|
||||||
|
compressCharArray(reply.content.c_str(), strlen(reply.content.c_str()), compressed);
|
||||||
|
// std::cout << "[info] compression took " << get_timestamp() - time << " seconds." << std::endl;
|
||||||
|
|
||||||
|
reply.setSize(compressed.size());
|
||||||
|
std::vector<boost::asio::const_buffer> outputBuffer = reply.HeaderstoBuffers();
|
||||||
|
outputBuffer.push_back(boost::asio::buffer(compressed));
|
||||||
|
// std::cout << "[debug] outbuffer.size(): " << outputBuffer.size() << std::endl;
|
||||||
|
boost::asio::async_write(TCPsocket, outputBuffer, strand.wrap( boost::bind(&Connection::handleWrite, this->shared_from_this(), boost::asio::placeholders::error)));
|
||||||
|
|
||||||
|
} else {
|
||||||
boost::asio::async_write(TCPsocket, reply.toBuffers(), strand.wrap( boost::bind(&Connection::handleWrite, this->shared_from_this(), boost::asio::placeholders::error)));
|
boost::asio::async_write(TCPsocket, reply.toBuffers(), strand.wrap( boost::bind(&Connection::handleWrite, this->shared_from_this(), boost::asio::placeholders::error)));
|
||||||
|
}
|
||||||
|
|
||||||
} else if (!result) {
|
} else if (!result) {
|
||||||
reply = Reply::stockReply(Reply::badRequest);
|
reply = Reply::stockReply(Reply::badRequest);
|
||||||
boost::asio::async_write(TCPsocket, reply.toBuffers(), strand.wrap( boost::bind(&Connection::handleWrite, this->shared_from_this(), boost::asio::placeholders::error)));
|
boost::asio::async_write(TCPsocket, reply.toBuffers(), strand.wrap( boost::bind(&Connection::handleWrite, this->shared_from_this(), boost::asio::placeholders::error)));
|
||||||
@ -80,6 +112,49 @@ private:
|
|||||||
// destructor closes the socket.
|
// destructor closes the socket.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void compressCharArray(const void *in_data, size_t in_data_size, std::vector<unsigned char> &buffer) {
|
||||||
|
// std::vector<unsigned char> buffer;
|
||||||
|
|
||||||
|
const size_t BUFSIZE = 128 * 1024;
|
||||||
|
unsigned char temp_buffer[BUFSIZE];
|
||||||
|
|
||||||
|
z_stream strm;
|
||||||
|
strm.zalloc = 0;
|
||||||
|
strm.zfree = 0;
|
||||||
|
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);
|
||||||
|
|
||||||
|
while (strm.avail_in != 0) {
|
||||||
|
int res = deflate(&strm, Z_NO_FLUSH);
|
||||||
|
assert(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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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::io_service::strand strand;
|
||||||
boost::asio::ip::tcp::socket TCPsocket;
|
boost::asio::ip::tcp::socket TCPsocket;
|
||||||
RequestHandler& requestHandler;
|
RequestHandler& requestHandler;
|
||||||
|
@ -32,9 +32,9 @@ public:
|
|||||||
RequestParser() : state_(method_start) { }
|
RequestParser() : state_(method_start) { }
|
||||||
void Reset() { state_ = method_start; }
|
void Reset() { state_ = method_start; }
|
||||||
|
|
||||||
boost::tuple<boost::tribool, char*> Parse(Request& req, char* begin, char* end) {
|
boost::tuple<boost::tribool, char*> Parse(Request& req, char* begin, char* end, CompressionType * compressionType) {
|
||||||
while (begin != end) {
|
while (begin != end) {
|
||||||
boost::tribool result = consume(req, *begin++);
|
boost::tribool result = consume(req, *begin++, compressionType);
|
||||||
if (result || !result){
|
if (result || !result){
|
||||||
return boost::make_tuple(result, begin);
|
return boost::make_tuple(result, begin);
|
||||||
}
|
}
|
||||||
@ -44,7 +44,7 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
boost::tribool consume(Request& req, char input) {
|
boost::tribool consume(Request& req, char input, CompressionType * compressionType) {
|
||||||
switch (state_) {
|
switch (state_) {
|
||||||
case method_start:
|
case method_start:
|
||||||
if (!isChar(input) || isCTL(input) || isTSpecial(input)) {
|
if (!isChar(input) || isCTL(input) || isTSpecial(input)) {
|
||||||
@ -163,6 +163,14 @@ private:
|
|||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
state_ = header_name;
|
state_ = header_name;
|
||||||
|
if(header.name == "Accept-Encoding") {
|
||||||
|
if(header.value.find("deflate") != std::string::npos)
|
||||||
|
*compressionType = deflateRFC1951;
|
||||||
|
// if(header.value.find("gzip") != std::string::npos)
|
||||||
|
// *compressionType = gzipRFC1952;
|
||||||
|
}
|
||||||
|
header.Clear();
|
||||||
|
header.name.push_back(input);
|
||||||
return boost::indeterminate;
|
return boost::indeterminate;
|
||||||
}
|
}
|
||||||
case header_lws:
|
case header_lws:
|
||||||
@ -185,6 +193,7 @@ private:
|
|||||||
} else if (!isChar(input) || isCTL(input) || isTSpecial(input)) {
|
} else if (!isChar(input) || isCTL(input) || isTSpecial(input)) {
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
|
header.name.push_back(input);
|
||||||
return boost::indeterminate;
|
return boost::indeterminate;
|
||||||
}
|
}
|
||||||
case space_before_header_value:
|
case space_before_header_value:
|
||||||
@ -201,6 +210,7 @@ private:
|
|||||||
} else if (isCTL(input)) {
|
} else if (isCTL(input)) {
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
|
header.value.push_back(input);
|
||||||
return boost::indeterminate;
|
return boost::indeterminate;
|
||||||
}
|
}
|
||||||
case expecting_newline_2:
|
case expecting_newline_2:
|
||||||
@ -264,6 +274,8 @@ private:
|
|||||||
expecting_newline_2,
|
expecting_newline_2,
|
||||||
expecting_newline_3
|
expecting_newline_3
|
||||||
} state_;
|
} state_;
|
||||||
|
|
||||||
|
Header header;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace http
|
} // namespace http
|
||||||
|
Loading…
Reference in New Issue
Block a user