整体
服务端从basicServer.js开始, 在此以之前的Basic Example的客户端发送的CreteToken为例子, 服务端的http框架监听到该请求后开始处理, 对发送过来的消息进行解析之后,调用getOrCreateRoom创建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); //调用到此处创建房间,创建token
}
});
getOrCreateRoom中使用了N.API.getRooms()去向nuve获取房间列表,校验当前的房间是否已经创建,如果创建了则使用该room去创建token, 如果没有则使用N.API.createRoom()创建房间之后再去创建token.
const getOrCreateRoom = (name, type = 'erizo', mediaConfiguration = 'default',
callback = () => {}) => {
if (name === defaultRoomName && defaultRoom) {
callback(defaultRoom);
return;
}
//向NUVE发请求获取房间列表,如果该room存在,使用这个room创建token,
//不存在,创建room之后再创建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是一个用来发送请求到后端nuve的工具类,提供了用户的业务后台到nuve的通讯接口,licode中的设计中,对于一些基础的操作,createroken, createroom,deleteroom都通过nuve进行处理, 开发者的业务后台只需要调用并同步信息即可,如以下两个函数, 主要是用send发送请求到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作为监听的http框架,去处理发送过来的请求,以createroom为例,如下所示,一旦有请求,首先触发对应请求的鉴权,该鉴权就不细看了,其会通过请求头的service_id获取一个service对象, 通过service进行签名校验之后鉴权结束,这个service相当于在nuve的一个session, 可通过post,get向nuve请求创建后返回id,后续请求需要带上该id(注:但该Basic Example中的是预创建的)
// licode\nuve\nuveAPI\nuve.js
//鉴权
app.post('*', nuveAuthenticator.authenticate);
- createroom
鉴权通过后触发roomsResource.createRoom进行处理
// licode\nuve\nuveAPI\nuve.js
//监听创建房间的请求
app.post('/rooms', roomsResource.createRoom);
roomsResource.createRoom中使用roomRegistry.addRoom()往monogo中写房间,写库成功后将roomid加入service.rooms数组中进行管理
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之后进行
roomRegistry.addRoom(room, (result) => {
currentService.rooms.push(result); //将房间id(save返回的主键)放入数组
serviceRegistry.addRoomToService(currentService, result);//将房间与service关联起来
log.info(`message: createRoom success, roomName:${req.body.name}, ` +
`serviceId: ${currentService.name}, p2p: ${room.p2p}`);
res.send(result);
});
}
};
- createtoken
创建完房间之后,basicServer发送请求创建token的请求,nuve监听到之后执行,先通过doInit找到房间,然后去创建token
exports.create = (req, res) => {
//获取room后执行创建token的回调
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;
}
//创建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, 如果没有策略,则遍历队列,根据状态返回一个可用的erizoController队列,获取其首元素进行使用。
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);
});
});
};