docs: function Server (opts) {
    var self = this
    if (!(self instanceof Server)) return new Server(opts)
    EventEmitter.call(self)
    if (!opts) opts = {}
  
    debug('new server %s', JSON.stringify(opts))
  
    self.intervalMs = opts.interval
      ? opts.interval
      : 10 * 60 * 1000 // 10 min
  
    self._trustProxy = !!opts.trustProxy
    if (typeof opts.filter === 'function') self._filter = opts.filter
  
    self.peersCacheLength = opts.peersCacheLength
    self.peersCacheTtl = opts.peersCacheTtl
  
    self._listenCalled = false
    self.listening = false
    self.destroyed = false
    self.torrents = {}
  
    self.http = null
    self.udp4 = null
    self.udp6 = null
    self.ws = null
    self.zswarm = null
  
    // create a zeronet swarm and apply the tracker commands
    if (opts.zero !== false) {
      self.zswarm = opts.zswarm
      if (self.zswarm) {
        self.zswarm_custom = true
      } else {
        self.zswarm = true
      }
    }
  
    // start an http tracker unless the user explictly says no
    if (opts.http !== false) {
      self.http = http.createServer()
      self.http.on('error', function (err) { self._onError(err) })
      self.http.on('listening', onListening)
  
      // Add default http request handler on next tick to give user the chance to add
      // their own handler first. Handle requests untouched by user's handler.
      process.nextTick(function () {
        self.http.on('request', function (req, res) {
          if (res.headersSent) return
          self.onHttpRequest(req, res)
        })
      })
    }
  
    // start a udp tracker unless the user explicitly says no
    if (opts.udp !== false) {
      var isNode10 = /^v0.10./.test(process.version)
  
      self.udp4 = self.udp = dgram.createSocket(
        isNode10 ? 'udp4' : { type: 'udp4', reuseAddr: true }
      )
      self.udp4.on('message', function (msg, rinfo) { self.onUdpRequest(msg, rinfo) })
      self.udp4.on('error', function (err) { self._onError(err) })
      self.udp4.on('listening', onListening)
  
      self.udp6 = dgram.createSocket(
        isNode10 ? 'udp6' : { type: 'udp6', reuseAddr: true }
      )
      self.udp6.on('message', function (msg, rinfo) { self.onUdpRequest(msg, rinfo) })
      self.udp6.on('error', function (err) { self._onError(err) })
      self.udp6.on('listening', onListening)
    }
  
    // start a websocket tracker (for WebTorrent) unless the user explicitly says no
    if (opts.ws !== false) {
      if (!self.http) {
        self.http = http.createServer()
        self.http.on('error', function (err) { self._onError(err) })
        self.http.on('listening', onListening)
  
        // Add default http request handler on next tick to give user the chance to add
        // their own handler first. Handle requests untouched by user's handler.
        process.nextTick(function () {
          self.http.on('request', function (req, res) {
            if (res.headersSent) return
            // For websocket trackers, we only need to handle the UPGRADE http method.
            // Return 404 for all other request types.
            res.statusCode = 404
            res.end('404 Not Found')
          })
        })
      }
      self.ws = new WebSocketServer({
        server: self.http,
        perMessageDeflate: false,
        clientTracking: false
      })
      self.ws.address = function () {
        return self.http.address()
      }
      self.ws.on('error', function (err) { self._onError(err) })
      self.ws.on('connection', function (socket, req) {
        // Note: socket.upgradeReq was removed in ws@3.0.0, so re-add it.
        // https://github.com/websockets/ws/pull/1099
        socket.upgradeReq = req
        self.onWebSocketConnection(socket)
      })
    }
  
    if (opts.stats !== false) {
      if (!self.http) {
        self.http = http.createServer()
        self.http.on('error', function (err) { self._onError(err) })
        self.http.on('listening', onListening)
      }
  
      // Http handler for '/stats' route
      self.http.on('request', function (req, res) {
        if (res.headersSent) return
  
        var infoHashes = Object.keys(self.torrents)
        var activeTorrents = 0
        var allPeers = {}
  
        function countPeers (filterFunction) {
          var count = 0
          var key
  
          for (key in allPeers) {
            if (allPeers.hasOwnProperty(key) && filterFunction(allPeers[key])) {
              count++
            }
          }
  
          return count
        }
  
        function groupByClient () {
          var clients = {}
          for (var key in allPeers) {
            if (allPeers.hasOwnProperty(key)) {
              var peer = allPeers[key]
  
              if (!clients[peer.client.client]) {
                clients[peer.client.client] = {}
              }
              var client = clients[peer.client.client]
              // If the client is not known show 8 chars from peerId as version
              var version = peer.client.version || new Buffer(peer.peerId, 'hex').toString().substring(0, 8)
              if (!client[version]) {
                client[version] = 0
              }
              client[version]++
            }
          }
          return clients
        }
  
        function printClients (clients) {
          var html = '<ul>\n'
          for (var name in clients) {
            if (clients.hasOwnProperty(name)) {
              var client = clients[name]
              for (var version in client) {
                if (client.hasOwnProperty(version)) {
                  html += '<li><strong>' + name + '</strong> ' + version + ' : ' + client[version] + '</li>\n'
                }
              }
            }
          }
          html += '</ul>'
          return html
        }
  
        if (req.method === 'GET' && (req.url === '/stats' || req.url === '/stats.json')) {
          infoHashes.forEach(function (infoHash) {
            var peers = self.torrents[infoHash].peers
            var keys = peers.keys
            if (keys.length > 0) activeTorrents++
  
            keys.forEach(function (peerId) {
              // Don't mark the peer as most recently used for stats
              var peer = peers.peek(peerId)
              if (peer == null) return // peers.peek() can evict the peer
  
              if (!allPeers.hasOwnProperty(peerId)) {
                allPeers[peerId] = {
                  ipv4: false,
                  ipv6: false,
                  seeder: false,
                  leecher: false
                }
              }
  
              if (peer.ip.indexOf(':') >= 0) {
                allPeers[peerId].ipv6 = true
              } else {
                allPeers[peerId].ipv4 = true
              }
  
              if (peer.complete) {
                allPeers[peerId].seeder = true
              } else {
                allPeers[peerId].leecher = true
              }
  
              allPeers[peerId].peerId = peer.peerId
              allPeers[peerId].client = peerid(peer.peerId)
            })
          })
  
          var isSeederOnly = function (peer) { return peer.seeder && peer.leecher === false }
          var isLeecherOnly = function (peer) { return peer.leecher && peer.seeder === false }
          var isSeederAndLeecher = function (peer) { return peer.seeder && peer.leecher }
          var isIPv4 = function (peer) { return peer.ipv4 }
          var isIPv6 = function (peer) { return peer.ipv6 }
  
          var stats = {
            torrents: infoHashes.length,
            activeTorrents: activeTorrents,
            peersAll: Object.keys(allPeers).length,
            peersSeederOnly: countPeers(isSeederOnly),
            peersLeecherOnly: countPeers(isLeecherOnly),
            peersSeederAndLeecher: countPeers(isSeederAndLeecher),
            peersIPv4: countPeers(isIPv4),
            peersIPv6: countPeers(isIPv6),
            clients: groupByClient()
          }
  
          if (req.url === '/stats.json' || req.headers['accept'] === 'application/json') {
            res.write(JSON.stringify(stats))
            res.end()
          } else if (req.url === '/stats') {
            res.end('<h1>' + stats.torrents + ' torrents (' + stats.activeTorrents + ' active)</h1>\n' +
              '<h2>Connected Peers: ' + stats.peersAll + '</h2>\n' +
              '<h3>Peers Seeding Only: ' + stats.peersSeederOnly + '</h3>\n' +
              '<h3>Peers Leeching Only: ' + stats.peersLeecherOnly + '</h3>\n' +
              '<h3>Peers Seeding & Leeching: ' + stats.peersSeederAndLeecher + '</h3>\n' +
              '<h3>IPv4 Peers: ' + stats.peersIPv4 + '</h3>\n' +
              '<h3>IPv6 Peers: ' + stats.peersIPv6 + '</h3>\n' +
              '<h3>Clients:</h3>\n' +
              printClients(stats.clients)
            )
          }
        }
      })
    }
  
    var num = !!self.http + !!self.udp4 + !!self.udp6
    function onListening () {
      num -= 1
      if (num === 0) {
        self.listening = true
        debug('listening')
        self.emit('listening')
      }
    }
  }