feat: Update Dockerfile and server configuration for improved health checks and error handling
This commit is contained in:
@@ -1,22 +1,19 @@
|
||||
import { XMLParser } from "fast-xml-parser";
|
||||
import { execFile } from "node:child_process";
|
||||
import { promisify } from "node:util";
|
||||
import { readFile, writeFile, mkdir } from "node:fs/promises";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { createHash } from "node:crypto";
|
||||
import { parseWeeksDocx, parseBellsDocx } from "./calendar.js";
|
||||
import { apiError, sourceError } from "./errors.js";
|
||||
import { DATA } from "./paths.js";
|
||||
import {
|
||||
academicStartYear,
|
||||
assignSemesters,
|
||||
todayIso,
|
||||
weekIndexOf,
|
||||
} from "../shared/weeks.js";
|
||||
const run = promisify(execFile);
|
||||
const ORIGIN = "https://nextcloud.psu.by";
|
||||
// The university's Nextcloud; tests point PSU_SOURCE_ORIGIN at a local stand-in.
|
||||
const ORIGIN = process.env.PSU_SOURCE_ORIGIN || "https://nextcloud.psu.by";
|
||||
const SHARE = "/public.php/dav/files/no5zKbMrns6j5jQ/";
|
||||
const YEAR_FOLDER = /^(\d{4})-(\d{4})$/;
|
||||
const DATA = fileURLToPath(new URL("../.data/", import.meta.url));
|
||||
const parser = new XMLParser({ removeNSPrefix: true, ignoreAttributes: false });
|
||||
const arr = (value) => (value ? (Array.isArray(value) ? value : [value]) : []);
|
||||
const DISCOVERY_TTL = 15 * 60 * 1000,
|
||||
@@ -39,6 +36,8 @@ const status = { catalogError: null, calendarError: null };
|
||||
const failedAt = { catalog: 0, calendar: 0 };
|
||||
const backingOff = (what) => Date.now() - failedAt[what] < MIN_REFRESH;
|
||||
|
||||
const TIMEOUT = Number(process.env.PSU_SOURCE_TIMEOUT_MS) || 25000;
|
||||
const MAX_SIZE = 10 * 1024 * 1024;
|
||||
export async function request(path, method = "GET") {
|
||||
// Only the university share is reachable; listings and catalog entries supply the paths.
|
||||
if (
|
||||
@@ -48,31 +47,35 @@ export async function request(path, method = "GET") {
|
||||
!["GET", "PROPFIND"].includes(method)
|
||||
)
|
||||
throw new Error("Недопустимый путь документа.");
|
||||
const url = ORIGIN + path;
|
||||
// curl also works with the system trust store on macOS. Certificate checks stay enabled.
|
||||
const args = [
|
||||
"--fail",
|
||||
"--silent",
|
||||
"--show-error",
|
||||
"--max-time",
|
||||
"25",
|
||||
"--max-filesize",
|
||||
"10485760",
|
||||
"--max-redirs",
|
||||
"0",
|
||||
"--request",
|
||||
method,
|
||||
];
|
||||
if (method === "PROPFIND") args.push("--header", "Depth: 1");
|
||||
args.push(url);
|
||||
const { stdout } = await run("curl", args, {
|
||||
encoding: "buffer",
|
||||
maxBuffer: 11 * 1024 * 1024,
|
||||
timeout: 28000,
|
||||
}).catch((error) => {
|
||||
// Node's fetch keeps connections to the university open between requests, so a catalog
|
||||
// crawl of a hundred listings pays for one TLS handshake, not a hundred. Certificates are
|
||||
// checked, redirects are refused, and the answer is cut off past 10 MB or 25 seconds.
|
||||
try {
|
||||
const response = await fetch(ORIGIN + path, {
|
||||
method,
|
||||
headers: method === "PROPFIND" ? { Depth: "1" } : {},
|
||||
redirect: "manual",
|
||||
signal: AbortSignal.timeout(TIMEOUT),
|
||||
});
|
||||
if (!response.ok) {
|
||||
await response.body?.cancel();
|
||||
throw new Error(`HTTP ${response.status}`);
|
||||
}
|
||||
if (Number(response.headers.get("content-length")) > MAX_SIZE) {
|
||||
await response.body?.cancel();
|
||||
throw new Error("Документ больше 10 МБ.");
|
||||
}
|
||||
const chunks = [];
|
||||
let size = 0;
|
||||
for await (const chunk of response.body || []) {
|
||||
size += chunk.length;
|
||||
if (size > MAX_SIZE) throw new Error("Документ больше 10 МБ.");
|
||||
chunks.push(chunk);
|
||||
}
|
||||
return Buffer.concat(chunks);
|
||||
} catch (error) {
|
||||
throw sourceError(error);
|
||||
});
|
||||
return stdout;
|
||||
}
|
||||
}
|
||||
export function decodeListing(xml, root = SHARE) {
|
||||
const entries = arr(parser.parse(xml).multistatus?.response);
|
||||
|
||||
Reference in New Issue
Block a user