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()))