feat: add graceful shutdown and better deployment environment logic
This commit is contained in:
+74
-24
@@ -4,12 +4,13 @@ import { Hono } from "hono";
|
|||||||
import { serve } from "@hono/node-server";
|
import { serve } from "@hono/node-server";
|
||||||
import { serveStatic } from "@hono/node-server/serve-static";
|
import { serveStatic } from "@hono/node-server/serve-static";
|
||||||
|
|
||||||
import { env } from "./config/env.js";
|
|
||||||
import { log } from "./config/logger.js";
|
import { log } from "./config/logger.js";
|
||||||
import { corsConfig } from "./config/cors.js";
|
import { corsConfig } from "./config/cors.js";
|
||||||
import { ratelimit } from "./config/ratelimit.js";
|
import { ratelimit } from "./config/ratelimit.js";
|
||||||
|
import { execGracefulShutdown } from "./utils.js";
|
||||||
|
import { DeploymentEnv, env, SERVERLESS_ENVIRONMENTS } from "./config/env.js";
|
||||||
import { errorHandler, notFoundHandler } from "./config/errorHandler.js";
|
import { errorHandler, notFoundHandler } from "./config/errorHandler.js";
|
||||||
import type { AniwatchAPIVariables } from "./config/variables.js";
|
import type { ServerContext } from "./config/variables.js";
|
||||||
|
|
||||||
import { hianimeRouter } from "./routes/hianime.js";
|
import { hianimeRouter } from "./routes/hianime.js";
|
||||||
import { logging } from "./middleware/logging.js";
|
import { logging } from "./middleware/logging.js";
|
||||||
@@ -20,22 +21,30 @@ import pkgJson from "../package.json" with { type: "json" };
|
|||||||
//
|
//
|
||||||
const BASE_PATH = "/api/v2" as const;
|
const BASE_PATH = "/api/v2" as const;
|
||||||
|
|
||||||
const app = new Hono<{ Variables: AniwatchAPIVariables }>();
|
const app = new Hono<ServerContext>();
|
||||||
|
|
||||||
app.use(logging);
|
app.use(logging);
|
||||||
app.use(corsConfig);
|
app.use(corsConfig);
|
||||||
app.use(cacheControl);
|
app.use(cacheControl);
|
||||||
|
|
||||||
//
|
/*
|
||||||
// CAUTION: For personal deployments, "refrain" from having an env
|
CAUTION:
|
||||||
// named "ANIWATCH_API_HOSTNAME". You may face rate limitting
|
Having the "ANIWATCH_API_HOSTNAME" env will
|
||||||
// or other issues if you do.
|
enable rate limitting for the deployment.
|
||||||
const ISNT_PERSONAL_DEPLOYMENT = Boolean(env.ANIWATCH_API_HOSTNAME);
|
WARNING:
|
||||||
if (ISNT_PERSONAL_DEPLOYMENT) {
|
If you are using any serverless environment, you must set the
|
||||||
|
"ANIWATCH_API_DEPLOYMENT_ENV" to that environment's name,
|
||||||
|
otherwise you may face issues.
|
||||||
|
*/
|
||||||
|
const isPersonalDeployment = Boolean(env.ANIWATCH_API_HOSTNAME);
|
||||||
|
if (isPersonalDeployment) {
|
||||||
app.use(ratelimit);
|
app.use(ratelimit);
|
||||||
}
|
}
|
||||||
|
|
||||||
app.use("/", serveStatic({ root: "public" }));
|
if (env.ANIWATCH_API_DEPLOYMENT_ENV === DeploymentEnv.NODEJS) {
|
||||||
|
app.use("/", serveStatic({ root: "public" }));
|
||||||
|
}
|
||||||
|
|
||||||
app.get("/health", (c) => c.text("daijoubu", { status: 200 }));
|
app.get("/health", (c) => c.text("daijoubu", { status: 200 }));
|
||||||
app.get("/v", async (c) =>
|
app.get("/v", async (c) =>
|
||||||
c.text(
|
c.text(
|
||||||
@@ -55,31 +64,72 @@ app.notFound(notFoundHandler);
|
|||||||
app.onError(errorHandler);
|
app.onError(errorHandler);
|
||||||
|
|
||||||
//
|
//
|
||||||
// NOTE: this env is "required" for vercel deployments
|
(function () {
|
||||||
if (!env.ANIWATCH_API_VERCEL_DEPLOYMENT) {
|
/*
|
||||||
serve({
|
NOTE:
|
||||||
|
"ANIWATCH_API_DEPLOYMENT_MODE" env must be set to
|
||||||
|
its supported name for serverless deployments
|
||||||
|
Eg: "vercel" for vercel deployments
|
||||||
|
*/
|
||||||
|
if (SERVERLESS_ENVIRONMENTS.includes(env.ANIWATCH_API_DEPLOYMENT_ENV)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const server = serve({
|
||||||
port: env.ANIWATCH_API_PORT,
|
port: env.ANIWATCH_API_PORT,
|
||||||
fetch: app.fetch,
|
fetch: app.fetch,
|
||||||
}).addListener("listening", () => {
|
}).addListener("listening", () =>
|
||||||
log.info(
|
log.info(
|
||||||
`aniwatch-api RUNNING at http://localhost:${env.ANIWATCH_API_PORT}`
|
`aniwatch-api RUNNING at http://localhost:${env.ANIWATCH_API_PORT}`
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
process.on("SIGINT", () => execGracefulShutdown(server));
|
||||||
|
process.on("SIGTERM", () => execGracefulShutdown(server));
|
||||||
|
process.on("uncaughtException", (err) => {
|
||||||
|
log.error(`Uncaught Exception: ${err.message}`);
|
||||||
|
execGracefulShutdown(server);
|
||||||
|
});
|
||||||
|
process.on("unhandledRejection", (reason, promise) => {
|
||||||
|
log.error(
|
||||||
|
`Unhandled Rejection at: ${promise}, reason: ${reason instanceof Error ? reason.message : reason}`
|
||||||
);
|
);
|
||||||
|
execGracefulShutdown(server);
|
||||||
});
|
});
|
||||||
|
|
||||||
// NOTE: remove the `if` block below for personal deployments
|
/*
|
||||||
if (ISNT_PERSONAL_DEPLOYMENT) {
|
CAUTION:
|
||||||
const interval = 9 * 60 * 1000; // 9mins
|
The `if` below block is for `render free deployments` only,
|
||||||
|
as their free tier has an approx 10 or 15 minute sleep time.
|
||||||
|
This is to keep the server awake and prevent it from sleeping.
|
||||||
|
You can enable the automatic health check by setting the
|
||||||
|
environment variables "ANIWATCH_API_HOSTNAME" to your deployment's hostname,
|
||||||
|
and "ANIWATCH_API_DEPLOYMENT_ENV" to "render" in your environment variables.
|
||||||
|
If you are not using render, you can remove the below `if` block.
|
||||||
|
*/
|
||||||
|
if (
|
||||||
|
isPersonalDeployment &&
|
||||||
|
env.ANIWATCH_API_DEPLOYMENT_ENV === DeploymentEnv.RENDER
|
||||||
|
) {
|
||||||
|
const INTERVAL_DELAY = 8 * 60 * 1000; // 8mins
|
||||||
|
const url = new URL(`https://${env.ANIWATCH_API_HOSTNAME}/health`);
|
||||||
|
|
||||||
// don't sleep
|
// don't sleep
|
||||||
setInterval(() => {
|
setInterval(() => {
|
||||||
log.info(
|
|
||||||
`aniwatch-api HEALTH_CHECK at ${new Date().toISOString()}`
|
|
||||||
);
|
|
||||||
https
|
https
|
||||||
.get(`https://${env.ANIWATCH_API_HOSTNAME}/health`)
|
.get(url.href)
|
||||||
.on("error", (err) => log.error(err.message.trim()));
|
.on("response", () => {
|
||||||
}, interval);
|
log.info(
|
||||||
|
`aniwatch-api HEALTH_CHECK at ${new Date().toISOString()}`
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.on("error", (err) =>
|
||||||
|
log.warn(
|
||||||
|
`aniwatch-api HEALTH_CHECK failed; ${err.message.trim()}`
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}, INTERVAL_DELAY);
|
||||||
}
|
}
|
||||||
}
|
})();
|
||||||
|
|
||||||
export default app;
|
export default app;
|
||||||
|
|||||||
Reference in New Issue
Block a user