我已经使用来自github的这个online tutorial的socket.io,webrtc,nodejs组建了一个视频聊天应用程序,但现在我在转换它时通过https使用时出错:
Request URL:https://telemed.caduceususa.com/socket.io/?EIO=3&transport=polling&t=1479396416422-0
Request Method:GET
Status Code:404 Not Found
Remote Address:10.9.2.169:443
我在此过程中遇到的其他错误如下:
当我尝试声明一个不同的PORT时,我得到了 - ERR_SSL_PROTOCOL_ERROR,
当我尝试声明端口80或8080时,我得到:ERR_CONNECTION_TIMED_OUT
socket.io.js中的这一行出了问题:
xhr.send(this.data);
我正在Windows Server 2012上运行node.js服务器,我已经设置了IIS来为PORT 80上的服务器提供服务。我在DNS中创建了子域https://telemed.caduceususa.com,并购买了一个受信任的SSL证书通过HTTPS运行网站。
以下是开发工具代码的摘录,其中包含导致错误的上述行以及我的其他代码:
/**
* Creates the XHR object and sends the request.
*
* @api private
*/
Request.prototype.create = function(){
var opts = { agent: this.agent, xdomain: this.xd, xscheme: this.xs, enablesXDR: this.enablesXDR };
// SSL options for Node.js client
opts.pfx = this.pfx;
opts.key = this.key;
opts.passphrase = this.passphrase;
opts.cert = this.cert;
opts.ca = this.ca;
opts.ciphers = this.ciphers;
opts.rejectUnauthorized = this.rejectUnauthorized;
var xhr = this.xhr = new XMLHttpRequest(opts);
var self = this;
try {
debug('xhr open %s: %s', this.method, this.uri);
xhr.open(this.method, this.uri, this.async);
if (this.supportsBinary) {
// This has to be done after open because Firefox is stupid
// https://stackoverflow.com/questions/13216903/get-binary-data-with-xmlhttprequest-in-a-firefox-extension
xhr.responseType = 'arraybuffer';
}
if ('POST' == this.method) {
try {
if (this.isBinary) {
xhr.setRequestHeader('Content-type', 'application/octet-stream');
} else {
xhr.setRequestHeader('Content-type', 'text/plain;charset=UTF-8');
}
} catch (e) {}
}
// ie6 check
if ('withCredentials' in xhr) {
xhr.withCredentials = true;
}
if (this.hasXDR()) {
xhr.onload = function(){
self.onLoad();
};
xhr.onerror = function(){
self.onError(xhr.responseText);
};
} else {
xhr.onreadystatechange = function(){
if (4 != xhr.readyState) return;
if (200 == xhr.status || 1223 == xhr.status) {
self.onLoad();
} else {
// make sure the `error` event handler that's user-set
// does not throw in the same tick and gets caught here
setTimeout(function(){
self.onError(xhr.status);
}, 0);
}
};
}
debug('xhr data %s', this.data);
xhr.send(this.data);
}
这是server.js文件:
var fs = require('fs');
var hskey = fs.readFileSync('ssl/telemed_internal_server.key');
var hscert = fs.readFileSync('ssl/telemed_internal_cert.pem');
var ca = fs.readFileSync('ssl/telemed_internal_key.pem');
var credentials = {
ca: ca,
key: hskey,
cert: hscert
};
var static = require('node-static');
var https = require('https');
var util = require('util');
var file = new(static.Server)();
var app = https.createServer(credentials, function (req, res) {
file.serve(req, res);
}).listen(process.env.PORT || 80);
var io = require('socket.io').listen(app);
io.sockets.on('connection', function (socket){
// convenience function to log server messages on the client
function log(){
var array = [">>> Message from server: "];
for (var i = 0; i < arguments.length; i++) {
array.push(arguments[i]);
}
socket.emit('log', array);
}
// when receive sdp, broadcast sdp to other user
socket.on('sdp', function(data){
console.log('Received SDP from ' + socket.id);
socket.to(data.room).emit('sdp received', data.sdp);
});
// when receive ice candidate, broadcast sdp to other user
socket.on('ice candidate', function(data){
console.log('Received ICE candidate from ' + socket.id + ' ' + data.candidate);
socket.to(data.room).emit('ice candidate received', data.candidate);
});
socket.on('message', function (message) {
log('Got message:', message);
// for a real app, would be room only (not broadcast)
socket.broadcast.emit('message', message);
});
socket.on('create or join', function (room) {
// join room
var existingRoom = io.sockets.adapter.rooms[room];
var clients = [];
if(existingRoom){
clients = Object.keys(existingRoom);
}
if(clients.length == 0){
socket.join(room);
io.to(room).emit('empty', room);
}
else if(clients.length == 1){
socket.join(room);
socket.to(room).emit('joined', room, clients.length + 1);
}
// only allow 2 users max per room
else{
socket.emit('full', room);
}
});
socket.on('error', function(error){
console.error(error);
})
});
这是main.js(config)文件:
//my signalling server
var serverIP = "https://telemed.caduceususa.com/";
// RTCPeerConnection Options
var server = {
// Uses Google's STUN server
iceServers: [{
"url": "stun:stun4.l.google.com:19302"
},
{
url: 'turn:numb.viagenie.ca',
credential: 'muazkh',
username: 'webrtc@live.com'
}]
};
// various other development IPs
// var serverIP = "https://192.168.43.241:2013";
// var serverIP = "https://10.0.11.196:2013";
var localPeerConnection, signallingServer;
var btnSend = document.getElementById('btn-send');
var btnVideoStop = document.getElementById('btn-video-stop');
var btnVideoStart = document.getElementById('btn-video-start');
var btnVideoJoin = document.getElementById('btn-video-join');
var localVideo = document.getElementById('local-video');
var remoteVideo = document.getElementById('remote-video');
var inputRoomName = document.getElementById('room-name');
var localStream, localIsCaller;
btnVideoStop.onclick = function(e) {
e.preventDefault();
// stop video stream
if (localStream != null) {
localStream.stop();
}
// kill all connections
if (localPeerConnection != null) {
localPeerConnection.removeStream(localStream);
localPeerConnection.close();
signallingServer.close();
localVideo.src = "";
remoteVideo.src = "";
}
btnVideoStart.disabled = false;
btnVideoJoin.disabled = false;
btnVideoStop.disabled = true;
}
btnVideoStart.onclick = function(e) {
e.preventDefault();
// is starting the call
localIsCaller = true;
initConnection();
}
btnVideoJoin.onclick = function(e) {
e.preventDefault();
// just joining a call, not offering
localIsCaller = false;
initConnection();
}
function initConnection() {
var room = inputRoomName.value;
if (room == undefined || room.length <= 0) {
alert('Please enter room name');
return;
}
// start connection!
connect(room);
btnVideoStart.disabled = true;
btnVideoJoin.disabled = true;
btnVideoStop.disabled = false;
}
// WEBRTC STUFF STARTS HERE
// Set objects as most are currently prefixed
window.RTCPeerConnection = window.RTCPeerConnection || window.mozRTCPeerConnection ||
window.webkitRTCPeerConnection || window.msRTCPeerConnection;
window.RTCSessionDescription = window.RTCSessionDescription || window.mozRTCSessionDescription ||
window.webkitRTCSessionDescription || window.msRTCSessionDescription;
navigator.getUserMedia = navigator.getUserMedia || navigator.mozGetUserMedia ||
navigator.webkitGetUserMedia || navigator.msGetUserMedia;
window.SignallingServer = window.SignallingServer;
var sdpConstraints = {
optional: [],
mandatory: {
OfferToReceiveVideo: true,
}
}
function connect(room) {
// create peer connection
localPeerConnection = new RTCPeerConnection(server);
// create local data channel, send it to remote
navigator.getUserMedia({
video: true,
audio: true
}, function(stream) {
// get and save local stream
trace('Got stream, saving it now and starting RTC conn');
// must add before calling setRemoteDescription() because then
// it triggers 'addstream' event
localPeerConnection.addStream(stream);
localStream = stream;
// show local video
localVideo.src = window.URL.createObjectURL(stream);
// can start once have gotten local video
establishRTCConnection(room);
}, errorHandler)
}
function establishRTCConnection(room) {
// create signalling server
signallingServer = new SignallingServer(room, serverIP);
signallingServer.connect();
// a remote peer has joined room, initiate sdp exchange
signallingServer.onGuestJoined = function() {
trace('guest joined!')
// set local description and send to remote
localPeerConnection.createOffer(function(sessionDescription) {
trace('set local session desc with offer');
localPeerConnection.setLocalDescription(sessionDescription);
// send local sdp to remote
signallingServer.sendSDP(sessionDescription);
});
}
// got sdp from remote
signallingServer.onReceiveSdp = function(sdp) {
// get stream again
localPeerConnection.addStream(localStream);
trace(localStream)
// if local was the caller, set remote desc
if (localIsCaller) {
trace('is caller');
trace('set remote session desc with answer');
localPeerConnection.setRemoteDescription(new RTCSessionDescription(
sdp));
}
// if local is joining a call, set remote sdp and create answer
else {
trace('set remote session desc with offer');
localPeerConnection.setRemoteDescription(new RTCSessionDescription(
sdp), function() {
trace('make answer')
localPeerConnection.createAnswer(function(
sessionDescription) {
// set local description
trace('set local session desc with answer');
localPeerConnection.setLocalDescription(
sessionDescription);
// send local sdp to remote too
signallingServer.sendSDP(sessionDescription);
});
});
}
}
// when received ICE candidate
signallingServer.onReceiveICECandidate = function(candidate) {
trace('Set remote ice candidate');
localPeerConnection.addIceCandidate(new RTCIceCandidate(candidate));
}
// when room is full, alert user
signallingServer.onRoomFull = function(room) {
window.alert('Room "' + room +
'"" is full! Please join or create another room');
}
// get ice candidates and send them over
// wont get called unless SDP has been exchanged
localPeerConnection.onicecandidate = function(event) {
if (event.candidate) {
//!!! send ice candidate over via signalling channel
trace("Sending candidate");
signallingServer.sendICECandidate(event.candidate);
}
}
// when stream is added to connection, put it in video src
localPeerConnection.onaddstream = function(data) {
remoteVideo.src = window.URL.createObjectURL(data.stream);
}
}
function errorHandler(error) {
console.error('Something went wrong!');
console.error(error);
}
function trace(text) {
console.info(text);
}
这是信令服务器:
function trace(text){
console.info(text);
}
// Connects to signalling server with given room and IP
// has methods to exchange SDP and ICE candidates
var SignallingServer = function(room, socketServer){
this.room = room;
this.socket = io.connect(socketServer);
this.socket.on('full', function (room){
trace('Room ' + room + ' is full');
this.onRoomFull(room);
}.bind(this));
this.socket.on('empty', function (room){
this.isInitiator = true;
trace('Room ' + room + ' is empty');
});
this.socket.on('join', function (room){
trace('Making request to join room ' + room);
});
this.socket.on('joined', function (room, numClients){
trace('New user has joined ' + room);
trace('Room has ' + numClients + ' clients');
//ask host to initiate sdp transfer
this.onGuestJoined();
}.bind(this));
this.socket.on('sdp received', function(sdp){
trace('Received SDP ');
trace(sdp);
this.onReceiveSdp(sdp);
}.bind(this));
this.socket.on('ice candidate received', function(candidate){
trace('Received ICE candidate ');
trace(candidate);
this.onReceiveICECandidate(candidate);
}.bind(this));
this.socket.on('log', function (array){
console.log.apply(console, array);
});
}
SignallingServer.prototype = {
connect: function(){
if (this.room !== '') {
trace('Joining room ' + this.room);
this.socket.emit('create or join', this.room);
}
},
close: function(){
trace('Disconnecting')
this.socket.disconnect();
},
sendSDP: function(sdp){
trace('sending sdp')
this.socket.emit('sdp', {
room: this.room,
sdp: sdp
});
},
sendICECandidate: function(candidate){
trace('sending ice candidate');
this.socket.emit('ice candidate', {
room: this.room,
candidate: candidate
});
},
onReceiveSdp: function(sdp){
trace('Placeholder function: Received SDP')
},
onGuestJoined: function(){
trace('Placeholder function: Guest joined room')
},
onReceiveICECandidate: function(candidate){
trace('Placeholder function: Received ICE candidate')
},
onRoomFull: function(room){
trace('Placeholder function: Room is full!');
}
}
window.SignallingServer = SignallingServer;
最后是HTML(也可以解释一下LIVERELOAD.JS是什么?):
<!doctype html>
<!--[if lt IE 7]>
<html class="no-js lt-ie9 lt-ie8 lt-ie7" lang="">
<![endif]-->
<!--[if IE 7]>
<html class="no-js lt-ie9 lt-ie8" lang="">
<![endif]-->
<!--[if IE 8]>
<html class="no-js lt-ie9" lang="">
<![endif]-->
<!--[if gt IE 8]>
<!-->
<html class="no-js" lang="">
<!--<![endif]-->
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title></title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="css/bootstrap.min.css">
<style>
body {
padding-top: 50px;
padding-bottom: 20px;
}
</style>
<link rel="stylesheet" href="css/bootstrap-theme.min.css">
<link rel="stylesheet" href="css/main.css">
<script src="js/vendor/modernizr-2.8.3-respond-1.4.2.min.js"></script>
</head>
<body>
<!--[if lt IE 8]>
<p class="browserupgrade">
You are using an <strong>outdated</strong>
browser. Please
<a href="https://browsehappy.com/">upgrade your browser</a>
to improve your experience.
</p>
<![endif]-->
<nav class="navbar navbar-default navbar-fixed-top" role="navigation">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="#">WebRTC Video Chat</a>
</div>
<div id="navbar" class="navbar-collapse collapse">
<!-- chatroom name form -->
<form class="navbar-form navbar-right form-inline">
<div class="form-group">
<input class="form-control" type="text" id="room-name" placeholder="Room name"/>
</div>
<button class="btn btn-primary" id="btn-video-start">Start</button>
<button class="btn btn-default" id="btn-video-join">Join</button>
<button class="btn btn-default" disabled id="btn-video-stop">Stop</button>
</form>
</div>
<!--/.navbar-collapse --> </div>
</nav>
<div class="container main">
<div class="row videos">
<div class="remote-video">
<video width="280" height="250" autoplay id="remote-video"></video>
</div>
<div class="local-video">
<video width="280" height="250" autoplay id="local-video" muted></video>
</div>
</div>
</div>
</div>
<!-- /container -->
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.2/jquery.min.js"></script>
<script>window.jQuery || document.write('<script src="js/vendor/jquery-1.11.2.min.js"><\/script>')</script>
<script src="js/vendor/bootstrap.min.js"></script>
<script src="js/vendor/socket.io.js"></script>
<script src="js/main.js"></script>
<script src="js/signalling.js"></script>
<script src="//localhost:9010/livereload.js"></script>
</body>
</html>