diff --git a/Server/BasicDatastructures.h b/Server/BasicDatastructures.h index b951b5c58..5cc269f63 100644 --- a/Server/BasicDatastructures.h +++ b/Server/BasicDatastructures.h @@ -20,7 +20,6 @@ or see http://www.gnu.org/licenses/agpl.txt. #ifndef BASIC_DATASTRUCTURES_H #define BASIC_DATASTRUCTURES_H - #include #include @@ -39,8 +38,18 @@ const char crlf[] = { '\r', '\n' }; struct Header { std::string name; std::string value; + void Clear() { + name.clear(); + value.clear(); + } }; +enum CompressionType { + none, + gzipRFC1952, + deflateRFC1951 +} Compression; + struct Request { std::string uri; }; @@ -55,8 +64,19 @@ struct Reply { std::vector
headers; std::string content; - std::vector toBuffers(); + std::vector toBuffers(); + std::vector HeaderstoBuffers(); 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) { @@ -96,6 +116,21 @@ std::vector Reply::toBuffers(){ return buffers; } +std::vector Reply::HeaderstoBuffers(){ + std::vector 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 rep; rep.status = status; diff --git a/Server/Connection.h b/Server/Connection.h index c5a675c69..166cab15c 100644 --- a/Server/Connection.h +++ b/Server/Connection.h @@ -16,7 +16,7 @@ You should have received a copy of the GNU Affero General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA or see http://www.gnu.org/licenses/agpl.txt. -*/ + */ #ifndef CONNECTION_H #define CONNECTION_H @@ -29,10 +29,14 @@ or see http://www.gnu.org/licenses/agpl.txt. #include #include #include + +#include "../DataStructures/Util.h" #include "BasicDatastructures.h" #include "RequestHandler.h" #include "RequestParser.h" +#include "zlib.h" + namespace http { /// Represents a single connection from a client. @@ -52,12 +56,40 @@ public: private: void handleRead(const boost::system::error_code& e, std::size_t bytes_transferred) { if (!e) { + CompressionType compressionType(none); 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) { +// 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); - boost::asio::async_write(TCPsocket, reply.toBuffers(), strand.wrap( boost::bind(&Connection::handleWrite, this->shared_from_this(), boost::asio::placeholders::error))); + + if(compressionType == deflateRFC1951) { + Header compressionHeader; + compressionHeader.name = "Content-Encoding"; + compressionHeader.value = "deflate"; + reply.headers.insert(reply.headers.begin(), compressionHeader); //push_back(compressionHeader); + + std::vector 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 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))); + } + } else if (!result) { 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))); @@ -80,6 +112,49 @@ private: // destructor closes the socket. } + void compressCharArray(const void *in_data, size_t in_data_size, std::vector &buffer) { +// std::vector 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::ip::tcp::socket TCPsocket; RequestHandler& requestHandler; diff --git a/Server/RequestHandler.h b/Server/RequestHandler.h index 7e44dfa8f..681d31d7e 100644 --- a/Server/RequestHandler.h +++ b/Server/RequestHandler.h @@ -42,7 +42,7 @@ public: void handle_request(const Request& req, Reply& rep){ //parse command - std::string request(req.uri); + std::string request(req.uri); std::string command; std::size_t firstAmpPosition = request.find_first_of("&"); command = request.substr(1,firstAmpPosition-1); diff --git a/Server/RequestParser.h b/Server/RequestParser.h index e2fef57c4..e45dd2be6 100644 --- a/Server/RequestParser.h +++ b/Server/RequestParser.h @@ -29,241 +29,253 @@ namespace http { class RequestParser { public: - RequestParser() : state_(method_start) { } - void Reset() { state_ = method_start; } + RequestParser() : state_(method_start) { } + void Reset() { state_ = method_start; } - boost::tuple Parse(Request& req, char* begin, char* end) { - while (begin != end) { - boost::tribool result = consume(req, *begin++); - if (result || !result){ - return boost::make_tuple(result, begin); - } - } - boost::tribool result = boost::indeterminate; - return boost::make_tuple(result, begin); - } + boost::tuple Parse(Request& req, char* begin, char* end, CompressionType * compressionType) { + while (begin != end) { + boost::tribool result = consume(req, *begin++, compressionType); + if (result || !result){ + return boost::make_tuple(result, begin); + } + } + boost::tribool result = boost::indeterminate; + return boost::make_tuple(result, begin); + } private: - boost::tribool consume(Request& req, char input) { - switch (state_) { - case method_start: - if (!isChar(input) || isCTL(input) || isTSpecial(input)) { - return false; - } else { - state_ = method; - return boost::indeterminate; - } - case method: - if (input == ' ') { - state_ = uri; - return boost::indeterminate; - } else if (!isChar(input) || isCTL(input) || isTSpecial(input)) { - return false; - } else { - return boost::indeterminate; - } - case uri_start: - if (isCTL(input)) { - return false; - } else { - state_ = uri; - req.uri.push_back(input); - return boost::indeterminate; - } - case uri: - if (input == ' ') { - state_ = http_version_h; - return boost::indeterminate; - } else if (isCTL(input)) { - return false; - } else { - req.uri.push_back(input); - return boost::indeterminate; - } - case http_version_h: - if (input == 'H') { - state_ = http_version_t_1; - return boost::indeterminate; - } else { - return false; - } - case http_version_t_1: - if (input == 'T') { - state_ = http_version_t_2; - return boost::indeterminate; - } else { - return false; - } - case http_version_t_2: - if (input == 'T') { - state_ = http_version_p; - return boost::indeterminate; - } else { - return false; - } - case http_version_p: - if (input == 'P') { - state_ = http_version_slash; - return boost::indeterminate; - } else { - return false; - } - case http_version_slash: - if (input == '/') { - state_ = http_version_major_start; - return boost::indeterminate; - } else { - return false; - } - case http_version_major_start: - if (isDigit(input)) { - state_ = http_version_major; - return boost::indeterminate; - } else { - return false; - } - case http_version_major: - if (input == '.') { - state_ = http_version_minor_start; - return boost::indeterminate; - } else if (isDigit(input)) { - return boost::indeterminate; - } else { - return false; - } - case http_version_minor_start: - if (isDigit(input)) { - state_ = http_version_minor; - return boost::indeterminate; - } else { - return false; - } - case http_version_minor: - if (input == '\r') { - state_ = expecting_newline_1; - return boost::indeterminate; - } else if (isDigit(input)) { - return boost::indeterminate; - } - else { - return false; - } - case expecting_newline_1: - if (input == '\n') { - state_ = header_line_start; - return boost::indeterminate; - } else { - return false; - } - case header_line_start: - if (input == '\r') { - state_ = expecting_newline_3; - return boost::indeterminate; - } else if (!isChar(input) || isCTL(input) || isTSpecial(input)) { - return false; - } else { - state_ = header_name; - return boost::indeterminate; - } - case header_lws: - if (input == '\r') { - state_ = expecting_newline_2; - return boost::indeterminate; - } else if (input == ' ' || input == '\t') { - return boost::indeterminate; - } - else if (isCTL(input)) { - return false; - } else { - state_ = header_value; - return boost::indeterminate; - } - case header_name: - if (input == ':') { - state_ = space_before_header_value; - return boost::indeterminate; - } else if (!isChar(input) || isCTL(input) || isTSpecial(input)) { - return false; - } else { - return boost::indeterminate; - } - case space_before_header_value: - if (input == ' ') { - state_ = header_value; - return boost::indeterminate; - } else { - return false; - } - case header_value: - if (input == '\r') { - state_ = expecting_newline_2; - return boost::indeterminate; - } else if (isCTL(input)) { - return false; - } else { - return boost::indeterminate; - } - case expecting_newline_2: - if (input == '\n') { - state_ = header_line_start; - return boost::indeterminate; - } else { - return false; - } - case expecting_newline_3: - return (input == '\n'); - default: - return false; - } - } + boost::tribool consume(Request& req, char input, CompressionType * compressionType) { + switch (state_) { + case method_start: + if (!isChar(input) || isCTL(input) || isTSpecial(input)) { + return false; + } else { + state_ = method; + return boost::indeterminate; + } + case method: + if (input == ' ') { + state_ = uri; + return boost::indeterminate; + } else if (!isChar(input) || isCTL(input) || isTSpecial(input)) { + return false; + } else { + return boost::indeterminate; + } + case uri_start: + if (isCTL(input)) { + return false; + } else { + state_ = uri; + req.uri.push_back(input); + return boost::indeterminate; + } + case uri: + if (input == ' ') { + state_ = http_version_h; + return boost::indeterminate; + } else if (isCTL(input)) { + return false; + } else { + req.uri.push_back(input); + return boost::indeterminate; + } + case http_version_h: + if (input == 'H') { + state_ = http_version_t_1; + return boost::indeterminate; + } else { + return false; + } + case http_version_t_1: + if (input == 'T') { + state_ = http_version_t_2; + return boost::indeterminate; + } else { + return false; + } + case http_version_t_2: + if (input == 'T') { + state_ = http_version_p; + return boost::indeterminate; + } else { + return false; + } + case http_version_p: + if (input == 'P') { + state_ = http_version_slash; + return boost::indeterminate; + } else { + return false; + } + case http_version_slash: + if (input == '/') { + state_ = http_version_major_start; + return boost::indeterminate; + } else { + return false; + } + case http_version_major_start: + if (isDigit(input)) { + state_ = http_version_major; + return boost::indeterminate; + } else { + return false; + } + case http_version_major: + if (input == '.') { + state_ = http_version_minor_start; + return boost::indeterminate; + } else if (isDigit(input)) { + return boost::indeterminate; + } else { + return false; + } + case http_version_minor_start: + if (isDigit(input)) { + state_ = http_version_minor; + return boost::indeterminate; + } else { + return false; + } + case http_version_minor: + if (input == '\r') { + state_ = expecting_newline_1; + return boost::indeterminate; + } else if (isDigit(input)) { + return boost::indeterminate; + } + else { + return false; + } + case expecting_newline_1: + if (input == '\n') { + state_ = header_line_start; + return boost::indeterminate; + } else { + return false; + } + case header_line_start: + if (input == '\r') { + state_ = expecting_newline_3; + return boost::indeterminate; + } else if (!isChar(input) || isCTL(input) || isTSpecial(input)) { + return false; + } else { + 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; + } + case header_lws: + if (input == '\r') { + state_ = expecting_newline_2; + return boost::indeterminate; + } else if (input == ' ' || input == '\t') { + return boost::indeterminate; + } + else if (isCTL(input)) { + return false; + } else { + state_ = header_value; + return boost::indeterminate; + } + case header_name: + if (input == ':') { + state_ = space_before_header_value; + return boost::indeterminate; + } else if (!isChar(input) || isCTL(input) || isTSpecial(input)) { + return false; + } else { + header.name.push_back(input); + return boost::indeterminate; + } + case space_before_header_value: + if (input == ' ') { + state_ = header_value; + return boost::indeterminate; + } else { + return false; + } + case header_value: + if (input == '\r') { + state_ = expecting_newline_2; + return boost::indeterminate; + } else if (isCTL(input)) { + return false; + } else { + header.value.push_back(input); + return boost::indeterminate; + } + case expecting_newline_2: + if (input == '\n') { + state_ = header_line_start; + return boost::indeterminate; + } else { + return false; + } + case expecting_newline_3: + return (input == '\n'); + default: + return false; + } + } - inline bool isChar(int c) { - return c >= 0 && c <= 127; - } + inline bool isChar(int c) { + return c >= 0 && c <= 127; + } - inline bool isCTL(int c) { - return (c >= 0 && c <= 31) || (c == 127); - } + inline bool isCTL(int c) { + return (c >= 0 && c <= 31) || (c == 127); + } - inline bool isTSpecial(int c) { - switch (c) { - case '(': case ')': case '<': case '>': case '@': - case ',': case ';': case ':': case '\\': case '"': - case '/': case '[': case ']': case '?': case '=': - case '{': case '}': case ' ': case '\t': - return true; - default: - return false; - } - } + inline bool isTSpecial(int c) { + switch (c) { + case '(': case ')': case '<': case '>': case '@': + case ',': case ';': case ':': case '\\': case '"': + case '/': case '[': case ']': case '?': case '=': + case '{': case '}': case ' ': case '\t': + return true; + default: + return false; + } + } - inline bool isDigit(int c) { - return c >= '0' && c <= '9'; - } + inline bool isDigit(int c) { + return c >= '0' && c <= '9'; + } - enum state { - method_start, - method, - uri_start, - uri, - http_version_h, - http_version_t_1, - http_version_t_2, - http_version_p, - http_version_slash, - http_version_major_start, - http_version_major, - http_version_minor_start, - http_version_minor, - expecting_newline_1, - header_line_start, - header_lws, - header_name, - space_before_header_value, - header_value, - expecting_newline_2, - expecting_newline_3 - } state_; + enum state { + method_start, + method, + uri_start, + uri, + http_version_h, + http_version_t_1, + http_version_t_2, + http_version_p, + http_version_slash, + http_version_major_start, + http_version_major, + http_version_minor_start, + http_version_minor, + expecting_newline_1, + header_line_start, + header_lws, + header_name, + space_before_header_value, + header_value, + expecting_newline_2, + expecting_newline_3 + } state_; + + Header header; }; } // namespace http