Skip to content

Instantly share code, notes, and snippets.

@yingziwu
Last active June 19, 2022 10:38
Show Gist options
  • Select an option

  • Save yingziwu/1ed1ad71f876638cde8790adc1cc2295 to your computer and use it in GitHub Desktop.

Select an option

Save yingziwu/1ed1ad71f876638cde8790adc1cc2295 to your computer and use it in GitHub Desktop.

Revisions

  1. yingziwu revised this gist Dec 22, 2021. 1 changed file with 1 addition and 2 deletions.
    3 changes: 1 addition & 2 deletions cors-deno.ts
    Original file line number Diff line number Diff line change
    @@ -59,9 +59,8 @@ async function proxy(request: Request): Promise<Response> {
    function isIgnore(name: string) {
    const ignoreList = [
    "content-length",
    /^access\-control\-/,
    /^cf\-/,
    "x-forwarded-proto",
    /^x\-forwarded\-/,
    "x-real-ip",
    ];

  2. yingziwu revised this gist Dec 22, 2021. 1 changed file with 2 additions and 2 deletions.
    4 changes: 2 additions & 2 deletions cors-deno.ts
    Original file line number Diff line number Diff line change
    @@ -24,8 +24,8 @@ function options(): Promise<Response> {
    }

    async function proxy(request: Request): Promise<Response> {
    const { pathname } = new URL(request.url);
    const url = pathname.replace(/^\//, "");
    const { href, origin } = new URL(request.url);
    const url = href.replace(origin, "").replace(/^\//, "");
    if (!urlTest(url)) {
    return error(400);
    }
  3. yingziwu revised this gist Dec 22, 2021. 1 changed file with 42 additions and 54 deletions.
    96 changes: 42 additions & 54 deletions cors-deno.ts
    Original file line number Diff line number Diff line change
    @@ -31,75 +31,65 @@ async function proxy(request: Request): Promise<Response> {
    }
    const { host } = new URL(url);

    async function getProxyRequest(): Promise<Request> {
    async function getProxyRequest(req: Request): Promise<Request> {
    const init = {
    body: getRequestBody(request),
    cache: request.cache,
    headers: getRequestHeaders(),
    keepalive: request.keepalive,
    method: request.method,
    body: req.body,
    cache: req.cache,
    headers: getRequestHeaders(req),
    keepalive: req.keepalive,
    method: req.method,
    redirect: "follow" as RequestRedirect,
    };
    const req = new Request(url, init);
    return req;
    const proxyReq = new Request(url, init);
    return proxyReq;

    function getRequestBody(_req: Request) {
    const body = _req.body;
    return body ?? null;
    }
    function getRequestHeaders() {
    function getRequestHeaders(_req: Request) {
    const headers: Record<string, string> = {};
    [...request.headers.entries()].forEach((kv) => {
    [..._req.headers.entries()].forEach((kv) => {
    if (kv[0].toLowerCase() === "host") {
    return (headers["host"] = host);
    } else if (isIgnore(kv[0])) {
    return;
    } else {
    return (headers[kv[0]] = kv[1]);
    }
    });
    return headers;

    function isIgnore(name: string) {
    const ignoreList = [
    "content-length",
    /^access\-control\-/,
    /^cf\-/,
    "x-forwarded-proto",
    "x-real-ip",
    ];

    for (const i of ignoreList) {
    if (typeof i === "string") {
    if (i === name.toLocaleLowerCase()) {
    return true;
    }
    function isIgnore(name: string) {
    const ignoreList = [
    "content-length",
    /^access\-control\-/,
    /^cf\-/,
    "x-forwarded-proto",
    "x-real-ip",
    ];

    for (const i of ignoreList) {
    if (typeof i === "string") {
    if (i === name.toLocaleLowerCase()) {
    return true;
    }
    if (i instanceof RegExp) {
    if (i.test(name)) {
    return true;
    }
    }
    if (i instanceof RegExp) {
    if (i.test(name)) {
    return true;
    }
    }
    return false;
    }
    });
    return headers;
    return false;
    }
    }
    }

    async function getProxyResponse(req: Request): Promise<Response> {
    const resp = await fetch(req);
    const headers = getResponseHeaders(resp);
    const status = resp.status;
    const _body = resp.body;
    let body: ReadableStream<Uint8Array> | null;
    if (_body !== null) {
    body = bodyHandler(_body);
    } else {
    body = _body;
    }
    const body = resp.body ? bodyHandler(resp.body) : resp.body;
    const response = new Response(body, {
    headers: headers,
    status: status,
    headers,
    status,
    });
    return response;

    @@ -113,15 +103,13 @@ async function proxy(request: Request): Promise<Response> {
    "Access-Control-Max-Age": "2073600",
    };

    const headersMap = new Map(
    [..._headers.entries()].map((kv) => [kv[0].toLowerCase(), kv[1]])
    const headers: Record<string, string> = {};
    [..._headers.entries()].forEach(
    (kv) => (headers[kv[0].toLocaleLowerCase()] = kv[1])
    );
    Object.entries(corsHeaders).forEach((kv) =>
    headersMap.set(kv[0].toLowerCase(), kv[1])
    Object.entries(corsHeaders).forEach(
    (kv) => (headers[kv[0].toLocaleLowerCase()] = kv[1])
    );

    const headers: Record<string, string> = {};
    [...headersMap.entries()].forEach((kv) => (headers[kv[0]] = kv[1]));
    return headers;
    }

    @@ -166,9 +154,9 @@ async function proxy(request: Request): Promise<Response> {
    }
    }

    const proxyReq = await getProxyRequest();
    const proxyResp = await getProxyResponse(proxyReq);
    return proxyResp;
    const proxyRequest = await getProxyRequest(request);
    const proxyResponse = await getProxyResponse(proxyRequest);
    return proxyResponse;

    function urlTest(url: string) {
    try {
  4. yingziwu revised this gist Dec 22, 2021. 1 changed file with 143 additions and 72 deletions.
    215 changes: 143 additions & 72 deletions cors-deno.ts
    Original file line number Diff line number Diff line change
    @@ -1,8 +1,8 @@
    import { serve } from "https://deno.land/std/http/server.ts";

    function error(): Promise<Response> {
    function error(code: number): Promise<Response> {
    const response = new Response(null, {
    status: 400,
    status: code,
    });
    return Promise.resolve(response);
    }
    @@ -26,78 +26,84 @@ function options(): Promise<Response> {
    async function proxy(request: Request): Promise<Response> {
    const { pathname } = new URL(request.url);
    const url = pathname.replace(/^\//, "");
    const urlTest = (url: string) => {
    try {
    new URL(url);
    return true;
    } catch (error) {
    return false;
    }
    };
    if (!urlTest(url)) {
    return error();
    return error(400);
    }
    const { host } = new URL(url);

    async function getNewRequest(): Promise<Request> {
    const getRequestBody = async (_req: Request): Promise<BodyInit | null> => {
    if (!_req.body) {
    return null;
    }
    const reader = _req.body.getReader();
    const stream = await reader.read();
    const body = stream.value;
    return body ?? null;
    };
    const _headers = request.headers;
    const headers: Record<string, string> = {};
    [..._headers.entries()].forEach((kv) => {
    const ignoreList = [
    "content-length",
    /^access\-control\-/,
    /^cf\-/,
    "x-forwarded-proto",
    "x-real-ip",
    ];
    const isIgnore = (name: string) => {
    for (const i of ignoreList) {
    if (typeof i === "string") {
    if (i === name.toLocaleLowerCase()) {
    return true;
    }
    }
    if (i instanceof RegExp) {
    if (i.test(name)) {
    return true;
    }
    }
    }
    return false;
    };

    if (kv[0].toLowerCase() === "host") {
    headers["host"] = host;
    } else if (isIgnore(kv[0])) {
    // pass
    } else {
    headers[kv[0]] = kv[1];
    }
    });
    async function getProxyRequest(): Promise<Request> {
    const init = {
    body: await getRequestBody(request),
    body: getRequestBody(request),
    cache: request.cache,
    headers,
    headers: getRequestHeaders(),
    keepalive: request.keepalive,
    method: request.method,
    redirect: "follow" as RequestRedirect,
    };
    const req = new Request(url, init);
    return req;

    function getRequestBody(_req: Request) {
    const body = _req.body;
    return body ?? null;
    }
    function getRequestHeaders() {
    const headers: Record<string, string> = {};
    [...request.headers.entries()].forEach((kv) => {
    if (kv[0].toLowerCase() === "host") {
    return (headers["host"] = host);
    } else if (isIgnore(kv[0])) {
    return;
    } else {
    return (headers[kv[0]] = kv[1]);
    }

    function isIgnore(name: string) {
    const ignoreList = [
    "content-length",
    /^access\-control\-/,
    /^cf\-/,
    "x-forwarded-proto",
    "x-real-ip",
    ];

    for (const i of ignoreList) {
    if (typeof i === "string") {
    if (i === name.toLocaleLowerCase()) {
    return true;
    }
    }
    if (i instanceof RegExp) {
    if (i.test(name)) {
    return true;
    }
    }
    }
    return false;
    }
    });
    return headers;
    }
    }

    async function getNewResponse(req: Request): Promise<Response> {
    async function getProxyResponse(req: Request): Promise<Response> {
    const resp = await fetch(req);
    const getResponseHeaders = (_resp: Response): HeadersInit => {
    const headers = getResponseHeaders(resp);
    const status = resp.status;
    const _body = resp.body;
    let body: ReadableStream<Uint8Array> | null;
    if (_body !== null) {
    body = bodyHandler(_body);
    } else {
    body = _body;
    }
    const response = new Response(body, {
    headers: headers,
    status: status,
    });
    return response;

    function getResponseHeaders(_resp: Response): HeadersInit {
    const _headers = _resp.headers;
    const corsHeaders = {
    "Access-Control-Allow-Origin": "*",
    @@ -117,25 +123,85 @@ async function proxy(request: Request): Promise<Response> {
    const headers: Record<string, string> = {};
    [...headersMap.entries()].forEach((kv) => (headers[kv[0]] = kv[1]));
    return headers;
    };
    const respHeaders = getResponseHeaders(resp);
    const respStatus = resp.status;
    const respBody = await resp.blob();
    const response = new Response(respBody, {
    headers: respHeaders,
    status: respStatus,
    });
    return response;
    }

    function bodyHandler(
    body: ReadableStream<Uint8Array>
    ): ReadableStream<Uint8Array> {
    // See also: https://zhuanlan.zhihu.com/p/98848420
    let aborter: (reason: any) => void;
    let aborted = false;

    const reader = body.getReader();
    const stream = new ReadableStream({
    start(controller) {
    function push() {
    reader
    .read()
    .then(({ value, done }) => {
    if (done) {
    if (!aborted) {
    controller.close();
    }
    return;
    }
    controller.enqueue(value);
    push();
    })
    .catch((error) => console.error(error));
    }
    aborter = (reason) => {
    controller.error(new Error(reason));
    aborted = true;
    };

    push();
    },
    cancel(reason) {
    reader.cancel(reason);
    aborter(reason);
    },
    });
    return stream;
    }
    }

    const proxyReq = await getProxyRequest();
    const proxyResp = await getProxyResponse(proxyReq);
    return proxyResp;

    function urlTest(url: string) {
    try {
    new URL(url);
    return true;
    } catch (error) {
    return false;
    }
    }
    }

    const newReq = await getNewRequest();
    const newResp = await getNewResponse(newReq);
    return newResp;
    function logError(request: Request, error: Error) {
    const logObj = {
    time: new Date().toISOString(),
    type: "error",
    request: {
    url: request.url,
    method: request.method,
    headers: [...request.headers.entries()],
    },
    error: {
    name: error.name,
    message: error.message,
    stack: error.stack,
    },
    };
    console.log(JSON.stringify(logObj));
    }

    function log(request: Request, response: Response) {
    const logObj = {
    time: new Date().toISOString(),
    type: "info",
    request: {
    url: request.url,
    method: request.method,
    @@ -154,7 +220,12 @@ async function handler(request: Request): Promise<Response> {
    if (request.method === "OPTIONS") {
    response = await options();
    } else {
    response = await proxy(request);
    try {
    response = await proxy(request);
    } catch (err) {
    logError(request, err);
    response = await error(500);
    }
    }
    log(request, response);
    return response;
  5. yingziwu revised this gist Dec 22, 2021. 1 changed file with 24 additions and 2 deletions.
    26 changes: 24 additions & 2 deletions cors-deno.ts
    Original file line number Diff line number Diff line change
    @@ -52,10 +52,32 @@ async function proxy(request: Request): Promise<Response> {
    const _headers = request.headers;
    const headers: Record<string, string> = {};
    [..._headers.entries()].forEach((kv) => {
    const ignoreList = ["content-length"];
    const ignoreList = [
    "content-length",
    /^access\-control\-/,
    /^cf\-/,
    "x-forwarded-proto",
    "x-real-ip",
    ];
    const isIgnore = (name: string) => {
    for (const i of ignoreList) {
    if (typeof i === "string") {
    if (i === name.toLocaleLowerCase()) {
    return true;
    }
    }
    if (i instanceof RegExp) {
    if (i.test(name)) {
    return true;
    }
    }
    }
    return false;
    };

    if (kv[0].toLowerCase() === "host") {
    headers["host"] = host;
    } else if (ignoreList.includes(kv[0].toLowerCase())) {
    } else if (isIgnore(kv[0])) {
    // pass
    } else {
    headers[kv[0]] = kv[1];
  6. yingziwu created this gist Dec 22, 2021.
    141 changes: 141 additions & 0 deletions cors-deno.ts
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,141 @@
    import { serve } from "https://deno.land/std/http/server.ts";

    function error(): Promise<Response> {
    const response = new Response(null, {
    status: 400,
    });
    return Promise.resolve(response);
    }

    function options(): Promise<Response> {
    const headers = {
    "Access-Control-Allow-Origin": "*",
    "Access-Control-Allow-Methods": "*",
    "Access-Control-Allow-Headers": "*",
    "Access-Control-Expose-Headers":
    "Date, Etag, Content-Length, Accept-Ranges, Content-Range, Server, Location",
    "Access-Control-Max-Age": "2073600",
    };
    const response = new Response(null, {
    headers,
    status: 204,
    });
    return Promise.resolve(response);
    }

    async function proxy(request: Request): Promise<Response> {
    const { pathname } = new URL(request.url);
    const url = pathname.replace(/^\//, "");
    const urlTest = (url: string) => {
    try {
    new URL(url);
    return true;
    } catch (error) {
    return false;
    }
    };
    if (!urlTest(url)) {
    return error();
    }
    const { host } = new URL(url);

    async function getNewRequest(): Promise<Request> {
    const getRequestBody = async (_req: Request): Promise<BodyInit | null> => {
    if (!_req.body) {
    return null;
    }
    const reader = _req.body.getReader();
    const stream = await reader.read();
    const body = stream.value;
    return body ?? null;
    };
    const _headers = request.headers;
    const headers: Record<string, string> = {};
    [..._headers.entries()].forEach((kv) => {
    const ignoreList = ["content-length"];
    if (kv[0].toLowerCase() === "host") {
    headers["host"] = host;
    } else if (ignoreList.includes(kv[0].toLowerCase())) {
    // pass
    } else {
    headers[kv[0]] = kv[1];
    }
    });
    const init = {
    body: await getRequestBody(request),
    cache: request.cache,
    headers,
    keepalive: request.keepalive,
    method: request.method,
    redirect: "follow" as RequestRedirect,
    };
    const req = new Request(url, init);
    return req;
    }

    async function getNewResponse(req: Request): Promise<Response> {
    const resp = await fetch(req);
    const getResponseHeaders = (_resp: Response): HeadersInit => {
    const _headers = _resp.headers;
    const corsHeaders = {
    "Access-Control-Allow-Origin": "*",
    "Access-Control-Allow-Methods": "*",
    "Access-Control-Allow-Headers": "*",
    "Access-Control-Expose-Headers": [..._headers.keys()].join(", "),
    "Access-Control-Max-Age": "2073600",
    };

    const headersMap = new Map(
    [..._headers.entries()].map((kv) => [kv[0].toLowerCase(), kv[1]])
    );
    Object.entries(corsHeaders).forEach((kv) =>
    headersMap.set(kv[0].toLowerCase(), kv[1])
    );

    const headers: Record<string, string> = {};
    [...headersMap.entries()].forEach((kv) => (headers[kv[0]] = kv[1]));
    return headers;
    };
    const respHeaders = getResponseHeaders(resp);
    const respStatus = resp.status;
    const respBody = await resp.blob();
    const response = new Response(respBody, {
    headers: respHeaders,
    status: respStatus,
    });
    return response;
    }

    const newReq = await getNewRequest();
    const newResp = await getNewResponse(newReq);
    return newResp;
    }

    function log(request: Request, response: Response) {
    const logObj = {
    time: new Date().toISOString(),
    request: {
    url: request.url,
    method: request.method,
    headers: [...request.headers.entries()],
    },
    response: {
    status: response.status,
    headers: [...response.headers.entries()],
    },
    };
    console.log(JSON.stringify(logObj));
    }

    async function handler(request: Request): Promise<Response> {
    let response;
    if (request.method === "OPTIONS") {
    response = await options();
    } else {
    response = await proxy(request);
    }
    log(request, response);
    return response;
    }

    serve(handler);