diff --git a/lib/http_parser.cpp b/lib/http_parser.cpp index 373e0f77..7408c016 100644 --- a/lib/http_parser.cpp +++ b/lib/http_parser.cpp @@ -274,7 +274,7 @@ void HTTP::Parser::SendResponse(std::string code, std::string message, Socket::C /// a zero-content-length HTTP/1.0 response. \param code The HTTP response code. Usually you want /// 200. \param message The HTTP response message. Usually you want "OK". \param request The HTTP /// request to respond to. \param conn The connection to send over. -void HTTP::Parser::StartResponse(std::string code, std::string message, HTTP::Parser &request, +void HTTP::Parser::StartResponse(std::string code, std::string message, const HTTP::Parser &request, Socket::Connection &conn, bool bufferAllChunks){ std::string prot = request.protocol; sendingChunks = diff --git a/lib/http_parser.h b/lib/http_parser.h index af6a83ab..9c843f20 100644 --- a/lib/http_parser.h +++ b/lib/http_parser.h @@ -42,7 +42,7 @@ namespace HTTP{ void sendRequest(Socket::Connection &conn, const void *body = 0, const size_t bodyLen = 0, bool allAtOnce = false); void SendResponse(std::string code, std::string message, Socket::Connection &conn); - void StartResponse(std::string code, std::string message, Parser &request, + void StartResponse(std::string code, std::string message, const Parser &request, Socket::Connection &conn, bool bufferAllChunks = false); void StartResponse(Parser &request, Socket::Connection &conn, bool bufferAllChunks = false); void Chunkify(const std::string &bodypart, Socket::Connection &conn); diff --git a/src/output/output_aac.cpp b/src/output/output_aac.cpp index bd04b0ad..22508d2a 100644 --- a/src/output/output_aac.cpp +++ b/src/output/output_aac.cpp @@ -24,8 +24,6 @@ namespace Mist{ cfg->addOption("target", opt); } - bool OutAAC::isRecording(){return config->getString("target").size();} - void OutAAC::initialSeek(){ if (!meta){return;} maxSkipAhead = 30000; @@ -42,40 +40,20 @@ namespace Mist{ void OutAAC::sendNext(){ char *dataPointer = 0; size_t len = 0; - thisPacket.getString("data", dataPointer, len); std::string head = TS::getAudioHeader(len, M.getInit(thisIdx)); myConn.SendNow(head); myConn.SendNow(dataPointer, len); } - void OutAAC::sendHeader(){ - if (!isRecording()){ - H.Clean(); - H.SetHeader("Content-Type", "audio/aac"); - H.SetHeader("Accept-Ranges", "none"); - H.protocol = "HTTP/1.0"; - H.setCORSHeaders(); - H.SendResponse("200", "OK", myConn); + void OutAAC::respondHTTP(const HTTP::Parser & req, bool headersOnly){ + HTTPOutput::respondHTTP(req, headersOnly); + H.protocol = "HTTP/1.0"; + H.SendResponse("200", "OK", myConn); + if (!headersOnly){ + parseData = true; + wantRequest = false; } - sentHeader = true; - } - - void OutAAC::onHTTP(){ - std::string method = H.method; - if (method == "OPTIONS" || method == "HEAD"){ - H.Clean(); - H.SetHeader("Content-Type", "audio/aac"); - H.SetHeader("Accept-Ranges", "none"); - H.protocol = "HTTP/1.0"; - H.setCORSHeaders(); - H.SendResponse("200", "OK", myConn); - H.Clean(); - return; - } - - parseData = true; - wantRequest = false; } }// namespace Mist diff --git a/src/output/output_aac.h b/src/output/output_aac.h index 7bb52672..c28281a9 100644 --- a/src/output/output_aac.h +++ b/src/output/output_aac.h @@ -5,13 +5,11 @@ namespace Mist{ public: OutAAC(Socket::Connection &conn); static void init(Util::Config *cfg); - void onHTTP(); + void respondHTTP(const HTTP::Parser & req, bool headersOnly); void sendNext(); - void sendHeader(); void initialSeek(); private: - bool isRecording(); bool isFileTarget(){return isRecording();} }; }// namespace Mist diff --git a/src/output/output_ebml.cpp b/src/output/output_ebml.cpp index e1a6b428..e69cf876 100644 --- a/src/output/output_ebml.cpp +++ b/src/output/output_ebml.cpp @@ -391,17 +391,15 @@ namespace Mist{ // End of file. This probably won't work right, but who cares, it's the end of the file. } - void OutEBML::onHTTP(){ - std::string method = H.method; - if (method == "OPTIONS" || method == "HEAD"){ - H.Clean(); - H.setCORSHeaders(); - H.SetHeader("Content-Type", "video/MP4"); - H.SetHeader("Accept-Ranges", "bytes, parsec"); - H.SendResponse("200", "OK", myConn); - return; - } - if (H.url.find(".webm") != std::string::npos){ + void OutEBML::respondHTTP(const HTTP::Parser & req, bool headersOnly){ + //Set global defaults, first + HTTPOutput::respondHTTP(req, headersOnly); + + //If non-live, we accept range requests + if (!M.getLive()){H.SetHeader("Accept-Ranges", "bytes, parsec");} + + //We change the header's document type based on file extension + if (req.url.find(".webm") != std::string::npos){ doctype = "webm"; }else{ doctype = "matroska"; @@ -417,72 +415,46 @@ namespace Mist{ size_t byteEnd = totalSize - 1; size_t byteStart = 0; - - /*LTS-START*/ - // allow setting of max lead time through buffer variable. - // max lead time is set in MS, but the variable is in integer seconds for simplicity. - if (H.GetVar("buffer") != ""){maxSkipAhead = JSON::Value(H.GetVar("buffer")).asInt() * 1000;} - // allow setting of play back rate through buffer variable. - // play back rate is set in MS per second, but the variable is a simple multiplier. - if (H.GetVar("rate") != ""){ - long long int multiplier = JSON::Value(H.GetVar("rate")).asInt(); - if (multiplier){ - realTime = 1000 / multiplier; - }else{ - realTime = 0; - } - } - if (H.GetHeader("X-Mist-Rate") != ""){ - long long int multiplier = JSON::Value(H.GetHeader("X-Mist-Rate")).asInt(); - if (multiplier){ - realTime = 1000 / multiplier; - }else{ - realTime = 0; - } - } - /*LTS-END*/ - - char rangeType = ' '; - if (M.getVod()){ - if (H.GetHeader("Range") != ""){ - if (parseRange(byteStart, byteEnd)){ - if (H.GetVar("buffer") == ""){ - size_t idx = getMainSelectedTrack(); - maxSkipAhead = (M.getLastms(idx) - M.getFirstms(idx)) / 20 + 7500; - } + if (!M.getLive() && req.GetHeader("Range") != ""){ + //Range request + if (parseRange(req.GetHeader("Range"), byteStart, byteEnd)){ + if (!req.GetVar("buffer").size()){ + size_t idx = getMainSelectedTrack(); + maxSkipAhead = (M.getLastms(idx) - M.getFirstms(idx)) / 20 + 7500; } - rangeType = H.GetHeader("Range")[0]; } - } - H.Clean(); // make sure no parts of old requests are left in any buffers - H.setCORSHeaders(); - H.SetHeader("Content-Type", "video/webm"); - if (M.getVod()){H.SetHeader("Accept-Ranges", "bytes, parsec");} - if (rangeType != ' '){ + //Failed range request if (!byteEnd){ - if (rangeType == 'p'){ - H.SetBody("Starsystem not in communications range"); + if (req.GetHeader("Range")[0] == 'p'){ + if (!headersOnly){H.SetBody("Starsystem not in communications range");} H.SendResponse("416", "Starsystem not in communications range", myConn); return; } - H.SetBody("Requested Range Not Satisfiable"); + if (!headersOnly){H.SetBody("Requested Range Not Satisfiable");} H.SendResponse("416", "Requested Range Not Satisfiable", myConn); return; } + //Successful range request std::stringstream rangeReply; rangeReply << "bytes " << byteStart << "-" << byteEnd << "/" << totalSize; H.SetHeader("Content-Length", byteEnd - byteStart + 1); H.SetHeader("Content-Range", rangeReply.str()); /// \todo Switch to chunked? H.SendResponse("206", "Partial content", myConn); - byteSeek(byteStart); + if (!headersOnly){ + byteSeek(byteStart); + } }else{ - if (M.getVod()){H.SetHeader("Content-Length", byteEnd - byteStart + 1);} + //Non-range request + if (!M.getLive()){H.SetHeader("Content-Length", byteEnd - byteStart + 1);} /// \todo Switch to chunked? H.SendResponse("200", "OK", myConn); } - parseData = true; - wantRequest = false; + //Start outputting data + if (!headersOnly){ + parseData = true; + wantRequest = false; + } } void OutEBML::calcVodSizes(){ diff --git a/src/output/output_ebml.h b/src/output/output_ebml.h index 4250e85a..bc121263 100644 --- a/src/output/output_ebml.h +++ b/src/output/output_ebml.h @@ -6,7 +6,7 @@ namespace Mist{ public: OutEBML(Socket::Connection &conn); static void init(Util::Config *cfg); - void onHTTP(); + void respondHTTP(const HTTP::Parser & req, bool headersOnly); void sendNext(); void sendHeader(); size_t clusterSize(uint64_t start, uint64_t end); diff --git a/src/output/output_http.cpp b/src/output/output_http.cpp index 47ebe400..c69c9e32 100644 --- a/src/output/output_http.cpp +++ b/src/output/output_http.cpp @@ -1,5 +1,6 @@ #include "output_http.h" #include +#include #include #include #include @@ -221,8 +222,6 @@ namespace Mist{ myConn.close(); return; } - std::string connHeader = H.GetHeader("Connection"); - Util::stringToLower(connHeader); if (handler != capa["name"].asStringRef() || H.GetVar("stream") != streamName){ MEDIUM_MSG("Switching from %s (%s) to %s (%s)", capa["name"].asStringRef().c_str(), streamName.c_str(), handler.c_str(), H.GetVar("stream").c_str()); @@ -263,8 +262,12 @@ namespace Mist{ if (H.GetVar("stop") != ""){targetParams["stop"] = H.GetVar("stop");} if (H.GetVar("startunix") != ""){targetParams["startunix"] = H.GetVar("startunix");} if (H.GetVar("stopunix") != ""){targetParams["stopunix"] = H.GetVar("stopunix");} - if (H.GetVar("buffer") != ""){targetParams["buffer"] = H.GetVar("buffer");} - // allow setting of play back rate through buffer variable. + // allow setting of max lead time through buffer variable. + // max lead time is set in MS, but the variable is in integer seconds for simplicity. + if (H.GetVar("buffer") != ""){ + maxSkipAhead = JSON::Value(H.GetVar("buffer")).asInt() * 1000; + } + // allow setting of play back rate through rate variable or custom HTTP header. // play back rate is set in MS per second, but the variable is a simple multiplier. if (H.GetVar("rate") != ""){ long long int multiplier = JSON::Value(H.GetVar("rate")).asInt(); @@ -301,18 +304,58 @@ namespace Mist{ } responded = false; preHTTP(); + if (!myConn){return;} onHTTP(); idleLast = Util::bootMS(); if (!H.bufferChunks){H.Clean();} } } + /// Default HTTP handler. + /// Only takes care of OPTIONS and HEAD, saving the original request, and calling respondHTTP + void HTTPOutput::onHTTP(){ + const HTTP::Parser reqH = H; + bool headersOnly = (reqH.method == "OPTIONS" || reqH.method == "HEAD"); + H.Clean(); + respondHTTP(reqH, headersOnly); + } + /// Default implementation of preHTTP simply calls initialize and selectDefaultTracks. void HTTPOutput::preHTTP(){ initialize(); selectDefaultTracks(); } + /// Sets common HTTP headers. Virtual, so child classes can/will implement further behaviour if needed. + /// Child classes are suggested to call the parent implementation and then add their own logic afterwards. + void HTTPOutput::respondHTTP(const HTTP::Parser & req, bool headersOnly){ + //We generally want the CORS headers to be set for all responses + H.setCORSHeaders(); + //Set attachment header to force download, if applicable + if (req.GetVar("dl").size()){ + //If we want to download, and the string contains a dot, use as-is. + std::string dl = req.GetVar("dl"); + //Without a dot, we use the last segment of the bare URL + if (dl.find('.') == std::string::npos){ + dl = HTTP::URL(req.url).getFilePath(); + size_t lSlash = dl.rfind('/'); + if (lSlash != std::string::npos){dl = dl.substr(lSlash+1);} + } + H.SetHeader("Content-Disposition", "attachment; filename=" + Encodings::URL::encode(dl) + ";"); + //Force max download speed when downloading + realTime = 0; + } + //If there is a method defined, and the first method has a type with two slashes in it, + //assume the last two sections are the content type. + if (capa.isMember("methods") && capa["methods"].isArray() && capa["methods"].size() && capa["methods"][0u].isMember("type")){ + const std::string & cType = capa["methods"][0u]["type"].asStringRef(); + size_t fSlash = cType.find('/'); + if (fSlash != std::string::npos && cType.rfind('/') != fSlash){ + H.SetHeader("Content-Type", cType.substr(fSlash+1)); + } + } + } + static inline void builPipedPart(JSON::Value &p, char *argarr[], int &argnum, JSON::Value &argset){ jsonForEach(argset, it){ if (it->isMember("option") && p.isMember(it.key())){ @@ -488,8 +531,7 @@ namespace Mist{ /// Parses a "Range: " header, setting byteStart and byteEnd. /// Assumes byteStart and byteEnd are initialized to their minimum respectively maximum values /// when the function is called. On error, byteEnd is set to zero and the function return false. - bool HTTPOutput::parseRange(uint64_t &byteStart, uint64_t &byteEnd){ - std::string header = H.GetHeader("Range"); + bool HTTPOutput::parseRange(std::string header, uint64_t &byteStart, uint64_t &byteEnd){ if (header.size() < 6 || header.substr(0, 6) != "bytes="){ byteEnd = 0; WARN_MSG("Invalid range header: %s", header.c_str()); diff --git a/src/output/output_http.h b/src/output/output_http.h index 4d922593..402bd742 100644 --- a/src/output/output_http.h +++ b/src/output/output_http.h @@ -12,7 +12,8 @@ namespace Mist{ virtual ~HTTPOutput(); static void init(Util::Config *cfg); virtual void onFail(const std::string &msg, bool critical = false); - virtual void onHTTP(){}; + virtual void onHTTP(); + virtual void respondHTTP(const HTTP::Parser & req, bool headersOnly); virtual void onIdle(){}; virtual void onWebsocketFrame(){}; virtual void onWebsocketConnect(){}; @@ -23,7 +24,7 @@ namespace Mist{ virtual bool doesWebsockets(){return false;} void reConnector(std::string &connector); std::string getHandler(); - bool parseRange(uint64_t &byteStart, uint64_t &byteEnd); + bool parseRange(std::string header, uint64_t &byteStart, uint64_t &byteEnd); protected: bool firstRun; diff --git a/src/output/output_mp4.cpp b/src/output/output_mp4.cpp index 525f5462..9ba6b522 100644 --- a/src/output/output_mp4.cpp +++ b/src/output/output_mp4.cpp @@ -1060,60 +1060,17 @@ namespace Mist{ H.Chunkify(mdatHeader, 8, myConn); } - void OutMP4::onHTTP(){ + void OutMP4::respondHTTP(const HTTP::Parser & req, bool headersOnly){ + //Set global defaults, first + HTTPOutput::respondHTTP(req, headersOnly); - if (webSock) { - return; - } - - std::string dl; - if (H.GetVar("dl").size()){ - dl = H.GetVar("dl"); - if (dl.find('.') == std::string::npos){dl = streamName + ".mp4";} - } - if (H.method == "OPTIONS" || H.method == "HEAD"){ - H.Clean(); - H.setCORSHeaders(); - if (dl.size()){ - H.SetHeader("Content-Disposition", "attachment; filename=" + Encodings::URL::encode(dl) + ";"); - } - H.SetHeader("Content-Type", "video/MP4"); - H.SetHeader("Accept-Ranges", "bytes, parsec"); - H.StartResponse(H, myConn); - return; - } + H.SetHeader("Content-Type", "video/MP4"); + if (!M.getLive()){H.SetHeader("Accept-Ranges", "bytes, parsec");} - chromeWorkaround = (H.GetHeader("User-Agent").find("Chrome") != std::string::npos && - H.GetHeader("User-Agent").find("Edge") == std::string::npos && - H.GetHeader("User-Agent").find("OPR/") == std::string::npos); + chromeWorkaround = (req.GetHeader("User-Agent").find("Chrome") != std::string::npos && + req.GetHeader("User-Agent").find("Edge") == std::string::npos && + req.GetHeader("User-Agent").find("OPR/") == std::string::npos); - /*LTS-START*/ - // allow setting of max lead time through buffer variable. - // max lead time is set in MS, but the variable is in integer seconds for simplicity. - if (H.GetVar("buffer") != ""){maxSkipAhead = JSON::Value(H.GetVar("buffer")).asInt() * 1000;} - // allow setting of play back rate through buffer variable. - // play back rate is set in MS per second, but the variable is a simple multiplier. - if (H.GetVar("rate") != ""){ - long long int multiplier = JSON::Value(H.GetVar("rate")).asInt(); - if (multiplier){ - realTime = 1000 / multiplier; - }else{ - realTime = 0; - } - } - if (H.GetHeader("X-Mist-Rate") != ""){ - long long int multiplier = JSON::Value(H.GetHeader("X-Mist-Rate")).asInt(); - if (multiplier){ - realTime = 1000 / multiplier; - }else{ - realTime = 0; - } - } - - /*LTS-END*/ - // Always initialize before anything else, and break if this failed. - initialize(); - if (!myConn){return;} uint32_t mainTrack = M.mainTrack(); if (mainTrack == INVALID_TRACK_ID){ onFail("No main track found", true); @@ -1122,9 +1079,9 @@ namespace Mist{ DTSC::Fragments fragments(M.fragments(mainTrack)); - if (H.GetVar("startfrag") != ""){ + if (req.GetVar("startfrag") != ""){ realTime = 0; - size_t startFrag = JSON::Value(H.GetVar("startfrag")).asInt(); + size_t startFrag = JSON::Value(req.GetVar("startfrag")).asInt(); if (startFrag >= fragments.getFirstValid() && startFrag < fragments.getEndValid()){ startTime = M.getTimeForFragmentIndex(mainTrack, startFrag); @@ -1140,8 +1097,8 @@ namespace Mist{ } } - if (H.GetVar("endfrag") != ""){ - size_t endFrag = JSON::Value(H.GetVar("endfrag")).asInt(); + if (req.GetVar("endfrag") != ""){ + size_t endFrag = JSON::Value(req.GetVar("endfrag")).asInt(); if (endFrag < fragments.getEndValid()){ endTime = M.getTimeForFragmentIndex(mainTrack, endFrag); }else{ @@ -1149,19 +1106,14 @@ namespace Mist{ } } - if (H.GetVar("starttime") != ""){ - startTime = std::max((uint64_t)JSON::Value(H.GetVar("starttime")).asInt(), M.getFirstms(mainTrack)); + if (req.GetVar("starttime") != ""){ + startTime = std::max((uint64_t)JSON::Value(req.GetVar("starttime")).asInt(), M.getFirstms(mainTrack)); } - if (H.GetVar("endtime") != ""){ - endTime = std::min((uint64_t)JSON::Value(H.GetVar("endtime")).asInt(), M.getLastms(mainTrack)); + if (req.GetVar("endtime") != ""){ + endTime = std::min((uint64_t)JSON::Value(req.GetVar("endtime")).asInt(), M.getLastms(mainTrack)); } - // Make sure we start receiving data after this function - parseData = true; - wantRequest = false; - sentHeader = false; - // Check if the url contains .3gp --> if yes, we will send a 3gp header sending3GP = (H.url.find(".3gp") != std::string::npos); @@ -1169,14 +1121,9 @@ namespace Mist{ headerSize = mp4HeaderSize(fileSize, M.getLive()); seekPoint = 0; - if (M.getLive()){ - // for live we use fragmented mode - fragSeqNum = 0; - } - byteStart = 0; - byteEnd = fileSize - 1; - char rangeType = ' '; - currPos = 0; + // for live we use fragmented mode + if (M.getLive()){fragSeqNum = 0;} + sortSet.clear(); for (std::map::const_iterator subIt = userSelect.begin(); subIt != userSelect.end(); subIt++){ @@ -1186,23 +1133,14 @@ namespace Mist{ temp.index = 0; sortSet.insert(temp); } - if (M.getVod() && H.GetHeader("Range") != ""){ - if (parseRange(byteStart, byteEnd)){findSeekPoint(byteStart, seekPoint, headerSize);} - rangeType = H.GetHeader("Range")[0]; - } - HTTP::Parser request = H; - H.Clean(); // make sure no parts of old requests are left in any buffers - H.setCORSHeaders(); - if (dl.size()){ - H.SetHeader("Content-Disposition", "attachment; filename=" + Encodings::URL::encode(dl) + ";"); - realTime = 0; // force max download speed when downloading - } - H.SetHeader("Content-Type", "video/MP4"); // Send the correct content-type for MP4 files - if (M.getVod()){H.SetHeader("Accept-Ranges", "bytes, parsec");} - if (rangeType != ' '){ + byteStart = 0; + byteEnd = fileSize - 1; + currPos = 0; + if (!M.getLive() && req.GetHeader("Range") != ""){ + if (parseRange(req.GetHeader("Range"), byteStart, byteEnd)){findSeekPoint(byteStart, seekPoint, headerSize);} if (!byteEnd){ - if (rangeType == 'p'){ + if (req.GetHeader("Range")[0] == 'p'){ H.SetBody("Starsystem not in communications range"); H.SendResponse("416", "Starsystem not in communications range", myConn); parseData = false; @@ -1220,12 +1158,21 @@ namespace Mist{ rangeReply << "bytes " << byteStart << "-" << byteEnd << "/" << fileSize; H.SetHeader("Content-Length", byteEnd - byteStart + 1); H.SetHeader("Content-Range", rangeReply.str()); - H.StartResponse("206", "Partial content", request, myConn); + H.StartResponse("206", "Partial content", req, myConn); } }else{ if (M.getVod()){H.SetHeader("Content-Length", byteEnd - byteStart + 1);} - H.StartResponse("200", "OK", request, myConn); + H.StartResponse("200", "OK", req, myConn); } + + if (headersOnly){return;} + + //Start sending data + parseData = true; + wantRequest = false; + sentHeader = false; + + //Send MP4 header if needed leftOver = byteEnd - byteStart + 1; // add one byte, because range "0-0" = 1 byte of data byteEnd++; if (byteStart < headerSize){ diff --git a/src/output/output_mp4.h b/src/output/output_mp4.h index 21855ef6..7d137f73 100644 --- a/src/output/output_mp4.h +++ b/src/output/output_mp4.h @@ -100,7 +100,7 @@ namespace Mist{ void findSeekPoint(uint64_t byteStart, uint64_t &seekPoint, uint64_t headerSize); void appendSinglePacketMoof(Util::ResizeablePointer& moofOut, size_t extraBytes = 0); size_t fragmentHeaderSize(std::deque& sortedTracks, std::set& trunOrder, uint64_t startFragmentTime, uint64_t endFragmentTime); - void onHTTP(); + void respondHTTP(const HTTP::Parser & req, bool headersOnly); void sendNext(); void sendHeader(); bool doesWebsockets() { return true; }