diff --git a/server/WebApi.cpp b/server/WebApi.cpp index 036c1977..9a4fcdb7 100644 --- a/server/WebApi.cpp +++ b/server/WebApi.cpp @@ -616,12 +616,7 @@ void installWebApi() { //测试url http://127.0.0.1/index/api/downloadBin API_REGIST_INVOKER(api,downloadBin,{ CHECK_SECRET(); - auto body = std::make_shared(exePath()); - if(!body->remainSize()){ - invoker("404 Not Found", HttpSession::KeyValue(), ""); - return; - } - invoker("200 OK", HttpSession::KeyValue(), body); + invoker.responseFile(headerIn,StrCaseMap(),exePath()); }); ////////////以下是注册的Hook API//////////// diff --git a/src/Http/HttpSession.cpp b/src/Http/HttpSession.cpp index 03e00b33..d58825d5 100644 --- a/src/Http/HttpSession.cpp +++ b/src/Http/HttpSession.cpp @@ -59,47 +59,50 @@ string dateStr() { strftime(buf, sizeof buf, "%a, %b %d %Y %H:%M:%S GMT", gmtime(&tt)); return buf; } -static const char* -get_mime_type(const char* name) { - const char* dot; - dot = strrchr(name, '.'); - static HttpSession::KeyValue mapType; - static onceToken token([&]() { - mapType.emplace(".html","text/html"); - mapType.emplace(".htm","text/html"); - mapType.emplace(".mp4","video/mp4"); - mapType.emplace(".m3u8","application/vnd.apple.mpegurl"); - mapType.emplace(".jpg","image/jpeg"); - mapType.emplace(".jpeg","image/jpeg"); - mapType.emplace(".gif","image/gif"); - mapType.emplace(".png","image/png"); - mapType.emplace(".ico","image/x-icon"); - mapType.emplace(".css","text/css"); - mapType.emplace(".js","application/javascript"); - mapType.emplace(".au","audio/basic"); - mapType.emplace(".wav","audio/wav"); - mapType.emplace(".avi","video/x-msvideo"); - mapType.emplace(".mov","video/quicktime"); - mapType.emplace(".qt","video/quicktime"); - mapType.emplace(".mpeg","video/mpeg"); - mapType.emplace(".mpe","video/mpeg"); - mapType.emplace(".vrml","model/vrml"); - mapType.emplace(".wrl","model/vrml"); - mapType.emplace(".midi","audio/midi"); - mapType.emplace(".mid","audio/midi"); - mapType.emplace(".mp3","audio/mpeg"); - mapType.emplace(".ogg","application/ogg"); - mapType.emplace(".pac","application/x-ns-proxy-autoconfig"); - mapType.emplace(".flv","video/x-flv"); - }, nullptr); - if(!dot){ - return "text/plain"; - } - auto it = mapType.find(dot); - if (it == mapType.end()) { - return "text/plain"; - } - return it->second.data(); + +const char *HttpSession::get_mime_type(const char *name) { + const char *dot; + dot = strrchr(name, '.'); + static HttpSession::KeyValue mapType; + static onceToken token([&]() { + mapType.emplace(".html", "text/html"); + mapType.emplace(".htm", "text/html"); + mapType.emplace(".mp4", "video/mp4"); + mapType.emplace(".mkv", "video/x-matroska"); + mapType.emplace(".rmvb", "application/vnd.rn-realmedia"); + mapType.emplace(".rm", "application/vnd.rn-realmedia"); + mapType.emplace(".m3u8", "application/vnd.apple.mpegurl"); + mapType.emplace(".jpg", "image/jpeg"); + mapType.emplace(".jpeg", "image/jpeg"); + mapType.emplace(".gif", "image/gif"); + mapType.emplace(".png", "image/png"); + mapType.emplace(".ico", "image/x-icon"); + mapType.emplace(".css", "text/css"); + mapType.emplace(".js", "application/javascript"); + mapType.emplace(".au", "audio/basic"); + mapType.emplace(".wav", "audio/wav"); + mapType.emplace(".avi", "video/x-msvideo"); + mapType.emplace(".mov", "video/quicktime"); + mapType.emplace(".qt", "video/quicktime"); + mapType.emplace(".mpeg", "video/mpeg"); + mapType.emplace(".mpe", "video/mpeg"); + mapType.emplace(".vrml", "model/vrml"); + mapType.emplace(".wrl", "model/vrml"); + mapType.emplace(".midi", "audio/midi"); + mapType.emplace(".mid", "audio/midi"); + mapType.emplace(".mp3", "audio/mpeg"); + mapType.emplace(".ogg", "application/ogg"); + mapType.emplace(".pac", "application/x-ns-proxy-autoconfig"); + mapType.emplace(".flv", "video/x-flv"); + }, nullptr); + if (!dot) { + return "text/plain"; + } + auto it = mapType.find(dot); + if (it == mapType.end()) { + return "text/plain"; + } + return it->second.data(); } //////////////////////////////////////////////////////////////////////////////////////////////////// @@ -132,6 +135,54 @@ HttpResponseInvokerImp::HttpResponseInvokerImp(const HttpResponseInvokerImp::Htt }; } +void HttpResponseInvokerImp::responseFile(const StrCaseMap &requestHeader, + const StrCaseMap &responseHeader, + const string &filePath) const { + try { + struct stat tFileStat; + if (filePath.empty() || 0 != stat(filePath.data(), &tFileStat)) { + //文件不存在 + throw std::runtime_error("file not exited"); + } + + auto &strRange = const_cast(requestHeader)["Range"]; + int64_t iRangeStart = 0, iRangeEnd = 0; + iRangeStart = atoll(FindField(strRange.data(), "bytes=", "-").data()); + iRangeEnd = atoll(FindField(strRange.data(), "-", "\r\n").data()); + if (iRangeEnd == 0) { + iRangeEnd = tFileStat.st_size - 1; + } + const char *pcHttpResult = NULL; + StrCaseMap &httpHeader = const_cast(responseHeader); + if (strRange.size() == 0) { + //全部下载 + pcHttpResult = "200 OK"; + } else { + //分节下载 + pcHttpResult = "206 Partial Content"; + //分节下载返回Content-Range头 + httpHeader.emplace("Content-Range", StrPrinter << "bytes " << iRangeStart << "-" << iRangeEnd << "/" << tFileStat.st_size << endl); + } + + std::shared_ptr fp(fopen(filePath.data(), "rb"), [](FILE *fp) { + if (fp) { + fclose(fp); + } + }); + if (!fp) { + //打开文件失败 + throw std::runtime_error(StrPrinter << "open file not failed:" << get_uv_errmsg(false)); + } + + //回复文件 + HttpBody::Ptr fileBody = std::make_shared(fp, iRangeStart, iRangeEnd - iRangeStart + 1); + (*this)(pcHttpResult, httpHeader, fileBody); + + }catch (std::exception &ex){ + (*this)("404 Not Found", responseHeader, ex.what()); + } +} + HttpResponseInvokerImp::operator bool(){ return _lambad.operator bool(); } @@ -163,7 +214,7 @@ int64_t HttpSession::onRecvHeader(const char *header,uint64_t len) { string cmd = _parser.Method(); auto it = g_mapCmdIndex.find(cmd); if (it == g_mapCmdIndex.end()) { - sendResponse("403 Forbidden", makeHttpHeader(true), ""); + sendResponse("403 Forbidden", true); shutdown(SockException(Err_shutdown,StrPrinter << "403 Forbidden:" << cmd)); return 0; } @@ -248,7 +299,7 @@ bool HttpSession::checkWebSocket(){ } auto Sec_WebSocket_Accept = encodeBase64(SHA1::encode_bin(Sec_WebSocket_Key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11")); - KeyValue headerOut = makeHttpHeader(); + KeyValue headerOut; headerOut["Upgrade"] = "websocket"; headerOut["Connection"] = "Upgrade"; headerOut["Sec-WebSocket-Accept"] = Sec_WebSocket_Accept; @@ -258,7 +309,7 @@ bool HttpSession::checkWebSocket(){ auto res_cb = [this,headerOut](){ _flv_over_websocket = true; - sendResponse("101 Switching Protocols",headerOut,""); + sendResponse("101 Switching Protocols",false,nullptr,headerOut,nullptr,false); }; //判断是否为websocket-flv @@ -269,11 +320,11 @@ bool HttpSession::checkWebSocket(){ //如果checkLiveFlvStream返回false,则代表不是websocket-flv,而是普通的websocket连接 if(!onWebSocketConnect(_parser)){ - sendResponse("501 Not Implemented",headerOut,""); + sendResponse("501 Not Implemented",true, nullptr, headerOut); shutdown(SockException(Err_shutdown,"WebSocket server not implemented")); return true; } - sendResponse("101 Switching Protocols",headerOut,""); + sendResponse("101 Switching Protocols",false, nullptr,headerOut); return true; } //http-flv 链接格式:http://vhost-url:port/app/streamid.flv?key1=value1&key2=value2 @@ -320,14 +371,14 @@ bool HttpSession::checkLiveFlvStream(const function &cb){ auto onRes = [this,rtmp_src,cb](const string &err){ bool authSuccess = err.empty(); if(!authSuccess){ - sendResponse("401 Unauthorized", makeHttpHeader(true,err.size()),err); + sendResponse("401 Unauthorized", true, nullptr, KeyValue(), std::make_shared(err)); shutdown(SockException(Err_shutdown,StrPrinter << "401 Unauthorized:" << err)); return ; } if(!cb) { //找到rtmp源,发送http头,负载后续发送 - sendResponse("200 OK", makeHttpHeader(false, -1, get_mime_type(".flv")), ""); + sendResponse("200 OK", false, get_mime_type(".flv"),KeyValue(),nullptr,false); }else{ cb(); } @@ -571,11 +622,11 @@ void HttpSession::Handle_Req_GET(int64_t &content_len) { if(!errMsg.empty()){ const_cast(strMeun) = errMsg; } - auto headerOut = makeHttpHeader(bClose,strMeun.size()); + KeyValue headerOut; if(cookie){ headerOut["Set-Cookie"] = cookie->getCookie((*cookie)[kCookiePathKey].get()); } - sendResponse(errMsg.empty() ? "200 OK" : "401 Unauthorized" , headerOut, strMeun); + sendResponse(errMsg.empty() ? "200 OK" : "401 Unauthorized" ,bClose, "text/html", headerOut, std::make_shared(strMeun)); throw SockException(bClose ? Err_shutdown : Err_success,"close connection after access folder"); }); return; @@ -594,64 +645,28 @@ void HttpSession::Handle_Req_GET(int64_t &content_len) { sendNotFound(bClose); throw SockException(bClose ? Err_shutdown : Err_success,"close connection after send 404 not found on file"); } - //文件智能指针,防止退出时未关闭 - std::shared_ptr pFilePtr(fopen(strFile.data(), "rb"), [](FILE *pFile) { - if(pFile){ - fclose(pFile); - } - }); - - if (!pFilePtr) { - //打开文件失败 - sendNotFound(bClose); - throw SockException(bClose ? Err_shutdown : Err_success,"close connection after send 404 not found on open file failed"); - } auto parser = _parser; //判断是否有权限访问该文件 - canAccessPath(_parser.Url(),false,[this,parser,tFileStat,pFilePtr,bClose,strFile](const string &errMsg,const HttpServerCookie::Ptr &cookie){ + canAccessPath(_parser.Url(),false,[this,parser,tFileStat,bClose,strFile](const string &errMsg,const HttpServerCookie::Ptr &cookie){ if(!errMsg.empty()){ - auto headerOut = makeHttpHeader(bClose,errMsg.size()); + KeyValue headerOut; if(cookie){ headerOut["Set-Cookie"] = cookie->getCookie((*cookie)[kCookiePathKey].get()); } - sendResponse("401 Unauthorized" , headerOut, errMsg); + sendResponse("401 Unauthorized" ,bClose, nullptr, headerOut, std::make_shared(errMsg)); throw SockException(bClose ? Err_shutdown : Err_success,"close connection after access file failed"); } - //判断是不是分节下载 - auto &strRange = parser["Range"]; - int64_t iRangeStart = 0, iRangeEnd = 0; - iRangeStart = atoll(FindField(strRange.data(), "bytes=", "-").data()); - iRangeEnd = atoll(FindField(strRange.data(), "-", "\r\n").data()); - if (iRangeEnd == 0) { - iRangeEnd = tFileStat.st_size - 1; - } - auto httpHeader = makeHttpHeader(bClose, iRangeEnd - iRangeStart + 1, get_mime_type(strFile.data())); - const char *pcHttpResult = NULL; - if (strRange.size() == 0) { - //全部下载 - pcHttpResult = "200 OK"; - } else { - //分节下载 - pcHttpResult = "206 Partial Content"; - //分节下载返回Content-Range头 - httpHeader.emplace("Content-Range",StrPrinter<<"bytes " << iRangeStart << "-" << iRangeEnd << "/" << tFileStat.st_size<< endl); - } - + KeyValue httpHeader; if(cookie){ httpHeader["Set-Cookie"] = cookie->getCookie((*cookie)[kCookiePathKey].get()); } - if (iRangeEnd - iRangeStart < 0) { - sendResponse(pcHttpResult,httpHeader,""); - //文件是空的! - throw SockException(bClose ? Err_shutdown : Err_success,"close connection after access file"); - } - - //先回复HTTP头部分 - HttpBody::Ptr fileBody = std::make_shared(pFilePtr,iRangeStart,iRangeEnd - iRangeStart + 1); - sendResponse(pcHttpResult,httpHeader,fileBody,bClose); + HttpResponseInvoker invoker = [this,bClose,&strFile](const string &codeOut, const KeyValue &headerOut, const HttpBody::Ptr &body){ + sendResponse(codeOut.data(), bClose, get_mime_type(strFile.data()), headerOut, body); + }; + invoker.responseFile(parser.getValues(),httpHeader,strFile); }); } @@ -754,22 +769,77 @@ bool makeMeun(const string &httpPath,const string &strFullPath, string &strRet) return true; } -void HttpSession::sendResponse(const char* pcStatus, const KeyValue& header, const string& strContent) { - sendResponse(pcStatus, header,strContent.empty() ? nullptr : std::make_shared(strContent), false); -} -void HttpSession::sendResponse(const char *pcStatus,const KeyValue &header,const HttpBody::Ptr &body,bool bClose){ +void HttpSession::sendResponse(const char *pcStatus, + bool bClose, + const char *pcContentType, + const HttpSession::KeyValue &header, + const HttpBody::Ptr &body, + bool set_content_len ){ + + GET_CONFIG(string,charSet,Http::kCharSet); + GET_CONFIG(uint32_t,keepAliveSec,Http::kKeepAliveSecond); + GET_CONFIG(uint32_t,reqCnt,Http::kMaxReqCount); + + //body默认为空 + int64_t size = 0; + if (body && body->remainSize()) { + //有body,获取body大小 + size = body->remainSize(); + if (size >= INT64_MAX) { + //不固定长度的body,那么不设置content-length字段 + size = -1; + } + } + + if(!set_content_len || size == -1){ + //如果是不定长度body,或者不设置conten-length, + //那么一定是Keep-Alive类型 + bClose = false; + } + + HttpSession::KeyValue &headerOut = const_cast(header); + headerOut.emplace("Date", dateStr()); + headerOut.emplace("Server", SERVER_NAME); + headerOut.emplace("Connection", bClose ? "close" : "keep-alive"); + if(!bClose){ + headerOut.emplace("Keep-Alive",StrPrinter << "timeout=" << keepAliveSec << ", max=" << reqCnt << endl); + } + + if(!_origin.empty()){ + //设置跨域 + headerOut.emplace("Access-Control-Allow-Origin",_origin); + headerOut.emplace("Access-Control-Allow-Credentials", "true"); + } + + if(set_content_len && size >= 0){ + //文件长度为定值或者,且不是http-flv设置Content-Length + headerOut.emplace("Content-Length", StrPrinter << size <remainSize()){ + if(!size){ //没有body if(bClose){ shutdown(SockException(Err_shutdown,"close connection after send http header completed")); @@ -832,34 +902,6 @@ void HttpSession::sendResponse(const char *pcStatus,const KeyValue &header,const _sock->setOnFlush(onFlush); } - -HttpSession::KeyValue HttpSession::makeHttpHeader(bool bClose, int64_t iContentSize,const char* pcContentType) { - KeyValue headerOut; - GET_CONFIG(string,charSet,Http::kCharSet); - GET_CONFIG(uint32_t,keepAliveSec,Http::kKeepAliveSecond); - GET_CONFIG(uint32_t,reqCnt,Http::kMaxReqCount); - - headerOut.emplace("Date", dateStr()); - headerOut.emplace("Server", SERVER_NAME); - headerOut.emplace("Connection", bClose ? "close" : "keep-alive"); - if(!bClose){ - headerOut.emplace("Keep-Alive",StrPrinter << "timeout=" << keepAliveSec << ", max=" << reqCnt << endl); - } - if(pcContentType){ - auto strContentType = StrPrinter << pcContentType << "; charset=" << charSet << endl; - headerOut.emplace("Content-Type",strContentType); - } - if(iContentSize >= 0){ - headerOut.emplace("Content-Length", StrPrinter<remainSize()) { - //有body,获取body大小 - size = body->remainSize(); - if (size >= INT64_MAX) { - //不固定长度的body,那么不设置content-length字段 - size = -1; - } - } - - auto headerOther = strongSelf->makeHttpHeader(bClose, size, "text/plain"); - for (auto &pr : headerOther) { - //添加默认http头,默认http头不能覆盖用户自定义的头 - const_cast(headerOut).emplace(pr.first, pr.second); - } - strongSelf->sendResponse(codeOut.data(), headerOut, body, bClose); + strongSelf->sendResponse(codeOut.data(), bClose, nullptr, headerOut, body); }); }; ///////////////////广播HTTP事件/////////////////////////// @@ -1012,7 +1038,7 @@ void HttpSession::Handle_Req_POST(int64_t &content_len) { void HttpSession::sendNotFound(bool bClose) { GET_CONFIG(string,notFound,Http::kNotFound); - sendResponse("404 Not Found", makeHttpHeader(bClose, notFound.size()), notFound); + sendResponse("404 Not Found", bClose,"text/html",KeyValue(),std::make_shared(notFound)); } void HttpSession::setSocketFlags(){ diff --git a/src/Http/HttpSession.h b/src/Http/HttpSession.h index e6276840..98f234c3 100644 --- a/src/Http/HttpSession.h +++ b/src/Http/HttpSession.h @@ -61,6 +61,7 @@ public: void operator()(const string &codeOut, const StrCaseMap &headerOut, const HttpBody::Ptr &body) const; void operator()(const string &codeOut, const StrCaseMap &headerOut, const string &body) const; + void responseFile(const StrCaseMap &requestHeader,const StrCaseMap &responseHeader,const string &filePath) const; operator bool(); private: HttpResponseInvokerLambda0 _lambad; @@ -89,6 +90,7 @@ public: virtual void onManager() override; static string urlDecode(const string &str); + static const char* get_mime_type(const char* name); protected: //FlvMuxer override void onWrite(const Buffer::Ptr &data) override ; @@ -140,10 +142,9 @@ private: bool emitHttpEvent(bool doInvoke); void urlDecode(Parser &parser); void sendNotFound(bool bClose); - void sendResponse(const char *pcStatus,const KeyValue &header,const string &strContent); - void sendResponse(const char *pcStatus,const KeyValue &header,const HttpBody::Ptr &body,bool bClose); - KeyValue makeHttpHeader(bool bClose=false,int64_t iContentSize=-1,const char *pcContentType="text/html"); - + void sendResponse(const char *pcStatus, bool bClose, const char *pcContentType = nullptr, + const HttpSession::KeyValue &header = HttpSession::KeyValue(), + const HttpBody::Ptr &body = nullptr,bool set_content_len = true); /** * 判断http客户端是否有权限访问文件的逻辑步骤 *