fix(striker-ui-api): read server screenshot from memory instead of through job

main
Tsu-ba-me 2 years ago
parent d98df4b2a4
commit 8b828f81a5
  1. 10
      striker-ui-api/src/lib/consts/ENV.ts
  2. 187
      striker-ui-api/src/lib/request_handlers/server/getServerDetail.ts

@ -33,13 +33,3 @@ export const PUID = resolveUid(process.env.PUID ?? 'striker-ui-api');
* @default PUID * @default PUID
*/ */
export const PGID = resolveGid(process.env.PGID ?? PUID); export const PGID = resolveGid(process.env.PGID ?? PUID);
/**
* Get server screenshot job timeout in milliseconds. The job will be
* forced to progress 100 if it doesn't start within this time limit.
*
* @default 30000
*/
export const GET_SERVER_SCREENSHOT_TIMEOUT = Number.parseInt(
process.env.GET_SERVER_SCREENSHOT_TIMEOUT ?? '30000',
);

@ -1,25 +1,12 @@
import assert from 'assert'; import assert from 'assert';
import { RequestHandler } from 'express'; import { RequestHandler } from 'express';
import { ReadStream, createReadStream, writeFileSync } from 'fs'; import { existsSync, readFileSync } from 'fs';
import path from 'path'; import path from 'path';
import { import { REP_UUID, SERVER_PATHS } from '../../consts';
GET_SERVER_SCREENSHOT_TIMEOUT,
REP_UUID,
SERVER_PATHS,
} from '../../consts';
import { getLocalHostUUID, job, query, write } from '../../accessModule';
import { sanitize } from '../../sanitize'; import { sanitize } from '../../sanitize';
import { mkfifo, rm, stderr, stdout } from '../../shell'; import { stderr, stdout, stdoutVar } from '../../shell';
const rmfifo = (path: string) => {
try {
rm(path);
} catch (rmfifoError) {
stderr(`Failed to clean up FIFO; CAUSE: ${rmfifoError}`);
}
};
export const getServerDetail: RequestHandler< export const getServerDetail: RequestHandler<
ServerDetailParamsDictionary, ServerDetailParamsDictionary,
@ -28,21 +15,18 @@ export const getServerDetail: RequestHandler<
ServerDetailParsedQs ServerDetailParsedQs
> = async (request, response) => { > = async (request, response) => {
const { const {
params: { serverUUID }, params: { serverUUID: serverUuid },
query: { ss, resize }, query: { ss },
} = request; } = request;
const epoch = Date.now();
const isScreenshot = sanitize(ss, 'boolean'); const isScreenshot = sanitize(ss, 'boolean');
stdout( stdout(`serverUUID=[${serverUuid}],isScreenshot=[${isScreenshot}]`);
`serverUUID=[${serverUUID}],epoch=[${epoch}],isScreenshot=[${isScreenshot}]`,
);
try { try {
assert( assert(
REP_UUID.test(serverUUID), REP_UUID.test(serverUuid),
`Server UUID must be a valid UUID; got [${serverUUID}]`, `Server UUID must be a valid UUID; got [${serverUuid}]`,
); );
} catch (assertError) { } catch (assertError) {
stderr( stderr(
@ -53,161 +37,32 @@ export const getServerDetail: RequestHandler<
} }
if (isScreenshot) { if (isScreenshot) {
let requestHostUUID: string, serverHostUUID: string; const imageFileName = `${serverUuid}_screenshot`;
const imageFilePath = path.join(SERVER_PATHS.tmp.self, imageFileName);
try {
requestHostUUID = getLocalHostUUID();
} catch (subError) {
stderr(String(subError));
return response.status(500).send();
}
stdout(`requestHostUUID=[${requestHostUUID}]`);
try {
[[serverHostUUID]] = await query(`
SELECT server_host_uuid
FROM servers
WHERE server_uuid = '${serverUUID}';`);
} catch (queryError) {
stderr(`Failed to get server host UUID; CAUSE: ${queryError}`);
return response.status(500).send();
}
stdout(`serverHostUUID=[${serverHostUUID}]`);
const imageFifoName = `${serverUUID}_screenshot_${epoch}`;
const imageFifoPath = path.join(SERVER_PATHS.tmp.self, imageFifoName);
let fifoReadStream: ReadStream;
try {
mkfifo(imageFifoPath);
fifoReadStream = createReadStream(imageFifoPath, {
autoClose: true,
emitClose: true,
encoding: 'utf-8',
});
let imageData = '';
fifoReadStream.once('error', (readError) => {
stderr(`Failed to read from FIFO; CAUSE: ${readError}`);
});
fifoReadStream.once('close', () => {
stdout(`On close; removing FIFO at ${imageFifoPath}.`);
rmfifo(imageFifoPath);
return response.status(200).send({ screenshot: imageData });
});
fifoReadStream.on('data', (data) => { stdoutVar(
const imageChunk = data.toString().trim(); { imageFileName, imageFilePath },
const peekLength = 10; `Server ${serverUuid} image file: `,
stdout(
`${serverUUID} image chunk: ${
imageChunk.length > 0
? `${imageChunk.substring(
0,
peekLength,
)}...${imageChunk.substring(
imageChunk.length - peekLength - 1,
)}`
: 'empty'
}`,
);
imageData += imageChunk;
});
} catch (prepPipeError) {
stderr(
`Failed to prepare FIFO and/or receive image data; CAUSE: ${prepPipeError}`,
); );
rmfifo(imageFifoPath); const rsbody = { screenshot: '' };
return response.status(500).send();
}
let resizeArgs = sanitize(resize, 'string');
if (!/^\d+x\d+$/.test(resizeArgs)) {
resizeArgs = '';
}
let jobUuid: string;
if (existsSync(imageFilePath)) {
try { try {
jobUuid = await job({ rsbody.screenshot = readFileSync(imageFilePath, { encoding: 'utf-8' });
file: __filename,
job_command: SERVER_PATHS.usr.sbin['anvil-get-server-screenshot'].self,
job_data: `server-uuid=${serverUUID}
request-host-uuid=${requestHostUUID}
resize=${resizeArgs}
out-file-id=${epoch}`,
job_name: `get_server_screenshot::${serverUUID}::${epoch}`,
job_title: 'job_0356',
job_description: 'job_0357',
job_host_uuid: serverHostUUID,
});
} catch (subError) {
stderr(`Failed to queue fetch server screenshot job; CAUSE: ${subError}`);
return response.status(500).send();
}
const timeoutId: NodeJS.Timeout = setTimeout<[string, string]>(
async (uuid, fpath) => {
const [[isNotInProgress]]: [[number]] = await query(
`SELECT
CASE
WHEN job_progress IN (0, 100)
THEN CAST(1 AS BOOLEAN)
ELSE CAST(0 AS BOOLEAN)
END AS is_job_started
FROM jobs
WHERE job_uuid = '${uuid}';`,
);
if (isNotInProgress) {
stdout(
`Discard job ${uuid} because it's not-in-progress after timeout`,
);
try {
const wcode = await write(
`UPDATE jobs SET job_progress = 100 WHERE job_uuid = '${uuid}';`,
);
assert(wcode === 0, `Write exited with code ${wcode}`);
writeFileSync(fpath, '');
} catch (error) { } catch (error) {
stderr(`Failed to discard job ${uuid} on timeout; CAUSE: ${error}`); stderr(
`Failed to read image file at ${imageFilePath}; CAUSE: ${error}`,
);
return response.status(500).send(); return response.status(500).send();
} }
} }
},
GET_SERVER_SCREENSHOT_TIMEOUT,
jobUuid,
imageFifoPath,
);
fifoReadStream.once('data', () => {
stdout(`Receiving server screenshot image data; cancel timeout`);
clearTimeout(timeoutId); return response.send(rsbody);
});
} else { } else {
// For getting sever detail data. // For getting sever detail data.
response.status(200).send(); response.send();
} }
}; };

Loading…
Cancel
Save