Skip to content

Commit e871fdf

Browse files
OlegChuev-MDmarjune163
authored andcommitted
feat(archive): add concurrency control for archive operations
Introduce a new `archiveWorkers` channel to limit the number of concurrent archive operations. When the limit is reached, new requests will receive a 429 Too Many Requests response. This prevents resource exhaustion and improves system stability. The maximum number of workers can be configured via the `--max-archive-workers` CLI option.
1 parent 4e9e758 commit e871fdf

File tree

4 files changed

+64
-33
lines changed

4 files changed

+64
-33
lines changed

README.md

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ Will generate executable file "main" in current directory.
2828
Start server on port 8080, root directory is current working directory:
2929
```sh
3030
ghfs -l 8080
31-
```
31+
```
3232

3333
Start server on port 8080, root directory is /usr/share/doc:
3434
```sh
@@ -258,7 +258,10 @@ ghfs [options]
258258
-A|--global-archive
259259
Allow user to download the whole contents of current directory for all url paths.
260260
A download link will appear on top part of the page.
261-
Make sure there is no circular symbol links.
261+
--max-archive-workers <number>
262+
Maximum number of concurrent archive operations.
263+
Set to -1 for unlimited (default).
264+
When the limit is reached, new archive requests will receive 429 Too Many Requests.
262265
--archive <url-path> ...
263266
--archive-user <separator><url-path>[<separator><allowed-username>...] ...
264267
Allow user to download the whole contents of current directory for specific url paths(and sub paths).
@@ -304,7 +307,7 @@ ghfs [options]
304307
-S|--show <wildcard> ...
305308
-SD|--show-dir <wildcard> ...
306309
-SF|--show-file <wildcard> ...
307-
If specified, files or directories match wildcards(except hidden by hide option) will be shown.
310+
If specified, files or directories match wildcards(except hidden by hide option) will be shown.
308311
309312
-H|--hide <wildcard> ...
310313
-HD|--hide-dir <wildcard> ...

src/param/cli.go

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,13 @@ package param
22

33
import (
44
"errors"
5-
"mjpclab.dev/ghfs/src/goNixArgParser"
6-
"mjpclab.dev/ghfs/src/goVirtualHost"
7-
"mjpclab.dev/ghfs/src/serverError"
85
"net/http"
96
"os"
107
"strings"
8+
9+
"mjpclab.dev/ghfs/src/goNixArgParser"
10+
"mjpclab.dev/ghfs/src/goVirtualHost"
11+
"mjpclab.dev/ghfs/src/serverError"
1112
)
1213

