Skip to content

[proposal] - Allow CORS Headers for intranet / VPN purposes#94

Open
WebReflection wants to merge 1 commit into
antirez:mainfrom
WebReflection:allow-cors
Open

[proposal] - Allow CORS Headers for intranet / VPN purposes#94
WebReflection wants to merge 1 commit into
antirez:mainfrom
WebReflection:allow-cors

Conversation

@WebReflection
Copy link
Copy Markdown

@WebReflection WebReflection commented May 12, 2026

This MR has been successfully tested in my local network with a DGX Spark around the WiFi and I need this variant to be able to query via my localhost or any other connected device that Spark so that we can all benefit from this project within my house.

Thanks for considering this change/update.

To be discussed

Ideally there should be a --cors flag when starting the server but I'd like to start with this implementation that "just works" ™️ and hear out from others/maintainer if there's anything else I can improve/change but trust me it works already and I am playing around a tiny library that would let me lurk ds4 from anywhere I am in my own apartment, as long as the DGX is up and running.

P.S. thanks a lot for this project, I will inevitably try to bring it to ROCm once I have my machine around but so far with Spark it's working wonderfully!

@WebReflection
Copy link
Copy Markdown
Author

If anyone is interested around "how can I test this" ?

test.js

import Queue from 'https://esm.run/gen-q';

const { parse, stringify } = JSON;
const decoder = new TextDecoder;

const chatOptions = {
  stream: true,
  role: 'user',
};

export default class DS4 {
  #model;
  #url;
  constructor({
    url = 'http://YOUR_MACHINE_IP:8000',
    model = 'deepseek-v4-flash',
    version = 'v1',
  }) {
    this.#model = model;
    this.#queue = new Queue();
    this.#url = new URL(`${url}/${version}`);
  }

  async *chat(content, { stream = true, role = 'user' } = chatOptions) {
    const items = new Queue;

    const { body } = await fetch(`${this.#url}/chat/completions`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: stringify({
        model: this.#model,
        messages: [
          { role, content }
        ],
        stream,
      }),
    });

    const reader = body.getReader();

    new ReadableStream({
      async start(controller) {
        (function next() {
          reader.read().then(({ done, value }) => {
            if (done) {
              items.splice(0);
              controller.close();
              return;
            }
            const text = decoder.decode(value);
            if (text.startsWith('data: {'))
              items.push(...parse(text.slice(6, text.lastIndexOf('}') + 1)).choices);
            next();
          });
        }());
      },
    });

    for await (const item of items) {
      const { finish_reason, delta } = item;
      if (finish_reason !== null) break;
      const { reasoning_content, content } = delta;
      if (content != null) yield content;
    //   else if (reasoning_content != null) console.info(reasoning_content);
    }
  }

}

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>DS4</title>
    <script type="module">
        import DS4 from './test.js';

        const ds4 = new DS4({
          url: 'http://YOUR_MACHINE_IP:8000',
          model: 'deepseek-v4-flash',
          version: 'v1',
        });

        // write the chat content you'd like to read on the body
        for await (const chunk of ds4.chat('List three Redis design principles.'))
          document.body.append(chunk);

        console.log('done');
    </script>
</head>
<body>
    
</body>
</html>

The testing library is a WIP and it will be able to consume all channels and do more but with that, and this patch, you'll see results from a localhost without a sweat 🥳

@WebReflection
Copy link
Copy Markdown
Author

this might be a duplicate of #70 which I've just realized was in already ... my thoughts:

  • that MR is way more permissive but ...
  • one needs to explicit use --host 0.0.0.0 instead of default 127.0.0.1 to make the machine reachable out there
  • accordingly, I am not super sure the --cors flag is even needed ... it might be better, as explicit intent, yet it's impractical if the host is not 0.0.0.0 or bound to something reachable from the intranet, so that we could eventually just merge 70 and call it a day, I wouldn't be upset at all as long as CORS in intranet is possible

thank you!

@calvinrp
Copy link
Copy Markdown

Alternatively, #44

@WebReflection
Copy link
Copy Markdown
Author

@calvinrp answered in here #70 (comment)

@d3y4n
Copy link
Copy Markdown

d3y4n commented May 12, 2026

Why not add a proxy on top for all the shenanigans? IMHO this should be kept as simple as possible.

@WebReflection
Copy link
Copy Markdown
Author

@d3y4n having CORS options backed in is the "as simple as possible" idea indeed ... anything else is not simple anymore.

@d3y4n
Copy link
Copy Markdown

d3y4n commented May 12, 2026

@WebReflection not my call, just saying you're hardcoding values and tomorrow someone might need different ones (even you).
What I suggest is add caddy on top as this is anyways not "real" production server.

api.example.com {

    @preflight method OPTIONS

    handle @preflight {
        header {
            Access-Control-Allow-Origin   "{http.request.header.Origin}"
            Access-Control-Allow-Methods  "GET, POST, PUT, PATCH, DELETE, OPTIONS"
            Access-Control-Allow-Headers  "Authorization, Content-Type, Accept, X-Requested-With, X-CSRF-Token"
            Access-Control-Max-Age        "3600"
            Vary                          "Origin"
        }
        respond "" 204
    }

    header {
        Access-Control-Allow-Origin   "{http.request.header.Origin}"
        Access-Control-Expose-Headers "Content-Length, Content-Range"
        Vary                          "Origin"
    }

    reverse_proxy localhost:8080
}

Cheers

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants