添加访问http目录权限事件

This commit is contained in:
xiongziliang 2019-06-12 17:53:48 +08:00
parent ce9a9188b2
commit 555601506b
9 changed files with 771 additions and 90 deletions

View File

@ -667,6 +667,12 @@ void installWebApi() {
val["close"] = true; val["close"] = true;
}); });
API_REGIST(hook,on_http_access,{
//能访问根目录10分钟
val["path"] = "/";
val["second"] = 10 * 60;
});
} }

View File

@ -38,6 +38,7 @@
#include "Http/HttpRequester.h" #include "Http/HttpRequester.h"
#include "Network/TcpSession.h" #include "Network/TcpSession.h"
#include "Rtsp/RtspSession.h" #include "Rtsp/RtspSession.h"
#include "Http/HttpSession.h"
using namespace Json; using namespace Json;
using namespace toolkit; using namespace toolkit;
@ -69,6 +70,7 @@ const char kOnStreamNotFound[] = HOOK_FIELD"on_stream_not_found";
const char kOnRecordMp4[] = HOOK_FIELD"on_record_mp4"; const char kOnRecordMp4[] = HOOK_FIELD"on_record_mp4";
const char kOnShellLogin[] = HOOK_FIELD"on_shell_login"; const char kOnShellLogin[] = HOOK_FIELD"on_shell_login";
const char kOnStreamNoneReader[] = HOOK_FIELD"on_stream_none_reader"; const char kOnStreamNoneReader[] = HOOK_FIELD"on_stream_none_reader";
const char kOnHttpAccess[] = HOOK_FIELD"on_http_access";
const char kAdminParams[] = HOOK_FIELD"admin_params"; const char kAdminParams[] = HOOK_FIELD"admin_params";
onceToken token([](){ onceToken token([](){
@ -84,6 +86,7 @@ onceToken token([](){
mINI::Instance()[kOnRecordMp4] = "https://127.0.0.1/index/hook/on_record_mp4"; mINI::Instance()[kOnRecordMp4] = "https://127.0.0.1/index/hook/on_record_mp4";
mINI::Instance()[kOnShellLogin] = "https://127.0.0.1/index/hook/on_shell_login"; mINI::Instance()[kOnShellLogin] = "https://127.0.0.1/index/hook/on_shell_login";
mINI::Instance()[kOnStreamNoneReader] = "https://127.0.0.1/index/hook/on_stream_none_reader"; mINI::Instance()[kOnStreamNoneReader] = "https://127.0.0.1/index/hook/on_stream_none_reader";
mINI::Instance()[kOnHttpAccess] = "https://127.0.0.1/index/hook/on_http_access";
mINI::Instance()[kAdminParams] = "secret=035c73f7-bb6b-4889-a715-d9eb2d1925cc"; mINI::Instance()[kAdminParams] = "secret=035c73f7-bb6b-4889-a715-d9eb2d1925cc";
},nullptr); },nullptr);
}//namespace Hook }//namespace Hook
@ -188,6 +191,8 @@ void installWebHook(){
GET_CONFIG(string,hook_record_mp4,Hook::kOnRecordMp4); GET_CONFIG(string,hook_record_mp4,Hook::kOnRecordMp4);
GET_CONFIG(string,hook_shell_login,Hook::kOnShellLogin); GET_CONFIG(string,hook_shell_login,Hook::kOnShellLogin);
GET_CONFIG(string,hook_stream_none_reader,Hook::kOnStreamNoneReader); GET_CONFIG(string,hook_stream_none_reader,Hook::kOnStreamNoneReader);
GET_CONFIG(string,hook_http_access,Hook::kOnHttpAccess);
NoticeCenter::Instance().addListener(nullptr,Broadcast::kBroadcastMediaPublish,[](BroadcastMediaPublishArgs){ NoticeCenter::Instance().addListener(nullptr,Broadcast::kBroadcastMediaPublish,[](BroadcastMediaPublishArgs){
if(!hook_enable || args._param_strs == hook_adminparams || hook_publish.empty() || sender.get_peer_ip() == "127.0.0.1"){ if(!hook_enable || args._param_strs == hook_adminparams || hook_publish.empty() || sender.get_peer_ip() == "127.0.0.1"){
@ -377,6 +382,37 @@ void installWebHook(){
}); });
NoticeCenter::Instance().addListener(nullptr,Broadcast::kBroadcastHttpAccess,[](BroadcastHttpAccessArgs){
if(!hook_enable || args._param_strs == hook_adminparams || hook_http_access.empty() ){
//这种情况下随便访问,先让他随便访问1分钟之后可能开启鉴权
invoker("/",60);
return;
}
ArgsType body;
body["ip"] = sender.get_peer_ip();
body["port"] = sender.get_peer_port();
body["id"] = sender.getIdentifier();
body["path"] = path;
body["is_dir"] = is_dir;
body["params"] = parser.Params();
body["content"] = parser.Content();
for(auto &pr : parser.getValues()){
body[string("header.") + pr.first] = pr.second;
}
//执行hook
do_http_hook(hook_http_access,body, [invoker](const Value &obj,const string &err){
if(!err.empty()){
//如果接口访问失败那么10秒内该客户端都没有访问http服务器的权限
invoker("",10);
return;
}
//path参数是该客户端能访问的根目录该目录下的所有文件它都能访问
//second参数规定该cookie超时时间,超过这个时间后,用户需要重新鉴权
invoker(obj["path"].asString(),obj["second"].asInt());
});
});
} }
void unInstallWebHook(){ void unInstallWebHook(){

View File

@ -57,6 +57,7 @@ namespace Broadcast {
const char kBroadcastMediaChanged[] = "kBroadcastMediaChanged"; const char kBroadcastMediaChanged[] = "kBroadcastMediaChanged";
const char kBroadcastRecordMP4[] = "kBroadcastRecordMP4"; const char kBroadcastRecordMP4[] = "kBroadcastRecordMP4";
const char kBroadcastHttpRequest[] = "kBroadcastHttpRequest"; const char kBroadcastHttpRequest[] = "kBroadcastHttpRequest";
const char kBroadcastHttpAccess[] = "kBroadcastHttpAccess";
const char kBroadcastOnGetRtspRealm[] = "kBroadcastOnGetRtspRealm"; const char kBroadcastOnGetRtspRealm[] = "kBroadcastOnGetRtspRealm";
const char kBroadcastOnRtspAuth[] = "kBroadcastOnRtspAuth"; const char kBroadcastOnRtspAuth[] = "kBroadcastOnRtspAuth";
const char kBroadcastMediaPlayed[] = "kBroadcastMediaPlayed"; const char kBroadcastMediaPlayed[] = "kBroadcastMediaPlayed";

View File

@ -79,6 +79,10 @@ extern const char kBroadcastRecordMP4[];
extern const char kBroadcastHttpRequest[]; extern const char kBroadcastHttpRequest[];
#define BroadcastHttpRequestArgs const Parser &parser,const HttpSession::HttpResponseInvoker &invoker,bool &consumed,TcpSession &sender #define BroadcastHttpRequestArgs const Parser &parser,const HttpSession::HttpResponseInvoker &invoker,bool &consumed,TcpSession &sender
//收到http 访问文件或目录的广播
extern const char kBroadcastHttpAccess[];
#define BroadcastHttpAccessArgs const Parser &parser,const MediaInfo &args,const string &path,const bool &is_dir,const HttpSession::HttpAccessPathInvoker &invoker,TcpSession &sender
//该流是否需要认证是的话调用invoker并传入realm,否则传入空的realm.如果该事件不监听则不认证 //该流是否需要认证是的话调用invoker并传入realm,否则传入空的realm.如果该事件不监听则不认证
extern const char kBroadcastOnGetRtspRealm[]; extern const char kBroadcastOnGetRtspRealm[];
#define BroadcastOnGetRtspRealmArgs const MediaInfo &args,const RtspSession::onGetRealm &invoker,TcpSession &sender #define BroadcastOnGetRtspRealmArgs const MediaInfo &args,const RtspSession::onGetRealm &invoker,TcpSession &sender

279
src/Http/CookieManager.cpp Normal file
View File

@ -0,0 +1,279 @@
/*
* MIT License
*
* Copyright (c) 2016-2019 xiongziliang <771730766@qq.com>
*
* This file is part of ZLMediaKit(https://github.com/xiongziliang/ZLMediaKit).
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include "Util/util.h"
#include "Util/MD5.h"
#include "Common/config.h"
#include "CookieManager.h"
//////////////////////////////CookieData////////////////////////////////////
CookieData::CookieData(const CookieManager::Ptr &manager,
const string &cookie,
const string &uid,
uint64_t max_elapsed,
const string &path){
_uid = uid;
_max_elapsed = max_elapsed;
_cookie_uuid = cookie;
_path = path;
_manager = manager;
manager->onAddCookie(path,uid,cookie);
}
CookieData::~CookieData() {
auto strongManager = _manager.lock();
if(strongManager){
strongManager->onDelCookie(_path,_uid,_cookie_uuid);
}
}
string CookieData::getCookie(const string &cookie_name) const {
return (StrPrinter << cookie_name << "=" << _cookie_uuid << ";expires=" << cookieExpireTime() << ";path=" << _path);
}
bool CookieData::isExpired() {
return _ticker.elapsedTime() > _max_elapsed * 1000;
}
void CookieData::updateTime() {
_ticker.resetTime();
}
const string & CookieData::getUid() const{
return _uid;
}
string CookieData::cookieExpireTime() const{
char buf[64];
time_t tt = time(NULL) + _max_elapsed;
strftime(buf, sizeof buf, "%a, %b %d %Y %H:%M:%S GMT", gmtime(&tt));
return buf;
}
const string& CookieData::getCookie() const {
return _cookie_uuid;
}
const string& CookieData::getPath() const{
return _path;
}
//////////////////////////////CookieManager////////////////////////////////////
INSTANCE_IMP(CookieManager);
CookieData::Ptr CookieManager::addCookie(const string &uidIn, int max_client ,uint64_t max_elapsed, const string &path) {
lock_guard<recursive_mutex> lck(_mtx_cookie);
auto cookie = _geneator.obtain();
auto uid = uidIn.empty() ? cookie : uidIn;
auto oldCookie = getOldestCookie(uid, max_client , path);
if(!oldCookie.empty()){
//假如该账号已经登录了那么删除老的cookie。
//目的是实现单账号多地登录时挤占登录
delCookie(oldCookie,path);
}
CookieData::Ptr data(new CookieData(shared_from_this(),cookie,uid,max_elapsed,path));
//保存该账号下的新cookie
_map_cookie[path][cookie] = data;
return data;
}
bool CookieManager::delCookie(const CookieData::Ptr &cookie) {
if(!cookie){
return false;
}
return delCookie(cookie->getPath(),cookie->getCookie());
}
bool CookieManager::delCookie(const string &path , const string &cookie) {
lock_guard<recursive_mutex> lck(_mtx_cookie);
auto it = _map_cookie.find(path);
if(it == _map_cookie.end()){
return false;
}
return it->second.erase(cookie);
}
CookieData::Ptr CookieManager::getCookie(const string &cookie, const string &path) {
lock_guard<recursive_mutex> lck(_mtx_cookie);
auto it_path = _map_cookie.find(path);
if(it_path == _map_cookie.end()){
//该path下没有任何cookie
return nullptr;
}
auto it_cookie = it_path->second.find(cookie);
if(it_cookie == it_path->second.end()){
//该path下没有对应的cookie
return nullptr;
}
if(it_cookie->second->isExpired()){
//cookie过期
it_path->second.erase(it_cookie);
return nullptr;
}
return it_cookie->second;
}
CookieData::Ptr CookieManager::getCookie(const StrCaseMap &http_header, const string &cookie_name, const string &path) {
auto it = http_header.find("Cookie");
if (it == http_header.end()) {
return nullptr;
}
auto cookie = FindField(it->second.data(), (cookie_name + "=").data(), ";");
if (!cookie.size()) {
cookie = FindField(it->second.data(), (cookie_name + "=").data(), nullptr);
}
if(cookie.empty()){
return nullptr;
}
return CookieManager::Instance().getCookie(cookie, path);
}
CookieManager::CookieManager() {
//定时删除过期的cookie防止内存膨胀
_timer = std::make_shared<Timer>(10,[this](){
onManager();
return true;
}, nullptr);
}
CookieManager::~CookieManager() {
_timer.reset();
}
void CookieManager::onManager() {
lock_guard<recursive_mutex> lck(_mtx_cookie);
//先遍历所有path
for(auto it_path = _map_cookie.begin() ; it_path != _map_cookie.end() ;){
//再遍历该path下的所有cookie
for (auto it_cookie = it_path->second.begin() ; it_cookie != it_path->second.end() ; ){
if(it_cookie->second->isExpired()){
//cookie过期,移除记录
WarnL << it_cookie->second->getUid() << " cookie过期";
it_cookie = it_path->second.erase(it_cookie);
continue;
}
++it_cookie;
}
if(it_path->second.empty()){
//该path下没有任何cooki记录,移除之
WarnL << "该path下没有任何cooki记录:" << it_path->first;
it_path = _map_cookie.erase(it_path);
continue;
}
++it_path;
}
}
void CookieManager::onAddCookie(const string &path,const string &uid,const string &cookie){
//添加新的cookie我们记录下这个uid下有哪些cookie目的是实现单账号多地登录时挤占登录
lock_guard<recursive_mutex> lck(_mtx_cookie);
//相同用户下可以存在多个cookie(意味多地登录)这些cookie根据登录时间的早晚依次排序
_map_uid_to_cookie[path][uid][getCurrentMillisecond()] = cookie;
}
void CookieManager::onDelCookie(const string &path,const string &uid,const string &cookie){
lock_guard<recursive_mutex> lck(_mtx_cookie);
//回收随机字符串
_geneator.release(cookie);
auto it_path = _map_uid_to_cookie.find(path);
if(it_path == _map_uid_to_cookie.end()){
//该path下未有任意用户登录
return;
}
auto it_uid = it_path->second.find(uid);
if(it_uid == it_path->second.end()){
//该用户尚未登录
return;
}
//遍历同一名用户下的所有客户端,移除命中的客户端
for(auto it_cookie = it_uid->second.begin() ; it_cookie != it_uid->second.end() ; ++it_cookie ){
if(it_cookie->second != cookie) {
//不是该cookie
continue;
}
//移除该用户名下的某个cookie这个设备cookie将失效
it_uid->second.erase(it_cookie);
if(it_uid->second.size() != 0) {
break;
}
//该用户名下没有任何设备在线,移除之
it_path->second.erase(it_uid);
if(it_path->second.size() != 0) {
break;
}
//该path下未有任何用户在线移除之
_map_uid_to_cookie.erase(it_path);
break;
}
}
string CookieManager::getOldestCookie(const string &uid, int max_client,const string &path){
lock_guard<recursive_mutex> lck(_mtx_cookie);
auto it_path = _map_uid_to_cookie.find(path);
if(it_path == _map_uid_to_cookie.end()){
//该路径下未有任意cookie
return "";
}
auto it_uid = it_path->second.find(uid);
if(it_uid == it_path->second.end()){
//该用户从未登录过
return "";
}
if(it_uid->second.size() < MAX(1,max_client)){
//同一名用户下,客户端个数还没达到限制个数
return "";
}
//客户端个数超过限制,移除最先登录的客户端
return it_uid->second.begin()->second;
}
/////////////////////////////////CookieGeneator////////////////////////////////////
string CookieGeneator::obtain(){
//获取唯一的防膨胀的随机字符串
while (true){
auto str = obtain_l();
if(_obtained.find(str) == _obtained.end()){
//没有重复
_obtained.emplace(str);
return str;
}
}
}
void CookieGeneator::release(const string &str){
//从防膨胀库中移除
_obtained.erase(str);
}
string CookieGeneator::obtain_l(){
//12个伪随机字节 + 4个递增的整形字节然后md5即为随机字符串
auto str = makeRandStr(12,false);
str.append((char *)&_index, sizeof(_index));
++_index;
return MD5(str).hexdigest();
}

232
src/Http/CookieManager.h Normal file
View File

@ -0,0 +1,232 @@
/*
* MIT License
*
* Copyright (c) 2016-2019 xiongziliang <771730766@qq.com>
*
* This file is part of ZLMediaKit(https://github.com/xiongziliang/ZLMediaKit).
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#ifndef SRC_HTTP_COOKIEMANAGER_H
#define SRC_HTTP_COOKIEMANAGER_H
#include <memory>
#include <unordered_map>
#include "Util/mini.h"
#include "Util/TimeTicker.h"
#include "Network/Socket.h"
#include "Rtsp/Rtsp.h"
using namespace std;
using namespace toolkit;
using namespace mediakit;
#define COOKIE_DEFAULT_LIFE (7 * 24 * 60 * 60)
class CookieManager;
/**
* cookie对象cookie的一些相关属性
*/
class CookieData : public mINI , public noncopyable{
public:
typedef std::shared_ptr<CookieData> Ptr;
/**
* cookie
* @param manager cookie管理者对象
* @param cookie cookie随机字符串
* @param uid id
* @param max_elapsed
* @param path http路径/index/files/
*/
CookieData(const std::shared_ptr<CookieManager> &manager,const string &cookie,
const string &uid,uint64_t max_elapsed,const string &path);
~CookieData() ;
/**
* uid
* @return uid
*/
const string &getUid() const;
/**
* http中Set-Cookie字段的值
* @param cookie_name cookie的名称 MY_SESSION
* @return MY_SESSION=XXXXXX;expires=Wed, Jun 12 2019 06:30:48 GMT;path=/index/files/
*/
string getCookie(const string &cookie_name) const;
/**
* cookie随机字符串
* @return cookie随机字符串
*/
const string& getCookie() const;
/**
* cookie对应的path
* @return
*/
const string& getPath() const;
/**
* cookie的过期时间cookie不失效
*/
void updateTime();
/**
* cookie是否过期
* @return
*/
bool isExpired();
private:
string cookieExpireTime() const ;
private:
string _uid;
string _path;
string _cookie_uuid;
uint64_t _max_elapsed;
Ticker _ticker;
std::weak_ptr<CookieManager> _manager;
};
/**
* cookie随机字符串生成器
*/
class CookieGeneator{
public:
CookieGeneator() = default;
~CookieGeneator() = default;
/**
*
* @return
*/
string obtain();
/**
*
* @param str
*/
void release(const string &str);
private:
string obtain_l();
private:
//碰撞库
unordered_set<string> _obtained;
//增长index防止碰撞用
int _index = 0;
};
/**
* cookie管理器cookie的生成以及过期管理
*
*/
class CookieManager : public std::enable_shared_from_this<CookieManager> {
public:
typedef std::shared_ptr<CookieManager> Ptr;
friend class CookieData;
~CookieManager();
/**
*
*/
static CookieManager &Instance();
/**
* cookie
* @param uid id
* @param max_client
* @param max_elapsed cookie过期时间
* @param path cookie对应的http路径
* @return cookie对象
*/
CookieData::Ptr addCookie(const string &uid, int max_client , uint64_t max_elapsed = COOKIE_DEFAULT_LIFE,const string &path = "/" );
/**
* cookie随机字符串查找cookie对象
* @param cookie cookie随机字符串
* @param path cookie对应的http路径
* @return cookie对象nullptr
*/
CookieData::Ptr getCookie(const string &cookie,const string &path = "/");
/**
* http头中获取cookie对象
* @param http_header http头
* @param cookie_name cookie名
* @param path http路径
* @return cookie对象
*/
CookieData::Ptr getCookie(const StrCaseMap &http_header,const string &cookie_name , const string &path = "/");
/**
* cookie使
* @param cookie cookie对象nullptr
* @return
*/
bool delCookie(const CookieData::Ptr &cookie);
/**
* cookie
* @param path http路径
* @param uid id
* @param max_client
* @return cookie随机字符串
*/
string getOldestCookie( const string &uid, int max_client ,const string &path = "/");
private:
CookieManager();
void onManager();
/**
* cookie对象时触发cookie
* @param path http路径
* @param uid id
* @param cookie cookie随机字符串
*/
void onAddCookie(const string &path,const string &uid,const string &cookie);
/**
* cookie对象时触发
* @param path http路径
* @param uid id
* @param cookie cookie随机字符串
*/
void onDelCookie(const string &path,const string &uid,const string &cookie);
/**
* cookie
* @param path http路径
* @param cookie cookie随机字符串
* @return true
*/
bool delCookie(const string &path,const string &cookie);
private:
unordered_map<string/*path*/,unordered_map<string/*cookie*/,CookieData::Ptr/*cookie_data*/> >_map_cookie;
unordered_map<string/*path*/,unordered_map<string/*uid*/,map<uint64_t/*cookie time stamp*/,string/*cookie*/> > >_map_uid_to_cookie;
recursive_mutex _mtx_cookie;
Timer::Ptr _timer;
CookieGeneator _geneator;
};
#endif //SRC_HTTP_COOKIEMANAGER_H

View File

@ -49,6 +49,12 @@ using namespace toolkit;
namespace mediakit { namespace mediakit {
static int kSockFlags = SOCKET_DEFAULE_FLAGS | FLAG_MORE; static int kSockFlags = SOCKET_DEFAULE_FLAGS | FLAG_MORE;
static const string kCookieName = "ZL_COOKIE";
static const string kAccessPathKey = "kAccessPathKey";
static int kMaxClientPerUid = 1;
static const string kAccessDirUnauthorized = "你没有权限访问该目录";
static const string kAccessFileUnauthorized = "你没有权限访问该文件";
string dateStr() { string dateStr() {
char buf[64]; char buf[64];
@ -315,6 +321,81 @@ inline static string findIndexFile(const string &dir){
return ""; return "";
} }
inline string HttpSession::getClientUid(){
//该ip端口只能有一个cookie不能重复获取cookie
//目的是为了防止我们让客户端设置cookie但是客户端不支持cookie导致一直重复生成cookie
//判断是否为同一个用户还可以根据url相关字段但是这个跟具体业务逻辑相关在这里不便实现
//如果一个http客户端不支持cookie并且一直变换端口号那么会导致服务器无法追踪该用户从而导致一直触发事件并且一直生成cookie
return StrPrinter << get_peer_ip() << ":" << get_peer_port();
}
inline void HttpSession::canAccessPath(const string &path_in,bool is_dir,const function<void(bool canAccess,const CookieData::Ptr &cookie)> &callback_in){
auto path = path_in;
replace(const_cast<string &>(path),"//","/");
auto callback = [callback_in,this](bool canAccess,const CookieData::Ptr &cookie){
try {
callback_in(canAccess,cookie);
}catch (SockException &ex){
if(ex){
shutdown(ex);
}
}catch (exception &ex){
shutdown(SockException(Err_shutdown,ex.what()));
}
};
//根据http头中的cookie字段获取cookie
auto cookie = CookieManager::Instance().getCookie(_parser.getValues(), kCookieName);
if (cookie) {
//判断该用户是否有权限访问该目录并且不再设置客户端cookie
callback(!(*cookie)[kAccessPathKey].empty() && path.find((*cookie)[kAccessPathKey]) == 0, nullptr);
return;
}
//根据该用户的用户名获取cookie
string uid = getClientUid();
auto cookie_str = CookieManager::Instance().getOldestCookie(uid, kMaxClientPerUid);
if(!cookie_str.empty()){
//该用户已经登录过了,但是它(http客户端)貌似不支持cookie所以我们只能通过它的用户名获取cookie
cookie = CookieManager::Instance().getCookie(cookie_str);
if (cookie) {
//判断该用户是否有权限访问该目录并且不再设置客户端cookie
callback(!(*cookie)[kAccessPathKey].empty() && path.find((*cookie)[kAccessPathKey]) == 0, nullptr);
return;
}
}
//该用户从来未获取过cookie这个时候我们广播是否允许该用户访问该http目录
weak_ptr<HttpSession> weakSelf = dynamic_pointer_cast<HttpSession>(shared_from_this());
HttpAccessPathInvoker accessPathInvoker = [weakSelf, callback, uid , path] (const string &accessPath, int cookieLifeSecond) {
auto strongSelf = weakSelf.lock();
if (!strongSelf) {
//自己已经销毁
return;
}
strongSelf->async([weakSelf, callback, accessPath, cookieLifeSecond, uid , path]() {
//切换到自己线程
auto strongSelf = weakSelf.lock();
if (!strongSelf) {
//自己已经销毁
return;
}
//我们给用户生成追踪cookie
auto cookie = CookieManager::Instance().addCookie(uid, kMaxClientPerUid, cookieLifeSecond);
//记录用户能访问的路径
(*cookie)[kAccessPathKey] = accessPath;
//判断该用户是否有权限访问该目录并且设置客户端cookie
callback(!accessPath.empty() && path.find(accessPath) == 0, cookie);
});
};
bool flag = NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastHttpAccess,_parser,_mediaInfo,path,is_dir,accessPathInvoker,*this);
if(!flag){
//此事件无人监听,我们默认都有权限访问
callback(true, nullptr);
}
}
inline void HttpSession::Handle_Req_GET(int64_t &content_len) { inline void HttpSession::Handle_Req_GET(int64_t &content_len) {
//先看看是否为WebSocket请求 //先看看是否为WebSocket请求
@ -357,17 +438,30 @@ inline void HttpSession::Handle_Req_GET(int64_t &content_len) {
if(!indexFile.empty()){ if(!indexFile.empty()){
//发现该文件夹下有index文件 //发现该文件夹下有index文件
strFile = strFile + "/" + indexFile; strFile = strFile + "/" + indexFile;
_parser.setUrl(_parser.Url() + "/" + indexFile);
break; break;
} }
//生成文件夹菜单索引
string strMeun; string strMeun;
//生成文件夹菜单索引
if (!makeMeun(_parser.Url(),strFile,strMeun)) { if (!makeMeun(_parser.Url(),strFile,strMeun)) {
//文件夹不存在 //文件夹不存在
sendNotFound(bClose); sendNotFound(bClose);
throw SockException(bClose ? Err_shutdown : Err_success,"close connection after send 404 not found on folder"); throw SockException(bClose ? Err_shutdown : Err_success,"close connection after send 404 not found on folder");
} }
sendResponse("200 OK", makeHttpHeader(bClose,strMeun.size() ), strMeun);
throw SockException(bClose ? Err_shutdown : Err_success,"close connection after send 200 ok on folder"); //判断是否有权限访问该目录
canAccessPath(_parser.Url(),true,[this,bClose,strFile,strMeun](bool canAccess,const CookieData::Ptr &cookie){
if(!canAccess){
const_cast<string &>(strMeun) = kAccessDirUnauthorized;
}
auto headerOut = makeHttpHeader(bClose,strMeun.size());
if(cookie){
headerOut["Set-Cookie"] = cookie->getCookie(kCookieName);
}
sendResponse(canAccess ? "200 OK" : "401 Unauthorized" , headerOut, strMeun);
throw SockException(bClose ? Err_shutdown : Err_success,"close connection after access folder");
});
return;
} }
}while(0); }while(0);
@ -391,97 +485,106 @@ inline void HttpSession::Handle_Req_GET(int64_t &content_len) {
throw SockException(bClose ? Err_shutdown : Err_success,"close connection after send 404 not found on open file failed"); throw SockException(bClose ? Err_shutdown : Err_success,"close connection after send 404 not found on open file failed");
} }
//判断是不是分节下载 auto parser = _parser;
auto &strRange = _parser["Range"]; //判断是否有权限访问该文件
int64_t iRangeStart = 0, iRangeEnd = 0; canAccessPath(_parser.Url(),false,[this,parser,tFileStat,pFilePtr,bClose,strFile](bool canAccess,const CookieData::Ptr &cookie){
iRangeStart = atoll(FindField(strRange.data(), "bytes=", "-").data()); //判断是不是分节下载
iRangeEnd = atoll(FindField(strRange.data(), "-", "\r\n").data()); auto &strRange = parser["Range"];
if (iRangeEnd == 0) { int64_t iRangeStart = 0, iRangeEnd = 0;
iRangeEnd = tFileStat.st_size - 1; iRangeStart = atoll(FindField(strRange.data(), "bytes=", "-").data());
} iRangeEnd = atoll(FindField(strRange.data(), "-", "\r\n").data());
const char *pcHttpResult = NULL; if (iRangeEnd == 0) {
if (strRange.size() == 0) { iRangeEnd = tFileStat.st_size - 1;
//全部下载 }
pcHttpResult = "200 OK"; const char *pcHttpResult = NULL;
} else { if (strRange.size() == 0) {
//分节下载 //全部下载
pcHttpResult = "206 Partial Content"; pcHttpResult = "200 OK";
fseek(pFilePtr.get(), iRangeStart, SEEK_SET); } else {
} //分节下载
auto httpHeader=makeHttpHeader(bClose, iRangeEnd - iRangeStart + 1, get_mime_type(strFile.data())); pcHttpResult = "206 Partial Content";
if (strRange.size() != 0) { fseek(pFilePtr.get(), iRangeStart, SEEK_SET);
//分节下载返回Content-Range头 }
httpHeader.emplace("Content-Range",StrPrinter<<"bytes " << iRangeStart << "-" << iRangeEnd << "/" << tFileStat.st_size<< endl); auto httpHeader = canAccess ? makeHttpHeader(bClose, iRangeEnd - iRangeStart + 1, get_mime_type(strFile.data()))
} : makeHttpHeader(bClose, kAccessFileUnauthorized.size());
auto Origin = _parser["Origin"]; if (strRange.size() != 0) {
if(!Origin.empty()){ //分节下载返回Content-Range头
httpHeader["Access-Control-Allow-Origin"] = Origin; httpHeader.emplace("Content-Range",StrPrinter<<"bytes " << iRangeStart << "-" << iRangeEnd << "/" << tFileStat.st_size<< endl);
httpHeader["Access-Control-Allow-Credentials"] = "true"; }
} auto Origin = parser["Origin"];
//先回复HTTP头部分 if(!Origin.empty()){
sendResponse(pcHttpResult, httpHeader, ""); httpHeader["Access-Control-Allow-Origin"] = Origin;
if (iRangeEnd - iRangeStart < 0) { httpHeader["Access-Control-Allow-Credentials"] = "true";
//文件是空的! }
throw SockException(bClose ? Err_shutdown : Err_success,"close connection after send file partial range excpted");
}
//回复Content部分
std::shared_ptr<int64_t> piLeft(new int64_t(iRangeEnd - iRangeStart + 1));
GET_CONFIG(uint32_t,sendBufSize,Http::kSendBufSize); if(cookie){
httpHeader["Set-Cookie"] = cookie->getCookie(kCookieName);
}
//先回复HTTP头部分
sendResponse(canAccess ? pcHttpResult : "401 Unauthorized" , httpHeader,canAccess ? "" : kAccessFileUnauthorized);
if (!canAccess || iRangeEnd - iRangeStart < 0) {
//文件是空的!
throw SockException(bClose ? Err_shutdown : Err_success,"close connection after access file");
}
//回复Content部分
std::shared_ptr<int64_t> piLeft(new int64_t(iRangeEnd - iRangeStart + 1));
weak_ptr<HttpSession> weakSelf = dynamic_pointer_cast<HttpSession>(shared_from_this()); GET_CONFIG(uint32_t,sendBufSize,Http::kSendBufSize);
auto onFlush = [pFilePtr,bClose,weakSelf,piLeft]() {
TimeTicker();
auto strongSelf = weakSelf.lock();
while(*piLeft && strongSelf){
//更新超时定时器
strongSelf->_ticker.resetTime();
//从循环池获取一个内存片
auto sendBuf = strongSelf->obtainBuffer();
sendBuf->setCapacity(sendBufSize);
//本次需要读取文件字节数
int64_t iReq = MIN(sendBufSize,*piLeft);
//读文件
int iRead;
do{
iRead = fread(sendBuf->data(), 1, iReq, pFilePtr.get());
}while(-1 == iRead && UV_EINTR == get_uv_error(false));
//文件剩余字节数
*piLeft -= iRead;
if (iRead < iReq || !*piLeft) { weak_ptr<HttpSession> weakSelf = dynamic_pointer_cast<HttpSession>(shared_from_this());
//文件读完 auto onFlush = [pFilePtr,bClose,weakSelf,piLeft]() {
if(iRead>0) { TimeTicker();
sendBuf->setSize(iRead); auto strongSelf = weakSelf.lock();
strongSelf->send(sendBuf); while(*piLeft && strongSelf){
} //更新超时定时器
if(bClose) { strongSelf->_ticker.resetTime();
strongSelf->shutdown(SockException(Err_shutdown,"read file eof")); //从循环池获取一个内存片
} auto sendBuf = strongSelf->obtainBuffer();
return false; sendBuf->setCapacity(sendBufSize);
} //本次需要读取文件字节数
//文件还未读完 int64_t iReq = MIN(sendBufSize,*piLeft);
sendBuf->setSize(iRead); //读文件
int iSent = strongSelf->send(sendBuf); int iRead;
if(iSent == -1) { do{
//套机制销毁 iRead = fread(sendBuf->data(), 1, iReq, pFilePtr.get());
return false; }while(-1 == iRead && UV_EINTR == get_uv_error(false));
} //文件剩余字节数
if(strongSelf->isSocketBusy()){ *piLeft -= iRead;
//套接字忙,那么停止继续写
return true; if (iRead < iReq || !*piLeft) {
//文件读完
if(iRead>0) {
sendBuf->setSize(iRead);
strongSelf->send(sendBuf);
}
if(bClose) {
strongSelf->shutdown(SockException(Err_shutdown,"read file eof"));
}
return false;
}
//文件还未读完
sendBuf->setSize(iRead);
int iSent = strongSelf->send(sendBuf);
if(iSent == -1) {
//套机制销毁
return false;
}
if(strongSelf->isSocketBusy()){
//套接字忙,那么停止继续写
return true;
}
//继续写套接字
} }
//继续写套接字 return false;
} };
return false; //关闭tcp_nodelay ,优化性能
}; SockUtil::setNoDelay(_sock->rawFD(),false);
//关闭tcp_nodelay ,优化性能 //设置MSG_MORE优化性能
SockUtil::setNoDelay(_sock->rawFD(),false); (*this) << SocketFlags(kSockFlags);
//设置MSG_MORE优化性能
(*this) << SocketFlags(kSockFlags);
onFlush(); onFlush();
_sock->setOnFlush(onFlush); _sock->setOnFlush(onFlush);
});
} }
inline bool makeMeun(const string &httpPath,const string &strFullPath, string &strRet) { inline bool makeMeun(const string &httpPath,const string &strFullPath, string &strRet) {

View File

@ -35,6 +35,7 @@
#include "RtmpMuxer/FlvMuxer.h" #include "RtmpMuxer/FlvMuxer.h"
#include "HttpRequestSplitter.h" #include "HttpRequestSplitter.h"
#include "WebSocketSplitter.h" #include "WebSocketSplitter.h"
#include "CookieManager.h"
using namespace std; using namespace std;
using namespace toolkit; using namespace toolkit;
@ -51,6 +52,8 @@ public:
const KeyValue &headerOut, const KeyValue &headerOut,
const string &contentOut)> HttpResponseInvoker; const string &contentOut)> HttpResponseInvoker;
typedef std::function<void(const string &accessPath, int cookieLifeSecond)> HttpAccessPathInvoker;
HttpSession(const Socket::Ptr &pSock); HttpSession(const Socket::Ptr &pSock);
virtual ~HttpSession(); virtual ~HttpSession();
@ -109,6 +112,17 @@ private:
const string &codeOut, const string &codeOut,
const KeyValue &headerOut, const KeyValue &headerOut,
const string &contentOut); const string &contentOut);
/**
* 访
* @param path
* @param is_dir path是否为目录
* @param callback
*/
inline void canAccessPath(const string &path,bool is_dir,const function<void(bool canAccess,const CookieData::Ptr &cookie)> &callback);
//获取用户唯一识别id我们默认为ip+端口号
inline string getClientUid();
private: private:
Parser _parser; Parser _parser;
Ticker _ticker; Ticker _ticker;

View File

@ -145,7 +145,8 @@ public:
auto args_pos = _strFullUrl.find('?'); auto args_pos = _strFullUrl.find('?');
if (args_pos != string::npos) { if (args_pos != string::npos) {
_strUrl = _strFullUrl.substr(0, args_pos); _strUrl = _strFullUrl.substr(0, args_pos);
_mapUrlArgs = parseArgs(_strFullUrl.substr(args_pos + 1)); _params = _strFullUrl.substr(args_pos + 1);
_mapUrlArgs = parseArgs(_params);
} else { } else {
_strUrl = _strFullUrl; _strUrl = _strFullUrl;
} }
@ -202,11 +203,15 @@ public:
_strMethod.clear(); _strMethod.clear();
_strUrl.clear(); _strUrl.clear();
_strFullUrl.clear(); _strFullUrl.clear();
_params.clear();
_strTail.clear(); _strTail.clear();
_strContent.clear(); _strContent.clear();
_mapHeaders.clear(); _mapHeaders.clear();
_mapUrlArgs.clear(); _mapUrlArgs.clear();
} }
const string &Params() const {
return _params;
}
void setUrl(const string &url) { void setUrl(const string &url) {
this->_strUrl = url; this->_strUrl = url;
@ -242,6 +247,7 @@ private:
string _strContent; string _strContent;
string _strNull; string _strNull;
string _strFullUrl; string _strFullUrl;
string _params;
mutable StrCaseMap _mapHeaders; mutable StrCaseMap _mapHeaders;
mutable StrCaseMap _mapUrlArgs; mutable StrCaseMap _mapUrlArgs;
}; };