1314
var cliCmd = NewCliCmd()
@@ -147,6 +148,9 @@ func NewCliCmd() *goNixArgParser.Command {
147148
err = options.AddFlagValues("archivedirsusers", "--archive-dir-user", "", nil, "file system path that allow archive files for specific users, <sep><fs-path>[<sep><user>...]")
148149
serverError.CheckFatal(err)
149150

151+
err = options.AddFlagValue("maxarchiveworkers", "--max-archive-workers", "", "-1", "maximum number of concurrent archive operations (-1 for unlimited)")
152+
serverError.CheckFatal(err)
153+
150154
err = options.AddFlag("globalcors", "--global-cors", "GHFS_GLOBAL_CORS", "enable CORS headers for all directories")
151155
serverError.CheckFatal(err)
152156

@@ -436,6 +440,8 @@ func CmdResultsToParams(results []*goNixArgParser.ParseResult) (params Params, e
436440
archiveDirsUsers, _ := result.GetStrings("archivedirsusers")
437441
param.ArchiveDirsUsers = SplitAllKeyValues(archiveDirsUsers)
438442

443+
param.ArchiveMaxWorkers, _ = result.GetInt("maxarchiveworkers")
444+
439445
// global restrict access
440446
if result.HasKey("globalrestrictaccess") {
441447
param.GlobalRestrictAccess, _ = result.GetStrings("globalrestrictaccess")
@@ -464,7 +470,7 @@ func CmdResultsToParams(results []*goNixArgParser.ParseResult) (params Params, e
464470
// certificate
465471
certFiles, _ := result.GetStrings("certs")
466472
keyFiles, _ := result.GetStrings("keys")
467-
param.CertKeyPaths, es = goVirtualHost.CertsKeysToPairs(certFiles, keyFiles)
473+
param.CertKeyPaths, _ = goVirtualHost.CertsKeysToPairs(certFiles, keyFiles)
468474

469475
// listen
470476
listens, _ := result.GetStrings("listens")

src/param/main.go

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
package param
22

33
import (
4+
"os"
5+
"path/filepath"
6+
47
"mjpclab.dev/ghfs/src/middleware"
58
"mjpclab.dev/ghfs/src/serverError"
69
"mjpclab.dev/ghfs/src/util"
7-
"os"
8-
"path/filepath"
910
)
1011

1112
type Param struct {
@@ -56,11 +57,13 @@ type Param struct {
5657
DeleteDirs []string
5758
DeleteDirsUsers [][]string // [][path, user...]
5859

59-
GlobalArchive bool
60-
ArchiveUrls []string
61-
ArchiveUrlsUsers [][]string // [][path, user...]
62-
ArchiveDirs []string
63-
ArchiveDirsUsers [][]string // [][path, user...]
60+
GlobalArchive bool
61+
ArchiveUrls []string
62+
ArchiveUrlsUsers [][]string // [][path, user...]
63+
ArchiveDirs []string
64+
ArchiveDirsUsers [][]string // [][path, user...]
65+
ArchiveMaxWorkers int
66+
ArchivationsSem chan struct{}
6467

6568
GlobalCors bool
6669
CorsUrls []string
@@ -163,6 +166,10 @@ func (param *Param) Normalize() (errs []error) {
163166
param.DeleteDirs = NormalizeFsPaths(param.DeleteDirs)
164167
param.ArchiveUrls = NormalizeUrlPaths(param.ArchiveUrls)
165168
param.ArchiveDirs = NormalizeFsPaths(param.ArchiveDirs)
169+
if param.ArchiveMaxWorkers > 0 {
170+
param.ArchivationsSem = make(chan struct{}, param.ArchiveMaxWorkers)
171+
}
172+
166173
param.CorsUrls = NormalizeUrlPaths(param.CorsUrls)
167174
param.CorsDirs = NormalizeFsPaths(param.CorsDirs)
168175

src/serverHandler/aliasHandler.go

Lines changed: 34 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
11
package serverHandler
22

33
import (
4+
"net/http"
5+
"regexp"
6+
"strconv"
7+
"strings"
8+
49
"mjpclab.dev/ghfs/src/middleware"
510
"mjpclab.dev/ghfs/src/param"
611
"mjpclab.dev/ghfs/src/serverLog"
712
"mjpclab.dev/ghfs/src/tpl/theme"
813
"mjpclab.dev/ghfs/src/user"
914
"mjpclab.dev/ghfs/src/util"
10-
"net/http"
11-
"regexp"
12-
"strconv"
13-
"strings"
1415
)
1516

1617
var defaultHandler = http.NotFoundHandler()
@@ -48,6 +49,8 @@ type aliasHandler struct {
4849
archive *hierarchyAvailability
4950
cors *hierarchyAvailability
5051

52+
archiveWorkers chan struct{}
53+
5154
globalRestrictAccess []string
5255
restrictAccessUrls pathStringsList
5356
restrictAccessDirs pathStringsList
@@ -120,21 +123,8 @@ func (h *aliasHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
120123

121124
if session.isMutate && h.mutate(w, r, session, data) {
122125
return
123-
} else if session.isArchive {
124-
switch session.archiveFormat {
125-
case tarFmt:
126-
if h.tar(w, r, session, data) {
127-
return
128-
}
129-
case tgzFmt:
130-
if h.tgz(w, r, session, data) {
131-
return
132-
}
133-
case zipFmt:
134-
if h.zip(w, r, session, data) {
135-
return
136-
}
137-
}
126+
} else if session.isArchive && h.createArchive(w, r, session, data) {
127+
return
138128
}
139129
}
140130

@@ -152,6 +142,29 @@ func (h *aliasHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
152142
}
153143
}
154144

145+
func (h *aliasHandler) createArchive(w http.ResponseWriter, r *http.Request, session *sessionContext, data *responseData) bool {
146+
if h.archiveWorkers != nil {
147+
select {
148+
case h.archiveWorkers <- struct{}{}:
149+
defer func() { <-h.archiveWorkers }()
150+
default:
151+
data.Status = http.StatusTooManyRequests
152+
return false
153+
}
154+
}
155+
156+
switch session.archiveFormat {
157+
case tarFmt:
158+
return h.tar(w, r, session, data)
159+
case tgzFmt:
160+
return h.tgz(w, r, session, data)
161+
case zipFmt:
162+
return h.zip(w, r, session, data)
163+
}
164+
165+
return false
166+
}
167+
155168
func newAliasHandler(
156169
p *param.Param,
157170
vhostCtx *vhostContext,
@@ -179,6 +192,8 @@ func newAliasHandler(
179192
toHttpsPort: p.ToHttpsPort,
180193
defaultSort: p.DefaultSort,
181194

195+
archiveWorkers: p.ArchivationsSem,
196+
182197
users: vhostCtx.users,
183198
theme: vhostCtx.theme,
184199
logger: vhostCtx.logger,

0 commit comments

Comments
 (0)