deno_std icon indicating copy to clipboard operation
deno_std copied to clipboard

[RFE] @std/http/file_server - Allow to modify file contents on-the-fly

Open TriMoon opened this issue 10 months ago • 3 comments

Is your feature request related to a problem? Please describe. It would be nice (i would like tobe able) if we could modify the file contents of certain files served "On-The-Fly", eg, before actual sending of the contents.

Excuse me for still being a total noob wrt deno, TypeScript, et all...

I would like to automatically inject, for example the below, inside the <head> tag of html files that will be served:

  • <!-- https://guybedford.com/es-module-shims-2.0 -->
    <!-- Load ES Module Shims from your CDN of choice -->
    <script async src="https://ga.jspm.io/npm:[email protected]/dist/es-module-shims.js"></script>
    <!-- Enable the TypeScript and CSS Import features (only import maps are polyfilled by default) -->
    <script type="esms-options">
      { "polyfillEnable": ["typescript", "css-modules"] }
    </script>
    <!-- Set dependencies in import map -->
    <script type="importmap">
    {
      "imports": {
        // This is just an example ofcourse, replace or add to your needs...
        "vue": "https://ga.jspm.io/npm:[email protected]/dist/vue.esm-browser.prod.js"
      }
    }
    </script>
    
  • <!DOCTYPE html>
    <html>
    <head>
       <meta charset="UTF-8"/>
       <meta name="viewport" content="width=device-width, initial-scale=1.0">
       <title>blabla</title>
    
       <!-- The above would be inserted here -->
    
       <!-- ES Module Shims will find this and handle the rest -->
       <script src="app.ts" type="module"></script>
    </head>
    ...
    

Describe the solution you'd like Somekind of hook/callback we could add to the function call, that modifies the text-data from the file before that data is send out, depending on file type and name.

  • // myFilter.mts
    interface FilterParams {
      fname: string,
      fext: string,
      fdata: Array<string>; // One string per \n delimited line fe...
    };
    
    export const filter =
      (resp:FilterParams): string[] => {
        if (resp.fname === 'index'
          && resp.fext === "html"
        ) {
          /*
            Do some processing on resp.fdata
            1. Find the 'head' element
            2. Insert some html before the first script tag using Array() functionality.
            3. Optionally add some SRI to respective tags, or some CSP.
          */
        }
        // resp.fdata will now hold the altered or unaltered file data.
        return resp.fdata;
      };
    
  1. Usage inside a module:

    // serve.ts
    import { filter } from "./myFilter.mts";
    import { serveDir } from "@std/http/file-server";
    
    // Something like this, i don't know how to use this yet but you get the idea...
    Deno.serve({
      serveDir(req),
      filter
    });
    
  2. Or from command line:

    $ file-server --filter=./myFilter.mts ./src
    
    

The above would process each request and serve the file(s) that is/are requested

  • unaltered UNLESS it is intercepted and changed by the filter.

Describe alternatives you've considered

No idea yet, as i'm still in the process of reading deno docs and learning TypeScript 🤷‍♀

If what i describe is already possible please show me the way to do it with an example and if possible add it to the docs. 😉

TriMoon avatar Feb 04 '25 21:02 TriMoon

Doesn't sounds like a common needs to a static file server.

I'd recommend you should read the file on your own handler, modify it, and respond:

Deno.serve(async (req) => {
  const path = new URL(req.url).pathname;
  let text: string
  try {
    text = await Deno.readTextFile("." + path);
  } catch {
    return new Response("Not found", { status: 404 });
  }
  if (condition(req)) {
    return new Response(text, { headers: { "content-type": "text/html" } });
  }
  return new Response(addHeader(text), { headers: { "content-type": "text/html" } });
});

kt3k avatar Feb 05 '25 04:02 kt3k

Isn't the purpose of Deno.serve to eliminate the need for everyone to re-invent the same wheel? 🤔 I don't think it's such a "special" case (to add a filter hook), that should be excluded from this ready-to-use wheel. 🤷‍♀

Anyhow thanks for the example code you provided and i will try to make use of it... Although i have no idea how to pass the control back to the default request handler in case it is not my targeted file in your example.

  • Plus If im not mistaken you are adding the contents of that file as a HTTP-Header not as text inside the original contents of the requested file...
    • ohhh aha wait a sec, addHeader in your example is meant to call my custom filter function?

All in all, i'm still lost as a new deno user... I'm getting old i guess 🤷‍♀

TriMoon avatar Feb 11 '25 11:02 TriMoon

Deno.serve and @std/http covers common needs around HTTP protocol, but they don't try to be a ready-to-use web framework.

I guess what you are looking for is web frameworks (such as hono, fresh, lume, etc)

ohhh aha wait a sec, addHeader in your example is meant to call my custom filter function?

Yeah, I meant addHeader modifies the file contents and generates some html.

kt3k avatar Feb 12 '25 02:02 kt3k