mirror of
https://github.com/clusterio/clusterio.git
synced 2026-11-14 23:24:52 +09:00
Stop reading rejected save uploads to the controller
When a host pushes a save to a stream that is gone, or that timed out before anyone downloaded it, the controller answers 404 or 500 without reading the body. Node then reads and throws away the rest of the body to keep the connection open, so a proxy in front of the controller that keeps passing the upload on made the whole save get uploaded for nothing. Close the connection if the body hasn't ended a second after the response. Document turning off request buffering in reverse proxies, as a buffering proxy holds the upload back until all of it has arrived.
This commit is contained in:
3 files changed
+86
-3
No files matched your search
@@ -344,10 +344,36 @@ export async function createProxyStream(app: Application): Promise<ProxyStream>
|
||||
return stream;
|
||||
}
|
||||
|
||||
/** Time a rejected upload is read and thrown away before its connection is closed */
|
||||
const rejectedUploadDrainMs = 1000;
|
||||
|
||||
/**
|
||||
* Reject an upload without reading the rest of its body
|
||||
*
|
||||
* Node reads and discards the body of a request answered before it was
|
||||
* read to keep the connection open. A client, or a proxy in front of the
|
||||
* controller, that keeps sending would upload the whole body for nothing,
|
||||
* so the connection is closed if the body hasn't ended shortly after the
|
||||
* response. Closing it right away could make the client lose the response.
|
||||
* @param req - The upload.
|
||||
* @param res - Response to the upload.
|
||||
* @param status - HTTP status to respond with.
|
||||
*/
|
||||
function rejectUpload(req: Request, res: Response, status: number) {
|
||||
res.sendStatus(status);
|
||||
if (req.complete) {
|
||||
return;
|
||||
}
|
||||
const socket = req.socket;
|
||||
const timeout = setTimeout(() => socket.destroy(), rejectedUploadDrainMs);
|
||||
req.once("end", () => clearTimeout(timeout));
|
||||
socket.once("close", () => clearTimeout(timeout));
|
||||
}
|
||||
|
||||
async function putStream(req: Request<{ id: string }>, res: Response) {
|
||||
const stream = req.app.locals.streams.get(req.params.id);
|
||||
if (!stream || stream.source) {
|
||||
res.sendStatus(404);
|
||||
rejectUpload(req, res, 404);
|
||||
return;
|
||||
}
|
||||
stream.source = req;
|
||||
@@ -357,8 +383,7 @@ async function putStream(req: Request<{ id: string }>, res: Response) {
|
||||
stream.events.emit("source");
|
||||
stream.events.on("close", () => {
|
||||
if (!stream.flowing) {
|
||||
req.resume();
|
||||
res.sendStatus(500);
|
||||
rejectUpload(req, res, 500);
|
||||
} else {
|
||||
res.sendStatus(200);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user