serve favicons over hashed paths for improved caching

This commit is contained in:
Nick Krecklow 2020-05-08 04:47:10 -05:00
parent 66da5f6497
commit 4dfecce966
No known key found for this signature in database
GPG Key ID: 5F149FDE156FFA94
4 changed files with 73 additions and 18 deletions

@ -210,7 +210,13 @@ export class ServerRegistration {
// An updated favicon has been sent, update the src // An updated favicon has been sent, update the src
if (ping.favicon) { if (ping.favicon) {
document.getElementById('favicon_' + this.serverId).setAttribute('src', ping.favicon) const faviconElement = document.getElementById('favicon_' + this.serverId)
// Since favicons may be URLs, only update the attribute when it has changed
// Otherwise the browser may send multiple requests to the same URL
if (faviconElement.getAttribute('src') !== ping.favicon) {
faviconElement.setAttribute('src', ping.favicon)
}
} }
} }

@ -14,7 +14,7 @@ class App {
constructor () { constructor () {
this.mojangUpdater = new MojangUpdater(this) this.mojangUpdater = new MojangUpdater(this)
this.pingController = new PingController(this) this.pingController = new PingController(this)
this.server = new Server(this.handleClientConnection) this.server = new Server(this)
this.timeTracker = new TimeTracker(this) this.timeTracker = new TimeTracker(this)
} }

@ -11,9 +11,11 @@ function getRemoteAddr (req) {
} }
class Server { class Server {
constructor (clientSocketHandler) { constructor (app) {
this._app = app
this.createHttpServer() this.createHttpServer()
this.createWebSocketServer(clientSocketHandler) this.createWebSocketServer()
} }
createHttpServer () { createHttpServer () {
@ -23,15 +25,40 @@ class Server {
this._http = http.createServer((req, res) => { this._http = http.createServer((req, res) => {
logger.log('info', '%s requested: %s', getRemoteAddr(req), req.url) logger.log('info', '%s requested: %s', getRemoteAddr(req), req.url)
if (req.url.startsWith('/hashedfavicon?')) {
this.handleFaviconRequest(req, res)
} else {
// Attempt to handle req using distServeStatic, otherwise fail over to faviconServeStatic // Attempt to handle req using distServeStatic, otherwise fail over to faviconServeStatic
// If faviconServeStatic fails, pass to finalHttpHandler to terminate // If faviconServeStatic fails, pass to finalHttpHandler to terminate
distServeStatic(req, res, () => { distServeStatic(req, res, () => {
faviconsServeStatic(req, res, finalHttpHandler(req, res)) faviconsServeStatic(req, res, finalHttpHandler(req, res))
}) })
}
}) })
} }
createWebSocketServer (proxyClientSocketHandler) { handleFaviconRequest = (req, res) => {
const hash = req.url.split('?')[1]
for (const serverRegistration of this._app.serverRegistrations) {
if (serverRegistration.faviconHash && serverRegistration.faviconHash === hash) {
const buf = Buffer.from(serverRegistration.lastFavicon.split(',')[1], 'base64')
res.writeHead(200, {
'Content-Type': 'image/png',
'Content-Length': buf.length
}).end(buf)
return
}
}
// Terminate request, no match
res.writeHead(404)
res.end()
}
createWebSocketServer () {
this._wss = new WebSocket.Server({ this._wss = new WebSocket.Server({
server: this._http server: this._http
}) })
@ -45,7 +72,7 @@ class Server {
}) })
// Pass client off to proxy handler // Pass client off to proxy handler
proxyClientSocketHandler(client) this._app.handleClientConnection(client)
}) })
} }

@ -1,3 +1,5 @@
const crypto = require('crypto')
const TimeTracker = require('./time') const TimeTracker = require('./time')
const config = require('../config') const config = require('../config')
@ -61,11 +63,8 @@ class ServerRegistration {
update.recordData = this.recordData update.recordData = this.recordData
} }
// Compare against this.data.favicon to support favicon overrides if (this.updateFavicon(resp.favicon)) {
const newFavicon = resp.favicon || this.data.favicon update.favicon = this.getFaviconUrl()
if (this.updateFavicon(newFavicon)) {
// Append an updated favicon
update.favicon = newFavicon
} }
// Append a result object // Append a result object
@ -101,7 +100,7 @@ class ServerRegistration {
const payload = { const payload = {
versions: this.versions, versions: this.versions,
recordData: this.recordData, recordData: this.recordData,
favicon: this.lastFavicon favicon: this.getFaviconUrl()
} }
// Only append graphPeakData if defined // Only append graphPeakData if defined
@ -134,7 +133,8 @@ class ServerRegistration {
message: 'Pinging...' message: 'Pinging...'
}, },
recordData: this.recordData, recordData: this.recordData,
graphPeakData: this.getGraphPeak() graphPeakData: this.getGraphPeak(),
favicon: this.data.favicon
} }
} }
@ -216,13 +216,35 @@ class ServerRegistration {
} }
updateFavicon (favicon) { updateFavicon (favicon) {
// If data.favicon is defined, then a favicon override is present
// Disregard the incoming favicon, regardless if it is different
if (this.data.favicon) {
return false
}
if (favicon && favicon !== this.lastFavicon) { if (favicon && favicon !== this.lastFavicon) {
this.lastFavicon = favicon this.lastFavicon = favicon
// Generate an updated hash
// This is used by #getFaviconUrl
if (!this._faviconHasher) {
this._faviconHasher = crypto.createHash('md5')
}
this.faviconHash = this._faviconHasher.update(favicon).digest('hex').toString()
return true return true
} }
return false return false
} }
getFaviconUrl () {
if (this.faviconHash) {
return '/hashedfavicon?' + this.faviconHash
} else if (this.data.favicon) {
return this.data.favicon
}
}
updateProtocolVersionCompat (incomingId, outgoingId, protocolIndex) { updateProtocolVersionCompat (incomingId, outgoingId, protocolIndex) {
// If the result version matches the attempted version, the version is supported // If the result version matches the attempted version, the version is supported
const isSuccess = incomingId === outgoingId const isSuccess = incomingId === outgoingId