整體
服務(wù)端從basicServer.js開始, 在此以之前的Basic Example的客戶端發(fā)送的CreteToken為例子, 服務(wù)端的http框架監(jiān)聽到該請求后開始處理, 對發(fā)送過來的消息進(jìn)行解析之后霞赫,調(diào)用getOrCreateRoom創(chuàng)建room和token.
//licode\extras\basic_example\basicServer.js
app.post('/createToken/', (req, res) => {
console.log('Creating token. Request body: ', req.body);
const username = req.body.username;
const role = req.body.role;
let room = defaultRoomName;
let type;
let roomId;
let mediaConfiguration;
if (req.body.room) room = req.body.room;
if (req.body.type) type = req.body.type;
if (req.body.roomId) roomId = req.body.roomId;
if (req.body.mediaConfiguration) mediaConfiguration = req.body.mediaConfiguration;
const createToken = (tokenRoomId) => {
N.API.createToken(tokenRoomId, username, role, (token) => {
console.log('Token created', token);
res.send(token);
}, (error) => {
console.log('Error creating token', error);
res.status(401).send('No Erizo Controller found');
});
};
if (roomId) {
createToken(roomId);
} else {
getOrCreateRoom(room, type, mediaConfiguration, createToken); //調(diào)用到此處創(chuàng)建房間湃鹊,創(chuàng)建token
}
});
getOrCreateRoom中使用了N.API.getRooms()去向nuve獲取房間列表测柠,校驗當(dāng)前的房間是否已經(jīng)創(chuàng)建水醋,如果創(chuàng)建了則使用該room去創(chuàng)建token, 如果沒有則使用N.API.createRoom()創(chuàng)建房間之后再去創(chuàng)建token.
const getOrCreateRoom = (name, type = 'erizo', mediaConfiguration = 'default',
callback = () => {}) => {
if (name === defaultRoomName && defaultRoom) {
callback(defaultRoom);
return;
}
//向NUVE發(fā)請求獲取房間列表蹬竖,如果該room存在林艘,使用這個room創(chuàng)建token,
//不存在灰伟,創(chuàng)建room之后再創(chuàng)建token
N.API.getRooms((roomlist) => {
let theRoom = '';
const rooms = JSON.parse(roomlist);
for (let i = 0; i < rooms.length; i += 1) {
const room = rooms[i];
if (room.name === name &&
room.data &&
room.data.basicExampleRoom) {
theRoom = room._id;
callback(theRoom);//create token
return;
}
}
const extra = { data: { basicExampleRoom: true }, mediaConfiguration };
if (type === 'p2p') extra.p2p = true;
N.API.createRoom(name, (roomID) => {
theRoom = roomID._id;
callback(theRoom);//create token
}, () => {}, extra);
});
};
此處的N.API是一個用來發(fā)送請求到后端nuve的工具類呆奕,提供了用戶的業(yè)務(wù)后臺到nuve的通訊接口峰尝,licode中的設(shè)計中偏窝,對于一些基礎(chǔ)的操作,createroken, createroom武学,deleteroom都通過nuve進(jìn)行處理祭往, 開發(fā)者的業(yè)務(wù)后臺只需要調(diào)用并同步信息即可,如以下兩個函數(shù), 主要是用send發(fā)送請求到nuve端
N.API = (function (N) {
getRooms = function (callback, callbackError, params) {
send(callback, callbackError, 'GET', undefined, 'rooms', params);
};
createRoom = function (name, callback, callbackError, options, params) {
if (!options) {
options = {};
}
send(function (roomRtn) {
var room = JSON.parse(roomRtn);
callback(room);
}, callbackError, 'POST', {name: name, options: options}, 'rooms', params);
};
}
nuve的起點在文件nuve.js中火窒,其上面也是用express作為監(jiān)聽的http框架硼补,去處理發(fā)送過來的請求,以createroom為例熏矿,如下所示已骇,一旦有請求,首先觸發(fā)對應(yīng)請求的鑒權(quán)票编,該鑒權(quán)就不細(xì)看了褪储,其會通過請求頭的service_id獲取一個service對象, 通過service進(jìn)行簽名校驗之后鑒權(quán)結(jié)束慧域,這個service相當(dāng)于在nuve的一個session, 可通過post鲤竹,get向nuve請求創(chuàng)建后返回id,后續(xù)請求需要帶上該id(注:但該Basic Example中的是預(yù)創(chuàng)建的)
// licode\nuve\nuveAPI\nuve.js
//鑒權(quán)
app.post('*', nuveAuthenticator.authenticate);
- createroom
鑒權(quán)通過后觸發(fā)roomsResource.createRoom進(jìn)行處理
// licode\nuve\nuveAPI\nuve.js
//監(jiān)聽創(chuàng)建房間的請求
app.post('/rooms', roomsResource.createRoom);
roomsResource.createRoom中使用roomRegistry.addRoom()往monogo中寫房間昔榴,寫庫成功后將roomid加入service.rooms數(shù)組中進(jìn)行管理
exports.createRoom = (req, res) => {
let room;
const currentService = req.service;
//...省略...
req.body.options = req.body.options || {};
if (req.body.options.test) {
//...省略...
}
else {
room = { name: req.body.name };
if (req.body.options.p2p) {
room.p2p = true;
}
if (req.body.options.data) {
room.data = req.body.options.data;
}
if (typeof req.body.options.mediaConfiguration === 'string') {
room.mediaConfiguration = req.body.options.mediaConfiguration;
}
//addroom之后進(jìn)行
roomRegistry.addRoom(room, (result) => {
currentService.rooms.push(result); //將房間id(save返回的主鍵)放入數(shù)組
serviceRegistry.addRoomToService(currentService, result);//將房間與service關(guān)聯(lián)起來
log.info(`message: createRoom success, roomName:${req.body.name}, ` +
`serviceId: ${currentService.name}, p2p: ${room.p2p}`);
res.send(result);
});
}
};
- createtoken
創(chuàng)建完房間之后,basicServer發(fā)送請求創(chuàng)建token的請求辛藻,nuve監(jiān)聽到之后執(zhí)行,先通過doInit找到房間乘寒,然后去創(chuàng)建token
exports.create = (req, res) => {
//獲取room后執(zhí)行創(chuàng)建token的回調(diào)
doInit(req, (currentService, currentRoom) => {
if (currentService === undefined) {
log.warn('message: createToken - service not found');
res.status(404).send('Service not found');
return;
} else if (currentRoom === undefined) {
log.warn(`message: createToken - room not found, roomId: ${req.params.room}`);
res.status(404).send('Room does not exist');
return;
}
//創(chuàng)建token
generateToken(req, (tokenS) => {
if (tokenS === undefined) {
res.status(401).send('Name and role?');
return;
}
if (tokenS === 'error') {
log.error('message: createToken error, errorMgs: No Erizo Controller available');
res.status(404).send('No Erizo Controller found');
return;
}
log.info(`message: createToken success, roomId: ${currentRoom._id}, ` +
`serviceId: ${currentService._id}`);
res.send(tokenS);
});
});
};
在generateToken()中,會將roomid辩撑,username等寫入到token中方面,還會分配ErizoController, 將erizoController的IP和port寫入到token中
const generateToken = (req, callback) => {
//....省略.....
token = {};
token.userName = user;
token.room = currentRoom._id;
token.role = role;
token.service = currentService._id;
token.creationDate = new Date();
token.mediaConfiguration = 'default';
//....省略.....
//分配ErizoController, 將erizoController的IP和port寫入到token中
cloudHandler.getErizoControllerForRoom(currentRoom, (ec) => {
if (ec === 'timeout' || !ec) {
callback('error');
return;
}
token.secure = ec.ssl;
if (ec.hostname !== '') {
token.host = ec.hostname;
} else {
token.host = ec.ip;
}
token.host += `:${ec.port}`;
tokenRegistry.addToken(token, (id, err) => {
if (err) {
return callback('error');
}
const tokenAdded = getTokenString(id, token);
return callback(tokenAdded);
});
};
獲取tErizoController的過程如下,首先通過room->erizoControllerId去獲取erizoController, 如果沒有的話岩榆,通過策略從隊列中獲取erizoController错负, 如果沒有策略,則遍歷隊列勇边,根據(jù)狀態(tài)返回一個可用的erizoController隊列犹撒,獲取其首元素進(jìn)行使用。
const getErizoControllerForRoom = (room, callback) => {
const roomId = room._id;
//通過room->erizoControllerId去獲取erizoController
roomRegistry.getRoom(roomId, (roomResult) => {
const id = roomResult.erizoControllerId;
if (id) {
erizoControllerRegistry.getErizoController(id, (erizoController) => {
if (erizoController) {
callback(erizoController);
} else {
roomResult.erizoControllerId = undefined;
roomRegistry.updateRoom(roomResult._id, roomResult);
getErizoControllerForRoom(roomResult, callback);
}
});
return;
}
let attempts = 0;
let intervalId;
// 如果room->erizoControllerId是空的粒褒,從erizoController獲取
getEcQueue((ecQueue) => {
intervalId = setInterval(() => {
let erizoController;
if (getErizoController) {
//通過策略獲取
erizoController = getErizoController(room, ecQueue);
} else {
//從隊列獲取
erizoController = ecQueue[0];
}
const erizoControllerId = erizoController ? erizoController._id : undefined;
if (erizoControllerId !== undefined) {
assignErizoController(erizoControllerId, room, (assignedEc) => {
callback(assignedEc);
clearInterval(intervalId);
});
}
if (attempts > TOTAL_ATTEMPTS_EC_READY) {
clearInterval(intervalId);
callback('timeout');
}
attempts += 1;
}, INTERVAL_TIME_EC_READY);
});
});
};