mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-14 20:40:17 -05:00
Merge pull request #21011 from overleaf/ls-serve-static-wrapper
add a wrapper for serve static to handle premature close error GitOrigin-RevId: 8128702d9340a893624061d07bf0da15ea457f43
This commit is contained in:
parent
3be3bf9037
commit
8c342dc226
3 changed files with 104 additions and 4 deletions
34
services/web/app/src/infrastructure/ServeStaticWrapper.mjs
Normal file
34
services/web/app/src/infrastructure/ServeStaticWrapper.mjs
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
import express from 'express'
|
||||||
|
import { plainTextResponse } from './Response.js'
|
||||||
|
|
||||||
|
/*
|
||||||
|
This wrapper is implemented specifically to handle "Premature Close" errors.
|
||||||
|
These errors occur when the client cancels a request while static assets are being loaded.
|
||||||
|
This issue is beyond our control, it can result in unnecessary log noise.
|
||||||
|
Therefore, this wrapper is added to handle such errors.
|
||||||
|
*/
|
||||||
|
function serveStaticWrapper(root, options) {
|
||||||
|
const serveStatic = express.static(root, options)
|
||||||
|
return (req, res, next) => {
|
||||||
|
serveStatic(req, res, error => {
|
||||||
|
if (!error) {
|
||||||
|
return next()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error.code !== 'ERR_STREAM_PREMATURE_CLOSE') {
|
||||||
|
return next(error)
|
||||||
|
}
|
||||||
|
|
||||||
|
req.logger.addFields({ err: error })
|
||||||
|
req.logger.setLevel('debug')
|
||||||
|
if (res.headersSent) {
|
||||||
|
res.end()
|
||||||
|
} else {
|
||||||
|
res.status(400)
|
||||||
|
plainTextResponse(res, 'Premature close')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default serveStaticWrapper
|
|
@ -37,6 +37,7 @@ import noCache from 'nocache'
|
||||||
import os from 'os'
|
import os from 'os'
|
||||||
import http from 'http'
|
import http from 'http'
|
||||||
import { fileURLToPath } from 'url'
|
import { fileURLToPath } from 'url'
|
||||||
|
import serveStaticWrapper from './ServeStaticWrapper.mjs'
|
||||||
|
|
||||||
const sessionsRedisClient = UserSessionsRedis.client()
|
const sessionsRedisClient = UserSessionsRedis.client()
|
||||||
|
|
||||||
|
@ -120,10 +121,13 @@ if (Settings.exposeHostname) {
|
||||||
}
|
}
|
||||||
|
|
||||||
webRouter.use(
|
webRouter.use(
|
||||||
express.static(fileURLToPath(new URL('../../../public', import.meta.url)), {
|
serveStaticWrapper(
|
||||||
maxAge: STATIC_CACHE_AGE,
|
fileURLToPath(new URL('../../../public', import.meta.url)),
|
||||||
setHeaders: csp.removeCSPHeaders,
|
{
|
||||||
})
|
maxAge: STATIC_CACHE_AGE,
|
||||||
|
setHeaders: csp.removeCSPHeaders,
|
||||||
|
}
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
app.set('views', fileURLToPath(new URL('../../views', import.meta.url)))
|
app.set('views', fileURLToPath(new URL('../../views', import.meta.url)))
|
||||||
|
|
|
@ -0,0 +1,62 @@
|
||||||
|
import { strict as esmock } from 'esmock'
|
||||||
|
import { expect } from 'chai'
|
||||||
|
import Path from 'node:path'
|
||||||
|
import { fileURLToPath } from 'node:url'
|
||||||
|
import sinon from 'sinon'
|
||||||
|
import MockResponse from '../helpers/MockResponse.js'
|
||||||
|
import MockRequest from '../helpers/MockRequest.js'
|
||||||
|
|
||||||
|
const __dirname = fileURLToPath(new URL('.', import.meta.url))
|
||||||
|
const modulePath = Path.join(
|
||||||
|
__dirname,
|
||||||
|
'../../../../app/src/infrastructure/ServeStaticWrapper'
|
||||||
|
)
|
||||||
|
|
||||||
|
describe('ServeStaticWrapperTests', function () {
|
||||||
|
let error = null
|
||||||
|
|
||||||
|
beforeEach(async function () {
|
||||||
|
this.req = new MockRequest()
|
||||||
|
this.res = new MockResponse()
|
||||||
|
this.express = {
|
||||||
|
static: () => (req, res, next) => {
|
||||||
|
if (error) {
|
||||||
|
next(error)
|
||||||
|
} else {
|
||||||
|
next()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
this.serveStaticWrapper = await esmock(modulePath, {
|
||||||
|
express: this.express,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
this.afterEach(() => {
|
||||||
|
error = null
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Premature close error thrown', async function () {
|
||||||
|
error = new Error()
|
||||||
|
error.code = 'ERR_STREAM_PREMATURE_CLOSE'
|
||||||
|
const middleware = this.serveStaticWrapper('test_folder', {})
|
||||||
|
const next = sinon.stub()
|
||||||
|
middleware(this.req, this.res, next)
|
||||||
|
expect(next.called).to.be.false
|
||||||
|
})
|
||||||
|
|
||||||
|
it('No error thrown', async function () {
|
||||||
|
const middleware = this.serveStaticWrapper('test_folder', {})
|
||||||
|
const next = sinon.stub()
|
||||||
|
middleware(this.req, this.res, next)
|
||||||
|
expect(next).to.be.calledWith()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Other error thrown', async function () {
|
||||||
|
error = new Error()
|
||||||
|
const middleware = this.serveStaticWrapper('test_folder', {})
|
||||||
|
const next = sinon.stub()
|
||||||
|
middleware(this.req, this.res, next)
|
||||||
|
expect(next).to.be.calledWith(error)
|
||||||
|
})
|
||||||
|
})
|
Loading…
Reference in a new issue