Skip to content

Commit 7ee1e42

Browse files
authored
Merge pull request #143 from gofiber/codex/2025-07-20-12-26-44
2 parents 64374d6 + d9bca8e commit 7ee1e42

File tree

11 files changed

+425
-7
lines changed

11 files changed

+425
-7
lines changed

.github/dependabot.yml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,10 @@ version: 2
44
updates:
55
- package-ecosystem: "gomod"
66
directory: "/" # Location of package manifests
7-
default_labels:
7+
labels:
88
- "🤖 Dependencies"
99
schedule:
1010
interval: "daily"
11-
automerged_updates:
12-
- match:
13-
dependency_name: "gofiber/fiber/*"
1411
groups:
1512
charmbracelet:
1613
patterns:
@@ -29,9 +26,12 @@ updates:
2926
- "github.com/jarcoal/httpmock"
3027
- "github.com/stretchr/testify"
3128
- "gopkg.in/check.v1"
29+
gofiber:
30+
patterns:
31+
- "github.com/gofiber/*"
3232
- package-ecosystem: "github-actions"
3333
directory: "/" # Location of package manifests
34-
default_labels:
34+
labels:
3535
- "🤖 Dependencies"
3636
schedule:
3737
interval: "daily"

README.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,41 @@ fiber dev [flags]
6161
-t, --target string target path for go build (default ".")
6262
```
6363

64+
## fiber serve
65+
66+
### Synopsis
67+
68+
Serve static files
69+
70+
See the [File server guide](docs/guide/fileserver.md) for more details.
71+
72+
```bash
73+
fiber serve [flags]
74+
```
75+
76+
### Options
77+
78+
```text
79+
--addr string address to listen on (default ":3000")
80+
--browse enable directory browsing
81+
--cache duration cache duration (default 10s)
82+
--cert string TLS certificate file
83+
--compress enable compression
84+
--cors enable CORS middleware
85+
--dir string directory to serve (default ".")
86+
--download force file downloads
87+
--health enable health check endpoints (default true)
88+
--index string comma-separated list of index files (default "index.html")
89+
--key string TLS private key file
90+
--logger enable logger middleware (default true)
91+
--maxage int Cache-Control max-age header in seconds
92+
--path string request path to serve (default "/")
93+
--prefork enable prefork mode
94+
--quiet disable startup message
95+
--range enable byte range requests
96+
-h, --help help for serve
97+
```
98+
6499
## fiber new
65100

66101
### Synopsis

cmd/fileserver/app/app.go

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
package fileserver
2+
3+
import (
4+
"strings"
5+
"time"
6+
7+
"github.com/gofiber/fiber/v3"
8+
"github.com/gofiber/fiber/v3/middleware/cors"
9+
"github.com/gofiber/fiber/v3/middleware/healthcheck"
10+
"github.com/gofiber/fiber/v3/middleware/logger"
11+
"github.com/gofiber/fiber/v3/middleware/recover"
12+
"github.com/gofiber/fiber/v3/middleware/static"
13+
)
14+
15+
// Options holds the settings for the file server.
16+
type Options struct {
17+
Dir string
18+
Path string
19+
Index string
20+
Cache time.Duration
21+
MaxAge int
22+
Logger bool
23+
Cors bool
24+
Health bool
25+
Browse bool
26+
Download bool
27+
Compress bool
28+
ByteRange bool
29+
}
30+
31+
// NewApp creates a Fiber application using the provided options.
32+
func NewApp(o Options) *fiber.App {
33+
app := fiber.New()
34+
35+
// Recover should be registered first to handle panics from later middleware.
36+
app.Use(recover.New())
37+
38+
if o.Logger {
39+
app.Use(logger.New())
40+
}
41+
if o.Cors {
42+
app.Use(cors.New())
43+
}
44+
45+
if o.Health {
46+
app.Get(healthcheck.DefaultLivenessEndpoint, healthcheck.NewHealthChecker())
47+
app.Get(healthcheck.DefaultReadinessEndpoint, healthcheck.NewHealthChecker())
48+
app.Get(healthcheck.DefaultStartupEndpoint, healthcheck.NewHealthChecker())
49+
}
50+
51+
cfgStatic := static.Config{
52+
Browse: o.Browse,
53+
Download: o.Download,
54+
Compress: o.Compress,
55+
ByteRange: o.ByteRange,
56+
CacheDuration: o.Cache,
57+
MaxAge: o.MaxAge,
58+
}
59+
if o.Index != "" {
60+
cfgStatic.IndexNames = strings.Split(o.Index, ",")
61+
}
62+
app.Use(o.Path, static.New(o.Dir, cfgStatic))
63+
64+
return app
65+
}

cmd/fileserver/app/app_test.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package fileserver
2+
3+
import (
4+
"io"
5+
"net/http/httptest"
6+
"os"
7+
"path/filepath"
8+
"runtime"
9+
"testing"
10+
"time"
11+
12+
"github.com/gofiber/fiber/v3"
13+
"github.com/gofiber/fiber/v3/middleware/healthcheck"
14+
"github.com/stretchr/testify/require"
15+
)
16+
17+
func TestNewAppHealthEndpoints(t *testing.T) {
18+
t.Parallel()
19+
opts := Options{Dir: t.TempDir(), Path: "/", Health: true}
20+
app := NewApp(opts)
21+
22+
resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, healthcheck.DefaultLivenessEndpoint, nil))
23+
require.NoError(t, err)
24+
t.Cleanup(func() { require.NoError(t, resp.Body.Close()) })
25+
require.Equal(t, fiber.StatusOK, resp.StatusCode)
26+
}
27+
28+
func TestNewAppServeIndex(t *testing.T) {
29+
t.Parallel()
30+
31+
if runtime.GOOS == "windows" {
32+
t.Skip("skipping on windows")
33+
}
34+
35+
dir := t.TempDir()
36+
err := os.WriteFile(filepath.Join(dir, "index.html"), []byte("hello"), 0o600)
37+
require.NoError(t, err)
38+
39+
opts := Options{Dir: dir, Path: "/", Index: "index.html", Cache: time.Second}
40+
app := NewApp(opts)
41+
42+
resp, err := app.Test(httptest.NewRequest(fiber.MethodGet, "/", nil))
43+
require.NoError(t, err)
44+
t.Cleanup(func() { require.NoError(t, resp.Body.Close()) })
45+
require.Equal(t, fiber.StatusOK, resp.StatusCode)
46+
47+
body, err := io.ReadAll(resp.Body)
48+
require.NoError(t, err)
49+
require.Contains(t, string(body), "hello")
50+
}

cmd/fileserver/main.go

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package main
2+
3+
import (
4+
"time"
5+
6+
fileserver "github.com/gofiber/cli/cmd/fileserver/app"
7+
"github.com/gofiber/fiber/v3"
8+
fiberlog "github.com/gofiber/fiber/v3/log"
9+
"github.com/spf13/pflag"
10+
)
11+
12+
func main() {
13+
dir := pflag.String("dir", ".", "directory to serve")
14+
addr := pflag.String("addr", ":3000", "address to listen on")
15+
path := pflag.String("path", "/", "request path to serve")
16+
enableLogger := pflag.Bool("logger", true, "enable logger middleware")
17+
enableCors := pflag.Bool("cors", false, "enable CORS middleware")
18+
enableHealth := pflag.Bool("health", true, "enable health check endpoints")
19+
cert := pflag.String("cert", "", "TLS certificate file")
20+
key := pflag.String("key", "", "TLS private key file")
21+
browse := pflag.Bool("browse", false, "enable directory browsing")
22+
download := pflag.Bool("download", false, "force file downloads")
23+
compress := pflag.Bool("compress", false, "enable compression")
24+
cache := pflag.Duration("cache", 10*time.Second, "cache duration")
25+
maxAge := pflag.Int("maxage", 0, "Cache-Control max-age header in seconds")
26+
index := pflag.String("index", "index.html", "comma-separated list of index files")
27+
byteRange := pflag.Bool("range", false, "enable byte range requests")
28+
prefork := pflag.Bool("prefork", false, "enable prefork mode")
29+
disableStartup := pflag.Bool("quiet", false, "disable startup message")
30+
pflag.Parse()
31+
32+
app := fileserver.NewApp(fileserver.Options{
33+
Dir: *dir,
34+
Path: *path,
35+
Logger: *enableLogger,
36+
Cors: *enableCors,
37+
Health: *enableHealth,
38+
Browse: *browse,
39+
Download: *download,
40+
Compress: *compress,
41+
Cache: *cache,
42+
MaxAge: *maxAge,
43+
Index: *index,
44+
ByteRange: *byteRange,
45+
})
46+
47+
cfg := fiber.ListenConfig{EnablePrefork: *prefork, DisableStartupMessage: *disableStartup}
48+
if *cert != "" && *key != "" {
49+
cfg.CertFile = *cert
50+
cfg.CertKeyFile = *key
51+
}
52+
53+
if err := app.Listen(*addr, cfg); err != nil {
54+
fiberlog.Fatalf("failed to start server: %v", err)
55+
}
56+
}

cmd/root.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ func init() {
7272
rootCmd.Long = getLongDescription()
7373

7474
rootCmd.AddCommand(
75-
versionCmd, newCmd, devCmd, upgradeCmd, migrateCmd,
75+
versionCmd, newCmd, devCmd, serveCmd, upgradeCmd, migrateCmd,
7676
)
7777
}
7878

cmd/serve.go

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
package cmd
2+
3+
import (
4+
"fmt"
5+
"time"
6+
7+
fileserver "github.com/gofiber/cli/cmd/fileserver/app"
8+
"github.com/gofiber/fiber/v3"
9+
"github.com/spf13/cobra"
10+
)
11+
12+
var (
13+
serveDir string
14+
serveAddr string
15+
servePath string
16+
serveLogger bool
17+
serveCors bool
18+
serveHealth bool
19+
serveCert string
20+
serveKey string
21+
serveBrowse bool
22+
serveDownload bool
23+
serveCompress bool
24+
serveCache time.Duration
25+
serveMaxAge int
26+
serveIndex string
27+
serveByteRange bool
28+
servePrefork bool
29+
serveQuiet bool
30+
)
31+
32+
func init() {
33+
serveCmd.Flags().StringVar(&serveDir, "dir", ".", "directory to serve")
34+
serveCmd.Flags().StringVar(&serveAddr, "addr", ":3000", "address to listen on")
35+
serveCmd.Flags().StringVar(&servePath, "path", "/", "request path to serve")
36+
serveCmd.Flags().BoolVar(&serveLogger, "logger", true, "enable logger middleware")
37+
serveCmd.Flags().BoolVar(&serveCors, "cors", false, "enable CORS middleware")
38+
serveCmd.Flags().BoolVar(&serveHealth, "health", true, "enable health check endpoints")
39+
serveCmd.Flags().StringVar(&serveCert, "cert", "", "TLS certificate file")
40+
serveCmd.Flags().StringVar(&serveKey, "key", "", "TLS private key file")
41+
serveCmd.Flags().BoolVar(&serveBrowse, "browse", false, "enable directory browsing")
42+
serveCmd.Flags().BoolVar(&serveDownload, "download", false, "force file downloads")
43+
serveCmd.Flags().BoolVar(&serveCompress, "compress", false, "enable compression")
44+
serveCmd.Flags().DurationVar(&serveCache, "cache", 10*time.Second, "cache duration")
45+
serveCmd.Flags().IntVar(&serveMaxAge, "maxage", 0, "Cache-Control max-age header in seconds")
46+
serveCmd.Flags().StringVar(&serveIndex, "index", "index.html", "comma-separated list of index files")
47+
serveCmd.Flags().BoolVar(&serveByteRange, "range", false, "enable byte range requests")
48+
serveCmd.Flags().BoolVar(&servePrefork, "prefork", false, "enable prefork mode")
49+
serveCmd.Flags().BoolVar(&serveQuiet, "quiet", false, "disable startup message")
50+
51+
rootCmd.AddCommand(serveCmd)
52+
}
53+
54+
var serveCmd = &cobra.Command{
55+
Use: "serve",
56+
Short: "Serve static files",
57+
RunE: serveRunE,
58+
}
59+
60+
var listen = func(app *fiber.App, addr string, cfg fiber.ListenConfig) error {
61+
return app.Listen(addr, cfg)
62+
}
63+
64+
func serveRunE(_ *cobra.Command, _ []string) error {
65+
app := fileserver.NewApp(fileserver.Options{
66+
Dir: serveDir,
67+
Path: servePath,
68+
Logger: serveLogger,
69+
Cors: serveCors,
70+
Health: serveHealth,
71+
Browse: serveBrowse,
72+
Download: serveDownload,
73+
Compress: serveCompress,
74+
Cache: serveCache,
75+
MaxAge: serveMaxAge,
76+
Index: serveIndex,
77+
ByteRange: serveByteRange,
78+
})
79+
80+
cfg := fiber.ListenConfig{EnablePrefork: servePrefork, DisableStartupMessage: serveQuiet}
81+
if serveCert != "" && serveKey != "" {
82+
cfg.CertFile = serveCert
83+
cfg.CertKeyFile = serveKey
84+
}
85+
86+
if err := listen(app, serveAddr, cfg); err != nil {
87+
return fmt.Errorf("failed to start server: %w", err)
88+
}
89+
return nil
90+
}

cmd/serve_test.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package cmd
2+
3+
import (
4+
"errors"
5+
"testing"
6+
7+
"github.com/gofiber/fiber/v3"
8+
"github.com/stretchr/testify/require"
9+
)
10+
11+
func Test_ServeRunE(t *testing.T) {
12+
old := listen
13+
listen = func(_ *fiber.App, _ string, _ fiber.ListenConfig) error { return nil }
14+
defer func() { listen = old }()
15+
16+
_, err := runCobraCmd(serveCmd, "--dir=.")
17+
require.NoError(t, err)
18+
}
19+
20+
func Test_ServeRunE_Error(t *testing.T) {
21+
old := listen
22+
listen = func(_ *fiber.App, _ string, _ fiber.ListenConfig) error { return errors.New("fail") }
23+
defer func() { listen = old }()
24+
25+
_, err := runCobraCmd(serveCmd, "--dir=.")
26+
require.Error(t, err)
27+
}

0 commit comments

Comments
 (0)