File size: 5,308 Bytes
8ac5a25
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
fd5d7f3
 
 
 
 
 
 
 
 
 
 
 
 
8ac5a25
fd5d7f3
8ac5a25
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
/**
 * Serves the built app and proxies the model, on one origin.
 *
 * Same-origin matters for more than tidiness. The page sets COEP
 * `require-corp` so the in-browser WebGPU path can allocate a
 * SharedArrayBuffer, and a cross-origin call to the model would then need CORS
 * negotiated on top of mixed-content rules. Proxying /ollama on this origin
 * removes both problems: the browser sees one server.
 */
import { createServer } from 'node:http';
import { createReadStream, existsSync, statSync } from 'node:fs';
import { extname, join, normalize, resolve } from 'node:path';
import { request as httpRequest } from 'node:http';

const ROOT = resolve('/app/public');
const PORT = Number(process.env.PORT ?? 7860);
const OLLAMA = { host: '127.0.0.1', port: 11434 };

const TYPES = {
  '.html': 'text/html; charset=utf-8',
  '.js': 'text/javascript; charset=utf-8',
  '.mjs': 'text/javascript; charset=utf-8',
  '.css': 'text/css; charset=utf-8',
  '.json': 'application/json; charset=utf-8',
  '.wasm': 'application/wasm',
  '.bin': 'application/octet-stream',
  '.svg': 'image/svg+xml',
  '.png': 'image/png',
  '.webp': 'image/webp',
  '.woff2': 'font/woff2',
  '.txt': 'text/plain; charset=utf-8',
};

/** Cross-origin isolation, so the WebGPU fallback keeps working. */
const ISOLATION = {
  'cross-origin-embedder-policy': 'require-corp',
  'cross-origin-opener-policy': 'same-origin',
  'cross-origin-resource-policy': 'cross-origin',
};

const server = createServer((req, res) => {
  const url = new URL(req.url, 'http://localhost');
  const path = decodeURIComponent(url.pathname);

  // ── the model ─────────────────────────────────────────────────────────────
  if (path === '/ollama' || path.startsWith('/ollama/')) {
    const upstream = path.slice('/ollama'.length) || '/';

    // Strip the browser's Origin and Referer before forwarding.
    //
    // Ollama enforces its own origin allowlist and answers 403 to anything
    // that is not localhost. Passing the page's Origin through made every
    // request from the deployed Space fail with
    // "Ollama could not load granite4:micro: 403", while curl worked, because
    // curl sends no Origin. THIS server is the client here, not the browser,
    // and a server-side proxy has no business claiming a browser origin.
    const headers = { ...req.headers, host: `${OLLAMA.host}:${OLLAMA.port}` };
    delete headers.origin;
    delete headers.referer;

    const proxied = httpRequest(
      { ...OLLAMA, path: upstream + url.search, method: req.method, headers },
      (up) => {
        res.writeHead(up.statusCode ?? 502, { ...up.headers, ...ISOLATION });
        up.pipe(res);
      },
    );
    proxied.on('error', (e) => {
      // Answer in the shape the adapter expects, so the app can say the model
      // is unavailable rather than hanging on a socket that will never open.
      res.writeHead(503, { 'content-type': 'application/json', ...ISOLATION });
      res.end(JSON.stringify({ error: `model backend unavailable: ${e.message}` }));
    });
    req.pipe(proxied);
    return;
  }

  if (path === '/healthz') {
    res.writeHead(200, { 'content-type': 'text/plain' });
    res.end('ok\n');
    return;
  }

  // ── the app ───────────────────────────────────────────────────────────────
  const candidate = resolve(join(ROOT, normalize(path)));
  if (!candidate.startsWith(ROOT)) {
    res.writeHead(403).end('forbidden');
    return;
  }

  let file = candidate;
  if (!existsSync(file) || statSync(file).isDirectory()) {
    const index = join(file, 'index.html');
    // adapter-static writes one fallback page for every unknown route.
    file = existsSync(index) ? index : join(ROOT, 'index.html');
  }
  if (!existsSync(file)) {
    res.writeHead(404).end('not found');
    return;
  }

  const stat = statSync(file);
  const type = TYPES[extname(file)] ?? 'application/octet-stream';
  const immutable = path.startsWith('/_app/immutable/');

  // Range support: the routing graph is 36 MB and the browser asks for it in
  // pieces.
  const range = req.headers.range;
  if (range) {
    const m = /^bytes=(\d*)-(\d*)$/.exec(range);
    if (m) {
      const start = m[1] ? Number(m[1]) : 0;
      const end = m[2] ? Number(m[2]) : stat.size - 1;
      if (start < stat.size && end < stat.size && start <= end) {
        res.writeHead(206, {
          'content-type': type,
          'content-length': end - start + 1,
          'content-range': `bytes ${start}-${end}/${stat.size}`,
          'accept-ranges': 'bytes',
          ...ISOLATION,
        });
        createReadStream(file, { start, end }).pipe(res);
        return;
      }
    }
  }

  res.writeHead(200, {
    'content-type': type,
    'content-length': stat.size,
    'accept-ranges': 'bytes',
    'cache-control': immutable ? 'public, max-age=31536000, immutable' : 'public, max-age=300',
    ...ISOLATION,
  });
  createReadStream(file).pipe(res);
});

server.listen(PORT, '0.0.0.0', () => {
  console.log(`DesirePath serving ${ROOT} on :${PORT}, model proxied at /ollama`);
});