Skip to content

Commit 49878af

Browse files
committed
refactor: use hono instead of manual request handling
1 parent 774658a commit 49878af

File tree

7 files changed

+64
-104
lines changed

7 files changed

+64
-104
lines changed

deno.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,10 @@
1212
}
1313
},
1414
"imports": {
15+
"@hono/hono": "jsr:@hono/hono@^4.6.13",
1516
"@lambdalisue/github-emoji": "jsr:@lambdalisue/github-emoji@^1.0.0",
1617
"@std/encoding": "jsr:@std/encoding@^1.0.5",
17-
"@std/log": "jsr:@std/log@^0.224.11",
18-
"x/http_errors": "https://deno.land/x/[email protected]/mod.ts"
18+
"@std/log": "jsr:@std/log@^0.224.11"
1919
},
2020
"unstable": [
2121
"kv"

deno.lock

Lines changed: 6 additions & 22 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/filter.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@ import { UrlConfig } from "./types.d.ts";
33
import { requestLog, wildcardMatch } from "./util.ts";
44

55
export default async function filter(
6-
headers: Headers,
6+
headers: Record<string, string>,
77
json: any,
88
config: UrlConfig,
99
): Promise<string | null> {
1010
const reqLog = requestLog(headers);
11-
const event = headers.get("x-github-event") || "unknown";
11+
const event = headers["x-github-event"] || "unknown";
1212
const login: string | undefined = json.sender?.login?.toLowerCase();
1313

1414
// ignore events that Discord won't render anyway

src/handler.ts

Lines changed: 18 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,63 +1,40 @@
1-
import * as httpErrors from "x/http_errors";
1+
import { HTTPException } from "@hono/hono/http-exception";
22

3-
import config from "./config.ts";
4-
import { hasKey, verify } from "./crypto.ts";
53
import filterWebhook from "./filter.ts";
64
import fixupEmbeds from "./formatter.ts";
75
import { UrlConfig } from "./types.d.ts";
86
import { parseBool, requestLog } from "./util.ts";
97
import { sendWebhook } from "./webhook.ts";
108

119
export default async function handle(
12-
req: Request,
13-
): Promise<Response | [Response, Record<string, string>]> {
14-
const url = new URL(req.url);
15-
16-
// redirect to repo if `GET /`
17-
if (req.method === "GET" && config.mainRedirect && url.pathname === "/") {
18-
return Response.redirect(config.mainRedirect);
19-
}
20-
21-
// everything else should be a POST
22-
if (req.method !== "POST") {
23-
throw httpErrors.createError(405);
24-
}
25-
26-
// split url into parts
27-
const [, id, token] = url.pathname.split("/");
28-
if (!id || !token) {
29-
throw httpErrors.createError(400);
30-
}
31-
32-
// verify signature
33-
if (hasKey) {
34-
const signature = url.searchParams.get("sig");
35-
if (!signature) throw httpErrors.createError(400);
36-
if (!(await verify(`${id}/${token}`, signature))) throw httpErrors.createError(403);
37-
}
38-
39-
// extract data
40-
const urlConfig = getUrlConfig(url.searchParams);
41-
const data = await req.text();
42-
const json: Record<string, any> = JSON.parse(data);
10+
json: Record<string, any>,
11+
headers: Record<string, string>,
12+
queryParams: Record<string, string>,
13+
id: string,
14+
token: string,
15+
): Promise<[Response, Record<string, string>]> {
16+
const urlConfig = getUrlConfig(queryParams);
4317

4418
// do the thing
45-
const filterReason = await filterWebhook(req.headers, json, urlConfig);
19+
const filterReason = await filterWebhook(headers, json, urlConfig);
4620
if (filterReason !== null) {
47-
const reqLog = requestLog(req.headers);
21+
const reqLog = requestLog(headers);
4822
reqLog.debug(`handler: ignored due to '${filterReason}'`);
49-
return new Response(`Ignored by webhook filter (reason: ${filterReason})`, { status: 203 });
23+
return [
24+
new Response(`Ignored by webhook filter (reason: ${filterReason})`, { status: 203 }),
25+
{},
26+
];
5027
}
5128

5229
// mutate `json` in-place (fixing codeblocks etc.)
5330
fixupEmbeds(json);
5431

55-
return await sendWebhook(id, token, req.headers, json);
32+
return await sendWebhook(id, token, headers, json);
5633
}
5734

58-
function getUrlConfig(params: URLSearchParams): UrlConfig {
35+
function getUrlConfig(params: Record<string, string>): UrlConfig {
5936
const config: UrlConfig = {};
60-
for (const [key, value] of params) {
37+
for (const [key, value] of Object.entries(params)) {
6138
switch (key) {
6239
case "sig":
6340
continue;
@@ -71,7 +48,7 @@ function getUrlConfig(params: URLSearchParams): UrlConfig {
7148
config.commentBurstLimit = parseInt(value);
7249
break;
7350
default:
74-
throw httpErrors.createError(418, `Unknown config option: ${key}`);
51+
throw new HTTPException(418, { message: `Unknown config option: ${key}` });
7552
}
7653
}
7754
return config;

src/main.ts

Lines changed: 33 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1+
import { Hono } from "@hono/hono";
2+
import { HTTPException } from "@hono/hono/http-exception";
3+
import { logger } from "@hono/hono/logger";
14
import * as log from "@std/log";
2-
import * as httpErrors from "x/http_errors";
35

46
import config from "./config.ts";
7+
import { hasKey, verify } from "./crypto.ts";
58
import handler from "./handler.ts";
6-
import { requestLog } from "./util.ts";
79

810
function setupLogs() {
911
log.setup({
@@ -21,28 +23,30 @@ function setupLogs() {
2123
});
2224
}
2325

24-
async function handleRequest(req: Request, info: Deno.ServeHandlerInfo): Promise<Response> {
25-
const reqLog = requestLog(req.headers);
26+
const app = new Hono();
2627

27-
let res: Response;
28-
let meta: Record<string, string> | null = null;
29-
try {
30-
const webhookResult = await handler(req);
31-
if (webhookResult instanceof Response) {
32-
res = webhookResult;
33-
} else {
34-
[res, meta] = webhookResult;
35-
}
36-
} catch (err) {
37-
if (err instanceof httpErrors.HttpError && err.expose) {
38-
reqLog.warn(`http error: ${err.message}`);
39-
res = new Response(err.message, { status: err.status });
40-
} else {
41-
reqLog.critical(err);
42-
res = new Response("Internal Server Error", { status: 500 });
28+
app.use(logger());
29+
30+
if (config.mainRedirect) {
31+
app.get("/", (c) => {
32+
return c.redirect(config.mainRedirect!);
33+
});
34+
}
35+
36+
app.post("/:id/:token", async (c) => {
37+
const { id, token } = c.req.param();
38+
39+
// verify signature
40+
if (hasKey) {
41+
const signature = c.req.query("sig");
42+
if (!signature || !(await verify(`${id}/${token}`, signature))) {
43+
throw new HTTPException(403);
4344
}
4445
}
4546

47+
const data = await c.req.json();
48+
let [res, meta] = await handler(data, c.req.header(), c.req.query(), id, token);
49+
4650
// clone response to make headers mutable
4751
res = new Response(res.body, res);
4852

@@ -60,15 +64,8 @@ async function handleRequest(req: Request, info: Deno.ServeHandlerInfo): Promise
6064
res.headers.delete(header);
6165
}
6266

63-
// log request
64-
const respLen = res.headers.get("content-length") || 0;
65-
const addr = info.remoteAddr;
66-
reqLog.info(
67-
`http: ${addr.hostname}:${addr.port} - ${req.method} ${req.url} ${res.status} ${respLen}`,
68-
);
69-
7067
return res;
71-
}
68+
});
7269

7370
if (import.meta.main) {
7471
setupLogs();
@@ -77,10 +74,12 @@ if (import.meta.main) {
7774
log.info("url signing enabled");
7875
}
7976

80-
log.info(`starting webserver on ${config.hostname}:${config.port}`);
81-
Deno.serve({
82-
hostname: config.hostname,
83-
port: config.port,
84-
onListen: () => log.info(`listening on ${config.hostname}:${config.port}`),
85-
}, handleRequest);
77+
Deno.serve(
78+
{
79+
hostname: config.hostname,
80+
port: config.port,
81+
onListen: () => log.info(`listening on ${config.hostname}:${config.port}`),
82+
},
83+
app.fetch,
84+
);
8685
}

src/util.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,8 @@ export function wildcardMatch(pattern: string, target: string): boolean {
3333
/**
3434
* logging proxy that adds some metadata to log messages
3535
*/
36-
export function requestLog(headers: Headers) {
37-
const deliveryId = headers.get("x-github-delivery");
36+
export function requestLog(headers: Record<string, string>) {
37+
const deliveryId = headers["x-github-delivery"];
3838
const prefix = deliveryId ? `[${deliveryId}] ` : "";
3939

4040
// ugh

src/webhook.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { requestLog, sleep } from "./util.ts";
44
export async function sendWebhook(
55
id: string,
66
token: string,
7-
headers: Headers,
7+
headers: Record<string, string>,
88
data: Record<string, any>,
99
): Promise<[Response, Record<string, string>]> {
1010
const reqLog = requestLog(headers);

0 commit comments

Comments
 (0)