From 3b4e9d20bb727561e1b26b4196f2b13ae8cf1acf Mon Sep 17 00:00:00 2001 From: Denis Date: Mon, 30 Oct 2023 19:14:42 -0400 Subject: [PATCH] add cors (#46) --- index.ts | 5 ++++- src/fetch/GET.ts | 11 ++++++----- src/fetch/OPTIONS.ts | 5 +++++ src/fetch/block.ts | 5 +++-- src/fetch/chains.ts | 5 +++-- src/fetch/cors.ts | 37 +++++++++++++++++++++++++++++++++++++ src/fetch/health.ts | 7 ++++--- 7 files changed, 62 insertions(+), 13 deletions(-) create mode 100644 src/fetch/OPTIONS.ts create mode 100644 src/fetch/cors.ts diff --git a/index.ts b/index.ts index 0829e2c..bae1c44 100644 --- a/index.ts +++ b/index.ts @@ -1,7 +1,9 @@ import { config } from "./src/config.js"; import { logger } from "./src/logger.js"; import GET from "./src/fetch/GET.js"; +import OPTIONS from "./src/fetch/OPTIONS.js"; import * as prometheus from "./src/prometheus.js"; +import { BadRequest } from "./src/fetch/cors.js"; if (config.verbose) logger.enable(); @@ -10,8 +12,9 @@ const app = Bun.serve({ port: config.port, fetch(req: Request) { if (req.method === "GET") return GET(req); + if (req.method === "OPTIONS") return OPTIONS(req); prometheus.request_error.inc({pathname: new URL(req.url).pathname, status: 400}); - return new Response("Invalid request", { status: 400 }); + return BadRequest } }); diff --git a/src/fetch/GET.ts b/src/fetch/GET.ts index efc3a6a..f3c8e9e 100644 --- a/src/fetch/GET.ts +++ b/src/fetch/GET.ts @@ -7,18 +7,19 @@ import * as prometheus from "../prometheus.js"; import { logger } from "../logger.js"; import swaggerHtml from "../../swagger/index.html" import swaggerFavicon from "../../swagger/favicon.png" +import { NotFound, toFile, toJSON, toText } from "./cors.js"; export default async function (req: Request) { const { pathname} = new URL(req.url); prometheus.request.inc({pathname}); - if ( pathname === "/" ) return new Response(Bun.file(swaggerHtml)); - if ( pathname === "/favicon.png" ) return new Response(Bun.file(swaggerFavicon)); + if ( pathname === "/" ) return toFile(Bun.file(swaggerHtml)); + if ( pathname === "/favicon.png" ) return toFile(Bun.file(swaggerFavicon)); if ( pathname === "/health" ) return health(req); - if ( pathname === "/metrics" ) return new Response(await registry.metrics(), {headers: {"Content-Type": registry.contentType}}); - if ( pathname === "/openapi" ) return new Response(openapi, {headers: {"Content-Type": "application/json"}}); + if ( pathname === "/metrics" ) return toText(await registry.metrics()); + if ( pathname === "/openapi" ) return toJSON(openapi); if ( pathname === "/chains" ) return chains(req); if ( pathname === "/block" ) return block(req); logger.warn(`Not found: ${pathname}`); prometheus.request_error.inc({pathname, status: 404}); - return new Response("Not found", { status: 404 }); + return NotFound; } diff --git a/src/fetch/OPTIONS.ts b/src/fetch/OPTIONS.ts new file mode 100644 index 0000000..ef01632 --- /dev/null +++ b/src/fetch/OPTIONS.ts @@ -0,0 +1,5 @@ +import { CORS_HEADERS } from "./cors.js"; + +export default async function (req: Request) { + return new Response('Departed', {headers: CORS_HEADERS}); +} \ No newline at end of file diff --git a/src/fetch/block.ts b/src/fetch/block.ts index c58dd4a..912b6fc 100644 --- a/src/fetch/block.ts +++ b/src/fetch/block.ts @@ -2,6 +2,7 @@ import { makeQuery } from "../clickhouse/makeQuery.js"; import { logger } from "../logger.js"; import { Block, getBlock } from "../queries.js"; import * as prometheus from "../prometheus.js"; +import { BadRequest, toJSON } from "./cors.js"; export default async function (req: Request) { try { @@ -9,10 +10,10 @@ export default async function (req: Request) { logger.info({searchParams: Object.fromEntries(Array.from(searchParams))}); const query = await getBlock(searchParams); const response = await makeQuery(query) - return new Response(JSON.stringify(response.data), { headers: { "Content-Type": "application/json" } }); + return toJSON(response.data); } catch (e: any) { logger.error(e); prometheus.request_error.inc({pathname: "/block", status: 400}); - return new Response(e.message, { status: 400 }); + return BadRequest } } \ No newline at end of file diff --git a/src/fetch/chains.ts b/src/fetch/chains.ts index d8c192d..5a3001a 100644 --- a/src/fetch/chains.ts +++ b/src/fetch/chains.ts @@ -2,6 +2,7 @@ import { makeQuery } from "../clickhouse/makeQuery.js"; import { logger } from "../logger.js"; import * as prometheus from "../prometheus.js"; import { getChain } from "../queries.js"; +import { BadRequest, toJSON } from "./cors.js"; export async function supportedChainsQuery() { const response = await makeQuery<{chain: string}>(getChain()); @@ -11,10 +12,10 @@ export async function supportedChainsQuery() { export default async function (req: Request) { try { const chains = await supportedChainsQuery(); - return new Response(JSON.stringify(chains), { headers: { "Content-Type": "application/json" } }); + return toJSON(chains); } catch (e: any) { logger.error(e); prometheus.request_error.inc({pathname: "/chains", status: 400}); - return new Response(e.message, { status: 400 }); + return BadRequest; } } \ No newline at end of file diff --git a/src/fetch/cors.ts b/src/fetch/cors.ts new file mode 100644 index 0000000..ec7a9b3 --- /dev/null +++ b/src/fetch/cors.ts @@ -0,0 +1,37 @@ +import { BunFile } from "bun"; + +export const BadRequest = toText('Bad Request', 400); +export const NotFound = toText('Not Found', 404); +export const InternalServerError = toText("Internal Server Error", 500); + +export const CORS_HEADERS = new Headers({ + "Access-Control-Allow-Origin": "*", + "Access-Control-Allow-Methods": "GET, POST, OPTIONS", + "Access-Control-Allow-Headers": "Content-Type, WWW-Authenticate", +}); +export const JSON_HEADERS = new Headers({"Content-Type": "application/json"}); +export const TEXT_HEADERS = new Headers({"Content-Type": "text/plain; version=0.0.4; charset=utf-8"}); + +export function appendHeaders(...args: Headers[]) { + const headers = new Headers(CORS_HEADERS); // CORS as default headers + for (const arg of args) { + for (const [key, value] of arg.entries()) { + headers.set(key, value); + } + } + return headers; +}; + +export function toJSON(body: any, status = 200, headers = new Headers()) { + const data = typeof body == "string" ? body : JSON.stringify(body, null, 2); + return new Response(data, { status, headers: appendHeaders(JSON_HEADERS, headers) }); +} + +export function toText(body: string, status = 200, headers = new Headers()) { + return new Response(body, { status, headers: appendHeaders(TEXT_HEADERS, headers) }); +} + +export function toFile(body: BunFile, status = 200, headers = new Headers()) { + const fileHeaders = new Headers({"Content-Type": body.type}); + return new Response(body, { status, headers: appendHeaders(fileHeaders, headers) }); +} diff --git a/src/fetch/health.ts b/src/fetch/health.ts index a0b6684..b87b35c 100644 --- a/src/fetch/health.ts +++ b/src/fetch/health.ts @@ -1,16 +1,17 @@ import client from "../clickhouse/createClient.js"; import { logger } from "../logger.js"; import * as prometheus from "../prometheus.js"; +import { InternalServerError, toText } from "./cors.js"; export default async function (req: Request) { try { const response = await client.ping(); if (response.success === false) throw new Error(response.error.message); - if (response.success === true ) return new Response("OK"); - return new Response("Unknown response from ClickHouse"); + if (response.success === true ) return toText("OK"); + return InternalServerError; } catch (e: any) { logger.error(e); prometheus.request_error.inc({ pathname: "/health", status: 500}); - return new Response(e.message, { status: 500 }); + return InternalServerError; } } \ No newline at end of file