From 0c2f90ee5c713fcb3aedb236fcebe7dd6d323ba3 Mon Sep 17 00:00:00 2001 From: Vegard Storheil Eriksen Date: Wed, 2 Mar 2011 22:25:09 +0100 Subject: Add ability to serve parts of a file. --- http_connection.cpp | 64 +++++++++++++++++++++++++++++++++++++++++++++++++++-- http_connection.h | 8 ++++++- http_static.cpp | 8 ++----- music.cpp | 31 ++++++++++++++++++++++++-- 4 files changed, 100 insertions(+), 11 deletions(-) diff --git a/http_connection.cpp b/http_connection.cpp index 041cd05..daba10b 100644 --- a/http_connection.cpp +++ b/http_connection.cpp @@ -1,17 +1,24 @@ #include "http_connection.h" #include +#include #include #include +#include +#include + +#include namespace response_map_init { typedef std::pair P; const P m[] = { P(200, "OK"), + P(206, "Partial Content"), P(400, "Bad Request"), P(403, "Forbidden"), P(404, "Not Found"), + P(416, "Requested Range Not Satisfiable"), P(500, "Internal Server Error"), P(501, "Not Implemented") }; @@ -52,6 +59,59 @@ void HTTP::Connection::send_data(std::istream& stream) { } } +void HTTP::Connection::send_file(const fs::path& filename) { + //req->add_header("Content-Type", "application/octet-stream"); + + fs::ifstream is(filename, std::ios::in | std::ios::binary); + is.seekg(0, std::ios::end); + std::size_t length = is.tellg(); + is.seekg(0, std::ios::beg); + + // Simple Range-parser. + // TODO: Support other formats than a single range. + if(headers.count("Range")) { + std::string range_str = headers["Range"]; + + if(!boost::starts_with(range_str, "bytes=")) { + // Broken header? We can't cope with that. + return; + } + + range_str = range_str.substr(6); + + std::string::iterator it = std::find(range_str.begin(), range_str.end(), '-'); + + std::size_t begin = boost::lexical_cast(std::string(range_str.begin(), it)); + std::size_t end = boost::lexical_cast(std::string(it + 1, range_str.end())) + 1; + + // Is the range past the end of the file? + if(end > length) { + end = length; + } + + // Do the file contain requested range? + if(end && end < begin || begin > length) { + send_error(416); + } + + add_header("Content-Range", boost::str(boost::format("bytes %d-%d/%d") % begin % (end - 1) % length)); + add_header("Content-Length", boost::str(boost::format("%d") % (end - begin))); + + write_headers(206); + + is.seekg(begin); + + char* buf = new char[end - begin]; + is.read(buf, end - begin); + send_data(buf, is.gcount()); + delete buf; + + } else { + add_header("Content-Length", boost::str(boost::format("%d") % length)); + send_data(is); + } +} + HTTP::Connection::Connection(boost::asio::io_service& io_service) : socket(io_service) { headers_written = false; } @@ -114,13 +174,13 @@ std::string HTTP::Connection::pop_path_base() { return base_path.back(); } -void HTTP::Connection::write_headers() { +void HTTP::Connection::write_headers(int code) { if(headers_written) { return; } headers_written = true; - boost::asio::write(socket, boost::asio::buffer(std::string("HTTP/1.1 200 OK\r\n"))); + boost::asio::write(socket, boost::asio::buffer(boost::str(boost::format("HTTP/1.1 %d %s\r\n") % code % response_map.find(code)->second))); for(HeaderList::iterator it = response_headers.begin(); it != response_headers.end(); it++) { boost::asio::write(socket, boost::asio::buffer(boost::str(boost::format("%s: %s\r\n") % it->first % it->second))); diff --git a/http_connection.h b/http_connection.h index d810254..f5cd01a 100644 --- a/http_connection.h +++ b/http_connection.h @@ -11,6 +11,9 @@ #include using boost::asio::ip::tcp; +#include +namespace fs = boost::filesystem; + #include namespace HTTP { @@ -54,6 +57,9 @@ namespace HTTP { void send_data(const void* data, std::size_t size); void send_data(std::istream& stream); + //! Send file. + void send_file(const fs::path& filename); + private: typedef std::vector > HeaderList; @@ -76,7 +82,7 @@ namespace HTTP { bool parse_request(boost::asio::streambuf& buf); //! Write response headers. - void write_headers(); + void write_headers(int code = 200); //! Response headers written? bool headers_written; diff --git a/http_static.cpp b/http_static.cpp index 7f56744..a0856f4 100644 --- a/http_static.cpp +++ b/http_static.cpp @@ -19,12 +19,8 @@ void HTTP::Static::operator()(Connection::p connection) { } if(fs::is_regular_file(path)) { - fs::ifstream is(path, std::ios::in | std::ios::binary); - is.seekg(0, std::ios::end); - connection->add_header("content-length", boost::str(boost::format("%d") % is.tellg())); - is.seekg(0, std::ios::beg); - - connection->send_data(is); + connection->send_file(path); + } else if(fs::exists(path)) { connection->send_error(403); } else { diff --git a/music.cpp b/music.cpp index ee9f2ba..e277ee5 100644 --- a/music.cpp +++ b/music.cpp @@ -247,8 +247,19 @@ MusicDirectory::MusicDirectory(const fs::path root) { } void MusicTrack::render(HTTP::Connection::p req) { - req->add_header("content-type", "application/octet-stream"); - + req->add_header("Content-Type", "audio/mpeg"); + + int skip = 0; + int range = 0; + + if(req->headers.count("Range")) { + if(req->headers["Range"] == "bytes=0-1024") { + range = 1025; + } else { + skip = 1025; + } + } + if(req->args.count("decoder") && req->args.count("encoder")) { Decoder::p decoder = Decoder::get(req->args["decoder"], path.string()); Encoder::p encoder = Encoder::get(req->args["encoder"], decoder); @@ -256,6 +267,19 @@ void MusicTrack::render(HTTP::Connection::p req) { // TODO: Make an encoder-to-istream adapter to get rid of this: char data[0x10000]; std::streamsize size = 1; + + if(skip) { + size = encoder->read(data, skip); + } + + if(range) { + size = encoder->read(data, range); + if(size > 0) { + req->send_data(data, size); + } + return; + } + while(size) { size = encoder->read(data, 0x10000); if(size > 0) @@ -263,11 +287,14 @@ void MusicTrack::render(HTTP::Connection::p req) { } } else { + req->send_file(path); + /* fs::ifstream is(path, std::ios::in | std::ios::binary); is.seekg(0, std::ios::end); req->add_header("content-length", boost::str(boost::format("%d") % is.tellg())); is.seekg(0, std::ios::beg); req->send_data(is); + */ } } -- cgit v1.2.3