serving files

Massimo Melina committed Dec 11, 2021 at 21:01 UTC 6863fc6f2fa9d1e2de752a03125281ef86ea900e
7 files changed +100 -33
frontend/package-lock.json
+10 -3
@@ -5190,9 +5190,14 @@
5190 }
5191 },
5192 "node_modules/caniuse-lite": {
5193 - "version": "1.0.30001191",
5193 + "version": "1.0.30001286",
5194 + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001286.tgz",
5195 + "integrity": "sha512-zaEMRH6xg8ESMi2eQ3R4eZ5qw/hJiVsO/HlLwniIwErij0JDr9P+8V4dtx1l+kLq6j3yy8l8W4fst1lBnat5wQ==",
5196 "dev": true,
5195 - "license": "CC-BY-4.0"
5197 + "funding": {
5198 + "type": "opencollective",
5199 + "url": "https://opencollective.com/browserslist"
5200 + }
5201 },
5202 "node_modules/capture-exit": {
5203 "version": "2.0.0",
@@ -22612,7 +22617,9 @@
22617 }
22618 },
22619 "caniuse-lite": {
22615 - "version": "1.0.30001191",
22620 + "version": "1.0.30001286",
22621 + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001286.tgz",
22622 + "integrity": "sha512-zaEMRH6xg8ESMi2eQ3R4eZ5qw/hJiVsO/HlLwniIwErij0JDr9P+8V4dtx1l+kLq6j3yy8l8W4fst1lBnat5wQ==",
22623 "dev": true
22624 },
22625 "capture-exit": {
frontend/src/api.ts
+1 -1
@@ -1,7 +1,7 @@
1 import { useEffect, useState } from 'react';
2
3 export function apiCall(cmd: string, params?: object) : Promise<any> {
4 - return fetch('/~api/'+cmd, {
4 + return fetch('/~/api/'+cmd, {
5 method: 'POST',
6 headers: { 'content-type': 'application/json' },
7 body: params && JSON.stringify(params),
package-lock.json
+15 -20
@@ -11,15 +11,16 @@
11 "koa-better-http-proxy": "^0.2.9",
12 "koa-bodyparser": "^4.3.0",
13 "koa-mount": "^4.0.0",
14 - "koa-static": "^5.0.0"
14 + "koa-static": "^5.0.0",
15 + "mime-types": "^2.1.34"
16 },
17 "devDependencies": {
18 "@types/koa": "^2.13.4",
19 "@types/koa__router": "^8.0.11",
20 "@types/koa-bodyparser": "^4.3.4",
21 "@types/koa-mount": "^4.0.1",
21 - "@types/koa-router": "^7.4.4",
22 "@types/koa-static": "^4.0.2",
23 + "@types/mime-types": "^2.1.1",
24 "@types/node": "^16.11.12",
25 "nodemon": "^2.0.15",
26 "ts-node": "^10.4.0",
@@ -278,15 +279,6 @@
279 "@types/koa": "*"
280 }
281 },
281 - "node_modules/@types/koa-router": {
282 - "version": "7.4.4",
283 - "resolved": "https://registry.npmjs.org/@types/koa-router/-/koa-router-7.4.4.tgz",
284 - "integrity": "sha512-3dHlZ6CkhgcWeF6wafEUvyyqjWYfKmev3vy1PtOmr0mBc3wpXPU5E8fBBd4YQo5bRpHPfmwC5yDaX7s4jhIN6A==",
285 - "dev": true,
286 - "dependencies": {
287 - "@types/koa": "*"
288 - }
289 - },
282 "node_modules/@types/koa-send": {
283 "version": "4.1.3",
284 "resolved": "https://registry.npmjs.org/@types/koa-send/-/koa-send-4.1.3.tgz",
@@ -312,6 +304,12 @@
304 "integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==",
305 "dev": true
306 },
307 + "node_modules/@types/mime-types": {
308 + "version": "2.1.1",
309 + "resolved": "https://registry.npmjs.org/@types/mime-types/-/mime-types-2.1.1.tgz",
310 + "integrity": "sha512-vXOTGVSLR2jMw440moWTC7H19iUyLtP3Z1YTj7cSsubOICinjMxFeb/V57v9QdyyPGbbWolUFSSmSiRSn94tFw==",
311 + "dev": true
312 + },
313 "node_modules/@types/node": {
314 "version": "16.11.12",
315 "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.12.tgz",
@@ -2744,15 +2742,6 @@
2742 "@types/koa": "*"
2743 }
2744 },
2747 - "@types/koa-router": {
2748 - "version": "7.4.4",
2749 - "resolved": "https://registry.npmjs.org/@types/koa-router/-/koa-router-7.4.4.tgz",
2750 - "integrity": "sha512-3dHlZ6CkhgcWeF6wafEUvyyqjWYfKmev3vy1PtOmr0mBc3wpXPU5E8fBBd4YQo5bRpHPfmwC5yDaX7s4jhIN6A==",
2751 - "dev": true,
2752 - "requires": {
2753 - "@types/koa": "*"
2754 - }
2755 - },
2745 "@types/koa-send": {
2746 "version": "4.1.3",
2747 "resolved": "https://registry.npmjs.org/@types/koa-send/-/koa-send-4.1.3.tgz",
@@ -2778,6 +2767,12 @@
2767 "integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==",
2768 "dev": true
2769 },
2770 + "@types/mime-types": {
2771 + "version": "2.1.1",
2772 + "resolved": "https://registry.npmjs.org/@types/mime-types/-/mime-types-2.1.1.tgz",
2773 + "integrity": "sha512-vXOTGVSLR2jMw440moWTC7H19iUyLtP3Z1YTj7cSsubOICinjMxFeb/V57v9QdyyPGbbWolUFSSmSiRSn94tFw==",
2774 + "dev": true
2775 + },
2776 "@types/node": {
2777 "version": "16.11.12",
2778 "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.12.tgz",
package.json
+3 -1
@@ -10,7 +10,8 @@
10 "koa-better-http-proxy": "^0.2.9",
11 "koa-bodyparser": "^4.3.0",
12 "koa-mount": "^4.0.0",
13 - "koa-static": "^5.0.0"
13 + "koa-static": "^5.0.0",
14 + "mime-types": "^2.1.34"
15 },
16 "devDependencies": {
17 "@types/koa": "^2.13.4",
@@ -18,6 +19,7 @@
19 "@types/koa-bodyparser": "^4.3.4",
20 "@types/koa-mount": "^4.0.1",
21 "@types/koa-static": "^4.0.2",
22 + "@types/mime-types": "^2.1.1",
23 "@types/node": "^16.11.12",
24 "nodemon": "^2.0.15",
25 "ts-node": "^10.4.0",
src/MemoMap.ts new
+12
@@ -0,0 +1,12 @@
1 +export default class MemoMap<T> {
2 + map = new Map()
3 + getOrSet(k: any, maker: () => T) : T {
4 + let ret = this.map.get(k)
5 + if (!ret) {
6 + ret = maker()
7 + this.map.set(k, ret)
8 + }
9 + return ret
10 + }
11 +}
12 +
src/frontend.ts new
+40
@@ -0,0 +1,40 @@
1 +import proxy from 'koa-better-http-proxy'
2 +import Koa from 'koa'
3 +import MemoMap from './MemoMap'
4 +import mime from 'mime-types'
5 +import { readFile } from 'fs'
6 +import { DEV, FRONTEND_URI } from '.'
7 +
8 +const FRONTEND = 'frontend/build/'
9 +
10 +export const serveFrontend = DEV ? serveProxyFrontend() : serveStaticFrontend()
11 +
12 +function serveProxyFrontend() {
13 + return proxy('localhost:3000', {
14 + userResDecorator: (res, data) => replaceFrontEndRes(data.toString('utf8'))
15 + })
16 +}
17 +
18 +function serveStaticFrontend() : Koa.Middleware {
19 + const cache = new MemoMap()
20 + return async (ctx, next) => {
21 + let file = ctx.url
22 + if (file.startsWith('/'))
23 + file = file.slice(1)
24 + ctx.body = await cache.getOrSet(file, () =>
25 + filePromise(FRONTEND + (file || 'index.html')).then(res =>
26 + replaceFrontEndRes(res.toString('utf8')) ))
27 + ctx.type = file ? (mime.lookup(file) || 'application/octet-stream') : 'html'
28 + await next()
29 + }
30 +}
31 +
32 +function filePromise(path: string) : Promise<Buffer> {
33 + return new Promise((resolve, reject) =>
34 + readFile(path, (err,res) =>
35 + err ? reject(err) : resolve(res) ))
36 +}
37 +
38 +function replaceFrontEndRes(body: string) {
39 + return body.replace(/((?:src|href) *= *['"])\/?/g, '$1'+FRONTEND_URI)
40 +}
src/index.ts
+19 -8
@@ -2,14 +2,14 @@ import Koa from 'koa'
2 import serve from 'koa-static'
3 import mount from 'koa-mount'
4 import bodyParser from 'koa-bodyparser'
5 -import Router from '@koa/router'
6 -import proxy from 'koa-better-http-proxy'
5 import apis from './apis'
6 +import { serveFrontend } from './frontend'
7
8 const PORT = 80
10 -const FRONTEND = 'frontend/build'
11 -const DEV = process.env.NODE_ENV === 'development' ? 'DEV' : ''
12 -const API_URI = '/~api/'
9 +export const DEV = process.env.NODE_ENV === 'development' ? 'DEV' : ''
10 +const SPECIAL_URI = '/~/'
11 +export const FRONTEND_URI = SPECIAL_URI + 'front/'
12 +const API_URI = SPECIAL_URI + 'api/'
13
14 if (DEV)
15 console.clear()
@@ -24,9 +24,20 @@ srv.use(preventCrossDir())
24
25 srv.use(mount(API_URI, new Koa().use(bodyParser()).use(api(apis))))
26
27 -const router = new Router();
28 -router.get('/(.*)', DEV ? proxy('localhost:3000', {}) : serve(FRONTEND))
29 -srv.use(router.routes())
27 +const serveFiles = serve('.')
28 +
29 +srv.use(async (ctx, next) => {
30 + if (ctx.method === 'GET') {
31 + if (ctx.url.endsWith('/'))
32 + await serveFrontend(ctx,next)
33 + else if (ctx.url.startsWith(FRONTEND_URI))
34 + await mount(FRONTEND_URI, serveFrontend)(ctx,next)
35 + else
36 + await serveFiles(ctx,next)
37 + }
38 + else
39 + await next()
40 +})
41
42 srv.on('error', err => console.error('server error', err))
43 srv.listen(PORT, ()=> console.log('running on port', PORT, DEV, new Date().toLocaleString()))