feat: add graceful shutdown and better deployment environment logic
This commit is contained in:
+72
-22
@@ -4,12 +4,13 @@ import { Hono } from "hono";
|
||||
import { serve } from "@hono/node-server";
|
||||
import { serveStatic } from "@hono/node-server/serve-static";
|
||||
|
||||
import { env } from "./config/env.js";
|
||||
import { log } from "./config/logger.js";
|
||||
import { corsConfig } from "./config/cors.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 type { AniwatchAPIVariables } from "./config/variables.js";
|
||||
import type { ServerContext } from "./config/variables.js";
|
||||
|
||||
import { hianimeRouter } from "./routes/hianime.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 app = new Hono<{ Variables: AniwatchAPIVariables }>();
|
||||
const app = new Hono<ServerContext>();
|
||||
|
||||
app.use(logging);
|
||||
app.use(corsConfig);
|
||||
app.use(cacheControl);
|
||||
|
||||
//
|
||||
// CAUTION: For personal deployments, "refrain" from having an env
|
||||
// named "ANIWATCH_API_HOSTNAME". You may face rate limitting
|
||||
// or other issues if you do.
|
||||
const ISNT_PERSONAL_DEPLOYMENT = Boolean(env.ANIWATCH_API_HOSTNAME);
|
||||
if (ISNT_PERSONAL_DEPLOYMENT) {
|
||||
/*
|
||||
CAUTION:
|
||||
Having the "ANIWATCH_API_HOSTNAME" env will
|
||||
enable rate limitting for the deployment.
|
||||
WARNING:
|
||||
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("/", 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("/v", async (c) =>
|
||||
c.text(
|
||||
@@ -55,31 +64,72 @@ app.notFound(notFoundHandler);
|
||||
app.onError(errorHandler);
|
||||
|
||||
//
|
||||
// NOTE: this env is "required" for vercel deployments
|
||||
if (!env.ANIWATCH_API_VERCEL_DEPLOYMENT) {
|
||||
serve({
|
||||
(function () {
|
||||
/*
|
||||
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,
|
||||
fetch: app.fetch,
|
||||
}).addListener("listening", () => {
|
||||
}).addListener("listening", () =>
|
||||
log.info(
|
||||
`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) {
|
||||
const interval = 9 * 60 * 1000; // 9mins
|
||||
/*
|
||||
CAUTION:
|
||||
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
|
||||
setInterval(() => {
|
||||
https
|
||||
.get(url.href)
|
||||
.on("response", () => {
|
||||
log.info(
|
||||
`aniwatch-api HEALTH_CHECK at ${new Date().toISOString()}`
|
||||
);
|
||||
https
|
||||
.get(`https://${env.ANIWATCH_API_HOSTNAME}/health`)
|
||||
.on("error", (err) => log.error(err.message.trim()));
|
||||
}, interval);
|
||||
})
|
||||
.on("error", (err) =>
|
||||
log.warn(
|
||||
`aniwatch-api HEALTH_CHECK failed; ${err.message.trim()}`
|
||||
)
|
||||
);
|
||||
}, INTERVAL_DELAY);
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
||||
export default app;
|
||||
|
||||
Reference in New Issue
Block a user