title: socket.io, webrtc, nodejs video chat app getting errors over https: ERR_SSL_PROTOCOL_ERROR, 404 (Not Found), and ERR_CONNECTION_TIMED_OUT
body: <p>I have put together a video chat app using socket.io, webrtc, nodejs from this <a href="https://github.com/piratefsh/webrtc-video-chat" rel="nofollow noreferrer">online tutorial</a> from github but now I am getting errors when converting it to be used over https: </p>
  
  <pre><code>Request URL:https://telemed.caduceususa.com/socket.io/?EIO=3&amp;transport=polling&amp;t=1479396416422-0
  Request Method:GET
  Status Code:404 Not Found
  Remote Address:10.9.2.169:443
  </code></pre>
  
  <p>Other errors I have gotten in this process are as follows:</p>
  
  <p>When I try to declare a different PORT I get - ERR_SSL_PROTOCOL_ERROR, </p>
  
  <p>When I try to declare port 80 or 8080 i get: ERR_CONNECTION_TIMED_OUT</p>
  
  <p>Something is going wrong on this line inside socket.io.js:</p>
  
  <pre><code>xhr.send(this.data);
  </code></pre>
  
  <p>I am running a node.js server on Windows Server 2012 and I have set up IIS to serve up the server on PORT 80. I have created the subdomain <a href="https://telemed.caduceususa.com" rel="nofollow noreferrer">https://telemed.caduceususa.com</a> in DNS and have purchased a trusted SSL cert to run the site over HTTPS.</p>
  
  <p>Here is the excerpt of code from the dev tools that contains the above line that is causing the error as well as my other code:</p>
  
  <pre><code>/**
   * 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);
    }
  </code></pre>
  
  <p>Here is the server.js file:</p>
  
  <pre><code>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 = ["&gt;&gt;&gt; Message from server: "];
        for (var i = 0; i &lt; 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);
      })
  
  });
  </code></pre>
  
  <p>Here is the main.js (config) file:</p>
  
  <pre><code>//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 &lt;= 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);
  }
  </code></pre>
  
  <p>Here is the signalling server:</p>
  
  <pre><code>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;
  </code></pre>
  
  <p>AND FINALLY THE HTML (CAN SOMEONE ALSO EXPLAIN WHAT LIVERELOAD.JS IS?):</p>
  
  <pre><code>&lt;!doctype html&gt;
  &lt;!--[if lt IE 7]&gt;
  &lt;html class="no-js lt-ie9 lt-ie8 lt-ie7" lang=""&gt;
    &lt;![endif]--&gt;
    &lt;!--[if IE 7]&gt;
    &lt;html class="no-js lt-ie9 lt-ie8" lang=""&gt;
      &lt;![endif]--&gt;
      &lt;!--[if IE 8]&gt;
      &lt;html class="no-js lt-ie9" lang=""&gt;
        &lt;![endif]--&gt;
        &lt;!--[if gt IE 8]&gt;
        &lt;!--&gt;
        &lt;html class="no-js" lang=""&gt;
          &lt;!--&lt;![endif]--&gt;
  &lt;head&gt;
          &lt;meta charset="utf-8"&gt;
          &lt;meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"&gt;
          &lt;title&gt;&lt;/title&gt;
          &lt;meta name="description" content=""&gt;
          &lt;meta name="viewport" content="width=device-width, initial-scale=1"&gt;
  
          &lt;link rel="stylesheet" href="css/bootstrap.min.css"&gt;
          &lt;style&gt;
              body {
                  padding-top: 50px;
                  padding-bottom: 20px;
              }
          &lt;/style&gt;
          &lt;link rel="stylesheet" href="css/bootstrap-theme.min.css"&gt;
          &lt;link rel="stylesheet" href="css/main.css"&gt;
  
          &lt;script src="js/vendor/modernizr-2.8.3-respond-1.4.2.min.js"&gt;&lt;/script&gt;
  &lt;/head&gt;
  &lt;body&gt;
          &lt;!--[if lt IE 8]&gt;
          &lt;p class="browserupgrade"&gt;
            You are using an &lt;strong&gt;outdated&lt;/strong&gt;
            browser. Please
            &lt;a href="https://browsehappy.com/"&gt;upgrade your browser&lt;/a&gt;
            to improve your experience.
          &lt;/p&gt;
          &lt;![endif]--&gt;
          &lt;nav class="navbar navbar-default navbar-fixed-top" role="navigation"&gt;
            &lt;div class="container"&gt;
              &lt;div class="navbar-header"&gt;
              &lt;button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false"&gt;
                &lt;span class="sr-only"&gt;Toggle navigation&lt;/span&gt;
                &lt;span class="icon-bar"&gt;&lt;/span&gt;
                &lt;span class="icon-bar"&gt;&lt;/span&gt;
                &lt;span class="icon-bar"&gt;&lt;/span&gt;
              &lt;/button&gt;
                &lt;a class="navbar-brand" href="#"&gt;WebRTC Video Chat&lt;/a&gt;
              &lt;/div&gt;
              &lt;div id="navbar" class="navbar-collapse collapse"&gt;
                &lt;!-- chatroom name form --&gt;
                &lt;form class="navbar-form navbar-right form-inline"&gt;
                  &lt;div class="form-group"&gt;
                    &lt;input class="form-control" type="text" id="room-name" placeholder="Room name"/&gt;
                  &lt;/div&gt;
                  &lt;button class="btn btn-primary" id="btn-video-start"&gt;Start&lt;/button&gt;
                  &lt;button class="btn btn-default"  id="btn-video-join"&gt;Join&lt;/button&gt;
                  &lt;button class="btn btn-default"  disabled id="btn-video-stop"&gt;Stop&lt;/button&gt;
                &lt;/form&gt;
              &lt;/div&gt;
  
              &lt;!--/.navbar-collapse --&gt; &lt;/div&gt;
  
          &lt;/nav&gt;
  
          &lt;div class="container main"&gt;
            &lt;div class="row videos"&gt;
              &lt;div class="remote-video"&gt;
                &lt;video width="280" height="250" autoplay id="remote-video"&gt;&lt;/video&gt;
              &lt;/div&gt;
              &lt;div class="local-video"&gt;
                &lt;video width="280" height="250" autoplay id="local-video" muted&gt;&lt;/video&gt;
              &lt;/div&gt;
            &lt;/div&gt;
          &lt;/div&gt;
        &lt;/div&gt;
        &lt;!-- /container --&gt;
        &lt;script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.2/jquery.min.js"&gt;&lt;/script&gt;
        &lt;script&gt;window.jQuery || document.write('&lt;script src="js/vendor/jquery-1.11.2.min.js"&gt;&lt;\/script&gt;')&lt;/script&gt;
  
      &lt;script src="js/vendor/bootstrap.min.js"&gt;&lt;/script&gt;
      &lt;script src="js/vendor/socket.io.js"&gt;&lt;/script&gt;
  
      &lt;script src="js/main.js"&gt;&lt;/script&gt;
      &lt;script src="js/signalling.js"&gt;&lt;/script&gt;
  
      &lt;script src="//localhost:9010/livereload.js"&gt;&lt;/script&gt;
  
  &lt;/body&gt;
    &lt;/html&gt;
  </code></pre>