Generic HTTP handler

# Conflicts:
#	src/output/output_mp4.cpp
#	src/output/output_mp4.h
This commit is contained in:
Thulinma 2021-04-22 16:35:11 +02:00
parent 6f6827607d
commit 9953cd6ee2
10 changed files with 129 additions and 191 deletions

View file

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

View file

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

View file

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

View file

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

View file

@ -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(){

View file

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

View file

@ -1,5 +1,6 @@
#include "output_http.h"
#include <mist/checksum.h>
#include <mist/encode.h>
#include <mist/langcodes.h>
#include <mist/stream.h>
#include <mist/util.h>
@ -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());

View file

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

View file

@ -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<size_t, Comms::Users>::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){

View file

@ -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<size_t>& sortedTracks, std::set<keyPart>& 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; }