https support

Massimo Melina committed Jan 18, 2022 at 18:42 UTC 0b1728c0f151edd5e0eb3c3b3059149c29f9e4fc
4 files changed +87 -21
README.md
+7
@@ -12,6 +12,7 @@ This is a full rewrite of [the Delphi version](https://github.com/rejetto/hfs2).
12 You won't find all previous features here (yet), but still we got:
13
14 # Features
15 +- https
16 - unicode
17 - virtual file system
18 - mobile friendly front-end
@@ -45,6 +46,12 @@ Supported entries are:
46 You can use the special value `auto` to attempt automatic detection.
47 - `max_kbps` throttle output speed. Default is Infinity.
48 - `max_kbps_per_ip` throttle output speed on a per-ip basis. Default is Infinity.
49 +- `zip-calculate-size-for-seconds` how long should we wait before the zip archive starts streaming, trying to understand its finale size. Default is 1.
50 +- `open_browser_at_start` should HFS open browser on localhost on start? Default is true.
51 +- `https_port` listen on a specific port. Default is 443.
52 +- `cert` use this file for https certificate. Minimum to start https is to give a cert and a private_key. Default is none.
53 +- `private_key` use this file for https private key. Default is none.
54 +- `plugins_config`
55
56 ## Virtual File System (VFS)
57
config.yaml
+3
@@ -5,6 +5,9 @@
5 #error_log:
6 #zip-calculate-size-for-seconds: 1
7 #open_browser_at_start: false
8 +#https_port: 443
9 +#cert: filepath
10 +#private_key: filepath
11 mime:
12 "*.jpg|*.png|*.mp3|*.txt": auto
13 disable_plugins: [ 'theme-example', 'middleware-example', 'download-counter' ]
src/listen.ts
+76 -19
@@ -1,37 +1,94 @@
1 -import { Server } from 'http'
1 +import * as http from 'http'
2 import { getConfig, subscribeConfig } from './config'
3 import open from 'open'
4 import { app } from './index'
5 +import * as https from 'https'
6 +import { watchLoad } from './watchLoad'
7
6 -let srv: Server
8 let firstTime = true
9 +let httpSrv: http.Server
10 +let httpsSrv: http.Server
11 +let cert:string, key: string
12 +
13 subscribeConfig({ k:'port', defaultValue: 80 }, async (port: number) => {
9 - await new Promise(resolve => {
10 - if (!srv)
11 - return resolve(null)
12 - srv.close(err => {
13 - if (err && (err as any).code !== 'ERR_SERVER_NOT_RUNNING')
14 - console.debug('failed to stop server', String(err))
15 - resolve(err)
16 - })
14 + await stopServer(httpSrv)
15 + httpSrv = http.createServer(app.callback());
16 + await startServer(httpSrv, port ?? 80)
17 +})
18 +
19 +
20 +subscribeConfig({ k:'cert' }, async (v: string) => {
21 + await stopServer(httpsSrv)
22 + cert = v
23 + if (!cert) return
24 + if (cert.includes('\n'))
25 + return considerHttps()
26 + // it's a path
27 + watchLoad(cert, data => {
28 + cert = data
29 + considerHttps()
30 + })
31 + cert = ''
32 +})
33 +
34 +subscribeConfig({ k:'private_key' }, async (v: string) => {
35 + await stopServer(httpsSrv)
36 + key = v
37 + if (!key) return
38 + if (key.includes('\n'))
39 + return considerHttps()
40 + // it's a path
41 + watchLoad(key, data => {
42 + key = data
43 + considerHttps()
44 })
18 - await new Promise(resolve => {
45 + key = ''
46 +})
47 +
48 +const CFG_HTTPS_PORT = 'https_port'
49 +subscribeConfig({ k:CFG_HTTPS_PORT, defaultValue: 443 }, considerHttps)
50 +
51 +async function considerHttps() {
52 + if (!cert || !key)
53 + return stopServer(httpsSrv)
54 + httpsSrv = https.createServer({ key, cert }, app.callback());
55 + return await startServer(httpsSrv, getConfig('https_port'), 's')
56 +}
57 +
58 +function startServer(srv: http.Server, port: number, secure:string='') {
59 + return new Promise((resolve, reject) => {
60 try {
20 - srv = app.listen(port, () => {
21 - console.log('running on port', port)
22 - if (firstTime && getConfig('open_browser_at_start') !== false)
23 - open('http://localhost:'+port)
24 - firstTime = false
61 + if (port < 0)
62 + return resolve(null)
63 + srv.listen(port, () => {
64 + const proto = 'http' + secure
65 + console.log(proto + ` serving on port`, port)
66 + if (firstTime && getConfig('open_browser_at_start') !== false) {
67 + open(proto + '://localhost:' + port).then()
68 + firstTime = false
69 + }
70 resolve(null)
71 }).on('error', e => {
72 const { code } = e as any
28 - if (code === 'EADDRINUSE')
29 - console.error(`couldn't listen on busy port ${port}`)
73 + console.error(code === 'EADDRINUSE' ? `couldn't listen on busy port ${port}` : e)
74 + reject(e)
75 })
76 }
77 catch(e) {
78 console.error("couldn't listen on port", port, String(e))
79 + reject(e)
80 }
81 + })
82 +}
83
84 +function stopServer(srv: http.Server) {
85 + return new Promise(resolve => {
86 + if (!srv?.listening)
87 + return resolve(null)
88 + srv.close(err => {
89 + if (err && (err as any).code !== 'ERR_SERVER_NOT_RUNNING')
90 + console.debug('failed to stop server', String(err))
91 + resolve(err)
92 + })
93 })
37 -})
94 +}
todo.md
+1 -2
@@ -6,10 +6,9 @@
6 - update tests to SRP login
7 - upload
8 - upload unzipping (while streaming?)
9 -- https
9 +- plugin to automatic generate letsencrypt cert?
10 - delete
11 - updater (stop,unzip,start)
12 -- search and login dialogs should push to history so that mobile can use back button to close them
12 - node.comment
13 - config: max connections (total/per-ip)
14 - config: bans