Skip to content

Commit 25885da

Browse files
committed
new config
1 parent 14ebc1d commit 25885da

9 files changed

Lines changed: 196 additions & 142 deletions

File tree

caddy/app.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -279,7 +279,7 @@ func (f *FrankenPHPApp) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
279279
}
280280

281281
case "worker":
282-
wc, err := parseWorkerConfig(d)
282+
wc, err := unmarshalWorker(d)
283283
if err != nil {
284284
return err
285285
}

caddy/caddy.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import (
1313
const (
1414
defaultDocumentRoot = "public"
1515
defaultWatchPattern = "./**/*.{env,php,twig,yaml,yml}"
16-
defaultHotReloadPattern = "./**/*.{css,env,gif,htm,html,jpg,jpeg,js,mjs,php,png,svg,twig,webp,xml,yaml,yml}"
1716
)
1817

1918
func init() {

caddy/hotreload-skip.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
11
//go:build nowatcher || nomercure
2+
23
package caddy
34

5+
type hotReloadContext struct {
6+
}
7+
48
func (_ *FrankenPHPModule) configureHotReload(_ *FrankenPHPApp) error {
59
return nil
610
}
11+
12+
func (_ *FrankenPHPModule) unmarshalHotReload(d *caddyfile.Dispenser) error {
13+
return errors.New("Hot reload support disabled")
14+
}

caddy/hotreload.go

Lines changed: 90 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,111 @@
11
//go:build !nowatcher && !nomercure
2+
23
package caddy
34

45
import (
6+
"bytes"
7+
"encoding/gob"
58
"errors"
9+
"fmt"
10+
"hash/fnv"
611
"net/url"
712

13+
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
814
"github.com/dunglas/frankenphp"
915
)
1016

17+
const defaultHotReloadPattern = "./**/*.{css,env,gif,htm,html,jpg,jpeg,js,mjs,php,png,svg,twig,webp,xml,yaml,yml}"
18+
19+
type hotReloadContext struct {
20+
// HotReload specifies files to watch for file changes to trigger hot reloads updates. Supports the glob syntax.
21+
HotReload *hotReloadConfig `json:"hot_reload,omitempty"`
22+
}
23+
24+
type hotReloadConfig struct {
25+
Topic string `json:"topic"`
26+
Watch []string `json:"watch"`
27+
}
28+
1129
func (f *FrankenPHPModule) configureHotReload(app *FrankenPHPApp) error {
12-
if len(f.HotReload) == 0 {
30+
if f.HotReload == nil {
1331
return nil
1432
}
1533

1634
if f.mercureHub == nil {
1735
return errors.New("unable to enable hot reloading: no Mercure hub configured")
1836
}
1937

20-
app.opts = append(app.opts, frankenphp.WithHotReload(f.Name, f.mercureHub, f.HotReload))
21-
f.preparedEnv["FRANKENPHP_HOT_RELOAD\x00"] = "/.well-known/mercure?topic=https%3A%2F%2Ffrankenphp.dev%2Fhot-reload%2F" + url.QueryEscape(f.Name)
38+
if len(f.HotReload.Watch) == 0 {
39+
f.HotReload.Watch = []string{defaultHotReloadPattern}
40+
}
41+
42+
if f.HotReload.Topic == "" {
43+
uid, err := uniqueID(f)
44+
if err != nil {
45+
return err
46+
}
47+
48+
f.HotReload.Topic = "https://frankenphp.dev/hot-reload/" + uid
49+
}
50+
51+
app.opts = append(app.opts, frankenphp.WithHotReload(f.HotReload.Topic, f.mercureHub, f.HotReload.Watch))
52+
f.preparedEnv["FRANKENPHP_HOT_RELOAD\x00"] = "/.well-known/mercure?topic=" + url.QueryEscape(f.HotReload.Topic)
2253

2354
return nil
2455
}
56+
57+
func (f *FrankenPHPModule) unmarshalHotReload(d *caddyfile.Dispenser) error {
58+
patterns := d.RemainingArgs()
59+
if len(patterns) > 0 {
60+
f.HotReload = &hotReloadConfig{
61+
Watch: patterns,
62+
}
63+
}
64+
65+
for d.NextBlock(1) {
66+
switch v := d.Val(); v {
67+
case "topic":
68+
if !d.NextArg() {
69+
return d.ArgErr()
70+
}
71+
72+
if f.HotReload == nil {
73+
f.HotReload = &hotReloadConfig{}
74+
}
75+
76+
f.HotReload.Topic = d.Val()
77+
78+
case "watch":
79+
patterns := d.RemainingArgs()
80+
if len(patterns) == 0 {
81+
return d.ArgErr()
82+
}
83+
84+
if f.HotReload == nil {
85+
f.HotReload = &hotReloadConfig{}
86+
}
87+
88+
f.HotReload.Watch = append(f.HotReload.Watch, patterns...)
89+
90+
default:
91+
return wrongSubDirectiveError("hot_reload", "topic, watch", v)
92+
}
93+
}
94+
95+
return nil
96+
}
97+
98+
func uniqueID(s any) (string, error) {
99+
var b bytes.Buffer
100+
101+
if err := gob.NewEncoder(&b).Encode(s); err != nil {
102+
return "", fmt.Errorf("unable to generate unique name: %w", err)
103+
}
104+
105+
h := fnv.New64a()
106+
if _, err := h.Write(b.Bytes()); err != nil {
107+
return "", fmt.Errorf("unable to generate unique name: %w", err)
108+
}
109+
110+
return fmt.Sprintf("%016x", h.Sum64()), nil
111+
}

caddy/hotreload_test.go

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
//go:build !nowatcher && !nomercure
2+
3+
package caddy_test
4+
5+
import (
6+
"context"
7+
"net/http"
8+
"net/url"
9+
"os"
10+
"path/filepath"
11+
"strings"
12+
"sync"
13+
"testing"
14+
15+
"github.com/caddyserver/caddy/v2/caddytest"
16+
"github.com/stretchr/testify/require"
17+
)
18+
19+
func TestHotReload(t *testing.T) {
20+
const topic = "https://frankenphp.dev/hot-reload/test"
21+
22+
u := "/.well-known/mercure?topic=" + url.QueryEscape(topic)
23+
24+
tmpDir := t.TempDir()
25+
indexFile := filepath.Join(tmpDir, "index.php")
26+
27+
tester := caddytest.NewTester(t)
28+
tester.InitServer(`
29+
{
30+
debug
31+
skip_install_trust
32+
admin localhost:2999
33+
}
34+
35+
http://localhost:`+testPort+` {
36+
mercure {
37+
transport local
38+
subscriber_jwt TestKey
39+
anonymous
40+
}
41+
42+
php_server {
43+
root `+tmpDir+`
44+
hot_reload {
45+
topic `+topic+`
46+
watch `+tmpDir+`/*.php
47+
}
48+
}
49+
`, "caddyfile")
50+
51+
var connected, received sync.WaitGroup
52+
53+
connected.Add(1)
54+
received.Go(func() {
55+
cx, cancel := context.WithCancel(t.Context())
56+
req, _ := http.NewRequest(http.MethodGet, "http://localhost:"+testPort+u, nil)
57+
req = req.WithContext(cx)
58+
resp := tester.AssertResponseCode(req, http.StatusOK)
59+
60+
connected.Done()
61+
62+
var receivedBody strings.Builder
63+
64+
buf := make([]byte, 1024)
65+
for {
66+
_, err := resp.Body.Read(buf)
67+
require.NoError(t, err)
68+
69+
receivedBody.Write(buf)
70+
71+
if strings.Contains(receivedBody.String(), "index.php") {
72+
cancel()
73+
74+
break
75+
}
76+
}
77+
78+
require.NoError(t, resp.Body.Close())
79+
})
80+
81+
connected.Wait()
82+
83+
require.NoError(t, os.WriteFile(indexFile, []byte("<?=$_SERVER['FRANKENPHP_HOT_RELOAD'];"), 0644))
84+
85+
received.Wait()
86+
87+
tester.AssertGetResponse("http://localhost:"+testPort+"/index.php", http.StatusOK, u)
88+
}

caddy/module.go

Lines changed: 4 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,9 @@
11
package caddy
22

33
import (
4-
"bytes"
5-
"encoding/gob"
64
"encoding/json"
75
"errors"
86
"fmt"
9-
"hash/fnv"
107
"log/slog"
118
"net/http"
129
"path/filepath"
@@ -37,9 +34,8 @@ var serverHeader = []string{"FrankenPHP Caddy"}
3734
// }
3835
type FrankenPHPModule struct {
3936
mercureContext
37+
hotReloadContext
4038

41-
// Name for the server. Default: to the worker filename if any, a unique ID otherwise.
42-
Name string `json:"name,omitempty"`
4339
// Root sets the root folder to the site. Default: `root` directive, or the path of the public directory of the embed app it exists.
4440
Root string `json:"root,omitempty"`
4541
// SplitPath sets the substrings for splitting the URI into two parts. The first matching substring will be used to split the "path info" from the path. The first piece is suffixed with the matching substring and will be assumed as the actual resource (CGI script) name. The second piece will be set to PATH_INFO for the CGI script to use. Default: `.php`.
@@ -50,8 +46,6 @@ type FrankenPHPModule struct {
5046
Env map[string]string `json:"env,omitempty"`
5147
// Workers configures the worker scripts to start.
5248
Workers []workerConfig `json:"workers,omitempty"`
53-
// HotReload specifies files to watch for file changes to trigger hot reloads updates. Supports the glob syntax.
54-
HotReload []string `json:"hot_reload,omitempty"`
5549

5650
resolvedDocumentRoot string
5751
preparedEnv frankenphp.PreparedEnv
@@ -85,12 +79,6 @@ func (f *FrankenPHPModule) Provision(ctx caddy.Context) error {
8579

8680
f.assignMercureHub(ctx)
8781

88-
// If there is only one worker, and a name is set for the server,
89-
// use the server name for the worker if none is set.
90-
if len(f.Workers) == 1 && f.Name != "" && f.Workers[0].Name == "" {
91-
f.Workers[0].Name = f.Name
92-
}
93-
9482
loggerOpt := frankenphp.WithRequestLogger(f.logger)
9583
for i, wc := range f.Workers {
9684
// make the file path absolute from the public directory
@@ -114,12 +102,6 @@ func (f *FrankenPHPModule) Provision(ctx caddy.Context) error {
114102
}
115103
f.Workers = workers
116104

117-
// If there is only one worker, and no name is set for the server,
118-
// use the worker name without the "m#" prefix
119-
if len(f.Workers) == 1 && f.Name == "" && f.Workers[0].Name != "" {
120-
f.Name = strings.TrimPrefix(f.Workers[0].Name, "m#")
121-
}
122-
123105
if f.Root == "" {
124106
if frankenphp.EmbeddedAppPath == "" {
125107
f.Root = "{http.vars.root}"
@@ -171,13 +153,6 @@ func (f *FrankenPHPModule) Provision(ctx caddy.Context) error {
171153
}
172154
}
173155

174-
if f.Name == "" {
175-
// Generate a unique name if none is provided
176-
if f.Name, err = uniqueID(f); err != nil {
177-
return err
178-
}
179-
}
180-
181156
if err := f.configureHotReload(fapp); err != nil {
182157
return err
183158
}
@@ -272,12 +247,6 @@ func (f *FrankenPHPModule) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
272247
for d.Next() {
273248
for d.NextBlock(0) {
274249
switch d.Val() {
275-
case "name":
276-
if !d.NextArg() {
277-
return d.ArgErr()
278-
}
279-
280-
f.Name = d.Val()
281250
case "root":
282251
if !d.NextArg() {
283252
return d.ArgErr()
@@ -316,23 +285,18 @@ func (f *FrankenPHPModule) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
316285
f.ResolveRootSymlink = &v
317286

318287
case "worker":
319-
wc, err := parseWorkerConfig(d)
288+
wc, err := unmarshalWorker(d)
320289
if err != nil {
321290
return err
322291
}
323292

324293
f.Workers = append(f.Workers, wc)
325294

326295
case "hot_reload":
327-
patterns := d.RemainingArgs()
328-
if len(patterns) == 0 {
329-
f.HotReload = append(f.HotReload, defaultHotReloadPattern)
330-
331-
continue
296+
if err := f.unmarshalHotReload(d); err != nil {
297+
return err
332298
}
333299

334-
f.HotReload = append(f.HotReload, patterns...)
335-
336300
default:
337301
return wrongSubDirectiveError("php or php_server", "hot_reload, name, root, split, env, resolve_root_symlink, worker", d.Val())
338302
}
@@ -693,21 +657,6 @@ func prependWorkerRoutes(routes caddyhttp.RouteList, h httpcaddyfile.Helper, f F
693657
return routes
694658
}
695659

696-
func uniqueID(s any) (string, error) {
697-
var b bytes.Buffer
698-
699-
if err := gob.NewEncoder(&b).Encode(s); err != nil {
700-
return "", fmt.Errorf("unable to generate unique name: %w", err)
701-
}
702-
703-
h := fnv.New64a()
704-
if _, err := h.Write(b.Bytes()); err != nil {
705-
return "", fmt.Errorf("unable to generate unique name: %w", err)
706-
}
707-
708-
return fmt.Sprintf("%016x", h.Sum64()), nil
709-
}
710-
711660
// Interface guards
712661
var (
713662
_ caddy.Provisioner = (*FrankenPHPModule)(nil)

0 commit comments

Comments
 (0)