mirror of
https://github.com/dunglas/frankenphp.git
synced 2025-12-24 13:38:11 +08:00
Compare commits
592 Commits
v1.0.0-bet
...
feat/frank
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
79e3a98a11 | ||
|
|
afc4463ab3 | ||
|
|
6749ddbde5 | ||
|
|
82ba882a4e | ||
|
|
4b1679e70f | ||
|
|
75ce2e22c2 | ||
|
|
5a43e9f4de | ||
|
|
5542044376 | ||
|
|
52b65311c2 | ||
|
|
2dc8048ad2 | ||
|
|
a59b649dac | ||
|
|
68a4548bf4 | ||
|
|
6f049f9a9c | ||
|
|
340b1fd1c2 | ||
|
|
c9329bd717 | ||
|
|
f54a1fa85e | ||
|
|
b4115ca9a2 | ||
|
|
14469d4a0a | ||
|
|
ee394756b1 | ||
|
|
5a260c430a | ||
|
|
b6fcab5a95 | ||
|
|
1e49586b0e | ||
|
|
b27cd1c986 | ||
|
|
c6483088c5 | ||
|
|
5a9785d0d9 | ||
|
|
c522b52804 | ||
|
|
9a8ad979e0 | ||
|
|
663aff7cc4 | ||
|
|
79f2b2347b | ||
|
|
bf5c98410b | ||
|
|
cf7541fde6 | ||
|
|
25491068df | ||
|
|
d72751b9fd | ||
|
|
8820e53819 | ||
|
|
d2b6f9e723 | ||
|
|
13fbe126ea | ||
|
|
afa7dafe1c | ||
|
|
39e22bd5e0 | ||
|
|
bbbfdb31b5 | ||
|
|
0b83602575 | ||
|
|
ecca9dc01d | ||
|
|
eb40c03a21 | ||
|
|
c2390e7c3b | ||
|
|
0d12a5162d | ||
|
|
a48db9422d | ||
|
|
ab0fcd80de | ||
|
|
2f7b987198 | ||
|
|
1d74b2caa8 | ||
|
|
92e92335e3 | ||
|
|
8f5f9e4c8b | ||
|
|
5b7fc5ec52 | ||
|
|
05220de0e3 | ||
|
|
3741782330 | ||
|
|
a6e1d3554d | ||
|
|
6f1b4f3bae | ||
|
|
cd540bda11 | ||
|
|
8125993001 | ||
|
|
8583afd83e | ||
|
|
d10a243f86 | ||
|
|
1ec37f6cc9 | ||
|
|
4ad5e870ec | ||
|
|
49d2e62996 | ||
|
|
8febee71af | ||
|
|
16814581f9 | ||
|
|
ffa52f7c8d | ||
|
|
4550027de4 | ||
|
|
7f8e43fd62 | ||
|
|
254c0a8a55 | ||
|
|
22cf94d556 | ||
|
|
a4dc93f831 | ||
|
|
c276a3f434 | ||
|
|
02a1c92b88 | ||
|
|
8092f4a35c | ||
|
|
b250bd9a07 | ||
|
|
99064ee3e1 | ||
|
|
58a728b790 | ||
|
|
66aa975d47 | ||
|
|
5e1ad5d797 | ||
|
|
96dd739064 | ||
|
|
729cf9bba1 | ||
|
|
c5752f9e3b | ||
|
|
ba36f92a35 | ||
|
|
d3589f9770 | ||
|
|
8e6a183bda | ||
|
|
855b3f93b1 | ||
|
|
f85ca1c2d2 | ||
|
|
a30ed2e9d3 | ||
|
|
565b3a9629 | ||
|
|
3701516e5e | ||
|
|
f36bd51163 | ||
|
|
45bba2101f | ||
|
|
87315a19ae | ||
|
|
3bc426482a | ||
|
|
341b0240c9 | ||
|
|
432824edf1 | ||
|
|
9cca12858b | ||
|
|
cc473ee03e | ||
|
|
93266dfcad | ||
|
|
6203d207fa | ||
|
|
424ca426cb | ||
|
|
a9cf944b62 | ||
|
|
8d9ce15849 | ||
|
|
409c0fdf5f | ||
|
|
f50248a7d2 | ||
|
|
09b8219ad4 | ||
|
|
f2bae25a78 | ||
|
|
3dd90a3071 | ||
|
|
c57f741d83 | ||
|
|
3ba4e257a1 | ||
|
|
619c903386 | ||
|
|
78824107f0 | ||
|
|
f64c0f948e | ||
|
|
db3e1a047c | ||
|
|
80f13f07ea | ||
|
|
072151dfee | ||
|
|
965fa6570c | ||
|
|
251567a617 | ||
|
|
57e7747b9b | ||
|
|
f109f0403b | ||
|
|
d407dbd498 | ||
|
|
d970309544 | ||
|
|
30bf69cbe5 | ||
|
|
f61bc180c4 | ||
|
|
9f5e7a9eaa | ||
|
|
a5ca60da44 | ||
|
|
1c097a6fdf | ||
|
|
233753ca6b | ||
|
|
9dd05b0b1b | ||
|
|
4c92633396 | ||
|
|
be2e4714f5 | ||
|
|
941f218a79 | ||
|
|
7bd6ca89b0 | ||
|
|
5342d34126 | ||
|
|
dd250e3bda | ||
|
|
7e39e0a201 | ||
|
|
05aafc7c44 | ||
|
|
eee1de147e | ||
|
|
ece420c569 | ||
|
|
2f4c8310e2 | ||
|
|
d712aed2a5 | ||
|
|
d0b259df42 | ||
|
|
0681c63bc9 | ||
|
|
92e6b48156 | ||
|
|
e53ba345a1 | ||
|
|
34dfd8789a | ||
|
|
16bb790d52 | ||
|
|
1e56edceb8 | ||
|
|
f05f3b3d13 | ||
|
|
c3031ea07f | ||
|
|
39a88c3e83 | ||
|
|
19344a0dfe | ||
|
|
5b86f2c554 | ||
|
|
fd6cc7148d | ||
|
|
72120d7a2c | ||
|
|
479ba0a063 | ||
|
|
2b7b3d1e4b | ||
|
|
ec8eea0c7d | ||
|
|
c2ca4dbf03 | ||
|
|
2276129c6d | ||
|
|
045ce00c15 | ||
|
|
43c1de2372 | ||
|
|
5a148342b0 | ||
|
|
2f93baf984 | ||
|
|
7aaea72f14 | ||
|
|
028bad3e54 | ||
|
|
851ff9976e | ||
|
|
07622be221 | ||
|
|
d8f393900b | ||
|
|
e2687dbeb9 | ||
|
|
43984c3990 | ||
|
|
e874ea8710 | ||
|
|
92f95342d1 | ||
|
|
0fc6ccc5ce | ||
|
|
13eb9e8534 | ||
|
|
cb37c3d66d | ||
|
|
f288c3688e | ||
|
|
8cf6616ed6 | ||
|
|
a3e5af523c | ||
|
|
1bebb12ad9 | ||
|
|
57bc54864e | ||
|
|
d276032e20 | ||
|
|
20eaecf325 | ||
|
|
b16b60b053 | ||
|
|
85c273543d | ||
|
|
ec99f6a761 | ||
|
|
79ab84dad7 | ||
|
|
c0e0c2d07f | ||
|
|
fbbc129e4d | ||
|
|
f592e0f47b | ||
|
|
2676bffa98 | ||
|
|
047ce0c8b2 | ||
|
|
2f3e4b650b | ||
|
|
61922473ac | ||
|
|
fc6be5d09e | ||
|
|
9e2433e39b | ||
|
|
8a199bb4d7 | ||
|
|
fd49eade1a | ||
|
|
a396e64ad6 | ||
|
|
15ad1b4cb4 | ||
|
|
ccf2af7ab2 | ||
|
|
6d123c7e66 | ||
|
|
a1797c49b0 | ||
|
|
1e279bc348 | ||
|
|
449a0e7191 | ||
|
|
08e99fc85f | ||
|
|
2d6a299dbc | ||
|
|
b4748ee110 | ||
|
|
b40c5a64a8 | ||
|
|
0c123a2563 | ||
|
|
fa64198d52 | ||
|
|
a441e22a1b | ||
|
|
7af06f18d7 | ||
|
|
9ad06f11d3 | ||
|
|
0328d0600e | ||
|
|
2538849433 | ||
|
|
843d199469 | ||
|
|
102b4d1ad0 | ||
|
|
51e4445c00 | ||
|
|
172b598f3b | ||
|
|
022b8f1094 | ||
|
|
9013614801 | ||
|
|
56d5d50ea9 | ||
|
|
75dab8f33d | ||
|
|
1c3ce114f6 | ||
|
|
e5ca97308e | ||
|
|
ee8e1b97b9 | ||
|
|
69c43ee43d | ||
|
|
dad858b697 | ||
|
|
f567318e19 | ||
|
|
afedeb9d58 | ||
|
|
d53f909d20 | ||
|
|
2532eb5887 | ||
|
|
17e57287eb | ||
|
|
ed3703a16a | ||
|
|
f43de0ccf5 | ||
|
|
e812473fe1 | ||
|
|
5ec030830a | ||
|
|
dbd3ae54af | ||
|
|
5601cc9640 | ||
|
|
aa98b8c014 | ||
|
|
cc21b4dfd3 | ||
|
|
e864142a7b | ||
|
|
cda74730ae | ||
|
|
334139ca2b | ||
|
|
ea7a514389 | ||
|
|
f1e2b3ad07 | ||
|
|
8bbd16d585 | ||
|
|
9acfb8be20 | ||
|
|
d99b16a158 | ||
|
|
e9c075a4a5 | ||
|
|
ce13140d6b | ||
|
|
f2d7e212bd | ||
|
|
95c381ec78 | ||
|
|
56d2f99548 | ||
|
|
029ce7e0ad | ||
|
|
8d9b6e755b | ||
|
|
aa585f7da0 | ||
|
|
b8e5ad16cd | ||
|
|
1e20f65e26 | ||
|
|
59f1690596 | ||
|
|
5d43fc2c8d | ||
|
|
861fd8b2fa | ||
|
|
ae31cb9995 | ||
|
|
f62244fb69 | ||
|
|
6c0292af99 | ||
|
|
a4ac4eb3fb | ||
|
|
dcf190ebcb | ||
|
|
d968334371 | ||
|
|
47257ec919 | ||
|
|
a16076e082 | ||
|
|
f5bec5c13c | ||
|
|
db12b4e113 | ||
|
|
4a8555571c | ||
|
|
d12551762f | ||
|
|
27ca1ae4f7 | ||
|
|
60e3aba981 | ||
|
|
7a524ddbd5 | ||
|
|
ac37760e37 | ||
|
|
496831329f | ||
|
|
ba58e3d829 | ||
|
|
d532772355 | ||
|
|
3ca52f5934 | ||
|
|
968176a948 | ||
|
|
2af8fd2e31 | ||
|
|
26cfcc145a | ||
|
|
fd97c977c1 | ||
|
|
6c708be99d | ||
|
|
bcc825a121 | ||
|
|
4de9abb49d | ||
|
|
93859e3149 | ||
|
|
fb23c64632 | ||
|
|
323edefc4b | ||
|
|
a6572225f6 | ||
|
|
6d5cb37647 | ||
|
|
0751f453b9 | ||
|
|
4fab5a3169 | ||
|
|
e743f6ab87 | ||
|
|
0500ebc191 | ||
|
|
b87cf4e8b9 | ||
|
|
ebdb2656b6 | ||
|
|
29d47f42c8 | ||
|
|
ae4ebd11f6 | ||
|
|
8ff6cfdda8 | ||
|
|
952dd7a79b | ||
|
|
213be22967 | ||
|
|
11e3745b8b | ||
|
|
153e7d6686 | ||
|
|
a313f3a809 | ||
|
|
e45a788824 | ||
|
|
549239d16f | ||
|
|
b47f4d3aa0 | ||
|
|
d6d1b2731c | ||
|
|
e0ccd816e6 | ||
|
|
039d021f51 | ||
|
|
6b44b532f5 | ||
|
|
c0c56a8cf8 | ||
|
|
d99ce659f6 | ||
|
|
c4aba6be02 | ||
|
|
4537f27f67 | ||
|
|
acf6d0ffc4 | ||
|
|
ce4732aa43 | ||
|
|
68c0a4c246 | ||
|
|
74e9a3c9e5 | ||
|
|
3714fdf3a1 | ||
|
|
0b4a427cac | ||
|
|
b96db939b7 | ||
|
|
ad2c18a6b7 | ||
|
|
2183f29b18 | ||
|
|
90a7b98b10 | ||
|
|
322e45c186 | ||
|
|
da342b6f2f | ||
|
|
3d065eda35 | ||
|
|
c894a92135 | ||
|
|
835ad8acb2 | ||
|
|
73e9b640d6 | ||
|
|
d01733dd3e | ||
|
|
f773c1f529 | ||
|
|
469070ce85 | ||
|
|
ea83ea6dbd | ||
|
|
a2f0eb9140 | ||
|
|
e5fcea0690 | ||
|
|
cd2049f611 | ||
|
|
3dbb3fd48d | ||
|
|
627f817b59 | ||
|
|
ea5e19ff4b | ||
|
|
12fb11eead | ||
|
|
25a858954c | ||
|
|
593233db17 | ||
|
|
fc76447cad | ||
|
|
ac2dd4ab56 | ||
|
|
5d68a3c5e5 | ||
|
|
6597b71f52 | ||
|
|
fe7d69d01b | ||
|
|
977cad0314 | ||
|
|
60b5a11e5a | ||
|
|
a9ebc3aeea | ||
|
|
4a97a40f4a | ||
|
|
404086d4d5 | ||
|
|
498294a561 | ||
|
|
06f7c9448f | ||
|
|
85b8a8c805 | ||
|
|
03c0247ae5 | ||
|
|
238a6ebe9f | ||
|
|
5d1289cc0d | ||
|
|
5cb5d0b8f1 | ||
|
|
a910e39b06 | ||
|
|
1abd549eb7 | ||
|
|
15a600cdaa | ||
|
|
ee05142582 | ||
|
|
6f69939b0d | ||
|
|
e7e0dbfa3d | ||
|
|
e127cf5e1c | ||
|
|
d973206174 | ||
|
|
0e163a0a75 | ||
|
|
eed3a019a6 | ||
|
|
40924d2996 | ||
|
|
2426a2fff7 | ||
|
|
ba33754ea4 | ||
|
|
c11488f99d | ||
|
|
6a3db9429d | ||
|
|
7b4f34d7da | ||
|
|
f182eba6f0 | ||
|
|
b18a079eb9 | ||
|
|
71661c45e2 | ||
|
|
408cc5fb5a | ||
|
|
822f80829e | ||
|
|
1511decad6 | ||
|
|
07a74e5c5a | ||
|
|
9a88401b03 | ||
|
|
83aaa0977f | ||
|
|
68b1d6f632 | ||
|
|
e0531fa17c | ||
|
|
7a81855f12 | ||
|
|
90eaa3a680 | ||
|
|
ecd7e4d5f2 | ||
|
|
14d1fd93a4 | ||
|
|
2d87fdaf0d | ||
|
|
1768f8b073 | ||
|
|
d36a80c76e | ||
|
|
3af07894d8 | ||
|
|
06dbc988b6 | ||
|
|
7f32ab6404 | ||
|
|
f5af2a2e87 | ||
|
|
c9d33b981b | ||
|
|
2c6e201ea6 | ||
|
|
b71dae9b03 | ||
|
|
963b3e0f59 | ||
|
|
a6fc22505c | ||
|
|
c00a011221 | ||
|
|
d1a6e38438 | ||
|
|
36b752d0a6 | ||
|
|
feaa950d89 | ||
|
|
f90e4614b6 | ||
|
|
f152a5fdaf | ||
|
|
b60fc5c374 | ||
|
|
5da805d9ee | ||
|
|
a996c2ee28 | ||
|
|
4cb77b552d | ||
|
|
241ca55d7a | ||
|
|
ae958516ea | ||
|
|
b61900eae1 | ||
|
|
e53b1ce891 | ||
|
|
6dee113a01 | ||
|
|
fe7e9e7c79 | ||
|
|
ab7ce9cb18 | ||
|
|
5afde55ebf | ||
|
|
da63e700b0 | ||
|
|
5a8e5f9518 | ||
|
|
3d9f344a50 | ||
|
|
175b9a0296 | ||
|
|
65c8720250 | ||
|
|
59dd04234f | ||
|
|
36a6daa8ba | ||
|
|
85b36acce1 | ||
|
|
d9618ac917 | ||
|
|
be08c1e717 | ||
|
|
62d53253d1 | ||
|
|
f35e276ee6 | ||
|
|
a736a5a2ce | ||
|
|
c71e55551c | ||
|
|
7454826d4b | ||
|
|
49baf02035 | ||
|
|
a92d774742 | ||
|
|
effb5805f1 | ||
|
|
5d32f32639 | ||
|
|
bccbaafc84 | ||
|
|
6f108a4203 | ||
|
|
3bdd6fd072 | ||
|
|
183451a13f | ||
|
|
2ef7762068 | ||
|
|
b624a13430 | ||
|
|
00b1d0e4b6 | ||
|
|
d4c313f3db | ||
|
|
f19c153d06 | ||
|
|
3692818d0c | ||
|
|
7830aae549 | ||
|
|
2055142904 | ||
|
|
9b9957f5cf | ||
|
|
5bda50cbd7 | ||
|
|
11711bb6da | ||
|
|
497876443c | ||
|
|
94776119bd | ||
|
|
229cb9e3e6 | ||
|
|
67fdefc416 | ||
|
|
e962f4394f | ||
|
|
0054b92115 | ||
|
|
34e4ef1e84 | ||
|
|
9bf991ca88 | ||
|
|
64672a267a | ||
|
|
f16cb0b5f0 | ||
|
|
efa49848e4 | ||
|
|
2eabec8c92 | ||
|
|
f6873efee4 | ||
|
|
47ada94c41 | ||
|
|
a571d990ec | ||
|
|
b7c8d4cd49 | ||
|
|
7c5f18fe3f | ||
|
|
0015493543 | ||
|
|
3709c2a50b | ||
|
|
8dd22269dc | ||
|
|
f71f9875ca | ||
|
|
184b86c6e3 | ||
|
|
77f858d009 | ||
|
|
517e086786 | ||
|
|
ebd5b45dda | ||
|
|
ebcd937092 | ||
|
|
781e8607ae | ||
|
|
2ac2e9ec38 | ||
|
|
b2e92a673a | ||
|
|
b6e2277863 | ||
|
|
9410418c5c | ||
|
|
1da3dbc93a | ||
|
|
fd6e28df2a | ||
|
|
efa8b243c9 | ||
|
|
e3e2ab3612 | ||
|
|
9046b97fa5 | ||
|
|
cb02ce4783 | ||
|
|
9b09be22be | ||
|
|
1564c762ee | ||
|
|
4e00ed1a7b | ||
|
|
6ba945091b | ||
|
|
e19aa75e72 | ||
|
|
d7e40985d2 | ||
|
|
50feb79326 | ||
|
|
b845670f07 | ||
|
|
72c22f3c6e | ||
|
|
d93bfc0cb9 | ||
|
|
b9eae571e5 | ||
|
|
72d983ec5e | ||
|
|
d427f55298 | ||
|
|
5e946fc02a | ||
|
|
ad1c4500d3 | ||
|
|
5c9d7d3f6d | ||
|
|
58597bfeab | ||
|
|
5235cb9ae1 | ||
|
|
e3361c2b3f | ||
|
|
dee84ed906 | ||
|
|
537f899939 | ||
|
|
7536b89815 | ||
|
|
49d81c4ea2 | ||
|
|
ee38702846 | ||
|
|
dd1af53432 | ||
|
|
8664f8f4bc | ||
|
|
6509cddd2a | ||
|
|
bb931c99ff | ||
|
|
f1ccb0a9c5 | ||
|
|
99e0a7038a | ||
|
|
723f40be16 | ||
|
|
8eb1e9dea9 | ||
|
|
c9bf9940d1 | ||
|
|
b675d09c49 | ||
|
|
e9dfe4000e | ||
|
|
5a85a11c60 | ||
|
|
de5de146c3 | ||
|
|
b32e738d75 | ||
|
|
c884d26799 | ||
|
|
b4aa8038ff | ||
|
|
2e72b50d10 | ||
|
|
39b4f75365 | ||
|
|
d80cc88ae1 | ||
|
|
2c583bcb16 | ||
|
|
aa1d968dcf | ||
|
|
ca76e3a763 | ||
|
|
b56b47d187 | ||
|
|
4c70ae285a | ||
|
|
3d2c9b6f6f | ||
|
|
12791636ee | ||
|
|
981f954cf2 | ||
|
|
0266175df8 | ||
|
|
9d56f2530a | ||
|
|
4d2cd5f715 | ||
|
|
a4938102e1 | ||
|
|
bb1ed8b212 | ||
|
|
e7bd54cc00 | ||
|
|
ea2c7c2895 | ||
|
|
8475ad9bc8 | ||
|
|
35d4075c50 | ||
|
|
255dd4b6d6 | ||
|
|
ce1b02b1bd | ||
|
|
c1b8c5df5f | ||
|
|
43722a9dbe | ||
|
|
385b47ee3b | ||
|
|
90caf2701a | ||
|
|
e13e394700 | ||
|
|
6874e44ddd | ||
|
|
2a205b16ce | ||
|
|
dfa19978f8 | ||
|
|
120006a297 | ||
|
|
8c57f4cc85 | ||
|
|
7f9c50e6f5 | ||
|
|
ec35afdc7f | ||
|
|
51038bbdf5 | ||
|
|
c615fe0087 | ||
|
|
af3ed6e26d | ||
|
|
669a0175f3 | ||
|
|
52caf17a40 | ||
|
|
e193a374d3 | ||
|
|
8b46d79560 | ||
|
|
9bf24b7d88 | ||
|
|
c624971fa7 | ||
|
|
b4780b6495 | ||
|
|
b04326ee83 | ||
|
|
5874072d46 | ||
|
|
7d41aa5243 | ||
|
|
2d91a606fd | ||
|
|
69416cc0d6 | ||
|
|
29be0c0cdb | ||
|
|
eca8cc7350 | ||
|
|
f8e7b161b5 | ||
|
|
374c581ee5 | ||
|
|
53a7f64984 | ||
|
|
3f9cda365f |
@@ -1,79 +0,0 @@
|
||||
version: 2.1
|
||||
|
||||
orbs:
|
||||
gh: circleci/github-cli@2.2.0
|
||||
|
||||
jobs:
|
||||
release_mac_arm:
|
||||
macos:
|
||||
xcode: 14.3.1
|
||||
environment:
|
||||
HOMEBREW_NO_AUTO_UPDATE: 1
|
||||
steps:
|
||||
- checkout
|
||||
- run: brew install --formula go automake cmake composer
|
||||
- run:
|
||||
name: Clone static-php-cli
|
||||
command: git clone --depth 1 https://github.com/crazywhalecc/static-php-cli
|
||||
- restore_cache:
|
||||
keys:
|
||||
- spc-{{ checksum "static-php-cli/composer.json" }}
|
||||
- run:
|
||||
name: Install static-php-cli and fetch libraries sources
|
||||
working_directory: static-php-cli/
|
||||
command: |
|
||||
composer install --no-dev -a
|
||||
./bin/spc fetch --with-php=8.2 -A
|
||||
- save_cache:
|
||||
key: spc-{{ checksum "static-php-cli/composer.json" }}
|
||||
paths:
|
||||
- static-php-cli/downloads/
|
||||
- static-php-cli/vendor/
|
||||
- run:
|
||||
working_directory: static-php-cli/
|
||||
name: Build libphp.a
|
||||
command: ./bin/spc build --enable-zts --build-embed "bcmath,calendar,ctype,curl,dba,dom,exif,filter,fileinfo,gd,iconv,intl,mbstring,mbregex,mysqli,mysqlnd,opcache,openssl,pcntl,pdo,pdo_mysql,pdo_pgsql,pdo_sqlite,pgsql,phar,posix,readline,redis,session,simplexml,sockets,sqlite3,tokenizer,xml,xmlreader,xmlwriter,zip,zlib,apcu"
|
||||
- run:
|
||||
working_directory: static-php-cli/
|
||||
name: Set CGO flags
|
||||
command: |
|
||||
if [ -z "$CIRCLE_TAG" ]; then export FRANKENPHP_VERSION=$CIRCLE_SHA1; else export FRANKENPHP_VERSION=${CIRCLE_TAG:1}; fi
|
||||
echo "export CGO_CFLAGS='-DFRANKENPHP_VERSION=$FRANKENPHP_VERSION $(./buildroot/bin/php-config --includes | sed s#-I/#-I$PWD/buildroot/#g)'" >> "$BASH_ENV"
|
||||
echo "export CGO_LDFLAGS='-framework CoreFoundation -framework SystemConfiguration $(./buildroot/bin/php-config --ldflags) $(./buildroot/bin/php-config --libs)'" >> "$BASH_ENV"
|
||||
echo "export PHP_VERSION='$(./buildroot/bin/php-config --version)'" >> "$BASH_ENV"
|
||||
echo "export FRANKENPHP_VERSION='$FRANKENPHP_VERSION'" >> "$BASH_ENV"
|
||||
- restore_cache:
|
||||
keys:
|
||||
- go-mod-v4-{{ checksum "caddy/go.sum" }}
|
||||
- run:
|
||||
name: Build FrankenPHP
|
||||
working_directory: caddy/frankenphp/
|
||||
command: |
|
||||
go env
|
||||
go build -buildmode=pie -tags "cgo netgo osusergo static_build" -ldflags "-linkmode=external -extldflags -static-pie -w -s -X 'github.com/caddyserver/caddy/v2.CustomVersion=FrankenPHP $FRANKENPHP_VERSION PHP $PHP_VERSION Caddy'" -o frankenphp-mac-arm64
|
||||
./frankenphp-mac-arm64 version
|
||||
- gh/setup
|
||||
- run:
|
||||
name: Upload asset
|
||||
working_directory: caddy/frankenphp/
|
||||
command: |
|
||||
if [ -n "$CIRCLE_TAG" ]; then
|
||||
gh release upload $CIRCLE_TAG frankenphp-mac-arm64 --repo dunglas/frankenphp --clobber
|
||||
fi
|
||||
- store_artifacts:
|
||||
path: caddy/frankenphp/frankenphp-mac-arm64
|
||||
destination: frankenphp-mac-arm64
|
||||
- save_cache:
|
||||
key: go-mod-v4-{{ checksum "caddy/go.sum" }}
|
||||
paths:
|
||||
- "~/go/pkg/mod"
|
||||
|
||||
workflows:
|
||||
release:
|
||||
jobs:
|
||||
- release_mac_arm:
|
||||
filters:
|
||||
branches:
|
||||
ignore: /.*/
|
||||
tags:
|
||||
only: /^v.*/
|
||||
@@ -1,10 +1,11 @@
|
||||
# ignored
|
||||
**/*
|
||||
|
||||
# authorized
|
||||
!**/Caddyfile
|
||||
!**/*.go
|
||||
!**/go.*
|
||||
!**/*.c
|
||||
!**/*.h
|
||||
!testdata/*.php
|
||||
/caddy/frankenphp/frankenphp
|
||||
/internal/testserver/testserver
|
||||
/internal/testcli/testcli
|
||||
/dist
|
||||
.DS_Store
|
||||
.idea/
|
||||
.vscode/
|
||||
__debug_bin
|
||||
frankenphp.dev.test
|
||||
caddy/frankenphp/Build
|
||||
*.log
|
||||
|
||||
1
.github/FUNDING.yml
vendored
1
.github/FUNDING.yml
vendored
@@ -1 +0,0 @@
|
||||
github: dunglas
|
||||
89
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
Normal file
89
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
Normal file
@@ -0,0 +1,89 @@
|
||||
---
|
||||
name: Bug Report
|
||||
description: File a bug report
|
||||
labels: [bug]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for taking the time to fill out this bug report!
|
||||
Before submitting a bug, please double-check that your problem [is not
|
||||
a known issue](https://frankenphp.dev/docs/known-issues/)
|
||||
(especially if you use XDebug or Tideways), and that is has not
|
||||
[already been reported](https://github.com/dunglas/frankenphp/issues).
|
||||
- type: textarea
|
||||
id: what-happened
|
||||
attributes:
|
||||
label: What happened?
|
||||
description: |
|
||||
Tell us what you do, what you get and what you expected.
|
||||
Provide us with some step-by-step instructions to reproduce the issue.
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
id: build
|
||||
attributes:
|
||||
label: Build Type
|
||||
description: What build of FrankenPHP do you use?
|
||||
options:
|
||||
- Docker (Debian Bookworm)
|
||||
- Docker (Alpine)
|
||||
- Official static build
|
||||
- Standalone binary
|
||||
- Custom (tell us more in the description)
|
||||
default: 0
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
id: worker
|
||||
attributes:
|
||||
label: Worker Mode
|
||||
description: Does the problem happen only when using the worker mode?
|
||||
options:
|
||||
- "Yes"
|
||||
- "No"
|
||||
default: 0
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
id: os
|
||||
attributes:
|
||||
label: Operating System
|
||||
description: What operating system are you executing FrankenPHP with?
|
||||
options:
|
||||
- GNU/Linux
|
||||
- macOS
|
||||
- Other (tell us more in the description)
|
||||
default: 0
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
id: arch
|
||||
attributes:
|
||||
label: CPU Architecture
|
||||
description: What CPU architecture are you using?
|
||||
options:
|
||||
- x86_64
|
||||
- Apple Silicon
|
||||
- x86
|
||||
- aarch64
|
||||
- Other (tell us more in the description)
|
||||
default: 0
|
||||
- type: textarea
|
||||
id: php
|
||||
attributes:
|
||||
label: PHP configuration
|
||||
description: |
|
||||
Please copy and paste the output of the `phpinfo()` function -- remember to remove **sensitive information** like passwords, API keys, etc.
|
||||
render: shell
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: logs
|
||||
attributes:
|
||||
label: Relevant log output
|
||||
description: |
|
||||
Please copy and paste any relevant log output.
|
||||
This will be automatically formatted into code,
|
||||
so no need for backticks.
|
||||
render: shell
|
||||
20
.github/ISSUE_TEMPLATE/feature_request.yaml
vendored
Normal file
20
.github/ISSUE_TEMPLATE/feature_request.yaml
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
---
|
||||
name: Feature Request
|
||||
description: Suggest an idea for this project
|
||||
labels: [enhancement]
|
||||
body:
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: Describe you feature request
|
||||
value: |
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is.
|
||||
Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions
|
||||
or features you've considered.
|
||||
34
.github/actions/watcher/action.yaml
vendored
Normal file
34
.github/actions/watcher/action.yaml
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
name: watcher
|
||||
description: Install e-dant/watcher
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Determine e-dant/watcher version
|
||||
id: determine-watcher-version
|
||||
run: echo version="$(gh release view --repo e-dant/watcher --json tagName --template '{{ .tagName }}')" >> "${GITHUB_OUTPUT}"
|
||||
shell: bash
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
- name: Cache e-dant/watcher
|
||||
id: cache-watcher
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: watcher/target
|
||||
key: watcher-${{ runner.os }}-${{ runner.arch }}-${{ steps.determine-watcher-version.outputs.version }}-${{ env.CC && env.CC || 'gcc' }}
|
||||
- if: steps.cache-watcher.outputs.cache-hit != 'true'
|
||||
name: Compile e-dant/watcher
|
||||
run: |
|
||||
mkdir watcher
|
||||
gh release download --repo e-dant/watcher -A tar.gz -O - | tar -xz -C watcher --strip-components 1
|
||||
cd watcher
|
||||
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
|
||||
cmake --build build
|
||||
sudo cmake --install build --prefix target
|
||||
shell: bash
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
- name: Update LD_LIBRARY_PATH
|
||||
run: |
|
||||
sudo sh -c "echo ${PWD}/watcher/target/lib > /etc/ld.so.conf.d/watcher.conf"
|
||||
sudo ldconfig
|
||||
shell: bash
|
||||
33
.github/dependabot.yaml
vendored
Normal file
33
.github/dependabot.yaml
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
---
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: gomod
|
||||
directory: /
|
||||
schedule:
|
||||
interval: weekly
|
||||
commit-message:
|
||||
prefix: chore
|
||||
groups:
|
||||
go-modules:
|
||||
patterns:
|
||||
- "*"
|
||||
- package-ecosystem: gomod
|
||||
directory: /caddy
|
||||
schedule:
|
||||
interval: weekly
|
||||
commit-message:
|
||||
prefix: chore(caddy)
|
||||
groups:
|
||||
go-modules:
|
||||
patterns:
|
||||
- "*"
|
||||
- package-ecosystem: github-actions
|
||||
directory: /
|
||||
schedule:
|
||||
interval: weekly
|
||||
commit-message:
|
||||
prefix: ci
|
||||
groups:
|
||||
github-actions:
|
||||
patterns:
|
||||
- "*"
|
||||
244
.github/workflows/docker.yaml
vendored
Normal file
244
.github/workflows/docker.yaml
vendored
Normal file
@@ -0,0 +1,244 @@
|
||||
---
|
||||
name: Build Docker images
|
||||
concurrency:
|
||||
cancel-in-progress: true
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
paths-ignore:
|
||||
- "docs/**"
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
tags:
|
||||
- v*.*.*
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
#checkov:skip=CKV_GHA_7
|
||||
version:
|
||||
description: "FrankenPHP version"
|
||||
required: false
|
||||
type: string
|
||||
schedule:
|
||||
- cron: "0 4 * * *"
|
||||
permissions:
|
||||
contents: read
|
||||
env:
|
||||
IMAGE_NAME: ${{ (github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && inputs.version) || startsWith(github.ref, 'refs/tags/')) && 'dunglas/frankenphp' || 'dunglas/frankenphp-dev' }}
|
||||
jobs:
|
||||
prepare:
|
||||
runs-on: ubuntu-24.04
|
||||
outputs:
|
||||
# Push if it's a scheduled job, a tag, or if we're committing to the main branch
|
||||
push: ${{ (github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && inputs.version) || startsWith(github.ref, 'refs/tags/') || (github.ref == 'refs/heads/main' && github.event_name != 'pull_request')) && true || false }}
|
||||
variants: ${{ steps.matrix.outputs.variants }}
|
||||
platforms: ${{ steps.matrix.outputs.platforms }}
|
||||
metadata: ${{ steps.matrix.outputs.metadata }}
|
||||
php_version: ${{ steps.check.outputs.php_version }}
|
||||
php82_version: ${{ steps.check.outputs.php82_version }}
|
||||
php83_version: ${{ steps.check.outputs.php83_version }}
|
||||
skip: ${{ steps.check.outputs.skip }}
|
||||
ref: ${{ steps.check.outputs.ref || (github.event_name == 'workflow_dispatch' && inputs.version) || '' }}
|
||||
steps:
|
||||
- name: Check PHP versions
|
||||
id: check
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
PHP_82_LATEST=$(skopeo inspect docker://docker.io/library/php:8.2 --override-os linux --override-arch amd64 | jq -r '.Env[] | select(test("^PHP_VERSION=")) | sub("^PHP_VERSION="; "")')
|
||||
PHP_83_LATEST=$(skopeo inspect docker://docker.io/library/php:8.3 --override-os linux --override-arch amd64 | jq -r '.Env[] | select(test("^PHP_VERSION=")) | sub("^PHP_VERSION="; "")')
|
||||
PHP_84_LATEST=$(skopeo inspect docker://docker.io/library/php:8.4 --override-os linux --override-arch amd64 | jq -r '.Env[] | select(test("^PHP_VERSION=")) | sub("^PHP_VERSION="; "")')
|
||||
{
|
||||
echo php_version="${PHP_82_LATEST},${PHP_83_LATEST},${PHP_84_LATEST}"
|
||||
echo php82_version="${PHP_82_LATEST//./-}"
|
||||
echo php83_version="${PHP_83_LATEST//./-}"
|
||||
echo php84_version="${PHP_84_LATEST//./-}"
|
||||
} >> "${GITHUB_OUTPUT}"
|
||||
|
||||
# Check if the Docker images must be rebuilt
|
||||
if [[ "${GITHUB_EVENT_NAME}" != "schedule" ]]; then
|
||||
echo skip=false >> "${GITHUB_OUTPUT}"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
FRANKENPHP_82_LATEST=$(skopeo inspect docker://docker.io/dunglas/frankenphp:php8.2 --override-os linux --override-arch amd64 | jq -r '.Env[] | select(test("^PHP_VERSION=")) | sub("^PHP_VERSION="; "")')
|
||||
FRANKENPHP_83_LATEST=$(skopeo inspect docker://docker.io/dunglas/frankenphp:php8.3 --override-os linux --override-arch amd64 | jq -r '.Env[] | select(test("^PHP_VERSION=")) | sub("^PHP_VERSION="; "")')
|
||||
FRANKENPHP_84_LATEST=$(skopeo inspect docker://docker.io/dunglas/frankenphp:php8.4 --override-os linux --override-arch amd64 | jq -r '.Env[] | select(test("^PHP_VERSION=")) | sub("^PHP_VERSION="; "")')
|
||||
|
||||
if [[ "${FRANKENPHP_82_LATEST}" == "${PHP_82_LATEST}" ]] && [[ "${FRANKENPHP_83_LATEST}" == "${PHP_83_LATEST}" ]] && [[ "${FRANKENPHP_84_LATEST}" == "${PHP_84_LATEST}" ]]; then
|
||||
echo skip=true >> "${GITHUB_OUTPUT}"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
{
|
||||
echo ref="$(gh release view --repo dunglas/frankenphp --json tagName --jq '.tagName')"
|
||||
echo skip=false
|
||||
} >> "${GITHUB_OUTPUT}"
|
||||
- uses: actions/checkout@v4
|
||||
if: ${{ !fromJson(steps.check.outputs.skip) }}
|
||||
with:
|
||||
ref: ${{ steps.check.outputs.ref }}
|
||||
- name: Set up Docker Buildx
|
||||
if: ${{ !fromJson(steps.check.outputs.skip) }}
|
||||
uses: docker/setup-buildx-action@v3
|
||||
- name: Create variants matrix
|
||||
if: ${{ !fromJson(steps.check.outputs.skip) }}
|
||||
id: matrix
|
||||
shell: bash
|
||||
run: |
|
||||
set -e
|
||||
METADATA="$(docker buildx bake --print | jq -c)"
|
||||
{
|
||||
echo metadata="${METADATA}"
|
||||
echo variants="$(jq -c '.group.default.targets|map(sub("runner-|builder-"; ""))|unique' <<< "${METADATA}")"
|
||||
echo platforms="$(jq -c 'first(.target[]) | .platforms' <<< "${METADATA}")"
|
||||
} >> "${GITHUB_OUTPUT}"
|
||||
env:
|
||||
SHA: ${{ github.sha }}
|
||||
VERSION: ${{ (github.ref_type == 'tag' && github.ref_name) || steps.check.outputs.ref || 'dev' }}
|
||||
PHP_VERSION: ${{ steps.check.outputs.php_version }}
|
||||
build:
|
||||
runs-on: ${{ startsWith(matrix.platform, 'linux/arm') && 'ubuntu-24.04-arm' || 'ubuntu-24.04' }}
|
||||
needs:
|
||||
- prepare
|
||||
if: ${{ !fromJson(needs.prepare.outputs.skip) }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
variant: ${{ fromJson(needs.prepare.outputs.variants) }}
|
||||
platform: ${{ fromJson(needs.prepare.outputs.platforms) }}
|
||||
include:
|
||||
- race: ""
|
||||
- platform: linux/amd64
|
||||
race: "-race" # The Go race detector is only supported on amd64
|
||||
exclude:
|
||||
# arm/v6 is only available for Alpine: https://github.com/docker-library/golang/issues/502
|
||||
- variant: php-${{ needs.prepare.outputs.php82_version }}-bookworm
|
||||
platform: linux/arm/v6
|
||||
- variant: php-${{ needs.prepare.outputs.php83_version }}-bookworm
|
||||
platform: linux/arm/v6
|
||||
steps:
|
||||
- name: Prepare
|
||||
id: prepare
|
||||
run: |
|
||||
platform=${{ matrix.platform }}
|
||||
echo "sanitized_platform=${platform//\//-}" >> "${GITHUB_OUTPUT}"
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ needs.prepare.outputs.ref }}
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
with:
|
||||
platforms: ${{ matrix.platform }}
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v3
|
||||
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
|
||||
with:
|
||||
username: ${{ secrets.REGISTRY_USERNAME }}
|
||||
password: ${{ secrets.REGISTRY_PASSWORD }}
|
||||
- name: Build
|
||||
id: build
|
||||
uses: docker/bake-action@v6
|
||||
with:
|
||||
pull: true
|
||||
load: ${{ !fromJson(needs.prepare.outputs.push) }}
|
||||
targets: |
|
||||
builder-${{ matrix.variant }}
|
||||
runner-${{ matrix.variant }}
|
||||
# Remove tags to prevent "can't push tagged ref [...] by digest" error
|
||||
set: |
|
||||
${{ (github.event_name == 'pull_request') && '*.args.NO_COMPRESS=1' || '' }}
|
||||
*.tags=
|
||||
*.platform=${{ matrix.platform }}
|
||||
builder-${{ matrix.variant }}.cache-from=type=gha,scope=builder-${{ matrix.variant }}-${{ needs.prepare.outputs.ref || github.ref }}-${{ matrix.platform }}
|
||||
builder-${{ matrix.variant }}.cache-from=type=gha,scope=refs/heads/main-builder-${{ matrix.variant }}-${{ matrix.platform }}
|
||||
builder-${{ matrix.variant }}.cache-to=type=gha,scope=builder-${{ matrix.variant }}-${{ needs.prepare.outputs.ref || github.ref }}-${{ matrix.platform }},ignore-error=true
|
||||
runner-${{ matrix.variant }}.cache-from=type=gha,scope=runner-${{ matrix.variant }}-${{ needs.prepare.outputs.ref || github.ref }}-${{ matrix.platform }}
|
||||
runner-${{ matrix.variant }}.cache-from=type=gha,scope=refs/heads/main-runner-${{ matrix.variant }}-${{ matrix.platform }}
|
||||
runner-${{ matrix.variant }}.cache-to=type=gha,scope=runner-${{ matrix.variant }}-${{ needs.prepare.outputs.ref || github.ref }}-${{ matrix.platform }},ignore-error=true
|
||||
${{ fromJson(needs.prepare.outputs.push) && format('*.output=type=image,name={0},push-by-digest=true,name-canonical=true,push=true', env.IMAGE_NAME) || '' }}
|
||||
env:
|
||||
SHA: ${{ github.sha }}
|
||||
VERSION: ${{ (github.ref_type == 'tag' && github.ref_name) || needs.prepare.outputs.ref || 'dev' }}
|
||||
PHP_VERSION: ${{ needs.prepare.outputs.php_version }}
|
||||
- # Workaround for https://github.com/actions/runner/pull/2477#issuecomment-1501003600
|
||||
name: Export metadata
|
||||
if: fromJson(needs.prepare.outputs.push)
|
||||
run: |
|
||||
mkdir -p /tmp/metadata/builder /tmp/metadata/runner
|
||||
|
||||
builderDigest=$(jq -r '."builder-${{ matrix.variant }}"."containerimage.digest"' <<< "${METADATA}")
|
||||
touch "/tmp/metadata/builder/${builderDigest#sha256:}"
|
||||
|
||||
runnerDigest=$(jq -r '."runner-${{ matrix.variant }}"."containerimage.digest"' <<< "${METADATA}")
|
||||
touch "/tmp/metadata/runner/${runnerDigest#sha256:}"
|
||||
env:
|
||||
METADATA: ${{ steps.build.outputs.metadata }}
|
||||
- name: Upload builder metadata
|
||||
if: fromJson(needs.prepare.outputs.push)
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: metadata-builder-${{ matrix.variant }}-${{ steps.prepare.outputs.sanitized_platform }}
|
||||
path: /tmp/metadata/builder/*
|
||||
if-no-files-found: error
|
||||
retention-days: 1
|
||||
- name: Upload runner metadata
|
||||
if: fromJson(needs.prepare.outputs.push)
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: metadata-runner-${{ matrix.variant }}-${{ steps.prepare.outputs.sanitized_platform }}
|
||||
path: /tmp/metadata/runner/*
|
||||
if-no-files-found: error
|
||||
retention-days: 1
|
||||
- name: Run tests
|
||||
if: ${{ !fromJson(needs.prepare.outputs.push) }}
|
||||
run: |
|
||||
docker run --platform=${{ matrix.platform }} --rm \
|
||||
"$(jq -r '."builder-${{ matrix.variant }}"."containerimage.config.digest"' <<< "${METADATA}")" \
|
||||
sh -c 'go test -tags ${{ matrix.race }} -v ./... && cd caddy && go test -tags nobadger,nomysql,nopgx ${{ matrix.race }} -v ./...'
|
||||
env:
|
||||
METADATA: ${{ steps.build.outputs.metadata }}
|
||||
# Adapted from https://docs.docker.com/build/ci/github-actions/multi-platform/
|
||||
push:
|
||||
runs-on: ubuntu-24.04
|
||||
needs:
|
||||
- prepare
|
||||
- build
|
||||
if: fromJson(needs.prepare.outputs.push)
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
variant: ${{ fromJson(needs.prepare.outputs.variants) }}
|
||||
target: ["builder", "runner"]
|
||||
steps:
|
||||
- name: Download metadata
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
pattern: metadata-${{ matrix.target }}-${{ matrix.variant }}-*
|
||||
path: /tmp/metadata
|
||||
merge-multiple: true
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v3
|
||||
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
|
||||
with:
|
||||
username: ${{ secrets.REGISTRY_USERNAME }}
|
||||
password: ${{ secrets.REGISTRY_PASSWORD }}
|
||||
- name: Create manifest list and push
|
||||
working-directory: /tmp/metadata
|
||||
run: |
|
||||
set -x
|
||||
# shellcheck disable=SC2046,SC2086
|
||||
docker buildx imagetools create $(jq -cr '.target."${{ matrix.target }}-${{ matrix.variant }}".tags | map("-t " + .) | join(" ")' <<< ${METADATA}) \
|
||||
$(printf "${IMAGE_NAME}@sha256:%s " *)
|
||||
env:
|
||||
METADATA: ${{ needs.prepare.outputs.metadata }}
|
||||
- name: Inspect image
|
||||
run: |
|
||||
# shellcheck disable=SC2046,SC2086
|
||||
docker buildx imagetools inspect $(jq -cr '.target."${{ matrix.target }}-${{ matrix.variant }}".tags | first' <<< ${METADATA})
|
||||
env:
|
||||
METADATA: ${{ needs.prepare.outputs.metadata }}
|
||||
188
.github/workflows/docker.yml
vendored
188
.github/workflows/docker.yml
vendored
@@ -1,188 +0,0 @@
|
||||
name: Build Docker images
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
tags:
|
||||
- v*.*.*
|
||||
workflow_dispatch:
|
||||
inputs: {}
|
||||
jobs:
|
||||
prepare:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
# Push only if it's a tag or if we're committing in the main branch
|
||||
push: ${{toJson(startsWith(github.ref, 'refs/tags/') || (github.ref == 'refs/heads/main' && github.event_name != 'pull_request'))}}
|
||||
variants: ${{ steps.matrix.outputs.variants }}
|
||||
platforms: ${{ steps.matrix.outputs.platforms }}
|
||||
metadata: ${{ steps.matrix.outputs.metadata }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
with:
|
||||
version: latest
|
||||
|
||||
- name: Create variants matrix
|
||||
id: matrix
|
||||
run: |
|
||||
METADATA=$(docker buildx bake --print | jq -c)
|
||||
echo "metadata=$METADATA" >> "$GITHUB_OUTPUT"
|
||||
echo "variants=$(jq -c '.group.default.targets|map(sub("runner-|builder-"; ""))|unique' <<< $METADATA)" >> "$GITHUB_OUTPUT"
|
||||
echo "platforms=$(jq -c 'first(.target[]) | .platforms' <<< $METADATA)" >> "$GITHUB_OUTPUT"
|
||||
env:
|
||||
LATEST: '1' # TODO: unset this variable when releasing the first stable version
|
||||
SHA: ${{github.sha}}
|
||||
VERSION: ${{github.ref_type == 'tag' && github.ref_name || github.sha}}
|
||||
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- prepare
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
variant: ${{ fromJson(needs.prepare.outputs.variants) }}
|
||||
platform: ${{ fromJson(needs.prepare.outputs.platforms) }}
|
||||
include:
|
||||
- race: ""
|
||||
qemu: true
|
||||
- platform: linux/amd64
|
||||
qemu: false
|
||||
race: "-race" # The Go race detector is only supported on amd64
|
||||
- platform: linux/386
|
||||
qemu: false
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up QEMU
|
||||
if: matrix.qemu
|
||||
uses: docker/setup-qemu-action@v2
|
||||
with:
|
||||
platforms: ${{matrix.platform}}
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
with:
|
||||
platforms: ${{matrix.platform}}
|
||||
version: latest
|
||||
|
||||
- name: Login to DockerHub
|
||||
if: fromJson(needs.prepare.outputs.push)
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{secrets.REGISTRY_USERNAME}}
|
||||
password: ${{secrets.REGISTRY_PASSWORD}}
|
||||
|
||||
- name: Build
|
||||
id: build
|
||||
uses: docker/bake-action@v3
|
||||
with:
|
||||
pull: true
|
||||
load: ${{!fromJson(needs.prepare.outputs.push)}}
|
||||
targets: |
|
||||
builder-${{matrix.variant}}
|
||||
runner-${{matrix.variant}}
|
||||
# Remove tags to prevent "can't push tagged ref [...] by digest" error
|
||||
set: |
|
||||
*.tags=
|
||||
*.platform=${{matrix.platform}}
|
||||
*.cache-from=type=gha,scope=${{github.ref}}-${{matrix.platform}}
|
||||
*.cache-from=type=gha,scope=refs/heads/main-${{matrix.platform}}
|
||||
*.cache-to=type=gha,scope=${{github.ref}}-${{matrix.platform}}
|
||||
${{fromJson(needs.prepare.outputs.push) && '*.output=type=image,name=dunglas/frankenphp,push-by-digest=true,name-canonical=true,push=true' || ''}}
|
||||
env:
|
||||
LATEST: '1' # TODO: unset this variable when releasing the first stable version
|
||||
SHA: ${{github.sha}}
|
||||
VERSION: ${{github.ref_type == 'tag' && github.ref_name || github.sha}}
|
||||
|
||||
# Workaround for https://github.com/actions/runner/pull/2477#issuecomment-1501003600
|
||||
- name: Export metadata
|
||||
if: fromJson(needs.prepare.outputs.push)
|
||||
run: |
|
||||
mkdir -p /tmp/metadata/builder /tmp/metadata/runner
|
||||
|
||||
builderDigest=$(jq -r '."builder-${{matrix.variant}}"."containerimage.digest"' <<< $METADATA)
|
||||
touch "/tmp/metadata/builder/${builderDigest#sha256:}"
|
||||
|
||||
runnerDigest=$(jq -r '."runner-${{matrix.variant}}"."containerimage.digest"' <<< $METADATA)
|
||||
touch "/tmp/metadata/runner/${runnerDigest#sha256:}"
|
||||
env:
|
||||
METADATA: ${{steps.build.outputs.metadata}}
|
||||
|
||||
- name: Upload runner metadata
|
||||
if: fromJson(needs.prepare.outputs.push)
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: metadata-builder-${{matrix.variant}}
|
||||
path: /tmp/metadata/builder/*
|
||||
if-no-files-found: error
|
||||
retention-days: 1
|
||||
|
||||
- name: Upload runner metadata
|
||||
if: fromJson(needs.prepare.outputs.push)
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: metadata-runner-${{matrix.variant}}
|
||||
path: /tmp/metadata/runner/*
|
||||
if-no-files-found: error
|
||||
retention-days: 1
|
||||
|
||||
- name: Run tests
|
||||
if: '!matrix.qemu'
|
||||
continue-on-error: ${{fromJson(needs.prepare.outputs.push)}}
|
||||
run: |
|
||||
docker run --platform=${{matrix.platform}} --rm \
|
||||
$(jq -r '."builder-${{matrix.variant}}"."containerimage.config.digest"' <<< $METADATA) \
|
||||
sh -c 'go test ${{matrix.race}} -v ./... && cd caddy && go test ${{matrix.race}} -v ./...'
|
||||
env:
|
||||
METADATA: ${{steps.build.outputs.metadata}}
|
||||
|
||||
# Adapted from https://docs.docker.com/build/ci/github-actions/multi-platform/
|
||||
push:
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- prepare
|
||||
- build
|
||||
if: fromJson(needs.prepare.outputs.push)
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
variant: ${{ fromJson(needs.prepare.outputs.variants) }}
|
||||
target: ['builder', 'runner']
|
||||
steps:
|
||||
- name: Download metadata
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: metadata-${{matrix.target}}-${{matrix.variant}}
|
||||
path: /tmp/metadata
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
with:
|
||||
version: latest
|
||||
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{secrets.REGISTRY_USERNAME}}
|
||||
password: ${{secrets.REGISTRY_PASSWORD}}
|
||||
|
||||
- name: Create manifest list and push
|
||||
working-directory: /tmp/metadata
|
||||
run: |
|
||||
docker buildx imagetools create $(jq -cr '.target."${{matrix.target}}-${{matrix.variant}}".tags | map("-t " + .) | join(" ")' <<< $METADATA) \
|
||||
$(printf 'dunglas/frankenphp@sha256:%s ' *)
|
||||
env:
|
||||
METADATA: ${{needs.prepare.outputs.metadata}}
|
||||
|
||||
- name: Inspect image
|
||||
run: |
|
||||
docker buildx imagetools inspect $(jq -cr '.target."${{matrix.target}}-${{matrix.variant}}".tags | first' <<< $METADATA)
|
||||
env:
|
||||
METADATA: ${{needs.prepare.outputs.metadata}}
|
||||
|
||||
42
.github/workflows/lint.yaml
vendored
Normal file
42
.github/workflows/lint.yaml
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
---
|
||||
name: Lint Code Base
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
permissions:
|
||||
contents: read
|
||||
packages: read
|
||||
statuses: write
|
||||
jobs:
|
||||
build:
|
||||
name: Lint Code Base
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Lint Code Base
|
||||
uses: super-linter/super-linter/slim@v7.4.0
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
LINTER_RULES_PATH: /
|
||||
MARKDOWN_CONFIG_FILE: .markdown-lint.yaml
|
||||
VALIDATE_CPP: false
|
||||
VALIDATE_JSCPD: false
|
||||
VALIDATE_GO: false
|
||||
VALIDATE_GO_MODULES: false
|
||||
VALIDATE_PHP_PHPCS: false
|
||||
VALIDATE_PHP_PHPSTAN: false
|
||||
VALIDATE_PHP_PSALM: false
|
||||
VALIDATE_TERRAGRUNT: false
|
||||
VALIDATE_DOCKERFILE_HADOLINT: false
|
||||
# Prettier and StandardJS are incompatible
|
||||
VALIDATE_JAVASCRIPT_PRETTIER: false
|
||||
VALIDATE_TYPESCRIPT_PRETTIER: false
|
||||
# Conflicts with MARKDOWN
|
||||
VALIDATE_MARKDOWN_PRETTIER: false
|
||||
104
.github/workflows/sanitizers.yaml
vendored
Normal file
104
.github/workflows/sanitizers.yaml
vendored
Normal file
@@ -0,0 +1,104 @@
|
||||
---
|
||||
name: Sanitizers
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
paths-ignore:
|
||||
- "docs/**"
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths-ignore:
|
||||
- "docs/**"
|
||||
permissions:
|
||||
contents: read
|
||||
env:
|
||||
GOTOOLCHAIN: local
|
||||
jobs:
|
||||
# Adapted from https://github.com/beberlei/hdrhistogram-php
|
||||
sanitizers:
|
||||
name: ${{ matrix.sanitizer }}
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
sanitizer: ["asan", "msan"]
|
||||
env:
|
||||
CFLAGS: -g -O0 -fsanitize=${{ matrix.sanitizer == 'asan' && 'address' || 'memory' }} -DZEND_TRACK_ARENA_ALLOC
|
||||
LDFLAGS: -fsanitize=${{ matrix.sanitizer == 'asan' && 'address' || 'memory' }}
|
||||
CC: clang
|
||||
CXX: clang++
|
||||
USE_ZEND_ALLOC: 0
|
||||
LIBRARY_PATH: ${{ github.workspace }}/php/target/lib:${{ github.workspace }}/watcher/target/lib
|
||||
LD_LIBRARY_PATH: ${{ github.workspace }}/php/target/lib
|
||||
steps:
|
||||
- name: Remove local PHP
|
||||
run: sudo apt-get remove --purge --autoremove 'php*' 'libmemcached*'
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: "1.24"
|
||||
cache-dependency-path: |
|
||||
go.sum
|
||||
caddy/go.sum
|
||||
- name: Determine PHP version
|
||||
id: determine-php-version
|
||||
run: |
|
||||
curl -fsSL 'https://www.php.net/releases/index.php?json&max=1&version=8.4' -o version.json
|
||||
echo version="$(jq -r 'keys[0]' version.json)" >> "$GITHUB_OUTPUT"
|
||||
echo archive="$(jq -r '.[] .source[] | select(.filename |endswith(".xz")) | "https://www.php.net/distributions/" + .filename' version.json)" >> "$GITHUB_OUTPUT"
|
||||
- name: Cache PHP
|
||||
id: cache-php
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: php/target
|
||||
key: php-sanitizers-${{ matrix.sanitizer }}-${{ runner.arch }}-${{ steps.determine-php-version.outputs.version }}
|
||||
- if: steps.cache-php.outputs.cache-hit != 'true'
|
||||
name: Compile PHP
|
||||
run: |
|
||||
mkdir php/
|
||||
curl -fsSL "${{ steps.determine-php-version.outputs.archive }}" | tar -Jx -C php --strip-components=1
|
||||
cd php/
|
||||
./configure \
|
||||
CFLAGS="$CFLAGS" \
|
||||
LDFLAGS="$LDFLAGS" \
|
||||
--enable-debug \
|
||||
--enable-embed \
|
||||
--enable-zts \
|
||||
--enable-option-checking=fatal \
|
||||
--disable-zend-signals \
|
||||
--without-sqlite3 \
|
||||
--without-pdo-sqlite \
|
||||
--without-libxml \
|
||||
--disable-dom \
|
||||
--disable-simplexml \
|
||||
--disable-xml \
|
||||
--disable-xmlreader \
|
||||
--disable-xmlwriter \
|
||||
--without-pcre-jit \
|
||||
--disable-opcache-jit \
|
||||
--disable-cli \
|
||||
--disable-cgi \
|
||||
--disable-phpdbg \
|
||||
--without-pear \
|
||||
--disable-mbregex \
|
||||
--enable-werror \
|
||||
${{ matrix.sanitizer == 'msan' && '--enable-memory-sanitizer' || '' }} \
|
||||
--prefix="$(pwd)/target/"
|
||||
make -j"$(getconf _NPROCESSORS_ONLN)"
|
||||
make install
|
||||
- name: Add PHP to the PATH
|
||||
run: echo "$(pwd)/php/target/bin" >> "$GITHUB_PATH"
|
||||
- name: Install e-dant/watcher
|
||||
uses: ./.github/actions/watcher
|
||||
- name: Set Set CGO flags
|
||||
run: |
|
||||
{
|
||||
echo "CGO_CFLAGS=$CFLAGS -I${PWD}/watcher/target/include $(php-config --includes)"
|
||||
echo "CGO_LDFLAGS=$LDFLAGS $(php-config --ldflags) $(php-config --libs)"
|
||||
} >> "$GITHUB_ENV"
|
||||
- name: Compile tests
|
||||
run: go test ${{ matrix.sanitizer == 'msan' && '-tags=nowatcher' || '' }} -${{ matrix.sanitizer }} -v -x -c
|
||||
- name: Run tests
|
||||
run: ./frankenphp.dev.test -test.v
|
||||
409
.github/workflows/static.yaml
vendored
Normal file
409
.github/workflows/static.yaml
vendored
Normal file
@@ -0,0 +1,409 @@
|
||||
---
|
||||
name: Build binary releases
|
||||
concurrency:
|
||||
cancel-in-progress: true
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
paths-ignore:
|
||||
- "docs/**"
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
tags:
|
||||
- v*.*.*
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
#checkov:skip=CKV_GHA_7
|
||||
version:
|
||||
description: "FrankenPHP version"
|
||||
required: false
|
||||
type: string
|
||||
schedule:
|
||||
- cron: "0 0 * * *"
|
||||
permissions:
|
||||
contents: write
|
||||
id-token: write
|
||||
attestations: write
|
||||
env:
|
||||
IMAGE_NAME: ${{ (github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && inputs.version) || startsWith(github.ref, 'refs/tags/')) && 'dunglas/frankenphp' || 'dunglas/frankenphp-dev' }}
|
||||
GOTOOLCHAIN: local
|
||||
jobs:
|
||||
prepare:
|
||||
runs-on: ubuntu-24.04
|
||||
outputs:
|
||||
push: ${{ toJson((steps.check.outputs.ref || (github.event_name == 'workflow_dispatch' && inputs.version) || startsWith(github.ref, 'refs/tags/') || (github.ref == 'refs/heads/main' && github.event_name != 'pull_request')) && true || false) }}
|
||||
platforms: ${{ steps.matrix.outputs.platforms }}
|
||||
metadata: ${{ steps.matrix.outputs.metadata }}
|
||||
gnu_metadata: ${{ steps.matrix.outputs.gnu_metadata }}
|
||||
ref: ${{ steps.check.outputs.ref }}
|
||||
steps:
|
||||
- name: Get version
|
||||
id: check
|
||||
if: github.event_name == 'schedule'
|
||||
run: |
|
||||
ref="${{ (github.ref_type == 'tag' && github.ref_name) || (github.event_name == 'workflow_dispatch' && inputs.version) || '' }}"
|
||||
if [[ -z "${ref}" ]]; then
|
||||
ref="$(gh release view --repo dunglas/frankenphp --json tagName --jq '.tagName')"
|
||||
fi
|
||||
|
||||
echo "ref=${ref}" >> "${GITHUB_OUTPUT}"
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ steps.check.outputs.ref }}
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
- name: Create platforms matrix
|
||||
id: matrix
|
||||
run: |
|
||||
METADATA="$(docker buildx bake --print static-builder-musl | jq -c)"
|
||||
GNU_METADATA="$(docker buildx bake --print static-builder-gnu | jq -c)"
|
||||
{
|
||||
echo metadata="${METADATA}"
|
||||
echo platforms="$(jq -c 'first(.target[]) | .platforms' <<< "${METADATA}")"
|
||||
echo gnu_metadata="${GNU_METADATA}"
|
||||
} >> "${GITHUB_OUTPUT}"
|
||||
env:
|
||||
SHA: ${{ github.sha }}
|
||||
VERSION: ${{ steps.check.outputs.ref || 'dev' }}
|
||||
build-linux-musl:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
platform: ${{ fromJson(needs.prepare.outputs.platforms) }}
|
||||
debug: [false]
|
||||
mimalloc: [false]
|
||||
include:
|
||||
- platform: linux/amd64
|
||||
- platform: linux/amd64
|
||||
debug: true
|
||||
- platform: linux/amd64
|
||||
mimalloc: true
|
||||
name: Build ${{ matrix.platform }} static musl binary${{ matrix.debug && ' (debug)' || '' }}${{ matrix.mimalloc && ' (mimalloc)' || '' }}
|
||||
runs-on: ${{ startsWith(matrix.platform, 'linux/arm') && 'ubuntu-24.04-arm' || 'ubuntu-24.04' }}
|
||||
needs: [prepare]
|
||||
steps:
|
||||
- name: Prepare
|
||||
id: prepare
|
||||
run: |
|
||||
platform=${{ matrix.platform }}
|
||||
echo "sanitized_platform=${platform//\//-}" >> "${GITHUB_OUTPUT}"
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ needs.prepare.outputs.ref }}
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
with:
|
||||
platforms: ${{ matrix.platform }}
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v3
|
||||
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
|
||||
with:
|
||||
username: ${{ secrets.REGISTRY_USERNAME }}
|
||||
password: ${{ secrets.REGISTRY_PASSWORD }}
|
||||
- name: Build
|
||||
id: build
|
||||
uses: docker/bake-action@v6
|
||||
with:
|
||||
pull: true
|
||||
load: ${{ !fromJson(needs.prepare.outputs.push) || matrix.debug || matrix.mimalloc }}
|
||||
targets: static-builder-musl
|
||||
set: |
|
||||
${{ matrix.debug && 'static-builder-musl.args.DEBUG_SYMBOLS=1' || '' }}
|
||||
${{ matrix.mimalloc && 'static-builder-musl.args.MIMALLOC=1' || '' }}
|
||||
${{ (github.event_name == 'pull_request' || matrix.platform == 'linux/arm64') && 'static-builder-musl.args.NO_COMPRESS=1' || '' }}
|
||||
*.tags=
|
||||
*.platform=${{ matrix.platform }}
|
||||
*.cache-from=type=gha,scope=${{ needs.prepare.outputs.ref || github.ref }}-static-builder-musl${{ matrix.debug && '-debug' || '' }}${{ matrix.mimalloc && '-mimalloc' || '' }}
|
||||
*.cache-from=type=gha,scope=refs/heads/main-static-builder-musl${{ matrix.debug && '-debug' || '' }}${{ matrix.mimalloc && '-mimalloc' || '' }}
|
||||
*.cache-to=type=gha,scope=${{ needs.prepare.outputs.ref || github.ref }}-static-builder-musl${{ matrix.debug && '-debug' || '' }}${{ matrix.mimalloc && '-mimalloc' || '' }},ignore-error=true
|
||||
${{ (fromJson(needs.prepare.outputs.push) && !matrix.debug && !matrix.mimalloc) && format('*.output=type=image,name={0},push-by-digest=true,name-canonical=true,push=true', env.IMAGE_NAME) || '' }}
|
||||
env:
|
||||
SHA: ${{ github.sha }}
|
||||
VERSION: ${{ (github.ref_type == 'tag' && github.ref_name) || needs.prepare.outputs.ref || 'dev' }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- # Workaround for https://github.com/actions/runner/pull/2477#issuecomment-1501003600
|
||||
name: Export metadata
|
||||
if: fromJson(needs.prepare.outputs.push) && !matrix.debug && !matrix.mimalloc
|
||||
run: |
|
||||
mkdir -p /tmp/metadata
|
||||
|
||||
# shellcheck disable=SC2086
|
||||
digest=$(jq -r '."static-builder-musl"."containerimage.digest"' <<< ${METADATA})
|
||||
touch "/tmp/metadata/${digest#sha256:}"
|
||||
env:
|
||||
METADATA: ${{ steps.build.outputs.metadata }}
|
||||
- name: Upload metadata
|
||||
if: fromJson(needs.prepare.outputs.push) && !matrix.debug && !matrix.mimalloc
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: metadata-static-builder-musl-${{ steps.prepare.outputs.sanitized_platform }}
|
||||
path: /tmp/metadata/*
|
||||
if-no-files-found: error
|
||||
retention-days: 1
|
||||
- name: Copy binary
|
||||
run: |
|
||||
# shellcheck disable=SC2034
|
||||
digest=$(jq -r '."static-builder-musl"."${{ (fromJson(needs.prepare.outputs.push) && !matrix.debug && !matrix.mimalloc) && 'containerimage.digest' || 'containerimage.config.digest' }}"' <<< "${METADATA}")
|
||||
docker create --platform=${{ matrix.platform }} --name static-builder-musl "${{ (fromJson(needs.prepare.outputs.push) && !matrix.debug && !matrix.mimalloc) && '${IMAGE_NAME}@${digest}' || '${digest}' }}"
|
||||
docker cp "static-builder-musl:/go/src/app/dist/${BINARY}" "${BINARY}${{ matrix.debug && '-debug' || '' }}${{ matrix.mimalloc && '-mimalloc' || '' }}"
|
||||
env:
|
||||
METADATA: ${{ steps.build.outputs.metadata }}
|
||||
BINARY: frankenphp-linux-${{ matrix.platform == 'linux/amd64' && 'x86_64' || 'aarch64' }}
|
||||
- name: Upload artifact
|
||||
if: ${{ !fromJson(needs.prepare.outputs.push) }}
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: frankenphp-linux-${{ matrix.platform == 'linux/amd64' && 'x86_64' || 'aarch64' }}${{ matrix.debug && '-debug' || '' }}${{ matrix.mimalloc && '-mimalloc' || '' }}
|
||||
path: frankenphp-linux-${{ matrix.platform == 'linux/amd64' && 'x86_64' || 'aarch64' }}${{ matrix.debug && '-debug' || '' }}${{ matrix.mimalloc && '-mimalloc' || '' }}
|
||||
- name: Upload assets
|
||||
if: fromJson(needs.prepare.outputs.push) && (needs.prepare.outputs.ref || github.ref_type == 'tag')
|
||||
run: gh release upload "${{ (github.ref_type == 'tag' && github.ref_name) || needs.prepare.outputs.ref }}" frankenphp-linux-${{ matrix.platform == 'linux/amd64' && 'x86_64' || 'aarch64' }}${{ matrix.debug && '-debug' || '' }}${{ matrix.mimalloc && '-mimalloc' || '' }} --repo dunglas/frankenphp --clobber
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- if: fromJson(needs.prepare.outputs.push) && (needs.prepare.outputs.ref || github.ref_type == 'tag')
|
||||
uses: actions/attest-build-provenance@v2
|
||||
with:
|
||||
subject-path: ${{ github.workspace }}/frankenphp-linux-*
|
||||
- name: Run sanity checks
|
||||
run: |
|
||||
"${BINARY}" version
|
||||
"${BINARY}" build-info
|
||||
"${BINARY}" list-modules | grep frankenphp
|
||||
"${BINARY}" list-modules | grep http.encoders.br
|
||||
"${BINARY}" list-modules | grep http.handlers.mercure
|
||||
"${BINARY}" list-modules | grep http.handlers.mercure
|
||||
"${BINARY}" list-modules | grep http.handlers.vulcain
|
||||
"${BINARY}" php-cli -r "echo 'Sanity check passed';"
|
||||
env:
|
||||
BINARY: ./frankenphp-linux-${{ matrix.platform == 'linux/amd64' && 'x86_64' || 'aarch64' }}${{ matrix.debug && '-debug' || '' }}${{ matrix.mimalloc && '-mimalloc' || '' }}
|
||||
|
||||
build-linux-gnu:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
platform: ${{ fromJson(needs.prepare.outputs.platforms) }}
|
||||
name: Build ${{ matrix.platform }} static GNU binary
|
||||
runs-on: ${{ startsWith(matrix.platform, 'linux/arm') && 'ubuntu-24.04-arm' || 'ubuntu-24.04' }}
|
||||
needs: [prepare]
|
||||
steps:
|
||||
- name: Prepare
|
||||
id: prepare
|
||||
run: |
|
||||
platform=${{ matrix.platform }}
|
||||
echo "sanitized_platform=${platform//\//-}" >> "${GITHUB_OUTPUT}"
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ needs.prepare.outputs.ref }}
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
with:
|
||||
platforms: ${{ matrix.platform }}
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v3
|
||||
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
|
||||
with:
|
||||
username: ${{ secrets.REGISTRY_USERNAME }}
|
||||
password: ${{ secrets.REGISTRY_PASSWORD }}
|
||||
- name: Build
|
||||
id: build
|
||||
uses: docker/bake-action@v6
|
||||
with:
|
||||
pull: true
|
||||
load: ${{ !fromJson(needs.prepare.outputs.push) }}
|
||||
targets: static-builder-gnu
|
||||
set: |
|
||||
${{ (github.event_name == 'pull_request' || matrix.platform == 'linux/arm64') && 'static-builder-gnu.args.NO_COMPRESS=1' || '' }}
|
||||
static-builder-gnu.args.BUILD_PACKAGES=1
|
||||
*.tags=
|
||||
*.platform=${{ matrix.platform }}
|
||||
*.cache-from=type=gha,scope=${{ needs.prepare.outputs.ref || github.ref }}-static-builder-gnu
|
||||
*.cache-from=type=gha,scope=refs/heads/main-static-builder-gnu
|
||||
*.cache-to=type=gha,scope=${{ needs.prepare.outputs.ref || github.ref }}-static-builder-gnu,ignore-error=true
|
||||
${{ fromJson(needs.prepare.outputs.push) && format('*.output=type=image,name={0},push-by-digest=true,name-canonical=true,push=true', env.IMAGE_NAME) || '' }}
|
||||
env:
|
||||
SHA: ${{ github.sha }}
|
||||
VERSION: ${{ (github.ref_type == 'tag' && github.ref_name) || needs.prepare.outputs.ref || 'dev' }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- # Workaround for https://github.com/actions/runner/pull/2477#issuecomment-1501003600
|
||||
name: Export metadata
|
||||
if: fromJson(needs.prepare.outputs.push)
|
||||
run: |
|
||||
mkdir -p /tmp/metadata-gnu
|
||||
|
||||
# shellcheck disable=SC2086
|
||||
digest=$(jq -r '."static-builder-gnu"."containerimage.digest"' <<< ${METADATA})
|
||||
touch "/tmp/metadata-gnu/${digest#sha256:}"
|
||||
env:
|
||||
METADATA: ${{ steps.build.outputs.metadata }}
|
||||
- name: Upload metadata
|
||||
if: fromJson(needs.prepare.outputs.push)
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: metadata-static-builder-gnu-${{ steps.prepare.outputs.sanitized_platform }}
|
||||
path: /tmp/metadata-gnu/*
|
||||
if-no-files-found: error
|
||||
retention-days: 1
|
||||
- name: Copy all frankenphp* files
|
||||
run: |
|
||||
# shellcheck disable=SC2034
|
||||
digest=$(jq -r '."static-builder-gnu"."${{ fromJson(needs.prepare.outputs.push) && 'containerimage.digest' || 'containerimage.config.digest' }}"' <<< "${METADATA}")
|
||||
container_id=$(docker create --platform=${{ matrix.platform }} "${{ fromJson(needs.prepare.outputs.push) && '${IMAGE_NAME}@${digest}' || '${digest}' }}")
|
||||
mkdir -p gh-output
|
||||
cd gh-output
|
||||
for file in $(docker run --rm "${{ fromJson(needs.prepare.outputs.push) && '${IMAGE_NAME}@${digest}' || '${digest}' }}" sh -c "ls /go/src/app/dist | grep '^frankenphp'"); do
|
||||
docker cp "${container_id}:/go/src/app/dist/${file}" "./${file}"
|
||||
done
|
||||
docker rm "${container_id}"
|
||||
mv "${BINARY}" "${BINARY}-gnu"
|
||||
env:
|
||||
METADATA: ${{ steps.build.outputs.metadata }}
|
||||
BINARY: frankenphp-linux-${{ matrix.platform == 'linux/amd64' && 'x86_64' || 'aarch64' }}
|
||||
- name: Upload artifact
|
||||
if: ${{ !fromJson(needs.prepare.outputs.push) }}
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: frankenphp-linux-${{ matrix.platform == 'linux/amd64' && 'x86_64' || 'aarch64' }}-gnu-files
|
||||
path: gh-output/*
|
||||
- name: Upload assets
|
||||
if: fromJson(needs.prepare.outputs.push) && (needs.prepare.outputs.ref || github.ref_type == 'tag')
|
||||
run: gh release upload "${{ (github.ref_type == 'tag' && github.ref_name) || needs.prepare.outputs.ref }}" gh-output/* --repo dunglas/frankenphp --clobber
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- if: fromJson(needs.prepare.outputs.push) && (needs.prepare.outputs.ref || github.ref_type == 'tag')
|
||||
uses: actions/attest-build-provenance@v2
|
||||
with:
|
||||
subject-path: ${{ github.workspace }}/gh-output/frankenphp-linux-*-gnu
|
||||
- name: Run sanity checks
|
||||
run: |
|
||||
"${BINARY}" version
|
||||
"${BINARY}" list-modules | grep frankenphp
|
||||
"${BINARY}" list-modules | grep http.encoders.br
|
||||
"${BINARY}" list-modules | grep http.handlers.mercure
|
||||
"${BINARY}" list-modules | grep http.handlers.mercure
|
||||
"${BINARY}" list-modules | grep http.handlers.vulcain
|
||||
"${BINARY}" php-cli -r "echo 'Sanity check passed';"
|
||||
env:
|
||||
BINARY: ./gh-output/frankenphp-linux-${{ matrix.platform == 'linux/amd64' && 'x86_64' || 'aarch64' }}-gnu
|
||||
|
||||
# Adapted from https://docs.docker.com/build/ci/github-actions/multi-platform/
|
||||
push:
|
||||
runs-on: ubuntu-24.04
|
||||
needs:
|
||||
- prepare
|
||||
- build-linux-musl
|
||||
- build-linux-gnu
|
||||
if: fromJson(needs.prepare.outputs.push)
|
||||
steps:
|
||||
- name: Download metadata
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
pattern: metadata-static-builder-musl-*
|
||||
path: /tmp/metadata
|
||||
merge-multiple: true
|
||||
- name: Download GNU metadata
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
pattern: metadata-static-builder-gnu-*
|
||||
path: /tmp/metadata-gnu
|
||||
merge-multiple: true
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v3
|
||||
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
|
||||
with:
|
||||
username: ${{ secrets.REGISTRY_USERNAME }}
|
||||
password: ${{ secrets.REGISTRY_PASSWORD }}
|
||||
- name: Create manifest list and push
|
||||
working-directory: /tmp/metadata
|
||||
run: |
|
||||
# shellcheck disable=SC2046,SC2086
|
||||
docker buildx imagetools create $(jq -cr '.target."static-builder-musl".tags | map("-t " + .) | join(" ")' <<< "${METADATA}") \
|
||||
$(printf "${IMAGE_NAME}@sha256:%s " *)
|
||||
env:
|
||||
METADATA: ${{ needs.prepare.outputs.metadata }}
|
||||
- name: Create GNU manifest list and push
|
||||
working-directory: /tmp/metadata-gnu
|
||||
run: |
|
||||
# shellcheck disable=SC2046,SC2086
|
||||
docker buildx imagetools create $(jq -cr '.target."static-builder-gnu".tags | map("-t " + .) | join(" ")' <<< "${GNU_METADATA}") \
|
||||
$(printf "${IMAGE_NAME}@sha256:%s " *)
|
||||
env:
|
||||
GNU_METADATA: ${{ needs.prepare.outputs.gnu_metadata }}
|
||||
- name: Inspect image
|
||||
run: |
|
||||
# shellcheck disable=SC2046,SC2086
|
||||
docker buildx imagetools inspect "$(jq -cr '.target."static-builder-musl".tags | first' <<< "${METADATA}")"
|
||||
env:
|
||||
METADATA: ${{ needs.prepare.outputs.metadata }}
|
||||
- name: Inspect GNU image
|
||||
run: |
|
||||
# shellcheck disable=SC2046,SC2086
|
||||
docker buildx imagetools inspect "$(jq -cr '.target."static-builder-gnu".tags | first' <<< "${GNU_METADATA}")-gnu"
|
||||
env:
|
||||
GNU_METADATA: ${{ needs.prepare.outputs.gnu_metadata }}
|
||||
|
||||
build-mac:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
platform: ["arm64", "x86_64"]
|
||||
name: Build macOS ${{ matrix.platform }} binaries
|
||||
runs-on: ${{ matrix.platform == 'arm64' && 'macos-14' || 'macos-13' }}
|
||||
needs: [prepare]
|
||||
env:
|
||||
HOMEBREW_NO_AUTO_UPDATE: 1
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ needs.prepare.outputs.ref }}
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: "1.24"
|
||||
cache-dependency-path: |
|
||||
go.sum
|
||||
caddy/go.sum
|
||||
- name: Set FRANKENPHP_VERSION
|
||||
run: |
|
||||
if [ "${GITHUB_REF_TYPE}" == "tag" ]; then
|
||||
export FRANKENPHP_VERSION=${GITHUB_REF_NAME:1}
|
||||
elif [ "${GITHUB_EVENT_NAME}" == "schedule" ]; then
|
||||
export FRANKENPHP_VERSION="${{ needs.prepare.outputs.ref }}"
|
||||
else
|
||||
export FRANKENPHP_VERSION=${GITHUB_SHA}
|
||||
fi
|
||||
|
||||
echo "FRANKENPHP_VERSION=${FRANKENPHP_VERSION}" >> "${GITHUB_ENV}"
|
||||
- name: Build FrankenPHP
|
||||
run: ./build-static.sh
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
RELEASE: ${{ (needs.prepare.outputs.ref || github.ref_type == 'tag') && '1' || '' }}
|
||||
NO_COMPRESS: ${{ github.event_name == 'pull_request' && '1' || '' }}
|
||||
- if: needs.prepare.outputs.ref || github.ref_type == 'tag'
|
||||
uses: actions/attest-build-provenance@v2
|
||||
with:
|
||||
subject-path: ${{ github.workspace }}/dist/frankenphp-mac-*
|
||||
- name: Upload artifact
|
||||
if: github.ref_type == 'branch'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: frankenphp-mac-${{ matrix.platform }}
|
||||
path: dist/frankenphp-mac-${{ matrix.platform }}
|
||||
- name: Run sanity checks
|
||||
run: |
|
||||
"${BINARY}" version
|
||||
"${BINARY}" build-info
|
||||
"${BINARY}" list-modules | grep frankenphp
|
||||
"${BINARY}" list-modules | grep http.encoders.br
|
||||
"${BINARY}" list-modules | grep http.handlers.mercure
|
||||
"${BINARY}" list-modules | grep http.handlers.mercure
|
||||
"${BINARY}" list-modules | grep http.handlers.vulcain
|
||||
"${BINARY}" php-cli -r "echo 'Sanity check passed';"
|
||||
env:
|
||||
BINARY: dist/frankenphp-mac-${{ matrix.platform }}
|
||||
137
.github/workflows/static.yml
vendored
137
.github/workflows/static.yml
vendored
@@ -1,137 +0,0 @@
|
||||
name: Build binary releases
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
tags:
|
||||
- v*.*.*
|
||||
workflow_dispatch:
|
||||
inputs: {}
|
||||
jobs:
|
||||
release:
|
||||
name: Build Linux x86_64 binary
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Create release
|
||||
if: github.ref_type == 'tag'
|
||||
uses: ncipollo/release-action@v1
|
||||
with:
|
||||
generateReleaseNotes: true
|
||||
allowUpdates: true
|
||||
omitBodyDuringUpdate: true
|
||||
omitNameDuringUpdate: true
|
||||
build-linux:
|
||||
name: Build Linux x86_64 binary
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
with:
|
||||
version: latest
|
||||
|
||||
- name: Build
|
||||
id: build
|
||||
uses: docker/bake-action@v3
|
||||
with:
|
||||
pull: true
|
||||
load: true
|
||||
targets: static-builder
|
||||
set: |
|
||||
*.cache-from=type=gha,scope=${{github.ref}}-static-builder
|
||||
*.cache-from=type=gha,scope=refs/heads/main-static-builder
|
||||
*.cache-to=type=gha,scope=${{github.ref}}-static-builder
|
||||
env:
|
||||
VERSION: ${{github.ref_type == 'tag' && github.ref_name || github.sha}}
|
||||
SHA: ${{github.sha}}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Copy binary
|
||||
run: docker cp $(docker create --name static-builder dunglas/frankenphp:static-builder):/go/src/app/caddy/frankenphp/frankenphp frankenphp-linux-x86_64 ; docker rm static-builder
|
||||
|
||||
- name: Upload asset
|
||||
if: github.ref_type == 'tag'
|
||||
uses: ncipollo/release-action@v1
|
||||
with:
|
||||
generateReleaseNotes: true
|
||||
allowUpdates: true
|
||||
omitBodyDuringUpdate: true
|
||||
omitNameDuringUpdate: true
|
||||
artifacts: frankenphp-linux-x86_64
|
||||
|
||||
- name: Upload artifact
|
||||
if: github.ref_type == 'branch'
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
path: frankenphp-linux-x86_64
|
||||
|
||||
build-mac:
|
||||
name: Build macOS x86_64 binaries
|
||||
runs-on: macos-latest
|
||||
env:
|
||||
HOMEBREW_NO_AUTO_UPDATE: 1
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
repository: crazywhalecc/static-php-cli
|
||||
path: static-php-cli
|
||||
|
||||
- name: Install missing system dependencies
|
||||
run: brew install automake
|
||||
|
||||
- uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: '1.21'
|
||||
cache-dependency-path: |
|
||||
go.sum
|
||||
caddy/go.sum
|
||||
|
||||
- name: Install static-php-cli dependencies
|
||||
working-directory: static-php-cli/
|
||||
run: composer install --no-dev -a
|
||||
|
||||
- name: Fetch libraries sources
|
||||
working-directory: static-php-cli/
|
||||
run: ./bin/spc fetch --with-php=8.2 -A
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build libphp.a
|
||||
working-directory: static-php-cli/
|
||||
run: ./bin/spc build --enable-zts --build-embed "bcmath,calendar,ctype,curl,dba,dom,exif,filter,fileinfo,gd,iconv,intl,mbstring,mbregex,mysqli,mysqlnd,opcache,openssl,pcntl,pdo,pdo_mysql,pdo_pgsql,pdo_sqlite,pgsql,phar,posix,readline,redis,session,simplexml,sockets,sqlite3,tokenizer,xml,xmlreader,xmlwriter,zip,zlib,apcu"
|
||||
|
||||
- name: Set CGO flags
|
||||
working-directory: static-php-cli/
|
||||
run: |
|
||||
if [ "$GITHUB_REF_TYPE" == "tag" ]; then export FRANKENPHP_VERSION=${GITHUB_REF_NAME:1}; else export FRANKENPHP_VERSION=$GITHUB_SHA; fi
|
||||
echo "CGO_CFLAGS=-DFRANKENPHP_VERSION=$FRANKENPHP_VERSION $(./buildroot/bin/php-config --includes | sed s#-I/#-I$PWD/buildroot/#g)" >> "$GITHUB_ENV"
|
||||
echo "CGO_LDFLAGS=-framework CoreFoundation -framework SystemConfiguration $(./buildroot/bin/php-config --ldflags) $(./buildroot/bin/php-config --libs)" >> "$GITHUB_ENV"
|
||||
echo "PHP_VERSION=$(./buildroot/bin/php-config --version)" >> "$GITHUB_ENV"
|
||||
echo "FRANKENPHP_VERSION=$FRANKENPHP_VERSION" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Build FrankenPHP
|
||||
working-directory: caddy/frankenphp/
|
||||
run: |
|
||||
go build -buildmode=pie -tags "cgo netgo osusergo static_build" -ldflags "-linkmode=external -extldflags -static-pie -w -s -X 'github.com/caddyserver/caddy/v2.CustomVersion=FrankenPHP $FRANKENPHP_VERSION PHP $PHP_VERSION Caddy'" -o frankenphp-mac-x86_64
|
||||
./frankenphp-mac-x86_64 version
|
||||
|
||||
- name: Upload asset
|
||||
if: github.ref_type == 'tag'
|
||||
uses: ncipollo/release-action@v1
|
||||
with:
|
||||
generateReleaseNotes: true
|
||||
allowUpdates: true
|
||||
omitBodyDuringUpdate: true
|
||||
omitNameDuringUpdate: true
|
||||
artifacts: caddy/frankenphp/frankenphp-mac-x86_64
|
||||
|
||||
- name: Upload binary
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
path: caddy/frankenphp/frankenphp-mac-x86_64
|
||||
111
.github/workflows/tests.yaml
vendored
Normal file
111
.github/workflows/tests.yaml
vendored
Normal file
@@ -0,0 +1,111 @@
|
||||
---
|
||||
name: Tests
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
paths-ignore:
|
||||
- "docs/**"
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths-ignore:
|
||||
- "docs/**"
|
||||
permissions:
|
||||
contents: read
|
||||
env:
|
||||
GOTOOLCHAIN: local
|
||||
GOEXPERIMENT: cgocheck2
|
||||
jobs:
|
||||
tests-linux:
|
||||
name: Tests (Linux, PHP ${{ matrix.php-versions }})
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
php-versions: ["8.2", "8.3", "8.4"]
|
||||
env:
|
||||
GOMAXPROCS: 10
|
||||
LIBRARY_PATH: ${{ github.workspace }}/watcher/target/lib
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: "1.24"
|
||||
cache-dependency-path: |
|
||||
go.sum
|
||||
caddy/go.sum
|
||||
- uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: ${{ matrix.php-versions }}
|
||||
ini-file: development
|
||||
coverage: none
|
||||
tools: none
|
||||
env:
|
||||
phpts: ts
|
||||
debug: true
|
||||
- name: Install e-dant/watcher
|
||||
uses: ./.github/actions/watcher
|
||||
- name: Set CGO flags
|
||||
run: echo "CGO_CFLAGS=-I${PWD}/watcher/target/include $(php-config --includes)" >> "${GITHUB_ENV}"
|
||||
- name: Build
|
||||
run: go build
|
||||
- name: Build testcli binary
|
||||
working-directory: internal/testcli/
|
||||
run: go build
|
||||
- name: Run library tests
|
||||
run: go test -race -v ./...
|
||||
- name: Run Caddy module tests
|
||||
working-directory: caddy/
|
||||
run: go test -tags nobadger,nomysql,nopgx -race -v ./...
|
||||
- name: Run Fuzzing Tests
|
||||
working-directory: caddy/
|
||||
run: go test -fuzz FuzzRequest -fuzztime 20s
|
||||
- name: Build the server
|
||||
working-directory: caddy/frankenphp/
|
||||
run: go build
|
||||
- name: Start the server
|
||||
working-directory: testdata/
|
||||
run: sudo ../caddy/frankenphp/frankenphp start
|
||||
- name: Run integrations tests
|
||||
run: ./reload_test.sh
|
||||
- name: Lint Go code
|
||||
uses: golangci/golangci-lint-action@v8
|
||||
if: matrix.php-versions == '8.4'
|
||||
with:
|
||||
version: latest
|
||||
tests-mac:
|
||||
name: Tests (macOS, PHP 8.4)
|
||||
runs-on: macos-latest
|
||||
env:
|
||||
HOMEBREW_NO_AUTO_UPDATE: 1
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: "1.24"
|
||||
cache-dependency-path: |
|
||||
go.sum
|
||||
caddy/go.sum
|
||||
- uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: 8.4
|
||||
ini-file: development
|
||||
coverage: none
|
||||
tools: none
|
||||
env:
|
||||
phpts: ts
|
||||
debug: true
|
||||
- name: Set Set CGO flags
|
||||
run: |
|
||||
{
|
||||
echo "CGO_CFLAGS=-I/opt/homebrew/include/ $(php-config --includes)"
|
||||
echo "CGO_LDFLAGS=-L/opt/homebrew/lib/ $(php-config --ldflags) $(php-config --libs)"
|
||||
} >> "${GITHUB_ENV}"
|
||||
- name: Build
|
||||
run: go build -tags nowatcher
|
||||
- name: Run library tests
|
||||
run: go test -tags nowatcher -race -v ./...
|
||||
- name: Run Caddy module tests
|
||||
working-directory: caddy/
|
||||
run: go test -tags nowatcher,nobadger,nomysql,nopgx -race -v ./...
|
||||
41
.github/workflows/tests.yml
vendored
41
.github/workflows/tests.yml
vendored
@@ -1,41 +0,0 @@
|
||||
name: Tests
|
||||
on: [push, pull_request]
|
||||
jobs:
|
||||
tests:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
php-versions: ['8.2', '8.3']
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: '1.21'
|
||||
cache-dependency-path: |
|
||||
go.sum
|
||||
caddy/go.sum
|
||||
|
||||
- uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: ${{ matrix.php-versions }}
|
||||
ini-file: development
|
||||
extensions: opcache
|
||||
coverage: none
|
||||
env:
|
||||
phpts: ts
|
||||
|
||||
- name: Set CGO flags
|
||||
run: echo "CGO_CFLAGS=$(php-config --includes)" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Build
|
||||
run: go build
|
||||
env:
|
||||
GOEXPERIMENT: cgocheck2
|
||||
|
||||
- name: Run library tests
|
||||
run: go test -race -v ./...
|
||||
|
||||
- name: Run Caddy module tests
|
||||
working-directory: caddy/
|
||||
run: go test -race -v ./...
|
||||
8
.gitignore
vendored
8
.gitignore
vendored
@@ -1,6 +1,12 @@
|
||||
/caddy/frankenphp/frankenphp
|
||||
/internal/testserver/testserver
|
||||
/internal/testcli/testcli
|
||||
/dist
|
||||
.DS_Store
|
||||
.idea/
|
||||
.vscode/
|
||||
__debug_bin
|
||||
frankenphp.test
|
||||
frankenphp.dev.test
|
||||
caddy/frankenphp/Build
|
||||
package/etc/php.ini
|
||||
*.log
|
||||
|
||||
7
.golangci.yaml
Normal file
7
.golangci.yaml
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
version: "2"
|
||||
run:
|
||||
build-tags:
|
||||
- nobadger
|
||||
- nomysql
|
||||
- nopgx
|
||||
6
.hadolint.yaml
Normal file
6
.hadolint.yaml
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
ignored:
|
||||
- DL3006
|
||||
- DL3008
|
||||
- DL3018
|
||||
- DL3022
|
||||
4
.markdown-lint.yaml
Normal file
4
.markdown-lint.yaml
Normal file
@@ -0,0 +1,4 @@
|
||||
---
|
||||
no-hard-tabs: false
|
||||
MD013: false
|
||||
MD033: false
|
||||
@@ -1,21 +0,0 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016 Johan Hanssen Seferidis
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -1,69 +0,0 @@
|
||||
[](https://circleci.com/gh/Pithikos/C-Thread-Pool)
|
||||
|
||||
# C Thread Pool
|
||||
|
||||
This is a minimal but advanced threadpool implementation.
|
||||
|
||||
* ANCI C and POSIX compliant
|
||||
* Pause/resume/wait as you like
|
||||
* Simple easy-to-digest API
|
||||
* Well tested
|
||||
|
||||
The threadpool is under MIT license. Notice that this project took a considerable amount of work and sacrifice of my free time and the reason I give it for free (even for commercial use) is so when you become rich and wealthy you don't forget about us open-source creatures of the night. Cheers!
|
||||
|
||||
If this project reduced your development time feel free to buy me a coffee.
|
||||
|
||||
[](https://www.paypal.me/seferidis)
|
||||
|
||||
|
||||
## Run an example
|
||||
|
||||
The library is not precompiled so you have to compile it with your project. The thread pool
|
||||
uses POSIX threads so if you compile with gcc on Linux you have to use the flag `-pthread` like this:
|
||||
|
||||
gcc example.c thpool.c -D THPOOL_DEBUG -pthread -o example
|
||||
|
||||
|
||||
Then run the executable like this:
|
||||
|
||||
./example
|
||||
|
||||
|
||||
## Basic usage
|
||||
|
||||
1. Include the header in your source file: `#include "thpool.h"`
|
||||
2. Create a thread pool with number of threads you want: `threadpool thpool = thpool_init(4);`
|
||||
3. Add work to the pool: `thpool_add_work(thpool, (void*)function_p, (void*)arg_p);`
|
||||
|
||||
The workers(threads) will start their work automatically as fast as there is new work
|
||||
in the pool. If you want to wait for all added work to be finished before continuing
|
||||
you can use `thpool_wait(thpool);`. If you want to destroy the pool you can use
|
||||
`thpool_destroy(thpool);`.
|
||||
|
||||
|
||||
## API
|
||||
|
||||
For a deeper look into the documentation check in the [thpool.h](https://github.com/Pithikos/C-Thread-Pool/blob/master/thpool.h) file. Below is a fast practical overview.
|
||||
|
||||
| Function example | Description |
|
||||
|---------------------------------|---------------------------------------------------------------------|
|
||||
| ***thpool_init(4)*** | Will return a new threadpool with `4` threads. |
|
||||
| ***thpool_add_work(thpool, (void*)function_p, (void*)arg_p)*** | Will add new work to the pool. Work is simply a function. You can pass a single argument to the function if you wish. If not, `NULL` should be passed. |
|
||||
| ***thpool_wait(thpool)*** | Will wait for all jobs (both in queue and currently running) to finish. |
|
||||
| ***thpool_destroy(thpool)*** | This will destroy the threadpool. If jobs are currently being executed, then it will wait for them to finish. |
|
||||
| ***thpool_pause(thpool)*** | All threads in the threadpool will pause no matter if they are idle or executing work. |
|
||||
| ***thpool_resume(thpool)*** | If the threadpool is paused, then all threads will resume from where they were. |
|
||||
| ***thpool_num_threads_working(thpool)*** | Will return the number of currently working threads. |
|
||||
|
||||
|
||||
## Contribution
|
||||
|
||||
You are very welcome to contribute. If you have a new feature in mind, you can always open an issue on github describing it so you don't end up doing a lot of work that might not be eventually merged. Generally we are very open to contributions as long as they follow the below keypoints.
|
||||
|
||||
* Try to keep the API as minimal as possible. That means if a feature or fix can be implemented without affecting the existing API but requires more development time, then we will opt to sacrifice development time.
|
||||
* Solutions need to be POSIX compliant. The thread-pool is advertised as such so it makes sense that it actually is.
|
||||
* For coding style simply try to stick to the conventions you find in the existing codebase.
|
||||
* Tests: A new fix or feature should be covered by tests. If the existing tests are not sufficient, we expect an according test to follow with the pull request.
|
||||
* Documentation: for a new feature please add documentation. For an API change the documentation has to be thorough and super easy to understand.
|
||||
|
||||
If you wish to **get access as a collaborator** feel free to mention it in the issue https://github.com/Pithikos/C-Thread-Pool/issues/78
|
||||
@@ -1,40 +0,0 @@
|
||||
/*
|
||||
* WHAT THIS EXAMPLE DOES
|
||||
*
|
||||
* We create a pool of 4 threads and then add 40 tasks to the pool(20 task1
|
||||
* functions and 20 task2 functions). task1 and task2 simply print which thread is running them.
|
||||
*
|
||||
* As soon as we add the tasks to the pool, the threads will run them. It can happen that
|
||||
* you see a single thread running all the tasks (highly unlikely). It is up the OS to
|
||||
* decide which thread will run what. So it is not an error of the thread pool but rather
|
||||
* a decision of the OS.
|
||||
*
|
||||
* */
|
||||
|
||||
#include <stdio.h>
|
||||
#include <pthread.h>
|
||||
#include <stdint.h>
|
||||
#include "thpool.h"
|
||||
|
||||
void task(void *arg){
|
||||
printf("Thread #%u working on %d\n", (int)pthread_self(), (int) arg);
|
||||
}
|
||||
|
||||
|
||||
int main(){
|
||||
|
||||
puts("Making threadpool with 4 threads");
|
||||
threadpool thpool = thpool_init(4);
|
||||
|
||||
puts("Adding 40 tasks to threadpool");
|
||||
int i;
|
||||
for (i=0; i<40; i++){
|
||||
thpool_add_work(thpool, task, (void*)(uintptr_t)i);
|
||||
};
|
||||
|
||||
thpool_wait(thpool);
|
||||
puts("Killing threadpool");
|
||||
thpool_destroy(thpool);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -1,553 +0,0 @@
|
||||
/* ********************************
|
||||
* Author: Johan Hanssen Seferidis
|
||||
* License: MIT
|
||||
* Description: Library providing a threading pool where you can add
|
||||
* work. For usage, check the thpool.h file or README.md
|
||||
*
|
||||
*//** @file thpool.h *//*
|
||||
*
|
||||
********************************/
|
||||
|
||||
#if defined(__APPLE__)
|
||||
#include <AvailabilityMacros.h>
|
||||
#else
|
||||
#ifndef _POSIX_C_SOURCE
|
||||
#define _POSIX_C_SOURCE 200809L
|
||||
#endif
|
||||
#endif
|
||||
#include <unistd.h>
|
||||
#include <signal.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <pthread.h>
|
||||
#include <errno.h>
|
||||
#include <time.h>
|
||||
#if defined(__linux__)
|
||||
#include <sys/prctl.h>
|
||||
#endif
|
||||
|
||||
#include "thpool.h"
|
||||
|
||||
#ifdef THPOOL_DEBUG
|
||||
#define THPOOL_DEBUG 1
|
||||
#else
|
||||
#define THPOOL_DEBUG 0
|
||||
#endif
|
||||
|
||||
#if !defined(DISABLE_PRINT) || defined(THPOOL_DEBUG)
|
||||
#define err(str) fprintf(stderr, str)
|
||||
#else
|
||||
#define err(str)
|
||||
#endif
|
||||
|
||||
static volatile int threads_keepalive;
|
||||
static volatile int threads_on_hold;
|
||||
|
||||
|
||||
|
||||
/* ========================== STRUCTURES ============================ */
|
||||
|
||||
|
||||
/* Binary semaphore */
|
||||
typedef struct bsem {
|
||||
pthread_mutex_t mutex;
|
||||
pthread_cond_t cond;
|
||||
int v;
|
||||
} bsem;
|
||||
|
||||
|
||||
/* Job */
|
||||
typedef struct job{
|
||||
struct job* prev; /* pointer to previous job */
|
||||
void (*function)(void* arg); /* function pointer */
|
||||
void* arg; /* function's argument */
|
||||
} job;
|
||||
|
||||
|
||||
/* Job queue */
|
||||
typedef struct jobqueue{
|
||||
pthread_mutex_t rwmutex; /* used for queue r/w access */
|
||||
job *front; /* pointer to front of queue */
|
||||
job *rear; /* pointer to rear of queue */
|
||||
bsem *has_jobs; /* flag as binary semaphore */
|
||||
int len; /* number of jobs in queue */
|
||||
} jobqueue;
|
||||
|
||||
|
||||
/* Thread */
|
||||
typedef struct thread{
|
||||
int id; /* friendly id */
|
||||
pthread_t pthread; /* pointer to actual thread */
|
||||
struct thpool_* thpool_p; /* access to thpool */
|
||||
} thread;
|
||||
|
||||
|
||||
/* Threadpool */
|
||||
typedef struct thpool_{
|
||||
thread** threads; /* pointer to threads */
|
||||
volatile int num_threads_alive; /* threads currently alive */
|
||||
volatile int num_threads_working; /* threads currently working */
|
||||
pthread_mutex_t thcount_lock; /* used for thread count etc */
|
||||
pthread_cond_t threads_all_idle; /* signal to thpool_wait */
|
||||
jobqueue jobqueue; /* job queue */
|
||||
} thpool_;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/* ========================== PROTOTYPES ============================ */
|
||||
|
||||
|
||||
static int thread_init(thpool_* thpool_p, struct thread** thread_p, int id);
|
||||
static void* thread_do(struct thread* thread_p);
|
||||
static void thread_hold(int sig_id);
|
||||
static void thread_destroy(struct thread* thread_p);
|
||||
|
||||
static int jobqueue_init(jobqueue* jobqueue_p);
|
||||
static void jobqueue_clear(jobqueue* jobqueue_p);
|
||||
static void jobqueue_push(jobqueue* jobqueue_p, struct job* newjob_p);
|
||||
static struct job* jobqueue_pull(jobqueue* jobqueue_p);
|
||||
static void jobqueue_destroy(jobqueue* jobqueue_p);
|
||||
|
||||
static void bsem_init(struct bsem *bsem_p, int value);
|
||||
static void bsem_reset(struct bsem *bsem_p);
|
||||
static void bsem_post(struct bsem *bsem_p);
|
||||
static void bsem_post_all(struct bsem *bsem_p);
|
||||
static void bsem_wait(struct bsem *bsem_p);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/* ========================== THREADPOOL ============================ */
|
||||
|
||||
|
||||
/* Initialise thread pool */
|
||||
struct thpool_* thpool_init(int num_threads){
|
||||
|
||||
threads_on_hold = 0;
|
||||
threads_keepalive = 1;
|
||||
|
||||
if (num_threads < 0){
|
||||
num_threads = 0;
|
||||
}
|
||||
|
||||
/* Make new thread pool */
|
||||
thpool_* thpool_p;
|
||||
thpool_p = (struct thpool_*)malloc(sizeof(struct thpool_));
|
||||
if (thpool_p == NULL){
|
||||
err("thpool_init(): Could not allocate memory for thread pool\n");
|
||||
return NULL;
|
||||
}
|
||||
thpool_p->num_threads_alive = 0;
|
||||
thpool_p->num_threads_working = 0;
|
||||
|
||||
/* Initialise the job queue */
|
||||
if (jobqueue_init(&thpool_p->jobqueue) == -1){
|
||||
err("thpool_init(): Could not allocate memory for job queue\n");
|
||||
free(thpool_p);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Make threads in pool */
|
||||
thpool_p->threads = (struct thread**)malloc(num_threads * sizeof(struct thread *));
|
||||
if (thpool_p->threads == NULL){
|
||||
err("thpool_init(): Could not allocate memory for threads\n");
|
||||
jobqueue_destroy(&thpool_p->jobqueue);
|
||||
free(thpool_p);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
pthread_mutex_init(&(thpool_p->thcount_lock), NULL);
|
||||
pthread_cond_init(&thpool_p->threads_all_idle, NULL);
|
||||
|
||||
/* Thread init */
|
||||
int n;
|
||||
for (n=0; n<num_threads; n++){
|
||||
thread_init(thpool_p, &thpool_p->threads[n], n);
|
||||
#if THPOOL_DEBUG
|
||||
printf("THPOOL_DEBUG: Created thread %d in pool \n", n);
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Wait for threads to initialize */
|
||||
while (thpool_p->num_threads_alive != num_threads) {}
|
||||
|
||||
return thpool_p;
|
||||
}
|
||||
|
||||
|
||||
/* Add work to the thread pool */
|
||||
int thpool_add_work(thpool_* thpool_p, void (*function_p)(void*), void* arg_p){
|
||||
job* newjob;
|
||||
|
||||
newjob=(struct job*)malloc(sizeof(struct job));
|
||||
if (newjob==NULL){
|
||||
err("thpool_add_work(): Could not allocate memory for new job\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* add function and argument */
|
||||
newjob->function=function_p;
|
||||
newjob->arg=arg_p;
|
||||
|
||||
/* add job to queue */
|
||||
jobqueue_push(&thpool_p->jobqueue, newjob);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/* Wait until all jobs have finished */
|
||||
void thpool_wait(thpool_* thpool_p){
|
||||
pthread_mutex_lock(&thpool_p->thcount_lock);
|
||||
while (thpool_p->jobqueue.len || thpool_p->num_threads_working) {
|
||||
pthread_cond_wait(&thpool_p->threads_all_idle, &thpool_p->thcount_lock);
|
||||
}
|
||||
pthread_mutex_unlock(&thpool_p->thcount_lock);
|
||||
}
|
||||
|
||||
|
||||
/* Destroy the threadpool */
|
||||
void thpool_destroy(thpool_* thpool_p){
|
||||
/* No need to destory if it's NULL */
|
||||
if (thpool_p == NULL) return ;
|
||||
|
||||
volatile int threads_total = thpool_p->num_threads_alive;
|
||||
|
||||
/* End each thread 's infinite loop */
|
||||
threads_keepalive = 0;
|
||||
|
||||
/* Give one second to kill idle threads */
|
||||
double TIMEOUT = 1.0;
|
||||
time_t start, end;
|
||||
double tpassed = 0.0;
|
||||
time (&start);
|
||||
while (tpassed < TIMEOUT && thpool_p->num_threads_alive){
|
||||
bsem_post_all(thpool_p->jobqueue.has_jobs);
|
||||
time (&end);
|
||||
tpassed = difftime(end,start);
|
||||
}
|
||||
|
||||
/* Poll remaining threads */
|
||||
while (thpool_p->num_threads_alive){
|
||||
bsem_post_all(thpool_p->jobqueue.has_jobs);
|
||||
sleep(1);
|
||||
}
|
||||
|
||||
/* Job queue cleanup */
|
||||
jobqueue_destroy(&thpool_p->jobqueue);
|
||||
/* Deallocs */
|
||||
int n;
|
||||
for (n=0; n < threads_total; n++){
|
||||
thread_destroy(thpool_p->threads[n]);
|
||||
}
|
||||
free(thpool_p->threads);
|
||||
free(thpool_p);
|
||||
}
|
||||
|
||||
|
||||
/* Pause all threads in threadpool */
|
||||
void thpool_pause(thpool_* thpool_p) {
|
||||
int n;
|
||||
for (n=0; n < thpool_p->num_threads_alive; n++){
|
||||
pthread_kill(thpool_p->threads[n]->pthread, SIGUSR1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Resume all threads in threadpool */
|
||||
void thpool_resume(thpool_* thpool_p) {
|
||||
// resuming a single threadpool hasn't been
|
||||
// implemented yet, meanwhile this supresses
|
||||
// the warnings
|
||||
(void)thpool_p;
|
||||
|
||||
threads_on_hold = 0;
|
||||
}
|
||||
|
||||
|
||||
int thpool_num_threads_working(thpool_* thpool_p){
|
||||
return thpool_p->num_threads_working;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/* ============================ THREAD ============================== */
|
||||
|
||||
|
||||
/* Initialize a thread in the thread pool
|
||||
*
|
||||
* @param thread address to the pointer of the thread to be created
|
||||
* @param id id to be given to the thread
|
||||
* @return 0 on success, -1 otherwise.
|
||||
*/
|
||||
static int thread_init (thpool_* thpool_p, struct thread** thread_p, int id){
|
||||
|
||||
*thread_p = (struct thread*)malloc(sizeof(struct thread));
|
||||
if (*thread_p == NULL){
|
||||
err("thread_init(): Could not allocate memory for thread\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
(*thread_p)->thpool_p = thpool_p;
|
||||
(*thread_p)->id = id;
|
||||
|
||||
pthread_create(&(*thread_p)->pthread, NULL, (void * (*)(void *)) thread_do, (*thread_p));
|
||||
pthread_detach((*thread_p)->pthread);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/* Sets the calling thread on hold */
|
||||
static void thread_hold(int sig_id) {
|
||||
(void)sig_id;
|
||||
threads_on_hold = 1;
|
||||
while (threads_on_hold){
|
||||
sleep(1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* What each thread is doing
|
||||
*
|
||||
* In principle this is an endless loop. The only time this loop gets interuppted is once
|
||||
* thpool_destroy() is invoked or the program exits.
|
||||
*
|
||||
* @param thread thread that will run this function
|
||||
* @return nothing
|
||||
*/
|
||||
static void* thread_do(struct thread* thread_p){
|
||||
|
||||
/* Set thread name for profiling and debuging */
|
||||
char thread_name[16] = {0};
|
||||
snprintf(thread_name, 16, "thpool-%d", thread_p->id);
|
||||
|
||||
#if defined(__linux__)
|
||||
/* Use prctl instead to prevent using _GNU_SOURCE flag and implicit declaration */
|
||||
prctl(PR_SET_NAME, thread_name);
|
||||
#elif defined(__APPLE__) && defined(__MACH__)
|
||||
pthread_setname_np(thread_name);
|
||||
#else
|
||||
err("thread_do(): pthread_setname_np is not supported on this system");
|
||||
#endif
|
||||
|
||||
/* Assure all threads have been created before starting serving */
|
||||
thpool_* thpool_p = thread_p->thpool_p;
|
||||
|
||||
/* Register signal handler */
|
||||
struct sigaction act;
|
||||
sigemptyset(&act.sa_mask);
|
||||
act.sa_flags = SA_ONSTACK;
|
||||
act.sa_handler = thread_hold;
|
||||
if (sigaction(SIGUSR1, &act, NULL) == -1) {
|
||||
err("thread_do(): cannot handle SIGUSR1");
|
||||
}
|
||||
|
||||
/* Mark thread as alive (initialized) */
|
||||
pthread_mutex_lock(&thpool_p->thcount_lock);
|
||||
thpool_p->num_threads_alive += 1;
|
||||
pthread_mutex_unlock(&thpool_p->thcount_lock);
|
||||
|
||||
while(threads_keepalive){
|
||||
|
||||
bsem_wait(thpool_p->jobqueue.has_jobs);
|
||||
|
||||
if (threads_keepalive){
|
||||
|
||||
pthread_mutex_lock(&thpool_p->thcount_lock);
|
||||
thpool_p->num_threads_working++;
|
||||
pthread_mutex_unlock(&thpool_p->thcount_lock);
|
||||
|
||||
/* Read job from queue and execute it */
|
||||
void (*func_buff)(void*);
|
||||
void* arg_buff;
|
||||
job* job_p = jobqueue_pull(&thpool_p->jobqueue);
|
||||
if (job_p) {
|
||||
func_buff = job_p->function;
|
||||
arg_buff = job_p->arg;
|
||||
func_buff(arg_buff);
|
||||
free(job_p);
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&thpool_p->thcount_lock);
|
||||
thpool_p->num_threads_working--;
|
||||
if (!thpool_p->num_threads_working) {
|
||||
pthread_cond_signal(&thpool_p->threads_all_idle);
|
||||
}
|
||||
pthread_mutex_unlock(&thpool_p->thcount_lock);
|
||||
|
||||
}
|
||||
}
|
||||
pthread_mutex_lock(&thpool_p->thcount_lock);
|
||||
thpool_p->num_threads_alive --;
|
||||
pthread_mutex_unlock(&thpool_p->thcount_lock);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
/* Frees a thread */
|
||||
static void thread_destroy (thread* thread_p){
|
||||
free(thread_p);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/* ============================ JOB QUEUE =========================== */
|
||||
|
||||
|
||||
/* Initialize queue */
|
||||
static int jobqueue_init(jobqueue* jobqueue_p){
|
||||
jobqueue_p->len = 0;
|
||||
jobqueue_p->front = NULL;
|
||||
jobqueue_p->rear = NULL;
|
||||
|
||||
jobqueue_p->has_jobs = (struct bsem*)malloc(sizeof(struct bsem));
|
||||
if (jobqueue_p->has_jobs == NULL){
|
||||
return -1;
|
||||
}
|
||||
|
||||
pthread_mutex_init(&(jobqueue_p->rwmutex), NULL);
|
||||
bsem_init(jobqueue_p->has_jobs, 0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/* Clear the queue */
|
||||
static void jobqueue_clear(jobqueue* jobqueue_p){
|
||||
|
||||
while(jobqueue_p->len){
|
||||
free(jobqueue_pull(jobqueue_p));
|
||||
}
|
||||
|
||||
jobqueue_p->front = NULL;
|
||||
jobqueue_p->rear = NULL;
|
||||
bsem_reset(jobqueue_p->has_jobs);
|
||||
jobqueue_p->len = 0;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/* Add (allocated) job to queue
|
||||
*/
|
||||
static void jobqueue_push(jobqueue* jobqueue_p, struct job* newjob){
|
||||
|
||||
pthread_mutex_lock(&jobqueue_p->rwmutex);
|
||||
newjob->prev = NULL;
|
||||
|
||||
switch(jobqueue_p->len){
|
||||
|
||||
case 0: /* if no jobs in queue */
|
||||
jobqueue_p->front = newjob;
|
||||
jobqueue_p->rear = newjob;
|
||||
break;
|
||||
|
||||
default: /* if jobs in queue */
|
||||
jobqueue_p->rear->prev = newjob;
|
||||
jobqueue_p->rear = newjob;
|
||||
|
||||
}
|
||||
jobqueue_p->len++;
|
||||
|
||||
bsem_post(jobqueue_p->has_jobs);
|
||||
pthread_mutex_unlock(&jobqueue_p->rwmutex);
|
||||
}
|
||||
|
||||
|
||||
/* Get first job from queue(removes it from queue)
|
||||
* Notice: Caller MUST hold a mutex
|
||||
*/
|
||||
static struct job* jobqueue_pull(jobqueue* jobqueue_p){
|
||||
|
||||
pthread_mutex_lock(&jobqueue_p->rwmutex);
|
||||
job* job_p = jobqueue_p->front;
|
||||
|
||||
switch(jobqueue_p->len){
|
||||
|
||||
case 0: /* if no jobs in queue */
|
||||
break;
|
||||
|
||||
case 1: /* if one job in queue */
|
||||
jobqueue_p->front = NULL;
|
||||
jobqueue_p->rear = NULL;
|
||||
jobqueue_p->len = 0;
|
||||
break;
|
||||
|
||||
default: /* if >1 jobs in queue */
|
||||
jobqueue_p->front = job_p->prev;
|
||||
jobqueue_p->len--;
|
||||
/* more than one job in queue -> post it */
|
||||
bsem_post(jobqueue_p->has_jobs);
|
||||
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&jobqueue_p->rwmutex);
|
||||
return job_p;
|
||||
}
|
||||
|
||||
|
||||
/* Free all queue resources back to the system */
|
||||
static void jobqueue_destroy(jobqueue* jobqueue_p){
|
||||
jobqueue_clear(jobqueue_p);
|
||||
free(jobqueue_p->has_jobs);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/* ======================== SYNCHRONISATION ========================= */
|
||||
|
||||
|
||||
/* Init semaphore to 1 or 0 */
|
||||
static void bsem_init(bsem *bsem_p, int value) {
|
||||
if (value < 0 || value > 1) {
|
||||
err("bsem_init(): Binary semaphore can take only values 1 or 0");
|
||||
exit(1);
|
||||
}
|
||||
pthread_mutex_init(&(bsem_p->mutex), NULL);
|
||||
pthread_cond_init(&(bsem_p->cond), NULL);
|
||||
bsem_p->v = value;
|
||||
}
|
||||
|
||||
|
||||
/* Reset semaphore to 0 */
|
||||
static void bsem_reset(bsem *bsem_p) {
|
||||
bsem_init(bsem_p, 0);
|
||||
}
|
||||
|
||||
|
||||
/* Post to at least one thread */
|
||||
static void bsem_post(bsem *bsem_p) {
|
||||
pthread_mutex_lock(&bsem_p->mutex);
|
||||
bsem_p->v = 1;
|
||||
pthread_cond_signal(&bsem_p->cond);
|
||||
pthread_mutex_unlock(&bsem_p->mutex);
|
||||
}
|
||||
|
||||
|
||||
/* Post to all threads */
|
||||
static void bsem_post_all(bsem *bsem_p) {
|
||||
pthread_mutex_lock(&bsem_p->mutex);
|
||||
bsem_p->v = 1;
|
||||
pthread_cond_broadcast(&bsem_p->cond);
|
||||
pthread_mutex_unlock(&bsem_p->mutex);
|
||||
}
|
||||
|
||||
|
||||
/* Wait on semaphore until semaphore has value 0 */
|
||||
static void bsem_wait(bsem* bsem_p) {
|
||||
pthread_mutex_lock(&bsem_p->mutex);
|
||||
while (bsem_p->v != 1) {
|
||||
pthread_cond_wait(&bsem_p->cond, &bsem_p->mutex);
|
||||
}
|
||||
bsem_p->v = 0;
|
||||
pthread_mutex_unlock(&bsem_p->mutex);
|
||||
}
|
||||
@@ -1,187 +0,0 @@
|
||||
/**********************************
|
||||
* @author Johan Hanssen Seferidis
|
||||
* License: MIT
|
||||
*
|
||||
**********************************/
|
||||
|
||||
#ifndef _THPOOL_
|
||||
#define _THPOOL_
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* =================================== API ======================================= */
|
||||
|
||||
|
||||
typedef struct thpool_* threadpool;
|
||||
|
||||
|
||||
/**
|
||||
* @brief Initialize threadpool
|
||||
*
|
||||
* Initializes a threadpool. This function will not return until all
|
||||
* threads have initialized successfully.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ..
|
||||
* threadpool thpool; //First we declare a threadpool
|
||||
* thpool = thpool_init(4); //then we initialize it to 4 threads
|
||||
* ..
|
||||
*
|
||||
* @param num_threads number of threads to be created in the threadpool
|
||||
* @return threadpool created threadpool on success,
|
||||
* NULL on error
|
||||
*/
|
||||
threadpool thpool_init(int num_threads);
|
||||
|
||||
|
||||
/**
|
||||
* @brief Add work to the job queue
|
||||
*
|
||||
* Takes an action and its argument and adds it to the threadpool's job queue.
|
||||
* If you want to add to work a function with more than one arguments then
|
||||
* a way to implement this is by passing a pointer to a structure.
|
||||
*
|
||||
* NOTICE: You have to cast both the function and argument to not get warnings.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* void print_num(int num){
|
||||
* printf("%d\n", num);
|
||||
* }
|
||||
*
|
||||
* int main() {
|
||||
* ..
|
||||
* int a = 10;
|
||||
* thpool_add_work(thpool, (void*)print_num, (void*)a);
|
||||
* ..
|
||||
* }
|
||||
*
|
||||
* @param threadpool threadpool to which the work will be added
|
||||
* @param function_p pointer to function to add as work
|
||||
* @param arg_p pointer to an argument
|
||||
* @return 0 on success, -1 otherwise.
|
||||
*/
|
||||
int thpool_add_work(threadpool, void (*function_p)(void*), void* arg_p);
|
||||
|
||||
|
||||
/**
|
||||
* @brief Wait for all queued jobs to finish
|
||||
*
|
||||
* Will wait for all jobs - both queued and currently running to finish.
|
||||
* Once the queue is empty and all work has completed, the calling thread
|
||||
* (probably the main program) will continue.
|
||||
*
|
||||
* Smart polling is used in wait. The polling is initially 0 - meaning that
|
||||
* there is virtually no polling at all. If after 1 seconds the threads
|
||||
* haven't finished, the polling interval starts growing exponentially
|
||||
* until it reaches max_secs seconds. Then it jumps down to a maximum polling
|
||||
* interval assuming that heavy processing is being used in the threadpool.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ..
|
||||
* threadpool thpool = thpool_init(4);
|
||||
* ..
|
||||
* // Add a bunch of work
|
||||
* ..
|
||||
* thpool_wait(thpool);
|
||||
* puts("All added work has finished");
|
||||
* ..
|
||||
*
|
||||
* @param threadpool the threadpool to wait for
|
||||
* @return nothing
|
||||
*/
|
||||
void thpool_wait(threadpool);
|
||||
|
||||
|
||||
/**
|
||||
* @brief Pauses all threads immediately
|
||||
*
|
||||
* The threads will be paused no matter if they are idle or working.
|
||||
* The threads return to their previous states once thpool_resume
|
||||
* is called.
|
||||
*
|
||||
* While the thread is being paused, new work can be added.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* threadpool thpool = thpool_init(4);
|
||||
* thpool_pause(thpool);
|
||||
* ..
|
||||
* // Add a bunch of work
|
||||
* ..
|
||||
* thpool_resume(thpool); // Let the threads start their magic
|
||||
*
|
||||
* @param threadpool the threadpool where the threads should be paused
|
||||
* @return nothing
|
||||
*/
|
||||
void thpool_pause(threadpool);
|
||||
|
||||
|
||||
/**
|
||||
* @brief Unpauses all threads if they are paused
|
||||
*
|
||||
* @example
|
||||
* ..
|
||||
* thpool_pause(thpool);
|
||||
* sleep(10); // Delay execution 10 seconds
|
||||
* thpool_resume(thpool);
|
||||
* ..
|
||||
*
|
||||
* @param threadpool the threadpool where the threads should be unpaused
|
||||
* @return nothing
|
||||
*/
|
||||
void thpool_resume(threadpool);
|
||||
|
||||
|
||||
/**
|
||||
* @brief Destroy the threadpool
|
||||
*
|
||||
* This will wait for the currently active threads to finish and then 'kill'
|
||||
* the whole threadpool to free up memory.
|
||||
*
|
||||
* @example
|
||||
* int main() {
|
||||
* threadpool thpool1 = thpool_init(2);
|
||||
* threadpool thpool2 = thpool_init(2);
|
||||
* ..
|
||||
* thpool_destroy(thpool1);
|
||||
* ..
|
||||
* return 0;
|
||||
* }
|
||||
*
|
||||
* @param threadpool the threadpool to destroy
|
||||
* @return nothing
|
||||
*/
|
||||
void thpool_destroy(threadpool);
|
||||
|
||||
|
||||
/**
|
||||
* @brief Show currently working threads
|
||||
*
|
||||
* Working threads are the threads that are performing work (not idle).
|
||||
*
|
||||
* @example
|
||||
* int main() {
|
||||
* threadpool thpool1 = thpool_init(2);
|
||||
* threadpool thpool2 = thpool_init(2);
|
||||
* ..
|
||||
* printf("Working threads: %d\n", thpool_num_threads_working(thpool1));
|
||||
* ..
|
||||
* return 0;
|
||||
* }
|
||||
*
|
||||
* @param threadpool the threadpool of interest
|
||||
* @return integer number of threads working
|
||||
*/
|
||||
int thpool_num_threads_working(threadpool);
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
216
CONTRIBUTING.md
216
CONTRIBUTING.md
@@ -1,143 +1,219 @@
|
||||
# Contributing
|
||||
|
||||
## Compiling PHP
|
||||
|
||||
### With Docker (Linux)
|
||||
|
||||
Build the dev Docker image:
|
||||
|
||||
docker build -t frankenphp-dev -f dev.Dockerfile .
|
||||
docker run --cap-add=SYS_PTRACE --security-opt seccomp=unconfined -p 8080:8080 -p 443:443 -v $PWD:/go/src/app -it frankenphp-dev
|
||||
```console
|
||||
docker build -t frankenphp-dev -f dev.Dockerfile .
|
||||
docker run --cap-add=SYS_PTRACE --security-opt seccomp=unconfined -p 8080:8080 -p 443:443 -p 443:443/udp -v $PWD:/go/src/app -it frankenphp-dev
|
||||
```
|
||||
|
||||
The image contains the usual development tools (Go, GDB, Valgrind, Neovim...).
|
||||
The image contains the usual development tools (Go, GDB, Valgrind, Neovim...) and uses the following php setting locations
|
||||
|
||||
- php.ini: `/etc/frankenphp/php.ini` A php.ini file with development presets is provided by default.
|
||||
- additional configuration files: `/etc/frankenphp/php.d/*.ini`
|
||||
- php extensions: `/usr/lib/frankenphp/modules/`
|
||||
|
||||
If your docker version is lower than 23.0, the build will fail due to dockerignore [pattern issue](https://github.com/moby/moby/pull/42676). Add directories to `.dockerignore`.
|
||||
|
||||
```patch
|
||||
!testdata/*.php
|
||||
!testdata/*.txt
|
||||
+!caddy
|
||||
+!internal
|
||||
```
|
||||
|
||||
### Without Docker (Linux and macOS)
|
||||
|
||||
[Follow the instructions to compile from sources](docs/compile.md) and pass the `--debug` configuration flag.
|
||||
[Follow the instructions to compile from sources](https://frankenphp.dev/docs/compile/) and pass the `--debug` configuration flag.
|
||||
|
||||
## Running the test suite
|
||||
|
||||
go test -race -v ./...
|
||||
```console
|
||||
go test -tags watcher -race -v ./...
|
||||
```
|
||||
|
||||
## Caddy module
|
||||
|
||||
Build Caddy with the FrankenPHP Caddy module:
|
||||
|
||||
cd caddy/frankenphp/
|
||||
go build
|
||||
cd ../../
|
||||
```console
|
||||
cd caddy/frankenphp/
|
||||
go build -tags watcher,brotli,nobadger,nomysql,nopgx
|
||||
cd ../../
|
||||
```
|
||||
|
||||
Run the Caddy with the FrankenPHP Caddy module:
|
||||
|
||||
cd testdata/
|
||||
../caddy/frankenphp/frankenphp run
|
||||
```console
|
||||
cd testdata/
|
||||
../caddy/frankenphp/frankenphp run
|
||||
```
|
||||
|
||||
The server is listening on `127.0.0.1:8080`:
|
||||
The server is listening on `127.0.0.1:80`:
|
||||
|
||||
curl -vk https://localhost/phpinfo.php
|
||||
> [!NOTE]
|
||||
> if you are using Docker, you will have to either bind container port 80 or execute from inside the container
|
||||
|
||||
```console
|
||||
curl -vk http://127.0.0.1/phpinfo.php
|
||||
```
|
||||
|
||||
## Minimal test server
|
||||
|
||||
Build the minimal test server:
|
||||
|
||||
cd internal/testserver/
|
||||
go build
|
||||
cd ../../
|
||||
```console
|
||||
cd internal/testserver/
|
||||
go build
|
||||
cd ../../
|
||||
```
|
||||
|
||||
Run the test server:
|
||||
|
||||
cd testdata/
|
||||
../internal/testserver/testserver
|
||||
```console
|
||||
cd testdata/
|
||||
../internal/testserver/testserver
|
||||
```
|
||||
|
||||
The server is listening on `127.0.0.1:8080`:
|
||||
|
||||
curl -v http://127.0.0.1:8080/phpinfo.php
|
||||
```console
|
||||
curl -v http://127.0.0.1:8080/phpinfo.php
|
||||
```
|
||||
|
||||
# Building Docker Images Locally
|
||||
## Building Docker Images Locally
|
||||
|
||||
Print bake plan:
|
||||
|
||||
```
|
||||
```console
|
||||
docker buildx bake -f docker-bake.hcl --print
|
||||
```
|
||||
|
||||
Build FrankenPHP images for amd64 locally:
|
||||
|
||||
```
|
||||
```console
|
||||
docker buildx bake -f docker-bake.hcl --pull --load --set "*.platform=linux/amd64"
|
||||
```
|
||||
|
||||
Build FrankenPHP images for arm64 locally:
|
||||
|
||||
```
|
||||
```console
|
||||
docker buildx bake -f docker-bake.hcl --pull --load --set "*.platform=linux/arm64"
|
||||
```
|
||||
|
||||
Build FrankenPHP images from scratch for arm64 & amd64 and push to Docker Hub:
|
||||
|
||||
```
|
||||
```console
|
||||
docker buildx bake -f docker-bake.hcl --pull --no-cache --push
|
||||
```
|
||||
|
||||
## Debugging Segmentation Faults With Static Builds
|
||||
|
||||
1. Download the debug version of the FrankenPHP binary from GitHub or create your custom static build including debug symbols:
|
||||
|
||||
```console
|
||||
docker buildx bake \
|
||||
--load \
|
||||
--set static-builder.args.DEBUG_SYMBOLS=1 \
|
||||
--set "static-builder.platform=linux/amd64" \
|
||||
static-builder
|
||||
docker cp $(docker create --name static-builder-musl dunglas/frankenphp:static-builder-musl):/go/src/app/dist/frankenphp-linux-$(uname -m) frankenphp
|
||||
```
|
||||
|
||||
2. Replace your current version of `frankenphp` by the debug FrankenPHP executable
|
||||
3. Start FrankenPHP as usual (alternatively, you can directly start FrankenPHP with GDB: `gdb --args frankenphp run`)
|
||||
4. Attach to the process with GDB:
|
||||
|
||||
```console
|
||||
gdb -p `pidof frankenphp`
|
||||
```
|
||||
|
||||
5. If necessary, type `continue` in the GDB shell
|
||||
6. Make FrankenPHP crash
|
||||
7. Type `bt` in the GDB shell
|
||||
8. Copy the output
|
||||
|
||||
## Debugging Segmentation Faults in GitHub Actions
|
||||
|
||||
1. Open `.github/workflows/tests.yml`
|
||||
2. Enable PHP debug symbols
|
||||
```patch
|
||||
- uses: shivammathur/setup-php@v2
|
||||
# ...
|
||||
env:
|
||||
phpts: ts
|
||||
+ debug: true
|
||||
```
|
||||
|
||||
```patch
|
||||
- uses: shivammathur/setup-php@v2
|
||||
# ...
|
||||
env:
|
||||
phpts: ts
|
||||
+ debug: true
|
||||
```
|
||||
|
||||
3. Enable `tmate` to connect to the container
|
||||
```patch
|
||||
- name: Set include flags
|
||||
run: echo "CGO_CFLAGS=$(php-config --includes)" >> "$GITHUB_ENV"
|
||||
+ - run: |
|
||||
sudo apt install gdb
|
||||
+ mkdir -p /home/runner/.config/gdb/
|
||||
+ printf "set auto-load safe-path /\nhandle SIG34 nostop noprint pass" > /home/runner/.config/gdb/gdbinit
|
||||
+ - uses: mxschmitt/action-tmate@v3
|
||||
+ env:
|
||||
+ GOFLAGS: "-w -gcflags=all=-N -gcflags=all=-l"
|
||||
```
|
||||
4. Open `frankenphp.go`
|
||||
5. Enable `cgosymbolizer`
|
||||
```patch
|
||||
- //_ "github.com/ianlancetaylor/cgosymbolizer"
|
||||
+ _ "github.com/ianlancetaylor/cgosymbolizer"
|
||||
```
|
||||
6. Download the module: `go get`
|
||||
7. In the container, you can use GDB and the like:
|
||||
```sh
|
||||
sudo apt install gdb
|
||||
mkdir -p /home/runner/.config/gdb/
|
||||
go test -c -ldflags=-w
|
||||
gdb --args ./frankenphp.test -test.run ^MyTest$
|
||||
```
|
||||
8. When the bug is fixed, revert all these changes
|
||||
|
||||
```patch
|
||||
- name: Set CGO flags
|
||||
run: echo "CGO_CFLAGS=$(php-config --includes)" >> "$GITHUB_ENV"
|
||||
+ - run: |
|
||||
+ sudo apt install gdb
|
||||
+ mkdir -p /home/runner/.config/gdb/
|
||||
+ printf "set auto-load safe-path /\nhandle SIG34 nostop noprint pass" > /home/runner/.config/gdb/gdbinit
|
||||
+ - uses: mxschmitt/action-tmate@v3
|
||||
```
|
||||
|
||||
4. Connect to the container
|
||||
5. Open `frankenphp.go`
|
||||
6. Enable `cgosymbolizer`
|
||||
|
||||
```patch
|
||||
- //_ "github.com/ianlancetaylor/cgosymbolizer"
|
||||
+ _ "github.com/ianlancetaylor/cgosymbolizer"
|
||||
```
|
||||
|
||||
7. Download the module: `go get`
|
||||
8. In the container, you can use GDB and the like:
|
||||
|
||||
```console
|
||||
go test -tags watcher -c -ldflags=-w
|
||||
gdb --args frankenphp.dev.test -test.run ^MyTest$
|
||||
```
|
||||
|
||||
9. When the bug is fixed, revert all these changes
|
||||
|
||||
## Misc Dev Resources
|
||||
|
||||
* [PHP embedding in uWSGI](https://github.com/unbit/uwsgi/blob/master/plugins/php/php_plugin.c)
|
||||
* [PHP embedding in NGINX Unit](https://github.com/nginx/unit/blob/master/src/nxt_php_sapi.c)
|
||||
* [PHP embedding in Go (go-php)](https://github.com/deuill/go-php)
|
||||
* [PHP embedding in Go (GoEmPHP)](https://github.com/mikespook/goemphp)
|
||||
* [PHP embedding in C++](https://gist.github.com/paresy/3cbd4c6a469511ac7479aa0e7c42fea7)
|
||||
* [Extending and Embedding PHP by Sara Golemon](https://books.google.fr/books?id=zMbGvK17_tYC&pg=PA254&lpg=PA254#v=onepage&q&f=false)
|
||||
* [What the heck is TSRMLS_CC, anyway?](http://blog.golemon.com/2006/06/what-heck-is-tsrmlscc-anyway.html)
|
||||
* [PHP embedding on Mac](https://gist.github.com/jonnywang/61427ffc0e8dde74fff40f479d147db4)
|
||||
* [SDL bindings](https://pkg.go.dev/github.com/veandco/go-sdl2@v0.4.21/sdl#Main)
|
||||
- [PHP embedding in uWSGI](https://github.com/unbit/uwsgi/blob/master/plugins/php/php_plugin.c)
|
||||
- [PHP embedding in NGINX Unit](https://github.com/nginx/unit/blob/master/src/nxt_php_sapi.c)
|
||||
- [PHP embedding in Go (go-php)](https://github.com/deuill/go-php)
|
||||
- [PHP embedding in Go (GoEmPHP)](https://github.com/mikespook/goemphp)
|
||||
- [PHP embedding in C++](https://gist.github.com/paresy/3cbd4c6a469511ac7479aa0e7c42fea7)
|
||||
- [Extending and Embedding PHP by Sara Golemon](https://books.google.fr/books?id=zMbGvK17_tYC&pg=PA254&lpg=PA254#v=onepage&q&f=false)
|
||||
- [What the heck is TSRMLS_CC, anyway?](http://blog.golemon.com/2006/06/what-heck-is-tsrmlscc-anyway.html)
|
||||
- [SDL bindings](https://pkg.go.dev/github.com/veandco/go-sdl2@v0.4.21/sdl#Main)
|
||||
|
||||
## Docker-Related Resources
|
||||
|
||||
* [Bake file definition](https://docs.docker.com/build/customize/bake/file-definition/)
|
||||
* [docker buildx build](https://docs.docker.com/engine/reference/commandline/buildx_build/)
|
||||
|
||||
- [Bake file definition](https://docs.docker.com/build/customize/bake/file-definition/)
|
||||
- [docker buildx build](https://docs.docker.com/engine/reference/commandline/buildx_build/)
|
||||
|
||||
## Useful Command
|
||||
|
||||
```
|
||||
```console
|
||||
apk add strace util-linux gdb
|
||||
strace -e 'trace=!futex,epoll_ctl,epoll_pwait,tgkill,rt_sigreturn' -p 1
|
||||
```
|
||||
```
|
||||
|
||||
## Translating the Documentation
|
||||
|
||||
To translate the documentation and the site in a new language,
|
||||
follow these steps:
|
||||
|
||||
1. Create a new directory named with the language's 2-character ISO code in this repository's `docs/` directory
|
||||
2. Copy all the `.md` files in the root of the `docs/` directory into the new directory (always use the English version as source for translation, as it's always up to date)
|
||||
3. Copy the `README.md` and `CONTRIBUTING.md` files from the root directory to the new directory
|
||||
4. Translate the content of the files, but don't change the filenames, also don't translate strings starting with `> [!` (it's special markup for GitHub)
|
||||
5. Create a Pull Request with the translations
|
||||
6. In the [site repository](https://github.com/dunglas/frankenphp-website), copy and translate the translation files in the `content/`, `data/` and `i18n/` directories
|
||||
7. Translate the values in the created YAML file
|
||||
8. Open a Pull Request on the site repository
|
||||
|
||||
124
Dockerfile
124
Dockerfile
@@ -1,34 +1,42 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
#checkov:skip=CKV_DOCKER_2
|
||||
#checkov:skip=CKV_DOCKER_3
|
||||
#checkov:skip=CKV_DOCKER_7
|
||||
FROM php-base AS common
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-get -y --no-install-recommends install \
|
||||
mailcap \
|
||||
libcap2-bin \
|
||||
&& \
|
||||
apt-get clean && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
apt-get -y --no-install-recommends install \
|
||||
mailcap \
|
||||
libcap2-bin \
|
||||
&& \
|
||||
apt-get clean && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
RUN set -eux; \
|
||||
mkdir -p \
|
||||
/app/public \
|
||||
/config/caddy \
|
||||
/data/caddy \
|
||||
/etc/caddy; \
|
||||
/etc/caddy \
|
||||
/etc/frankenphp; \
|
||||
sed -i 's/php/frankenphp run/g' /usr/local/bin/docker-php-entrypoint; \
|
||||
echo '<?php phpinfo();' > /app/public/index.php
|
||||
|
||||
COPY --link caddy/frankenphp/Caddyfile /etc/caddy/Caddyfile
|
||||
COPY --from=mlocati/php-extension-installer /usr/bin/install-php-extensions /usr/local/bin/
|
||||
RUN ln /etc/caddy/Caddyfile /etc/frankenphp/Caddyfile && \
|
||||
curl -sSLf \
|
||||
-o /usr/local/bin/install-php-extensions \
|
||||
https://github.com/mlocati/docker-php-extension-installer/releases/latest/download/install-php-extensions && \
|
||||
chmod +x /usr/local/bin/install-php-extensions
|
||||
|
||||
CMD ["--config", "/etc/caddy/Caddyfile", "--adapter", "caddyfile"]
|
||||
HEALTHCHECK CMD curl -f https://localhost/healthz || exit 1
|
||||
CMD ["--config", "/etc/frankenphp/Caddyfile", "--adapter", "caddyfile"]
|
||||
HEALTHCHECK CMD curl -f http://localhost:2019/metrics || exit 1
|
||||
|
||||
# See https://caddyserver.com/docs/conventions#file-locations for details
|
||||
ENV XDG_CONFIG_HOME /config
|
||||
ENV XDG_DATA_HOME /data
|
||||
ENV XDG_CONFIG_HOME=/config
|
||||
ENV XDG_DATA_HOME=/data
|
||||
|
||||
EXPOSE 80
|
||||
EXPOSE 443
|
||||
@@ -46,58 +54,86 @@ LABEL org.opencontainers.image.vendor="Kévin Dunglas"
|
||||
FROM common AS builder
|
||||
|
||||
ARG FRANKENPHP_VERSION='dev'
|
||||
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
|
||||
|
||||
COPY --from=golang-base /usr/local/go /usr/local/go
|
||||
|
||||
ENV PATH /usr/local/go/bin:$PATH
|
||||
ENV PATH=/usr/local/go/bin:$PATH
|
||||
ENV GOTOOLCHAIN=local
|
||||
|
||||
# This is required to link the FrankenPHP binary to the PHP binary
|
||||
RUN apt-get update && \
|
||||
apt-get -y --no-install-recommends install \
|
||||
libargon2-dev \
|
||||
libcurl4-openssl-dev \
|
||||
libonig-dev \
|
||||
libreadline-dev \
|
||||
libsodium-dev \
|
||||
libsqlite3-dev \
|
||||
libssl-dev \
|
||||
libxml2-dev \
|
||||
zlib1g-dev \
|
||||
&& \
|
||||
apt-get clean
|
||||
apt-get -y --no-install-recommends install \
|
||||
cmake \
|
||||
git \
|
||||
libargon2-dev \
|
||||
libbrotli-dev \
|
||||
libcurl4-openssl-dev \
|
||||
libonig-dev \
|
||||
libreadline-dev \
|
||||
libsodium-dev \
|
||||
libsqlite3-dev \
|
||||
libssl-dev \
|
||||
libxml2-dev \
|
||||
zlib1g-dev \
|
||||
&& \
|
||||
apt-get clean
|
||||
|
||||
# Install e-dant/watcher (necessary for file watching)
|
||||
WORKDIR /usr/local/src/watcher
|
||||
RUN curl -s https://api.github.com/repos/e-dant/watcher/releases/latest | \
|
||||
grep tarball_url | \
|
||||
awk '{ print $2 }' | \
|
||||
sed 's/,$//' | \
|
||||
sed 's/"//g' | \
|
||||
xargs curl -L | \
|
||||
tar xz --strip-components 1 && \
|
||||
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release && \
|
||||
cmake --build build && \
|
||||
cmake --install build && \
|
||||
ldconfig
|
||||
|
||||
WORKDIR /go/src/app
|
||||
|
||||
COPY --link go.mod go.sum ./
|
||||
RUN go mod graph | awk '{if ($1 !~ "@") print $2}' | xargs go get
|
||||
RUN go mod download
|
||||
|
||||
RUN mkdir caddy && cd caddy
|
||||
COPY --link caddy/go.mod caddy/go.sum ./caddy/
|
||||
WORKDIR /go/src/app/caddy
|
||||
COPY --link caddy/go.mod caddy/go.sum ./
|
||||
RUN go mod download
|
||||
|
||||
RUN cd caddy && \
|
||||
go mod graph | awk '{if ($1 !~ "@") print $2}' | xargs go get
|
||||
WORKDIR /go/src/app
|
||||
COPY --link . ./
|
||||
|
||||
COPY --link *.* ./
|
||||
COPY --link caddy caddy
|
||||
COPY --link C-Thread-Pool C-Thread-Pool
|
||||
COPY --link internal internal
|
||||
COPY --link testdata testdata
|
||||
# See https://github.com/docker-library/php/blob/master/8.3/bookworm/zts/Dockerfile#L57-L59 for PHP values
|
||||
ENV CGO_CFLAGS="-DFRANKENPHP_VERSION=$FRANKENPHP_VERSION $PHP_CFLAGS"
|
||||
ENV CGO_CPPFLAGS=$PHP_CPPFLAGS
|
||||
ENV CGO_LDFLAGS="-L/usr/local/lib -lssl -lcrypto -lreadline -largon2 -lcurl -lonig -lz $PHP_LDFLAGS"
|
||||
|
||||
# todo: automate this?
|
||||
# see https://github.com/docker-library/php/blob/master/8.2/bookworm/zts/Dockerfile#L57-L59 for PHP values
|
||||
ENV CGO_LDFLAGS="-lssl -lcrypto -lreadline -largon2 -lcurl -lonig -lz $PHP_LDFLAGS" CGO_CFLAGS="-DFRANKENPHP_VERSION=$FRANKENPHP_VERSION $PHP_CFLAGS" CGO_CPPFLAGS=$PHP_CPPFLAGS
|
||||
RUN echo $CGO_LDFLAGS
|
||||
|
||||
RUN cd caddy/frankenphp && \
|
||||
GOBIN=/usr/local/bin go install -ldflags "-X 'github.com/caddyserver/caddy/v2.CustomVersion=FrankenPHP $FRANKENPHP_VERSION PHP $PHP_VERSION Caddy'" && \
|
||||
setcap cap_net_bind_service=+ep /usr/local/bin/frankenphp && \
|
||||
cp Caddyfile /etc/caddy/Caddyfile && \
|
||||
frankenphp version
|
||||
WORKDIR /go/src/app/caddy/frankenphp
|
||||
RUN GOBIN=/usr/local/bin go install -tags 'nobadger,nomysql,nopgx' -ldflags "-w -s -X 'github.com/caddyserver/caddy/v2.CustomVersion=FrankenPHP $FRANKENPHP_VERSION PHP $PHP_VERSION Caddy'" -buildvcs=true && \
|
||||
setcap cap_net_bind_service=+ep /usr/local/bin/frankenphp && \
|
||||
cp Caddyfile /etc/frankenphp/Caddyfile && \
|
||||
frankenphp version && \
|
||||
frankenphp build-info
|
||||
|
||||
WORKDIR /go/src/app
|
||||
|
||||
|
||||
FROM common AS runner
|
||||
|
||||
ENV GODEBUG=cgocheck=0
|
||||
|
||||
# copy watcher shared library
|
||||
COPY --from=builder /usr/local/lib/libwatcher* /usr/local/lib/
|
||||
# fix for the file watcher on arm
|
||||
RUN apt-get install -y --no-install-recommends libstdc++6 && \
|
||||
apt-get clean && \
|
||||
ldconfig
|
||||
|
||||
COPY --from=builder /usr/local/bin/frankenphp /usr/local/bin/frankenphp
|
||||
RUN setcap cap_net_bind_service=+ep /usr/local/bin/frankenphp && \
|
||||
frankenphp version
|
||||
frankenphp version && \
|
||||
frankenphp build-info
|
||||
|
||||
104
README.md
104
README.md
@@ -4,47 +4,107 @@
|
||||
|
||||
FrankenPHP is a modern application server for PHP built on top of the [Caddy](https://caddyserver.com/) web server.
|
||||
|
||||
FrankenPHP gives superpowers to your PHP apps thanks to its stunning features: [*Early Hints*](docs/early-hints.md), [worker mode](docs/worker.md), [real-time capabilities](docs/mercure.md), automatic HTTPS, HTTP/2, and HTTP/3 support...
|
||||
FrankenPHP gives superpowers to your PHP apps thanks to its stunning features: [_Early Hints_](https://frankenphp.dev/docs/early-hints/), [worker mode](https://frankenphp.dev/docs/worker/), [real-time capabilities](https://frankenphp.dev/docs/mercure/), automatic HTTPS, HTTP/2, and HTTP/3 support...
|
||||
|
||||
FrankenPHP works with any PHP app and makes your Symfony projects faster than ever thanks to provided integration with the worker mode (Laravel Octane support coming).
|
||||
FrankenPHP works with any PHP app and makes your Laravel and Symfony projects faster than ever thanks to their official integrations with the worker mode.
|
||||
|
||||
FrankenPHP can also be used as a standalone Go library to embed PHP in any app using `net/http`.
|
||||
|
||||
[**Learn more** on *frankenphp.dev*](https://frankenphp.dev) and in this slide deck:
|
||||
[**Learn more** on _frankenphp.dev_](https://frankenphp.dev) and in this slide deck:
|
||||
|
||||
<a href="https://dunglas.dev/2022/10/frankenphp-the-modern-php-app-server-written-in-go/"><img src="https://dunglas.dev/wp-content/uploads/2022/10/frankenphp.png" alt="Slides" width="600"></a>
|
||||
|
||||
## Getting Started
|
||||
|
||||
### Standalone Binary
|
||||
|
||||
We provide static FrankenPHP binaries for Linux and macOS
|
||||
containing [PHP 8.4](https://www.php.net/releases/8.4/en.php) and most popular PHP extensions.
|
||||
|
||||
On Windows, use [WSL](https://learn.microsoft.com/windows/wsl/) to run FrankenPHP.
|
||||
|
||||
[Download FrankenPHP](https://github.com/dunglas/frankenphp/releases) or copy this line into your
|
||||
terminal to automatically install the version appropriate for your platform:
|
||||
|
||||
```console
|
||||
docker run -v $PWD:/app/public \
|
||||
-p 80:80 -p 443:443 \
|
||||
curl https://frankenphp.dev/install.sh | sh
|
||||
mv frankenphp /usr/local/bin/
|
||||
```
|
||||
|
||||
To serve the content of the current directory, run:
|
||||
|
||||
```console
|
||||
frankenphp php-server
|
||||
```
|
||||
|
||||
You can also run command-line scripts with:
|
||||
|
||||
```console
|
||||
frankenphp php-cli /path/to/your/script.php
|
||||
```
|
||||
|
||||
### Docker
|
||||
|
||||
Alternatively, [Docker images](https://frankenphp.dev/docs/docker/) are available:
|
||||
|
||||
```console
|
||||
docker run -v .:/app/public \
|
||||
-p 80:80 -p 443:443 -p 443:443/udp \
|
||||
dunglas/frankenphp
|
||||
```
|
||||
|
||||
Go to `https://localhost`, and enjoy!
|
||||
|
||||
If you prefer not to use Docker, we provide standalone FrankenPHP binaries for Linux and macOS
|
||||
containing [PHP 8.2](https://www.php.net/releases/8.2/en.php) and most popular PHP extensions: [Download FrankenPHP](https://github.com/dunglas/frankenphp/releases)
|
||||
> [!TIP]
|
||||
>
|
||||
> Do not attempt to use `https://127.0.0.1`. Use `https://localhost` and accept the self-signed certificate.
|
||||
> Use the [`SERVER_NAME` environment variable](docs/config.md#environment-variables) to change the domain to use.
|
||||
|
||||
> Note: do not attempt to use `https://127.0.0.1`. Use `localhost` and accept the self-signed certificate. Caddy has an automatic TLS handling that auto-trusts some local-based hostnames like `localhost`, but it does not apply to IP addresses. More details [on Caddy's "automatic https" docs](https://caddyserver.com/docs/automatic-https#hostname-requirements).
|
||||
### Homebrew
|
||||
|
||||
FrankenPHP is also available as a [Homebrew](https://brew.sh) package for macOS and Linux.
|
||||
|
||||
To install it:
|
||||
|
||||
```console
|
||||
brew install dunglas/frankenphp/frankenphp
|
||||
```
|
||||
|
||||
To serve the content of the current directory, run:
|
||||
|
||||
```console
|
||||
frankenphp php-server
|
||||
```
|
||||
|
||||
## Docs
|
||||
|
||||
* [The worker mode](docs/worker.md)
|
||||
* [Early Hints support (103 HTTP status code)](docs/early-hints.md)
|
||||
* [Real-time](docs/mercure.md)
|
||||
* [Configuration](docs/config.md)
|
||||
* [Docker images](docs/docker.md)
|
||||
* [Compile from sources](docs/compile.md)
|
||||
* [Create static binaries](docs/static.md)
|
||||
* [Demo app (Symfony) and benchmarks](https://github.com/dunglas/frankenphp-demo)
|
||||
* [Go library documentation](https://pkg.go.dev/github.com/dunglas/frankenphp)
|
||||
* [Contributing and debugging](CONTRIBUTING.md)
|
||||
- [Classic mode](https://frankenphp.dev/docs/classic/)
|
||||
- [Worker mode](https://frankenphp.dev/docs/worker/)
|
||||
- [Early Hints support (103 HTTP status code)](https://frankenphp.dev/docs/early-hints/)
|
||||
- [Real-time](https://frankenphp.dev/docs/mercure/)
|
||||
- [Efficiently Serving Large Static Files](https://frankenphp.dev/docs/x-sendfile/)
|
||||
- [Configuration](https://frankenphp.dev/docs/config/)
|
||||
- [Docker images](https://frankenphp.dev/docs/docker/)
|
||||
- [Deploy in production](https://frankenphp.dev/docs/production/)
|
||||
- [Performance optimization](https://frankenphp.dev/docs/performance/)
|
||||
- [Create **standalone**, self-executable PHP apps](https://frankenphp.dev/docs/embed/)
|
||||
- [Create static binaries](https://frankenphp.dev/docs/static/)
|
||||
- [Compile from sources](https://frankenphp.dev/docs/compile/)
|
||||
- [Monitoring FrankenPHP](https://frankenphp.dev/docs/metrics/)
|
||||
- [Laravel integration](https://frankenphp.dev/docs/laravel/)
|
||||
- [Known issues](https://frankenphp.dev/docs/known-issues/)
|
||||
- [Demo app (Symfony) and benchmarks](https://github.com/dunglas/frankenphp-demo)
|
||||
- [Go library documentation](https://pkg.go.dev/frankenphp.dev)
|
||||
- [Contributing and debugging](https://frankenphp.dev/docs/contributing/)
|
||||
|
||||
## Examples and Skeletons
|
||||
|
||||
* [Symfony apps](https://github.com/dunglas/frankenphp-demo)
|
||||
* [WordPress](https://github.com/dunglas/frankenphp-wordpress)
|
||||
* [Drupal](https://github.com/dunglas/frankenphp-drupal)
|
||||
* [Sulu](https://sulu.io/blog/running-sulu-with-frankenphp)
|
||||
- [Symfony](https://github.com/dunglas/symfony-docker)
|
||||
- [API Platform](https://api-platform.com/docs/symfony)
|
||||
- [Laravel](https://frankenphp.dev/docs/laravel/)
|
||||
- [Sulu](https://sulu.io/blog/running-sulu-with-frankenphp)
|
||||
- [WordPress](https://github.com/StephenMiracle/frankenwp)
|
||||
- [Drupal](https://github.com/dunglas/frankenphp-drupal)
|
||||
- [Joomla](https://github.com/alexandreelise/frankenphp-joomla)
|
||||
- [TYPO3](https://github.com/ochorocho/franken-typo3)
|
||||
- [Magento2](https://github.com/ekino/frankenphp-magento2)
|
||||
|
||||
18
SECURITY.md
Normal file
18
SECURITY.md
Normal file
@@ -0,0 +1,18 @@
|
||||
# Security Policy
|
||||
|
||||
## Supported Versions
|
||||
|
||||
Only the latest version is supported.
|
||||
Please ensure that you're always using the latest release.
|
||||
|
||||
Binaries and Docker images are rebuilt nightly using the latest versions of dependencies.
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
If you believe you have discovered a security issue directly affecting FrankenPHP,
|
||||
please do **NOT** report it publicly.
|
||||
|
||||
Please write a detailed vulnerability report and send it [through GitHub](https://github.com/dunglas/frankenphp/security/advisories/new) or to [kevin+frankenphp-security@dunglas.dev](mailto:kevin+frankenphp-security@dunglas.dev?subject=Security%20issue%20affecting%20FrankenPHP).
|
||||
|
||||
Only vulnerabilities directly affecting FrankenPHP should be reported to this project.
|
||||
Flaws affecting components used by FrankenPHP (PHP, Caddy, Go...) or using FrankenPHP (Laravel Octane, PHP Runtime...) should be reported to the relevant projects.
|
||||
@@ -1,6 +1,11 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
#checkov:skip=CKV_DOCKER_2
|
||||
#checkov:skip=CKV_DOCKER_3
|
||||
#checkov:skip=CKV_DOCKER_7
|
||||
FROM php-base AS common
|
||||
|
||||
ARG TARGETARCH
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
RUN apk add --no-cache \
|
||||
@@ -13,19 +18,25 @@ RUN set -eux; \
|
||||
/app/public \
|
||||
/config/caddy \
|
||||
/data/caddy \
|
||||
/etc/caddy; \
|
||||
/etc/caddy \
|
||||
/etc/frankenphp; \
|
||||
sed -i 's/php/frankenphp run/g' /usr/local/bin/docker-php-entrypoint; \
|
||||
echo '<?php phpinfo();' > /app/public/index.php
|
||||
|
||||
COPY --link caddy/frankenphp/Caddyfile /etc/caddy/Caddyfile
|
||||
COPY --from=mlocati/php-extension-installer /usr/bin/install-php-extensions /usr/local/bin/
|
||||
|
||||
CMD ["--config", "/etc/caddy/Caddyfile", "--adapter", "caddyfile"]
|
||||
HEALTHCHECK CMD curl -f https://localhost/healthz || exit 1
|
||||
RUN ln /etc/caddy/Caddyfile /etc/frankenphp/Caddyfile && \
|
||||
curl -sSLf \
|
||||
-o /usr/local/bin/install-php-extensions \
|
||||
https://github.com/mlocati/docker-php-extension-installer/releases/latest/download/install-php-extensions && \
|
||||
chmod +x /usr/local/bin/install-php-extensions
|
||||
|
||||
CMD ["--config", "/etc/frankenphp/Caddyfile", "--adapter", "caddyfile"]
|
||||
HEALTHCHECK CMD curl -f http://localhost:2019/metrics || exit 1
|
||||
|
||||
# See https://caddyserver.com/docs/conventions#file-locations for details
|
||||
ENV XDG_CONFIG_HOME /config
|
||||
ENV XDG_DATA_HOME /data
|
||||
ENV XDG_CONFIG_HOME=/config
|
||||
ENV XDG_DATA_HOME=/data
|
||||
|
||||
EXPOSE 80
|
||||
EXPOSE 443
|
||||
@@ -43,55 +54,88 @@ LABEL org.opencontainers.image.vendor="Kévin Dunglas"
|
||||
FROM common AS builder
|
||||
|
||||
ARG FRANKENPHP_VERSION='dev'
|
||||
ARG NO_COMPRESS=''
|
||||
SHELL ["/bin/ash", "-eo", "pipefail", "-c"]
|
||||
|
||||
COPY --link --from=golang-base /usr/local/go /usr/local/go
|
||||
|
||||
ENV PATH /usr/local/go/bin:$PATH
|
||||
ENV PATH=/usr/local/go/bin:$PATH
|
||||
ENV GOTOOLCHAIN=local
|
||||
|
||||
# hadolint ignore=SC2086
|
||||
RUN apk add --no-cache --virtual .build-deps \
|
||||
$PHPIZE_DEPS \
|
||||
argon2-dev \
|
||||
# Needed for the custom Go build
|
||||
bash \
|
||||
brotli-dev \
|
||||
coreutils \
|
||||
curl-dev \
|
||||
# Needed for the custom Go build
|
||||
git \
|
||||
gnu-libiconv-dev \
|
||||
libsodium-dev \
|
||||
# Needed for the file watcher \
|
||||
cmake \
|
||||
libstdc++ \
|
||||
libxml2-dev \
|
||||
linux-headers \
|
||||
oniguruma-dev \
|
||||
openssl-dev \
|
||||
readline-dev \
|
||||
sqlite-dev
|
||||
sqlite-dev \
|
||||
upx
|
||||
|
||||
# Install e-dant/watcher (necessary for file watching)
|
||||
WORKDIR /usr/local/src/watcher
|
||||
RUN curl -s https://api.github.com/repos/e-dant/watcher/releases/latest | \
|
||||
grep tarball_url | \
|
||||
awk '{ print $2 }' | \
|
||||
sed 's/,$//' | \
|
||||
sed 's/"//g' | \
|
||||
xargs curl -L | \
|
||||
tar xz --strip-components 1 && \
|
||||
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release && \
|
||||
cmake --build build && \
|
||||
cmake --install build
|
||||
|
||||
WORKDIR /go/src/app
|
||||
|
||||
COPY --link go.mod go.sum ./
|
||||
RUN go mod graph | awk '{if ($1 !~ "@") print $2}' | xargs go get
|
||||
RUN go mod download
|
||||
|
||||
RUN mkdir caddy && cd caddy
|
||||
COPY caddy/go.mod caddy/go.sum ./caddy/
|
||||
WORKDIR /go/src/app/caddy
|
||||
COPY caddy/go.mod caddy/go.sum ./
|
||||
RUN go mod download
|
||||
|
||||
RUN cd caddy && go mod graph | awk '{if ($1 !~ "@") print $2}' | xargs go get
|
||||
WORKDIR /go/src/app
|
||||
COPY --link . ./
|
||||
|
||||
COPY --link *.* ./
|
||||
COPY --link caddy caddy
|
||||
COPY --link C-Thread-Pool C-Thread-Pool
|
||||
COPY --link internal internal
|
||||
COPY --link testdata testdata
|
||||
# See https://github.com/docker-library/php/blob/master/8.3/alpine3.20/zts/Dockerfile#L53-L55
|
||||
ENV CGO_CFLAGS="-DFRANKENPHP_VERSION=$FRANKENPHP_VERSION $PHP_CFLAGS"
|
||||
ENV CGO_CPPFLAGS=$PHP_CPPFLAGS
|
||||
ENV CGO_LDFLAGS="-lssl -lcrypto -lreadline -largon2 -lcurl -lonig -lz $PHP_LDFLAGS"
|
||||
|
||||
# todo: automate this?
|
||||
# see https://github.com/docker-library/php/blob/master/8.2/bookworm/zts/Dockerfile#L57-L59 for php values
|
||||
ENV CGO_LDFLAGS="-lssl -lcrypto -lreadline -largon2 -lcurl -lonig -lz $PHP_LDFLAGS" CGO_CFLAGS="-DFRANKENPHP_VERSION=$FRANKENPHP_VERSION $PHP_CFLAGS" CGO_CPPFLAGS=$PHP_CPPFLAGS
|
||||
|
||||
RUN cd caddy/frankenphp && \
|
||||
GOBIN=/usr/local/bin go install -ldflags "-X 'github.com/caddyserver/caddy/v2.CustomVersion=FrankenPHP $FRANKENPHP_VERSION PHP $PHP_VERSION Caddy'" && \
|
||||
WORKDIR /go/src/app/caddy/frankenphp
|
||||
RUN GOBIN=/usr/local/bin go install -tags 'nobadger,nomysql,nopgx' -ldflags "-w -s -extldflags '-Wl,-z,stack-size=0x80000' -X 'github.com/caddyserver/caddy/v2.CustomVersion=FrankenPHP $FRANKENPHP_VERSION PHP $PHP_VERSION Caddy'" -buildvcs=true && \
|
||||
setcap cap_net_bind_service=+ep /usr/local/bin/frankenphp && \
|
||||
frankenphp version
|
||||
([ -z "${NO_COMPRESS}" ] && upx --best /usr/local/bin/frankenphp || true) && \
|
||||
frankenphp version && \
|
||||
frankenphp build-info
|
||||
|
||||
WORKDIR /go/src/app
|
||||
|
||||
|
||||
FROM common AS runner
|
||||
|
||||
ENV GODEBUG=cgocheck=0
|
||||
|
||||
# copy watcher shared library (libgcc and libstdc++ are needed for the watcher)
|
||||
COPY --from=builder /usr/local/lib/libwatcher* /usr/local/lib/
|
||||
RUN apk add --no-cache libstdc++ && \
|
||||
ldconfig /usr/local/lib
|
||||
|
||||
COPY --from=builder /usr/local/bin/frankenphp /usr/local/bin/frankenphp
|
||||
RUN setcap cap_net_bind_service=+ep /usr/local/bin/frankenphp && \
|
||||
frankenphp version
|
||||
frankenphp version && \
|
||||
frankenphp build-info
|
||||
|
||||
0
app_checksum.txt
Normal file
0
app_checksum.txt
Normal file
51
backoff.go
Normal file
51
backoff.go
Normal file
@@ -0,0 +1,51 @@
|
||||
package frankenphp
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type exponentialBackoff struct {
|
||||
backoff time.Duration
|
||||
failureCount int
|
||||
mu sync.RWMutex
|
||||
maxBackoff time.Duration
|
||||
minBackoff time.Duration
|
||||
maxConsecutiveFailures int
|
||||
}
|
||||
|
||||
// recordSuccess resets the backoff and failureCount
|
||||
func (e *exponentialBackoff) recordSuccess() {
|
||||
e.mu.Lock()
|
||||
e.failureCount = 0
|
||||
e.backoff = e.minBackoff
|
||||
e.mu.Unlock()
|
||||
}
|
||||
|
||||
// recordFailure increments the failure count and increases the backoff, it returns true if maxConsecutiveFailures has been reached
|
||||
func (e *exponentialBackoff) recordFailure() bool {
|
||||
e.mu.Lock()
|
||||
e.failureCount += 1
|
||||
if e.backoff < e.minBackoff {
|
||||
e.backoff = e.minBackoff
|
||||
}
|
||||
|
||||
e.backoff = min(e.backoff*2, e.maxBackoff)
|
||||
|
||||
e.mu.Unlock()
|
||||
return e.failureCount >= e.maxConsecutiveFailures
|
||||
}
|
||||
|
||||
// wait sleeps for the backoff duration if failureCount is non-zero.
|
||||
// NOTE: this is not tested and should be kept 'obviously correct' (i.e., simple)
|
||||
func (e *exponentialBackoff) wait() {
|
||||
e.mu.RLock()
|
||||
if e.failureCount == 0 {
|
||||
e.mu.RUnlock()
|
||||
|
||||
return
|
||||
}
|
||||
e.mu.RUnlock()
|
||||
|
||||
time.Sleep(e.backoff)
|
||||
}
|
||||
41
backoff_test.go
Normal file
41
backoff_test.go
Normal file
@@ -0,0 +1,41 @@
|
||||
package frankenphp
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestExponentialBackoff_Reset(t *testing.T) {
|
||||
e := &exponentialBackoff{
|
||||
maxBackoff: 5 * time.Second,
|
||||
minBackoff: 500 * time.Millisecond,
|
||||
maxConsecutiveFailures: 3,
|
||||
}
|
||||
|
||||
assert.False(t, e.recordFailure())
|
||||
assert.False(t, e.recordFailure())
|
||||
e.recordSuccess()
|
||||
|
||||
e.mu.RLock()
|
||||
defer e.mu.RUnlock()
|
||||
assert.Equal(t, 0, e.failureCount, "expected failureCount to be reset to 0")
|
||||
assert.Equal(t, e.backoff, e.minBackoff, "expected backoff to be reset to minBackoff")
|
||||
}
|
||||
|
||||
func TestExponentialBackoff_Trigger(t *testing.T) {
|
||||
e := &exponentialBackoff{
|
||||
maxBackoff: 500 * 3 * time.Millisecond,
|
||||
minBackoff: 500 * time.Millisecond,
|
||||
maxConsecutiveFailures: 3,
|
||||
}
|
||||
|
||||
assert.False(t, e.recordFailure())
|
||||
assert.False(t, e.recordFailure())
|
||||
assert.True(t, e.recordFailure())
|
||||
|
||||
e.mu.RLock()
|
||||
defer e.mu.RUnlock()
|
||||
assert.Equal(t, e.failureCount, e.maxConsecutiveFailures, "expected failureCount to be maxConsecutiveFailures")
|
||||
assert.Equal(t, e.backoff, e.maxBackoff, "expected backoff to be maxBackoff")
|
||||
}
|
||||
119
build-packages.sh
Executable file
119
build-packages.sh
Executable file
@@ -0,0 +1,119 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -o errexit
|
||||
set -x
|
||||
|
||||
# Ensure required tools are installed
|
||||
if ! command -v rpmbuild &>/dev/null; then
|
||||
echo "Error: rpm-build is required to create RPM packages."
|
||||
echo "Install it with: sudo dnf install rpm-build"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! command -v ruby &>/dev/null; then
|
||||
echo "Error: Ruby is required by FPM."
|
||||
echo "Install it with: sudo dnf install ruby"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! command -v fpm &>/dev/null; then
|
||||
echo "Error: FPM (rubygem-fpm) is required to create RPM packages."
|
||||
echo "Install it with: sudo gem install fpm"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
arch="$(uname -m)"
|
||||
os="$(uname -s | tr '[:upper:]' '[:lower:]')"
|
||||
bin="frankenphp-${os}-${arch}"
|
||||
|
||||
if [ ! -f "dist/$bin" ]; then
|
||||
echo "Error: dist/$bin not found. Run './build-static.sh' first"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
version_output="$(dist/"$bin" version)"
|
||||
frankenphp_version=$(echo "$version_output" | grep -oP 'FrankenPHP\s+\K[^ ]+' || true)
|
||||
frankenphp_version=${frankenphp_version#v}
|
||||
|
||||
if [[ ! "${frankenphp_version}" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
||||
echo "Warning: frankenphp_version must be set to X.Y.Z (e.g. 1.5.1), got '${frankenphp_version}'"
|
||||
echo "Falling back to non-release version 0.0.0"
|
||||
frankenphp_version=0.0.0
|
||||
fi
|
||||
|
||||
group_preexists=0
|
||||
user_preexists=0
|
||||
|
||||
if getent group frankenphp >/dev/null; then
|
||||
group_preexists=1
|
||||
else
|
||||
sudo groupadd --system frankenphp
|
||||
fi
|
||||
|
||||
if getent passwd frankenphp >/dev/null; then
|
||||
user_preexists=1
|
||||
else
|
||||
sudo useradd --system \
|
||||
--gid frankenphp \
|
||||
--create-home \
|
||||
--home-dir /var/lib/frankenphp \
|
||||
--shell /usr/sbin/nologin \
|
||||
--comment "FrankenPHP web server" \
|
||||
frankenphp
|
||||
fi
|
||||
|
||||
mkdir -p package/empty
|
||||
mkdir -p package/etc
|
||||
[ -f ./dist/static-php-cli/source/php-src/php.ini-production ] && cp -f ./dist/static-php-cli/source/php-src/php.ini-production ./package/etc/php.ini
|
||||
|
||||
cd dist
|
||||
iteration=1
|
||||
glibc_version=$(ldd -v "$bin" | awk '/GLIBC_/ {gsub(/[()]/, "", $2); print $2}' | grep -v GLIBC_PRIVATE | sort -V | tail -n1)
|
||||
cxxabi_version=$(strings "$bin" | grep -oP 'CXXABI_\d+\.\d+(\.\d+)?' | sort -V | tail -n1)
|
||||
|
||||
fpm -s dir -t rpm -n frankenphp -v "${frankenphp_version}" \
|
||||
--config-files /etc/frankenphp/Caddyfile \
|
||||
--config-files /etc/frankenphp/php.ini \
|
||||
--depends "libc.so.6(${glibc_version})(64bit)" \
|
||||
--depends "libstdc++.so.6(${cxxabi_version})(64bit)" \
|
||||
--before-install ../package/rhel/preinstall.sh \
|
||||
--after-install ../package/rhel/postinstall.sh \
|
||||
--before-remove ../package/rhel/preuninstall.sh \
|
||||
--after-remove ../package/rhel/postuninstall.sh \
|
||||
--iteration "${iteration}" \
|
||||
--rpm-user frankenphp --rpm-group frankenphp \
|
||||
"${bin}=/usr/bin/frankenphp" \
|
||||
"../package/rhel/frankenphp.service=/usr/lib/systemd/system/frankenphp.service" \
|
||||
"../package/Caddyfile=/etc/frankenphp/Caddyfile" \
|
||||
"../package/content/=/usr/share/frankenphp" \
|
||||
"../package/etc/php.ini=/etc/frankenphp/php.ini" \
|
||||
"../package/empty/=/etc/frankenphp/php.d" \
|
||||
"../package/empty/=/usr/lib/frankenphp/modules" \
|
||||
"../package/empty/=/var/lib/frankenphp"
|
||||
|
||||
glibc_version=$(ldd -v "$bin" | awk '/GLIBC_/ {gsub(/[()]/, "", $2); print $2}' | grep -v GLIBC_PRIVATE | sed 's/GLIBC_//' | sort -V | tail -n1)
|
||||
cxxabi_version=$(strings "$bin" | grep -oP 'CXXABI_\d+\.\d+(\.\d+)?' | sed 's/CXXABI_//' | sort -V | tail -n1)
|
||||
|
||||
fpm -s dir -t deb -n frankenphp -v "${frankenphp_version}" \
|
||||
--config-files /etc/frankenphp/Caddyfile \
|
||||
--config-files /etc/frankenphp/php.ini \
|
||||
--depends "libc6 (>= ${glibc_version})" \
|
||||
--depends "libstdc++6 (>= ${cxxabi_version})" \
|
||||
--after-install ../package/debian/postinst.sh \
|
||||
--before-remove ../package/debian/prerm.sh \
|
||||
--after-remove ../package/debian/postrm.sh \
|
||||
--iteration "${iteration}" \
|
||||
--deb-user frankenphp --deb-group frankenphp \
|
||||
"${bin}=/usr/bin/frankenphp" \
|
||||
"../package/debian/frankenphp.service=/usr/lib/systemd/system/frankenphp.service" \
|
||||
"../package/Caddyfile=/etc/frankenphp/Caddyfile" \
|
||||
"../package/content/=/usr/share/frankenphp" \
|
||||
"../package/etc/php.ini=/etc/frankenphp/php.ini" \
|
||||
"../package/empty/=/etc/frankenphp/php.d" \
|
||||
"../package/empty/=/usr/lib/frankenphp/modules" \
|
||||
"../package/empty/=/var/lib/frankenphp"
|
||||
|
||||
[ "$user_preexists" -eq 0 ] && sudo userdel frankenphp
|
||||
[ "$group_preexists" -eq 0 ] && (sudo groupdel frankenphp || true)
|
||||
|
||||
cd ..
|
||||
355
build-static.sh
Executable file
355
build-static.sh
Executable file
@@ -0,0 +1,355 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -o errexit
|
||||
set -x
|
||||
|
||||
if ! type "git" >/dev/null 2>&1; then
|
||||
echo "The \"git\" command must be installed."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
arch="$(uname -m)"
|
||||
os="$(uname -s | tr '[:upper:]' '[:lower:]')"
|
||||
|
||||
# Supported variables:
|
||||
# - PHP_VERSION: PHP version to build (default: "8.4")
|
||||
# - PHP_EXTENSIONS: PHP extensions to build (default: ${defaultExtensions} set below)
|
||||
# - PHP_EXTENSION_LIBS: PHP extension libraries to build (default: ${defaultExtensionLibs} set below)
|
||||
# - FRANKENPHP_VERSION: FrankenPHP version (default: current Git commit)
|
||||
# - EMBED: Path to the PHP app to embed (default: none)
|
||||
# - DEBUG_SYMBOLS: Enable debug symbols if set to 1 (default: none)
|
||||
# - MIMALLOC: Use mimalloc as the allocator if set to 1 (default: none)
|
||||
# - XCADDY_ARGS: Additional arguments to pass to xcaddy
|
||||
# - RELEASE: [maintainer only] Create a GitHub release if set to 1 (default: none)
|
||||
|
||||
# - SPC_REL_TYPE: Release type to download (accept "source" and "binary", default: "source")
|
||||
# - SPC_OPT_BUILD_ARGS: Additional arguments to pass to spc build
|
||||
# - SPC_OPT_DOWNLOAD_ARGS: Additional arguments to pass to spc download
|
||||
# - SPC_LIBC: Set to glibc to build with GNU toolchain (default: musl)
|
||||
|
||||
# init spc command, if we use spc binary, just use it instead of fetching source
|
||||
if [ -z "${SPC_REL_TYPE}" ]; then
|
||||
SPC_REL_TYPE="source"
|
||||
fi
|
||||
# init spc libc
|
||||
if [ -z "${SPC_LIBC}" ]; then
|
||||
if [ "${os}" = "linux" ]; then
|
||||
SPC_LIBC="musl"
|
||||
fi
|
||||
fi
|
||||
# init spc build additional args
|
||||
if [ -z "${SPC_OPT_BUILD_ARGS}" ]; then
|
||||
SPC_OPT_BUILD_ARGS=""
|
||||
fi
|
||||
if [ "${SPC_LIBC}" = "musl" ] && [[ "${SPC_OPT_BUILD_ARGS}" != *"--disable-opcache-jit"* ]]; then
|
||||
SPC_OPT_BUILD_ARGS="${SPC_OPT_BUILD_ARGS} --disable-opcache-jit"
|
||||
fi
|
||||
# init spc download additional args
|
||||
if [ -z "${SPC_OPT_DOWNLOAD_ARGS}" ]; then
|
||||
SPC_OPT_DOWNLOAD_ARGS="--ignore-cache-sources=php-src --retry 5"
|
||||
if [ "${SPC_LIBC}" = "musl" ]; then
|
||||
SPC_OPT_DOWNLOAD_ARGS="${SPC_OPT_DOWNLOAD_ARGS} --prefer-pre-built"
|
||||
fi
|
||||
fi
|
||||
# if we need debug symbols, disable strip
|
||||
if [ -n "${DEBUG_SYMBOLS}" ]; then
|
||||
SPC_OPT_BUILD_ARGS="${SPC_OPT_BUILD_ARGS} --no-strip"
|
||||
fi
|
||||
# php version to build
|
||||
if [ -z "${PHP_VERSION}" ]; then
|
||||
get_latest_php_version() {
|
||||
input="$1"
|
||||
json=$(curl -s "https://www.php.net/releases/index.php?json&version=$input")
|
||||
latest=$(echo "$json" | jq -r '.version')
|
||||
|
||||
if [[ "$latest" == "$input"* ]]; then
|
||||
echo "$latest"
|
||||
else
|
||||
echo "$input"
|
||||
fi
|
||||
}
|
||||
|
||||
PHP_VERSION="$(get_latest_php_version "8.4")"
|
||||
export PHP_VERSION
|
||||
fi
|
||||
# default extension set
|
||||
defaultExtensions="apcu,bcmath,bz2,calendar,ctype,curl,dba,dom,exif,fileinfo,filter,ftp,gd,gmp,gettext,iconv,igbinary,imagick,intl,ldap,mbregex,mbstring,mysqli,mysqlnd,opcache,openssl,parallel,pcntl,pdo,pdo_mysql,pdo_pgsql,pdo_sqlite,pgsql,phar,posix,protobuf,readline,redis,session,shmop,simplexml,soap,sockets,sodium,sqlite3,ssh2,sysvmsg,sysvsem,sysvshm,tidy,tokenizer,xlswriter,xml,xmlreader,xmlwriter,zip,zlib,yaml,zstd"
|
||||
# if [ "${os}" != "linux" ] || [ "${SPC_LIBC}" = "glibc" ]; then
|
||||
# defaultExtensions="${defaultExtensions},ffi"
|
||||
# fi
|
||||
defaultExtensionLibs="bzip2,freetype,libavif,libjpeg,liblz4,libwebp,libzip,nghttp2"
|
||||
|
||||
md5binary="md5sum"
|
||||
if [ "${os}" = "darwin" ]; then
|
||||
os="mac"
|
||||
md5binary="md5 -q"
|
||||
fi
|
||||
|
||||
if [ "${os}" = "linux" ] && ! type "cmake" >/dev/null 2>&1; then
|
||||
echo "The \"cmake\" command must be installed."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "${os}" = "linux" ] && { [[ "${arch}" =~ "aarch" ]] || [[ "${arch}" =~ "arm" ]]; }; then
|
||||
fpic="-fPIC"
|
||||
fpie="-fPIE"
|
||||
|
||||
if [ -z "${DEBUG_SYMBOLS}" ]; then
|
||||
export SPC_PHP_DEFAULT_OPTIMIZE_CFLAGS="-g -fstack-protector-strong -fPIC -fPIE -Os -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64"
|
||||
fi
|
||||
else
|
||||
fpic="-fpic"
|
||||
fpie="-fpie"
|
||||
fi
|
||||
|
||||
if [ -z "${FRANKENPHP_VERSION}" ]; then
|
||||
FRANKENPHP_VERSION="$(git rev-parse --verify HEAD)"
|
||||
export FRANKENPHP_VERSION
|
||||
elif [ -d ".git/" ]; then
|
||||
CURRENT_REF="$(git rev-parse --abbrev-ref HEAD)"
|
||||
export CURRENT_REF
|
||||
|
||||
if echo "${FRANKENPHP_VERSION}" | grep -F -q "."; then
|
||||
# Tag
|
||||
|
||||
# Trim "v" prefix if any
|
||||
FRANKENPHP_VERSION=${FRANKENPHP_VERSION#v}
|
||||
export FRANKENPHP_VERSION
|
||||
|
||||
git checkout "v${FRANKENPHP_VERSION}"
|
||||
else
|
||||
git checkout "${FRANKENPHP_VERSION}"
|
||||
fi
|
||||
fi
|
||||
|
||||
bin="frankenphp-${os}-${arch}"
|
||||
|
||||
if [ -n "${CLEAN}" ]; then
|
||||
rm -Rf dist/
|
||||
go clean -cache
|
||||
fi
|
||||
|
||||
mkdir -p dist/
|
||||
cd dist/
|
||||
|
||||
if type "brew" >/dev/null 2>&1; then
|
||||
if ! type "composer" >/dev/null; then
|
||||
packages="composer"
|
||||
fi
|
||||
if ! type "go" >/dev/null 2>&1; then
|
||||
packages="${packages} go"
|
||||
fi
|
||||
if [ -n "${RELEASE}" ] && ! type "gh" >/dev/null 2>&1; then
|
||||
packages="${packages} gh"
|
||||
fi
|
||||
|
||||
if [ -n "${packages}" ]; then
|
||||
# shellcheck disable=SC2086
|
||||
brew install --formula --quiet ${packages}
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "${SPC_REL_TYPE}" = "binary" ]; then
|
||||
mkdir -p static-php-cli/
|
||||
cd static-php-cli/
|
||||
if [[ "${arch}" =~ "arm" ]]; then
|
||||
dl_arch="aarch64"
|
||||
else
|
||||
dl_arch="${arch}"
|
||||
fi
|
||||
curl -o spc -fsSL "https://dl.static-php.dev/static-php-cli/spc-bin/nightly/spc-linux-${dl_arch}"
|
||||
chmod +x spc
|
||||
spcCommand="./spc"
|
||||
elif [ -d "static-php-cli/src" ]; then
|
||||
cd static-php-cli/
|
||||
git pull
|
||||
composer install --no-dev -a --no-interaction
|
||||
spcCommand="./bin/spc"
|
||||
else
|
||||
git clone --depth 1 https://github.com/crazywhalecc/static-php-cli --branch main
|
||||
cd static-php-cli/
|
||||
composer install --no-dev -a --no-interaction
|
||||
spcCommand="./bin/spc"
|
||||
fi
|
||||
|
||||
# Extensions to build
|
||||
if [ -z "${PHP_EXTENSIONS}" ]; then
|
||||
# enable EMBED mode, first check if project has dumped extensions
|
||||
if [ -n "${EMBED}" ] && [ -f "${EMBED}/composer.json" ] && [ -f "${EMBED}/composer.lock" ] && [ -f "${EMBED}/vendor/installed.json" ]; then
|
||||
cd "${EMBED}"
|
||||
# read the extensions using spc dump-extensions
|
||||
PHP_EXTENSIONS=$(${spcCommand} dump-extensions "${EMBED}" --format=text --no-dev --no-ext-output="${defaultExtensions}")
|
||||
else
|
||||
PHP_EXTENSIONS="${defaultExtensions}"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Additional libraries to build
|
||||
if [ -z "${PHP_EXTENSION_LIBS}" ]; then
|
||||
PHP_EXTENSION_LIBS="${defaultExtensionLibs}"
|
||||
fi
|
||||
|
||||
# The Brotli library must always be built as it is required by http://github.com/dunglas/caddy-cbrotli
|
||||
if ! echo "${PHP_EXTENSION_LIBS}" | grep -q "\bbrotli\b"; then
|
||||
PHP_EXTENSION_LIBS="${PHP_EXTENSION_LIBS},brotli"
|
||||
fi
|
||||
|
||||
# The mimalloc library must be built if MIMALLOC is true
|
||||
if [ -n "${MIMALLOC}" ]; then
|
||||
if ! echo "${PHP_EXTENSION_LIBS}" | grep -q "\bmimalloc\b"; then
|
||||
PHP_EXTENSION_LIBS="${PHP_EXTENSION_LIBS},mimalloc"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Build libphp if necessary
|
||||
cache_key="${PHP_VERSION}-${PHP_EXTENSIONS}-${PHP_EXTENSION_LIBS}"
|
||||
if [ -f ../cache_key ] && [ "$(cat ../cache_key)" = "${cache_key}" ] && [ -f "buildroot/lib/libphp.a" ]; then
|
||||
echo "Hit cache, skipping libphp build."
|
||||
else
|
||||
${spcCommand} doctor --auto-fix
|
||||
# shellcheck disable=SC2086
|
||||
${spcCommand} download --with-php="${PHP_VERSION}" --for-extensions="${PHP_EXTENSIONS}" --for-libs="${PHP_EXTENSION_LIBS}" ${SPC_OPT_DOWNLOAD_ARGS}
|
||||
# shellcheck disable=SC2086
|
||||
${spcCommand} build --enable-zts --build-embed ${SPC_OPT_BUILD_ARGS} "${PHP_EXTENSIONS}" --with-libs="${PHP_EXTENSION_LIBS}"
|
||||
|
||||
echo -n "${cache_key}" >../cache_key
|
||||
fi
|
||||
|
||||
if ! type "go" >/dev/null 2>&1; then
|
||||
echo "The \"go\" command must be installed."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
XCADDY_COMMAND="xcaddy"
|
||||
if ! type "$XCADDY_COMMAND" >/dev/null 2>&1; then
|
||||
go install github.com/caddyserver/xcaddy/cmd/xcaddy@latest
|
||||
XCADDY_COMMAND="$(go env GOPATH)/bin/xcaddy"
|
||||
fi
|
||||
|
||||
curlGitHubHeaders=(--header "X-GitHub-Api-Version: 2022-11-28")
|
||||
if [ "${GITHUB_TOKEN}" ]; then
|
||||
curlGitHubHeaders+=(--header "Authorization: Bearer ${GITHUB_TOKEN}")
|
||||
fi
|
||||
|
||||
# Compile e-dant/watcher as a static library
|
||||
mkdir -p watcher
|
||||
cd watcher
|
||||
curl -f --retry 5 "${curlGitHubHeaders[@]}" https://api.github.com/repos/e-dant/watcher/releases/latest |
|
||||
grep tarball_url |
|
||||
awk '{ print $2 }' |
|
||||
sed 's/,$//' |
|
||||
sed 's/"//g' |
|
||||
xargs curl -fL --retry 5 "${curlGitHubHeaders[@]}" |
|
||||
tar xz --strip-components 1
|
||||
cd watcher-c
|
||||
if [ -z "${CC}" ]; then
|
||||
watcherCC=cc
|
||||
else
|
||||
watcherCC="${CC}"
|
||||
fi
|
||||
${watcherCC} -c -o libwatcher-c.o ./src/watcher-c.cpp -I ./include -I ../include -std=c++17 -Wall -Wextra "${fpic}"
|
||||
ar rcs libwatcher-c.a libwatcher-c.o
|
||||
cp libwatcher-c.a ../../buildroot/lib/libwatcher-c.a
|
||||
mkdir -p ../../buildroot/include/wtr
|
||||
cp -R include/wtr/watcher-c.h ../../buildroot/include/wtr/watcher-c.h
|
||||
cd ../../
|
||||
|
||||
# See https://github.com/docker-library/php/blob/master/8.3/alpine3.20/zts/Dockerfile#L53-L55
|
||||
CGO_CFLAGS="-DFRANKENPHP_VERSION=${FRANKENPHP_VERSION} -I${PWD}/buildroot/include/ $(${spcCommand} spc-config "${PHP_EXTENSIONS}" --with-libs="${PHP_EXTENSION_LIBS}" --includes)"
|
||||
if [ -n "${DEBUG_SYMBOLS}" ]; then
|
||||
CGO_CFLAGS="-g ${CGO_CFLAGS}"
|
||||
else
|
||||
CGO_CFLAGS="-fstack-protector-strong ${fpic} ${fpie} -O2 -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 ${CGO_CFLAGS}"
|
||||
fi
|
||||
export CGO_CFLAGS
|
||||
export CGO_CPPFLAGS="${CGO_CFLAGS}"
|
||||
|
||||
if [ "${os}" = "mac" ]; then
|
||||
export CGO_LDFLAGS="-framework CoreFoundation -framework SystemConfiguration"
|
||||
elif [ "${os}" = "linux" ] && [ -z "${DEBUG_SYMBOLS}" ]; then
|
||||
CGO_LDFLAGS="-Wl,-O1 -pie"
|
||||
fi
|
||||
if [ "${os}" = "linux" ] && [ "${SPC_LIBC}" = "glibc" ]; then
|
||||
CGO_LDFLAGS="${CGO_LDFLAGS} -Wl,--allow-multiple-definition -Wl,--export-dynamic"
|
||||
fi
|
||||
|
||||
CGO_LDFLAGS="${CGO_LDFLAGS} ${PWD}/buildroot/lib/libbrotlicommon.a ${PWD}/buildroot/lib/libbrotlienc.a ${PWD}/buildroot/lib/libbrotlidec.a ${PWD}/buildroot/lib/libwatcher-c.a $(${spcCommand} spc-config "${PHP_EXTENSIONS}" --with-libs="${PHP_EXTENSION_LIBS}" --libs)"
|
||||
if [[ "$CGO_LDFLAGS" == *"${PWD}/buildroot/lib/mimalloc.o"* ]]; then
|
||||
CGO_LDFLAGS=${CGO_LDFLAGS//${PWD}\/buildroot\/lib\/mimalloc.o/}
|
||||
CGO_LDFLAGS="${PWD}/buildroot/lib/libmimalloc.a $CGO_LDFLAGS"
|
||||
fi
|
||||
if [ "${os}" = "linux" ] && [ "${SPC_LIBC}" = "glibc" ]; then
|
||||
CGO_LDFLAGS="${CGO_LDFLAGS//-lphp/-Wl,--whole-archive -lphp -Wl,--no-whole-archive}"
|
||||
# shellcheck disable=SC2046
|
||||
ar d "${PWD}/buildroot/lib/libphp.a" $(ar t "${PWD}/buildroot/lib/libphp.a" | grep '\.a$')
|
||||
fi
|
||||
|
||||
export CGO_LDFLAGS
|
||||
|
||||
LIBPHP_VERSION="$(./buildroot/bin/php-config --version)"
|
||||
|
||||
cd ../
|
||||
|
||||
if [ -z "${DEBUG_SYMBOLS}" ]; then
|
||||
extraLdflags="-w -s"
|
||||
fi
|
||||
|
||||
cd ../
|
||||
|
||||
# Embed PHP app, if any
|
||||
if [ -n "${EMBED}" ] && [ -d "${EMBED}" ]; then
|
||||
tar -cf app.tar -C "${EMBED}" .
|
||||
${md5binary} app.tar | awk '{printf $1}' >app_checksum.txt
|
||||
fi
|
||||
|
||||
if [ -z "${XCADDY_ARGS}" ]; then
|
||||
XCADDY_ARGS="--with github.com/dunglas/caddy-cbrotli --with github.com/dunglas/mercure/caddy --with github.com/dunglas/vulcain/caddy"
|
||||
fi
|
||||
|
||||
XCADDY_DEBUG=0
|
||||
if [ -n "${DEBUG_SYMBOLS}" ]; then
|
||||
XCADDY_DEBUG=1
|
||||
fi
|
||||
|
||||
if [ "${SPC_LIBC}" = "musl" ]; then
|
||||
muslStackSizeFix="-Wl,-z,stack-size=0x80000"
|
||||
fi
|
||||
|
||||
go env
|
||||
cd caddy/
|
||||
if [ -z "${SPC_LIBC}" ] || [ "${SPC_LIBC}" = "musl" ]; then
|
||||
xcaddyGoBuildFlags="-buildmode=pie -tags cgo,netgo,osusergo,static_build,nobadger,nomysql,nopgx -ldflags \"-linkmode=external -extldflags '-static-pie ${muslStackSizeFix}' ${extraLdflags} -X 'github.com/caddyserver/caddy/v2.CustomVersion=FrankenPHP ${FRANKENPHP_VERSION} PHP ${LIBPHP_VERSION} Caddy'\""
|
||||
elif [ "${SPC_LIBC}" = "glibc" ]; then
|
||||
xcaddyGoBuildFlags="-buildmode=pie -tags cgo,netgo,osusergo,nobadger,nomysql,nopgx -ldflags \"-linkmode=external -extldflags '-pie' ${extraLdflags} -X 'github.com/caddyserver/caddy/v2.CustomVersion=FrankenPHP ${FRANKENPHP_VERSION} PHP ${LIBPHP_VERSION} Caddy'\""
|
||||
fi
|
||||
|
||||
# shellcheck disable=SC2086
|
||||
CGO_ENABLED=1 \
|
||||
XCADDY_GO_BUILD_FLAGS=${xcaddyGoBuildFlags} \
|
||||
XCADDY_DEBUG="${XCADDY_DEBUG}" \
|
||||
${XCADDY_COMMAND} build \
|
||||
--output "../dist/${bin}" \
|
||||
${XCADDY_ARGS} \
|
||||
--with frankenphp.dev=.. \
|
||||
--with frankenphp.dev/caddy=.
|
||||
cd ..
|
||||
|
||||
if [ -d "${EMBED}" ]; then
|
||||
truncate -s 0 app.tar
|
||||
truncate -s 0 app_checksum.txt
|
||||
fi
|
||||
|
||||
if type "upx" >/dev/null 2>&1 && [ -z "${DEBUG_SYMBOLS}" ] && [ -z "${NO_COMPRESS}" ]; then
|
||||
upx --best "dist/${bin}"
|
||||
fi
|
||||
|
||||
"dist/${bin}" version
|
||||
"dist/${bin}" build-info
|
||||
|
||||
if [ -n "${RELEASE}" ]; then
|
||||
gh release upload "v${FRANKENPHP_VERSION}" "dist/${bin}" --repo dunglas/frankenphp --clobber
|
||||
fi
|
||||
|
||||
if [ -n "${CURRENT_REF}" ]; then
|
||||
git checkout "${CURRENT_REF}"
|
||||
fi
|
||||
65
caddy/admin.go
Normal file
65
caddy/admin.go
Normal file
@@ -0,0 +1,65 @@
|
||||
package caddy
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
"frankenphp.dev"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type FrankenPHPAdmin struct{}
|
||||
|
||||
// if the id starts with "admin.api" the module will register AdminRoutes via module.Routes()
|
||||
func (FrankenPHPAdmin) CaddyModule() caddy.ModuleInfo {
|
||||
return caddy.ModuleInfo{
|
||||
ID: "admin.api.frankenphp",
|
||||
New: func() caddy.Module { return new(FrankenPHPAdmin) },
|
||||
}
|
||||
}
|
||||
|
||||
// EXPERIMENTAL: These routes are not yet stable and may change in the future.
|
||||
func (admin FrankenPHPAdmin) Routes() []caddy.AdminRoute {
|
||||
return []caddy.AdminRoute{
|
||||
{
|
||||
Pattern: "/frankenphp/workers/restart",
|
||||
Handler: caddy.AdminHandlerFunc(admin.restartWorkers),
|
||||
},
|
||||
{
|
||||
Pattern: "/frankenphp/threads",
|
||||
Handler: caddy.AdminHandlerFunc(admin.threads),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (admin *FrankenPHPAdmin) restartWorkers(w http.ResponseWriter, r *http.Request) error {
|
||||
if r.Method != http.MethodPost {
|
||||
return admin.error(http.StatusMethodNotAllowed, fmt.Errorf("method not allowed"))
|
||||
}
|
||||
|
||||
frankenphp.RestartWorkers()
|
||||
caddy.Log().Info("workers restarted from admin api")
|
||||
admin.success(w, "workers restarted successfully\n")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (admin *FrankenPHPAdmin) threads(w http.ResponseWriter, _ *http.Request) error {
|
||||
debugState := frankenphp.DebugState()
|
||||
prettyJson, err := json.MarshalIndent(debugState, "", " ")
|
||||
if err != nil {
|
||||
return admin.error(http.StatusInternalServerError, err)
|
||||
}
|
||||
|
||||
return admin.success(w, string(prettyJson))
|
||||
}
|
||||
|
||||
func (admin *FrankenPHPAdmin) success(w http.ResponseWriter, message string) error {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
_, err := w.Write([]byte(message))
|
||||
return err
|
||||
}
|
||||
|
||||
func (admin *FrankenPHPAdmin) error(statusCode int, err error) error {
|
||||
return caddy.APIError{HTTPStatus: statusCode, Err: err}
|
||||
}
|
||||
293
caddy/admin_test.go
Normal file
293
caddy/admin_test.go
Normal file
@@ -0,0 +1,293 @@
|
||||
package caddy_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"frankenphp.dev"
|
||||
"frankenphp.dev/internal/fastabs"
|
||||
"github.com/caddyserver/caddy/v2/caddytest"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestRestartWorkerViaAdminApi(t *testing.T) {
|
||||
tester := caddytest.NewTester(t)
|
||||
tester.InitServer(`
|
||||
{
|
||||
skip_install_trust
|
||||
admin localhost:2999
|
||||
http_port `+testPort+`
|
||||
|
||||
frankenphp {
|
||||
worker ../testdata/worker-with-counter.php 1
|
||||
}
|
||||
}
|
||||
|
||||
localhost:`+testPort+` {
|
||||
route {
|
||||
root ../testdata
|
||||
rewrite worker-with-counter.php
|
||||
php
|
||||
}
|
||||
}
|
||||
`, "caddyfile")
|
||||
|
||||
tester.AssertGetResponse("http://localhost:"+testPort+"/", http.StatusOK, "requests:1")
|
||||
tester.AssertGetResponse("http://localhost:"+testPort+"/", http.StatusOK, "requests:2")
|
||||
|
||||
assertAdminResponse(t, tester, "POST", "workers/restart", http.StatusOK, "workers restarted successfully\n")
|
||||
|
||||
tester.AssertGetResponse("http://localhost:"+testPort+"/", http.StatusOK, "requests:1")
|
||||
}
|
||||
|
||||
func TestShowTheCorrectThreadDebugStatus(t *testing.T) {
|
||||
tester := caddytest.NewTester(t)
|
||||
tester.InitServer(`
|
||||
{
|
||||
skip_install_trust
|
||||
admin localhost:2999
|
||||
http_port `+testPort+`
|
||||
|
||||
frankenphp {
|
||||
num_threads 3
|
||||
max_threads 6
|
||||
worker ../testdata/worker-with-counter.php 1
|
||||
worker ../testdata/index.php 1
|
||||
}
|
||||
}
|
||||
|
||||
localhost:`+testPort+` {
|
||||
route {
|
||||
root ../testdata
|
||||
rewrite worker-with-counter.php
|
||||
php
|
||||
}
|
||||
}
|
||||
`, "caddyfile")
|
||||
|
||||
debugState := getDebugState(t, tester)
|
||||
|
||||
// assert that the correct threads are present in the thread info
|
||||
assert.Equal(t, debugState.ThreadDebugStates[0].State, "ready")
|
||||
assert.Contains(t, debugState.ThreadDebugStates[1].Name, "worker-with-counter.php")
|
||||
assert.Contains(t, debugState.ThreadDebugStates[2].Name, "index.php")
|
||||
assert.Equal(t, debugState.ReservedThreadCount, 3)
|
||||
assert.Len(t, debugState.ThreadDebugStates, 3)
|
||||
}
|
||||
|
||||
func TestAutoScaleWorkerThreads(t *testing.T) {
|
||||
wg := sync.WaitGroup{}
|
||||
maxTries := 10
|
||||
requestsPerTry := 200
|
||||
tester := caddytest.NewTester(t)
|
||||
tester.InitServer(`
|
||||
{
|
||||
skip_install_trust
|
||||
admin localhost:2999
|
||||
http_port `+testPort+`
|
||||
|
||||
frankenphp {
|
||||
max_threads 10
|
||||
num_threads 2
|
||||
worker ../testdata/sleep.php 1
|
||||
}
|
||||
}
|
||||
|
||||
localhost:`+testPort+` {
|
||||
route {
|
||||
root ../testdata
|
||||
rewrite sleep.php
|
||||
php
|
||||
}
|
||||
}
|
||||
`, "caddyfile")
|
||||
|
||||
// spam an endpoint that simulates IO
|
||||
endpoint := "http://localhost:" + testPort + "/?sleep=2&work=1000"
|
||||
amountOfThreads := len(getDebugState(t, tester).ThreadDebugStates)
|
||||
|
||||
// try to spawn the additional threads by spamming the server
|
||||
for tries := 0; tries < maxTries; tries++ {
|
||||
wg.Add(requestsPerTry)
|
||||
for i := 0; i < requestsPerTry; i++ {
|
||||
go func() {
|
||||
tester.AssertGetResponse(endpoint, http.StatusOK, "slept for 2 ms and worked for 1000 iterations")
|
||||
wg.Done()
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
amountOfThreads = len(getDebugState(t, tester).ThreadDebugStates)
|
||||
if amountOfThreads > 2 {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// assert that there are now more threads than before
|
||||
assert.NotEqual(t, amountOfThreads, 2)
|
||||
}
|
||||
|
||||
// Note this test requires at least 2x40MB available memory for the process
|
||||
func TestAutoScaleRegularThreadsOnAutomaticThreadLimit(t *testing.T) {
|
||||
wg := sync.WaitGroup{}
|
||||
maxTries := 10
|
||||
requestsPerTry := 200
|
||||
tester := caddytest.NewTester(t)
|
||||
tester.InitServer(`
|
||||
{
|
||||
skip_install_trust
|
||||
admin localhost:2999
|
||||
http_port `+testPort+`
|
||||
|
||||
frankenphp {
|
||||
max_threads auto
|
||||
num_threads 1
|
||||
php_ini memory_limit 40M # a reasonable limit for the test
|
||||
}
|
||||
}
|
||||
|
||||
localhost:`+testPort+` {
|
||||
route {
|
||||
root ../testdata
|
||||
php
|
||||
}
|
||||
}
|
||||
`, "caddyfile")
|
||||
|
||||
// spam an endpoint that simulates IO
|
||||
endpoint := "http://localhost:" + testPort + "/sleep.php?sleep=2&work=1000"
|
||||
amountOfThreads := len(getDebugState(t, tester).ThreadDebugStates)
|
||||
|
||||
// try to spawn the additional threads by spamming the server
|
||||
for tries := 0; tries < maxTries; tries++ {
|
||||
wg.Add(requestsPerTry)
|
||||
for i := 0; i < requestsPerTry; i++ {
|
||||
go func() {
|
||||
tester.AssertGetResponse(endpoint, http.StatusOK, "slept for 2 ms and worked for 1000 iterations")
|
||||
wg.Done()
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
amountOfThreads = len(getDebugState(t, tester).ThreadDebugStates)
|
||||
if amountOfThreads > 1 {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// assert that there are now more threads present
|
||||
assert.NotEqual(t, amountOfThreads, 1)
|
||||
}
|
||||
|
||||
func assertAdminResponse(t *testing.T, tester *caddytest.Tester, method string, path string, expectedStatus int, expectedBody string) {
|
||||
adminUrl := "http://localhost:2999/frankenphp/"
|
||||
r, err := http.NewRequest(method, adminUrl+path, nil)
|
||||
assert.NoError(t, err)
|
||||
if expectedBody == "" {
|
||||
_ = tester.AssertResponseCode(r, expectedStatus)
|
||||
return
|
||||
}
|
||||
_, _ = tester.AssertResponse(r, expectedStatus, expectedBody)
|
||||
}
|
||||
|
||||
func getAdminResponseBody(t *testing.T, tester *caddytest.Tester, method string, path string) string {
|
||||
adminUrl := "http://localhost:2999/frankenphp/"
|
||||
r, err := http.NewRequest(method, adminUrl+path, nil)
|
||||
assert.NoError(t, err)
|
||||
resp := tester.AssertResponseCode(r, http.StatusOK)
|
||||
defer resp.Body.Close()
|
||||
bytes, err := io.ReadAll(resp.Body)
|
||||
assert.NoError(t, err)
|
||||
|
||||
return string(bytes)
|
||||
}
|
||||
|
||||
func getDebugState(t *testing.T, tester *caddytest.Tester) frankenphp.FrankenPHPDebugState {
|
||||
threadStates := getAdminResponseBody(t, tester, "GET", "threads")
|
||||
|
||||
var debugStates frankenphp.FrankenPHPDebugState
|
||||
err := json.Unmarshal([]byte(threadStates), &debugStates)
|
||||
assert.NoError(t, err)
|
||||
|
||||
return debugStates
|
||||
}
|
||||
|
||||
func TestAddModuleWorkerViaAdminApi(t *testing.T) {
|
||||
// Initialize a server with admin API enabled
|
||||
tester := caddytest.NewTester(t)
|
||||
tester.InitServer(`
|
||||
{
|
||||
skip_install_trust
|
||||
admin localhost:2999
|
||||
http_port `+testPort+`
|
||||
}
|
||||
|
||||
localhost:`+testPort+` {
|
||||
route {
|
||||
root ../testdata
|
||||
php
|
||||
}
|
||||
}
|
||||
`, "caddyfile")
|
||||
|
||||
// Get initial debug state to check number of workers
|
||||
initialDebugState := getDebugState(t, tester)
|
||||
initialWorkerCount := 0
|
||||
for _, thread := range initialDebugState.ThreadDebugStates {
|
||||
if thread.Name != "" && thread.Name != "ready" {
|
||||
initialWorkerCount++
|
||||
}
|
||||
}
|
||||
|
||||
// Create a Caddyfile configuration with a module worker
|
||||
workerConfig := `
|
||||
{
|
||||
skip_install_trust
|
||||
admin localhost:2999
|
||||
http_port ` + testPort + `
|
||||
}
|
||||
|
||||
localhost:` + testPort + ` {
|
||||
route {
|
||||
root ../testdata
|
||||
php {
|
||||
worker ../testdata/worker-with-counter.php 1
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
// Send the configuration to the admin API
|
||||
adminUrl := "http://localhost:2999/load"
|
||||
r, err := http.NewRequest("POST", adminUrl, bytes.NewBufferString(workerConfig))
|
||||
assert.NoError(t, err)
|
||||
r.Header.Set("Content-Type", "text/caddyfile")
|
||||
resp := tester.AssertResponseCode(r, http.StatusOK)
|
||||
defer resp.Body.Close()
|
||||
|
||||
// Get the updated debug state to check if the worker was added
|
||||
updatedDebugState := getDebugState(t, tester)
|
||||
updatedWorkerCount := 0
|
||||
workerFound := false
|
||||
filename, _ := fastabs.FastAbs("../testdata/worker-with-counter.php")
|
||||
for _, thread := range updatedDebugState.ThreadDebugStates {
|
||||
if thread.Name != "" && thread.Name != "ready" {
|
||||
updatedWorkerCount++
|
||||
if thread.Name == "Worker PHP Thread - "+filename {
|
||||
workerFound = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Assert that the worker was added
|
||||
assert.Greater(t, updatedWorkerCount, initialWorkerCount, "Worker count should have increased")
|
||||
assert.True(t, workerFound, fmt.Sprintf("Worker with name %q should be found", "Worker PHP Thread - "+filename))
|
||||
|
||||
// Make a request to the worker to verify it's working
|
||||
tester.AssertGetResponse("http://localhost:"+testPort+"/worker-with-counter.php", http.StatusOK, "requests:1")
|
||||
}
|
||||
5
caddy/br-skip.go
Normal file
5
caddy/br-skip.go
Normal file
@@ -0,0 +1,5 @@
|
||||
//go:build nobrotli
|
||||
|
||||
package caddy
|
||||
|
||||
var brotli = false
|
||||
5
caddy/br.go
Normal file
5
caddy/br.go
Normal file
@@ -0,0 +1,5 @@
|
||||
//go:build !nobrotli
|
||||
|
||||
package caddy
|
||||
|
||||
var brotli = true
|
||||
848
caddy/caddy.go
848
caddy/caddy.go
File diff suppressed because it is too large
Load Diff
1211
caddy/caddy_test.go
1211
caddy/caddy_test.go
File diff suppressed because it is too large
Load Diff
222
caddy/config_test.go
Normal file
222
caddy/config_test.go
Normal file
@@ -0,0 +1,222 @@
|
||||
package caddy
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestModuleWorkerDuplicateFilenamesFail(t *testing.T) {
|
||||
// Create a test configuration with duplicate worker filenames
|
||||
configWithDuplicateFilenames := `
|
||||
{
|
||||
php {
|
||||
worker {
|
||||
file worker-with-env.php
|
||||
num 1
|
||||
}
|
||||
worker {
|
||||
file worker-with-env.php
|
||||
num 2
|
||||
}
|
||||
}
|
||||
}`
|
||||
|
||||
// Parse the configuration
|
||||
d := caddyfile.NewTestDispenser(configWithDuplicateFilenames)
|
||||
module := &FrankenPHPModule{}
|
||||
|
||||
// Unmarshal the configuration
|
||||
err := module.UnmarshalCaddyfile(d)
|
||||
|
||||
// Verify that an error was returned
|
||||
require.Error(t, err, "Expected an error when two workers in the same module have the same filename")
|
||||
require.Contains(t, err.Error(), "must not have duplicate filenames", "Error message should mention duplicate filenames")
|
||||
}
|
||||
|
||||
func TestModuleWorkersWithDifferentFilenames(t *testing.T) {
|
||||
// Create a test configuration with different worker filenames
|
||||
configWithDifferentFilenames := `
|
||||
{
|
||||
php {
|
||||
worker ../testdata/worker-with-env.php
|
||||
worker ../testdata/worker-with-counter.php
|
||||
}
|
||||
}`
|
||||
|
||||
// Parse the configuration
|
||||
d := caddyfile.NewTestDispenser(configWithDifferentFilenames)
|
||||
module := &FrankenPHPModule{}
|
||||
|
||||
// Unmarshal the configuration
|
||||
err := module.UnmarshalCaddyfile(d)
|
||||
|
||||
// Verify that no error was returned
|
||||
require.NoError(t, err, "Expected no error when two workers in the same module have different filenames")
|
||||
|
||||
// Verify that both workers were added to the module
|
||||
require.Len(t, module.Workers, 2, "Expected two workers to be added to the module")
|
||||
require.Equal(t, "../testdata/worker-with-env.php", module.Workers[0].FileName, "First worker should have the correct filename")
|
||||
require.Equal(t, "../testdata/worker-with-counter.php", module.Workers[1].FileName, "Second worker should have the correct filename")
|
||||
}
|
||||
|
||||
func TestModuleWorkersDifferentNamesSucceed(t *testing.T) {
|
||||
// Create a test configuration with a worker name
|
||||
configWithWorkerName1 := `
|
||||
{
|
||||
php_server {
|
||||
worker {
|
||||
name test-worker-1
|
||||
file ../testdata/worker-with-env.php
|
||||
num 1
|
||||
}
|
||||
}
|
||||
}`
|
||||
|
||||
// Parse the first configuration
|
||||
d1 := caddyfile.NewTestDispenser(configWithWorkerName1)
|
||||
app := &FrankenPHPApp{}
|
||||
module1 := &FrankenPHPModule{}
|
||||
|
||||
// Unmarshal the first configuration
|
||||
err := module1.UnmarshalCaddyfile(d1)
|
||||
require.NoError(t, err, "First module should be configured without errors")
|
||||
|
||||
// Create a second test configuration with a different worker name
|
||||
configWithWorkerName2 := `
|
||||
{
|
||||
php_server {
|
||||
worker {
|
||||
name test-worker-2
|
||||
file ../testdata/worker-with-env.php
|
||||
num 1
|
||||
}
|
||||
}
|
||||
}`
|
||||
|
||||
// Parse the second configuration
|
||||
d2 := caddyfile.NewTestDispenser(configWithWorkerName2)
|
||||
module2 := &FrankenPHPModule{}
|
||||
|
||||
// Unmarshal the second configuration
|
||||
err = module2.UnmarshalCaddyfile(d2)
|
||||
|
||||
// Verify that no error was returned
|
||||
require.NoError(t, err, "Expected no error when two workers have different names")
|
||||
|
||||
_, err = app.addModuleWorkers(module1.Workers...)
|
||||
require.NoError(t, err, "Expected no error when adding the first module workers")
|
||||
_, err = app.addModuleWorkers(module2.Workers...)
|
||||
require.NoError(t, err, "Expected no error when adding the second module workers")
|
||||
|
||||
// Verify that both workers were added
|
||||
require.Len(t, app.Workers, 2, "Expected two workers in the app")
|
||||
require.Equal(t, "m#test-worker-1", app.Workers[0].Name, "First worker should have the correct name")
|
||||
require.Equal(t, "m#test-worker-2", app.Workers[1].Name, "Second worker should have the correct name")
|
||||
}
|
||||
|
||||
func TestModuleWorkerWithEnvironmentVariables(t *testing.T) {
|
||||
// Create a test configuration with environment variables
|
||||
configWithEnv := `
|
||||
{
|
||||
php {
|
||||
worker {
|
||||
file ../testdata/worker-with-env.php
|
||||
num 1
|
||||
env APP_ENV production
|
||||
env DEBUG true
|
||||
}
|
||||
}
|
||||
}`
|
||||
|
||||
// Parse the configuration
|
||||
d := caddyfile.NewTestDispenser(configWithEnv)
|
||||
module := &FrankenPHPModule{}
|
||||
|
||||
// Unmarshal the configuration
|
||||
err := module.UnmarshalCaddyfile(d)
|
||||
|
||||
// Verify that no error was returned
|
||||
require.NoError(t, err, "Expected no error when configuring a worker with environment variables")
|
||||
|
||||
// Verify that the worker was added to the module
|
||||
require.Len(t, module.Workers, 1, "Expected one worker to be added to the module")
|
||||
require.Equal(t, "../testdata/worker-with-env.php", module.Workers[0].FileName, "Worker should have the correct filename")
|
||||
|
||||
// Verify that the environment variables were set correctly
|
||||
require.Len(t, module.Workers[0].Env, 2, "Expected two environment variables")
|
||||
require.Equal(t, "production", module.Workers[0].Env["APP_ENV"], "APP_ENV should be set to production")
|
||||
require.Equal(t, "true", module.Workers[0].Env["DEBUG"], "DEBUG should be set to true")
|
||||
}
|
||||
|
||||
func TestModuleWorkerWithWatchConfiguration(t *testing.T) {
|
||||
// Create a test configuration with watch directories
|
||||
configWithWatch := `
|
||||
{
|
||||
php {
|
||||
worker {
|
||||
file ../testdata/worker-with-env.php
|
||||
num 1
|
||||
watch
|
||||
watch ./src/**/*.php
|
||||
watch ./config/**/*.yaml
|
||||
}
|
||||
}
|
||||
}`
|
||||
|
||||
// Parse the configuration
|
||||
d := caddyfile.NewTestDispenser(configWithWatch)
|
||||
module := &FrankenPHPModule{}
|
||||
|
||||
// Unmarshal the configuration
|
||||
err := module.UnmarshalCaddyfile(d)
|
||||
|
||||
// Verify that no error was returned
|
||||
require.NoError(t, err, "Expected no error when configuring a worker with watch directories")
|
||||
|
||||
// Verify that the worker was added to the module
|
||||
require.Len(t, module.Workers, 1, "Expected one worker to be added to the module")
|
||||
require.Equal(t, "../testdata/worker-with-env.php", module.Workers[0].FileName, "Worker should have the correct filename")
|
||||
|
||||
// Verify that the watch directories were set correctly
|
||||
require.Len(t, module.Workers[0].Watch, 3, "Expected three watch patterns")
|
||||
require.Equal(t, "./**/*.{php,yaml,yml,twig,env}", module.Workers[0].Watch[0], "First watch pattern should be the default")
|
||||
require.Equal(t, "./src/**/*.php", module.Workers[0].Watch[1], "Second watch pattern should match the configuration")
|
||||
require.Equal(t, "./config/**/*.yaml", module.Workers[0].Watch[2], "Third watch pattern should match the configuration")
|
||||
}
|
||||
|
||||
func TestModuleWorkerWithCustomName(t *testing.T) {
|
||||
// Create a test configuration with a custom worker name
|
||||
configWithCustomName := `
|
||||
{
|
||||
php {
|
||||
worker {
|
||||
file ../testdata/worker-with-env.php
|
||||
num 1
|
||||
name custom-worker-name
|
||||
}
|
||||
}
|
||||
}`
|
||||
|
||||
// Parse the configuration
|
||||
d := caddyfile.NewTestDispenser(configWithCustomName)
|
||||
module := &FrankenPHPModule{}
|
||||
app := &FrankenPHPApp{}
|
||||
|
||||
// Unmarshal the configuration
|
||||
err := module.UnmarshalCaddyfile(d)
|
||||
|
||||
// Verify that no error was returned
|
||||
require.NoError(t, err, "Expected no error when configuring a worker with a custom name")
|
||||
|
||||
// Verify that the worker was added to the module
|
||||
require.Len(t, module.Workers, 1, "Expected one worker to be added to the module")
|
||||
require.Equal(t, "../testdata/worker-with-env.php", module.Workers[0].FileName, "Worker should have the correct filename")
|
||||
|
||||
// Verify that the worker was added to app.Workers with the m# prefix
|
||||
module.Workers, err = app.addModuleWorkers(module.Workers...)
|
||||
require.NoError(t, err, "Expected no error when adding the worker to the app")
|
||||
require.Equal(t, "m#custom-worker-name", module.Workers[0].Name, "Worker should have the custom name, prefixed with m#")
|
||||
require.Equal(t, "m#custom-worker-name", app.Workers[0].Name, "Worker should have the custom name, prefixed with m#")
|
||||
}
|
||||
@@ -1,32 +1,33 @@
|
||||
# The Caddyfile is an easy way to configure FrankenPHP and the Caddy web server.
|
||||
#
|
||||
# https://frankenphp.dev/docs/config
|
||||
# https://caddyserver.com/docs/caddyfile
|
||||
{
|
||||
skip_install_trust
|
||||
|
||||
{$CADDY_GLOBAL_OPTIONS}
|
||||
|
||||
frankenphp {
|
||||
{$FRANKENPHP_CONFIG}
|
||||
#worker /path/to/your/worker.php
|
||||
}
|
||||
}
|
||||
|
||||
{$CADDY_EXTRA_CONFIG}
|
||||
{$SERVER_NAME:localhost}
|
||||
|
||||
{$SERVER_NAME:localhost} {
|
||||
log {
|
||||
# Redact the authorization query parameter that can be set by Mercure
|
||||
format filter {
|
||||
wrap console
|
||||
fields {
|
||||
uri query {
|
||||
replace authorization REDACTED
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#log {
|
||||
# # Redact the authorization query parameter that can be set by Mercure
|
||||
# format filter {
|
||||
# request>uri query {
|
||||
# replace authorization REDACTED
|
||||
# }
|
||||
# }
|
||||
#}
|
||||
|
||||
root public/
|
||||
route {
|
||||
# Healthcheck URL
|
||||
skip_log /healthz
|
||||
respond /healthz 200
|
||||
encode zstd br gzip
|
||||
|
||||
# Uncomment the following lines to enable Mercure and Vulcain modules
|
||||
root * public/
|
||||
#mercure {
|
||||
# # Transport to use (default to Bolt)
|
||||
# transport_url {$MERCURE_TRANSPORT_URL:bolt:///data/mercure.db}
|
||||
@@ -44,25 +45,15 @@ route {
|
||||
#vulcain
|
||||
|
||||
{$CADDY_SERVER_EXTRA_DIRECTIVES}
|
||||
# Add trailing slash for directory requests
|
||||
@canonicalPath {
|
||||
file {path}/index.php
|
||||
not path */
|
||||
|
||||
php_server {
|
||||
#worker /path/to/your/worker.php
|
||||
}
|
||||
}
|
||||
redir @canonicalPath {path}/ 308
|
||||
|
||||
# If the requested file does not exist, try index files
|
||||
@indexFiles file {
|
||||
try_files {path} {path}/index.php index.php
|
||||
split_path .php
|
||||
}
|
||||
rewrite @indexFiles {http.matchers.file.relative}
|
||||
|
||||
# FrankenPHP!
|
||||
@phpFiles path *.php
|
||||
php @phpFiles
|
||||
encode zstd gzip
|
||||
file_server
|
||||
|
||||
respond 404
|
||||
|
||||
# As an alternative to editing the above site block, you can add your own site
|
||||
# block files in the Caddyfile.d directory, and they will be included as long
|
||||
# as they use the .caddyfile extension.
|
||||
|
||||
import Caddyfile.d/*.caddyfile
|
||||
import Caddyfile.d/*.caddyfile
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
// Copied from https://github.com/caddyserver/xcaddy/blob/b7fd102f41e12be4735dc77b0391823989812ce8/environment.go#L251
|
||||
package main
|
||||
|
||||
import (
|
||||
caddycmd "github.com/caddyserver/caddy/v2/cmd"
|
||||
|
||||
_ "go.uber.org/automaxprocs"
|
||||
|
||||
// plug in Caddy modules here.
|
||||
_ "github.com/caddyserver/caddy/v2/modules/standard"
|
||||
_ "github.com/dunglas/frankenphp/caddy"
|
||||
_ "github.com/dunglas/caddy-cbrotli"
|
||||
_ "frankenphp.dev/caddy"
|
||||
_ "github.com/dunglas/mercure/caddy"
|
||||
_ "github.com/dunglas/vulcain/caddy"
|
||||
)
|
||||
|
||||
310
caddy/go.mod
310
caddy/go.mod
@@ -1,198 +1,208 @@
|
||||
module github.com/dunglas/frankenphp/caddy
|
||||
module frankenphp.dev/caddy
|
||||
|
||||
go 1.20
|
||||
go 1.24.0
|
||||
|
||||
replace github.com/dunglas/frankenphp => ../
|
||||
replace frankenphp.dev => ../
|
||||
|
||||
replace (
|
||||
// some packages must match versions defined in go.mod of caddyserver/caddy/v2
|
||||
github.com/caddyserver/certmagic => github.com/caddyserver/certmagic v0.19.2
|
||||
github.com/google/cel-go => github.com/google/cel-go v0.15.1
|
||||
github.com/quic-go/quic-go => github.com/quic-go/quic-go v0.37.6
|
||||
)
|
||||
retract v1.0.0-rc.1 // Human error
|
||||
|
||||
require (
|
||||
github.com/caddyserver/caddy/v2 v2.7.4
|
||||
github.com/dunglas/frankenphp v1.0.0-beta.1
|
||||
github.com/dunglas/mercure/caddy v0.15.2
|
||||
github.com/dunglas/vulcain/caddy v0.5.0
|
||||
go.uber.org/automaxprocs v1.5.3
|
||||
go.uber.org/zap v1.26.0
|
||||
github.com/caddyserver/caddy/v2 v2.10.0
|
||||
github.com/caddyserver/certmagic v0.23.0
|
||||
github.com/dunglas/caddy-cbrotli v1.0.0
|
||||
frankenphp.dev v1.7.0
|
||||
github.com/dunglas/mercure/caddy v0.19.2
|
||||
github.com/dunglas/vulcain/caddy v1.2.0
|
||||
github.com/prometheus/client_golang v1.22.0
|
||||
github.com/spf13/cobra v1.9.1
|
||||
github.com/stretchr/testify v1.10.0
|
||||
)
|
||||
|
||||
require github.com/smallstep/go-attestation v0.4.4-0.20241119153605-2306d5b464ca // indirect
|
||||
|
||||
require (
|
||||
filippo.io/edwards25519 v1.0.0 // indirect
|
||||
cel.dev/expr v0.24.0 // indirect
|
||||
dario.cat/mergo v1.0.2 // indirect
|
||||
filippo.io/edwards25519 v1.1.0 // indirect
|
||||
github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 // indirect
|
||||
github.com/BurntSushi/toml v1.3.2 // indirect
|
||||
github.com/BurntSushi/toml v1.5.0 // indirect
|
||||
github.com/KimMachineGun/automemlimit v0.7.2 // indirect
|
||||
github.com/Masterminds/goutils v1.1.1 // indirect
|
||||
github.com/Masterminds/semver/v3 v3.2.1 // indirect
|
||||
github.com/Masterminds/sprig/v3 v3.2.3 // indirect
|
||||
github.com/Masterminds/semver/v3 v3.3.1 // indirect
|
||||
github.com/Masterminds/sprig/v3 v3.3.0 // indirect
|
||||
github.com/MauriceGit/skiplist v0.0.0-20211105230623-77f5c8d3e145 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.1 // indirect
|
||||
github.com/RoaringBitmap/roaring v1.3.0 // indirect
|
||||
github.com/alecthomas/chroma/v2 v2.7.0 // indirect
|
||||
github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230305170008-8188dc5388df // indirect
|
||||
github.com/MicahParks/jwkset v0.9.6 // indirect
|
||||
github.com/MicahParks/keyfunc/v3 v3.4.0 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||
github.com/RoaringBitmap/roaring v1.9.4 // indirect
|
||||
github.com/alecthomas/chroma/v2 v2.18.0 // indirect
|
||||
github.com/antlr4-go/antlr/v4 v4.13.1 // indirect
|
||||
github.com/aryann/difflib v0.0.0-20210328193216-ff5ff6dc229b // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/bits-and-blooms/bitset v1.8.0 // indirect
|
||||
github.com/caddyserver/certmagic v0.19.2 // indirect
|
||||
github.com/cenkalti/backoff/v4 v4.2.1 // indirect
|
||||
github.com/bits-and-blooms/bitset v1.22.0 // indirect
|
||||
github.com/caddyserver/zerossl v0.1.3 // indirect
|
||||
github.com/ccoveille/go-safecast v1.6.1 // indirect
|
||||
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
||||
github.com/cespare/xxhash v1.1.0 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/chzyer/readline v1.5.1 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
|
||||
github.com/cloudflare/circl v1.6.1 // indirect
|
||||
github.com/coreos/go-oidc/v3 v3.14.1 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/dgraph-io/badger v1.6.2 // indirect
|
||||
github.com/dgraph-io/badger/v2 v2.2007.4 // indirect
|
||||
github.com/dgraph-io/ristretto v0.1.1 // indirect
|
||||
github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 // indirect
|
||||
github.com/dlclark/regexp2 v1.7.0 // indirect
|
||||
github.com/dunglas/httpsfv v1.0.1 // indirect
|
||||
github.com/dunglas/mercure v0.15.2 // indirect
|
||||
github.com/dunglas/vulcain v0.5.0 // indirect
|
||||
github.com/dgraph-io/ristretto v0.2.0 // indirect
|
||||
github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da // indirect
|
||||
github.com/dlclark/regexp2 v1.11.5 // indirect
|
||||
github.com/dolthub/maphash v0.1.0 // indirect
|
||||
github.com/dunglas/httpsfv v1.1.0 // indirect
|
||||
github.com/dunglas/mercure v0.19.2 // indirect
|
||||
github.com/dunglas/vulcain v1.2.0 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.3 // indirect
|
||||
github.com/fsnotify/fsnotify v1.6.0 // indirect
|
||||
github.com/fxamacker/cbor/v2 v2.4.0 // indirect
|
||||
github.com/getkin/kin-openapi v0.118.0 // indirect
|
||||
github.com/go-chi/chi v4.1.2+incompatible // indirect
|
||||
github.com/go-kit/kit v0.13.0 // indirect
|
||||
github.com/go-kit/log v0.2.1 // indirect
|
||||
github.com/go-logfmt/logfmt v0.6.0 // indirect
|
||||
github.com/go-logr/logr v1.2.4 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/francoispqt/gojay v1.2.13 // indirect
|
||||
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
||||
github.com/fxamacker/cbor/v2 v2.8.0 // indirect
|
||||
github.com/gammazero/deque v1.0.0 // indirect
|
||||
github.com/getkin/kin-openapi v0.132.0 // indirect
|
||||
github.com/go-chi/chi/v5 v5.2.1 // indirect
|
||||
github.com/go-jose/go-jose/v3 v3.0.4 // indirect
|
||||
github.com/go-jose/go-jose/v4 v4.1.0 // indirect
|
||||
github.com/go-logr/logr v1.4.2 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.20.0 // indirect
|
||||
github.com/go-openapi/swag v0.22.4 // indirect
|
||||
github.com/go-sql-driver/mysql v1.7.1 // indirect
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.21.1 // indirect
|
||||
github.com/go-openapi/swag v0.23.1 // indirect
|
||||
github.com/go-sql-driver/mysql v1.9.2 // indirect
|
||||
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
|
||||
github.com/go-viper/mapstructure/v2 v2.2.1 // indirect
|
||||
github.com/gofrs/uuid v4.4.0+incompatible // indirect
|
||||
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
|
||||
github.com/golang/glog v1.1.2 // indirect
|
||||
github.com/golang/mock v1.6.0 // indirect
|
||||
github.com/golang/protobuf v1.5.3 // indirect
|
||||
github.com/golang/snappy v0.0.4 // indirect
|
||||
github.com/google/cel-go v0.18.0 // indirect
|
||||
github.com/google/certificate-transparency-go v1.1.4 // indirect
|
||||
github.com/google/go-tpm v0.3.3 // indirect
|
||||
github.com/golang-jwt/jwt/v5 v5.2.2 // indirect
|
||||
github.com/golang/protobuf v1.5.4 // indirect
|
||||
github.com/golang/snappy v1.0.0 // indirect
|
||||
github.com/google/brotli/go/cbrotli v0.0.0-20241111155135-4303850b01d6 // indirect
|
||||
github.com/google/cel-go v0.25.0 // indirect
|
||||
github.com/google/certificate-transparency-go v1.3.1 // indirect
|
||||
github.com/google/go-tpm v0.9.5 // indirect
|
||||
github.com/google/go-tspi v0.3.0 // indirect
|
||||
github.com/google/pprof v0.0.0-20230912144702-c363fe2c2ed8 // indirect
|
||||
github.com/google/uuid v1.3.1 // indirect
|
||||
github.com/gorilla/handlers v1.5.1 // indirect
|
||||
github.com/gorilla/mux v1.8.0 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0 // indirect
|
||||
github.com/hashicorp/golang-lru v0.5.4 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/huandu/xstrings v1.4.0 // indirect
|
||||
github.com/imdario/mergo v0.3.16 // indirect
|
||||
github.com/google/pprof v0.0.0-20250602020802-c6617b811d0e // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/gorilla/handlers v1.5.2 // indirect
|
||||
github.com/gorilla/mux v1.8.1 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // indirect
|
||||
github.com/hashicorp/golang-lru v1.0.2 // indirect
|
||||
github.com/huandu/xstrings v1.5.0 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/invopop/yaml v0.2.0 // indirect
|
||||
github.com/jackc/chunkreader/v2 v2.0.1 // indirect
|
||||
github.com/jackc/pgconn v1.14.1 // indirect
|
||||
github.com/jackc/pgio v1.0.0 // indirect
|
||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||
github.com/jackc/pgproto3/v2 v2.3.2 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
|
||||
github.com/jackc/pgtype v1.14.0 // indirect
|
||||
github.com/jackc/pgx/v4 v4.18.1 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
||||
github.com/jackc/pgx/v5 v5.7.5 // indirect
|
||||
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/kevburnsjr/skipfilter v0.0.1 // indirect
|
||||
github.com/klauspost/compress v1.16.7 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.5 // indirect
|
||||
github.com/libdns/libdns v0.2.1 // indirect
|
||||
github.com/magiconair/properties v1.8.7 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/klauspost/compress v1.18.0 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.10 // indirect
|
||||
github.com/kylelemons/godebug v1.1.0 // indirect
|
||||
github.com/libdns/libdns v1.1.0 // indirect
|
||||
github.com/mailru/easyjson v0.9.0 // indirect
|
||||
github.com/manifoldco/promptui v0.9.0 // indirect
|
||||
github.com/mastercactapus/proxyprotocol v0.0.4 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.19 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
|
||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/maypok86/otter v1.2.4 // indirect
|
||||
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
|
||||
github.com/mholt/acmez v1.2.0 // indirect
|
||||
github.com/micromdm/scep/v2 v2.1.0 // indirect
|
||||
github.com/miekg/dns v1.1.56 // indirect
|
||||
github.com/mholt/acmez/v3 v3.1.2 // indirect
|
||||
github.com/miekg/dns v1.1.66 // indirect
|
||||
github.com/mitchellh/copystructure v1.2.0 // indirect
|
||||
github.com/mitchellh/go-ps v1.0.0 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
|
||||
github.com/mschoch/smat v0.2.0 // indirect
|
||||
github.com/onsi/ginkgo/v2 v2.12.0 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.0.9 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 // indirect
|
||||
github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 // indirect
|
||||
github.com/onsi/ginkgo/v2 v2.23.4 // indirect
|
||||
github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||
github.com/perimeterx/marshmallow v1.1.5 // indirect
|
||||
github.com/pires/go-proxyproto v0.8.1 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/prometheus/client_golang v1.16.0 // indirect
|
||||
github.com/prometheus/client_model v0.4.0 // indirect
|
||||
github.com/prometheus/common v0.44.0 // indirect
|
||||
github.com/prometheus/procfs v0.11.1 // indirect
|
||||
github.com/quic-go/qpack v0.4.0 // indirect
|
||||
github.com/quic-go/qtls-go1-20 v0.3.4 // indirect
|
||||
github.com/quic-go/quic-go v0.38.1 // indirect
|
||||
github.com/rs/xid v1.5.0 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/prometheus/client_model v0.6.2 // indirect
|
||||
github.com/prometheus/common v0.64.0 // indirect
|
||||
github.com/prometheus/procfs v0.16.1 // indirect
|
||||
github.com/quic-go/qpack v0.5.1 // indirect
|
||||
github.com/quic-go/quic-go v0.52.0 // indirect
|
||||
github.com/rs/xid v1.6.0 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/shopspring/decimal v1.3.1 // indirect
|
||||
github.com/sagikazarmark/locafero v0.9.0 // indirect
|
||||
github.com/shopspring/decimal v1.4.0 // indirect
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
|
||||
github.com/sirupsen/logrus v1.9.3 // indirect
|
||||
github.com/slackhq/nebula v1.7.2 // indirect
|
||||
github.com/smallstep/certificates v0.24.3-rc1 // indirect
|
||||
github.com/smallstep/go-attestation v0.4.4-0.20230509120429-e17291421738 // indirect
|
||||
github.com/smallstep/nosql v0.6.0 // indirect
|
||||
github.com/smallstep/truststore v0.12.1 // indirect
|
||||
github.com/spf13/afero v1.9.5 // indirect
|
||||
github.com/spf13/cast v1.5.1 // indirect
|
||||
github.com/spf13/cobra v1.7.0 // indirect
|
||||
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/spf13/viper v1.16.0 // indirect
|
||||
github.com/slackhq/nebula v1.9.5 // indirect
|
||||
github.com/smallstep/certificates v0.28.3 // indirect
|
||||
github.com/smallstep/cli-utils v0.12.1 // indirect
|
||||
github.com/smallstep/linkedca v0.23.0 // indirect
|
||||
github.com/smallstep/nosql v0.7.0 // indirect
|
||||
github.com/smallstep/pkcs7 v0.2.1 // indirect
|
||||
github.com/smallstep/scep v0.0.0-20250318231241-a25cabb69492 // indirect
|
||||
github.com/smallstep/truststore v0.13.0 // indirect
|
||||
github.com/sourcegraph/conc v0.3.0 // indirect
|
||||
github.com/spf13/afero v1.14.0 // indirect
|
||||
github.com/spf13/cast v1.9.2 // indirect
|
||||
github.com/spf13/pflag v1.0.6 // indirect
|
||||
github.com/spf13/viper v1.20.1 // indirect
|
||||
github.com/stoewer/go-strcase v1.3.0 // indirect
|
||||
github.com/subosito/gotenv v1.4.2 // indirect
|
||||
github.com/tailscale/tscert v0.0.0-20230806124524-28a91b69a046 // indirect
|
||||
github.com/tidwall/gjson v1.15.0 // indirect
|
||||
github.com/subosito/gotenv v1.6.0 // indirect
|
||||
github.com/tailscale/tscert v0.0.0-20240608151842-d3f834017e53 // indirect
|
||||
github.com/tidwall/gjson v1.18.0 // indirect
|
||||
github.com/tidwall/match v1.1.1 // indirect
|
||||
github.com/tidwall/pretty v1.2.1 // indirect
|
||||
github.com/tidwall/sjson v1.2.5 // indirect
|
||||
github.com/unrolled/secure v1.13.0 // indirect
|
||||
github.com/urfave/cli v1.22.14 // indirect
|
||||
github.com/unrolled/secure v1.17.0 // indirect
|
||||
github.com/urfave/cli v1.22.16 // indirect
|
||||
github.com/x448/float16 v0.8.4 // indirect
|
||||
github.com/yosida95/uritemplate/v3 v3.0.2 // indirect
|
||||
github.com/yuin/goldmark v1.5.5 // indirect
|
||||
github.com/yuin/goldmark v1.7.12 // indirect
|
||||
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc // indirect
|
||||
github.com/zeebo/blake3 v0.2.3 // indirect
|
||||
go.etcd.io/bbolt v1.3.7 // indirect
|
||||
go.mozilla.org/pkcs7 v0.0.0-20210826202110-33d05740a352 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.42.0 // indirect
|
||||
go.opentelemetry.io/contrib/propagators/autoprop v0.42.0 // indirect
|
||||
go.opentelemetry.io/contrib/propagators/aws v1.17.0 // indirect
|
||||
go.opentelemetry.io/contrib/propagators/b3 v1.17.0 // indirect
|
||||
go.opentelemetry.io/contrib/propagators/jaeger v1.17.0 // indirect
|
||||
go.opentelemetry.io/contrib/propagators/ot v1.17.0 // indirect
|
||||
go.opentelemetry.io/otel v1.16.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.16.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.16.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.16.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.16.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v1.16.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.16.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v0.19.0 // indirect
|
||||
go.step.sm/cli-utils v0.8.0 // indirect
|
||||
go.step.sm/crypto v0.35.0 // indirect
|
||||
go.step.sm/linkedca v0.20.0 // indirect
|
||||
github.com/zeebo/blake3 v0.2.4 // indirect
|
||||
go.etcd.io/bbolt v1.4.0 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 // indirect
|
||||
go.opentelemetry.io/contrib/propagators/autoprop v0.60.0 // indirect
|
||||
go.opentelemetry.io/contrib/propagators/aws v1.35.0 // indirect
|
||||
go.opentelemetry.io/contrib/propagators/b3 v1.35.0 // indirect
|
||||
go.opentelemetry.io/contrib/propagators/jaeger v1.35.0 // indirect
|
||||
go.opentelemetry.io/contrib/propagators/ot v1.35.0 // indirect
|
||||
go.opentelemetry.io/otel v1.35.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.35.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v1.35.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.35.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v1.6.0 // indirect
|
||||
go.step.sm/crypto v0.66.0 // indirect
|
||||
go.uber.org/automaxprocs v1.6.0 // indirect
|
||||
go.uber.org/mock v0.5.2 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
golang.org/x/crypto v0.13.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
|
||||
golang.org/x/mod v0.12.0 // indirect
|
||||
golang.org/x/net v0.15.0 // indirect
|
||||
golang.org/x/sync v0.3.0 // indirect
|
||||
golang.org/x/sys v0.12.0 // indirect
|
||||
golang.org/x/term v0.12.0 // indirect
|
||||
golang.org/x/text v0.13.0 // indirect
|
||||
golang.org/x/tools v0.13.0 // indirect
|
||||
google.golang.org/genproto v0.0.0-20230803162519-f966b187b2e5 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20230911183012-2d3300fd4832 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230911183012-2d3300fd4832 // indirect
|
||||
google.golang.org/grpc v1.58.0 // indirect
|
||||
google.golang.org/protobuf v1.31.0 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
go.uber.org/zap v1.27.0 // indirect
|
||||
go.uber.org/zap/exp v0.3.0 // indirect
|
||||
golang.org/x/crypto v0.38.0 // indirect
|
||||
golang.org/x/crypto/x509roots/fallback v0.0.0-20250531095911-4f9f0ca9fcfb // indirect
|
||||
golang.org/x/exp v0.0.0-20250531010427-b6e5de432a8b // indirect
|
||||
golang.org/x/mod v0.24.0 // indirect
|
||||
golang.org/x/net v0.40.0 // indirect
|
||||
golang.org/x/oauth2 v0.30.0 // indirect
|
||||
golang.org/x/sync v0.14.0 // indirect
|
||||
golang.org/x/sys v0.33.0 // indirect
|
||||
golang.org/x/term v0.32.0 // indirect
|
||||
golang.org/x/text v0.25.0 // indirect
|
||||
golang.org/x/time v0.11.0 // indirect
|
||||
golang.org/x/tools v0.33.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250528174236-200df99c418a // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a // indirect
|
||||
google.golang.org/grpc v1.72.2 // indirect
|
||||
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1 // indirect
|
||||
google.golang.org/protobuf v1.36.6 // indirect
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
|
||||
gopkg.in/square/go-jose.v2 v2.6.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
howett.net/plist v1.0.0 // indirect
|
||||
howett.net/plist v1.0.1 // indirect
|
||||
)
|
||||
|
||||
2069
caddy/go.sum
2069
caddy/go.sum
File diff suppressed because it is too large
Load Diff
50
caddy/php-cli.go
Normal file
50
caddy/php-cli.go
Normal file
@@ -0,0 +1,50 @@
|
||||
package caddy
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
caddycmd "github.com/caddyserver/caddy/v2/cmd"
|
||||
"frankenphp.dev"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func init() {
|
||||
caddycmd.RegisterCommand(caddycmd.Command{
|
||||
Name: "php-cli",
|
||||
Usage: "script.php [args ...]",
|
||||
Short: "Runs a PHP command",
|
||||
Long: `
|
||||
Executes a PHP script similarly to the CLI SAPI.`,
|
||||
CobraFunc: func(cmd *cobra.Command) {
|
||||
cmd.DisableFlagParsing = true
|
||||
cmd.RunE = caddycmd.WrapCommandFuncForCobra(cmdPHPCLI)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func cmdPHPCLI(fs caddycmd.Flags) (int, error) {
|
||||
args := os.Args[2:]
|
||||
if len(args) < 1 {
|
||||
return 1, errors.New("the path to the PHP script is required")
|
||||
}
|
||||
|
||||
if frankenphp.EmbeddedAppPath != "" {
|
||||
if _, err := os.Stat(args[0]); err != nil {
|
||||
args[0] = filepath.Join(frankenphp.EmbeddedAppPath, args[0])
|
||||
}
|
||||
}
|
||||
|
||||
var status int
|
||||
if len(args) >= 2 && args[0] == "-r" {
|
||||
status = frankenphp.ExecutePHPCode(args[1])
|
||||
} else {
|
||||
status = frankenphp.ExecuteScriptCLI(args[0], args)
|
||||
}
|
||||
|
||||
os.Exit(status)
|
||||
|
||||
return status, nil
|
||||
}
|
||||
353
caddy/php-server.go
Normal file
353
caddy/php-server.go
Normal file
@@ -0,0 +1,353 @@
|
||||
package caddy
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"log"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
mercureModule "github.com/dunglas/mercure/caddy"
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig"
|
||||
caddycmd "github.com/caddyserver/caddy/v2/cmd"
|
||||
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
||||
"github.com/caddyserver/caddy/v2/modules/caddyhttp/encode"
|
||||
"github.com/caddyserver/caddy/v2/modules/caddyhttp/fileserver"
|
||||
"github.com/caddyserver/caddy/v2/modules/caddyhttp/rewrite"
|
||||
"github.com/caddyserver/certmagic"
|
||||
"frankenphp.dev"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func init() {
|
||||
caddycmd.RegisterCommand(caddycmd.Command{
|
||||
Name: "php-server",
|
||||
Usage: "[--domain=<example.com>] [--root=<path>] [--listen=<addr>] [--worker=/path/to/worker.php<,nb-workers>] [--watch[=<glob-pattern>]]... [--access-log] [--debug] [--no-compress] [--mercure]",
|
||||
Short: "Spins up a production-ready PHP server",
|
||||
Long: `
|
||||
A simple but production-ready PHP server. Useful for quick deployments,
|
||||
demos, and development.
|
||||
|
||||
The listener's socket address can be customized with the --listen flag.
|
||||
|
||||
If a domain name is specified with --domain, the default listener address
|
||||
will be changed to the HTTPS port and the server will use HTTPS. If using
|
||||
a public domain, ensure A/AAAA records are properly configured before
|
||||
using this option.
|
||||
|
||||
For more advanced use cases, see https://github.com/dunglas/frankenphp/blob/main/docs/config.md`,
|
||||
CobraFunc: func(cmd *cobra.Command) {
|
||||
cmd.Flags().StringP("domain", "d", "", "Domain name at which to serve the files")
|
||||
cmd.Flags().StringP("root", "r", "", "The path to the root of the site")
|
||||
cmd.Flags().StringP("listen", "l", "", "The address to which to bind the listener")
|
||||
cmd.Flags().StringArrayP("worker", "w", []string{}, "Worker script")
|
||||
cmd.Flags().StringArray("watch", []string{}, "Glob pattern of directories and files to watch for changes")
|
||||
cmd.Flags().BoolP("access-log", "a", false, "Enable the access log")
|
||||
cmd.Flags().BoolP("debug", "v", false, "Enable verbose debug logs")
|
||||
cmd.Flags().BoolP("mercure", "m", false, "Enable the built-in Mercure.rocks hub")
|
||||
cmd.Flags().Bool("no-compress", false, "Disable Zstandard, Brotli and Gzip compression")
|
||||
|
||||
cmd.Flags().Lookup("watch").NoOptDefVal = defaultWatchPattern
|
||||
|
||||
cmd.RunE = caddycmd.WrapCommandFuncForCobra(cmdPHPServer)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// cmdPHPServer is freely inspired from the file-server command of the Caddy server (Apache License 2.0, Matthew Holt and The Caddy Authors)
|
||||
func cmdPHPServer(fs caddycmd.Flags) (int, error) {
|
||||
caddy.TrapSignals()
|
||||
|
||||
domain := fs.String("domain")
|
||||
root := fs.String("root")
|
||||
listen := fs.String("listen")
|
||||
accessLog := fs.Bool("access-log")
|
||||
debug := fs.Bool("debug")
|
||||
compress := !fs.Bool("no-compress")
|
||||
mercure := fs.Bool("mercure")
|
||||
|
||||
workers, err := fs.GetStringArray("worker")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
watch, err := fs.GetStringArray("watch")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if frankenphp.EmbeddedAppPath != "" {
|
||||
if err := os.Chdir(frankenphp.EmbeddedAppPath); err != nil {
|
||||
return caddy.ExitCodeFailedStartup, err
|
||||
}
|
||||
}
|
||||
|
||||
var workersOption []workerConfig
|
||||
if len(workers) != 0 {
|
||||
workersOption = make([]workerConfig, 0, len(workers))
|
||||
for _, worker := range workers {
|
||||
parts := strings.SplitN(worker, ",", 2)
|
||||
|
||||
var num uint64
|
||||
if len(parts) > 1 {
|
||||
num, _ = strconv.ParseUint(parts[1], 10, 32)
|
||||
}
|
||||
|
||||
workersOption = append(workersOption, workerConfig{FileName: parts[0], Num: int(num)})
|
||||
}
|
||||
workersOption[0].Watch = watch
|
||||
}
|
||||
|
||||
if frankenphp.EmbeddedAppPath != "" {
|
||||
if _, err := os.Stat("php.ini"); err == nil {
|
||||
iniScanDir := os.Getenv("PHP_INI_SCAN_DIR")
|
||||
|
||||
if err := os.Setenv("PHP_INI_SCAN_DIR", iniScanDir+":"+frankenphp.EmbeddedAppPath); err != nil {
|
||||
return caddy.ExitCodeFailedStartup, err
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := os.Stat("Caddyfile"); err == nil {
|
||||
config, _, err := caddycmd.LoadConfig("Caddyfile", "caddyfile")
|
||||
if err != nil {
|
||||
return caddy.ExitCodeFailedStartup, err
|
||||
}
|
||||
|
||||
if err = caddy.Load(config, true); err != nil {
|
||||
return caddy.ExitCodeFailedStartup, err
|
||||
}
|
||||
|
||||
select {}
|
||||
}
|
||||
|
||||
if root == "" {
|
||||
root = defaultDocumentRoot
|
||||
}
|
||||
}
|
||||
|
||||
const indexFile = "index.php"
|
||||
extensions := []string{".php"}
|
||||
tryFiles := []string{"{http.request.uri.path}", "{http.request.uri.path}/" + indexFile, indexFile}
|
||||
|
||||
rrs := true
|
||||
phpHandler := FrankenPHPModule{
|
||||
Root: root,
|
||||
SplitPath: extensions,
|
||||
ResolveRootSymlink: &rrs,
|
||||
}
|
||||
|
||||
// route to redirect to canonical path if index PHP file
|
||||
redirMatcherSet := caddy.ModuleMap{
|
||||
"file": caddyconfig.JSON(fileserver.MatchFile{
|
||||
Root: root,
|
||||
TryFiles: []string{"{http.request.uri.path}/" + indexFile},
|
||||
}, nil),
|
||||
"not": caddyconfig.JSON(caddyhttp.MatchNot{
|
||||
MatcherSetsRaw: []caddy.ModuleMap{
|
||||
{
|
||||
"path": caddyconfig.JSON(caddyhttp.MatchPath{"*/"}, nil),
|
||||
},
|
||||
},
|
||||
}, nil),
|
||||
}
|
||||
redirHandler := caddyhttp.StaticResponse{
|
||||
StatusCode: caddyhttp.WeakString(strconv.Itoa(http.StatusPermanentRedirect)),
|
||||
Headers: http.Header{"Location": []string{"{http.request.orig_uri.path}/"}},
|
||||
}
|
||||
redirRoute := caddyhttp.Route{
|
||||
MatcherSetsRaw: []caddy.ModuleMap{redirMatcherSet},
|
||||
HandlersRaw: []json.RawMessage{caddyconfig.JSONModuleObject(redirHandler, "handler", "static_response", nil)},
|
||||
}
|
||||
|
||||
// route to rewrite to PHP index file
|
||||
rewriteMatcherSet := caddy.ModuleMap{
|
||||
"file": caddyconfig.JSON(fileserver.MatchFile{
|
||||
Root: root,
|
||||
TryFiles: tryFiles,
|
||||
SplitPath: extensions,
|
||||
}, nil),
|
||||
}
|
||||
rewriteHandler := rewrite.Rewrite{
|
||||
URI: "{http.matchers.file.relative}",
|
||||
}
|
||||
rewriteRoute := caddyhttp.Route{
|
||||
MatcherSetsRaw: []caddy.ModuleMap{rewriteMatcherSet},
|
||||
HandlersRaw: []json.RawMessage{caddyconfig.JSONModuleObject(rewriteHandler, "handler", "rewrite", nil)},
|
||||
}
|
||||
|
||||
// route to actually pass requests to PHP files;
|
||||
// match only requests that are for PHP files
|
||||
var pathList []string
|
||||
for _, ext := range extensions {
|
||||
pathList = append(pathList, "*"+ext)
|
||||
}
|
||||
phpMatcherSet := caddy.ModuleMap{
|
||||
"path": caddyconfig.JSON(pathList, nil),
|
||||
}
|
||||
|
||||
// create the PHP route which is
|
||||
// conditional on matching PHP files
|
||||
phpRoute := caddyhttp.Route{
|
||||
MatcherSetsRaw: []caddy.ModuleMap{phpMatcherSet},
|
||||
HandlersRaw: []json.RawMessage{caddyconfig.JSONModuleObject(phpHandler, "handler", "php", nil)},
|
||||
}
|
||||
|
||||
fileRoute := caddyhttp.Route{
|
||||
MatcherSetsRaw: []caddy.ModuleMap{},
|
||||
HandlersRaw: []json.RawMessage{caddyconfig.JSONModuleObject(fileserver.FileServer{Root: root}, "handler", "file_server", nil)},
|
||||
}
|
||||
|
||||
subroute := caddyhttp.Subroute{
|
||||
Routes: caddyhttp.RouteList{redirRoute, rewriteRoute, phpRoute, fileRoute},
|
||||
}
|
||||
|
||||
if compress {
|
||||
gzip, err := caddy.GetModule("http.encoders.gzip")
|
||||
if err != nil {
|
||||
return caddy.ExitCodeFailedStartup, err
|
||||
}
|
||||
|
||||
br, err := caddy.GetModule("http.encoders.br")
|
||||
if err != nil {
|
||||
return caddy.ExitCodeFailedStartup, err
|
||||
}
|
||||
|
||||
zstd, err := caddy.GetModule("http.encoders.zstd")
|
||||
if err != nil {
|
||||
return caddy.ExitCodeFailedStartup, err
|
||||
}
|
||||
|
||||
var (
|
||||
encodings caddy.ModuleMap
|
||||
prefer []string
|
||||
)
|
||||
if brotli {
|
||||
encodings = caddy.ModuleMap{
|
||||
"zstd": caddyconfig.JSON(zstd.New(), nil),
|
||||
"br": caddyconfig.JSON(br.New(), nil),
|
||||
"gzip": caddyconfig.JSON(gzip.New(), nil),
|
||||
}
|
||||
prefer = []string{"zstd", "br", "gzip"}
|
||||
} else {
|
||||
encodings = caddy.ModuleMap{
|
||||
"zstd": caddyconfig.JSON(zstd.New(), nil),
|
||||
"gzip": caddyconfig.JSON(gzip.New(), nil),
|
||||
}
|
||||
prefer = []string{"zstd", "gzip"}
|
||||
}
|
||||
|
||||
encodeRoute := caddyhttp.Route{
|
||||
MatcherSetsRaw: []caddy.ModuleMap{},
|
||||
HandlersRaw: []json.RawMessage{caddyconfig.JSONModuleObject(encode.Encode{
|
||||
EncodingsRaw: encodings,
|
||||
Prefer: prefer,
|
||||
}, "handler", "encode", nil)},
|
||||
}
|
||||
|
||||
subroute.Routes = append(caddyhttp.RouteList{encodeRoute}, subroute.Routes...)
|
||||
}
|
||||
|
||||
if mercure {
|
||||
mercurePublisherJwtKey := os.Getenv("MERCURE_PUBLISHER_JWT_KEY")
|
||||
if mercurePublisherJwtKey == "" {
|
||||
panic(`The "MERCURE_PUBLISHER_JWT_KEY" environment variable must be set to use the Mercure.rocks hub`)
|
||||
}
|
||||
|
||||
mercureSubscriberJwtKey := os.Getenv("MERCURE_SUBSCRIBER_JWT_KEY")
|
||||
if mercureSubscriberJwtKey == "" {
|
||||
panic(`The "MERCURE_SUBSCRIBER_JWT_KEY" environment variable must be set to use the Mercure.rocks hub`)
|
||||
}
|
||||
|
||||
mercureRoute := caddyhttp.Route{
|
||||
HandlersRaw: []json.RawMessage{caddyconfig.JSONModuleObject(
|
||||
mercureModule.Mercure{
|
||||
PublisherJWT: mercureModule.JWTConfig{
|
||||
Alg: os.Getenv("MERCURE_PUBLISHER_JWT_ALG"),
|
||||
Key: mercurePublisherJwtKey,
|
||||
},
|
||||
SubscriberJWT: mercureModule.JWTConfig{
|
||||
Alg: os.Getenv("MERCURE_SUBSCRIBER_JWT_ALG"),
|
||||
Key: mercureSubscriberJwtKey,
|
||||
},
|
||||
},
|
||||
"handler",
|
||||
"mercure",
|
||||
nil,
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
subroute.Routes = append(caddyhttp.RouteList{mercureRoute}, subroute.Routes...)
|
||||
}
|
||||
|
||||
route := caddyhttp.Route{
|
||||
HandlersRaw: []json.RawMessage{caddyconfig.JSONModuleObject(subroute, "handler", "subroute", nil)},
|
||||
}
|
||||
|
||||
if domain != "" {
|
||||
route.MatcherSetsRaw = []caddy.ModuleMap{
|
||||
{
|
||||
"host": caddyconfig.JSON(caddyhttp.MatchHost{domain}, nil),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
server := &caddyhttp.Server{
|
||||
ReadHeaderTimeout: caddy.Duration(10 * time.Second),
|
||||
IdleTimeout: caddy.Duration(30 * time.Second),
|
||||
MaxHeaderBytes: 1024 * 10,
|
||||
Routes: caddyhttp.RouteList{route},
|
||||
}
|
||||
if listen == "" {
|
||||
if domain == "" {
|
||||
listen = ":80"
|
||||
} else {
|
||||
listen = ":" + strconv.Itoa(certmagic.HTTPSPort)
|
||||
}
|
||||
}
|
||||
server.Listen = []string{listen}
|
||||
if accessLog {
|
||||
server.Logs = &caddyhttp.ServerLogConfig{}
|
||||
}
|
||||
|
||||
httpApp := caddyhttp.App{
|
||||
Servers: map[string]*caddyhttp.Server{"php": server},
|
||||
}
|
||||
|
||||
var f bool
|
||||
cfg := &caddy.Config{
|
||||
Admin: &caddy.AdminConfig{
|
||||
Disabled: true,
|
||||
Config: &caddy.ConfigSettings{
|
||||
Persist: &f,
|
||||
},
|
||||
},
|
||||
AppsRaw: caddy.ModuleMap{
|
||||
"http": caddyconfig.JSON(httpApp, nil),
|
||||
"frankenphp": caddyconfig.JSON(FrankenPHPApp{Workers: workersOption}, nil),
|
||||
},
|
||||
}
|
||||
|
||||
if debug {
|
||||
cfg.Logging = &caddy.Logging{
|
||||
Logs: map[string]*caddy.CustomLog{
|
||||
"default": {
|
||||
BaseLog: caddy.BaseLog{Level: slog.LevelDebug.String()},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
err = caddy.Run(cfg)
|
||||
if err != nil {
|
||||
return caddy.ExitCodeFailedStartup, err
|
||||
}
|
||||
|
||||
log.Printf("Caddy serving PHP app on %s", listen)
|
||||
|
||||
select {}
|
||||
}
|
||||
38
caddy/watcher_test.go
Normal file
38
caddy/watcher_test.go
Normal file
@@ -0,0 +1,38 @@
|
||||
//go:build !nowatcher
|
||||
|
||||
package caddy_test
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/caddyserver/caddy/v2/caddytest"
|
||||
)
|
||||
|
||||
func TestWorkerWithInactiveWatcher(t *testing.T) {
|
||||
tester := caddytest.NewTester(t)
|
||||
tester.InitServer(`
|
||||
{
|
||||
skip_install_trust
|
||||
admin localhost:2999
|
||||
http_port `+testPort+`
|
||||
|
||||
frankenphp {
|
||||
worker {
|
||||
file ../testdata/worker-with-counter.php
|
||||
num 1
|
||||
watch ./**/*.php
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
localhost:`+testPort+` {
|
||||
root ../testdata
|
||||
rewrite worker-with-counter.php
|
||||
php
|
||||
}
|
||||
`, "caddyfile")
|
||||
|
||||
tester.AssertGetResponse("http://localhost:"+testPort, http.StatusOK, "requests:1")
|
||||
tester.AssertGetResponse("http://localhost:"+testPort, http.StatusOK, "requests:2")
|
||||
}
|
||||
363
cgi.go
363
cgi.go
@@ -1,224 +1,206 @@
|
||||
package frankenphp
|
||||
|
||||
// #cgo nocallback frankenphp_register_bulk
|
||||
// #cgo nocallback frankenphp_register_variables_from_request_info
|
||||
// #cgo nocallback frankenphp_register_variable_safe
|
||||
// #cgo nocallback frankenphp_register_single
|
||||
// #cgo noescape frankenphp_register_bulk
|
||||
// #cgo noescape frankenphp_register_variables_from_request_info
|
||||
// #cgo noescape frankenphp_register_variable_safe
|
||||
// #cgo noescape frankenphp_register_single
|
||||
// #include <php_variables.h>
|
||||
// #include "frankenphp.h"
|
||||
import "C"
|
||||
import (
|
||||
"crypto/tls"
|
||||
"net"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"unsafe"
|
||||
|
||||
"frankenphp.dev/internal/phpheaders"
|
||||
)
|
||||
|
||||
// populateEnv returns a set of CGI environment variables for the request.
|
||||
var knownServerKeys = []string{
|
||||
"CONTENT_LENGTH",
|
||||
"DOCUMENT_ROOT",
|
||||
"DOCUMENT_URI",
|
||||
"GATEWAY_INTERFACE",
|
||||
"HTTP_HOST",
|
||||
"HTTPS",
|
||||
"PATH_INFO",
|
||||
"PHP_SELF",
|
||||
"REMOTE_ADDR",
|
||||
"REMOTE_HOST",
|
||||
"REMOTE_PORT",
|
||||
"REQUEST_SCHEME",
|
||||
"SCRIPT_FILENAME",
|
||||
"SCRIPT_NAME",
|
||||
"SERVER_NAME",
|
||||
"SERVER_PORT",
|
||||
"SERVER_PROTOCOL",
|
||||
"SERVER_SOFTWARE",
|
||||
"SSL_PROTOCOL",
|
||||
"AUTH_TYPE",
|
||||
"REMOTE_IDENT",
|
||||
"CONTENT_TYPE",
|
||||
"PATH_TRANSLATED",
|
||||
"QUERY_STRING",
|
||||
"REMOTE_USER",
|
||||
"REQUEST_METHOD",
|
||||
"REQUEST_URI",
|
||||
}
|
||||
|
||||
// computeKnownVariables returns a set of CGI environment variables for the request.
|
||||
//
|
||||
// TODO: handle this case https://github.com/caddyserver/caddy/issues/3718
|
||||
// Inspired by https://github.com/caddyserver/caddy/blob/master/modules/caddyhttp/reverseproxy/fastcgi/fastcgi.go
|
||||
func populateEnv(request *http.Request) error {
|
||||
fc, ok := FromContext(request.Context())
|
||||
if !ok {
|
||||
panic("not a FrankenPHP request")
|
||||
func addKnownVariablesToServer(thread *phpThread, fc *frankenPHPContext, trackVarsArray *C.zval) {
|
||||
request := fc.request
|
||||
keys := mainThread.knownServerKeys
|
||||
// Separate remote IP and port; more lenient than net.SplitHostPort
|
||||
var ip, port string
|
||||
if idx := strings.LastIndex(request.RemoteAddr, ":"); idx > -1 {
|
||||
ip = request.RemoteAddr[:idx]
|
||||
port = request.RemoteAddr[idx+1:]
|
||||
} else {
|
||||
ip = request.RemoteAddr
|
||||
}
|
||||
|
||||
if fc.populated {
|
||||
return nil
|
||||
}
|
||||
// Remove [] from IPv6 addresses
|
||||
ip = strings.Replace(ip, "[", "", 1)
|
||||
ip = strings.Replace(ip, "]", "", 1)
|
||||
|
||||
_, addrOk := fc.Env["REMOTE_ADDR"]
|
||||
_, portOk := fc.Env["REMOTE_PORT"]
|
||||
if !addrOk || !portOk {
|
||||
// Separate remote IP and port; more lenient than net.SplitHostPort
|
||||
var ip, port string
|
||||
if idx := strings.LastIndex(request.RemoteAddr, ":"); idx > -1 {
|
||||
ip = request.RemoteAddr[:idx]
|
||||
port = request.RemoteAddr[idx+1:]
|
||||
} else {
|
||||
ip = request.RemoteAddr
|
||||
}
|
||||
|
||||
// Remove [] from IPv6 addresses
|
||||
ip = strings.Replace(ip, "[", "", 1)
|
||||
ip = strings.Replace(ip, "]", "", 1)
|
||||
|
||||
if _, ok := fc.Env["REMOTE_ADDR"]; !ok {
|
||||
fc.Env["REMOTE_ADDR"] = ip
|
||||
}
|
||||
if _, ok := fc.Env["REMOTE_HOST"]; !ok {
|
||||
fc.Env["REMOTE_HOST"] = ip // For speed, remote host lookups disabled
|
||||
}
|
||||
if _, ok := fc.Env["REMOTE_PORT"]; !ok {
|
||||
fc.Env["REMOTE_PORT"] = port
|
||||
}
|
||||
}
|
||||
|
||||
if _, ok := fc.Env["DOCUMENT_ROOT"]; !ok {
|
||||
// make sure file root is absolute
|
||||
root, err := filepath.Abs(fc.DocumentRoot)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if fc.ResolveRootSymlink {
|
||||
if root, err = filepath.EvalSymlinks(root); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
fc.Env["DOCUMENT_ROOT"] = root
|
||||
}
|
||||
|
||||
fpath := request.URL.Path
|
||||
scriptName := fpath
|
||||
|
||||
docURI := fpath
|
||||
// split "actual path" from "path info" if configured
|
||||
if splitPos := splitPos(fc, fpath); splitPos > -1 {
|
||||
docURI = fpath[:splitPos]
|
||||
fc.Env["PATH_INFO"] = fpath[splitPos:]
|
||||
|
||||
// Strip PATH_INFO from SCRIPT_NAME
|
||||
scriptName = strings.TrimSuffix(scriptName, fc.Env["PATH_INFO"])
|
||||
}
|
||||
|
||||
// SCRIPT_FILENAME is the absolute path of SCRIPT_NAME
|
||||
scriptFilename := sanitizedPathJoin(fc.Env["DOCUMENT_ROOT"], scriptName)
|
||||
|
||||
// Ensure the SCRIPT_NAME has a leading slash for compliance with RFC3875
|
||||
// Info: https://tools.ietf.org/html/rfc3875#section-4.1.13
|
||||
if scriptName != "" && !strings.HasPrefix(scriptName, "/") {
|
||||
scriptName = "/" + scriptName
|
||||
}
|
||||
|
||||
if _, ok := fc.Env["PHP_SELF"]; !ok {
|
||||
fc.Env["PHP_SELF"] = fpath
|
||||
}
|
||||
if _, ok := fc.Env["DOCUMENT_URI"]; !ok {
|
||||
fc.Env["DOCUMENT_URI"] = docURI
|
||||
}
|
||||
if _, ok := fc.Env["SCRIPT_FILENAME"]; !ok {
|
||||
fc.Env["SCRIPT_FILENAME"] = scriptFilename
|
||||
}
|
||||
if _, ok := fc.Env["SCRIPT_NAME"]; !ok {
|
||||
fc.Env["SCRIPT_NAME"] = scriptName
|
||||
}
|
||||
|
||||
if _, ok := fc.Env["REQUEST_SCHEME"]; !ok {
|
||||
if request.TLS == nil {
|
||||
fc.Env["REQUEST_SCHEME"] = "http"
|
||||
} else {
|
||||
fc.Env["REQUEST_SCHEME"] = "https"
|
||||
}
|
||||
}
|
||||
|
||||
if request.TLS != nil {
|
||||
if _, ok := fc.Env["HTTPS"]; !ok {
|
||||
fc.Env["HTTPS"] = "on"
|
||||
}
|
||||
var https string
|
||||
var sslProtocol string
|
||||
var rs string
|
||||
if request.TLS == nil {
|
||||
rs = "http"
|
||||
https = ""
|
||||
sslProtocol = ""
|
||||
} else {
|
||||
rs = "https"
|
||||
https = "on"
|
||||
|
||||
// and pass the protocol details in a manner compatible with apache's mod_ssl
|
||||
// (which is why these have a SSL_ prefix and not TLS_).
|
||||
_, sslProtocolOk := fc.Env["SSL_PROTOCOL"]
|
||||
v, versionOk := tlsProtocolStrings[request.TLS.Version]
|
||||
if !sslProtocolOk && versionOk {
|
||||
fc.Env["SSL_PROTOCOL"] = v
|
||||
// (which is why these have an SSL_ prefix and not TLS_).
|
||||
if v, ok := tlsProtocolStrings[request.TLS.Version]; ok {
|
||||
sslProtocol = v
|
||||
} else {
|
||||
sslProtocol = ""
|
||||
}
|
||||
}
|
||||
|
||||
if fc.Env["SERVER_NAME"] == "" || fc.Env["SERVER_PORT"] == "" {
|
||||
reqHost, reqPort, _ := net.SplitHostPort(request.Host)
|
||||
if fc.Env["SERVER_NAME"] == "" {
|
||||
fc.Env["SERVER_NAME"] = reqHost
|
||||
}
|
||||
if fc.Env["SERVER_PORT"] == "" {
|
||||
fc.Env["SERVER_PORT"] = reqPort
|
||||
}
|
||||
reqHost, reqPort, _ := net.SplitHostPort(request.Host)
|
||||
|
||||
if fc.Env["SERVER_NAME"] == "" {
|
||||
// whatever, just assume there was no port
|
||||
fc.Env["SERVER_NAME"] = request.Host
|
||||
}
|
||||
if reqHost == "" {
|
||||
// whatever, just assume there was no port
|
||||
reqHost = request.Host
|
||||
}
|
||||
|
||||
if reqPort == "" {
|
||||
// compliance with the CGI specification requires that
|
||||
// the SERVER_PORT variable MUST be set to the TCP/IP port number on which this request is received from the client
|
||||
// even if the port is the default port for the scheme and could otherwise be omitted from a URI.
|
||||
// https://tools.ietf.org/html/rfc3875#section-4.1.15
|
||||
if fc.Env["SERVER_PORT"] == "" {
|
||||
if fc.Env["REQUEST_SCHEME"] == "https" {
|
||||
fc.Env["SERVER_PORT"] = "443"
|
||||
} else {
|
||||
fc.Env["SERVER_PORT"] = "80"
|
||||
}
|
||||
switch rs {
|
||||
case "https":
|
||||
reqPort = "443"
|
||||
case "http":
|
||||
reqPort = "80"
|
||||
}
|
||||
}
|
||||
|
||||
// Variables defined in CGI 1.1 spec
|
||||
// Some variables are unused but cleared explicitly to prevent
|
||||
// the parent environment from interfering.
|
||||
// We never override an entry previously set
|
||||
if _, ok := fc.Env["REMOTE_IDENT"]; !ok {
|
||||
fc.Env["REMOTE_IDENT"] = "" // Not used
|
||||
}
|
||||
if _, ok := fc.Env["AUTH_TYPE"]; !ok {
|
||||
fc.Env["AUTH_TYPE"] = "" // Not used
|
||||
}
|
||||
if _, ok := fc.Env["CONTENT_LENGTH"]; !ok {
|
||||
fc.Env["CONTENT_LENGTH"] = request.Header.Get("Content-Length")
|
||||
}
|
||||
if _, ok := fc.Env["CONTENT_TYPE"]; !ok {
|
||||
fc.Env["CONTENT_TYPE"] = request.Header.Get("Content-Type")
|
||||
}
|
||||
if _, ok := fc.Env["GATEWAY_INTERFACE"]; !ok {
|
||||
fc.Env["GATEWAY_INTERFACE"] = "CGI/1.1"
|
||||
}
|
||||
if _, ok := fc.Env["QUERY_STRING"]; !ok {
|
||||
fc.Env["QUERY_STRING"] = request.URL.RawQuery
|
||||
}
|
||||
if _, ok := fc.Env["QUERY_STRING"]; !ok {
|
||||
fc.Env["QUERY_STRING"] = request.URL.RawQuery
|
||||
}
|
||||
if _, ok := fc.Env["REQUEST_METHOD"]; !ok {
|
||||
fc.Env["REQUEST_METHOD"] = request.Method
|
||||
}
|
||||
if _, ok := fc.Env["SERVER_PROTOCOL"]; !ok {
|
||||
fc.Env["SERVER_PROTOCOL"] = request.Proto
|
||||
}
|
||||
if _, ok := fc.Env["SERVER_SOFTWARE"]; !ok {
|
||||
fc.Env["SERVER_SOFTWARE"] = "FrankenPHP"
|
||||
}
|
||||
if _, ok := fc.Env["HTTP_HOST"]; !ok {
|
||||
fc.Env["HTTP_HOST"] = request.Host // added here, since not always part of headers
|
||||
}
|
||||
if _, ok := fc.Env["REQUEST_URI"]; !ok {
|
||||
fc.Env["REQUEST_URI"] = request.URL.RequestURI()
|
||||
serverPort := reqPort
|
||||
contentLength := request.Header.Get("Content-Length")
|
||||
|
||||
var requestURI string
|
||||
if fc.originalRequest != nil {
|
||||
requestURI = fc.originalRequest.URL.RequestURI()
|
||||
} else {
|
||||
requestURI = request.URL.RequestURI()
|
||||
}
|
||||
|
||||
// compliance with the CGI specification requires that
|
||||
// PATH_TRANSLATED should only exist if PATH_INFO is defined.
|
||||
// Info: https://www.ietf.org/rfc/rfc3875 Page 14
|
||||
if fc.Env["PATH_INFO"] != "" {
|
||||
fc.Env["PATH_TRANSLATED"] = sanitizedPathJoin(fc.Env["DOCUMENT_ROOT"], fc.Env["PATH_INFO"]) // Info: http://www.oreilly.com/openbook/cgi/ch02_04.html
|
||||
}
|
||||
C.frankenphp_register_bulk(
|
||||
trackVarsArray,
|
||||
packCgiVariable(keys["REMOTE_ADDR"], ip),
|
||||
packCgiVariable(keys["REMOTE_HOST"], ip),
|
||||
packCgiVariable(keys["REMOTE_PORT"], port),
|
||||
packCgiVariable(keys["DOCUMENT_ROOT"], fc.documentRoot),
|
||||
packCgiVariable(keys["PATH_INFO"], fc.pathInfo),
|
||||
packCgiVariable(keys["PHP_SELF"], request.URL.Path),
|
||||
packCgiVariable(keys["DOCUMENT_URI"], fc.docURI),
|
||||
packCgiVariable(keys["SCRIPT_FILENAME"], fc.scriptFilename),
|
||||
packCgiVariable(keys["SCRIPT_NAME"], fc.scriptName),
|
||||
packCgiVariable(keys["HTTPS"], https),
|
||||
packCgiVariable(keys["SSL_PROTOCOL"], sslProtocol),
|
||||
packCgiVariable(keys["REQUEST_SCHEME"], rs),
|
||||
packCgiVariable(keys["SERVER_NAME"], reqHost),
|
||||
packCgiVariable(keys["SERVER_PORT"], serverPort),
|
||||
// Variables defined in CGI 1.1 spec
|
||||
// Some variables are unused but cleared explicitly to prevent
|
||||
// the parent environment from interfering.
|
||||
// These values can not be overridden
|
||||
packCgiVariable(keys["CONTENT_LENGTH"], contentLength),
|
||||
packCgiVariable(keys["GATEWAY_INTERFACE"], "CGI/1.1"),
|
||||
packCgiVariable(keys["SERVER_PROTOCOL"], request.Proto),
|
||||
packCgiVariable(keys["SERVER_SOFTWARE"], "FrankenPHP"),
|
||||
packCgiVariable(keys["HTTP_HOST"], request.Host),
|
||||
// These values are always empty but must be defined:
|
||||
packCgiVariable(keys["AUTH_TYPE"], ""),
|
||||
packCgiVariable(keys["REMOTE_IDENT"], ""),
|
||||
// Request uri of the original request
|
||||
packCgiVariable(keys["REQUEST_URI"], requestURI),
|
||||
)
|
||||
|
||||
// Add all HTTP headers to env variables
|
||||
for field, val := range request.Header {
|
||||
k := "HTTP_" + headerNameReplacer.Replace(strings.ToUpper(field))
|
||||
if _, ok := fc.Env[k]; !ok {
|
||||
fc.Env[k] = strings.Join(val, ", ")
|
||||
// These values are already present in the SG(request_info), so we'll register them from there
|
||||
C.frankenphp_register_variables_from_request_info(
|
||||
trackVarsArray,
|
||||
keys["CONTENT_TYPE"],
|
||||
keys["PATH_TRANSLATED"],
|
||||
keys["QUERY_STRING"],
|
||||
keys["REMOTE_USER"],
|
||||
keys["REQUEST_METHOD"],
|
||||
)
|
||||
}
|
||||
|
||||
func packCgiVariable(key *C.zend_string, value string) C.ht_key_value_pair {
|
||||
return C.ht_key_value_pair{key, toUnsafeChar(value), C.size_t(len(value))}
|
||||
}
|
||||
|
||||
func addHeadersToServer(fc *frankenPHPContext, trackVarsArray *C.zval) {
|
||||
for field, val := range fc.request.Header {
|
||||
if k := mainThread.commonHeaders[field]; k != nil {
|
||||
v := strings.Join(val, ", ")
|
||||
C.frankenphp_register_single(k, toUnsafeChar(v), C.size_t(len(v)), trackVarsArray)
|
||||
continue
|
||||
}
|
||||
|
||||
// if the header name could not be cached, it needs to be registered safely
|
||||
// this is more inefficient but allows additional sanitizing by PHP
|
||||
k := phpheaders.GetUnCommonHeader(field)
|
||||
v := strings.Join(val, ", ")
|
||||
C.frankenphp_register_variable_safe(toUnsafeChar(k), toUnsafeChar(v), C.size_t(len(v)), trackVarsArray)
|
||||
}
|
||||
}
|
||||
|
||||
if _, ok := fc.Env["REMOTE_USER"]; !ok {
|
||||
var (
|
||||
authUser string
|
||||
ok bool
|
||||
)
|
||||
authUser, fc.authPassword, ok = request.BasicAuth()
|
||||
if ok {
|
||||
fc.Env["REMOTE_USER"] = authUser
|
||||
}
|
||||
func addPreparedEnvToServer(fc *frankenPHPContext, trackVarsArray *C.zval) {
|
||||
for k, v := range fc.env {
|
||||
C.frankenphp_register_variable_safe(toUnsafeChar(k), toUnsafeChar(v), C.size_t(len(v)), trackVarsArray)
|
||||
}
|
||||
fc.env = nil
|
||||
}
|
||||
|
||||
fc.populated = true
|
||||
//export go_register_variables
|
||||
func go_register_variables(threadIndex C.uintptr_t, trackVarsArray *C.zval) {
|
||||
thread := phpThreads[threadIndex]
|
||||
fc := thread.getRequestContext()
|
||||
|
||||
return nil
|
||||
addKnownVariablesToServer(thread, fc, trackVarsArray)
|
||||
addHeadersToServer(fc, trackVarsArray)
|
||||
|
||||
// The Prepared Environment is registered last and can overwrite any previous values
|
||||
addPreparedEnvToServer(fc, trackVarsArray)
|
||||
}
|
||||
|
||||
// splitPos returns the index where path should
|
||||
@@ -226,13 +208,13 @@ func populateEnv(request *http.Request) error {
|
||||
//
|
||||
// Adapted from https://github.com/caddyserver/caddy/blob/master/modules/caddyhttp/reverseproxy/fastcgi/fastcgi.go
|
||||
// Copyright 2015 Matthew Holt and The Caddy Authors
|
||||
func splitPos(fc *FrankenPHPContext, path string) int {
|
||||
if len(fc.SplitPath) == 0 {
|
||||
func splitPos(fc *frankenPHPContext, path string) int {
|
||||
if len(fc.splitPath) == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
lowerPath := strings.ToLower(path)
|
||||
for _, split := range fc.SplitPath {
|
||||
for _, split := range fc.splitPath {
|
||||
if idx := strings.Index(lowerPath, strings.ToLower(split)); idx > -1 {
|
||||
return idx + len(split)
|
||||
}
|
||||
@@ -249,8 +231,6 @@ var tlsProtocolStrings = map[uint16]string{
|
||||
tls.VersionTLS13: "TLSv1.3",
|
||||
}
|
||||
|
||||
var headerNameReplacer = strings.NewReplacer(" ", "_", "-", "_")
|
||||
|
||||
// SanitizedPathJoin performs filepath.Join(root, reqPath) that
|
||||
// is safe against directory traversal attacks. It uses logic
|
||||
// similar to that in the Go standard library, specifically
|
||||
@@ -269,7 +249,7 @@ func sanitizedPathJoin(root, reqPath string) string {
|
||||
path := filepath.Join(root, filepath.Clean("/"+reqPath))
|
||||
|
||||
// filepath.Join also cleans the path, and cleaning strips
|
||||
// the trailing slash, so we need to re-add it afterwards.
|
||||
// the trailing slash, so we need to re-add it afterward.
|
||||
// if the length is 1, then it's a path to the root,
|
||||
// and that should return ".", so we don't append the separator.
|
||||
if strings.HasSuffix(reqPath, "/") && len(reqPath) > 1 {
|
||||
@@ -280,3 +260,8 @@ func sanitizedPathJoin(root, reqPath string) string {
|
||||
}
|
||||
|
||||
const separator = string(filepath.Separator)
|
||||
|
||||
func toUnsafeChar(s string) *C.char {
|
||||
sData := unsafe.StringData(s)
|
||||
return (*C.char)(unsafe.Pointer(sData))
|
||||
}
|
||||
|
||||
166
context.go
Normal file
166
context.go
Normal file
@@ -0,0 +1,166 @@
|
||||
package frankenphp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// frankenPHPContext provides contextual information about the Request to handle.
|
||||
type frankenPHPContext struct {
|
||||
documentRoot string
|
||||
splitPath []string
|
||||
env PreparedEnv
|
||||
logger *slog.Logger
|
||||
request *http.Request
|
||||
originalRequest *http.Request
|
||||
|
||||
docURI string
|
||||
pathInfo string
|
||||
scriptName string
|
||||
scriptFilename string
|
||||
workerName string
|
||||
|
||||
// Whether the request is already closed by us
|
||||
isDone bool
|
||||
|
||||
responseWriter http.ResponseWriter
|
||||
|
||||
done chan interface{}
|
||||
startedAt time.Time
|
||||
}
|
||||
|
||||
// fromContext extracts the frankenPHPContext from a context.
|
||||
func fromContext(ctx context.Context) (fctx *frankenPHPContext, ok bool) {
|
||||
fctx, ok = ctx.Value(contextKey).(*frankenPHPContext)
|
||||
return
|
||||
}
|
||||
|
||||
// NewRequestWithContext creates a new FrankenPHP request context.
|
||||
func NewRequestWithContext(r *http.Request, opts ...RequestOption) (*http.Request, error) {
|
||||
fc := &frankenPHPContext{
|
||||
done: make(chan interface{}),
|
||||
startedAt: time.Now(),
|
||||
request: r,
|
||||
}
|
||||
for _, o := range opts {
|
||||
if err := o(fc); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if fc.logger == nil {
|
||||
fc.logger = logger
|
||||
}
|
||||
|
||||
if fc.documentRoot == "" {
|
||||
if EmbeddedAppPath != "" {
|
||||
fc.documentRoot = EmbeddedAppPath
|
||||
} else {
|
||||
var err error
|
||||
if fc.documentRoot, err = os.Getwd(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if fc.splitPath == nil {
|
||||
fc.splitPath = []string{".php"}
|
||||
}
|
||||
|
||||
if fc.env == nil {
|
||||
fc.env = make(map[string]string)
|
||||
}
|
||||
|
||||
if splitPos := splitPos(fc, r.URL.Path); splitPos > -1 {
|
||||
fc.docURI = r.URL.Path[:splitPos]
|
||||
fc.pathInfo = r.URL.Path[splitPos:]
|
||||
|
||||
// Strip PATH_INFO from SCRIPT_NAME
|
||||
fc.scriptName = strings.TrimSuffix(r.URL.Path, fc.pathInfo)
|
||||
|
||||
// Ensure the SCRIPT_NAME has a leading slash for compliance with RFC3875
|
||||
// Info: https://tools.ietf.org/html/rfc3875#section-4.1.13
|
||||
if fc.scriptName != "" && !strings.HasPrefix(fc.scriptName, "/") {
|
||||
fc.scriptName = "/" + fc.scriptName
|
||||
}
|
||||
}
|
||||
|
||||
// SCRIPT_FILENAME is the absolute path of SCRIPT_NAME
|
||||
fc.scriptFilename = sanitizedPathJoin(fc.documentRoot, fc.scriptName)
|
||||
c := context.WithValue(r.Context(), contextKey, fc)
|
||||
|
||||
return r.WithContext(c), nil
|
||||
}
|
||||
|
||||
func newDummyContext(requestPath string, opts ...RequestOption) (*frankenPHPContext, error) {
|
||||
r, err := http.NewRequest(http.MethodGet, requestPath, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fr, err := NewRequestWithContext(r, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fc, _ := fromContext(fr.Context())
|
||||
|
||||
return fc, nil
|
||||
}
|
||||
|
||||
// closeContext sends the response to the client
|
||||
func (fc *frankenPHPContext) closeContext() {
|
||||
if fc.isDone {
|
||||
return
|
||||
}
|
||||
|
||||
close(fc.done)
|
||||
fc.isDone = true
|
||||
}
|
||||
|
||||
// validate checks if the request should be outright rejected
|
||||
func (fc *frankenPHPContext) validate() bool {
|
||||
if !strings.Contains(fc.request.URL.Path, "\x00") {
|
||||
return true
|
||||
}
|
||||
|
||||
fc.rejectBadRequest("Invalid request path")
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (fc *frankenPHPContext) clientHasClosed() bool {
|
||||
select {
|
||||
case <-fc.request.Context().Done():
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// reject sends a response with the given status code and message
|
||||
func (fc *frankenPHPContext) reject(statusCode int, message string) {
|
||||
if fc.isDone {
|
||||
return
|
||||
}
|
||||
|
||||
rw := fc.responseWriter
|
||||
if rw != nil {
|
||||
rw.WriteHeader(statusCode)
|
||||
_, _ = rw.Write([]byte(message))
|
||||
|
||||
if f, ok := rw.(http.Flusher); ok {
|
||||
f.Flush()
|
||||
}
|
||||
}
|
||||
|
||||
fc.closeContext()
|
||||
}
|
||||
|
||||
func (fc *frankenPHPContext) rejectBadRequest(message string) {
|
||||
fc.reject(http.StatusBadRequest, message)
|
||||
}
|
||||
46
debugstate.go
Normal file
46
debugstate.go
Normal file
@@ -0,0 +1,46 @@
|
||||
package frankenphp
|
||||
|
||||
// EXPERIMENTAL: ThreadDebugState prints the state of a single PHP thread - debugging purposes only
|
||||
type ThreadDebugState struct {
|
||||
Index int
|
||||
Name string
|
||||
State string
|
||||
IsWaiting bool
|
||||
IsBusy bool
|
||||
WaitingSinceMilliseconds int64
|
||||
}
|
||||
|
||||
// EXPERIMENTAL: FrankenPHPDebugState prints the state of all PHP threads - debugging purposes only
|
||||
type FrankenPHPDebugState struct {
|
||||
ThreadDebugStates []ThreadDebugState
|
||||
ReservedThreadCount int
|
||||
}
|
||||
|
||||
// EXPERIMENTAL: DebugState prints the state of all PHP threads - debugging purposes only
|
||||
func DebugState() FrankenPHPDebugState {
|
||||
fullState := FrankenPHPDebugState{
|
||||
ThreadDebugStates: make([]ThreadDebugState, 0, len(phpThreads)),
|
||||
ReservedThreadCount: 0,
|
||||
}
|
||||
for _, thread := range phpThreads {
|
||||
if thread.state.is(stateReserved) {
|
||||
fullState.ReservedThreadCount++
|
||||
continue
|
||||
}
|
||||
fullState.ThreadDebugStates = append(fullState.ThreadDebugStates, threadDebugState(thread))
|
||||
}
|
||||
|
||||
return fullState
|
||||
}
|
||||
|
||||
// threadDebugState creates a small jsonable status message for debugging purposes
|
||||
func threadDebugState(thread *phpThread) ThreadDebugState {
|
||||
return ThreadDebugState{
|
||||
Index: thread.threadIndex,
|
||||
Name: thread.name(),
|
||||
State: thread.state.name(),
|
||||
IsWaiting: thread.state.isInWaitingState(),
|
||||
IsBusy: !thread.state.isInWaitingState(),
|
||||
WaitingSinceMilliseconds: thread.state.waitTime(),
|
||||
}
|
||||
}
|
||||
@@ -1,64 +1,85 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
FROM golang:1.21-alpine
|
||||
#checkov:skip=CKV_DOCKER_2
|
||||
#checkov:skip=CKV_DOCKER_3
|
||||
FROM golang:1.24-alpine
|
||||
|
||||
ENV GOTOOLCHAIN=local
|
||||
ENV CFLAGS="-ggdb3"
|
||||
ENV PHPIZE_DEPS \
|
||||
autoconf \
|
||||
dpkg-dev \
|
||||
file \
|
||||
g++ \
|
||||
gcc \
|
||||
libc-dev \
|
||||
make \
|
||||
pkgconfig \
|
||||
re2c
|
||||
ENV PHPIZE_DEPS="\
|
||||
autoconf \
|
||||
dpkg-dev \
|
||||
file \
|
||||
g++ \
|
||||
gcc \
|
||||
libc-dev \
|
||||
make \
|
||||
pkgconfig \
|
||||
re2c"
|
||||
|
||||
SHELL ["/bin/ash", "-eo", "pipefail", "-c"]
|
||||
|
||||
RUN apk add --no-cache \
|
||||
$PHPIZE_DEPS \
|
||||
argon2-dev \
|
||||
curl-dev \
|
||||
oniguruma-dev \
|
||||
readline-dev \
|
||||
libsodium-dev \
|
||||
sqlite-dev \
|
||||
openssl-dev \
|
||||
libxml2-dev \
|
||||
zlib-dev \
|
||||
bison \
|
||||
nss-tools \
|
||||
# Dev tools \
|
||||
git \
|
||||
clang \
|
||||
llvm \
|
||||
gdb \
|
||||
valgrind \
|
||||
neovim \
|
||||
zsh \
|
||||
libtool && \
|
||||
echo 'set auto-load safe-path /' > /root/.gdbinit
|
||||
$PHPIZE_DEPS \
|
||||
argon2-dev \
|
||||
brotli-dev \
|
||||
curl-dev \
|
||||
oniguruma-dev \
|
||||
readline-dev \
|
||||
libsodium-dev \
|
||||
sqlite-dev \
|
||||
openssl-dev \
|
||||
libxml2-dev \
|
||||
zlib-dev \
|
||||
bison \
|
||||
nss-tools \
|
||||
# file watcher
|
||||
libstdc++ \
|
||||
linux-headers \
|
||||
# Dev tools \
|
||||
git \
|
||||
clang \
|
||||
cmake \
|
||||
llvm \
|
||||
gdb \
|
||||
valgrind \
|
||||
neovim \
|
||||
zsh \
|
||||
libtool && \
|
||||
echo 'set auto-load safe-path /' > /root/.gdbinit
|
||||
|
||||
RUN git clone --branch=PHP-8.2 https://github.com/php/php-src.git && \
|
||||
cd php-src && \
|
||||
# --enable-embed is only necessary to generate libphp.so, we don't use this SAPI directly
|
||||
./buildconf --force && \
|
||||
./configure \
|
||||
--enable-embed \
|
||||
--enable-zts \
|
||||
--disable-zend-signals \
|
||||
--enable-zend-max-execution-timers \
|
||||
--enable-debug && \
|
||||
make -j$(nproc) && \
|
||||
make install && \
|
||||
ldconfig /etc/ld.so.conf.d && \
|
||||
cp php.ini-development /usr/local/lib/php.ini && \
|
||||
echo -e "zend_extension=opcache.so\nopcache.enable=1" >> /usr/local/lib/php.ini &&\
|
||||
php --version
|
||||
WORKDIR /usr/local/src/php
|
||||
RUN git clone --branch=PHP-8.4 https://github.com/php/php-src.git . && \
|
||||
# --enable-embed is only necessary to generate libphp.so, we don't use this SAPI directly
|
||||
./buildconf --force && \
|
||||
EXTENSION_DIR=/usr/lib/frankenphp/modules ./configure \
|
||||
--enable-embed \
|
||||
--enable-zts \
|
||||
--disable-zend-signals \
|
||||
--enable-zend-max-execution-timers \
|
||||
--with-config-file-path=/etc/frankenphp/php.ini \
|
||||
--with-config-file-scan-dir=/etc/frankenphp/php.d \
|
||||
--enable-debug && \
|
||||
make -j"$(nproc)" && \
|
||||
make install && \
|
||||
ldconfig /etc/ld.so.conf.d && \
|
||||
mkdir -p /etc/frankenphp/php.d && \
|
||||
cp php.ini-development /etc/frankenphp/php.ini && \
|
||||
echo "zend_extension=opcache.so" >> /etc/frankenphp/php.ini && \
|
||||
echo "opcache.enable=1" >> /etc/frankenphp/php.ini && \
|
||||
php --version
|
||||
|
||||
# Install e-dant/watcher (necessary for file watching)
|
||||
WORKDIR /usr/local/src/watcher
|
||||
RUN git clone https://github.com/e-dant/watcher . && \
|
||||
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release && \
|
||||
cmake --build build/ && \
|
||||
cmake --install build
|
||||
|
||||
WORKDIR /go/src/app
|
||||
|
||||
COPY . .
|
||||
|
||||
RUN cd caddy/frankenphp && \
|
||||
go build
|
||||
WORKDIR /go/src/app/caddy/frankenphp
|
||||
RUN go build
|
||||
|
||||
WORKDIR /go/src/app
|
||||
CMD [ "zsh" ]
|
||||
|
||||
133
dev.Dockerfile
133
dev.Dockerfile
@@ -1,68 +1,89 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
FROM golang:1.21
|
||||
#checkov:skip=CKV_DOCKER_2
|
||||
#checkov:skip=CKV_DOCKER_3
|
||||
FROM golang:1.24
|
||||
|
||||
ENV GOTOOLCHAIN=local
|
||||
ENV CFLAGS="-ggdb3"
|
||||
ENV PHPIZE_DEPS \
|
||||
autoconf \
|
||||
dpkg-dev \
|
||||
file \
|
||||
g++ \
|
||||
gcc \
|
||||
libc-dev \
|
||||
make \
|
||||
pkg-config \
|
||||
re2c
|
||||
ENV PHPIZE_DEPS="\
|
||||
autoconf \
|
||||
dpkg-dev \
|
||||
file \
|
||||
g++ \
|
||||
gcc \
|
||||
libc-dev \
|
||||
make \
|
||||
pkg-config \
|
||||
re2c"
|
||||
|
||||
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
|
||||
|
||||
# hadolint ignore=DL3009
|
||||
RUN apt-get update && \
|
||||
apt-get -y --no-install-recommends install \
|
||||
$PHPIZE_DEPS \
|
||||
libargon2-dev \
|
||||
libcurl4-openssl-dev \
|
||||
libonig-dev \
|
||||
libreadline-dev \
|
||||
libsodium-dev \
|
||||
libsqlite3-dev \
|
||||
libssl-dev \
|
||||
libxml2-dev \
|
||||
zlib1g-dev \
|
||||
bison \
|
||||
libnss3-tools \
|
||||
# Dev tools \
|
||||
git \
|
||||
clang \
|
||||
llvm \
|
||||
gdb \
|
||||
valgrind \
|
||||
neovim \
|
||||
zsh \
|
||||
libtool-bin && \
|
||||
echo 'set auto-load safe-path /' > /root/.gdbinit && \
|
||||
echo '* soft core unlimited' >> /etc/security/limits.conf \
|
||||
&& \
|
||||
apt-get clean
|
||||
apt-get -y --no-install-recommends install \
|
||||
$PHPIZE_DEPS \
|
||||
libargon2-dev \
|
||||
libbrotli-dev \
|
||||
libcurl4-openssl-dev \
|
||||
libonig-dev \
|
||||
libreadline-dev \
|
||||
libsodium-dev \
|
||||
libsqlite3-dev \
|
||||
libssl-dev \
|
||||
libxml2-dev \
|
||||
zlib1g-dev \
|
||||
bison \
|
||||
libnss3-tools \
|
||||
# Dev tools \
|
||||
git \
|
||||
clang \
|
||||
cmake \
|
||||
llvm \
|
||||
gdb \
|
||||
valgrind \
|
||||
neovim \
|
||||
zsh \
|
||||
libtool-bin && \
|
||||
echo 'set auto-load safe-path /' > /root/.gdbinit && \
|
||||
echo '* soft core unlimited' >> /etc/security/limits.conf \
|
||||
&& \
|
||||
apt-get clean
|
||||
|
||||
RUN git clone --branch=PHP-8.2 https://github.com/php/php-src.git && \
|
||||
cd php-src && \
|
||||
# --enable-embed is only necessary to generate libphp.so, we don't use this SAPI directly
|
||||
./buildconf --force && \
|
||||
./configure \
|
||||
--enable-embed \
|
||||
--enable-zts \
|
||||
--disable-zend-signals \
|
||||
--enable-zend-max-execution-timers \
|
||||
--enable-debug && \
|
||||
make -j$(nproc) && \
|
||||
make install && \
|
||||
ldconfig && \
|
||||
cp php.ini-development /usr/local/lib/php.ini && \
|
||||
echo "zend_extension=opcache.so\nopcache.enable=1" >> /usr/local/lib/php.ini &&\
|
||||
php --version
|
||||
WORKDIR /usr/local/src/php
|
||||
RUN git clone --branch=PHP-8.4 https://github.com/php/php-src.git . && \
|
||||
# --enable-embed is only necessary to generate libphp.so, we don't use this SAPI directly
|
||||
./buildconf --force && \
|
||||
EXTENSION_DIR=/usr/lib/frankenphp/modules ./configure \
|
||||
--enable-embed \
|
||||
--enable-zts \
|
||||
--disable-zend-signals \
|
||||
--enable-zend-max-execution-timers \
|
||||
--with-config-file-path=/etc/frankenphp/php.ini \
|
||||
--with-config-file-scan-dir=/etc/frankenphp/php.d \
|
||||
--enable-debug && \
|
||||
make -j"$(nproc)" && \
|
||||
make install && \
|
||||
ldconfig && \
|
||||
mkdir -p /etc/frankenphp/php.d && \
|
||||
cp php.ini-development /etc/frankenphp/php.ini && \
|
||||
echo "zend_extension=opcache.so" >> /etc/frankenphp/php.ini && \
|
||||
echo "opcache.enable=1" >> /etc/frankenphp/php.ini && \
|
||||
php --version
|
||||
|
||||
# Install e-dant/watcher (necessary for file watching)
|
||||
WORKDIR /usr/local/src/watcher
|
||||
RUN git clone https://github.com/e-dant/watcher . && \
|
||||
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release && \
|
||||
cmake --build build/ && \
|
||||
cmake --install build && \
|
||||
cp build/libwatcher-c.so /usr/local/lib/libwatcher-c.so && \
|
||||
ldconfig
|
||||
|
||||
WORKDIR /go/src/app
|
||||
|
||||
COPY . .
|
||||
|
||||
RUN cd caddy/frankenphp && \
|
||||
go build
|
||||
WORKDIR /go/src/app/caddy/frankenphp
|
||||
RUN go build -buildvcs=false -tags 'nobadger,nomysql,nopgx'
|
||||
|
||||
WORKDIR /go/src/app
|
||||
CMD [ "zsh" ]
|
||||
|
||||
100
docker-bake.hcl
100
docker-bake.hcl
@@ -6,31 +6,39 @@ variable "VERSION" {
|
||||
default = "dev"
|
||||
}
|
||||
|
||||
variable "PHP_VERSION" {
|
||||
default = "8.2,8.3,8.4"
|
||||
}
|
||||
|
||||
variable "GO_VERSION" {
|
||||
default = "1.21"
|
||||
default = "1.24"
|
||||
}
|
||||
|
||||
variable "SHA" {}
|
||||
|
||||
variable "LATEST" {
|
||||
default = false
|
||||
default = true
|
||||
}
|
||||
|
||||
variable "CACHE" {
|
||||
default = ""
|
||||
}
|
||||
|
||||
variable DEFAULT_PHP_VERSION {
|
||||
default = "8.4"
|
||||
}
|
||||
|
||||
function "tag" {
|
||||
params = [version, os, php-version, tgt]
|
||||
result = [
|
||||
version != "" ? format("%s:%s%s-php%s-%s", IMAGE_NAME, version, tgt == "builder" ? "-builder" : "", php-version, os) : "",
|
||||
php-version == "8.2" && os == "bookworm" && version != "" ? format("%s:%s%s", IMAGE_NAME, version, tgt == "builder" ? "-builder" : "") : "",
|
||||
php-version == "8.2" && version != "" ? format("%s:%s%s-%s", IMAGE_NAME, version, tgt == "builder" ? "-builder" : "", os) : "",
|
||||
php-version == "8.2" && version == "latest" ? format("%s:%s%s", IMAGE_NAME, os, tgt == "builder" ? "-builder" : "") : "",
|
||||
os == "bookworm" && version != "" ? format("%s:%s%s-php%s", IMAGE_NAME, version, tgt == "builder" ? "-builder" : "", php-version) : "",
|
||||
version == "" ? "" : "${IMAGE_NAME}:${trimprefix("${version}${tgt == "builder" ? "-builder" : ""}-php${php-version}-${os}", "latest-")}",
|
||||
php-version == DEFAULT_PHP_VERSION && os == "bookworm" && version != "" ? "${IMAGE_NAME}:${trimprefix("${version}${tgt == "builder" ? "-builder" : ""}", "latest-")}" : "",
|
||||
php-version == DEFAULT_PHP_VERSION && version != "" ? "${IMAGE_NAME}:${trimprefix("${version}${tgt == "builder" ? "-builder" : ""}-${os}", "latest-")}" : "",
|
||||
os == "bookworm" && version != "" ? "${IMAGE_NAME}:${trimprefix("${version}${tgt == "builder" ? "-builder" : ""}-php${php-version}", "latest-")}" : "",
|
||||
]
|
||||
}
|
||||
|
||||
# cleanTag ensures that the tag is a valid Docker tag
|
||||
# cleanTag ensures that the tag is a valid Docker tag
|
||||
# see https://github.com/distribution/distribution/blob/v2.8.2/reference/regexp.go#L37
|
||||
function "clean_tag" {
|
||||
@@ -52,14 +60,24 @@ function "_semver" {
|
||||
|
||||
function "__semver" {
|
||||
params = [v]
|
||||
result = v == {} ? [clean_tag(VERSION)] : v.prerelease == null ? ["latest", v.major, "${v.major}.${v.minor}", "${v.major}.${v.minor}.${v.patch}"] : ["${v.major}.${v.minor}.${v.patch}-${v.prerelease}"]
|
||||
result = v == {} ? [clean_tag(VERSION)] : v.prerelease == null ? [v.major, "${v.major}.${v.minor}", "${v.major}.${v.minor}.${v.patch}"] : ["${v.major}.${v.minor}.${v.patch}-${v.prerelease}"]
|
||||
}
|
||||
|
||||
function "php_version" {
|
||||
params = [v]
|
||||
result = _php_version(v, regexall("(?P<major>\\d+)\\.(?P<minor>\\d+)", v)[0])
|
||||
}
|
||||
|
||||
function "_php_version" {
|
||||
params = [v, m]
|
||||
result = "${m.major}.${m.minor}" == DEFAULT_PHP_VERSION ? [v, "${m.major}.${m.minor}", "${m.major}"] : [v, "${m.major}.${m.minor}"]
|
||||
}
|
||||
|
||||
target "default" {
|
||||
name = "${tgt}-php-${replace(php-version, ".", "-")}-${os}"
|
||||
matrix = {
|
||||
os = ["bookworm", "alpine"]
|
||||
php-version = ["8.2", "8.3.0RC2"]
|
||||
php-version = split(",", PHP_VERSION)
|
||||
tgt = ["builder", "runner"]
|
||||
}
|
||||
contexts = {
|
||||
@@ -69,17 +87,26 @@ target "default" {
|
||||
dockerfile = os == "alpine" ? "alpine.Dockerfile" : "Dockerfile"
|
||||
context = "./"
|
||||
target = tgt
|
||||
platforms = [
|
||||
# arm/v6 is only available for Alpine: https://github.com/docker-library/golang/issues/502
|
||||
platforms = os == "alpine" ? [
|
||||
"linux/amd64",
|
||||
"linux/386",
|
||||
"linux/arm/v6",
|
||||
# FIXME: armv6 doesn't build in GitHub actions because we use a custom Go build
|
||||
#"linux/arm/v6",
|
||||
"linux/arm/v7",
|
||||
"linux/arm64",
|
||||
] : [
|
||||
"linux/amd64",
|
||||
"linux/386",
|
||||
"linux/arm/v7",
|
||||
"linux/arm64"
|
||||
]
|
||||
tags = distinct(flatten([
|
||||
LATEST ? tag("latest", os, php-version, tgt) : [],
|
||||
tag(SHA == "" ? "" : "sha-${substr(SHA, 0, 7)}", os, php-version, tgt),
|
||||
[for v in semver(VERSION) : tag(v, os, php-version, tgt)]
|
||||
tags = distinct(flatten(
|
||||
[for pv in php_version(php-version) : flatten([
|
||||
LATEST ? tag("latest", os, pv, tgt) : [],
|
||||
tag(SHA == "" || VERSION != "dev" ? "" : "sha-${substr(SHA, 0, 7)}", os, pv, tgt),
|
||||
VERSION == "dev" ? [] : [for v in semver(VERSION) : tag(v, os, pv, tgt)]
|
||||
])
|
||||
]))
|
||||
labels = {
|
||||
"org.opencontainers.image.created" = "${timestamp()}"
|
||||
@@ -91,15 +118,52 @@ target "default" {
|
||||
}
|
||||
}
|
||||
|
||||
target "static-builder" {
|
||||
target "static-builder-musl" {
|
||||
contexts = {
|
||||
golang-base = "docker-image://golang:${GO_VERSION}-alpine"
|
||||
}
|
||||
dockerfile = "static-builder.Dockerfile"
|
||||
dockerfile = "static-builder-musl.Dockerfile"
|
||||
context = "./"
|
||||
tags = ["${IMAGE_NAME}:static-builder"]
|
||||
platforms = [
|
||||
"linux/amd64",
|
||||
"linux/arm64",
|
||||
]
|
||||
tags = distinct(flatten([
|
||||
LATEST ? "${IMAGE_NAME}:static-builder-musl" : "",
|
||||
SHA == "" || VERSION != "dev" ? "" : "${IMAGE_NAME}:static-builder-musl-sha-${substr(SHA, 0, 7)}",
|
||||
VERSION == "dev" ? [] : [for v in semver(VERSION) : "${IMAGE_NAME}:static-builder-musl-${v}"]
|
||||
]))
|
||||
labels = {
|
||||
"org.opencontainers.image.created" = "${timestamp()}"
|
||||
"org.opencontainers.image.version" = VERSION
|
||||
"org.opencontainers.image.revision" = SHA
|
||||
}
|
||||
args = {
|
||||
FRANKENPHP_VERSION = VERSION
|
||||
}
|
||||
secret = ["id=github-token,env=GITHUB_TOKEN"]
|
||||
}
|
||||
|
||||
target "static-builder-gnu" {
|
||||
dockerfile = "static-builder-gnu.Dockerfile"
|
||||
context = "./"
|
||||
platforms = [
|
||||
"linux/amd64",
|
||||
"linux/arm64"
|
||||
]
|
||||
tags = distinct(flatten([
|
||||
LATEST ? "${IMAGE_NAME}:static-builder-gnu" : "",
|
||||
SHA == "" || VERSION != "dev" ? "" : "${IMAGE_NAME}:static-builder-gnu-sha-${substr(SHA, 0, 7)}",
|
||||
VERSION == "dev" ? [] : [for v in semver(VERSION) : "${IMAGE_NAME}:static-builder-gnu-${v}"]
|
||||
]))
|
||||
labels = {
|
||||
"org.opencontainers.image.created" = "${timestamp()}"
|
||||
"org.opencontainers.image.version" = VERSION
|
||||
"org.opencontainers.image.revision" = SHA
|
||||
}
|
||||
args = {
|
||||
FRANKENPHP_VERSION = VERSION
|
||||
GO_VERSION = GO_VERSION
|
||||
}
|
||||
secret = ["id=github-token,env=GITHUB_TOKEN"]
|
||||
}
|
||||
|
||||
11
docs/classic.md
Normal file
11
docs/classic.md
Normal file
@@ -0,0 +1,11 @@
|
||||
# Using Classic Mode
|
||||
|
||||
Without any additional configuration, FrankenPHP operates in classic mode. In this mode, FrankenPHP functions like a traditional PHP server, directly serving PHP files. This makes it a seamless drop-in replacement for PHP-FPM or Apache with mod_php.
|
||||
|
||||
Similar to Caddy, FrankenPHP accepts an unlimited number of connections and uses a [fixed number of threads](config.md#caddyfile-config) to serve them. The number of accepted and queued connections is limited only by the available system resources.
|
||||
The PHP thread pool operates with a fixed number of threads initialized at startup, comparable to the static mode of PHP-FPM. It's also possible to let threads [scale automatically at runtime](performance.md#max_threads), similar to the dynamic mode of PHP-FPM.
|
||||
|
||||
Queued connections will wait indefinitely until a PHP thread is available to serve them. To prevent that, you can use the `max_wait_time` [configuration](config.md#caddyfile-config) to limit how long a request may wait for a free PHP thread before being rejected.
|
||||
Additionally, you can set a reasonable [write timeout in Caddy](https://caddyserver.com/docs/caddyfile/options#timeouts).
|
||||
|
||||
Each Caddy instance will only spin up one FrankenPHP thread pool, which will be shared across all `php_server` blocks.
|
||||
219
docs/cn/CONTRIBUTING.md
Normal file
219
docs/cn/CONTRIBUTING.md
Normal file
@@ -0,0 +1,219 @@
|
||||
# 贡献
|
||||
|
||||
## 编译 PHP
|
||||
|
||||
### 使用 Docker (Linux)
|
||||
|
||||
构建开发环境 Docker 镜像:
|
||||
|
||||
```console
|
||||
docker build -t frankenphp-dev -f dev.Dockerfile .
|
||||
docker run --cap-add=SYS_PTRACE --security-opt seccomp=unconfined -p 8080:8080 -p 443:443 -p 443:443/udp -v $PWD:/go/src/app -it frankenphp-dev
|
||||
```
|
||||
|
||||
该镜像包含常用的开发工具(Go、GDB、Valgrind、Neovim等)并使用以下 php 设置位置
|
||||
|
||||
- php.ini: `/etc/frankenphp/php.ini` 默认提供了一个带有开发预设的 php.ini 文件。
|
||||
- 附加配置文件: `/etc/frankenphp/php.d/*.ini`
|
||||
- php 扩展: `/usr/lib/frankenphp/modules/`
|
||||
|
||||
如果您的 docker 版本低于 23.0,则会因为 dockerignore [pattern issue](https://github.com/moby/moby/pull/42676) 而导致构建失败。将目录添加到 `.dockerignore`。
|
||||
|
||||
```patch
|
||||
!testdata/*.php
|
||||
!testdata/*.txt
|
||||
+!caddy
|
||||
+!internal
|
||||
```
|
||||
|
||||
### 不使用 Docker (Linux 和 macOS)
|
||||
|
||||
[按照说明从源代码编译](https://frankenphp.dev/docs/compile/) 并传递 `--debug` 配置标志。
|
||||
|
||||
## 运行测试套件
|
||||
|
||||
```console
|
||||
go test -tags watcher -race -v ./...
|
||||
```
|
||||
|
||||
## Caddy 模块
|
||||
|
||||
使用 FrankenPHP Caddy 模块构建 Caddy:
|
||||
|
||||
```console
|
||||
cd caddy/frankenphp/
|
||||
go build
|
||||
cd ../../
|
||||
```
|
||||
|
||||
使用 FrankenPHP Caddy 模块运行 Caddy:
|
||||
|
||||
```console
|
||||
cd testdata/
|
||||
../caddy/frankenphp/frankenphp run
|
||||
```
|
||||
|
||||
服务器正在监听 `127.0.0.1:8080`:
|
||||
|
||||
```console
|
||||
curl -vk https://localhost/phpinfo.php
|
||||
```
|
||||
|
||||
## 最小测试服务器
|
||||
|
||||
构建最小测试服务器:
|
||||
|
||||
```console
|
||||
cd internal/testserver/
|
||||
go build
|
||||
cd ../../
|
||||
```
|
||||
|
||||
运行测试服务器:
|
||||
|
||||
```console
|
||||
cd testdata/
|
||||
../internal/testserver/testserver
|
||||
```
|
||||
|
||||
服务器正在监听 `127.0.0.1:8080`:
|
||||
|
||||
```console
|
||||
curl -v http://127.0.0.1:8080/phpinfo.php
|
||||
```
|
||||
|
||||
## 本地构建 Docker 镜像
|
||||
|
||||
打印 bake 计划:
|
||||
|
||||
```console
|
||||
docker buildx bake -f docker-bake.hcl --print
|
||||
```
|
||||
|
||||
本地构建 amd64 的 FrankenPHP 镜像:
|
||||
|
||||
```console
|
||||
docker buildx bake -f docker-bake.hcl --pull --load --set "*.platform=linux/amd64"
|
||||
```
|
||||
|
||||
本地构建 arm64 的 FrankenPHP 镜像:
|
||||
|
||||
```console
|
||||
docker buildx bake -f docker-bake.hcl --pull --load --set "*.platform=linux/arm64"
|
||||
```
|
||||
|
||||
从头开始为 arm64 和 amd64 构建 FrankenPHP 镜像并推送到 Docker Hub:
|
||||
|
||||
```console
|
||||
docker buildx bake -f docker-bake.hcl --pull --no-cache --push
|
||||
```
|
||||
|
||||
## 使用静态构建调试分段错误
|
||||
|
||||
1. 从 GitHub 下载 FrankenPHP 二进制文件的调试版本或创建包含调试符号的自定义静态构建:
|
||||
|
||||
```console
|
||||
docker buildx bake \
|
||||
--load \
|
||||
--set static-builder.args.DEBUG_SYMBOLS=1 \
|
||||
--set "static-builder.platform=linux/amd64" \
|
||||
static-builder
|
||||
docker cp $(docker create --name static-builder-musl dunglas/frankenphp:static-builder-musl):/go/src/app/dist/frankenphp-linux-$(uname -m) frankenphp
|
||||
```
|
||||
|
||||
2. 将当前版本的 `frankenphp` 替换为 debug FrankenPHP 可执行文件
|
||||
3. 照常启动 FrankenPHP(或者,你可以直接使用 GDB 启动 FrankenPHP: `gdb --args frankenphp run`)
|
||||
4. 使用 GDB 附加到进程:
|
||||
|
||||
```console
|
||||
gdb -p `pidof frankenphp`
|
||||
```
|
||||
|
||||
5. 如有必要,请在 GDB shell 中输入 `continue`
|
||||
6. 使 FrankenPHP 崩溃
|
||||
7. 在 GDB shell 中输入 `bt`
|
||||
8. 复制输出
|
||||
|
||||
## 在 GitHub Actions 中调试分段错误
|
||||
|
||||
1. 打开 `.github/workflows/tests.yml`
|
||||
2. 启用 PHP 调试符号
|
||||
|
||||
```patch
|
||||
- uses: shivammathur/setup-php@v2
|
||||
# ...
|
||||
env:
|
||||
phpts: ts
|
||||
+ debug: true
|
||||
```
|
||||
|
||||
3. 启用 `tmate` 以连接到容器
|
||||
|
||||
```patch
|
||||
-
|
||||
name: Set CGO flags
|
||||
run: echo "CGO_CFLAGS=$(php-config --includes)" >> "$GITHUB_ENV"
|
||||
+ -
|
||||
+ run: |
|
||||
+ sudo apt install gdb
|
||||
+ mkdir -p /home/runner/.config/gdb/
|
||||
+ printf "set auto-load safe-path /\nhandle SIG34 nostop noprint pass" > /home/runner/.config/gdb/gdbinit
|
||||
+ -
|
||||
+ uses: mxschmitt/action-tmate@v3
|
||||
```
|
||||
|
||||
4. 连接到容器
|
||||
5. 打开 `frankenphp.go`
|
||||
6. 启用 `cgosymbolizer`
|
||||
|
||||
```patch
|
||||
- //_ "github.com/ianlancetaylor/cgosymbolizer"
|
||||
+ _ "github.com/ianlancetaylor/cgosymbolizer"
|
||||
```
|
||||
|
||||
7. 下载模块: `go get`
|
||||
8. 在容器中,可以使用 GDB 和以下:
|
||||
|
||||
```console
|
||||
go test -tags watcher -c -ldflags=-w
|
||||
gdb --args frankenphp.dev.test -test.run ^MyTest$
|
||||
```
|
||||
|
||||
9. 当错误修复后,恢复所有这些更改
|
||||
|
||||
## 其他开发资源
|
||||
|
||||
- [PHP 嵌入 uWSGI](https://github.com/unbit/uwsgi/blob/master/plugins/php/php_plugin.c)
|
||||
- [PHP 嵌入 NGINX Unit](https://github.com/nginx/unit/blob/master/src/nxt_php_sapi.c)
|
||||
- [PHP 嵌入 Go (go-php)](https://github.com/deuill/go-php)
|
||||
- [PHP 嵌入 Go (GoEmPHP)](https://github.com/mikespook/goemphp)
|
||||
- [PHP 嵌入 C++](https://gist.github.com/paresy/3cbd4c6a469511ac7479aa0e7c42fea7)
|
||||
- [扩展和嵌入 PHP 作者:Sara Golemon](https://books.google.fr/books?id=zMbGvK17_tYC&pg=PA254&lpg=PA254#v=onepage&q&f=false)
|
||||
- [TSRMLS_CC到底是什么?](http://blog.golemon.com/2006/06/what-heck-is-tsrmlscc-anyway.html)
|
||||
- [Mac 上的 PHP 嵌入](https://gist.github.com/jonnywang/61427ffc0e8dde74fff40f479d147db4)
|
||||
- [SDL 绑定](https://pkg.go.dev/github.com/veandco/go-sdl2@v0.4.21/sdl#Main)
|
||||
|
||||
## Docker 相关资源
|
||||
|
||||
- [Bake 文件定义](https://docs.docker.com/build/customize/bake/file-definition/)
|
||||
- [docker buildx 构建](https://docs.docker.com/engine/reference/commandline/buildx_build/)
|
||||
|
||||
## 有用的命令
|
||||
|
||||
```console
|
||||
apk add strace util-linux gdb
|
||||
strace -e 'trace=!futex,epoll_ctl,epoll_pwait,tgkill,rt_sigreturn' -p 1
|
||||
```
|
||||
|
||||
## 翻译文档
|
||||
|
||||
要将文档和网站翻译成新语言,请按照下列步骤操作:
|
||||
|
||||
1. 在此存储库的 `docs/` 目录中创建一个以语言的 2 个字符的 ISO 代码命名的新目录
|
||||
2. 将 `docs/` 目录根目录中的所有 `.md` 文件复制到新目录中(始终使用英文版本作为翻译源,因为它始终是最新的)
|
||||
3. 将 `README.md` 和 `CONTRIBUTING.md` 文件从根目录复制到新目录
|
||||
4. 翻译文件的内容,但不要更改文件名,也不要翻译以 `> [!` 开头的字符串(这是 GitHub 的特殊标记)
|
||||
5. 创建翻译的拉取请求
|
||||
6. 在 [站点存储库](https://github.com/dunglas/frankenphp-website/tree/main) 中,复制并翻译 `content/`、`data/` 和 `i18n/` 目录中的翻译文件
|
||||
7. 转换创建的 YAML 文件中的值
|
||||
8. 在站点存储库上打开拉取请求
|
||||
78
docs/cn/README.md
Normal file
78
docs/cn/README.md
Normal file
@@ -0,0 +1,78 @@
|
||||
# FrankenPHP: 适用于 PHP 的现代应用服务器
|
||||
|
||||
<h1 align="center"><a href="https://frankenphp.dev"><img src="../../frankenphp.png" alt="FrankenPHP" width="600"></a></h1>
|
||||
|
||||
FrankenPHP 是建立在 [Caddy](https://caddyserver.com/) Web 服务器之上的现代 PHP 应用程序服务器。
|
||||
|
||||
FrankenPHP 凭借其令人惊叹的功能为您的 PHP 应用程序提供了超能力:[早期提示](early-hints.md)、[worker 模式](worker.md)、[实时功能](mercure.md)、自动 HTTPS、HTTP/2 和 HTTP/3 支持......
|
||||
|
||||
FrankenPHP 可与任何 PHP 应用程序一起使用,并且由于提供了与 worker 模式的集成,使您的 Symfony 和 Laravel 项目比以往任何时候都更快。
|
||||
|
||||
FrankenPHP 也可以用作独立的 Go 库,将 PHP 嵌入到任何使用 net/http 的应用程序中。
|
||||
|
||||
[**了解更多** *frankenphp.dev*](https://frankenphp.dev/cn) 以及在以下地址中:
|
||||
|
||||
<a href="https://dunglas.dev/2022/10/frankenphp-the-modern-php-app-server-written-in-go/"><img src="https://dunglas.dev/wp-content/uploads/2022/10/frankenphp.png" alt="Slides" width="600"></a>
|
||||
|
||||
## 开始
|
||||
|
||||
### Docker
|
||||
|
||||
```console
|
||||
docker run -v $PWD:/app/public \
|
||||
-p 80:80 -p 443:443 -p 443:443/udp \
|
||||
dunglas/frankenphp
|
||||
```
|
||||
|
||||
访问 `https://localhost`, 并享受吧!
|
||||
|
||||
> [!TIP]
|
||||
>
|
||||
> 不要尝试使用 `https://127.0.0.1`。使用 `https://localhost` 并接受自签名证书。
|
||||
> 使用 [`SERVER_NAME` 环境变量](config.md#环境变量) 更改要使用的域。
|
||||
|
||||
### 独立二进制
|
||||
|
||||
如果您不想使用 Docker,我们为 Linux 和 macOS 提供独立的 FrankenPHP 二进制文件
|
||||
,其中包含 [PHP 8.4](https://www.php.net/releases/8.4/en.php) 和最流行的 PHP 扩展:[下载 FrankenPHP](https://github.com/dunglas/frankenphp/releases)。
|
||||
|
||||
若要启动当前目录的内容,请运行:
|
||||
|
||||
```console
|
||||
./frankenphp php-server
|
||||
```
|
||||
|
||||
您还可以使用以下命令运行命令行脚本:
|
||||
|
||||
```console
|
||||
./frankenphp php-cli /path/to/your/script.php
|
||||
```
|
||||
|
||||
## 文档
|
||||
|
||||
* [worker 模式](worker.md)
|
||||
* [早期提示支持(103 HTTP status code)](early-hints.md)
|
||||
* [实时功能](mercure.md)
|
||||
* [配置](config.md)
|
||||
* [Docker 镜像](docker.md)
|
||||
* [在生产环境中部署](production.md)
|
||||
* [创建独立、可自行执行的 PHP 应用程序](embed.md)
|
||||
* [创建静态二进制文件](static.md)
|
||||
* [从源代码编译](compile.md)
|
||||
* [Laravel 集成](laravel.md)
|
||||
* [已知问题](known-issues.md)
|
||||
* [演示应用程序 (Symfony) 和性能测试](https://github.com/dunglas/frankenphp-demo)
|
||||
* [Go 库文档](https://pkg.go.dev/github.com/dunglas/frankenphp)
|
||||
* [贡献和调试](https://frankenphp.dev/docs/contributing/)
|
||||
|
||||
## 示例和框架
|
||||
|
||||
* [Symfony](https://github.com/dunglas/symfony-docker)
|
||||
* [API Platform](https://api-platform.com/docs/distribution/)
|
||||
* [Laravel](laravel.md)
|
||||
* [Sulu](https://sulu.io/blog/running-sulu-with-frankenphp)
|
||||
* [WordPress](https://github.com/StephenMiracle/frankenwp)
|
||||
* [Drupal](https://github.com/dunglas/frankenphp-drupal)
|
||||
* [Joomla](https://github.com/alexandreelise/frankenphp-joomla)
|
||||
* [TYPO3](https://github.com/ochorocho/franken-typo3)
|
||||
* [Magento2](https://github.com/ekino/frankenphp-magento2)
|
||||
98
docs/cn/compile.md
Normal file
98
docs/cn/compile.md
Normal file
@@ -0,0 +1,98 @@
|
||||
# 从源代码编译
|
||||
|
||||
本文档解释了如何创建一个 FrankenPHP 构建,它将 PHP 加载为一个动态库。
|
||||
这是推荐的方法。
|
||||
|
||||
或者,你也可以 [编译静态版本](static.md)。
|
||||
|
||||
## 安装 PHP
|
||||
|
||||
FrankenPHP 支持 PHP 8.2 及更高版本。
|
||||
|
||||
首先,[获取 PHP 源代码](https://www.php.net/downloads.php) 并提取它们:
|
||||
|
||||
```console
|
||||
tar xf php-*
|
||||
cd php-*/
|
||||
```
|
||||
|
||||
然后,为您的平台配置 PHP.
|
||||
|
||||
这些参数是必需的,但你也可以添加其他编译参数(例如额外的扩展)。
|
||||
|
||||
### Linux
|
||||
|
||||
```console
|
||||
./configure \
|
||||
--enable-embed \
|
||||
--enable-zts \
|
||||
--disable-zend-signals \
|
||||
--enable-zend-max-execution-timers
|
||||
```
|
||||
|
||||
### Mac
|
||||
|
||||
使用 [Homebrew](https://brew.sh/) 包管理器安装 `libiconv`、`bison`、`re2c` 和 `pkg-config`:
|
||||
|
||||
```console
|
||||
brew install libiconv bison re2c pkg-config
|
||||
echo 'export PATH="/opt/homebrew/opt/bison/bin:$PATH"' >> ~/.zshrc
|
||||
```
|
||||
|
||||
然后运行 `./configure` 脚本:
|
||||
|
||||
```console
|
||||
./configure \
|
||||
--enable-embed=static \
|
||||
--enable-zts \
|
||||
--disable-zend-signals \
|
||||
--disable-opcache-jit \
|
||||
--enable-static \
|
||||
--enable-shared=no \
|
||||
--with-iconv=/opt/homebrew/opt/libiconv/
|
||||
```
|
||||
|
||||
## 编译并安装 PHP
|
||||
|
||||
最后,编译并安装 PHP:
|
||||
|
||||
```console
|
||||
make -j"$(getconf _NPROCESSORS_ONLN)"
|
||||
sudo make install
|
||||
```
|
||||
|
||||
## 编译 Go 应用
|
||||
|
||||
您现在可以使用 Go 库并编译我们的 Caddy 构建:
|
||||
|
||||
```console
|
||||
curl -L https://github.com/dunglas/frankenphp/archive/refs/heads/main.tar.gz | tar xz
|
||||
cd frankenphp-main/caddy/frankenphp
|
||||
CGO_CFLAGS=$(php-config --includes) CGO_LDFLAGS="$(php-config --ldflags) $(php-config --libs)" go build
|
||||
```
|
||||
|
||||
### 使用 xcaddy
|
||||
|
||||
你可以使用 [xcaddy](https://github.com/caddyserver/xcaddy) 来编译 [自定义 Caddy 模块](https://caddyserver.com/docs/modules/) 的 FrankenPHP:
|
||||
|
||||
```console
|
||||
CGO_ENABLED=1 \
|
||||
XCADDY_GO_BUILD_FLAGS="-ldflags '-w -s'" \
|
||||
xcaddy build \
|
||||
--output frankenphp \
|
||||
--with frankenphp.dev/caddy \
|
||||
--with github.com/dunglas/caddy-cbrotli \
|
||||
--with github.com/dunglas/mercure/caddy \
|
||||
--with github.com/dunglas/vulcain/caddy
|
||||
# Add extra Caddy modules here
|
||||
```
|
||||
|
||||
> [!TIP]
|
||||
>
|
||||
> 如果你的系统基于 musl libc(Alpine Linux 上默认使用)并搭配 Symfony 使用,
|
||||
> 您可能需要增加默认堆栈大小。
|
||||
> 否则,您可能会收到如下错误 `PHP Fatal error: Maximum call stack size of 83360 bytes reached during compilation. Try splitting expression`
|
||||
>
|
||||
> 请将 `XCADDY_GO_BUILD_FLAGS` 环境变量更改为如下类似的值
|
||||
> `XCADDY_GO_BUILD_FLAGS=$'-ldflags "-w -s -extldflags \'-Wl,-z,stack-size=0x80000\'"'`
|
||||
> (根据您的应用需求更改堆栈大小)。
|
||||
173
docs/cn/config.md
Normal file
173
docs/cn/config.md
Normal file
@@ -0,0 +1,173 @@
|
||||
# 配置
|
||||
|
||||
FrankenPHP,Caddy 以及 Mercure 和 Vulcain 模块可以使用 [Caddy 支持的格式](https://caddyserver.com/docs/getting-started#your-first-config) 进行配置。
|
||||
|
||||
在[Docker 映像](docker.md) 中,`Caddyfile` 位于 `/etc/frankenphp/Caddyfile`。
|
||||
静态二进制文件会在启动时所在的目录中查找 `Caddyfile`。
|
||||
PHP 本身可以[使用 `php.ini` 文件](https://www.php.net/manual/zh/configuration.file.php)进行配置。
|
||||
PHP 解释器将在以下位置查找:
|
||||
|
||||
Docker:
|
||||
|
||||
- php.ini: `/usr/local/etc/php/php.ini` 默认情况下不提供 php.ini。
|
||||
- 附加配置文件: `/usr/local/etc/php/conf.d/*.ini`
|
||||
- php 扩展: `/usr/local/lib/php/extensions/no-debug-zts-<YYYYMMDD>/`
|
||||
- 您应该复制 PHP 项目提供的官方模板:
|
||||
|
||||
```dockerfile
|
||||
FROM dunglas/frankenphp
|
||||
|
||||
# 生产:
|
||||
RUN cp $PHP_INI_DIR/php.ini-production $PHP_INI_DIR/php.ini
|
||||
|
||||
# 开发:
|
||||
RUN cp $PHP_INI_DIR/php.ini-development $PHP_INI_DIR/php.ini
|
||||
```
|
||||
|
||||
FrankenPHP 安装 (.rpm 或 .deb):
|
||||
|
||||
- php.ini: `/etc/frankenphp/php.ini` 默认情况下提供带有生产预设的 php.ini 文件。
|
||||
- 附加配置文件: `/etc/frankenphp/php.d/*.ini`
|
||||
- php 扩展: `/usr/lib/frankenphp/modules/`
|
||||
|
||||
静态二进制:
|
||||
|
||||
- php.ini: 执行 `frankenphp run` 或 `frankenphp php-server` 的目录,然后是 `/etc/frankenphp/php.ini`
|
||||
- 附加配置文件: `/etc/frankenphp/php.d/*.ini`
|
||||
- php 扩展: 无法加载
|
||||
- 复制[PHP 源代码](https://github.com/php/php-src/)中提供的`php.ini-production`或`php.ini-development`中的一个。
|
||||
|
||||
## Caddyfile 配置
|
||||
|
||||
可以在站点块中使用 `php_server` 或 `php` [HTTP 指令](https://caddyserver.com/docs/caddyfile/concepts#directives) 来为您的 PHP 应用程序提供服务。
|
||||
|
||||
最小示例:
|
||||
|
||||
```caddyfile
|
||||
localhost {
|
||||
# 启用压缩(可选)
|
||||
encode zstd br gzip
|
||||
# 执行当前目录中的 PHP 文件并提供资产
|
||||
php_server
|
||||
}
|
||||
```
|
||||
|
||||
您也可以使用全局选项显式配置 FrankenPHP:
|
||||
`frankenphp` [全局选项](https://caddyserver.com/docs/caddyfile/concepts#global-options) 可用于配置 FrankenPHP。
|
||||
|
||||
```caddyfile
|
||||
{
|
||||
frankenphp {
|
||||
num_threads <num_threads> # 设置要启动的 PHP 线程数。默认值:可用 CPU 数量的 2 倍。
|
||||
worker {
|
||||
file <path> # 设置 worker 脚本的路径。
|
||||
num <num> # 设置要启动的 PHP 线程数,默认为可用 CPU 数的 2 倍。
|
||||
env <key> <value> # 将额外的环境变量设置为给定值。可以为多个环境变量多次指定。
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ...
|
||||
```
|
||||
|
||||
或者,您可以使用 `worker` 选项的一行缩写形式:
|
||||
|
||||
```caddyfile
|
||||
{
|
||||
frankenphp {
|
||||
worker <file> <num>
|
||||
}
|
||||
}
|
||||
|
||||
# ...
|
||||
```
|
||||
|
||||
如果在同一服务器上运行多个应用,还可以定义多个 worker:
|
||||
|
||||
```caddyfile
|
||||
app.example.com {
|
||||
php_server {
|
||||
root /path/to/app/public
|
||||
worker index.php <num>
|
||||
}
|
||||
}
|
||||
|
||||
other.example.com {
|
||||
php_server {
|
||||
root /path/to/other/public
|
||||
worker index.php <num>
|
||||
}
|
||||
}
|
||||
# ...
|
||||
```
|
||||
|
||||
通常你只需要 `php_server` 指令,
|
||||
但如果要完全控制,则可以使用较低级别的 `php` 指令:
|
||||
|
||||
使用 `php_server` 指令等效于以下配置:
|
||||
|
||||
```caddyfile
|
||||
route {
|
||||
# 为目录请求添加尾部斜杠
|
||||
@canonicalPath {
|
||||
file {path}/index.php
|
||||
not path */
|
||||
}
|
||||
redir @canonicalPath {path}/ 308
|
||||
# 如果请求的文件不存在,则尝试 index 文件
|
||||
@indexFiles file {
|
||||
try_files {path} {path}/index.php index.php
|
||||
split_path .php
|
||||
}
|
||||
rewrite @indexFiles {http.matchers.file.relative}
|
||||
# FrankenPHP!
|
||||
@phpFiles path *.php
|
||||
php @phpFiles
|
||||
file_server
|
||||
}
|
||||
```
|
||||
|
||||
`php_server` 和 `php` 指令具有以下选项:
|
||||
|
||||
```caddyfile
|
||||
php_server [<matcher>] {
|
||||
root <directory> # 设置站点的根目录。默认值:`root` 指令。
|
||||
split_path <delim...> # 设置用于将 URI 拆分为两部分的子字符串。第一个匹配的子字符串将用于从路径中拆分"路径信息"。第一个部分以匹配的子字符串为后缀,并将假定为实际资源(CGI 脚本)名称。第二部分将设置为PATH_INFO,供脚本使用。默认值:`.php`
|
||||
resolve_root_symlink false # 禁用将 `root` 目录在符号链接时将其解析为实际值(默认启用)。
|
||||
env <key> <value> # 设置额外的环境变量,可以设置多个环境变量。
|
||||
file_server off # 禁用内置的 file_server 指令。
|
||||
worker { # 创建特定于此服务器的 worker。可以为多个 worker 多次指定。
|
||||
file <path> # 设置 worker 脚本的路径,可以相对于 php_server 根目录
|
||||
num <num> # 设置要启动的 PHP 线程数,默认为可用 CPU 数的 2 倍
|
||||
name <name> # 为 worker 设置名称,用于日志和指标。默认值:worker 文件的绝对路径。在 php_server 块中定义时始终以 m# 开头。
|
||||
watch <path> # 设置要监视文件更改的路径。可以为多个路径多次指定。
|
||||
env <key> <value> # 将额外的环境变量设置为给定值。可以为多个环境变量多次指定。此 worker 的环境变量也从 php_server 父级继承,但可以在此处覆盖。
|
||||
}
|
||||
worker <other_file> <num> # 也可以像在全局 frankenphp 块中一样使用简短形式。
|
||||
}
|
||||
```
|
||||
|
||||
## 环境变量
|
||||
|
||||
以下环境变量可用于在 `Caddyfile` 中注入 Caddy 指令,而无需对其进行修改:
|
||||
|
||||
- `SERVER_NAME`: 更改 [要监听的地址](https://caddyserver.com/docs/caddyfile/concepts#addresses),提供的主机名也将用于生成的 TLS 证书
|
||||
- `CADDY_GLOBAL_OPTIONS`: 注入 [全局选项](https://caddyserver.com/docs/caddyfile/options)
|
||||
- `FRANKENPHP_CONFIG`: 在 `frankenphp` 指令下注入配置
|
||||
|
||||
## PHP 配置
|
||||
|
||||
要加载 [其他 PHP INI 配置文件](https://www.php.net/manual/en/configuration.file.php#configuration.file.scan),
|
||||
可以使用 `PHP_INI_SCAN_DIR` 环境变量。
|
||||
设置后,PHP 将加载给定目录中存在 `.ini` 扩展名的所有文件。
|
||||
|
||||
## 启用调试模式
|
||||
|
||||
使用 Docker 镜像时,将 `CADDY_GLOBAL_OPTIONS` 环境变量设置为 `debug` 以启用调试模式:
|
||||
|
||||
```console
|
||||
docker run -v $PWD:/app/public \
|
||||
-e CADDY_GLOBAL_OPTIONS=debug \
|
||||
-p 80:80 -p 443:443 -p 443:443/udp \
|
||||
dunglas/frankenphp
|
||||
```
|
||||
169
docs/cn/docker.md
Normal file
169
docs/cn/docker.md
Normal file
@@ -0,0 +1,169 @@
|
||||
# 构建自定义 Docker 镜像
|
||||
|
||||
[FrankenPHP Docker 镜像](https://hub.docker.com/r/dunglas/frankenphp) 基于 [官方 PHP 镜像](https://hub.docker.com/_/php/)。
|
||||
Alpine Linux 和 Debian 衍生版适用于常见的处理器架构,支持 PHP 8.2、8.3 和 8.4。。[查看 Tags](https://hub.docker.com/r/dunglas/frankenphp/tags)。
|
||||
|
||||
## 如何使用镜像
|
||||
|
||||
在项目中创建 `Dockerfile`:
|
||||
|
||||
```dockerfile
|
||||
FROM dunglas/frankenphp
|
||||
|
||||
COPY . /app/public
|
||||
```
|
||||
|
||||
然后运行以下命令以构建并运行 Docker 镜像:
|
||||
|
||||
```console
|
||||
docker build -t my-php-app .
|
||||
docker run -it --rm --name my-running-app my-php-app
|
||||
```
|
||||
|
||||
## 如何安装更多 PHP 扩展
|
||||
|
||||
[`docker-php-extension-installer`](https://github.com/mlocati/docker-php-extension-installer) 脚本在基础镜像中提供。
|
||||
添加额外的 PHP 扩展很简单:
|
||||
|
||||
```dockerfile
|
||||
FROM dunglas/frankenphp
|
||||
|
||||
# 在此处添加其他扩展:
|
||||
RUN install-php-extensions \
|
||||
pdo_mysql \
|
||||
gd \
|
||||
intl \
|
||||
zip \
|
||||
opcache
|
||||
```
|
||||
|
||||
## 如何安装更多 Caddy 模块
|
||||
|
||||
FrankenPHP 建立在 Caddy 之上,所有 [Caddy 模块](https://caddyserver.com/docs/modules/) 都可以与 FrankenPHP 一起使用。
|
||||
|
||||
安装自定义 Caddy 模块的最简单方法是使用 [xcaddy](https://github.com/caddyserver/xcaddy):
|
||||
|
||||
```dockerfile
|
||||
FROM dunglas/frankenphp:builder AS builder
|
||||
|
||||
# 在构建器镜像中复制 xcaddy
|
||||
COPY --from=caddy:builder /usr/bin/xcaddy /usr/bin/xcaddy
|
||||
|
||||
# 必须启用 CGO 才能构建 FrankenPHP
|
||||
RUN CGO_ENABLED=1 \
|
||||
XCADDY_SETCAP=1 \
|
||||
XCADDY_GO_BUILD_FLAGS="-ldflags='-w -s' -tags=nobadger,nomysql,nopgx" \
|
||||
CGO_CFLAGS=$(php-config --includes) \
|
||||
CGO_LDFLAGS="$(php-config --ldflags) $(php-config --libs)" \
|
||||
xcaddy build \
|
||||
--output /usr/local/bin/frankenphp \
|
||||
--with frankenphp.dev=./ \
|
||||
--with frankenphp.dev/caddy=./caddy/ \
|
||||
--with github.com/dunglas/caddy-cbrotli \
|
||||
# Mercure 和 Vulcain 包含在官方版本中,如果不需要你可以删除它们
|
||||
--with github.com/dunglas/mercure/caddy \
|
||||
--with github.com/dunglas/vulcain/caddy
|
||||
# 在此处添加额外的 Caddy 模块
|
||||
|
||||
FROM dunglas/frankenphp AS runner
|
||||
|
||||
# 将官方二进制文件替换为包含自定义模块的二进制文件
|
||||
COPY --from=builder /usr/local/bin/frankenphp /usr/local/bin/frankenphp
|
||||
```
|
||||
|
||||
FrankenPHP 提供的 `builder` 镜像包含 libphp 的编译版本。
|
||||
[用于构建的镜像](https://hub.docker.com/r/dunglas/frankenphp/tags?name=builder) 适用于所有版本的 FrankenPHP 和 PHP,包括 Alpine 和 Debian。
|
||||
|
||||
> [!TIP]
|
||||
>
|
||||
> 如果你的系统基于 musl libc(Alpine Linux 上默认使用)并搭配 Symfony 使用,
|
||||
> 您可能需要 [增加默认堆栈大小](compile.md#使用-xcaddy)。
|
||||
|
||||
## 默认启用 worker 模式
|
||||
|
||||
设置 `FRANKENPHP_CONFIG` 环境变量以使用 worker 脚本启动 FrankenPHP:
|
||||
|
||||
```dockerfile
|
||||
FROM dunglas/frankenphp
|
||||
|
||||
# ...
|
||||
|
||||
ENV FRANKENPHP_CONFIG="worker ./public/index.php"
|
||||
```
|
||||
|
||||
## 开发挂载宿主机目录
|
||||
|
||||
要使用 FrankenPHP 轻松开发,请从包含应用程序源代码的主机挂载目录作为 Docker 容器中的 volume:
|
||||
|
||||
```console
|
||||
docker run -v $PWD:/app/public -p 80:80 -p 443:443 -p 443:443/udp --tty my-php-app
|
||||
```
|
||||
|
||||
> [!TIP]
|
||||
>
|
||||
> `--tty` 选项允许使用清晰可读的日志,而不是 JSON 日志。
|
||||
|
||||
使用 Docker Compose:
|
||||
|
||||
```yaml
|
||||
# compose.yaml
|
||||
|
||||
services:
|
||||
php:
|
||||
image: dunglas/frankenphp
|
||||
# 如果要使用自定义 Dockerfile,请取消注释以下行
|
||||
#build: .
|
||||
# 如果要在生产环境中运行,请取消注释以下行
|
||||
# restart: always
|
||||
ports:
|
||||
- "80:80" # HTTP
|
||||
- "443:443" # HTTPS
|
||||
- "443:443/udp" # HTTP/3
|
||||
volumes:
|
||||
- ./:/app/public
|
||||
- caddy_data:/data
|
||||
- caddy_config:/config
|
||||
# 在生产环境中注释以下行,它允许在 dev 中使用清晰可读日志
|
||||
tty: true
|
||||
|
||||
# Caddy 证书和配置所需的挂载目录
|
||||
volumes:
|
||||
caddy_data:
|
||||
caddy_config:
|
||||
```
|
||||
|
||||
## 以非 root 用户身份运行
|
||||
|
||||
FrankenPHP 可以在 Docker 中以非 root 用户身份运行。
|
||||
|
||||
下面是一个示例 Dockerfile:
|
||||
|
||||
```dockerfile
|
||||
FROM dunglas/frankenphp
|
||||
|
||||
ARG USER=appuser
|
||||
|
||||
RUN \
|
||||
# 在基于 alpine 的发行版使用 "adduser -D ${USER}"
|
||||
useradd ${USER}; \
|
||||
# 需要开放80和443端口的权限
|
||||
setcap CAP_NET_BIND_SERVICE=+eip /usr/local/bin/frankenphp; \
|
||||
# 需要 /data/caddy 和 /config/caddy 目录的写入权限
|
||||
chown -R ${USER}:${USER} /data/caddy && chown -R ${USER}:${USER} /config/caddy;
|
||||
|
||||
USER ${USER}
|
||||
```
|
||||
|
||||
## 更新
|
||||
|
||||
Docker 镜像会按照以下条件更新:
|
||||
|
||||
* 发布新的版本后
|
||||
* 每日 4:00(UTC 时间)检查新的 PHP 镜像
|
||||
|
||||
## 开发版本
|
||||
|
||||
可在此 [`dunglas/frankenphp-dev`](https://hub.docker.com/repository/docker/dunglas/frankenphp-dev) 仓库获取开发版本。
|
||||
每次在 GitHub 仓库的主分支有新的 commit 都会触发一次新的 build。
|
||||
|
||||
`latest*` tag 指向最新的 `main` 分支,且同样支持 `sha-<git-commit-hash>` 的 tag。
|
||||
21
docs/cn/early-hints.md
Normal file
21
docs/cn/early-hints.md
Normal file
@@ -0,0 +1,21 @@
|
||||
# 早期提示
|
||||
|
||||
FrankenPHP 原生支持 [103 Early Hints 状态码](https://developer.chrome.com/blog/early-hints/)。
|
||||
使用早期提示可以将网页的加载时间缩短 30%。
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
header('Link: </style.css>; rel=preload; as=style');
|
||||
headers_send(103);
|
||||
|
||||
// 慢速算法和 SQL 查询
|
||||
|
||||
echo <<<'HTML'
|
||||
<!DOCTYPE html>
|
||||
<title>Hello FrankenPHP</title>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
HTML;
|
||||
```
|
||||
|
||||
早期提示由普通模式和 [worker](worker.md) 模式支持。
|
||||
132
docs/cn/embed.md
Normal file
132
docs/cn/embed.md
Normal file
@@ -0,0 +1,132 @@
|
||||
# PHP 应用程序作为独立二进制文件
|
||||
|
||||
FrankenPHP 能够将 PHP 应用程序的源代码和资源文件嵌入到静态的、独立的二进制文件中。
|
||||
|
||||
由于这个特性,PHP 应用程序可以作为独立的二进制文件分发,包括应用程序本身、PHP 解释器和生产级 Web 服务器 Caddy。
|
||||
|
||||
了解有关此功能的更多信息 [Kévin 在 SymfonyCon 上的演讲](https://dunglas.dev/2023/12/php-and-symfony-apps-as-standalone-binaries/)。
|
||||
|
||||
## 准备你的应用
|
||||
|
||||
在创建独立二进制文件之前,请确保应用已准备好进行打包。
|
||||
|
||||
例如,您可能希望:
|
||||
|
||||
* 给应用安装生产环境的依赖
|
||||
* 导出 autoloader
|
||||
* 如果可能,为应用启用生产模式
|
||||
* 丢弃不需要的文件,例如 `.git` 或测试文件,以减小最终二进制文件的大小
|
||||
|
||||
例如,对于 Symfony 应用程序,您可以使用以下命令:
|
||||
|
||||
```console
|
||||
# 导出项目以避免 .git/ 等目录
|
||||
mkdir $TMPDIR/my-prepared-app
|
||||
git archive HEAD | tar -x -C $TMPDIR/my-prepared-app
|
||||
cd $TMPDIR/my-prepared-app
|
||||
|
||||
# 设置适当的环境变量
|
||||
echo APP_ENV=prod > .env.local
|
||||
echo APP_DEBUG=0 >> .env.local
|
||||
|
||||
# 删除测试文件
|
||||
rm -Rf tests/
|
||||
|
||||
# 安装依赖项
|
||||
composer install --ignore-platform-reqs --no-dev -a
|
||||
|
||||
# 优化 .env
|
||||
composer dump-env prod
|
||||
```
|
||||
|
||||
## 创建 Linux 二进制文件
|
||||
|
||||
创建 Linux 二进制文件的最简单方法是使用我们提供的基于 Docker 的构建器。
|
||||
|
||||
1. 在准备好的应用的存储库中创建一个名为 `static-build.Dockerfile` 的文件。
|
||||
|
||||
```dockerfile
|
||||
FROM --platform=linux/amd64 dunglas/frankenphp:static-builder
|
||||
|
||||
# 复制应用代码
|
||||
WORKDIR /go/src/app/dist/app
|
||||
COPY . .
|
||||
|
||||
# 构建静态二进制文件,只选择你需要的 PHP 扩展
|
||||
WORKDIR /go/src/app/
|
||||
RUN EMBED=dist/app/ \
|
||||
PHP_EXTENSIONS=ctype,iconv,pdo_sqlite \
|
||||
./build-static.sh
|
||||
```
|
||||
|
||||
> [!CAUTION]
|
||||
>
|
||||
> 某些 .dockerignore 文件(例如默认的 [symfony-docker .dockerignore](https://github.com/dunglas/symfony-docker/blob/main/.dockerignore))会忽略 vendor
|
||||
> 文件夹和环境文件。在构建之前,请务必调整或删除 .dockerignore 文件。
|
||||
|
||||
2. 构建:
|
||||
|
||||
```console
|
||||
docker build -t static-app -f static-build.Dockerfile .
|
||||
```
|
||||
|
||||
3. 提取二进制文件
|
||||
|
||||
```console
|
||||
docker cp $(docker create --name static-app-tmp static-app):/go/src/app/dist/frankenphp-linux-x86_64 my-app ; docker rm static-app-tmp
|
||||
```
|
||||
|
||||
生成的二进制文件是当前目录中名为 `my-app` 的文件。
|
||||
|
||||
## 为其他操作系统创建二进制文件
|
||||
|
||||
如果您不想使用 Docker,或者想要构建 macOS 二进制文件,你可以使用我们提供的 shell 脚本:
|
||||
|
||||
```console
|
||||
git clone https://github.com/dunglas/frankenphp
|
||||
cd frankenphp
|
||||
EMBED=/path/to/your/app \
|
||||
PHP_EXTENSIONS=ctype,iconv,pdo_sqlite \
|
||||
./build-static.sh
|
||||
```
|
||||
|
||||
在 `dist/` 目录中生成的二进制文件名称为 `frankenphp-<os>-<arch>`。
|
||||
|
||||
## 使用二进制文件
|
||||
|
||||
就是这样!`my-app` 文件(或其他操作系统上的 `dist/frankenphp-<os>-<arch>`)包含您的独立应用程序!
|
||||
|
||||
若要启动 Web 应用,请执行:
|
||||
|
||||
```console
|
||||
./my-app php-server
|
||||
```
|
||||
|
||||
如果您的应用包含 [worker 脚本](worker.md),请使用如下命令启动 worker:
|
||||
|
||||
```console
|
||||
./my-app php-server --worker public/index.php
|
||||
```
|
||||
|
||||
要启用 HTTPS(自动创建 Let's Encrypt 证书)、HTTP/2 和 HTTP/3,请指定要使用的域名:
|
||||
|
||||
```console
|
||||
./my-app php-server --domain localhost
|
||||
```
|
||||
|
||||
您还可以运行二进制文件中嵌入的 PHP CLI 脚本:
|
||||
|
||||
```console
|
||||
./my-app php-cli bin/console
|
||||
```
|
||||
|
||||
## 自定义构建
|
||||
|
||||
[阅读静态构建文档](static.md) 查看如何自定义二进制文件(扩展、PHP 版本等)。
|
||||
|
||||
## 分发二进制文件
|
||||
|
||||
创建的二进制文件不会被压缩。
|
||||
若要在发送文件之前减小文件的大小,可以对其进行压缩。
|
||||
|
||||
我们推荐使用 `xz`。
|
||||
31
docs/cn/github-actions.md
Normal file
31
docs/cn/github-actions.md
Normal file
@@ -0,0 +1,31 @@
|
||||
# 使用 GitHub Actions
|
||||
|
||||
此存储库构建 Docker 镜像并将其部署到 [Docker Hub](https://hub.docker.com/r/dunglas/frankenphp) 上
|
||||
每个批准的拉取请求或设置后在您自己的分支上。
|
||||
|
||||
## 设置 GitHub Actions
|
||||
|
||||
在存储库设置中的 `secrets` 下,添加以下字段:
|
||||
|
||||
- `REGISTRY_LOGIN_SERVER`: 要使用的 docker registry(如 `docker.io`)。
|
||||
- `REGISTRY_USERNAME`: 用于登录 registry 的用户名(如 `dunglas`)。
|
||||
- `REGISTRY_PASSWORD`: 用于登录 registry 的密码(如 `access key`)。
|
||||
- `IMAGE_NAME`: 镜像的名称(如 `dunglas/frankenphp`)。
|
||||
|
||||
## 构建和推送镜像
|
||||
|
||||
1. 创建 Pull Request 或推送到你的 Fork 分支。
|
||||
2. GitHub Actions 将生成镜像并运行每项测试。
|
||||
3. 如果生成成功,则将使用 `pr-x` 推送 registry,其中 `x` 是 PR 编号,作为标记将镜像推送到注册表。
|
||||
|
||||
## 部署镜像
|
||||
|
||||
1. 合并 Pull Request 后,GitHub Actions 将再次运行测试并生成新镜像。
|
||||
2. 如果构建成功,则 Docker 注册表中的 `main` tag 将更新。
|
||||
|
||||
## 发布
|
||||
|
||||
1. 在项目仓库中创建新 Tag。
|
||||
2. GitHub Actions 将生成镜像并运行每项测试。
|
||||
3. 如果构建成功,镜像将使用标记名称作为标记推送到 registry(例如,将创建 `v1.2.3` 和 `v1.2`)。
|
||||
4. `latest` 标签也将更新。
|
||||
69
docs/cn/known-issues.md
Normal file
69
docs/cn/known-issues.md
Normal file
@@ -0,0 +1,69 @@
|
||||
# 已知问题
|
||||
|
||||
## 不支持的 PHP 扩展
|
||||
|
||||
已知以下扩展与 FrankenPHP 不兼容:
|
||||
|
||||
| 名称 | 原因 | 替代方案 |
|
||||
|-------------------------------------------------------------|-------|----------------------------------------------------------------------------------------------------------------------|
|
||||
| [imap](https://www.php.net/manual/en/imap.installation.php) | 非线程安全 | [javanile/php-imap2](https://github.com/javanile/php-imap2), [webklex/php-imap](https://github.com/Webklex/php-imap) |
|
||||
|
||||
## get_browser
|
||||
|
||||
[get_browser()](https://www.php.net/manual/en/function.get-browser.php) 函数在一段时间后似乎表现不佳。解决方法是缓存(例如使用 [APCu](https://www.php.net/manual/zh/book.apcu.php))每个 User-Agent,因为它们是不变的。
|
||||
|
||||
## 独立的二进制和基于 Alpine 的 Docker 镜像
|
||||
|
||||
独立的二进制文件和基于 Alpine 的 docker 镜像 (`dunglas/frankenphp:*-alpine`) 使用的是 [musl libc](https://musl.libc.org/) 而不是 [glibc and friends](https://www.etalabs.net/compare_libcs.html),为的是保持较小的二进制大小。
|
||||
这可能会导致一些兼容性问题。特别是,glob 标志 `GLOB_BRACE` [不可用](https://www.php.net/manual/en/function.glob.php)。
|
||||
|
||||
## 在 Docker 中使用 `https://127.0.0.1`
|
||||
|
||||
默认情况下,FrankenPHP 会为 `localhost` 生成一个 TLS 证书。
|
||||
这是本地开发最简单且推荐的选项。
|
||||
|
||||
如果确实想使用 `127.0.0.1` 作为主机,可以通过将服务器名称设置为 `127.0.0.1` 来配置它以为其生成证书。
|
||||
|
||||
如果你使用 Docker,因为 [Docker 网络](https://docs.docker.com/network/) 问题,只做这些是不够的。
|
||||
您将收到类似于以下内容的 TLS 错误 `curl: (35) LibreSSL/3.3.6: error:1404B438:SSL routines:ST_CONNECT:tlsv1 alert internal error`。
|
||||
|
||||
如果你使用的是 Linux,解决方案是使用 [使用宿主机网络](https://docs.docker.com/network/network-tutorial-host/):
|
||||
|
||||
```console
|
||||
docker run \
|
||||
-e SERVER_NAME="127.0.0.1" \
|
||||
-v $PWD:/app/public \
|
||||
--network host \
|
||||
dunglas/frankenphp
|
||||
```
|
||||
|
||||
Mac 和 Windows 不支持 Docker 使用宿主机网络。在这些平台上,您必须猜测容器的 IP 地址并将其包含在服务器名称中。
|
||||
|
||||
运行 `docker network inspect bridge` 并查看 `Containers`,找到 `IPv4Address` 当前分配的最后一个 IP 地址,并增加 1。如果没有容器正在运行,则第一个分配的 IP 地址通常为 `172.17.0.2`。
|
||||
|
||||
然后将其包含在 `SERVER_NAME` 环境变量中:
|
||||
|
||||
```console
|
||||
docker run \
|
||||
-e SERVER_NAME="127.0.0.1, 172.17.0.3" \
|
||||
-v $PWD:/app/public \
|
||||
-p 80:80 -p 443:443 -p 443:443/udp \
|
||||
dunglas/frankenphp
|
||||
```
|
||||
|
||||
> [!CAUTION]
|
||||
>
|
||||
> 请务必将 `172.17.0.3` 替换为将分配给容器的 IP。
|
||||
|
||||
您现在应该能够从主机访问 `https://127.0.0.1`。
|
||||
|
||||
如果不是这种情况,请在调试模式下启动 FrankenPHP 以尝试找出问题:
|
||||
|
||||
```console
|
||||
docker run \
|
||||
-e CADDY_GLOBAL_OPTIONS="debug" \
|
||||
-e SERVER_NAME="127.0.0.1" \
|
||||
-v $PWD:/app/public \
|
||||
-p 80:80 -p 443:443 -p 443:443/udp \
|
||||
dunglas/frankenphp
|
||||
```
|
||||
74
docs/cn/laravel.md
Normal file
74
docs/cn/laravel.md
Normal file
@@ -0,0 +1,74 @@
|
||||
# Laravel
|
||||
|
||||
## Docker
|
||||
|
||||
使用 FrankenPHP 为 [Laravel](https://laravel.com) Web 应用程序提供服务就像将项目挂载到官方 Docker 镜像的 `/app` 目录中一样简单。
|
||||
|
||||
从 Laravel 应用程序的主目录运行以下命令:
|
||||
|
||||
```console
|
||||
docker run -p 80:80 -p 443:443 -p 443:443/udp -v $PWD:/app dunglas/frankenphp
|
||||
```
|
||||
|
||||
尽情享受吧!
|
||||
|
||||
## 本地安装
|
||||
|
||||
或者,你可以从本地机器上使用 FrankenPHP 运行 Laravel 项目:
|
||||
|
||||
1. [下载与您的系统相对应的二进制文件](https://github.com/dunglas/frankenphp/releases)
|
||||
2. 将以下配置添加到 Laravel 项目根目录中名为 `Caddyfile` 的文件中:
|
||||
|
||||
```caddyfile
|
||||
{
|
||||
frankenphp
|
||||
}
|
||||
|
||||
# 服务器的域名
|
||||
localhost {
|
||||
# 将 webroot 设置为 public/ 目录
|
||||
root public/
|
||||
# 启用压缩(可选)
|
||||
encode zstd br gzip
|
||||
# 执行当前目录中的 PHP 文件并提供资产
|
||||
php_server
|
||||
}
|
||||
```
|
||||
|
||||
3. 从 Laravel 项目的根目录启动 FrankenPHP:`frankenphp run`
|
||||
|
||||
## Laravel Octane
|
||||
|
||||
Octane 可以通过 Composer 包管理器安装:
|
||||
|
||||
```console
|
||||
composer require laravel/octane
|
||||
```
|
||||
|
||||
安装 Octane 后,您可以执行 `octane:install` Artisan 命令,该命令会将 Octane 的配置文件安装到您的应用程序中:
|
||||
|
||||
```console
|
||||
php artisan octane:install --server=frankenphp
|
||||
```
|
||||
|
||||
Octane 服务可以通过 `octane:frankenphp` Artisan 命令启动。
|
||||
|
||||
```console
|
||||
php artisan octane:frankenphp
|
||||
```
|
||||
|
||||
`octane:frankenphp` 命令可以采用以下选项:
|
||||
|
||||
* `--host`: 服务器应绑定到的 IP 地址(默认值: `127.0.0.1`)
|
||||
* `--port`: 服务器应可用的端口(默认值: `8000`)
|
||||
* `--admin-port`: 管理服务器应可用的端口(默认值: `2019`)
|
||||
* `--workers`: 应可用于处理请求的 worker 数(默认值: `auto`)
|
||||
* `--max-requests`: 在 worker 重启之前要处理的请求数(默认值: `500`)
|
||||
* `--caddyfile`: FrankenPHP `Caddyfile` 文件的路径
|
||||
* `--https`: 开启 HTTPS、HTTP/2 和 HTTP/3,自动生成和延长证书
|
||||
* `--http-redirect`: 启用 HTTP 到 HTTPS 重定向(仅在使用 `--https` 时启用)
|
||||
* `--watch`: 修改应用程序时自动重新加载服务器
|
||||
* `--poll`: 在监视时使用文件系统轮询,以便通过网络监视文件
|
||||
* `--log-level`: 在指定日志级别或高于指定日志级别的日志消息
|
||||
|
||||
你可以了解更多关于 [Laravel Octane 官方文档](https://laravel.com/docs/octane)。
|
||||
12
docs/cn/mercure.md
Normal file
12
docs/cn/mercure.md
Normal file
@@ -0,0 +1,12 @@
|
||||
# 实时
|
||||
|
||||
FrankenPHP 带有一个内置的 Mercure Hub!
|
||||
Mercure 允许将事件实时推送到所有连接的设备:它们将立即收到 JavaScript 事件。
|
||||
|
||||
无需 JS 库或 SDK!
|
||||
|
||||

|
||||
|
||||
要启用 Mercure Hub,请按照 [Mercure 网站](https://mercure.rocks/docs/hub/config) 中的说明更新 `Caddyfile`。
|
||||
|
||||
要从您的代码中推送 Mercure 更新,我们推荐 [Symfony Mercure Component](https://symfony.com/components/Mercure)(不需要 Symfony 框架来使用)。
|
||||
141
docs/cn/production.md
Normal file
141
docs/cn/production.md
Normal file
@@ -0,0 +1,141 @@
|
||||
# 在生产环境中部署
|
||||
|
||||
在本教程中,我们将学习如何使用 Docker Compose 在单个服务器上部署 PHP 应用程序。
|
||||
|
||||
如果您使用的是 Symfony,请阅读 Symfony Docker 项目(使用 FrankenPHP)的 [在生产环境中部署](https://github.com/dunglas/symfony-docker/blob/main/docs/production.md) 文档条目。
|
||||
|
||||
如果您使用的是 API Platform(同样使用 FrankenPHP),请参阅 [框架的部署文档](https://api-platform.com/docs/deployment/)。
|
||||
|
||||
## 准备应用
|
||||
|
||||
首先,在 PHP 项目的根目录中创建一个 `Dockerfile`:
|
||||
|
||||
```dockerfile
|
||||
FROM dunglas/frankenphp
|
||||
|
||||
# 请将 "your-domain-name.example.com" 替换为您的域名
|
||||
ENV SERVER_NAME=your-domain-name.example.com
|
||||
# 如果要禁用 HTTPS,请改用以下值:
|
||||
#ENV SERVER_NAME=:80
|
||||
|
||||
# 启用 PHP 生产配置
|
||||
RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini"
|
||||
|
||||
# 将项目的 PHP 文件复制到 public 目录中
|
||||
COPY . /app/public
|
||||
# 如果你使用 Symfony 或 Laravel,你需要复制整个项目:
|
||||
#COPY . /app
|
||||
```
|
||||
|
||||
有关更多详细信息和选项,请参阅 [构建自定义 Docker 镜像](docker.md)。
|
||||
要了解如何自定义配置,请安装 PHP 扩展和 Caddy 模块。
|
||||
|
||||
如果您的项目使用 Composer,
|
||||
请务必将其包含在 Docker 镜像中并安装您的依赖。
|
||||
|
||||
然后,添加一个 `compose.yaml` 文件:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
php:
|
||||
image: dunglas/frankenphp
|
||||
restart: always
|
||||
ports:
|
||||
- "80:80" # HTTP
|
||||
- "443:443" # HTTPS
|
||||
- "443:443/udp" # HTTP/3
|
||||
volumes:
|
||||
- caddy_data:/data
|
||||
- caddy_config:/config
|
||||
|
||||
# Caddy 证书和配置所需的挂载目录
|
||||
volumes:
|
||||
caddy_data:
|
||||
caddy_config:
|
||||
```
|
||||
|
||||
> [!NOTE]
|
||||
>
|
||||
> 前面的示例适用于生产用途。
|
||||
> 在开发中,你可能希望使用挂载目录,不同的 PHP 配置和不同的 `SERVER_NAME` 环境变量值。
|
||||
>
|
||||
> 见 [Symfony Docker](https://github.com/dunglas/symfony-docker) 项目
|
||||
> (使用 FrankenPHP)作为使用多阶段镜像的更高级示例,
|
||||
> Composer、额外的 PHP 扩展等。
|
||||
|
||||
最后,如果您使用 Git,请提交这些文件并推送。
|
||||
|
||||
## 准备服务器
|
||||
|
||||
若要在生产环境中部署应用程序,需要一台服务器。
|
||||
在本教程中,我们将使用 DigitalOcean 提供的虚拟机,但任何 Linux 服务器都可以工作。
|
||||
如果您已经有安装了 Docker 的 Linux 服务器,您可以直接跳到 [下一节](#配置域名)。
|
||||
|
||||
否则,请使用 [此会员链接](https://m.do.co/c/5d8aabe3ab80) 获得 200 美元的免费信用额度,创建一个帐户,然后单击“Create a Droplet”。
|
||||
然后,单击“Choose an image”部分下的“Marketplace”选项卡,然后搜索名为“Docker”的应用程序。
|
||||
这将配置已安装最新版本的 Docker 和 Docker Compose 的 Ubuntu 服务器!
|
||||
|
||||
出于测试目的,最便宜的就足够了。
|
||||
对于实际的生产用途,您可能需要在“general purpose”部分中选择一个计划来满足您的需求。
|
||||
|
||||

|
||||
|
||||
您可以保留其他设置的默认值,也可以根据需要进行调整。
|
||||
不要忘记添加您的 SSH 密钥或创建密码,然后点击“完成并创建”按钮。
|
||||
|
||||
然后,在 Droplet 预配时等待几秒钟。
|
||||
Droplet 准备就绪后,使用 SSH 进行连接:
|
||||
|
||||
```console
|
||||
ssh root@<droplet-ip>
|
||||
```
|
||||
|
||||
## 配置域名
|
||||
|
||||
在大多数情况下,您需要将域名与您的网站相关联。
|
||||
如果您还没有域名,则必须通过注册商购买。
|
||||
|
||||
然后为您的域名创建类型为 `A` 的 DNS 记录,指向服务器的 IP 地址:
|
||||
|
||||
```dns
|
||||
your-domain-name.example.com. IN A 207.154.233.113
|
||||
```
|
||||
|
||||
DigitalOcean 域服务示例(“Networking” > “Domains”):
|
||||
|
||||

|
||||
|
||||
> [!NOTE]
|
||||
>
|
||||
> Let's Encrypt 是 FrankenPHP 默认用于自动生成 TLS 证书的服务,不支持使用裸 IP 地址。使用域名是使用 Let's Encrypt 的必要条件。
|
||||
|
||||
## 部署
|
||||
|
||||
使用 `git clone`、`scp` 或任何其他可能适合您需要的工具在服务器上复制您的项目。
|
||||
如果使用 GitHub,则可能需要使用 [部署密钥](https://docs.github.com/en/free-pro-team@latest/developers/overview/managing-deploy-keys#deploy-keys)。
|
||||
部署密钥也 [由 GitLab 支持](https://docs.gitlab.com/ee/user/project/deploy_keys/)。
|
||||
|
||||
Git 示例:
|
||||
|
||||
```console
|
||||
git clone git@github.com:<username>/<project-name>.git
|
||||
```
|
||||
|
||||
进入包含项目 (`<project-name>`) 的目录,并在生产模式下启动应用:
|
||||
|
||||
```console
|
||||
docker compose up -d --wait
|
||||
```
|
||||
|
||||
您的服务器已启动并运行,并且已自动为您生成 HTTPS 证书。
|
||||
去 `https://your-domain-name.example.com` 享受吧!
|
||||
|
||||
> [!CAUTION]
|
||||
>
|
||||
> Docker 有一个缓存层,请确保每个部署都有正确的构建,或者使用 --no-cache 选项重新构建项目以避免缓存问题。
|
||||
|
||||
## 在多个节点上部署
|
||||
|
||||
如果要在计算机集群上部署应用程序,可以使用 [Docker Swarm](https://docs.docker.com/engine/swarm/stack-deploy/),
|
||||
它与提供的 Compose 文件兼容。
|
||||
要在 Kubernetes 上部署,请查看 [API 平台提供的 Helm 图表](https://api-platform.com/docs/deployment/kubernetes/),同样也使用 FrankenPHP。
|
||||
100
docs/cn/static.md
Normal file
100
docs/cn/static.md
Normal file
@@ -0,0 +1,100 @@
|
||||
# 创建静态构建
|
||||
|
||||
基于 [static-php-cli](https://github.com/crazywhalecc/static-php-cli) 项目(这个项目支持所有 SAPI,不仅仅是 `cli`),
|
||||
FrankenPHP 已支持创建静态二进制,无需安装本地 PHP。
|
||||
|
||||
使用这种方法,我们可构建一个包含 PHP 解释器、Caddy Web 服务器和 FrankenPHP 的可移植二进制文件!
|
||||
|
||||
FrankenPHP 还支持 [将 PHP 应用程序嵌入到静态二进制文件中](embed.md)。
|
||||
|
||||
## Linux
|
||||
|
||||
我们提供了一个 Docker 镜像来构建 Linux 静态二进制文件:
|
||||
|
||||
```console
|
||||
docker buildx bake --load static-builder
|
||||
docker cp $(docker create --name static-builder-musl dunglas/frankenphp:static-builder-musl):/go/src/app/dist/frankenphp-linux-$(uname -m) frankenphp ; docker rm static-builder
|
||||
```
|
||||
|
||||
生成的静态二进制文件名为 `frankenphp`,可在当前目录中找到。
|
||||
|
||||
如果您想在没有 Docker 的情况下构建静态二进制文件,请查看 macOS 说明,它也适用于 Linux。
|
||||
|
||||
### 自定义扩展
|
||||
|
||||
默认情况下,大多数流行的 PHP 扩展都会被编译。
|
||||
|
||||
若要减小二进制文件的大小并减少攻击面,可以选择使用 `PHP_EXTENSIONS` Docker 参数来自定义构建的扩展。
|
||||
|
||||
例如,运行以下命令以生成仅包含 `opcache,pdo_sqlite` 扩展的二进制:
|
||||
|
||||
```console
|
||||
docker buildx bake --load --set static-builder.args.PHP_EXTENSIONS=opcache,pdo_sqlite static-builder
|
||||
# ...
|
||||
```
|
||||
|
||||
若要将启用其他功能的库添加到已启用的扩展中,可以使用 `PHP_EXTENSION_LIBS` Docker 参数:
|
||||
|
||||
```console
|
||||
docker buildx bake \
|
||||
--load \
|
||||
--set static-builder.args.PHP_EXTENSIONS=gd \
|
||||
--set static-builder.args.PHP_EXTENSION_LIBS=libjpeg,libwebp \
|
||||
static-builder
|
||||
```
|
||||
|
||||
### 额外的 Caddy 模块
|
||||
|
||||
要向 [xcaddy](https://github.com/caddyserver/xcaddy) 添加额外的 Caddy 模块或传递其他参数,请使用 `XCADDY_ARGS` Docker 参数:
|
||||
|
||||
```console
|
||||
docker buildx bake \
|
||||
--load \
|
||||
--set static-builder.args.XCADDY_ARGS="--with github.com/darkweak/souin/plugins/caddy --with github.com/dunglas/caddy-cbrotli --with github.com/dunglas/mercure/caddy --with github.com/dunglas/vulcain/caddy" \
|
||||
static-builder
|
||||
```
|
||||
|
||||
在本例中,我们为 Caddy 添加了 [Souin](https://souin.io) HTTP 缓存模块,以及 [cbrotli](https://github.com/dunglas/caddy-cbrotli)、[Mercure](https://mercure.rocks) 和 [Vulcain](https://vulcain.rocks) 模块。
|
||||
|
||||
> [!TIP]
|
||||
>
|
||||
> 如果 `XCADDY_ARGS` 为空或未设置,则默认包含 cbrotli、Mercure 和 Vulcain 模块。
|
||||
> 如果自定义了 `XCADDY_ARGS` 的值,则必须显式地包含它们。
|
||||
|
||||
参见:[自定义构建](#自定义构建)
|
||||
|
||||
### GitHub Token
|
||||
|
||||
如果遇到了 GitHub API 速率限制,请在 `GITHUB_TOKEN` 的环境变量中设置 GitHub Personal Access Token:
|
||||
|
||||
```console
|
||||
GITHUB_TOKEN="xxx" docker --load buildx bake static-builder
|
||||
# ...
|
||||
```
|
||||
|
||||
## macOS
|
||||
|
||||
运行以下脚本以创建适用于 macOS 的静态二进制文件(需要先安装 [Homebrew](https://brew.sh/)):
|
||||
|
||||
```console
|
||||
git clone https://github.com/dunglas/frankenphp
|
||||
cd frankenphp
|
||||
./build-static.sh
|
||||
```
|
||||
|
||||
注意:此脚本也适用于 Linux(可能也适用于其他 Unix 系统),我们提供的用于构建静态二进制的 Docker 镜像也在内部使用这个脚本。
|
||||
|
||||
## 自定义构建
|
||||
|
||||
以下环境变量可以传递给 `docker build` 和 `build-static.sh`
|
||||
脚本来自定义静态构建:
|
||||
|
||||
* `FRANKENPHP_VERSION`: 要使用的 FrankenPHP 版本
|
||||
* `PHP_VERSION`: 要使用的 PHP 版本
|
||||
* `PHP_EXTENSIONS`: 要构建的 PHP 扩展([支持的扩展列表](https://static-php.dev/zh/guide/extensions.html))
|
||||
* `PHP_EXTENSION_LIBS`: 要构建的额外库,为扩展添加额外的功能
|
||||
* `XCADDY_ARGS`:传递给 [xcaddy](https://github.com/caddyserver/xcaddy) 的参数,例如用于添加额外的 Caddy 模块
|
||||
* `EMBED`: 要嵌入二进制文件的 PHP 应用程序的路径
|
||||
* `CLEAN`: 设置后,libphp 及其所有依赖项都是重新构建的(不使用缓存)
|
||||
* `DEBUG_SYMBOLS`: 设置后,调试符号将被保留在二进制文件内
|
||||
* `RELEASE`: (仅限维护者)设置后,生成的二进制文件将上传到 GitHub 上
|
||||
114
docs/cn/worker.md
Normal file
114
docs/cn/worker.md
Normal file
@@ -0,0 +1,114 @@
|
||||
# 使用 FrankenPHP Workers
|
||||
|
||||
启动应用程序一次并将其保存在内存中。
|
||||
FrankenPHP 将在几毫秒内处理传入的请求。
|
||||
|
||||
## 启动 Worker 脚本
|
||||
|
||||
### Docker
|
||||
|
||||
将 `FRANKENPHP_CONFIG` 环境变量的值设置为 `worker /path/to/your/worker/script.php`:
|
||||
|
||||
```console
|
||||
docker run \
|
||||
-e FRANKENPHP_CONFIG="worker /app/path/to/your/worker/script.php" \
|
||||
-v $PWD:/app \
|
||||
-p 80:80 -p 443:443 -p 443:443/udp \
|
||||
dunglas/frankenphp
|
||||
```
|
||||
|
||||
### 独立二进制
|
||||
|
||||
使用 `php-server` 命令的 `--worker` 选项, 执行命令使当前目录的内容使用 worker:
|
||||
|
||||
```console
|
||||
frankenphp php-server --worker /path/to/your/worker/script.php
|
||||
```
|
||||
|
||||
## Symfony Runtime
|
||||
|
||||
FrankenPHP 的 worker 模式由 [Symfony Runtime 组件](https://symfony.com/doc/current/components/runtime.html) 支持。
|
||||
要在 worker 中启动任何 Symfony 应用程序,请安装 [PHP Runtime](https://github.com/php-runtime/runtime) 的 FrankenPHP 软件包:
|
||||
|
||||
```console
|
||||
composer require runtime/frankenphp-symfony
|
||||
```
|
||||
|
||||
通过定义 `APP_RUNTIME` 环境变量来启动你的应用服务器,以使用 FrankenPHP Symfony Runtime:
|
||||
|
||||
```console
|
||||
docker run \
|
||||
-e FRANKENPHP_CONFIG="worker ./public/index.php" \
|
||||
-e APP_RUNTIME=Runtime\\FrankenPhpSymfony\\Runtime \
|
||||
-v $PWD:/app \
|
||||
-p 80:80 -p 443:443 -p 443:443/udp \
|
||||
dunglas/frankenphp
|
||||
```
|
||||
|
||||
## Laravel Octane
|
||||
|
||||
请参阅 [文档](laravel.md#laravel-octane)。
|
||||
|
||||
## 自定义应用程序
|
||||
|
||||
以下示例演示如何在不依赖第三方库的情况下创建自己的 worker 脚本:
|
||||
|
||||
```php
|
||||
<?php
|
||||
// public/index.php
|
||||
|
||||
// 防止在客户端连接中断时 worker 线程脚本终止
|
||||
ignore_user_abort(true);
|
||||
|
||||
// 启动应用
|
||||
require __DIR__.'/vendor/autoload.php';
|
||||
|
||||
$myApp = new \App\Kernel();
|
||||
$myApp->boot();
|
||||
|
||||
// 循环外的处理程序以获得更好的性能(减少工作量)
|
||||
$handler = static function () use ($myApp) {
|
||||
// 收到请求时调用
|
||||
// 超全局变量 php://input
|
||||
echo $myApp->handle($_GET, $_POST, $_COOKIE, $_FILES, $_SERVER);
|
||||
};
|
||||
for ($nbRequests = 0, $running = true; isset($_SERVER['MAX_REQUESTS']) && ($nbRequests < ((int)$_SERVER['MAX_REQUESTS'])) && $running; ++$nbRequests) {
|
||||
$running = \frankenphp_handle_request($handler);
|
||||
|
||||
// 发送 HTTP 响应后执行某些操作
|
||||
$myApp->terminate();
|
||||
|
||||
// 调用垃圾回收器以减少在页面生成过程中触发垃圾回收器的几率
|
||||
gc_collect_cycles();
|
||||
}
|
||||
// 结束清理
|
||||
$myApp->shutdown();
|
||||
```
|
||||
|
||||
然后,启动应用并使用 `FRANKENPHP_CONFIG` 环境变量来配置你的 worker:
|
||||
|
||||
```console
|
||||
docker run \
|
||||
-e FRANKENPHP_CONFIG="worker ./public/index.php" \
|
||||
-v $PWD:/app \
|
||||
-p 80:80 -p 443:443 -p 443:443/udp \
|
||||
dunglas/frankenphp
|
||||
```
|
||||
|
||||
默认情况下,每个 CPU 启动一个 worker。
|
||||
您还可以配置要启动的 worker 数:
|
||||
|
||||
```console
|
||||
docker run \
|
||||
-e FRANKENPHP_CONFIG="worker ./public/index.php 42" \
|
||||
-v $PWD:/app \
|
||||
-p 80:80 -p 443:443 -p 443:443/udp \
|
||||
dunglas/frankenphp
|
||||
```
|
||||
|
||||
### 在一定数量的请求后重新启动 Worker
|
||||
|
||||
由于 PHP 最初不是为长时间运行的进程而设计的,因此仍然有许多库和遗留代码会发生内存泄露。
|
||||
在 worker 模式下使用此类代码的解决方法是在处理一定数量的请求后重新启动 worker 程序脚本:
|
||||
|
||||
前面的 worker 代码段允许通过设置名为 `MAX_REQUESTS` 的环境变量来配置要处理的最大请求数。
|
||||
106
docs/compile.md
106
docs/compile.md
@@ -1,24 +1,42 @@
|
||||
# Compile From Sources
|
||||
|
||||
This document explain how to create a FrankenPHP build that will load PHP as a dymanic library.
|
||||
This document explains how to create a FrankenPHP binary that will load PHP as a dynamic library.
|
||||
This is the recommended method.
|
||||
|
||||
Alternatively, [creating static builds](static.md) is also possible.
|
||||
Alternatively, [fully and mostly static builds](static.md) can also be created.
|
||||
|
||||
## Install PHP
|
||||
|
||||
FrankenPHP is compatible with the PHP 8.2 and superior.
|
||||
FrankenPHP is compatible with PHP 8.2 and superior.
|
||||
|
||||
First, [get the sources of PHP](https://www.php.net/downloads.php) and extract them:
|
||||
### With Homebrew (Linux and Mac)
|
||||
|
||||
The easiest way to install a version of libphp compatible with FrankenPHP is to use the ZTS packages provided by [Homebrew PHP](https://github.com/shivammathur/homebrew-php).
|
||||
|
||||
First, if not already done, install [Homebrew](https://brew.sh).
|
||||
|
||||
Then, install the ZTS variant of PHP, Brotli (optional, for compression support) and watcher (optional, for file change detection):
|
||||
|
||||
```console
|
||||
brew install shivammathur/php/php-zts brotli watcher
|
||||
brew link --overwrite --force shivammathur/php/php-zts
|
||||
```
|
||||
|
||||
### By Compiling PHP
|
||||
|
||||
Alternatively, you can compile PHP from sources with the options needed by FrankenPHP by following these steps.
|
||||
~~
|
||||
~~First, [get the PHP sources](https://www.php.net/downloads.php) and extract them:
|
||||
|
||||
```console
|
||||
tar xf php-*
|
||||
cd php-*/
|
||||
```
|
||||
|
||||
Then, configure PHP for your platform:
|
||||
Then, run the `configure` script with the options needed for your platform.
|
||||
The following `./configure` flags are mandatory, but you can add others, for example, to compile extensions or additional features.
|
||||
|
||||
### Linux
|
||||
#### Linux
|
||||
|
||||
```console
|
||||
./configure \
|
||||
@@ -28,20 +46,12 @@ Then, configure PHP for your platform:
|
||||
--enable-zend-max-execution-timers
|
||||
```
|
||||
|
||||
Finally, compile and install PHP:
|
||||
#### Mac
|
||||
|
||||
Use the [Homebrew](https://brew.sh/) package manager to install the required and optional dependencies:
|
||||
|
||||
```console
|
||||
make -j$(nproc)
|
||||
sudo make install
|
||||
```
|
||||
|
||||
### Mac
|
||||
|
||||
Use the [Homebrew](https://brew.sh/) package manager to install
|
||||
`libiconv`, `bison`, `re2c` and `pkg-config`:
|
||||
|
||||
```console
|
||||
brew install libiconv bison re2c pkg-config
|
||||
brew install libiconv bison brotli re2c pkg-config watcher
|
||||
echo 'export PATH="/opt/homebrew/opt/bison/bin:$PATH"' >> ~/.zshrc
|
||||
```
|
||||
|
||||
@@ -49,31 +59,69 @@ Then run the configure script:
|
||||
|
||||
```console
|
||||
./configure \
|
||||
--enable-embed=static \
|
||||
--enable-embed \
|
||||
--enable-zts \
|
||||
--disable-zend-signals \
|
||||
--disable-opcache-jit \
|
||||
--enable-static \
|
||||
--enable-shared=no \
|
||||
--with-iconv=/opt/homebrew/opt/libiconv/
|
||||
```
|
||||
|
||||
These flags are required, but you can add other flags (e.g. extra extensions)
|
||||
if needed.
|
||||
#### Compile PHP
|
||||
|
||||
Finally, compile and install PHP:
|
||||
|
||||
```console
|
||||
make -j$(sysctl -n hw.logicalcpu)
|
||||
make -j"$(getconf _NPROCESSORS_ONLN)"
|
||||
sudo make install
|
||||
```
|
||||
|
||||
#### Compile the Go App
|
||||
## Install Optional Dependencies
|
||||
|
||||
You can now use the Go library and compile our Caddy build:
|
||||
Some FrankenPHP features depend on optional system dependencies that must be installed.
|
||||
Alternatively, these features can be disabled by passing build tags to the Go compiler.
|
||||
|
||||
| Feature | Dependency | Build tag to disable it |
|
||||
|--------------------------------|-----------------------------------------------------------------------|-------------------------|
|
||||
| Brotli compression | [Brotli](https://github.com/google/brotli) | nobrotli |
|
||||
| Restart workers on file change | [Watcher C](https://github.com/e-dant/watcher/tree/release/watcher-c) | nowatcher |
|
||||
|
||||
## Compile the Go App
|
||||
|
||||
You can now build the final binary.
|
||||
|
||||
### Using xcaddy
|
||||
|
||||
The recommended way is to use [xcaddy](https://github.com/caddyserver/xcaddy) to compile FrankenPHP.
|
||||
`xcaddy` also allows to easily add [custom Caddy modules](https://caddyserver.com/docs/modules/) and FrankenPHP extensions:
|
||||
|
||||
```console
|
||||
CGO_ENABLED=1 \
|
||||
XCADDY_GO_BUILD_FLAGS="-ldflags='-w -s' -tags=nobadger,nomysql,nopgx" \
|
||||
CGO_CFLAGS=$(php-config --includes) \
|
||||
CGO_LDFLAGS="$(php-config --ldflags) $(php-config --libs)" \
|
||||
xcaddy build \
|
||||
--output frankenphp \
|
||||
--with frankenphp.dev/caddy \
|
||||
--with github.com/dunglas/mercure/caddy \
|
||||
--with github.com/dunglas/vulcain/caddy
|
||||
# Add extra Caddy modules and FrankenPHP extensions here
|
||||
```
|
||||
curl -L https://github.com/dunglas/frankenphp/archive/refs/heads/main.tar.gz | tar x
|
||||
|
||||
> [!TIP]
|
||||
>
|
||||
> If you're using musl libc (the default on Alpine Linux) and Symfony,
|
||||
> you may need to increase the default stack size.
|
||||
> Otherwise, you may get errors like `PHP Fatal error: Maximum call stack size of 83360 bytes reached during compilation. Try splitting expression`
|
||||
>
|
||||
> To do so, change the `XCADDY_GO_BUILD_FLAGS` environment variable to something like
|
||||
> `XCADDY_GO_BUILD_FLAGS=$'-ldflags "-w -s -extldflags \'-Wl,-z,stack-size=0x80000\'"'`
|
||||
> (change the stack size value according to your app needs).
|
||||
|
||||
### Without xcaddy
|
||||
|
||||
Alternatively, it's possible to compile FrankenPHP without `xcaddy` by using the `go` command directly:
|
||||
|
||||
```console
|
||||
curl -L https://github.com/dunglas/frankenphp/archive/refs/heads/main.tar.gz | tar xz
|
||||
cd frankenphp-main/caddy/frankenphp
|
||||
CGO_CFLAGS=$(php-config --includes) go build
|
||||
CGO_CFLAGS=$(php-config --includes) CGO_LDFLAGS="$(php-config --ldflags) $(php-config --libs)" go build -tags=nobadger,nomysql,nopgx
|
||||
```
|
||||
|
||||
271
docs/config.md
271
docs/config.md
@@ -1,85 +1,278 @@
|
||||
# Configuration
|
||||
|
||||
FrankenPHP, Caddy as well the Mercure and Vulcain modules can be configured using [the formats supported by Caddy](https://caddyserver.com/docs/getting-started#your-first-config).
|
||||
FrankenPHP, Caddy as well as the Mercure and Vulcain modules can be configured using [the formats supported by Caddy](https://caddyserver.com/docs/getting-started#your-first-config).
|
||||
|
||||
In the Docker image, the `Caddyfile` is located at `/etc/Caddyfile`.
|
||||
In [the Docker images](docker.md), the `Caddyfile` is located at `/etc/frankenphp/Caddyfile`.
|
||||
The static binary will also look for the `Caddyfile` in the directory where the `frankenphp run` command is executed.
|
||||
You can specify a custom path with the `-c` or `--config` option.
|
||||
|
||||
You can also configure PHP using `php.ini` as usual.
|
||||
PHP itself can be configured [using a `php.ini` file](https://www.php.net/manual/en/configuration.file.php).
|
||||
|
||||
In the Docker image, the `php.ini` file is located at `/usr/local/lib/php.ini`.
|
||||
Depending on your installation method, the PHP interpreter will look for configuration files in locations described above.
|
||||
|
||||
## Caddy Directives
|
||||
## Docker
|
||||
|
||||
To register the FrankenPHP executor, the `frankenphp` directive must be set in Caddy global options, then the `php` HTTP directive must be set under routes serving PHP scripts:
|
||||
- `php.ini`: `/usr/local/etc/php/php.ini` (no `php.ini` is provided by default)
|
||||
- additional configuration files: `/usr/local/etc/php/conf.d/*.ini`
|
||||
- PHP extensions: `/usr/local/lib/php/extensions/no-debug-zts-<YYYYMMDD>/`
|
||||
- You should copy an official template provided by the PHP project:
|
||||
|
||||
```dockerfile
|
||||
FROM dunglas/frankenphp
|
||||
|
||||
Then, you can use the `php` HTTP directive to execute PHP scripts:
|
||||
# Production:
|
||||
RUN cp $PHP_INI_DIR/php.ini-production $PHP_INI_DIR/php.ini
|
||||
|
||||
# Or development:
|
||||
RUN cp $PHP_INI_DIR/php.ini-development $PHP_INI_DIR/php.ini
|
||||
```
|
||||
|
||||
## RPM and Debian packages
|
||||
|
||||
- `php.ini`: `/etc/frankenphp/php.ini` (a `php.ini` file with production presets is provided by default)
|
||||
- additional configuration files: `/etc/frankenphp/php.d/*.ini`
|
||||
- PHP extensions: `/usr/lib/frankenphp/modules/`
|
||||
|
||||
## Static binary
|
||||
|
||||
- `php.ini`: The directory in which `frankenphp run` or `frankenphp php-server` is executed, then `/etc/frankenphp/php.ini`
|
||||
- additional configuration files: `/etc/frankenphp/php.d/*.ini`
|
||||
- PHP extensions: cannot be loaded, bundle them in the binary itself
|
||||
- copy one of `php.ini-production` or `php.ini-development` provided [in the PHP sources](https://github.com/php/php-src/).
|
||||
|
||||
## Caddyfile Config
|
||||
|
||||
The `php_server` or the `php` [HTTP directives](https://caddyserver.com/docs/caddyfile/concepts#directives) may be used within the site blocks to serve your PHP app.
|
||||
|
||||
Minimal example:
|
||||
|
||||
```caddyfile
|
||||
{
|
||||
frankenphp
|
||||
}
|
||||
|
||||
localhost {
|
||||
route {
|
||||
php {
|
||||
root <directory> # Sets the root folder to the site. Default: `root` directive.
|
||||
split_path <delim...> # 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`
|
||||
resolve_root_symlink # Enables resolving the `root` directory to its actual value by evaluating a symbolic link, if one exists.
|
||||
env <key> <value> # Sets an extra environment variable to the given value. Can be specified more than once for multiple environment variables.
|
||||
}
|
||||
}
|
||||
# Enable compression (optional)
|
||||
encode zstd br gzip
|
||||
# Execute PHP files in the current directory and serve assets
|
||||
php_server
|
||||
}
|
||||
```
|
||||
|
||||
Optionnaly, the number of threads to create and [worker scripts](worker.md) to start with the server can be specified under the global directive.
|
||||
You can also explicitly configure FrankenPHP using the global option:
|
||||
The `frankenphp` [global option](https://caddyserver.com/docs/caddyfile/concepts#global-options) can be used to configure FrankenPHP.
|
||||
|
||||
```caddyfile
|
||||
{
|
||||
frankenphp {
|
||||
num_threads <num_threads> # Sets the number of PHP threads to start. Default: 2x the number of available CPUs.
|
||||
worker {
|
||||
file <path> # Sets the path to the worker script.
|
||||
num <num> # Sets the number of PHP threads to start, defaults to 2x the number of available CPUs.
|
||||
env <key> <value> # Sets an extra environment variable to the given value. Can be specified more than once for multiple environment variables.
|
||||
}
|
||||
}
|
||||
frankenphp {
|
||||
num_threads <num_threads> # Sets the number of PHP threads to start. Default: 2x the number of available CPUs.
|
||||
max_threads <num_threads> # Limits the number of additional PHP threads that can be started at runtime. Default: num_threads. Can be set to 'auto'.
|
||||
max_wait_time <duration> # Sets the maximum time a request may wait for a free PHP thread before timing out. Default: disabled.
|
||||
php_ini <key> <value> # Set a php.ini directive. Can be used several times to set multiple directives.
|
||||
worker {
|
||||
file <path> # Sets the path to the worker script.
|
||||
num <num> # Sets the number of PHP threads to start, defaults to 2x the number of available CPUs.
|
||||
env <key> <value> # Sets an extra environment variable to the given value. Can be specified more than once for multiple environment variables.
|
||||
watch <path> # Sets the path to watch for file changes. Can be specified more than once for multiple paths.
|
||||
name <name> # Sets the name of the worker, used in logs and metrics. Default: absolute path of worker file
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ...
|
||||
```
|
||||
|
||||
Alternatively, the short form of the `worker` directive can also be used:
|
||||
Alternatively, you may use the one-line short form of the `worker` option:
|
||||
|
||||
```caddyfile
|
||||
{
|
||||
frankenphp {
|
||||
worker <file> <num>
|
||||
}
|
||||
frankenphp {
|
||||
worker <file> <num>
|
||||
}
|
||||
}
|
||||
|
||||
# ...
|
||||
```
|
||||
|
||||
You can also define multiple workers if you serve multiple apps on the same server:
|
||||
|
||||
```caddyfile
|
||||
app.example.com {
|
||||
php_server {
|
||||
root /path/to/app/public
|
||||
worker index.php <num>
|
||||
}
|
||||
}
|
||||
|
||||
other.example.com {
|
||||
php_server {
|
||||
root /path/to/other/public
|
||||
worker index.php <num>
|
||||
}
|
||||
}
|
||||
|
||||
# ...
|
||||
```
|
||||
|
||||
Using the `php_server` directive is generally what you need,
|
||||
but if you need full control, you can use the lower-level `php` directive.
|
||||
The `php` directive passes all input to PHP, instead of first checking whether
|
||||
it's a PHP file or not. Read more about it in the [performance page](performance.md).
|
||||
|
||||
Using the `php_server` directive is equivalent to this configuration:
|
||||
|
||||
```caddyfile
|
||||
route {
|
||||
# Add trailing slash for directory requests
|
||||
@canonicalPath {
|
||||
file {path}/index.php
|
||||
not path */
|
||||
}
|
||||
redir @canonicalPath {path}/ 308
|
||||
# If the requested file does not exist, try index files
|
||||
@indexFiles file {
|
||||
try_files {path} {path}/index.php index.php
|
||||
split_path .php
|
||||
}
|
||||
rewrite @indexFiles {http.matchers.file.relative}
|
||||
# FrankenPHP!
|
||||
@phpFiles path *.php
|
||||
php @phpFiles
|
||||
file_server
|
||||
}
|
||||
```
|
||||
|
||||
The `php_server` and the `php` directives have the following options:
|
||||
|
||||
```caddyfile
|
||||
php_server [<matcher>] {
|
||||
root <directory> # Sets the root folder to the site. Default: `root` directive.
|
||||
split_path <delim...> # 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 script to use. Default: `.php`
|
||||
resolve_root_symlink false # Disables resolving the `root` directory to its actual value by evaluating a symbolic link, if one exists (enabled by default).
|
||||
env <key> <value> # Sets an extra environment variable to the given value. Can be specified more than once for multiple environment variables.
|
||||
file_server off # Disables the built-in file_server directive.
|
||||
worker { # Creates a worker specific to this server. Can be specified more than once for multiple workers.
|
||||
file <path> # Sets the path to the worker script, can be relative to the php_server root
|
||||
num <num> # Sets the number of PHP threads to start, defaults to 2x the number of available
|
||||
name <name> # Sets the name for the worker, used in logs and metrics. Default: absolute path of worker file. Always starts with m# when defined in a php_server block.
|
||||
watch <path> # Sets the path to watch for file changes. Can be specified more than once for multiple paths.
|
||||
env <key> <value> # Sets an extra environment variable to the given value. Can be specified more than once for multiple environment variables. Environment variables for this worker are also inherited from the php_server parent, but can be overwritten here.
|
||||
}
|
||||
worker <other_file> <num> # Can also use the short form like in the global frankenphp block.
|
||||
}
|
||||
```
|
||||
|
||||
### Watching for File Changes
|
||||
|
||||
Since workers only boot your application once and keep it in memory, any changes
|
||||
to your PHP files will not be reflected immediately.
|
||||
|
||||
Workers can instead be restarted on file changes via the `watch` directive.
|
||||
This is useful for development environments.
|
||||
|
||||
```caddyfile
|
||||
{
|
||||
frankenphp {
|
||||
worker {
|
||||
file /path/to/app/public/worker.php
|
||||
watch
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
If the `watch` directory is not specified, it will fall back to `./**/*.{php,yaml,yml,twig,env}`,
|
||||
which watches all `.php`, `.yaml`, `.yml`, `.twig` and `.env` files in the directory and subdirectories
|
||||
where the FrankenPHP process was started. You can instead also specify one or more directories via a
|
||||
[shell filename pattern](https://pkg.go.dev/path/filepath#Match):
|
||||
|
||||
```caddyfile
|
||||
{
|
||||
frankenphp {
|
||||
worker {
|
||||
file /path/to/app/public/worker.php
|
||||
watch /path/to/app # watches all files in all subdirectories of /path/to/app
|
||||
watch /path/to/app/*.php # watches files ending in .php in /path/to/app
|
||||
watch /path/to/app/**/*.php # watches PHP files in /path/to/app and subdirectories
|
||||
watch /path/to/app/**/*.{php,twig} # watches PHP and Twig files in /path/to/app and subdirectories
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- The `**` pattern signifies recursive watching
|
||||
- Directories can also be relative (to where the FrankenPHP process is started from)
|
||||
- If you have multiple workers defined, all of them will be restarted when a file changes
|
||||
- Be wary about watching files that are created at runtime (like logs) since they might cause unwanted worker restarts.
|
||||
|
||||
The file watcher is based on [e-dant/watcher](https://github.com/e-dant/watcher).
|
||||
|
||||
### Full Duplex (HTTP/1)
|
||||
|
||||
When using HTTP/1.x, it may be desirable to enable full-duplex mode to allow writing a response before the entire body
|
||||
has been read. (for example: WebSocket, Server-Sent Events, etc.)
|
||||
|
||||
This is an opt-in configuration that needs to be added to the global options in the `Caddyfile`:
|
||||
|
||||
```caddyfile
|
||||
{
|
||||
servers {
|
||||
enable_full_duplex
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> [!CAUTION]
|
||||
>
|
||||
> Enabling this option may cause old HTTP/1.x clients that don't support full-duplex to deadlock.
|
||||
> This can also be configured using the `CADDY_GLOBAL_OPTIONS` environment config:
|
||||
|
||||
```sh
|
||||
CADDY_GLOBAL_OPTIONS="servers {
|
||||
enable_full_duplex
|
||||
}"
|
||||
```
|
||||
|
||||
You can find more information about this setting in the [Caddy documentation](https://caddyserver.com/docs/caddyfile/options#enable-full-duplex).
|
||||
|
||||
## Environment Variables
|
||||
|
||||
The following environment variables can be used to inject Caddy directives in the `Caddyfile` without modifying it:
|
||||
|
||||
* `SERVER_NAME` change the server name
|
||||
* `CADDY_GLOBAL_OPTIONS`: inject [global options](https://caddyserver.com/docs/caddyfile/options)
|
||||
* `FRANKENPHP_CONFIG`: inject config under the `frankenphp` directive
|
||||
- `SERVER_NAME`: change [the addresses on which to listen](https://caddyserver.com/docs/caddyfile/concepts#addresses), the provided hostnames will also be used for the generated TLS certificate
|
||||
- `CADDY_GLOBAL_OPTIONS`: inject [global options](https://caddyserver.com/docs/caddyfile/options)
|
||||
- `FRANKENPHP_CONFIG`: inject config under the `frankenphp` directive
|
||||
|
||||
Unlike with FPM and CLI SAPIs, environment variables are **not** exposed by default in superglobals `$_SERVER` and `$_ENV`.
|
||||
As for FPM and CLI SAPIs, environment variables are exposed by default in the `$_SERVER` superglobal.
|
||||
|
||||
To propagate environment variables to `$_SERVER` and `$_ENV`, set the `php.ini` `variables_order` directive to `EGPS`.
|
||||
The `S` value of [the `variables_order` PHP directive](https://www.php.net/manual/en/ini.core.php#ini.variables-order) is always equivalent to `ES` regardless of the placement of `E` elsewhere in this directive.
|
||||
|
||||
## PHP config
|
||||
|
||||
To load [additional PHP configuration files](https://www.php.net/manual/en/configuration.file.php#configuration.file.scan),
|
||||
the `PHP_INI_SCAN_DIR` environment variable can be used.
|
||||
When set, PHP will load all the file with the `.ini` extension present in the given directories.
|
||||
|
||||
You can also change the PHP configuration using the `php_ini` directive in the `Caddyfile`:
|
||||
|
||||
```caddyfile
|
||||
{
|
||||
frankenphp {
|
||||
php_ini memory_limit 256M
|
||||
|
||||
# or
|
||||
|
||||
php_ini {
|
||||
memory_limit 256M
|
||||
max_execution_time 15
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Enable the Debug Mode
|
||||
|
||||
When using the Docker image, set the `CADDY_DEBUG` environment variable to `debug` to enable the debug mode:
|
||||
When using the Docker image, set the `CADDY_GLOBAL_OPTIONS` environment variable to `debug` to enable the debug mode:
|
||||
|
||||
```console
|
||||
docker run -v $PWD:/app/public \
|
||||
-e CADDY_GLOBAL_OPTIONS=debug \
|
||||
-p 80:80 -p 443:443 \
|
||||
-p 80:80 -p 443:443 -p 443:443/udp \
|
||||
dunglas/frankenphp
|
||||
```
|
||||
|
||||
BIN
docs/digitalocean-dns.png
Normal file
BIN
docs/digitalocean-dns.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 73 KiB |
BIN
docs/digitalocean-droplet.png
Normal file
BIN
docs/digitalocean-droplet.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 87 KiB |
165
docs/docker.md
165
docs/docker.md
@@ -1,6 +1,15 @@
|
||||
# Building Custom Docker Image
|
||||
|
||||
[FrankenPHP Docker images](https://hub.docker.com/r/dunglas/frankenphp) are based on [official PHP images](https://hub.docker.com/_/php/). Alpine Linux and Debian variants are provided for popular architectures. Variants for PHP 8.2 and PHP 8.3 are provided. [Browse tags](https://hub.docker.com/repository/docker/dunglas/frankenphp).
|
||||
[FrankenPHP Docker images](https://hub.docker.com/r/dunglas/frankenphp) are based on [official PHP images](https://hub.docker.com/_/php/). Debian and Alpine Linux variants are provided for popular architectures. Debian variants are recommended.
|
||||
|
||||
Variants for PHP 8.2, 8.3 and 8.4 are provided.
|
||||
|
||||
The tags follow this pattern: `dunglas/frankenphp:<frankenphp-version>-php<php-version>-<os>`
|
||||
|
||||
- `<frankenphp-version>` and `<php-version>` are version numbers of FrankenPHP and PHP respectively, ranging from major (e.g. `1`), minor (e.g. `1.2`) to patch versions (e.g. `1.2.3`).
|
||||
- `<os>` is either `bookworm` (for Debian Bookworm) or `alpine` (for the latest stable version of Alpine).
|
||||
|
||||
[Browse tags](https://hub.docker.com/r/dunglas/frankenphp/tags).
|
||||
|
||||
## How to Use The Images
|
||||
|
||||
@@ -12,33 +21,73 @@ FROM dunglas/frankenphp
|
||||
COPY . /app/public
|
||||
```
|
||||
|
||||
Then, run the commands to build and run the Docker image:
|
||||
Then, run these commands to build and run the Docker image:
|
||||
|
||||
```console
|
||||
$ docker build -t my-php-app .
|
||||
$ docker run -it --rm --name my-running-app my-php-app
|
||||
docker build -t my-php-app .
|
||||
docker run -it --rm --name my-running-app my-php-app
|
||||
```
|
||||
|
||||
## How to Install More PHP Extensions
|
||||
|
||||
The [`docker-php-extension-installer`](https://github.com/mlocati/docker-php-extension-installer) script is provided in the base image.
|
||||
Adding additional PHP extensions is straightforwardd:
|
||||
Adding additional PHP extensions is straightforward:
|
||||
|
||||
```dockerfile
|
||||
FROM dunglas/frankenphp
|
||||
|
||||
# add additional extensions here:
|
||||
RUN install-php-extensions \
|
||||
pdo_mysql \
|
||||
gd \
|
||||
intl \
|
||||
zip \
|
||||
opcache
|
||||
|
||||
# ...
|
||||
pdo_mysql \
|
||||
gd \
|
||||
intl \
|
||||
zip \
|
||||
opcache
|
||||
```
|
||||
|
||||
# Enabling the Worker Mode by Default
|
||||
## How to Install More Caddy Modules
|
||||
|
||||
FrankenPHP is built on top of Caddy, and all [Caddy modules](https://caddyserver.com/docs/modules/) can be used with FrankenPHP.
|
||||
|
||||
The easiest way to install custom Caddy modules is to use [xcaddy](https://github.com/caddyserver/xcaddy):
|
||||
|
||||
```dockerfile
|
||||
FROM dunglas/frankenphp:builder AS builder
|
||||
|
||||
# Copy xcaddy in the builder image
|
||||
COPY --from=caddy:builder /usr/bin/xcaddy /usr/bin/xcaddy
|
||||
|
||||
# CGO must be enabled to build FrankenPHP
|
||||
RUN CGO_ENABLED=1 \
|
||||
XCADDY_SETCAP=1 \
|
||||
XCADDY_GO_BUILD_FLAGS="-ldflags='-w -s' -tags=nobadger,nomysql,nopgx" \
|
||||
CGO_CFLAGS=$(php-config --includes) \
|
||||
CGO_LDFLAGS="$(php-config --ldflags) $(php-config --libs)" \
|
||||
xcaddy build \
|
||||
--output /usr/local/bin/frankenphp \
|
||||
--with frankenphp.dev=./ \
|
||||
--with frankenphp.dev/caddy=./caddy/ \
|
||||
--with github.com/dunglas/caddy-cbrotli \
|
||||
# Mercure and Vulcain are included in the official build, but feel free to remove them
|
||||
--with github.com/dunglas/mercure/caddy \
|
||||
--with github.com/dunglas/vulcain/caddy
|
||||
# Add extra Caddy modules here
|
||||
|
||||
FROM dunglas/frankenphp AS runner
|
||||
|
||||
# Replace the official binary by the one contained your custom modules
|
||||
COPY --from=builder /usr/local/bin/frankenphp /usr/local/bin/frankenphp
|
||||
```
|
||||
|
||||
The `builder` image provided by FrankenPHP contains a compiled version of `libphp`.
|
||||
[Builders images](https://hub.docker.com/r/dunglas/frankenphp/tags?name=builder) are provided for all versions of FrankenPHP and PHP, both for Debian and Alpine.
|
||||
|
||||
> [!TIP]
|
||||
>
|
||||
> If you're using Alpine Linux and Symfony,
|
||||
> you may need to [increase the default stack size](compile.md#using-xcaddy).
|
||||
|
||||
## Enabling the Worker Mode by Default
|
||||
|
||||
Set the `FRANKENPHP_CONFIG` environment variable to start FrankenPHP with a worker script:
|
||||
|
||||
@@ -50,20 +99,22 @@ FROM dunglas/frankenphp
|
||||
ENV FRANKENPHP_CONFIG="worker ./public/index.php"
|
||||
```
|
||||
|
||||
# Using a Volume in Development
|
||||
## Using a Volume in Development
|
||||
|
||||
To develop easily with FrankenPHP, mount the directory from your host containing the source code of the app as a volume in the Docker container:
|
||||
|
||||
```console
|
||||
docker run -v $PWD:/app/public -p 80:80 -p 443:443 my-php-app
|
||||
docker run -v $PWD:/app/public -p 80:80 -p 443:443 -p 443:443/udp --tty my-php-app
|
||||
```
|
||||
|
||||
> [!TIP]
|
||||
>
|
||||
> The `--tty` option allows to have nice human-readable logs instead of JSON logs.
|
||||
|
||||
With Docker Compose:
|
||||
|
||||
```yaml
|
||||
# compose.yml
|
||||
|
||||
version: '3.1'
|
||||
# compose.yaml
|
||||
|
||||
services:
|
||||
php:
|
||||
@@ -73,8 +124,82 @@ services:
|
||||
# uncomment the following line if you want to run this in a production environment
|
||||
# restart: always
|
||||
ports:
|
||||
- 80:80
|
||||
- 443:443
|
||||
- "80:80" # HTTP
|
||||
- "443:443" # HTTPS
|
||||
- "443:443/udp" # HTTP/3
|
||||
volumes:
|
||||
- ./:/app/public
|
||||
- caddy_data:/data
|
||||
- caddy_config:/config
|
||||
# comment the following line in production, it allows to have nice human-readable logs in dev
|
||||
tty: true
|
||||
|
||||
# Volumes needed for Caddy certificates and configuration
|
||||
volumes:
|
||||
caddy_data:
|
||||
caddy_config:
|
||||
```
|
||||
|
||||
## Running as a Non-Root User
|
||||
|
||||
FrankenPHP can run as non-root user in Docker.
|
||||
|
||||
Here is a sample `Dockerfile` doing this:
|
||||
|
||||
```dockerfile
|
||||
FROM dunglas/frankenphp
|
||||
|
||||
ARG USER=appuser
|
||||
|
||||
RUN \
|
||||
# Use "adduser -D ${USER}" for alpine based distros
|
||||
useradd ${USER}; \
|
||||
# Add additional capability to bind to port 80 and 443
|
||||
setcap CAP_NET_BIND_SERVICE=+eip /usr/local/bin/frankenphp; \
|
||||
# Give write access to /data/caddy and /config/caddy
|
||||
chown -R ${USER}:${USER} /data/caddy && chown -R ${USER}:${USER} /config/caddy
|
||||
|
||||
USER ${USER}
|
||||
```
|
||||
|
||||
### Running With No Capabilities
|
||||
|
||||
Even when running rootless, FrankenPHP needs the `CAP_NET_BIND_SERVICE` capability to bind the
|
||||
web server on privileged ports (80 and 443).
|
||||
|
||||
If you expose FrankenPHP on a non-privileged port (1024 and above), it's possible to run
|
||||
the webserver as a non-root user, and without the need for any capability:
|
||||
|
||||
```dockerfile
|
||||
FROM dunglas/frankenphp
|
||||
|
||||
ARG USER=appuser
|
||||
|
||||
RUN \
|
||||
# Use "adduser -D ${USER}" for alpine based distros
|
||||
useradd ${USER}; \
|
||||
# Remove default capability
|
||||
setcap -r /usr/local/bin/frankenphp; \
|
||||
# Give write access to /data/caddy and /config/caddy
|
||||
chown -R ${USER}:${USER} /data/caddy && chown -R ${USER}:${USER} /config/caddy
|
||||
|
||||
USER ${USER}
|
||||
```
|
||||
|
||||
Next, set the `SERVER_NAME` environment variable to use an unprivileged port.
|
||||
Example: `:8000`
|
||||
|
||||
## Updates
|
||||
|
||||
The Docker images are built:
|
||||
|
||||
- when a new release is tagged
|
||||
- daily at 4 am UTC, if new versions of the official PHP images are available
|
||||
|
||||
## Development Versions
|
||||
|
||||
Development versions are available in the [`dunglas/frankenphp-dev`](https://hub.docker.com/repository/docker/dunglas/frankenphp-dev) Docker repository.
|
||||
A new build is triggered every time a commit is pushed to the main branch of the GitHub repository.
|
||||
|
||||
The `latest*` tags point to the head of the `main` branch.
|
||||
Tags of the form `sha-<git-commit-hash>` are also available.
|
||||
|
||||
143
docs/embed.md
Normal file
143
docs/embed.md
Normal file
@@ -0,0 +1,143 @@
|
||||
# PHP Apps As Standalone Binaries
|
||||
|
||||
FrankenPHP has the ability to embed the source code and assets of PHP applications in a static, self-contained binary.
|
||||
|
||||
Thanks to this feature, PHP applications can be distributed as standalone binaries that include the application itself, the PHP interpreter, and Caddy, a production-level web server.
|
||||
|
||||
Learn more about this feature [in the presentation made by Kévin at SymfonyCon 2023](https://dunglas.dev/2023/12/php-and-symfony-apps-as-standalone-binaries/).
|
||||
|
||||
For embedding Laravel applications, [read this specific documentation entry](laravel.md#laravel-apps-as-standalone-binaries).
|
||||
|
||||
## Preparing Your App
|
||||
|
||||
Before creating the self-contained binary be sure that your app is ready for embedding.
|
||||
|
||||
For instance, you likely want to:
|
||||
|
||||
- Install the production dependencies of the app
|
||||
- Dump the autoloader
|
||||
- Enable the production mode of your application (if any)
|
||||
- Strip unneeded files such as `.git` or tests to reduce the size of your final binary
|
||||
|
||||
For instance, for a Symfony app, you can use the following commands:
|
||||
|
||||
```console
|
||||
# Export the project to get rid of .git/, etc
|
||||
mkdir $TMPDIR/my-prepared-app
|
||||
git archive HEAD | tar -x -C $TMPDIR/my-prepared-app
|
||||
cd $TMPDIR/my-prepared-app
|
||||
|
||||
# Set proper environment variables
|
||||
echo APP_ENV=prod > .env.local
|
||||
echo APP_DEBUG=0 >> .env.local
|
||||
|
||||
# Remove the tests and other unneeded files to save space
|
||||
# Alternatively, add these files with the export-ignore attribute in your .gitattributes file
|
||||
rm -Rf tests/
|
||||
|
||||
# Install the dependencies
|
||||
composer install --ignore-platform-reqs --no-dev -a
|
||||
|
||||
# Optimize .env
|
||||
composer dump-env prod
|
||||
```
|
||||
|
||||
### Customizing the Configuration
|
||||
|
||||
To customize [the configuration](config.md), you can put a `Caddyfile` as well as a `php.ini` file
|
||||
in the main directory of the app to be embedded (`$TMPDIR/my-prepared-app` in the previous example).
|
||||
|
||||
## Creating a Linux Binary
|
||||
|
||||
The easiest way to create a Linux binary is to use the Docker-based builder we provide.
|
||||
|
||||
1. Create a file named `static-build.Dockerfile` in the repository of your app:
|
||||
|
||||
```dockerfile
|
||||
FROM --platform=linux/amd64 dunglas/frankenphp:static-builder
|
||||
|
||||
# Copy your app
|
||||
WORKDIR /go/src/app/dist/app
|
||||
COPY . .
|
||||
|
||||
# Build the static binary
|
||||
WORKDIR /go/src/app/
|
||||
RUN EMBED=dist/app/ ./build-static.sh
|
||||
```
|
||||
|
||||
> [!CAUTION]
|
||||
>
|
||||
> Some `.dockerignore` files (e.g. default [Symfony Docker `.dockerignore`](https://github.com/dunglas/symfony-docker/blob/main/.dockerignore))
|
||||
> will ignore the `vendor/` directory and `.env` files. Be sure to adjust or remove the `.dockerignore` file before the build.
|
||||
|
||||
2. Build:
|
||||
|
||||
```console
|
||||
docker build -t static-app -f static-build.Dockerfile .
|
||||
```
|
||||
|
||||
3. Extract the binary:
|
||||
|
||||
```console
|
||||
docker cp $(docker create --name static-app-tmp static-app):/go/src/app/dist/frankenphp-linux-x86_64 my-app ; docker rm static-app-tmp
|
||||
```
|
||||
|
||||
The resulting binary is the file named `my-app` in the current directory.
|
||||
|
||||
## Creating a Binary for Other OSes
|
||||
|
||||
If you don't want to use Docker, or want to build a macOS binary, use the shell script we provide:
|
||||
|
||||
```console
|
||||
git clone https://github.com/dunglas/frankenphp
|
||||
cd frankenphp
|
||||
EMBED=/path/to/your/app ./build-static.sh
|
||||
```
|
||||
|
||||
The resulting binary is the file named `frankenphp-<os>-<arch>` in the `dist/` directory.
|
||||
|
||||
## Using The Binary
|
||||
|
||||
This is it! The `my-app` file (or `dist/frankenphp-<os>-<arch>` on other OSes) contains your self-contained app!
|
||||
|
||||
To start the web app run:
|
||||
|
||||
```console
|
||||
./my-app php-server
|
||||
```
|
||||
|
||||
If your app contains a [worker script](worker.md), start the worker with something like:
|
||||
|
||||
```console
|
||||
./my-app php-server --worker public/index.php
|
||||
```
|
||||
|
||||
To enable HTTPS (a Let's Encrypt certificate is automatically created), HTTP/2, and HTTP/3, specify the domain name to use:
|
||||
|
||||
```console
|
||||
./my-app php-server --domain localhost
|
||||
```
|
||||
|
||||
You can also run the PHP CLI scripts embedded in your binary:
|
||||
|
||||
```console
|
||||
./my-app php-cli bin/console
|
||||
```
|
||||
|
||||
## PHP Extensions
|
||||
|
||||
By default, the script will build extensions required by the `composer.json` file of your project, if any.
|
||||
If the `composer.json` file doesn't exist, the default extensions are built, as documented in [the static builds entry](static.md).
|
||||
|
||||
To customize the extensions, use the `PHP_EXTENSIONS` environment variable.
|
||||
|
||||
## Customizing The Build
|
||||
|
||||
[Read the static build documentation](static.md) to see how to customize the binary (extensions, PHP version...).
|
||||
|
||||
## Distributing The Binary
|
||||
|
||||
On Linux, the created binary is compressed using [UPX](https://upx.github.io).
|
||||
|
||||
On Mac, to reduce the size of the file before sending it, you can compress it.
|
||||
We recommend `xz`.
|
||||
220
docs/fr/CONTRIBUTING.md
Normal file
220
docs/fr/CONTRIBUTING.md
Normal file
@@ -0,0 +1,220 @@
|
||||
# Contribuer
|
||||
|
||||
## Compiler PHP
|
||||
|
||||
### Avec Docker (Linux)
|
||||
|
||||
Construisez l'image Docker de développement :
|
||||
|
||||
```console
|
||||
docker build -t frankenphp-dev -f dev.Dockerfile .
|
||||
docker run --cap-add=SYS_PTRACE --security-opt seccomp=unconfined -p 8080:8080 -p 443:443 -p 443:443/udp -v $PWD:/go/src/app -it frankenphp-dev
|
||||
```
|
||||
|
||||
L'image contient les outils de développement habituels (Go, GDB, Valgrind, Neovim...) et utilise les emplacements de configuration PHP suivants
|
||||
|
||||
- php.ini: `/etc/frankenphp/php.ini` Un fichier php.ini avec des préréglages de développement est fourni par défaut.
|
||||
- fichiers de configuration supplémentaires: `/etc/frankenphp/php.d/*.ini`
|
||||
- extensions php: `/usr/lib/frankenphp/modules/`
|
||||
|
||||
Si votre version de Docker est inférieure à 23.0, la construction échouera à cause d'un [problème de pattern](https://github.com/moby/moby/pull/42676) dans `.dockerignore`. Ajoutez les répertoires à `.dockerignore`.
|
||||
|
||||
```patch
|
||||
!testdata/*.php
|
||||
!testdata/*.txt
|
||||
+!caddy
|
||||
+!internal
|
||||
```
|
||||
|
||||
### Sans Docker (Linux et macOS)
|
||||
|
||||
[Suivez les instructions pour compiler à partir des sources](compile.md) et passez l'indicateur de configuration `--debug`.
|
||||
|
||||
## Exécution de la suite de tests
|
||||
|
||||
```console
|
||||
go test -tags watcher -race -v ./...
|
||||
```
|
||||
|
||||
## Module Caddy
|
||||
|
||||
Construire Caddy avec le module FrankenPHP :
|
||||
|
||||
```console
|
||||
cd caddy/frankenphp/
|
||||
go build -tags watcher,brotli,nobadger,nomysql,nopgx
|
||||
cd ../../
|
||||
```
|
||||
|
||||
Exécuter Caddy avec le module FrankenPHP :
|
||||
|
||||
```console
|
||||
cd testdata/
|
||||
../caddy/frankenphp/frankenphp run
|
||||
```
|
||||
|
||||
Le serveur est configuré pour écouter à l'adresse `127.0.0.1:80`:
|
||||
|
||||
> [!NOTE]
|
||||
>
|
||||
> Si vous utilisez Docker, vous devrez soit lier le port 80 du conteneur, soit exécuter depuis l'intérieur du conteneur.
|
||||
|
||||
```console
|
||||
curl -vk http://127.0.0.1/phpinfo.php
|
||||
```
|
||||
|
||||
## Serveur de test minimal
|
||||
|
||||
Construire le serveur de test minimal :
|
||||
|
||||
```console
|
||||
cd internal/testserver/
|
||||
go build
|
||||
cd ../../
|
||||
```
|
||||
|
||||
Lancer le test serveur :
|
||||
|
||||
```console
|
||||
cd testdata/
|
||||
../internal/testserver/testserver
|
||||
```
|
||||
|
||||
Le serveur est configuré pour écouter à l'adresse `127.0.0.1:8080`:
|
||||
|
||||
```console
|
||||
curl -v http://127.0.0.1:8080/phpinfo.php
|
||||
```
|
||||
|
||||
## Construire localement les images Docker
|
||||
|
||||
Afficher le plan de compilation :
|
||||
|
||||
```console
|
||||
docker buildx bake -f docker-bake.hcl --print
|
||||
```
|
||||
|
||||
Construire localement les images FrankenPHP pour amd64 :
|
||||
|
||||
```console
|
||||
docker buildx bake -f docker-bake.hcl --pull --load --set "*.platform=linux/amd64"
|
||||
```
|
||||
|
||||
Construire localement les images FrankenPHP pour arm64 :
|
||||
|
||||
```console
|
||||
docker buildx bake -f docker-bake.hcl --pull --load --set "*.platform=linux/arm64"
|
||||
```
|
||||
|
||||
Construire à partir de zéro les images FrankenPHP pour arm64 & amd64 et les pousser sur Docker Hub :
|
||||
|
||||
```console
|
||||
docker buildx bake -f docker-bake.hcl --pull --no-cache --push
|
||||
```
|
||||
|
||||
## Déboguer les erreurs de segmentation avec les builds statiques
|
||||
|
||||
1. Téléchargez la version de débogage du binaire FrankenPHP depuis GitHub ou créez votre propre build statique incluant des symboles de débogage :
|
||||
|
||||
```console
|
||||
docker buildx bake \
|
||||
--load \
|
||||
--set static-builder.args.DEBUG_SYMBOLS=1 \
|
||||
--set "static-builder.platform=linux/amd64" \
|
||||
static-builder
|
||||
docker cp $(docker create --name static-builder-musl dunglas/frankenphp:static-builder-musl):/go/src/app/dist/frankenphp-linux-$(uname -m) frankenphp
|
||||
```
|
||||
|
||||
2. Remplacez votre version actuelle de `frankenphp` par l'exécutable de débogage de FrankenPHP.
|
||||
3. Démarrez FrankenPHP comme d'habitude (alternativement, vous pouvez directement démarrer FrankenPHP avec GDB : `gdb --args frankenphp run`).
|
||||
4. Attachez-vous au processus avec GDB :
|
||||
|
||||
```console
|
||||
gdb -p `pidof frankenphp`
|
||||
```
|
||||
|
||||
5. Si nécessaire, tapez `continue` dans le shell GDB
|
||||
6. Faites planter FrankenPHP.
|
||||
7. Tapez `bt` dans le shell GDB
|
||||
8. Copiez la sortie
|
||||
|
||||
## Déboguer les erreurs de segmentation dans GitHub Actions
|
||||
|
||||
1. Ouvrir `.github/workflows/tests.yml`
|
||||
2. Activer les symboles de débogage de la bibliothèque PHP
|
||||
|
||||
```patch
|
||||
- uses: shivammathur/setup-php@v2
|
||||
# ...
|
||||
env:
|
||||
phpts: ts
|
||||
+ debug: true
|
||||
```
|
||||
|
||||
3. Activer `tmate` pour se connecter au conteneur
|
||||
|
||||
```patch
|
||||
- name: Set CGO flags
|
||||
run: echo "CGO_CFLAGS=$(php-config --includes)" >> "$GITHUB_ENV"
|
||||
+ - run: |
|
||||
+ sudo apt install gdb
|
||||
+ mkdir -p /home/runner/.config/gdb/
|
||||
+ printf "set auto-load safe-path /\nhandle SIG34 nostop noprint pass" > /home/runner/.config/gdb/gdbinit
|
||||
+ - uses: mxschmitt/action-tmate@v3
|
||||
```
|
||||
|
||||
4. Se connecter au conteneur
|
||||
5. Ouvrir `frankenphp.go`
|
||||
6. Activer `cgosymbolizer`
|
||||
|
||||
```patch
|
||||
- //_ "github.com/ianlancetaylor/cgosymbolizer"
|
||||
+ _ "github.com/ianlancetaylor/cgosymbolizer"
|
||||
```
|
||||
|
||||
7. Télécharger le module : `go get`
|
||||
8. Dans le conteneur, vous pouvez utiliser GDB et similaires :
|
||||
|
||||
```console
|
||||
go test -tags watcher -c -ldflags=-w
|
||||
gdb --args frankenphp.dev.test -test.run ^MyTest$
|
||||
```
|
||||
|
||||
9. Quand le bug est corrigé, annulez tous les changements.
|
||||
|
||||
## Ressources Diverses pour le Développement
|
||||
|
||||
- [Intégration de PHP dans uWSGI](https://github.com/unbit/uwsgi/blob/master/plugins/php/php_plugin.c)
|
||||
- [Intégration de PHP dans NGINX Unit](https://github.com/nginx/unit/blob/master/src/nxt_php_sapi.c)
|
||||
- [Intégration de PHP dans Go (go-php)](https://github.com/deuill/go-php)
|
||||
- [Intégration de PHP dans Go (GoEmPHP)](https://github.com/mikespook/goemphp)
|
||||
- [Intégration de PHP dans C++](https://gist.github.com/paresy/3cbd4c6a469511ac7479aa0e7c42fea7)
|
||||
- [Extending and Embedding PHP par Sara Golemon](https://books.google.fr/books?id=zMbGvK17_tYC&pg=PA254&lpg=PA254#v=onepage&q&f=false)
|
||||
- [Qu'est-ce que TSRMLS_CC, au juste ?](http://blog.golemon.com/2006/06/what-heck-is-tsrmlscc-anyway.html)
|
||||
- [Intégration de PHP sur Mac](https://gist.github.com/jonnywang/61427ffc0e8dde74fff40f479d147db4)
|
||||
- [Bindings SDL](https://pkg.go.dev/github.com/veandco/go-sdl2@v0.4.21/sdl#Main)
|
||||
|
||||
## Ressources Liées à Docker
|
||||
|
||||
- [Définition du fichier Bake](https://docs.docker.com/build/customize/bake/file-definition/)
|
||||
- [docker buildx build](https://docs.docker.com/engine/reference/commandline/buildx_build/)
|
||||
|
||||
## Commande utile
|
||||
|
||||
```console
|
||||
apk add strace util-linux gdb
|
||||
strace -e 'trace=!futex,epoll_ctl,epoll_pwait,tgkill,rt_sigreturn' -p 1
|
||||
```
|
||||
|
||||
## Traduire la documentation
|
||||
|
||||
Pour traduire la documentation et le site dans une nouvelle langue, procédez comme suit :
|
||||
|
||||
1. Créez un nouveau répertoire nommé avec le code ISO à 2 caractères de la langue dans le répertoire `docs/` de ce dépôt
|
||||
2. Copiez tous les fichiers `.md` à la racine du répertoire `docs/` dans le nouveau répertoire (utilisez toujours la version anglaise comme source de traduction, car elle est toujours à jour).
|
||||
3. Copiez les fichiers `README.md` et `CONTRIBUTING.md` du répertoire racine vers le nouveau répertoire.
|
||||
4. Traduisez le contenu des fichiers, mais ne changez pas les noms de fichiers, ne traduisez pas non plus les chaînes commençant par `> [!` (c'est un balisage spécial pour GitHub).
|
||||
5. Créez une Pull Request avec les traductions
|
||||
6. Dans le [référentiel du site](https://github.com/dunglas/frankenphp-website/tree/main), copiez et traduisez les fichiers de traduction dans les répertoires `content/`, `data/` et `i18n/`.
|
||||
7. Traduire les valeurs dans le fichier YAML créé.
|
||||
8. Ouvrir une Pull Request sur le dépôt du site.
|
||||
109
docs/fr/README.md
Normal file
109
docs/fr/README.md
Normal file
@@ -0,0 +1,109 @@
|
||||
# FrankenPHP : le serveur d'applications PHP moderne, écrit en Go
|
||||
|
||||
<h1 align="center"><a href="https://frankenphp.dev"><img src="../../frankenphp.png" alt="FrankenPHP" width="600"></a></h1>
|
||||
|
||||
FrankenPHP est un serveur d'applications moderne pour PHP construit à partir du serveur web [Caddy](https://caddyserver.com/).
|
||||
|
||||
FrankenPHP donne des super-pouvoirs à vos applications PHP grâce à ses fonctionnalités à la pointe : [_Early Hints_](early-hints.md), [mode worker](worker.md), [fonctionnalités en temps réel](mercure.md), HTTPS automatique, prise en charge de HTTP/2 et HTTP/3...
|
||||
|
||||
FrankenPHP fonctionne avec n'importe quelle application PHP et rend vos projets Laravel et Symfony plus rapides que jamais grâce à leurs intégrations officielles avec le mode worker.
|
||||
|
||||
FrankenPHP peut également être utilisé comme une bibliothèque Go autonome qui permet d'intégrer PHP dans n'importe quelle application en utilisant `net/http`.
|
||||
|
||||
Découvrez plus de détails sur ce serveur d’application dans le replay de cette conférence donnée au Forum PHP 2022 :
|
||||
|
||||
<a href="https://dunglas.dev/2022/10/frankenphp-the-modern-php-app-server-written-in-go/"><img src="https://dunglas.dev/wp-content/uploads/2022/10/frankenphp.png" alt="Diapositives" width="600"></a>
|
||||
|
||||
## Pour Commencer
|
||||
|
||||
### Binaire autonome
|
||||
|
||||
Si vous préférez ne pas utiliser Docker, nous fournissons des binaires autonomes de FrankenPHP pour Linux et macOS
|
||||
contenant [PHP 8.4](https://www.php.net/releases/8.4/fr.php) et la plupart des extensions PHP populaires.
|
||||
|
||||
Sous Windows, utilisez [WSL](https://learn.microsoft.com/windows/wsl/) pour exécuter FrankenPHP.
|
||||
|
||||
[Téléchargez FrankenPHP](https://github.com/dunglas/frankenphp/releases) ou copiez cette ligne dans votre terminal pour installer automatiquement la version appropriée à votre plateforme :
|
||||
|
||||
```console
|
||||
curl https://frankenphp.dev/install.sh | sh
|
||||
mv frankenphp /usr/local/bin/
|
||||
```
|
||||
|
||||
Pour servir le contenu du répertoire courant, exécutez :
|
||||
|
||||
```console
|
||||
frankenphp php-server
|
||||
```
|
||||
|
||||
Vous pouvez également exécuter des scripts en ligne de commande avec :
|
||||
|
||||
```console
|
||||
frankenphp php-cli /path/to/your/script.php
|
||||
```
|
||||
|
||||
### Docker
|
||||
|
||||
Des [images Docker](https://frankenphp.dev/docs/fr/docker/) sont également disponibles :
|
||||
|
||||
```console
|
||||
docker run -v .:/app/public \
|
||||
-p 80:80 -p 443:443 -p 443:443/udp \
|
||||
dunglas/frankenphp
|
||||
```
|
||||
|
||||
Rendez-vous sur `https://localhost`, c'est parti !
|
||||
|
||||
> [!TIP]
|
||||
>
|
||||
> Ne tentez pas d'utiliser `https://127.0.0.1`. Utilisez `https://localhost` et acceptez le certificat auto-signé.
|
||||
> Utilisez [la variable d'environnement `SERVER_NAME`](config.md#variables-denvironnement) pour changer le domaine à utiliser.
|
||||
|
||||
### Homebrew
|
||||
|
||||
FrankenPHP est également disponible sous forme de paquet [Homebrew](https://brew.sh) pour macOS et Linux.
|
||||
|
||||
Pour l'installer :
|
||||
|
||||
```console
|
||||
brew install dunglas/frankenphp/frankenphp
|
||||
```
|
||||
|
||||
Pour servir le contenu du répertoire courant, exécutez :
|
||||
|
||||
```console
|
||||
frankenphp php-server
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
- [Le mode classique](classic.md)
|
||||
- [Le mode worker](worker.md)
|
||||
- [Le support des Early Hints (code de statut HTTP 103)](early-hints.md)
|
||||
- [Temps réel](mercure.md)
|
||||
- [Servir efficacement les fichiers statiques volumineux](x-sendfile.md)
|
||||
- [Configuration](config.md)
|
||||
- [Images Docker](docker.md)
|
||||
- [Déploiement en production](production.md)
|
||||
- [Optimisation des performances](performance.md)
|
||||
- [Créer des applications PHP **standalone**, auto-exécutables](embed.md)
|
||||
- [Créer un build statique](static.md)
|
||||
- [Compiler depuis les sources](compile.md)
|
||||
- [Surveillance de FrankenPHP](metrics.md)
|
||||
- [Intégration Laravel](laravel.md)
|
||||
- [Problèmes connus](known-issues.md)
|
||||
- [Application de démo (Symfony) et benchmarks](https://github.com/dunglas/frankenphp-demo)
|
||||
- [Documentation de la bibliothèque Go](https://pkg.go.dev/github.com/dunglas/frankenphp)
|
||||
- [Contribuer et débugger](CONTRIBUTING.md)
|
||||
|
||||
## Exemples et squelettes
|
||||
|
||||
- [Symfony](https://github.com/dunglas/symfony-docker)
|
||||
- [API Platform](https://api-platform.com/docs/distribution/)
|
||||
- [Laravel](laravel.md)
|
||||
- [Sulu](https://sulu.io/blog/running-sulu-with-frankenphp)
|
||||
- [WordPress](https://github.com/StephenMiracle/frankenwp)
|
||||
- [Drupal](https://github.com/dunglas/frankenphp-drupal)
|
||||
- [Joomla](https://github.com/alexandreelise/frankenphp-joomla)
|
||||
- [TYPO3](https://github.com/ochorocho/franken-typo3)
|
||||
- [Magento2](https://github.com/ekino/frankenphp-magento2)
|
||||
11
docs/fr/classic.md
Normal file
11
docs/fr/classic.md
Normal file
@@ -0,0 +1,11 @@
|
||||
# Utilisation du mode classique
|
||||
|
||||
Sans aucune configuration additionnelle, FrankenPHP fonctionne en mode classique. Dans ce mode, FrankenPHP fonctionne comme un serveur PHP traditionnel, en servant directement les fichiers PHP. Cela en fait un remplaçant parfait à PHP-FPM ou Apache avec mod_php.
|
||||
|
||||
Comme Caddy, FrankenPHP accepte un nombre illimité de connexions et utilise un [nombre fixe de threads](config.md#configuration-du-caddyfile) pour les servir. Le nombre de connexions acceptées et en attente n'est limité que par les ressources système disponibles.
|
||||
Le pool de threads PHP fonctionne avec un nombre fixe de threads initialisés au démarrage, comparable au mode statique de PHP-FPM. Il est également possible de laisser les threads [s'adapter automatiquement à l'exécution](performance.md#max_threads), comme dans le mode dynamique de PHP-FPM.
|
||||
|
||||
Les connexions en file d'attente attendront indéfiniment jusqu'à ce qu'un thread PHP soit disponible pour les servir. Pour éviter cela, vous pouvez utiliser la [configuration](config.md#configuration-du-caddyfile) `max_wait_time` pour limiter la durée pendant laquelle une requête peut attendre un thread PHP libre avant d'être rejetée.
|
||||
En outre, vous pouvez définir un [délai d'écriture dans Caddy](https://caddyserver.com/docs/caddyfile/options#timeouts) raisonnable.
|
||||
|
||||
Chaque instance de Caddy n'utilisera qu'un seul pool de threads FrankenPHP, qui sera partagé par tous les blocs `php_server`.
|
||||
128
docs/fr/compile.md
Normal file
128
docs/fr/compile.md
Normal file
@@ -0,0 +1,128 @@
|
||||
# Compiler depuis les sources
|
||||
|
||||
Ce document explique comment créer un build FrankenPHP qui chargera PHP en tant que bibliothèque dynamique.
|
||||
C'est la méthode recommandée.
|
||||
|
||||
Alternativement, il est aussi possible de [créer des builds statiques](static.md).
|
||||
|
||||
## Installer PHP
|
||||
|
||||
FrankenPHP est compatible avec PHP 8.2 et versions ultérieures.
|
||||
|
||||
### Avec Homebrew (Linux et Mac)
|
||||
|
||||
La manière la plus simple d'installer une version de libphp compatible avec FrankenPHP est d'utiliser les paquets ZTS fournis par [Homebrew PHP](https://github.com/shivammathur/homebrew-php).
|
||||
|
||||
Tout d'abord, si ce n'est déjà fait, installez [Homebrew](https://brew.sh).
|
||||
|
||||
Ensuite, installez la variante ZTS de PHP, Brotli (facultatif, pour la prise en charge de la compression) et watcher (facultatif, pour la détection des modifications de fichiers) :
|
||||
|
||||
```console
|
||||
brew install shivammathur/php/php-zts brotli watcher
|
||||
brew link --overwrite --force shivammathur/php/php-zts
|
||||
```
|
||||
|
||||
### En compilant PHP
|
||||
|
||||
Vous pouvez également compiler PHP à partir des sources avec les options requises par FrankenPHP en suivant ces étapes.
|
||||
|
||||
Tout d'abord, [téléchargez les sources de PHP](https://www.php.net/downloads.php) et extrayez-les :
|
||||
|
||||
```console
|
||||
tar xf php-*
|
||||
cd php-*/
|
||||
```
|
||||
|
||||
Ensuite, configurez PHP pour votre système d'exploitation.
|
||||
|
||||
Les options de configuration suivantes sont nécessaires pour la compilation, mais vous pouvez également inclure d'autres options selon vos besoins, par exemple pour ajouter des extensions et fonctionnalités supplémentaires.
|
||||
|
||||
### Linux
|
||||
|
||||
```console
|
||||
./configure \
|
||||
--enable-embed \
|
||||
--enable-zts \
|
||||
--disable-zend-signals \
|
||||
--enable-zend-max-execution-timers
|
||||
```
|
||||
|
||||
### Mac
|
||||
|
||||
Utilisez le gestionnaire de paquets [Homebrew](https://brew.sh/) pour installer les dépendances obligatoires et optionnelles :
|
||||
|
||||
```console
|
||||
brew install libiconv bison brotli re2c pkg-config watcher
|
||||
echo 'export PATH="/opt/homebrew/opt/bison/bin:$PATH"' >> ~/.zshrc
|
||||
```
|
||||
|
||||
Puis exécutez le script de configuration :
|
||||
|
||||
```console
|
||||
./configure \
|
||||
--enable-embed \
|
||||
--enable-zts \
|
||||
--disable-zend-signals \
|
||||
--disable-opcache-jit \
|
||||
--with-iconv=/opt/homebrew/opt/libiconv/
|
||||
```
|
||||
|
||||
### Compilez PHP
|
||||
|
||||
Finalement, compilez et installez PHP :
|
||||
|
||||
```console
|
||||
make -j"$(getconf _NPROCESSORS_ONLN)"
|
||||
sudo make install
|
||||
```
|
||||
|
||||
## Installez les dépendances optionnelles
|
||||
|
||||
Certaines fonctionnalités de FrankenPHP nécessitent des dépendances optionnelles qui doivent être installées.
|
||||
Ces fonctionnalités peuvent également être désactivées en passant des tags de compilation au compilateur Go.
|
||||
|
||||
| Fonctionnalité | Dépendance | Tag de compilation pour la désactiver |
|
||||
|---------------------------------------------------------|-----------------------------------------------------------------------|---------------------------------------|
|
||||
| Compression Brotli | [Brotli](https://github.com/google/brotli) | nobrotli |
|
||||
| Redémarrage des workers en cas de changement de fichier | [Watcher C](https://github.com/e-dant/watcher/tree/release/watcher-c) | nowatcher |
|
||||
|
||||
## Compiler l'application Go
|
||||
|
||||
### Utiliser xcaddy
|
||||
|
||||
La méthode recommandée consiste à utiliser [xcaddy](https://github.com/caddyserver/xcaddy) pour compiler FrankenPHP.
|
||||
`xcaddy` permet également d'ajouter facilement des [modules Caddy personnalisés](https://caddyserver.com/docs/modules/) et des extensions FrankenPHP :
|
||||
|
||||
```console
|
||||
CGO_ENABLED=1 \
|
||||
XCADDY_GO_BUILD_FLAGS="-ldflags='-w -s' -tags=nobadger,nomysql,nopgx" \
|
||||
CGO_CFLAGS=$(php-config --includes) \
|
||||
CGO_LDFLAGS="$(php-config --ldflags) $(php-config --libs)" \
|
||||
xcaddy build \
|
||||
--output frankenphp \
|
||||
--with frankenphp.dev/caddy \
|
||||
--with github.com/dunglas/caddy-cbrotli \
|
||||
--with github.com/dunglas/mercure/caddy \
|
||||
--with github.com/dunglas/vulcain/caddy
|
||||
# Ajoutez les modules Caddy supplémentaires et les extensions FrankenPHP ici
|
||||
```
|
||||
|
||||
> [!TIP]
|
||||
>
|
||||
> Si vous utilisez musl libc (la bibliothèque par défaut sur Alpine Linux) et Symfony,
|
||||
> vous pourriez avoir besoin d'augmenter la taille par défaut de la pile.
|
||||
> Sinon, vous pourriez rencontrer des erreurs telles que `PHP Fatal error: Maximum call stack size of 83360 bytes reached during compilation. Try splitting expression`
|
||||
>
|
||||
> Pour ce faire, modifiez la variable d'environnement `XCADDY_GO_BUILD_FLAGS` en quelque chose comme
|
||||
> `XCADDY_GO_BUILD_FLAGS=$'-ldflags "-w -s -extldflags \'-Wl,-z,stack-size=0x80000\'"'`
|
||||
> (modifiez la valeur de la taille de la pile selon les besoins de votre application).
|
||||
|
||||
### Sans xcaddy
|
||||
|
||||
Il est également possible de compiler FrankenPHP sans `xcaddy` en utilisant directement la commande `go` :
|
||||
|
||||
```console
|
||||
curl -L https://github.com/dunglas/frankenphp/archive/refs/heads/main.tar.gz | tar xz
|
||||
cd frankenphp-main/caddy/frankenphp
|
||||
CGO_CFLAGS=$(php-config --includes) CGO_LDFLAGS="$(php-config --ldflags) $(php-config --libs)" go build -tags=nobadger,nomysql,nopgx
|
||||
```
|
||||
277
docs/fr/config.md
Normal file
277
docs/fr/config.md
Normal file
@@ -0,0 +1,277 @@
|
||||
# Configuration
|
||||
|
||||
FrankenPHP, Caddy ainsi que les modules Mercure et Vulcain peuvent être configurés en utilisant [les formats pris en charge par Caddy](https://caddyserver.com/docs/getting-started#your-first-config).
|
||||
|
||||
Dans [les images Docker](docker.md), le `Caddyfile` est situé dans `/etc/frankenphp/Caddyfile`.
|
||||
Le binaire statique cherchera le `Caddyfile` dans le répertoire dans lequel il est démarré.
|
||||
|
||||
PHP lui-même peut être configuré [en utilisant un fichier `php.ini`](https://www.php.net/manual/fr/configuration.file.php).
|
||||
|
||||
L'interpréteur PHP cherchera dans les emplacements suivants :
|
||||
|
||||
Docker :
|
||||
|
||||
- php.ini : `/usr/local/etc/php/php.ini` Aucun php.ini n'est fourni par défaut.
|
||||
- fichiers de configuration supplémentaires : `/usr/local/etc/php/conf.d/*.ini`
|
||||
- extensions php : `/usr/local/lib/php/extensions/no-debug-zts-<YYYYMMDD>/`
|
||||
- Vous devriez copier un modèle officiel fourni par le projet PHP :
|
||||
|
||||
```dockerfile
|
||||
FROM dunglas/frankenphp
|
||||
|
||||
# Production :
|
||||
RUN cp $PHP_INI_DIR/php.ini-production $PHP_INI_DIR/php.ini
|
||||
|
||||
# Ou développement :
|
||||
RUN cp $PHP_INI_DIR/php.ini-development $PHP_INI_DIR/php.ini
|
||||
```
|
||||
|
||||
Installation de FrankenPHP (.rpm ou .deb) :
|
||||
|
||||
- php.ini : `/etc/frankenphp/php.ini` Un fichier php.ini avec des préréglages de production est fourni par défaut.
|
||||
- fichiers de configuration supplémentaires : `/etc/frankenphp/php.d/*.ini`
|
||||
- extensions php : `/usr/lib/frankenphp/modules/`
|
||||
|
||||
Binaire statique :
|
||||
|
||||
- php.ini : Le répertoire dans lequel `frankenphp run` ou `frankenphp php-server` est exécuté, puis `/etc/frankenphp/php.ini`
|
||||
- fichiers de configuration supplémentaires : `/etc/frankenphp/php.d/*.ini`
|
||||
- extensions php : ne peuvent pas être chargées
|
||||
- copiez l'un des fichiers `php.ini-production` ou `php.ini-development` fournis [dans les sources de PHP](https://github.com/php/php-src/).
|
||||
|
||||
## Configuration du Caddyfile
|
||||
|
||||
Les [directives HTTP](https://caddyserver.com/docs/caddyfile/concepts#directives) `php_server` ou `php` peuvent être utilisées dans les blocs de site pour servir votre application PHP.
|
||||
|
||||
Exemple minimal :
|
||||
|
||||
```caddyfile
|
||||
localhost {
|
||||
# Activer la compression (optionnel)
|
||||
encode zstd br gzip
|
||||
# Exécuter les fichiers PHP dans le répertoire courant et servir les assets
|
||||
php_server
|
||||
}
|
||||
```
|
||||
|
||||
Vous pouvez également configurer explicitement FrankenPHP en utilisant l'option globale :
|
||||
L'[option globale](https://caddyserver.com/docs/caddyfile/concepts#global-options) `frankenphp` peut être utilisée pour configurer FrankenPHP.
|
||||
|
||||
```caddyfile
|
||||
{
|
||||
frankenphp {
|
||||
num_threads <num_threads> # Définit le nombre de threads PHP à démarrer. Par défaut : 2x le nombre de CPUs disponibles.
|
||||
max_threads <num_threads> # Limite le nombre de threads PHP supplémentaires qui peuvent être démarrés au moment de l'exécution. Valeur par défaut : num_threads. Peut être mis à 'auto'.
|
||||
max_wait_time <duration> # Définit le temps maximum pendant lequel une requête peut attendre un thread PHP libre avant d'être interrompue. Valeur par défaut : désactivé.
|
||||
php_ini <key> <value> Définit une directive php.ini. Peut être utilisé plusieurs fois pour définir plusieurs directives.
|
||||
worker {
|
||||
file <path> # Définit le chemin vers le script worker.
|
||||
num <num> # Définit le nombre de threads PHP à démarrer, par défaut 2x le nombre de CPUs disponibles.
|
||||
env <key> <value> # Définit une variable d'environnement supplémentaire avec la valeur donnée. Peut être spécifié plusieurs fois pour régler plusieurs variables d'environnement.
|
||||
watch <path> # Définit le chemin d'accès à surveiller pour les modifications de fichiers. Peut être spécifié plusieurs fois pour plusieurs chemins.
|
||||
name <name> # Définit le nom du worker, utilisé dans les journaux et les métriques. Défaut : chemin absolu du fichier du worker
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ...
|
||||
```
|
||||
|
||||
Vous pouvez également utiliser la forme courte de l'option worker en une seule ligne :
|
||||
|
||||
```caddyfile
|
||||
{
|
||||
frankenphp {
|
||||
worker <file> <num>
|
||||
}
|
||||
}
|
||||
|
||||
# ...
|
||||
```
|
||||
|
||||
Vous pouvez aussi définir plusieurs workers si vous servez plusieurs applications sur le même serveur :
|
||||
|
||||
```caddyfile
|
||||
app.example.com {
|
||||
php_server {
|
||||
root /path/to/app/public
|
||||
worker index.php <num>
|
||||
}
|
||||
}
|
||||
|
||||
other.example.com {
|
||||
php_server {
|
||||
root /path/to/other/public
|
||||
worker index.php <num>
|
||||
}
|
||||
}
|
||||
|
||||
# ...
|
||||
```
|
||||
|
||||
L'utilisation de la directive `php_server` est généralement suffisante,
|
||||
mais si vous avez besoin d'un contrôle total, vous pouvez utiliser la directive `php`, qui permet un plus grand niveau de finesse dans la configuration.
|
||||
La directive `php` transmet toutes les entrées à PHP, au lieu de vérifier d'abord si
|
||||
c'est un fichier PHP ou pas. En savoir plus à ce sujet dans la [page performances](performance.md).
|
||||
|
||||
Utiliser la directive `php_server` est équivalent à cette configuration :
|
||||
|
||||
```caddyfile
|
||||
route {
|
||||
# Ajoute un slash final pour les requêtes de répertoire
|
||||
@canonicalPath {
|
||||
file {path}/index.php
|
||||
not path */
|
||||
}
|
||||
redir @canonicalPath {path}/ 308
|
||||
# Si le fichier demandé n'existe pas, essayer les fichiers index
|
||||
@indexFiles file {
|
||||
try_files {path} {path}/index.php index.php
|
||||
split_path .php
|
||||
}
|
||||
rewrite @indexFiles {http.matchers.file.relative}
|
||||
# FrankenPHP!
|
||||
@phpFiles path *.php
|
||||
php @phpFiles
|
||||
file_server
|
||||
}
|
||||
```
|
||||
|
||||
Les directives `php_server` et `php` disposent des options suivantes :
|
||||
|
||||
```caddyfile
|
||||
php_server [<matcher>] {
|
||||
root <directory> # Définit le dossier racine du le site. Par défaut : valeur de la directive `root` parente.
|
||||
split_path <delim...> # Définit les sous-chaînes pour diviser l'URI en deux parties. La première sous-chaîne correspondante sera utilisée pour séparer le "path info" du chemin. La première partie est suffixée avec la sous-chaîne correspondante et sera considérée comme le nom réel de la ressource (script CGI). La seconde partie sera définie comme PATH_INFO pour utilisation par le script. Par défaut : `.php`
|
||||
resolve_root_symlink false # Désactive la résolution du répertoire `root` vers sa valeur réelle en évaluant un lien symbolique, s'il existe (activé par défaut).
|
||||
env <key> <value> # Définit une variable d'environnement supplémentaire avec la valeur donnée. Peut être spécifié plusieurs fois pour plusieurs variables d'environnement.
|
||||
file_server off # Désactive la directive file_server intégrée.
|
||||
worker { # Crée un worker spécifique à ce serveur. Peut être spécifié plusieurs fois pour plusieurs workers.
|
||||
file <path> # Définit le chemin vers le script worker, peut être relatif à la racine du php_server
|
||||
num <num> # Définit le nombre de threads PHP à démarrer, par défaut 2x le nombre de CPUs disponibles
|
||||
name <name> # Définit le nom du worker, utilisé dans les journaux et les métriques. Défaut : chemin absolu du fichier du worker. Commence toujours par m# lorsqu'il est défini dans un bloc php_server.
|
||||
watch <path> # Définit le chemin d'accès à surveiller pour les modifications de fichiers. Peut être spécifié plusieurs fois pour plusieurs chemins.
|
||||
env <key> <value> # Définit une variable d'environnement supplémentaire avec la valeur donnée. Peut être spécifié plusieurs fois pour plusieurs variables d'environnement. Les variables d'environnement pour ce worker sont également héritées du parent php_server, mais peuvent être écrasées ici.
|
||||
}
|
||||
worker <other_file> <num> # Peut également utiliser la forme courte comme dans le bloc frankenphp global.
|
||||
}
|
||||
```
|
||||
|
||||
### Surveillance des modifications de fichier
|
||||
|
||||
Vu que les workers ne démarrent votre application qu'une seule fois et la gardent en mémoire, toute modification
|
||||
apportée à vos fichiers PHP ne sera pas répercutée immédiatement.
|
||||
|
||||
Les workers peuvent être redémarrés en cas de changement de fichier via la directive `watch`.
|
||||
Ceci est utile pour les environnements de développement.
|
||||
|
||||
```caddyfile
|
||||
{
|
||||
frankenphp {
|
||||
worker {
|
||||
file /path/to/app/public/worker.php
|
||||
watch
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Si le répertoire `watch` n'est pas précisé, il se rabattra sur `./**/*.{php,yaml,yml,twig,env}`,
|
||||
qui surveille tous les fichiers `.php`, `.yaml`, `.yml`, `.twig` et `.env` dans le répertoire et les sous-répertoires
|
||||
où le processus FrankenPHP a été lancé. Vous pouvez également spécifier un ou plusieurs répertoires via une commande
|
||||
[motif de nom de fichier shell](https://pkg.go.dev/path/filepath#Match) :
|
||||
|
||||
```caddyfile
|
||||
{
|
||||
frankenphp {
|
||||
worker {
|
||||
file /path/to/app/public/worker.php
|
||||
watch /path/to/app # surveille tous les fichiers dans tous les sous-répertoires de /path/to/app
|
||||
watch /path/to/app/*.php # surveille les fichiers se terminant par .php dans /path/to/app
|
||||
watch /path/to/app/**/*.php # surveille les fichiers PHP dans /path/to/app et les sous-répertoires
|
||||
watch /path/to/app/**/*.{php,twig} # surveille les fichiers PHP et Twig dans /path/to/app et les sous-répertoires
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- Le motif `**` signifie une surveillance récursive.
|
||||
- Les répertoires peuvent également être relatifs (depuis l'endroit où le processus FrankenPHP est démarré).
|
||||
- Si vous avez défini plusieurs workers, ils seront tous redémarrés lorsqu'un fichier est modifié.
|
||||
- Méfiez-vous des fichiers créés au moment de l'exécution (comme les logs) car ils peuvent provoquer des redémarrages intempestifs du worker.
|
||||
|
||||
La surveillance des fichiers est basé sur [e-dant/watcher](https://github.com/e-dant/watcher).
|
||||
|
||||
### Full Duplex (HTTP/1)
|
||||
|
||||
Lors de l'utilisation de HTTP/1.x, il peut être souhaitable d'activer le mode full-duplex pour permettre l'écriture d'une réponse avant que le corps entier
|
||||
n'ait été lu. (par exemple : WebSocket, événements envoyés par le serveur, etc.)
|
||||
|
||||
Il s'agit d'une configuration optionnelle qui doit être ajoutée aux options globales dans le fichier `Caddyfile` :
|
||||
|
||||
```caddyfile
|
||||
{
|
||||
servers {
|
||||
enable_full_duplex
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> [!CAUTION]
|
||||
>
|
||||
> L'activation de cette option peut entraîner un blocage (deadlock) des anciens clients HTTP/1.x qui ne supportent pas le full-duplex.
|
||||
> Cela peut aussi être configuré en utilisant la variable d'environnement `CADDY_GLOBAL_OPTIONS` :
|
||||
|
||||
```sh
|
||||
CADDY_GLOBAL_OPTIONS="servers {
|
||||
enable_full_duplex
|
||||
}"
|
||||
```
|
||||
|
||||
Vous trouverez plus d'informations sur ce paramètre dans la [documentation Caddy](https://caddyserver.com/docs/caddyfile/options#enable-full-duplex).
|
||||
|
||||
## Variables d'environnement
|
||||
|
||||
Les variables d'environnement suivantes peuvent être utilisées pour insérer des directives Caddy dans le `Caddyfile` sans le modifier :
|
||||
|
||||
- `SERVER_NAME` : change [les adresses sur lesquelles écouter](https://caddyserver.com/docs/caddyfile/concepts#addresses), les noms d'hôte fournis seront également utilisés pour le certificat TLS généré
|
||||
- `CADDY_GLOBAL_OPTIONS` : injecte [des options globales](https://caddyserver.com/docs/caddyfile/options)
|
||||
- `FRANKENPHP_CONFIG` : insère la configuration sous la directive `frankenphp`
|
||||
|
||||
Comme pour les SAPI FPM et CLI, les variables d'environnement ne sont exposées par défaut dans la superglobale `$_SERVER`.
|
||||
|
||||
La valeur `S` de [la directive `variables_order` de PHP](https://www.php.net/manual/fr/ini.core.php#ini.variables-order) est toujours équivalente à `ES`, que `E` soit défini ailleurs dans cette directive ou non.
|
||||
|
||||
## Configuration PHP
|
||||
|
||||
Pour charger [des fichiers de configuration PHP supplémentaires](https://www.php.net/manual/fr/configuration.file.php#configuration.file.scan),
|
||||
la variable d'environnement `PHP_INI_SCAN_DIR` peut être utilisée.
|
||||
Lorsqu'elle est définie, PHP chargera tous les fichiers avec l'extension `.ini` présents dans les répertoires donnés.
|
||||
|
||||
Vous pouvez également modifier la configuration de PHP en utilisant la directive `php_ini` dans le fichier `Caddyfile` :
|
||||
|
||||
```caddyfile
|
||||
{
|
||||
frankenphp {
|
||||
php_ini memory_limit 256M
|
||||
|
||||
# or
|
||||
|
||||
php_ini {
|
||||
memory_limit 256M
|
||||
max_execution_time 15
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Activer le mode debug
|
||||
|
||||
Lors de l'utilisation de l'image Docker, définissez la variable d'environnement `CADDY_GLOBAL_OPTIONS` sur `debug` pour activer le mode debug :
|
||||
|
||||
```console
|
||||
docker run -v $PWD:/app/public \
|
||||
-e CADDY_GLOBAL_OPTIONS=debug \
|
||||
-p 80:80 -p 443:443 -p 443:443/udp \
|
||||
dunglas/frankenphp
|
||||
```
|
||||
203
docs/fr/docker.md
Normal file
203
docs/fr/docker.md
Normal file
@@ -0,0 +1,203 @@
|
||||
# Création d'une image Docker personnalisée
|
||||
|
||||
Les images Docker de [FrankenPHP](https://hub.docker.com/r/dunglas/frankenphp) sont basées sur les [images PHP officielles](https://hub.docker.com/_/php/). Des variantes Debian et Alpine Linux sont fournies pour les architectures populaires. Les variantes Debian sont recommandées.
|
||||
|
||||
Des variantes pour PHP 8.2, 8.3 et 8.4 sont disponibles. [Parcourir les tags](https://hub.docker.com/r/dunglas/frankenphp/tags).
|
||||
|
||||
Les tags suivent le pattern suivant: `dunglas/frankenphp:<frankenphp-version>-php<php-version>-<os>`
|
||||
|
||||
- `<frankenphp-version>` et `<php-version>` sont repsectivement les numéros de version de FrankenPHP et PHP, allant de majeur (e.g. `1`), mineur (e.g. `1.2`) à des versions correctives (e.g. `1.2.3`).
|
||||
- `<os>` est soit `bookworm` (pour Debian Bookworm) ou `alpine` (pour la dernière version stable d'Alpine).
|
||||
|
||||
[Parcourir les tags](https://hub.docker.com/r/dunglas/frankenphp/tags).
|
||||
|
||||
## Comment utiliser les images
|
||||
|
||||
Créez un `Dockerfile` dans votre projet :
|
||||
|
||||
```dockerfile
|
||||
FROM dunglas/frankenphp
|
||||
|
||||
COPY . /app/public
|
||||
```
|
||||
|
||||
Ensuite, exécutez ces commandes pour construire et exécuter l'image Docker :
|
||||
|
||||
```console
|
||||
docker build -t my-php-app .
|
||||
docker run -it --rm --name my-running-app my-php-app
|
||||
```
|
||||
|
||||
## Comment installer plus d'extensions PHP
|
||||
|
||||
Le script [`docker-php-extension-installer`](https://github.com/mlocati/docker-php-extension-installer) est fourni dans l'image de base.
|
||||
Il est facile d'ajouter des extensions PHP supplémentaires :
|
||||
|
||||
```dockerfile
|
||||
FROM dunglas/frankenphp
|
||||
|
||||
# ajoutez des extensions supplémentaires ici :
|
||||
RUN install-php-extensions \
|
||||
pdo_mysql \
|
||||
gd \
|
||||
intl \
|
||||
zip \
|
||||
opcache
|
||||
```
|
||||
|
||||
## Comment installer plus de modules Caddy
|
||||
|
||||
FrankenPHP est construit sur Caddy, et tous les [modules Caddy](https://caddyserver.com/docs/modules/) peuvent être utilisés avec FrankenPHP.
|
||||
|
||||
La manière la plus simple d'installer des modules Caddy personnalisés est d'utiliser [xcaddy](https://github.com/caddyserver/xcaddy) :
|
||||
|
||||
```dockerfile
|
||||
FROM dunglas/frankenphp:builder AS builder
|
||||
|
||||
# Copier xcaddy dans l'image du constructeur
|
||||
COPY --from=caddy:builder /usr/bin/xcaddy /usr/bin/xcaddy
|
||||
|
||||
# CGO doit être activé pour construire FrankenPHP
|
||||
RUN CGO_ENABLED=1 \
|
||||
XCADDY_SETCAP=1 \
|
||||
XCADDY_GO_BUILD_FLAGS="-ldflags='-w -s' -tags=nobadger,nomysql,nopgx" \
|
||||
CGO_CFLAGS=$(php-config --includes) \
|
||||
CGO_LDFLAGS="$(php-config --ldflags) $(php-config --libs)" \
|
||||
xcaddy build \
|
||||
--output /usr/local/bin/frankenphp \
|
||||
--with frankenphp.dev=./ \
|
||||
--with frankenphp.dev/caddy=./caddy/ \
|
||||
--with github.com/dunglas/caddy-cbrotli \
|
||||
# Mercure et Vulcain sont inclus dans la construction officielle, mais n'hésitez pas à les retirer
|
||||
--with github.com/dunglas/mercure/caddy \
|
||||
--with github.com/dunglas/vulcain/caddy
|
||||
# Ajoutez des modules Caddy supplémentaires ici
|
||||
|
||||
FROM dunglas/frankenphp AS runner
|
||||
|
||||
# Remplacer le binaire officiel par celui contenant vos modules personnalisés
|
||||
COPY --from=builder /usr/local/bin/frankenphp /usr/local/bin/frankenphp
|
||||
```
|
||||
|
||||
L'image builder fournie par FrankenPHP contient une version compilée de `libphp`.
|
||||
[Les images builder](https://hub.docker.com/r/dunglas/frankenphp/tags?name=builder) sont fournies pour toutes les versions de FrankenPHP et PHP, à la fois pour Debian et Alpine.
|
||||
|
||||
> [!TIP]
|
||||
>
|
||||
> Si vous utilisez Alpine Linux et Symfony,
|
||||
> vous devrez peut-être [augmenter la taille de pile par défaut](compile.md#utiliser-xcaddy).
|
||||
|
||||
## Activer le mode Worker par défaut
|
||||
|
||||
Définissez la variable d'environnement `FRANKENPHP_CONFIG` pour démarrer FrankenPHP avec un script worker :
|
||||
|
||||
```dockerfile
|
||||
FROM dunglas/frankenphp
|
||||
|
||||
# ...
|
||||
|
||||
ENV FRANKENPHP_CONFIG="worker ./public/index.php"
|
||||
```
|
||||
|
||||
## Utiliser un volume en développement
|
||||
|
||||
Pour développer facilement avec FrankenPHP, montez le répertoire de l'hôte contenant le code source de l'application comme un volume dans le conteneur Docker :
|
||||
|
||||
```console
|
||||
docker run -v $PWD:/app/public -p 80:80 -p 443:443 -p 443:443/udp --tty my-php-app
|
||||
```
|
||||
|
||||
> [!TIP]
|
||||
>
|
||||
> L'option --tty permet d'avoir des logs lisibles par un humain au lieu de logs JSON.
|
||||
|
||||
Avec Docker Compose :
|
||||
|
||||
```yaml
|
||||
# compose.yaml
|
||||
|
||||
services:
|
||||
php:
|
||||
image: dunglas/frankenphp
|
||||
# décommentez la ligne suivante si vous souhaitez utiliser un Dockerfile personnalisé
|
||||
#build: .
|
||||
# décommentez la ligne suivante si vous souhaitez exécuter ceci dans un environnement de production
|
||||
# restart: always
|
||||
ports:
|
||||
- "80:80" # HTTP
|
||||
- "443:443" # HTTPS
|
||||
- "443:443/udp" # HTTP/3
|
||||
volumes:
|
||||
- ./:/app/public
|
||||
- caddy_data:/data
|
||||
- caddy_config:/config
|
||||
# commentez la ligne suivante en production, elle permet d'avoir de beaux logs lisibles en dev
|
||||
tty: true
|
||||
|
||||
# Volumes nécessaires pour les certificats et la configuration de Caddy
|
||||
volumes:
|
||||
caddy_data:
|
||||
caddy_config:
|
||||
```
|
||||
|
||||
## Exécution en tant qu'utilisateur non-root
|
||||
|
||||
FrankenPHP peut s'exécuter en tant qu'utilisateur non-root dans Docker.
|
||||
|
||||
Voici un exemple de `Dockerfile` le permettant :
|
||||
|
||||
```dockerfile
|
||||
FROM dunglas/frankenphp
|
||||
|
||||
ARG USER=appuser
|
||||
|
||||
RUN \
|
||||
# Utilisez "adduser -D ${USER}" pour les distributions basées sur Alpine
|
||||
useradd ${USER}; \
|
||||
# Ajouter la capacité supplémentaire de se lier aux ports 80 et 443
|
||||
setcap CAP_NET_BIND_SERVICE=+eip /usr/local/bin/frankenphp; \
|
||||
# Donner l'accès en écriture à /data/caddy et /config/caddy
|
||||
chown -R ${USER}:${USER} /data/caddy && chown -R ${USER}:${USER} /config/caddy
|
||||
|
||||
USER ${USER}
|
||||
```
|
||||
|
||||
### Exécution sans capacité
|
||||
|
||||
Même lorsqu'il s'exécute en tant qu'utilisateur autre que root, FrankenPHP a besoin de la capacité `CAP_NET_BIND_SERVICE`
|
||||
pour que son serveur utilise les ports privilégiés (80 et 443).
|
||||
|
||||
Si vous exposez FrankenPHP sur un port non privilégié (à partir de 1024), il est possible de faire fonctionner le serveur web avec un utilisateur qui n'est pas root, et sans avoir besoin d'aucune capacité.
|
||||
|
||||
```dockerfile
|
||||
FROM dunglas/frankenphp
|
||||
|
||||
ARG USER=appuser
|
||||
|
||||
RUN \
|
||||
# Utiliser "adduser -D ${USER}" pour les distros basées sur Alpine
|
||||
useradd ${USER}; \
|
||||
# Supprimer la capacité par défaut \
|
||||
setcap -r /usr/local/bin/frankenphp; \
|
||||
# Donner un accès en écriture à /data/caddy et /config/caddy \
|
||||
chown -R ${USER}:${USER} /data/caddy && chown -R ${USER}:${USER} /config/caddy
|
||||
|
||||
USER ${USER}
|
||||
```
|
||||
|
||||
Ensuite, définissez la variable d'environnement `SERVER_NAME` pour utiliser un port non privilégié.
|
||||
Exemple `:8000`
|
||||
|
||||
## Mises à jour
|
||||
|
||||
Les images Docker sont construites :
|
||||
|
||||
- lorsqu'une nouvelle version est taguée
|
||||
- tous les jours à 4h UTC, si de nouvelles versions des images officielles PHP sont disponibles
|
||||
|
||||
## Versions de développement
|
||||
|
||||
Les versions de développement sont disponibles dans le dépôt Docker [`dunglas/frankenphp-dev`](https://hub.docker.com/repository/docker/dunglas/frankenphp-dev). Un nouveau build est déclenché chaque fois qu'un commit est poussé sur la branche principale du dépôt GitHub.
|
||||
|
||||
Les tags `latest*` pointent vers la tête de la branche `main`.
|
||||
Les tags sous la forme `sha-<hash-du-commit-git>` sont également disponibles.
|
||||
21
docs/fr/early-hints.md
Normal file
21
docs/fr/early-hints.md
Normal file
@@ -0,0 +1,21 @@
|
||||
# Early Hints
|
||||
|
||||
FrankenPHP prend nativement en charge le code de statut [103 Early Hints](https://developer.chrome.com/blog/early-hints/).
|
||||
L'utilisation des Early Hints peut améliorer le temps de chargement de vos pages web de 30 %.
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
header('Link: </style.css>; rel=preload; as=style');
|
||||
headers_send(103);
|
||||
|
||||
// vos algorithmes lents et requêtes SQL 🤪
|
||||
|
||||
echo <<<'HTML'
|
||||
<!DOCTYPE html>
|
||||
<title>Hello FrankenPHP</title>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
HTML;
|
||||
```
|
||||
|
||||
Les Early Hints sont pris en charge à la fois par les modes "standard" et [worker](worker.md).
|
||||
151
docs/fr/embed.md
Normal file
151
docs/fr/embed.md
Normal file
@@ -0,0 +1,151 @@
|
||||
# Applications PHP en tant que binaires autonomes
|
||||
|
||||
FrankenPHP a la capacité d'incorporer le code source et les assets des applications PHP dans un binaire statique et autonome.
|
||||
|
||||
Grâce à cette fonctionnalité, les applications PHP peuvent être distribuées en tant que binaires autonomes qui incluent l'application elle-même, l'interpréteur PHP et Caddy, un serveur web de qualité production.
|
||||
|
||||
Pour en savoir plus sur cette fonctionnalité, consultez [la présentation faite par Kévin à la SymfonyCon 2023](https://dunglas.dev/2023/12/php-and-symfony-apps-as-standalone-binaries/).
|
||||
|
||||
Pour embarquer des applications Laravel, [lisez ce point spécifique de la documentation](laravel.md#les-applications-laravel-en-tant-que-binaires-autonomes).
|
||||
|
||||
## Préparer votre application
|
||||
|
||||
Avant de créer le binaire autonome, assurez-vous que votre application est prête à être intégrée.
|
||||
|
||||
Vous devrez probablement :
|
||||
|
||||
- Installer les dépendances de production de l'application
|
||||
- Dumper l'autoloader
|
||||
- Activer le mode production de votre application (si disponible)
|
||||
- Supprimer les fichiers inutiles tels que `.git` ou les tests pour réduire la taille de votre binaire final
|
||||
|
||||
Par exemple, pour une application Symfony, lancez les commandes suivantes :
|
||||
|
||||
```console
|
||||
# Exporter le projet pour se débarrasser de .git/, etc.
|
||||
mkdir $TMPDIR/my-prepared-app
|
||||
git archive HEAD | tar -x -C $TMPDIR/my-prepared-app
|
||||
cd $TMPDIR/my-prepared-app
|
||||
|
||||
# Définir les variables d'environnement appropriées
|
||||
echo APP_ENV=prod > .env.local
|
||||
echo APP_DEBUG=0 >> .env.local
|
||||
|
||||
# Supprimer les tests et autres fichiers inutiles pour économiser de l'espace
|
||||
# Alternativement, ajoutez ces fichiers avec l'attribut export-ignore dans votre fichier .gitattributes
|
||||
rm -Rf tests/
|
||||
|
||||
# Installer les dépendances
|
||||
composer install --ignore-platform-reqs --no-dev -a
|
||||
|
||||
# Optimiser le .env
|
||||
composer dump-env prod
|
||||
```
|
||||
|
||||
### Personnaliser la configuration
|
||||
|
||||
Pour personnaliser [la configuration](config.md),
|
||||
vous pouvez mettre un fichier `Caddyfile` ainsi qu'un fichier `php.ini`
|
||||
dans le répertoire principal de l'application à intégrer
|
||||
(`$TMPDIR/my-prepared-app` dans l'exemple précédent).
|
||||
|
||||
## Créer un binaire Linux
|
||||
|
||||
La manière la plus simple de créer un binaire Linux est d'utiliser le builder basé sur Docker que nous fournissons.
|
||||
|
||||
1. Créez un fichier nommé `static-build.Dockerfile` dans le répertoire de votre application préparée :
|
||||
|
||||
```dockerfile
|
||||
FROM --platform=linux/amd64 dunglas/frankenphp:static-builder
|
||||
|
||||
# Copy your app
|
||||
WORKDIR /go/src/app/dist/app
|
||||
COPY . .
|
||||
|
||||
# Build the static binary, be sure to select only the PHP extensions you want
|
||||
WORKDIR /go/src/app/
|
||||
RUN EMBED=dist/app/ ./build-static.sh
|
||||
```
|
||||
|
||||
> [!CAUTION]
|
||||
>
|
||||
> Certains fichiers `.dockerignore` (par exemple celui fourni par défaut par [Symfony Docker](https://github.com/dunglas/symfony-docker/blob/main/.dockerignore))
|
||||
> empêchent la copie du dossier `vendor/` et des fichiers `.env`. Assurez-vous d'ajuster ou de supprimer le fichier `.dockerignore` avant le build.
|
||||
|
||||
2. Construisez:
|
||||
|
||||
```console
|
||||
docker build -t static-app -f static-build.Dockerfile .
|
||||
```
|
||||
|
||||
3. Extrayez le binaire :
|
||||
|
||||
```console
|
||||
docker cp $(docker create --name static-app-tmp static-app):/go/src/app/dist/frankenphp-linux-x86_64 my-app ; docker rm static-app-tmp
|
||||
```
|
||||
|
||||
Le binaire généré sera nommé `my-app` dans le répertoire courant.
|
||||
|
||||
## Créer un binaire pour d'autres systèmes d'exploitation
|
||||
|
||||
Si vous ne souhaitez pas utiliser Docker, ou souhaitez construire un binaire macOS, utilisez le script shell que nous fournissons :
|
||||
|
||||
```console
|
||||
git clone https://github.com/dunglas/frankenphp
|
||||
cd frankenphp
|
||||
EMBED=/path/to/your/app ./build-static.sh
|
||||
```
|
||||
|
||||
Le binaire obtenu est le fichier nommé `frankenphp-<os>-<arch>` dans le répertoire `dist/`.
|
||||
|
||||
## Utiliser le binaire
|
||||
|
||||
C'est tout ! Le fichier `my-app` (ou `dist/frankenphp-<os>-<arch>` sur d'autres systèmes d'exploitation) contient votre application autonome !
|
||||
|
||||
Pour démarrer l'application web, exécutez :
|
||||
|
||||
```console
|
||||
./my-app php-server
|
||||
```
|
||||
|
||||
Si votre application contient un [script worker](worker.md), démarrez le worker avec quelque chose comme :
|
||||
|
||||
```console
|
||||
./my-app php-server --worker public/index.php
|
||||
```
|
||||
|
||||
Pour activer HTTPS (un certificat Let's Encrypt est automatiquement créé), HTTP/2 et HTTP/3, spécifiez le nom de domaine à utiliser :
|
||||
|
||||
```console
|
||||
./my-app php-server --domain localhost
|
||||
```
|
||||
|
||||
Vous pouvez également exécuter les scripts CLI PHP incorporés dans votre binaire :
|
||||
|
||||
```console
|
||||
./my-app php-cli bin/console
|
||||
```
|
||||
|
||||
## Extensions PHP
|
||||
|
||||
Par défaut, le script construira les extensions requises par le fichier `composer.json` de votre projet, s'il y en a.
|
||||
Si le fichier `composer.json` n'existe pas, les extensions par défaut sont construites, comme documenté dans [Créer un binaire statique](static.md).
|
||||
|
||||
Pour personnaliser les extensions, utilisez la variable d'environnement `PHP_EXTENSIONS`.
|
||||
|
||||
```console
|
||||
EMBED=/path/to/your/app \
|
||||
PHP_EXTENSIONS=ctype,iconv,pdo_sqlite \
|
||||
./build-static.sh
|
||||
```
|
||||
|
||||
## Personnaliser la compilation
|
||||
|
||||
[Consultez la documentation sur la compilation statique](static.md) pour voir comment personnaliser le binaire (extensions, version PHP...).
|
||||
|
||||
## Distribuer le binaire
|
||||
|
||||
Sous Linux, le binaire est compressé par défaut à l'aide de [UPX](https://upx.github.io).
|
||||
|
||||
Sous Mac, pour réduire la taille du fichier avant de l'envoyer, vous pouvez le compresser.
|
||||
Nous recommandons `xz`.
|
||||
31
docs/fr/github-actions.md
Normal file
31
docs/fr/github-actions.md
Normal file
@@ -0,0 +1,31 @@
|
||||
# Utilisation de GitHub Actions
|
||||
|
||||
Ce dépôt construit et déploie l'image Docker sur [le Hub Docker](https://hub.docker.com/r/dunglas/frankenphp) pour
|
||||
chaque pull request approuvée ou sur votre propre fork une fois configuré.
|
||||
|
||||
## Configuration de GitHub Actions
|
||||
|
||||
Dans les paramètres du dépôt, sous "secrets", ajoutez les secrets suivants :
|
||||
|
||||
- `REGISTRY_LOGIN_SERVER` : Le registre Docker à utiliser (par exemple, `docker.io`).
|
||||
- `REGISTRY_USERNAME` : Le nom d'utilisateur à utiliser pour se connecter au registre (par exemple, `dunglas`).
|
||||
- `REGISTRY_PASSWORD` : Le mot de passe à utiliser pour se connecter au registre (par exemple, une clé d'accès).
|
||||
- `IMAGE_NAME` : Le nom de l'image (par exemple, `dunglas/frankenphp`).
|
||||
|
||||
## Construction et push de l'image
|
||||
|
||||
1. Créez une Pull Request ou poussez vers votre fork.
|
||||
2. GitHub Actions va construire l'image et exécuter tous les tests.
|
||||
3. Si la construction est réussie, l'image sera poussée vers le registre en utilisant le tag `pr-x`, où `x` est le numéro de la PR.
|
||||
|
||||
## Déploiement de l'image
|
||||
|
||||
1. Une fois la Pull Request fusionnée, GitHub Actions exécutera à nouveau les tests et construira une nouvelle image.
|
||||
2. Si la construction est réussie, le tag `main` sera mis à jour dans le registre Docker.
|
||||
|
||||
## Releases
|
||||
|
||||
1. Créez un nouveau tag dans le dépôt.
|
||||
2. GitHub Actions va construire l'image et exécuter tous les tests.
|
||||
3. Si la compilation est réussie, l'image sera poussée vers le registre en utilisant le nom du tag comme tag (par exemple, `v1.2.3` et `v1.2` seront créés).
|
||||
4. Le tag `latest` sera également mis à jour.
|
||||
143
docs/fr/known-issues.md
Normal file
143
docs/fr/known-issues.md
Normal file
@@ -0,0 +1,143 @@
|
||||
# Problèmes Connus
|
||||
|
||||
## Extensions PHP non prises en charge
|
||||
|
||||
Les extensions suivantes sont connues pour ne pas être compatibles avec FrankenPHP :
|
||||
|
||||
| Nom | Raison | Alternatives |
|
||||
| ----------------------------------------------------------------------------------------------------------- | --------------- | -------------------------------------------------------------------------------------------------------------------- |
|
||||
| [imap](https://www.php.net/manual/en/imap.installation.php) | Non thread-safe | [javanile/php-imap2](https://github.com/javanile/php-imap2), [webklex/php-imap](https://github.com/Webklex/php-imap) |
|
||||
| [newrelic](https://docs.newrelic.com/docs/apm/agents/php-agent/getting-started/introduction-new-relic-php/) | Non thread-safe | - |
|
||||
|
||||
## Extensions PHP boguées
|
||||
|
||||
Les extensions suivantes ont des bugs connus ou des comportements inattendus lorsqu'elles sont utilisées avec FrankenPHP :
|
||||
|
||||
| Nom | Problème |
|
||||
| ------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| [ext-openssl](https://www.php.net/manual/fr/book.openssl.php) | Lors de l'utilisation d'une version statique de FrankenPHP (construite avec la libc musl), l'extension OpenSSL peut planter sous de fortes charges. Une solution consiste à utiliser une version liée dynamiquement (comme celle utilisée dans les images Docker). Ce bogue est [suivi par PHP](https://github.com/php/php-src/issues/13648). |
|
||||
|
||||
## get_browser
|
||||
|
||||
La fonction [get_browser()](https://www.php.net/manual/fr/function.get-browser.php) semble avoir de mauvaises performances après un certain temps. Une solution est de mettre en cache (par exemple, avec [APCu](https://www.php.net/manual/en/book.apcu.php)) les résultats par agent utilisateur, car ils sont statiques.
|
||||
|
||||
## Binaire autonome et images Docker basées sur Alpine
|
||||
|
||||
Le binaire autonome et les images docker basées sur Alpine (`dunglas/frankenphp:*-alpine`) utilisent [musl libc](https://musl.libc.org/) au lieu de [glibc et ses amis](https://www.etalabs.net/compare_libcs.html), pour garder une taille de binaire plus petite. Cela peut entraîner des problèmes de compatibilité. En particulier, le drapeau glob `GLOB_BRACE` n'est [pas disponible](https://www.php.net/manual/fr/function.glob.php).
|
||||
|
||||
## Utilisation de `https://127.0.0.1` avec Docker
|
||||
|
||||
Par défaut, FrankenPHP génère un certificat TLS pour `localhost`.
|
||||
C'est l'option la plus simple et recommandée pour le développement local.
|
||||
|
||||
Si vous voulez vraiment utiliser `127.0.0.1` comme hôte, il est possible de configurer FrankenPHP pour générer un certificat pour cela en définissant le nom du serveur à `127.0.0.1`.
|
||||
|
||||
Malheureusement, cela ne suffit pas lors de l'utilisation de Docker à cause de [son système de gestion des réseaux](https://docs.docker.com/network/).
|
||||
Vous obtiendrez une erreur TLS similaire à `curl: (35) LibreSSL/3.3.6: error:1404B438:SSL routines:ST_CONNECT:tlsv1 alert internal error`.
|
||||
|
||||
Si vous utilisez Linux, une solution est d'utiliser [le pilote de réseau "hôte"](https://docs.docker.com/network/network-tutorial-host/) :
|
||||
|
||||
```console
|
||||
docker run \
|
||||
-e SERVER_NAME="127.0.0.1" \
|
||||
-v $PWD:/app/public \
|
||||
--network host \
|
||||
dunglas/frankenphp
|
||||
```
|
||||
|
||||
Le pilote de réseau "hôte" n'est pas pris en charge sur Mac et Windows. Sur ces plateformes, vous devrez deviner l'adresse IP du conteneur et l'inclure dans les noms de serveur.
|
||||
|
||||
Exécutez la commande `docker network inspect bridge` et inpectez la clef `Containers` pour identifier la dernière adresse IP attribuée sous la clef `IPv4Address`, puis incrémentez-la d'un. Si aucun conteneur n'est en cours d'exécution, la première adresse IP attribuée est généralement `172.17.0.2`.
|
||||
|
||||
Ensuite, incluez ceci dans la variable d'environnement `SERVER_NAME` :
|
||||
|
||||
```console
|
||||
docker run \
|
||||
-e SERVER_NAME="127.0.0.1, 172.17.0.3" \
|
||||
-v $PWD:/app/public \
|
||||
-p 80:80 -p 443:443 -p 443:443/udp \
|
||||
dunglas/frankenphp
|
||||
```
|
||||
|
||||
> [!CAUTION]
|
||||
>
|
||||
> Assurez-vous de remplacer `172.17.0.3` par l'IP qui sera attribuée à votre conteneur.
|
||||
|
||||
Vous devriez maintenant pouvoir accéder à `https://127.0.0.1` depuis la machine hôte.
|
||||
|
||||
Si ce n'est pas le cas, lancez FrankenPHP en mode debug pour essayer de comprendre le problème :
|
||||
|
||||
```console
|
||||
docker run \
|
||||
-e CADDY_GLOBAL_OPTIONS="debug" \
|
||||
-e SERVER_NAME="127.0.0.1" \
|
||||
-v $PWD:/app/public \
|
||||
-p 80:80 -p 443:443 -p 443:443/udp \
|
||||
dunglas/frankenphp
|
||||
```
|
||||
|
||||
## Scripts Composer Faisant Références à `@php`
|
||||
|
||||
Les [scripts Composer](https://getcomposer.org/doc/articles/scripts.md) peuvent vouloir exécuter un binaire PHP pour certaines tâches, par exemple dans [un projet Laravel](laravel.md) pour exécuter `@php artisan package:discover --ansi`. Cela [echoue actuellement](https://github.com/dunglas/frankenphp/issues/483#issuecomment-1899890915) pour deux raisons :
|
||||
|
||||
- Composer ne sait pas comment appeler le binaire FrankenPHP ;
|
||||
- Composer peut ajouter des paramètres PHP en utilisant le paramètre `-d` dans la commande, ce que FrankenPHP ne supporte pas encore.
|
||||
|
||||
Comme solution de contournement, nous pouvons créer un script shell dans `/usr/local/bin/php` qui supprime les paramètres non supportés et appelle ensuite FrankenPHP :
|
||||
|
||||
```bash
|
||||
#!/usr/bin/env bash
|
||||
args=("$@")
|
||||
index=0
|
||||
for i in "$@"
|
||||
do
|
||||
if [ "$i" == "-d" ]; then
|
||||
unset 'args[$index]'
|
||||
unset 'args[$index+1]'
|
||||
fi
|
||||
index=$((index+1))
|
||||
done
|
||||
|
||||
/usr/local/bin/frankenphp php-cli ${args[@]}
|
||||
```
|
||||
|
||||
Ensuite, mettez la variable d'environnement `PHP_BINARY` au chemin de notre script `php` et lancez Composer :
|
||||
|
||||
```console
|
||||
export PHP_BINARY=/usr/local/bin/php
|
||||
composer install
|
||||
```
|
||||
|
||||
## Résolution des problèmes TLS/SSL avec les binaires statiques
|
||||
|
||||
Lorsque vous utilisez les binaires statiques, vous pouvez rencontrer les erreurs suivantes liées à TLS, par exemple lors de l'envoi de courriels utilisant STARTTLS :
|
||||
|
||||
```text
|
||||
Unable to connect with STARTTLS: stream_socket_enable_crypto(): SSL operation failed with code 5. OpenSSL Error messages:
|
||||
error:80000002:system library::No such file or directory
|
||||
error:80000002:system library::No such file or directory
|
||||
error:80000002:system library::No such file or directory
|
||||
error:0A000086:SSL routines::certificate verify failed
|
||||
```
|
||||
|
||||
Comme le binaire statique ne contient pas de certificats TLS, vous devez indiquer à OpenSSL l'installation de vos certificats CA locaux.
|
||||
|
||||
Inspectez la sortie de [`openssl_get_cert_locations()`](https://www.php.net/manual/en/function.openssl-get-cert-locations.php),
|
||||
pour trouver l'endroit où les certificats CA doivent être installés et stockez-les à cet endroit.
|
||||
|
||||
> [!WARNING]
|
||||
>
|
||||
> Les contextes Web et CLI peuvent avoir des paramètres différents.
|
||||
> Assurez-vous d'exécuter `openssl_get_cert_locations()` dans le bon contexte.
|
||||
|
||||
[Les certificats CA extraits de Mozilla peuvent être téléchargés sur le site curl](https://curl.se/docs/caextract.html).
|
||||
|
||||
Alternativement, de nombreuses distributions, y compris Debian, Ubuntu, et Alpine fournissent des paquets nommés `ca-certificates` qui contiennent ces certificats.
|
||||
|
||||
Il est également possible d'utiliser `SSL_CERT_FILE` et `SSL_CERT_DIR` pour indiquer à OpenSSL où chercher les certificats CA :
|
||||
|
||||
```console
|
||||
# Définir les variables d'environnement des certificats TLS
|
||||
export SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt
|
||||
export SSL_CERT_DIR=/etc/ssl/certs
|
||||
```
|
||||
184
docs/fr/laravel.md
Normal file
184
docs/fr/laravel.md
Normal file
@@ -0,0 +1,184 @@
|
||||
# Laravel
|
||||
|
||||
## Docker
|
||||
|
||||
Déployer une application web [Laravel](https://laravel.com) avec FrankenPHP est très facile. Il suffit de monter le projet dans le répertoire `/app` de l'image Docker officielle.
|
||||
|
||||
Exécutez cette commande depuis le répertoire principal de votre application Laravel :
|
||||
|
||||
```console
|
||||
docker run -p 80:80 -p 443:443 -p 443:443/udp -v $PWD:/app dunglas/frankenphp
|
||||
```
|
||||
|
||||
Et profitez !
|
||||
|
||||
## Installation Locale
|
||||
|
||||
Vous pouvez également exécuter vos projets Laravel avec FrankenPHP depuis votre machine locale :
|
||||
|
||||
1. [Téléchargez le binaire correspondant à votre système](README.md#binaire-autonome)
|
||||
2. Ajoutez la configuration suivante dans un fichier nommé `Caddyfile` placé dans le répertoire racine de votre projet Laravel :
|
||||
|
||||
```caddyfile
|
||||
{
|
||||
frankenphp
|
||||
}
|
||||
|
||||
# Le nom de domaine de votre serveur
|
||||
localhost {
|
||||
# Définir le répertoire racine sur le dossier public/
|
||||
root public/
|
||||
# Autoriser la compression (optionnel)
|
||||
encode zstd br gzip
|
||||
# Exécuter les scripts PHP du dossier public/ et servir les assets
|
||||
php_server {
|
||||
try_files {path} index.php
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
3. Démarrez FrankenPHP depuis le répertoire racine de votre projet Laravel : `frankenphp run`
|
||||
|
||||
## Laravel Octane
|
||||
|
||||
Octane peut être installé via le gestionnaire de paquets Composer :
|
||||
|
||||
```console
|
||||
composer require laravel/octane
|
||||
```
|
||||
|
||||
Après avoir installé Octane, vous pouvez exécuter la commande Artisan `octane:install`, qui installera le fichier de configuration d'Octane dans votre application :
|
||||
|
||||
```console
|
||||
php artisan octane:install --server=frankenphp
|
||||
```
|
||||
|
||||
Le serveur Octane peut être démarré via la commande Artisan `octane:frankenphp`.
|
||||
|
||||
```console
|
||||
php artisan octane:frankenphp
|
||||
```
|
||||
|
||||
La commande `octane:frankenphp` peut prendre les options suivantes :
|
||||
|
||||
- `--host` : L'adresse IP à laquelle le serveur doit se lier (par défaut : `127.0.0.1`)
|
||||
- `--port` : Le port sur lequel le serveur doit être disponible (par défaut : `8000`)
|
||||
- `--admin-port` : Le port sur lequel le serveur administratif doit être disponible (par défaut : `2019`)
|
||||
- `--workers` : Le nombre de workers qui doivent être disponibles pour traiter les requêtes (par défaut : `auto`)
|
||||
- `--max-requests` : Le nombre de requêtes à traiter avant de recharger le serveur (par défaut : `500`)
|
||||
- `--caddyfile` : Le chemin vers le fichier `Caddyfile` de FrankenPHP
|
||||
- `--https` : Activer HTTPS, HTTP/2, et HTTP/3, et générer automatiquement et renouveler les certificats
|
||||
- `--http-redirect` : Activer la redirection HTTP vers HTTPS (uniquement activé si --https est passé)
|
||||
- `--watch` : Recharger automatiquement le serveur lorsque l'application est modifiée
|
||||
- `--poll` : Utiliser le sondage du système de fichiers pendant la surveillance pour surveiller les fichiers sur un réseau
|
||||
- `--log-level` : Enregistrer les messages au niveau de journalisation spécifié ou au-dessus, en utilisant le logger natif de Caddy
|
||||
|
||||
> [!TIP]
|
||||
> Pour obtenir des logs structurés en JSON logs (utile quand vous utilisez des solutions d'analyse de logs), passez explicitement l'option `--log-level`.
|
||||
|
||||
En savoir plus sur Laravel Octane [dans sa documentation officielle](https://laravel.com/docs/octane).
|
||||
|
||||
## Les Applications Laravel En Tant Que Binaires Autonomes
|
||||
|
||||
En utilisant la [fonctionnalité d'intégration d'applications de FrankenPHP](embed.md), il est possible de distribuer
|
||||
les applications Laravel sous forme de binaires autonomes.
|
||||
|
||||
Suivez ces étapes pour empaqueter votre application Laravel en tant que binaire autonome pour Linux :
|
||||
|
||||
1. Créez un fichier nommé `static-build.Dockerfile` dans le dépôt de votre application :
|
||||
|
||||
```dockerfile
|
||||
FROM --platform=linux/amd64 dunglas/frankenphp:static-builder
|
||||
|
||||
# Copiez votre application
|
||||
WORKDIR /go/src/app/dist/app
|
||||
COPY . .
|
||||
|
||||
# Supprimez les tests et autres fichiers inutiles pour gagner de la place
|
||||
# Alternativement, ajoutez ces fichiers à un fichier .dockerignore
|
||||
RUN rm -Rf tests/
|
||||
|
||||
# Copiez le fichier .env
|
||||
RUN cp .env.example .env
|
||||
# Modifier APP_ENV et APP_DEBUG pour qu'ils soient prêts pour la production
|
||||
RUN sed -i'' -e 's/^APP_ENV=.*/APP_ENV=production/' -e 's/^APP_DEBUG=.*/APP_DEBUG=false/' .env
|
||||
|
||||
# Apportez d'autres modifications à votre fichier .env si nécessaire
|
||||
|
||||
# Installez les dépendances
|
||||
RUN composer install --ignore-platform-reqs --no-dev -a
|
||||
|
||||
# Construire le binaire statique
|
||||
WORKDIR /go/src/app/
|
||||
RUN EMBED=dist/app/ ./build-static.sh
|
||||
```
|
||||
|
||||
> [!CAUTION]
|
||||
>
|
||||
> Certains fichiers `.dockerignore` ignoreront le répertoire `vendor/`
|
||||
> et les fichiers `.env`. Assurez-vous d'ajuster ou de supprimer le fichier `.dockerignore` avant la construction.
|
||||
|
||||
2. Build:
|
||||
|
||||
```console
|
||||
docker build -t static-laravel-app -f static-build.Dockerfile .
|
||||
```
|
||||
|
||||
3. Extraire le binaire
|
||||
|
||||
```console
|
||||
docker cp $(docker create --name static-laravel-app-tmp static-laravel-app):/go/src/app/dist/frankenphp-linux-x86_64 frankenphp ; docker rm static-laravel-app-tmp
|
||||
```
|
||||
|
||||
4. Remplir les caches :
|
||||
|
||||
```console
|
||||
frankenphp php-cli artisan optimize
|
||||
```
|
||||
|
||||
5. Exécutez les migrations de base de données (s'il y en a) :
|
||||
|
||||
```console
|
||||
frankenphp php-cli artisan migrate
|
||||
```
|
||||
|
||||
6. Générer la clé secrète de l'application :
|
||||
|
||||
```console
|
||||
frankenphp php-cli artisan key:generate
|
||||
```
|
||||
|
||||
7. Démarrez le serveur:
|
||||
|
||||
```console
|
||||
frankenphp php-server
|
||||
```
|
||||
|
||||
Votre application est maintenant prête !
|
||||
|
||||
Pour en savoir plus sur les options disponibles et sur la construction de binaires pour d'autres systèmes d'exploitation,
|
||||
consultez la documentation [Applications PHP en tant que binaires autonomes](embed.md).
|
||||
|
||||
### Changer le chemin de stockage
|
||||
|
||||
Par défaut, Laravel stocke les fichiers téléchargés, les caches, les logs, etc. dans le répertoire `storage/` de l'application.
|
||||
Ceci n'est pas adapté aux applications embarquées, car chaque nouvelle version sera extraite dans un répertoire temporaire différent.
|
||||
|
||||
Définissez la variable d'environnement `LARAVEL_STORAGE_PATH` (par exemple, dans votre fichier `.env`) ou appelez la méthode `Illuminate\Foundation\Application::useStoragePath()` pour utiliser un répertoire en dehors du répertoire temporaire.
|
||||
|
||||
### Exécuter Octane avec des binaires autonomes
|
||||
|
||||
Il est même possible d'empaqueter les applications Laravel Octane en tant que binaires autonomes !
|
||||
|
||||
Pour ce faire, [installez Octane correctement](#laravel-octane) et suivez les étapes décrites dans [la section précédente](#les-applications-laravel-en-tant-que-binaires-autonomes).
|
||||
|
||||
Ensuite, pour démarrer FrankenPHP en mode worker via Octane, exécutez :
|
||||
|
||||
```console
|
||||
PATH="$PWD:$PATH" frankenphp php-cli artisan octane:frankenphp
|
||||
```
|
||||
|
||||
> [!CAUTION]
|
||||
>
|
||||
> Pour que la commande fonctionne, le binaire autonome **doit** être nommé `frankenphp`
|
||||
> car Octane a besoin d'un programme nommé `frankenphp` disponible dans le chemin
|
||||
12
docs/fr/mercure.md
Normal file
12
docs/fr/mercure.md
Normal file
@@ -0,0 +1,12 @@
|
||||
# Temps Réel
|
||||
|
||||
FrankenPHP est livré avec un hub [Mercure](https://mercure.rocks) intégré.
|
||||
Mercure permet de pousser des événements en temps réel vers tous les appareils connectés : ils recevront un événement JavaScript instantanément.
|
||||
|
||||
Aucune bibliothèque JS ou SDK requis !
|
||||
|
||||

|
||||
|
||||
Pour activer le hub Mercure, mettez à jour le `Caddyfile` comme décrit [sur le site de Mercure](https://mercure.rocks/docs/hub/config).
|
||||
|
||||
Pour pousser des mises à jour Mercure depuis votre code, nous recommandons le [Composant Mercure de Symfony](https://symfony.com/components/Mercure) (vous n'avez pas besoin du framework full stack Symfony pour l'utiliser).
|
||||
17
docs/fr/metrics.md
Normal file
17
docs/fr/metrics.md
Normal file
@@ -0,0 +1,17 @@
|
||||
# Métriques
|
||||
|
||||
Lorsque les [métriques Caddy](https://caddyserver.com/docs/metrics) sont activées, FrankenPHP expose les métriques suivantes :
|
||||
|
||||
- `frankenphp_total_threads` : Le nombre total de threads PHP.
|
||||
- `frankenphp_busy_threads` : Le nombre de threads PHP en cours de traitement d'une requête (les workers en cours d'exécution consomment toujours un thread).
|
||||
- `frankenphp_queue_depth` : Le nombre de requêtes régulières en file d'attente
|
||||
- `frankenphp_total_workers{worker=« [nom_du_worker] »}` : Le nombre total de workers.
|
||||
- `frankenphp_busy_workers{worker=« [nom_du_worker] »}` : Le nombre de workers qui traitent actuellement une requête.
|
||||
- `frankenphp_worker_request_time{worker=« [nom_du_worker] »}` : Le temps passé à traiter les requêtes par tous les workers.
|
||||
- `frankenphp_worker_request_count{worker=« [nom_du_worker] »}` : Le nombre de requêtes traitées par tous les workers.
|
||||
- `frankenphp_ready_workers{worker=« [nom_du_worker] »}` : Le nombre de workers qui ont appelé `frankenphp_handle_request` au moins une fois.
|
||||
- `frankenphp_worker_crashes{worker=« [nom_du_worker] »}` : Le nombre de fois où un worker s'est arrêté de manière inattendue.
|
||||
- `frankenphp_worker_restarts{worker=« [nom_du_worker] »}` : Le nombre de fois où un worker a été délibérément redémarré.
|
||||
- `frankenphp_worker_queue_depth{worker=« [nom_du_worker] »}` : Le nombre de requêtes en file d'attente.
|
||||
|
||||
Pour les métriques de worker, le placeholder `[nom_du_worker]` est remplacé par le nom du worker dans le Caddyfile, sinon le chemin absolu du fichier du worker sera utilisé.
|
||||
157
docs/fr/performance.md
Normal file
157
docs/fr/performance.md
Normal file
@@ -0,0 +1,157 @@
|
||||
# Performance
|
||||
|
||||
Par défaut, FrankenPHP essaie d'offrir un bon compromis entre performance et facilité d'utilisation.
|
||||
Cependant, il est possible d'améliorer considérablement les performances en utilisant une configuration appropriée.
|
||||
|
||||
## Nombre de threads et de workers
|
||||
|
||||
Par défaut, FrankenPHP démarre deux fois plus de threads et de workers (en mode worker) que le nombre de CPU disponibles.
|
||||
|
||||
Les valeurs appropriées dépendent fortement de la manière dont votre application est écrite, de ce qu'elle fait et de votre matériel.
|
||||
Nous recommandons vivement de modifier ces valeurs.
|
||||
|
||||
Pour trouver les bonnes valeurs, il est souhaitable d'effectuer des tests de charge simulant le trafic réel.
|
||||
[k6](https://k6.io) et [Gatling](https://gatling.io) sont de bons outils pour cela.
|
||||
|
||||
Pour configurer le nombre de threads, utilisez l'option `num_threads` des directives `php_server` et `php`.
|
||||
Pour changer le nombre de travailleurs, utilisez l'option `num` de la section `worker` de la directive `frankenphp`.
|
||||
|
||||
### `max_threads`
|
||||
|
||||
Bien qu'il soit toujours préférable de savoir exactement à quoi ressemblera votre trafic, les applications réelles
|
||||
ont tendance à être plus imprévisibles. Le paramètre `max_threads` permet à FrankenPHP de créer automatiquement des threads supplémentaires au moment de l'exécution, jusqu'à la limite spécifiée.
|
||||
`max_threads` peut vous aider à déterminer le nombre de threads dont vous avez besoin pour gérer votre trafic et peut rendre le serveur plus résistant aux pics de latence.
|
||||
Si elle est fixée à `auto`, la limite sera estimée en fonction de la valeur de `memory_limit` dans votre `php.ini`. Si ce n'est pas possible,
|
||||
`auto` prendra par défaut 2x `num_threads`. Gardez à l'esprit que `auto` peut fortement sous-estimer le nombre de threads nécessaires.
|
||||
`max_threads` est similaire à [pm.max_children](https://www.php.net/manual/en/install.fpm.configuration.php#pm.max-children) de PHP FPM. La principale différence est que FrankenPHP utilise des threads au lieu de
|
||||
processus et les délègue automatiquement à différents scripts de travail et au `mode classique` selon les besoins.
|
||||
|
||||
## Mode worker
|
||||
|
||||
Activer [le mode worker](worker.md) améliore considérablement les performances,
|
||||
mais votre application doit être adaptée pour être compatible avec ce mode :
|
||||
vous devez créer un script worker et vous assurer que l'application n'a pas de fuite de mémoire.
|
||||
|
||||
## Ne pas utiliser musl
|
||||
|
||||
Les binaires statiques que nous fournissons, ainsi que la variante Alpine Linux des images Docker officielles, utilisent [la bibliothèque musl](https://musl.libc.org).
|
||||
|
||||
PHP est connu pour être [significativement plus lent](https://gitlab.alpinelinux.org/alpine/aports/-/issues/14381) lorsqu'il utilise cette bibliothèque C alternative au lieu de la bibliothèque GNU traditionnelle,
|
||||
surtout lorsqu'il est compilé en mode ZTS (_thread-safe_), ce qui est nécessaire pour FrankenPHP.
|
||||
|
||||
En outre, [certains bogues ne se produisent que lors de l'utilisation de musl](https://github.com/php/php-src/issues?q=sort%3Aupdated-desc+is%3Aissue+is%3Aopen+label%3ABug+musl).
|
||||
|
||||
Dans les environnements de production, nous recommandons fortement d'utiliser la glibc.
|
||||
|
||||
Cela peut être réalisé en utilisant les images Docker Debian (par défaut) et [en compilant FrankenPHP à partir des sources](compile.md).
|
||||
|
||||
Alternativement, nous fournissons des binaires statiques compilés avec [l'allocateur mimalloc](https://github.com/microsoft/mimalloc), ce qui rend FrankenPHP+musl plus rapide (mais toujours plus lent que FrankenPHP+glibc).
|
||||
|
||||
## Configuration du runtime Go
|
||||
|
||||
FrankenPHP est écrit en Go.
|
||||
|
||||
En général, le runtime Go ne nécessite pas de configuration particulière, mais dans certaines circonstances,
|
||||
une configuration spécifique améliore les performances.
|
||||
|
||||
Vous voudrez probablement mettre la variable d'environnement `GODEBUG` à `cgocheck=0` (la valeur par défaut dans les images Docker de FrankenPHP).
|
||||
|
||||
Si vous exécutez FrankenPHP dans des conteneurs (Docker, Kubernetes, LXC...) et que vous limitez la mémoire disponible pour les conteneurs,
|
||||
mettez la variable d'environnement `GOMEMLIMIT` à la quantité de mémoire disponible.
|
||||
|
||||
Pour plus de détails, [la page de documentation Go dédiée à ce sujet](https://pkg.go.dev/runtime#hdr-Environment_Variables) est à lire absolument pour tirer le meilleur parti du runtime.
|
||||
|
||||
## `file_server`
|
||||
|
||||
Par défaut, la directive `php_server` met automatiquement en place un serveur de fichiers
|
||||
pour servir les fichiers statiques (assets) stockés dans le répertoire racine.
|
||||
|
||||
Cette fonctionnalité est pratique, mais a un coût.
|
||||
Pour la désactiver, utilisez la configuration suivante :
|
||||
|
||||
```caddyfile
|
||||
php_server {
|
||||
file_server off
|
||||
}
|
||||
```
|
||||
|
||||
## `try_files`
|
||||
|
||||
En plus des fichiers statiques et des fichiers PHP, `php_server` essaiera aussi de servir les fichiers d'index
|
||||
et d'index de répertoire de votre application (`/path/` -> `/path/index.php`). Si vous n'avez pas besoin des index de répertoires,
|
||||
vous pouvez les désactiver en définissant explicitement `try_files` comme ceci :
|
||||
|
||||
```caddyfile
|
||||
php_server {
|
||||
try_files {path} index.php
|
||||
root /root/to/your/app # l'ajout explicite de la racine ici permet une meilleure mise en cache
|
||||
}
|
||||
```
|
||||
|
||||
Cela permet de réduire considérablement le nombre d'opérations inutiles sur les fichiers.
|
||||
|
||||
Une approche alternative avec 0 opérations inutiles sur le système de fichiers serait d'utiliser la directive `php`
|
||||
et de diviser les fichiers de PHP par chemin. Cette approche fonctionne bien si votre application entière est servie par un seul fichier d'entrée.
|
||||
Un exemple de [configuration](config.md#configuration-du-caddyfile) qui sert des fichiers statiques derrière un dossier `/assets` pourrait ressembler à ceci :
|
||||
|
||||
```caddyfile
|
||||
route {
|
||||
@assets {
|
||||
path /assets/*
|
||||
}
|
||||
|
||||
# tout ce qui se trouve derrière /assets est géré par le serveur de fichiers
|
||||
file_server @assets {
|
||||
root /root/to/your/app
|
||||
}
|
||||
|
||||
# tout ce qui n'est pas dans /assets est géré par votre index ou votre fichier PHP worker
|
||||
rewrite index.php
|
||||
php {
|
||||
root /root/to/your/app # l'ajout explicite de la racine ici permet une meilleure mise en cache
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## _Placeholders_
|
||||
|
||||
Vous pouvez utiliser des [_placeholders_](https://caddyserver.com/docs/conventions#placeholders) dans les directives `root` et `env`.
|
||||
Cependant, cela empêche la mise en cache de ces valeurs et a un coût important en termes de performances.
|
||||
|
||||
Si possible, évitez les _placeholders_ dans ces directives.
|
||||
|
||||
## `resolve_root_symlink`
|
||||
|
||||
Par défaut, si le _document root_ est un lien symbolique, il est automatiquement résolu par FrankenPHP (c'est nécessaire pour le bon fonctionnement de PHP).
|
||||
Si la racine du document n'est pas un lien symbolique, vous pouvez désactiver cette fonctionnalité.
|
||||
|
||||
```caddyfile
|
||||
php_server {
|
||||
resolve_root_symlink false
|
||||
}
|
||||
```
|
||||
|
||||
Cela améliorera les performances si la directive `root` contient des [_placeholders_](https://caddyserver.com/docs/conventions#placeholders).
|
||||
Le gain sera négligeable dans les autres cas.
|
||||
|
||||
## Journaux
|
||||
|
||||
La journalisation est évidemment très utile, mais, par définition, elle nécessite des opérations d'_I/O_ et des allocations de mémoire,
|
||||
ce qui réduit considérablement les performances.
|
||||
Assurez-vous de [définir le niveau de journalisation](https://caddyserver.com/docs/caddyfile/options#log) correctement,
|
||||
et de ne journaliser que ce qui est nécessaire.
|
||||
|
||||
## Performances de PHP
|
||||
|
||||
FrankenPHP utilise l'interpréteur PHP officiel.
|
||||
Toutes les optimisations de performances habituelles liées à PHP s'appliquent à FrankenPHP.
|
||||
|
||||
En particulier :
|
||||
|
||||
- vérifiez que [OPcache](https://www.php.net/manual/en/book.opcache.php) est installé, activé et correctement configuré
|
||||
- activez [les optimisations de l'autoloader de Composer](https://getcomposer.org/doc/articles/autoloader-optimization.md)
|
||||
- assurez-vous que le cache `realpath` est suffisamment grand pour les besoins de votre application
|
||||
- utilisez le [pré-chargement](https://www.php.net/manual/en/opcache.preloading.php)
|
||||
|
||||
Pour plus de détails, lisez [l'entrée de la documentation dédiée de Symfony](https://symfony.com/doc/current/performance.html)
|
||||
(la plupart des conseils sont utiles même si vous n'utilisez pas Symfony).
|
||||
139
docs/fr/production.md
Normal file
139
docs/fr/production.md
Normal file
@@ -0,0 +1,139 @@
|
||||
# Déploiement en Production
|
||||
|
||||
Dans ce tutoriel, nous apprendrons comment déployer une application PHP sur un serveur unique en utilisant Docker Compose.
|
||||
|
||||
Si vous utilisez Symfony, lisez plutôt la page de documentation "[Déployer en production](https://github.com/dunglas/symfony-docker/blob/main/docs/production.md)" du projet Symfony Docker (qui utilise FrankenPHP).
|
||||
|
||||
Si vous utilisez API Platform (qui utilise également FrankenPHP), référez-vous à [la documentation de déploiement du framework](https://api-platform.com/docs/deployment/).
|
||||
|
||||
## Préparer votre application
|
||||
|
||||
Tout d'abord, créez un `Dockerfile` dans le répertoire racine de votre projet PHP :
|
||||
|
||||
```dockerfile
|
||||
FROM dunglas/frankenphp
|
||||
|
||||
# Assurez-vous de remplacer "your-domain-name.example.com" par votre nom de domaine
|
||||
ENV SERVER_NAME=your-domain-name.example.com
|
||||
# Si vous souhaitez désactiver HTTPS, utilisez cette valeur à la place :
|
||||
#ENV SERVER_NAME=:80
|
||||
|
||||
# Activer les paramètres de production de PHP
|
||||
RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini"
|
||||
|
||||
# Copiez les fichiers PHP de votre projet dans le répertoire public
|
||||
COPY . /app/public
|
||||
# Si vous utilisez Symfony ou Laravel, vous devez copier l'intégralité du projet à la place :
|
||||
#COPY . /app
|
||||
```
|
||||
|
||||
Consultez "[Construire une image Docker personnalisée](docker.md)" pour plus de détails et d'options,
|
||||
et pour apprendre à personnaliser la configuration, installer des extensions PHP et des modules Caddy.
|
||||
|
||||
Si votre projet utilise Composer, assurez-vous de l'inclure dans l'image Docker et d'installer vos dépendances.
|
||||
|
||||
Ensuite, ajoutez un fichier `compose.yaml` :
|
||||
|
||||
```yaml
|
||||
services:
|
||||
php:
|
||||
image: dunglas/frankenphp
|
||||
restart: always
|
||||
ports:
|
||||
- "80:80" # HTTP
|
||||
- "443:443" # HTTPS
|
||||
- "443:443/udp" # HTTP/3
|
||||
volumes:
|
||||
- caddy_data:/data
|
||||
- caddy_config:/config
|
||||
|
||||
# Volumes nécessaires pour les certificats et la configuration de Caddy
|
||||
volumes:
|
||||
caddy_data:
|
||||
caddy_config:
|
||||
```
|
||||
|
||||
> [!NOTE]
|
||||
>
|
||||
> Les exemples précédents sont destinés à une utilisation en production.
|
||||
> En développement, vous pourriez vouloir utiliser un volume, une configuration PHP différente et une valeur différente pour la variable d'environnement `SERVER_NAME`.
|
||||
>
|
||||
> Jetez un œil au projet [Symfony Docker](https://github.com/dunglas/symfony-docker)
|
||||
> (qui utilise FrankenPHP) pour un exemple plus avancé utilisant des images multi-étapes,
|
||||
> Composer, des extensions PHP supplémentaires, etc.
|
||||
|
||||
Pour finir, si vous utilisez Git, commitez ces fichiers et poussez-les.
|
||||
|
||||
## Préparer un serveur
|
||||
|
||||
Pour déployer votre application en production, vous avez besoin d'un serveur.
|
||||
Dans ce tutoriel, nous utiliserons une machine virtuelle fournie par DigitalOcean, mais n'importe quel serveur Linux peut fonctionner.
|
||||
Si vous avez déjà un serveur Linux avec Docker installé, vous pouvez passer directement à [la section suivante](#configurer-un-nom-de-domaine).
|
||||
|
||||
Sinon, utilisez [ce lien affilié](https://m.do.co/c/5d8aabe3ab80) pour obtenir 200$ de crédit gratuit, créez un compte, puis cliquez sur "Créer un Droplet".
|
||||
Ensuite, cliquez sur l'onglet "Marketplace" sous la section "Choisir une image" et recherchez l'application nommée "Docker".
|
||||
Cela provisionnera un serveur Ubuntu avec les dernières versions de Docker et Docker Compose déjà installées !
|
||||
|
||||
Pour des fins de test, les plans les moins chers seront suffisants.
|
||||
Pour une utilisation en production réelle, vous voudrez probablement choisir un plan dans la section "General Usage" pour répondre à vos besoins.
|
||||
|
||||

|
||||
|
||||
Vous pouvez conserver les paramètres par défaut pour les autres paramètres, ou les ajuster selon vos besoins.
|
||||
N'oubliez pas d'ajouter votre clé SSH ou de créer un mot de passe puis appuyez sur le bouton "Finalize and create".
|
||||
|
||||
Ensuite, attendez quelques secondes pendant que votre Droplet est en cours de provisionnement.
|
||||
Lorsque votre Droplet est prêt, utilisez SSH pour vous connecter :
|
||||
|
||||
```console
|
||||
ssh root@<droplet-ip>
|
||||
```
|
||||
|
||||
## Configurer un nom de domaine
|
||||
|
||||
Dans la plupart des cas, vous souhaiterez associer un nom de domaine à votre site.
|
||||
Si vous ne possédez pas encore de nom de domaine, vous devrez en acheter un via un registraire.
|
||||
|
||||
Ensuite, créez un enregistrement DNS de type `A` pour votre nom de domaine pointant vers l'adresse IP de votre serveur :
|
||||
|
||||
```dns
|
||||
your-domain-name.example.com. IN A 207.154.233.113
|
||||
```
|
||||
|
||||
Exemple avec le service DigitalOcean Domains ("Networking" > "Domains") :
|
||||
|
||||

|
||||
|
||||
> [!NOTE]
|
||||
>
|
||||
> Let's Encrypt, le service utilisé par défaut par FrankenPHP pour générer automatiquement un certificat TLS, ne prend pas en charge l'utilisation d'adresses IP nues. L'utilisation d'un nom de domaine est obligatoire pour utiliser Let's Encrypt.
|
||||
|
||||
## Déploiement
|
||||
|
||||
Copiez votre projet sur le serveur en utilisant `git clone`, `scp`, ou tout autre outil qui pourrait répondre à votre besoin.
|
||||
Si vous utilisez GitHub, vous voudrez peut-être utiliser [une clef de déploiement](https://docs.github.com/en/free-pro-team@latest/developers/overview/managing-deploy-keys#deploy-keys).
|
||||
Les clés de déploiement sont également [prises en charge par GitLab](https://docs.gitlab.com/ee/user/project/deploy_keys/).
|
||||
|
||||
Exemple avec Git :
|
||||
|
||||
```console
|
||||
git clone git@github.com:<username>/<project-name>.git
|
||||
```
|
||||
|
||||
Accédez au répertoire contenant votre projet (`<project-name>`), et démarrez l'application en mode production :
|
||||
|
||||
```console
|
||||
docker compose up -d --wait
|
||||
```
|
||||
|
||||
Votre serveur est opérationnel, et un certificat HTTPS a été automatiquement généré pour vous.
|
||||
Rendez-vous sur `https://your-domain-name.example.com` !
|
||||
|
||||
> [!CAUTION]
|
||||
>
|
||||
> Docker peut avoir une couche de cache, assurez-vous d'avoir la bonne version de build pour chaque déploiement ou reconstruisez votre projet avec l'option `--no-cache` pour éviter les problèmes de cache.
|
||||
|
||||
## Déploiement sur Plusieurs Nœuds
|
||||
|
||||
Si vous souhaitez déployer votre application sur un cluster de machines, vous pouvez utiliser [Docker Swarm](https://docs.docker.com/engine/swarm/stack-deploy/), qui est compatible avec les fichiers Compose fournis.
|
||||
Pour un déploiement sur Kubernetes, jetez un œil au [Helm chart fourni avec API Platform](https://api-platform.com/docs/deployment/kubernetes/), qui utilise FrankenPHP.
|
||||
161
docs/fr/static.md
Normal file
161
docs/fr/static.md
Normal file
@@ -0,0 +1,161 @@
|
||||
# Créer un binaire statique
|
||||
|
||||
Au lieu d'utiliser une installation locale de la bibliothèque PHP, il est possible de créer un build statique de FrankenPHP grâce à l'excellent projet [static-php-cli](https://github.com/crazywhalecc/static-php-cli) (malgré son nom, ce projet prend en charge tous les SAPIs, pas seulement CLI).
|
||||
|
||||
Avec cette méthode, un binaire portable unique contiendra l'interpréteur PHP, le serveur web Caddy et FrankenPHP !
|
||||
|
||||
Les exécutables natifs entièrement statiques ne nécessitent aucune dépendance et peuvent même être exécutés sur une [image Docker `scratch`](https://docs.docker.com/build/building/base-images/#create-a-minimal-base-image-using-scratch).
|
||||
Cependant, ils ne peuvent pas charger les extensions dynamiques de PHP (comme Xdebug) et ont quelques limitations parce qu'ils utilisent la librairie musl.
|
||||
|
||||
La plupart des binaires statiques ne nécessitent que la `glibc` et peuvent charger des extensions dynamiques.
|
||||
|
||||
Lorsque c'est possible, nous recommandons d'utiliser des binaires statiques basés sur la glibc.
|
||||
|
||||
FrankenPHP permet également [d'embarquer l'application PHP dans le binaire statique](embed.md).
|
||||
|
||||
## Linux
|
||||
|
||||
Nous fournissons des images Docker pour créer des binaires statiques pour Linux :
|
||||
|
||||
### Build entièrement statique, basé sur musl
|
||||
|
||||
Pour un binaire entièrement statique qui fonctionne sur n'importe quelle distribution Linux sans dépendances,
|
||||
mais qui ne prend pas en charge le chargement dynamique des extensions :
|
||||
|
||||
```console
|
||||
docker buildx bake --load static-builder-musl
|
||||
docker cp $(docker create --name static-builder-musl dunglas/frankenphp:static-builder-musl):/go/src/app/dist/frankenphp-linux-$(uname -m) frankenphp ; docker rm static-builder-musl
|
||||
```
|
||||
|
||||
Pour améliorer les performances dans les scénarios fortement concurrents, envisagez d'utiliser l'allocateur [mimalloc](https://github.com/microsoft/mimalloc).
|
||||
|
||||
```console
|
||||
docker buildx bake --load --set static-builder-musl.args.MIMALLOC=1 static-builder-musl
|
||||
```
|
||||
|
||||
### Construction principalement statique (avec prise en charge des extensions dynamiques), basé sur la glibc
|
||||
|
||||
Pour un binaire qui supporte le chargement dynamique des extensions PHP tout en ayant les extensions sélectionnées compilées statiquement :
|
||||
|
||||
```console
|
||||
docker buildx bake --load static-builder-gnu
|
||||
docker cp $(docker create --name static-builder-gnu dunglas/frankenphp:static-builder-gnu):/go/src/app/dist/frankenphp-linux-$(uname -m) frankenphp ; docker rm static-builder-gnu
|
||||
```
|
||||
|
||||
Ce binaire supporte toutes les versions 2.17 et supérieures de la glibc mais ne fonctionne pas sur les systèmes basés sur musl (comme Alpine Linux).
|
||||
|
||||
Le binaire résultant, principalement statique (à l'exception de `glibc`), est nommé `frankenphp` et est disponible dans le répertoire courant.
|
||||
|
||||
Si vous voulez construire le binaire statique sans Docker, jetez un coup d'œil aux instructions pour macOS, qui fonctionnent aussi pour Linux.
|
||||
|
||||
### Extensions personnalisées
|
||||
|
||||
Par défaut, la plupart des extensions PHP populaires sont compilées.
|
||||
|
||||
Pour réduire la taille du binaire et diminuer la surface d'attaque, vous pouvez choisir la liste des extensions à construire en utilisant l'argument Docker `PHP_EXTENSIONS`.
|
||||
|
||||
Par exemple, exécutez la commande suivante pour ne construire que l'extension `opcache` :
|
||||
|
||||
```console
|
||||
docker buildx bake --load --set static-builder-musl.args.PHP_EXTENSIONS=opcache,pdo_sqlite static-builder-musl
|
||||
# ...
|
||||
```
|
||||
|
||||
Pour ajouter des bibliothèques permettant des fonctionnalités supplémentaires aux extensions que vous avez activées, vous pouvez utiliser l'argument Docker `PHP_EXTENSION_LIBS` :
|
||||
|
||||
```console
|
||||
docker buildx bake \
|
||||
--load \
|
||||
--set static-builder-musl.args.PHP_EXTENSIONS=gd \
|
||||
--set static-builder-musl.args.PHP_EXTENSION_LIBS=libjpeg,libwebp \
|
||||
static-builder-musl
|
||||
```
|
||||
|
||||
### Modules supplémentaires de Caddy
|
||||
|
||||
Pour ajouter des modules Caddy supplémentaires ou passer d'autres arguments à [xcaddy](https://github.com/caddyserver/xcaddy), utilisez l'argument Docker `XCADDY_ARGS` :
|
||||
|
||||
```console
|
||||
docker buildx bake \
|
||||
--load \
|
||||
--set static-builder-musl.args.XCADDY_ARGS="--with github.com/darkweak/souin/plugins/caddy --with github.com/dunglas/caddy-cbrotli --with github.com/dunglas/mercure/caddy --with github.com/dunglas/vulcain/caddy" \
|
||||
static-builder-musl
|
||||
```
|
||||
|
||||
Dans cet exemple, nous ajoutons le module de cache HTTP [Souin](https://souin.io) pour Caddy ainsi que les modules [cbrotli](https://github.com/dunglas/caddy-cbrotli), [Mercure](https://mercure.rocks) et [Vulcain](https://vulcain.rocks).
|
||||
|
||||
> [!TIP]
|
||||
>
|
||||
> Les modules cbrotli, Mercure et Vulcain sont inclus par défaut si `XCADDY_ARGS` est vide ou n'est pas défini.
|
||||
> Si vous personnalisez la valeur de `XCADDY_ARGS`, vous devez les inclure explicitement si vous voulez qu'ils soient inclus.
|
||||
|
||||
Voir aussi comment [personnaliser la construction](#personnalisation-de-la-construction)
|
||||
|
||||
### Jeton GitHub
|
||||
|
||||
Si vous atteignez la limite de taux d'appels de l'API GitHub, définissez un jeton d'accès personnel GitHub dans une variable d'environnement nommée `GITHUB_TOKEN` :
|
||||
|
||||
```console
|
||||
GITHUB_TOKEN="xxx" docker --load buildx bake static-builder-musl
|
||||
# ...
|
||||
```
|
||||
|
||||
## macOS
|
||||
|
||||
Exécutez le script suivant pour créer un binaire statique pour macOS (vous devez avoir [Homebrew](https://brew.sh/) d'installé) :
|
||||
|
||||
```console
|
||||
git clone https://github.com/dunglas/frankenphp
|
||||
cd frankenphp
|
||||
./build-static.sh
|
||||
```
|
||||
|
||||
Note : ce script fonctionne également sur Linux (et probablement sur d'autres Unix) et est utilisé en interne par le builder statique basé sur Docker que nous fournissons.
|
||||
|
||||
## Personnalisation de la construction
|
||||
|
||||
Les variables d'environnement suivantes peuvent être transmises à `docker build` et au script `build-static.sh` pour personnaliser la construction statique :
|
||||
|
||||
- `FRANKENPHP_VERSION` : la version de FrankenPHP à utiliser
|
||||
- `PHP_VERSION` : la version de PHP à utiliser
|
||||
- `PHP_EXTENSIONS` : les extensions PHP à construire ([liste des extensions prises en charge](https://static-php.dev/en/guide/extensions.html))
|
||||
- `PHP_EXTENSION_LIBS` : bibliothèques supplémentaires à construire qui ajoutent des fonctionnalités aux extensions
|
||||
- `XCADDY_ARGS` : arguments à passer à [xcaddy](https://github.com/caddyserver/xcaddy), par exemple pour ajouter des modules Caddy supplémentaires
|
||||
- `EMBED` : chemin de l'application PHP à intégrer dans le binaire
|
||||
- `CLEAN` : lorsque défini, `libphp` et toutes ses dépendances sont construites à partir de zéro (pas de cache)
|
||||
- `DEBUG_SYMBOLS` : lorsque défini, les symboles de débogage ne seront pas supprimés et seront ajoutés dans le binaire
|
||||
- `NO_COMPRESS`: ne pas compresser le binaire avec UPX
|
||||
- `MIMALLOC`: (expérimental, Linux seulement) remplace l'allocateur mallocng de musl par [mimalloc](https://github.com/microsoft/mimalloc) pour des performances améliorées
|
||||
- `RELEASE` : (uniquement pour les mainteneurs) lorsque défini, le binaire résultant sera uploadé sur GitHub
|
||||
|
||||
## Extensions
|
||||
|
||||
Avec la glibc ou les binaires basés sur macOS, vous pouvez charger des extensions PHP dynamiquement. Cependant, ces extensions devront être compilées avec le support ZTS.
|
||||
Comme la plupart des gestionnaires de paquets ne proposent pas de versions ZTS de leurs extensions, vous devrez les compiler vous-même.
|
||||
|
||||
Pour cela, vous pouvez construire et exécuter le conteneur Docker `static-builder-gnu`, vous y connecter à distance et compiler les extensions avec `./configure --with-php-config=/go/src/app/dist/static-php-cli/buildroot/bin/php-config`.
|
||||
|
||||
Exemple d'étapes pour [l'extension Xdebug](https://xdebug.org) :
|
||||
|
||||
```console
|
||||
docker build -t gnu-ext -f static-builder-gnu.Dockerfile --build-arg FRANKENPHP_VERSION=1.0 .
|
||||
docker create --name static-builder-gnu -it gnu-ext /bin/sh
|
||||
docker start static-builder-gnu
|
||||
docker exec -it static-builder-gnu /bin/sh
|
||||
cd /go/src/app/dist/static-php-cli/buildroot/bin
|
||||
git clone https://github.com/xdebug/xdebug.git && cd xdebug
|
||||
source scl_source enable devtoolset-10
|
||||
../phpize
|
||||
./configure --with-php-config=/go/src/app/dist/static-php-cli/buildroot/bin/php-config
|
||||
make
|
||||
exit
|
||||
docker cp static-builder-gnu:/go/src/app/dist/static-php-cli/buildroot/bin/xdebug/modules/xdebug.so xdebug-zts.so
|
||||
docker cp static-builder-gnu:/go/src/app/dist/frankenphp-linux-$(uname -m) ./frankenphp
|
||||
docker stop static-builder-gnu
|
||||
docker rm static-builder-gnu
|
||||
docker rmi gnu-ext
|
||||
```
|
||||
|
||||
Cela aura créé `frankenphp` et `xdebug-zts.so` dans le répertoire courant.
|
||||
Si vous déplacez `xdebug-zts.so` dans votre répertoire d'extension, ajoutez `zend_extension=xdebug-zts.so` à votre php.ini
|
||||
et lancez FrankenPHP, il chargera Xdebug.
|
||||
174
docs/fr/worker.md
Normal file
174
docs/fr/worker.md
Normal file
@@ -0,0 +1,174 @@
|
||||
# Utilisation des workers FrankenPHP
|
||||
|
||||
Démarrez votre application une fois et gardez-la en mémoire.
|
||||
FrankenPHP traitera les requêtes entrantes en quelques millisecondes.
|
||||
|
||||
## Démarrage des scripts workers
|
||||
|
||||
### Docker
|
||||
|
||||
Définissez la valeur de la variable d'environnement `FRANKENPHP_CONFIG` à `worker /path/to/your/worker/script.php` :
|
||||
|
||||
```console
|
||||
docker run \
|
||||
-e FRANKENPHP_CONFIG="worker /app/path/to/your/worker/script.php" \
|
||||
-v $PWD:/app \
|
||||
-p 80:80 -p 443:443 -p 443:443/udp \
|
||||
dunglas/frankenphp
|
||||
```
|
||||
|
||||
### Binaire autonome
|
||||
|
||||
Utilisez l'option --worker de la commande php-server pour servir le contenu du répertoire courant en utilisant un worker :
|
||||
|
||||
```console
|
||||
frankenphp php-server --worker /path/to/your/worker/script.php
|
||||
```
|
||||
|
||||
Si votre application PHP est [intégrée dans le binaire](embed.md), vous pouvez également ajouter un `Caddyfile` personnalisé dans le répertoire racine de l'application.
|
||||
Il sera utilisé automatiquement.
|
||||
|
||||
Il est également possible de [redémarrer le worker en cas de changement de fichier](config.md#surveillance-des-modifications-de-fichier) avec l'option `--watch`.
|
||||
La commande suivante déclenchera un redémarrage si un fichier se terminant par `.php` dans le répertoire `/path/to/your/app/` ou ses sous-répertoires est modifié :
|
||||
|
||||
```console
|
||||
frankenphp php-server --worker /path/to/your/worker/script.php --watch="/path/to/your/app/**/*.php"
|
||||
```
|
||||
|
||||
## Runtime Symfony
|
||||
|
||||
Le mode worker de FrankenPHP est pris en charge par le [Composant Runtime de Symfony](https://symfony.com/doc/current/components/runtime.html).
|
||||
Pour démarrer une application Symfony dans un worker, installez le package FrankenPHP de [PHP Runtime](https://github.com/php-runtime/runtime) :
|
||||
|
||||
```console
|
||||
composer require runtime/frankenphp-symfony
|
||||
```
|
||||
|
||||
Démarrez votre serveur d'application en définissant la variable d'environnement `APP_RUNTIME` pour utiliser le Runtime Symfony de FrankenPHP :
|
||||
|
||||
```console
|
||||
docker run \
|
||||
-e FRANKENPHP_CONFIG="worker ./public/index.php" \
|
||||
-e APP_RUNTIME=Runtime\\FrankenPhpSymfony\\Runtime \
|
||||
-v $PWD:/app \
|
||||
-p 80:80 -p 443:443 -p 443:443/udp \
|
||||
dunglas/frankenphp
|
||||
```
|
||||
|
||||
## Laravel Octane
|
||||
|
||||
Voir [la documentation dédiée](laravel.md#laravel-octane).
|
||||
|
||||
## Applications Personnalisées
|
||||
|
||||
L'exemple suivant montre comment créer votre propre script worker sans dépendre d'une bibliothèque tierce :
|
||||
|
||||
```php
|
||||
<?php
|
||||
// public/index.php
|
||||
|
||||
// Empêcher la terminaison du script worker lorsqu'une connexion client est interrompue
|
||||
ignore_user_abort(true);
|
||||
|
||||
// Démarrer votre application
|
||||
require __DIR__.'/vendor/autoload.php';
|
||||
|
||||
$myApp = new \App\Kernel();
|
||||
$myApp->boot();
|
||||
|
||||
// En dehors de la boucle pour de meilleures performances (moins de travail effectué)
|
||||
$handler = static function () use ($myApp) {
|
||||
// Appelé lorsqu'une requête est reçue,
|
||||
// les superglobales, php://input, etc., sont réinitialisés
|
||||
echo $myApp->handle($_GET, $_POST, $_COOKIE, $_FILES, $_SERVER);
|
||||
};
|
||||
|
||||
$maxRequests = (int)($_SERVER['MAX_REQUESTS'] ?? 0);
|
||||
for ($nbRequests = 0; !$maxRequests || $nbRequests < $maxRequests; ++$nbRequests) {
|
||||
$keepRunning = \frankenphp_handle_request($handler);
|
||||
|
||||
// Faire quelque chose après l'envoi de la réponse HTTP
|
||||
$myApp->terminate();
|
||||
|
||||
// Exécuter le ramasse-miettes pour réduire les chances qu'il soit déclenché au milieu de la génération d'une page
|
||||
gc_collect_cycles();
|
||||
|
||||
if (!$keepRunning) break;
|
||||
}
|
||||
|
||||
// Nettoyage
|
||||
$myApp->shutdown();
|
||||
```
|
||||
|
||||
Ensuite, démarrez votre application et utilisez la variable d'environnement `FRANKENPHP_CONFIG` pour configurer votre worker :
|
||||
|
||||
```console
|
||||
docker run \
|
||||
-e FRANKENPHP_CONFIG="worker ./public/index.php" \
|
||||
-v $PWD:/app \
|
||||
-p 80:80 -p 443:443 -p 443:443/udp \
|
||||
dunglas/frankenphp
|
||||
```
|
||||
|
||||
Par défaut, 2 workers par CPU sont démarrés.
|
||||
Vous pouvez également configurer le nombre de workers à démarrer :
|
||||
|
||||
```console
|
||||
docker run \
|
||||
-e FRANKENPHP_CONFIG="worker ./public/index.php 42" \
|
||||
-v $PWD:/app \
|
||||
-p 80:80 -p 443:443 -p 443:443/udp \
|
||||
dunglas/frankenphp
|
||||
```
|
||||
|
||||
### Redémarrer le worker après un certain nombre de requêtes
|
||||
|
||||
Comme PHP n'a pas été initialement conçu pour des processus de longue durée, de nombreuses bibliothèques et codes anciens présentent encore des fuites de mémoire.
|
||||
Une solution pour utiliser ce type de code en mode worker est de redémarrer le script worker après avoir traité un certain nombre de requêtes :
|
||||
|
||||
Le code du worker précédent permet de configurer un nombre maximal de requêtes à traiter en définissant une variable d'environnement nommée `MAX_REQUESTS`.
|
||||
|
||||
### Redémarrer les workers manuellement
|
||||
|
||||
Bien qu'il soit possible de redémarrer les workers [en cas de changement de fichier](config.md#surveillance-des-modifications-de-fichier),
|
||||
il est également possible de redémarrer tous les workers de manière élégante via l'[API Admin de Caddy](https://caddyserver.com/docs/api).
|
||||
Si l'administration est activée dans votre [Caddyfile](config.md#configuration-du-caddyfile), vous pouvez envoyer un ping
|
||||
à l'endpoint de redémarrage avec une simple requête POST comme celle-ci :
|
||||
|
||||
```console
|
||||
curl -X POST http://localhost:2019/frankenphp/workers/restart
|
||||
```
|
||||
|
||||
> [!NOTE]
|
||||
>
|
||||
> C'est une fonctionnalité expérimentale et peut être modifiée ou supprimée dans le futur.
|
||||
|
||||
### Worker Failures
|
||||
|
||||
Si un script de worker se plante avec un code de sortie non nul, FrankenPHP le redémarre avec une stratégie de backoff exponentielle.
|
||||
Si le script worker reste en place plus longtemps que le dernier backoff \* 2, FrankenPHP ne pénalisera pas le script et le redémarrera à nouveau.
|
||||
Toutefois, si le script de worker continue d'échouer avec un code de sortie non nul dans un court laps de temps
|
||||
(par exemple, une faute de frappe dans un script), FrankenPHP plantera avec l'erreur : `too many consecutive failures` (trop d'échecs consécutifs).
|
||||
|
||||
## Comportement des superglobales
|
||||
|
||||
[Les superglobales PHP](https://www.php.net/manual/fr/language.variables.superglobals.php) (`$_SERVER`, `$_ENV`, `$_GET`...)
|
||||
se comportent comme suit :
|
||||
|
||||
- avant le premier appel à `frankenphp_handle_request()`, les superglobales contiennent des valeurs liées au script worker lui-même
|
||||
- pendant et après l'appel à `frankenphp_handle_request()`, les superglobales contiennent des valeurs générées à partir de la requête HTTP traitée, chaque appel à `frankenphp_handle_request()` change les valeurs des superglobales
|
||||
|
||||
Pour accéder aux superglobales du script worker à l'intérieur de la fonction de rappel, vous devez les copier et importer la copie dans le scope de la fonction :
|
||||
|
||||
```php
|
||||
<?php
|
||||
// Copier la superglobale $_SERVER du worker avant le premier appel à frankenphp_handle_request()
|
||||
$workerServer = $_SERVER;
|
||||
|
||||
$handler = static function () use ($workerServer) {
|
||||
var_dump($_SERVER); // $_SERVER lié à la requête
|
||||
var_dump($workerServer); // $_SERVER du script worker
|
||||
};
|
||||
|
||||
// ...
|
||||
```
|
||||
71
docs/fr/x-sendfile.md
Normal file
71
docs/fr/x-sendfile.md
Normal file
@@ -0,0 +1,71 @@
|
||||
# Servir efficacement les gros fichiers statiques (`X-Sendfile`/`X-Accel-Redirect`)
|
||||
|
||||
Habituellement, les fichiers statiques peuvent être servis directement par le serveur web,
|
||||
mais parfois, il est nécessaire d'exécuter du code PHP avant de les envoyer :
|
||||
contrôle d'accès, statistiques, en-têtes HTTP personnalisés...
|
||||
|
||||
Malheureusement, utiliser PHP pour servir de gros fichiers statiques est inefficace comparé à
|
||||
à l'utilisation directe du serveur web (surcharge mémoire, diminution des performances...).
|
||||
|
||||
FrankenPHP permet de déléguer l'envoi des fichiers statiques au serveur web
|
||||
**après** avoir exécuté du code PHP personnalisé.
|
||||
|
||||
Pour ce faire, votre application PHP n'a qu'à définir un en-tête HTTP personnalisé
|
||||
contenant le chemin du fichier à servir. FrankenPHP se chargera du reste.
|
||||
|
||||
Cette fonctionnalité est connue sous le nom de **`X-Sendfile`** pour Apache, et **`X-Accel-Redirect`** pour NGINX.
|
||||
|
||||
Dans les exemples suivants, nous supposons que le "document root" du projet est le répertoire `public/`
|
||||
et que nous voulons utiliser PHP pour servir des fichiers stockés en dehors du dossier `public/`,
|
||||
depuis un répertoire nommé `private-files/`.
|
||||
|
||||
## Configuration
|
||||
|
||||
Tout d'abord, ajoutez la configuration suivante à votre `Caddyfile` pour activer cette fonctionnalité :
|
||||
|
||||
```patch
|
||||
root public/
|
||||
# ...
|
||||
|
||||
+ # Needed for Symfony, Laravel and other projects using the Symfony HttpFoundation component
|
||||
+ request_header X-Sendfile-Type x-accel-redirect
|
||||
+ request_header X-Accel-Mapping ../private-files=/private-files
|
||||
+
|
||||
+ intercept {
|
||||
+ @accel header X-Accel-Redirect *
|
||||
+ handle_response @accel {
|
||||
+ root private-files/
|
||||
+ rewrite * {resp.header.X-Accel-Redirect}
|
||||
+ method * GET
|
||||
+
|
||||
+ # Remove the X-Accel-Redirect header set by PHP for increased security
|
||||
+ header -X-Accel-Redirect
|
||||
+
|
||||
+ file_server
|
||||
+ }
|
||||
+ }
|
||||
|
||||
php_server
|
||||
```
|
||||
|
||||
## PHP simple
|
||||
|
||||
Définissez le chemin relatif du fichier (à partir de `private-files/`) comme valeur de l'en-tête `X-Accel-Redirect` :
|
||||
|
||||
```php
|
||||
header('X-Accel-Redirect: file.txt') ;
|
||||
```
|
||||
|
||||
## Projets utilisant le composant Symfony HttpFoundation (Symfony, Laravel, Drupal...)
|
||||
|
||||
Symfony HttpFoundation [supporte nativement cette fonctionnalité](https://symfony.com/doc/current/components/http_foundation.html#serving-files).
|
||||
Il va automatiquement déterminer la bonne valeur pour l'en-tête `X-Accel-Redirect` et l'ajoutera à la réponse.
|
||||
|
||||
```php
|
||||
use Symfony\Component\HttpFoundation\BinaryFileResponse;
|
||||
|
||||
BinaryFileResponse::trustXSendfileTypeHeader();
|
||||
$response = new BinaryFileResponse(__DIR__.'/../private-files/file.txt');
|
||||
|
||||
// ...
|
||||
```
|
||||
@@ -8,19 +8,19 @@ every approved pull request or on your own fork once setup.
|
||||
In the repository settings, under secrets, add the following secrets:
|
||||
|
||||
- `REGISTRY_LOGIN_SERVER`: The docker registry to use (e.g. `docker.io`).
|
||||
- `REGISTRY_USERNAME`: The username to use to login to the registry (e.g. `dunglas`).
|
||||
- `REGISTRY_PASSWORD`: The password to use to login to the registry (e.g. an access key).
|
||||
- `REGISTRY_USERNAME`: The username to use to log in to the registry (e.g. `dunglas`).
|
||||
- `REGISTRY_PASSWORD`: The password to use to log in to the registry (e.g. an access key).
|
||||
- `IMAGE_NAME`: The name of the image (e.g. `dunglas/frankenphp`).
|
||||
|
||||
## Building and pushing the image
|
||||
## Building and Pushing the Image
|
||||
|
||||
1. Create a pull request or push to your fork.
|
||||
1. Create a Pull Request or push to your fork.
|
||||
2. GitHub Actions will build the image and run any tests.
|
||||
3. If the build is successful, the image will be pushed to the registry using the `pr-x`, where `x` is the PR number, as the tag.
|
||||
|
||||
## Deploying the image
|
||||
## Deploying the Image
|
||||
|
||||
1. Once the pull request is merged, GitHub Actions will again run the tests and build a new image.
|
||||
1. Once the Pull Request is merged, GitHub Actions will again run the tests and build a new image.
|
||||
2. If the build is successful, the `main` tag will be updated in the Docker registry.
|
||||
|
||||
## Releases
|
||||
|
||||
143
docs/known-issues.md
Normal file
143
docs/known-issues.md
Normal file
@@ -0,0 +1,143 @@
|
||||
# Known Issues
|
||||
|
||||
## Unsupported PHP Extensions
|
||||
|
||||
The following extensions are known not to be compatible with FrankenPHP:
|
||||
|
||||
| Name | Reason | Alternatives |
|
||||
| ----------------------------------------------------------------------------------------------------------- | --------------- | -------------------------------------------------------------------------------------------------------------------- |
|
||||
| [imap](https://www.php.net/manual/en/imap.installation.php) | Not thread-safe | [javanile/php-imap2](https://github.com/javanile/php-imap2), [webklex/php-imap](https://github.com/Webklex/php-imap) |
|
||||
| [newrelic](https://docs.newrelic.com/docs/apm/agents/php-agent/getting-started/introduction-new-relic-php/) | Not thread-safe | - |
|
||||
|
||||
## Buggy PHP Extensions
|
||||
|
||||
The following extensions have known bugs and unexpected behaviors when used with FrankenPHP:
|
||||
|
||||
| Name | Problem |
|
||||
| ------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| [ext-openssl](https://www.php.net/manual/en/book.openssl.php) | When using a static build of FrankenPHP (built with the musl libc), the OpenSSL extension may crash under heavy loads. A workaround is to use a dynamically linked build (like the one used in Docker images). This bug is [being tracked by PHP](https://github.com/php/php-src/issues/13648). |
|
||||
|
||||
## get_browser
|
||||
|
||||
The [get_browser()](https://www.php.net/manual/en/function.get-browser.php) function seems to perform badly after a while. A workaround is to cache (e.g. with [APCu](https://www.php.net/manual/en/book.apcu.php)) the results per User Agent, as they are static.
|
||||
|
||||
## Standalone Binary and Alpine-based Docker Images
|
||||
|
||||
The standalone binary and Alpine-based docker images (`dunglas/frankenphp:*-alpine`) use [musl libc](https://musl.libc.org/) instead of [glibc and friends](https://www.etalabs.net/compare_libcs.html), to keep a smaller binary size. This may lead to some compatibility issues. In particular, the glob flag `GLOB_BRACE` is [not available](https://www.php.net/manual/en/function.glob.php)
|
||||
|
||||
## Using `https://127.0.0.1` with Docker
|
||||
|
||||
By default, FrankenPHP generates a TLS certificate for `localhost`.
|
||||
It's the easiest and recommended option for local development.
|
||||
|
||||
If you really want to use `127.0.0.1` as a host instead, it's possible to configure it to generate a certificate for it by setting the server name to `127.0.0.1`.
|
||||
|
||||
Unfortunately, this is not enough when using Docker because of [its networking system](https://docs.docker.com/network/).
|
||||
You will get a TLS error similar to `curl: (35) LibreSSL/3.3.6: error:1404B438:SSL routines:ST_CONNECT:tlsv1 alert internal error`.
|
||||
|
||||
If you're using Linux, a solution is to use [the host networking driver](https://docs.docker.com/network/network-tutorial-host/):
|
||||
|
||||
```console
|
||||
docker run \
|
||||
-e SERVER_NAME="127.0.0.1" \
|
||||
-v $PWD:/app/public \
|
||||
--network host \
|
||||
dunglas/frankenphp
|
||||
```
|
||||
|
||||
The host networking driver isn't supported on Mac and Windows. On these platforms, you will have to guess the IP address of the container and include it in the server names.
|
||||
|
||||
Run the `docker network inspect bridge` and look at the `Containers` key to identify the last currently assigned IP address under the `IPv4Address` key, and increment it by one. If no container is running, the first assigned IP address is usually `172.17.0.2`.
|
||||
|
||||
Then, include this in the `SERVER_NAME` environment variable:
|
||||
|
||||
```console
|
||||
docker run \
|
||||
-e SERVER_NAME="127.0.0.1, 172.17.0.3" \
|
||||
-v $PWD:/app/public \
|
||||
-p 80:80 -p 443:443 -p 443:443/udp \
|
||||
dunglas/frankenphp
|
||||
```
|
||||
|
||||
> [!CAUTION]
|
||||
>
|
||||
> Be sure to replace `172.17.0.3` with the IP that will be assigned to your container.
|
||||
|
||||
You should now be able to access `https://127.0.0.1` from the host machine.
|
||||
|
||||
If that's not the case, start FrankenPHP in debug mode to try to figure out the problem:
|
||||
|
||||
```console
|
||||
docker run \
|
||||
-e CADDY_GLOBAL_OPTIONS="debug" \
|
||||
-e SERVER_NAME="127.0.0.1" \
|
||||
-v $PWD:/app/public \
|
||||
-p 80:80 -p 443:443 -p 443:443/udp \
|
||||
dunglas/frankenphp
|
||||
```
|
||||
|
||||
## Composer Scripts Referencing `@php`
|
||||
|
||||
[Composer scripts](https://getcomposer.org/doc/articles/scripts.md) may want to execute a PHP binary for some tasks, e.g. in [a Laravel project](laravel.md) to run `@php artisan package:discover --ansi`. This [currently fails](https://github.com/dunglas/frankenphp/issues/483#issuecomment-1899890915) for two reasons:
|
||||
|
||||
- Composer does not know how to call the FrankenPHP binary;
|
||||
- Composer may add PHP settings using the `-d` flag in the command, which FrankenPHP does not yet support.
|
||||
|
||||
As a workaround, we can create a shell script in `/usr/local/bin/php` which strips the unsupported parameters and then calls FrankenPHP:
|
||||
|
||||
```bash
|
||||
#!/usr/bin/env bash
|
||||
args=("$@")
|
||||
index=0
|
||||
for i in "$@"
|
||||
do
|
||||
if [ "$i" == "-d" ]; then
|
||||
unset 'args[$index]'
|
||||
unset 'args[$index+1]'
|
||||
fi
|
||||
index=$((index+1))
|
||||
done
|
||||
|
||||
/usr/local/bin/frankenphp php-cli ${args[@]}
|
||||
```
|
||||
|
||||
Then set the environment variable `PHP_BINARY` to the path of our `php` script and run Composer:
|
||||
|
||||
```console
|
||||
export PHP_BINARY=/usr/local/bin/php
|
||||
composer install
|
||||
```
|
||||
|
||||
## Troubleshooting TLS/SSL Issues with Static Binaries
|
||||
|
||||
When using the static binaries, you may encounter the following TLS-related errors, for instance when sending emails using STARTTLS:
|
||||
|
||||
```text
|
||||
Unable to connect with STARTTLS: stream_socket_enable_crypto(): SSL operation failed with code 5. OpenSSL Error messages:
|
||||
error:80000002:system library::No such file or directory
|
||||
error:80000002:system library::No such file or directory
|
||||
error:80000002:system library::No such file or directory
|
||||
error:0A000086:SSL routines::certificate verify failed
|
||||
```
|
||||
|
||||
As the static binary doesn't bundle TLS certificates, you need to point OpenSSL to your local CA certificates installation.
|
||||
|
||||
Inspect the output of [`openssl_get_cert_locations()`](https://www.php.net/manual/en/function.openssl-get-cert-locations.php),
|
||||
to find where CA certificates must be installed and store them at this location.
|
||||
|
||||
> [!WARNING]
|
||||
>
|
||||
> Web and CLI contexts may have different settings.
|
||||
> Be sure to run `openssl_get_cert_locations()` in the proper context.
|
||||
|
||||
[CA certificates extracted from Mozilla can be downloaded on the curl site](https://curl.se/docs/caextract.html).
|
||||
|
||||
Alternatively, many distributions, including Debian, Ubuntu, and Alpine provide packages named `ca-certificates` that contain these certificates.
|
||||
|
||||
It's also possible to use the `SSL_CERT_FILE` and `SSL_CERT_DIR` to hint OpenSSL where to look for CA certificates:
|
||||
|
||||
```console
|
||||
# Set TLS certificates environment variables
|
||||
export SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt
|
||||
export SSL_CERT_DIR=/etc/ssl/certs
|
||||
```
|
||||
184
docs/laravel.md
Normal file
184
docs/laravel.md
Normal file
@@ -0,0 +1,184 @@
|
||||
# Laravel
|
||||
|
||||
## Docker
|
||||
|
||||
Serving a [Laravel](https://laravel.com) web application with FrankenPHP is as easy as mounting the project in the `/app` directory of the official Docker image.
|
||||
|
||||
Run this command from the main directory of your Laravel app:
|
||||
|
||||
```console
|
||||
docker run -p 80:80 -p 443:443 -p 443:443/udp -v $PWD:/app dunglas/frankenphp
|
||||
```
|
||||
|
||||
And enjoy!
|
||||
|
||||
## Local Installation
|
||||
|
||||
Alternatively, you can run your Laravel projects with FrankenPHP from your local machine:
|
||||
|
||||
1. [Download the binary corresponding to your system](../#standalone-binary)
|
||||
2. Add the following configuration to a file named `Caddyfile` in the root directory of your Laravel project:
|
||||
|
||||
```caddyfile
|
||||
{
|
||||
frankenphp
|
||||
}
|
||||
|
||||
# The domain name of your server
|
||||
localhost {
|
||||
# Set the webroot to the public/ directory
|
||||
root public/
|
||||
# Enable compression (optional)
|
||||
encode zstd br gzip
|
||||
# Execute PHP files from the public/ directory and serve assets
|
||||
php_server {
|
||||
try_files {path} index.php
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
3. Start FrankenPHP from the root directory of your Laravel project: `frankenphp run`
|
||||
|
||||
## Laravel Octane
|
||||
|
||||
Octane may be installed via the Composer package manager:
|
||||
|
||||
```console
|
||||
composer require laravel/octane
|
||||
```
|
||||
|
||||
After installing Octane, you may execute the `octane:install` Artisan command, which will install Octane's configuration file into your application:
|
||||
|
||||
```console
|
||||
php artisan octane:install --server=frankenphp
|
||||
```
|
||||
|
||||
The Octane server can be started via the `octane:frankenphp` Artisan command.
|
||||
|
||||
```console
|
||||
php artisan octane:frankenphp
|
||||
```
|
||||
|
||||
The `octane:frankenphp` command can take the following options:
|
||||
|
||||
- `--host`: The IP address the server should bind to (default: `127.0.0.1`)
|
||||
- `--port`: The port the server should be available on (default: `8000`)
|
||||
- `--admin-port`: The port the admin server should be available on (default: `2019`)
|
||||
- `--workers`: The number of workers that should be available to handle requests (default: `auto`)
|
||||
- `--max-requests`: The number of requests to process before reloading the server (default: `500`)
|
||||
- `--caddyfile`: The path to the FrankenPHP `Caddyfile` file (default: [stubbed `Caddyfile` in Laravel Octane](https://github.com/laravel/octane/blob/2.x/src/Commands/stubs/Caddyfile))
|
||||
- `--https`: Enable HTTPS, HTTP/2, and HTTP/3, and automatically generate and renew certificates
|
||||
- `--http-redirect`: Enable HTTP to HTTPS redirection (only enabled if --https is passed)
|
||||
- `--watch`: Automatically reload the server when the application is modified
|
||||
- `--poll`: Use file system polling while watching in order to watch files over a network
|
||||
- `--log-level`: Log messages at or above the specified log level, using the native Caddy logger
|
||||
|
||||
> [!TIP]
|
||||
> To get structured JSON logs (useful when using log analytics solutions), explicitly the pass `--log-level` option.
|
||||
|
||||
Learn more about [Laravel Octane in its official documentation](https://laravel.com/docs/octane).
|
||||
|
||||
## Laravel Apps As Standalone Binaries
|
||||
|
||||
Using [FrankenPHP's application embedding feature](embed.md), it's possible to distribute Laravel
|
||||
apps as standalone binaries.
|
||||
|
||||
Follow these steps to package your Laravel app as a standalone binary for Linux:
|
||||
|
||||
1. Create a file named `static-build.Dockerfile` in the repository of your app:
|
||||
|
||||
```dockerfile
|
||||
FROM --platform=linux/amd64 dunglas/frankenphp:static-builder
|
||||
|
||||
# Copy your app
|
||||
WORKDIR /go/src/app/dist/app
|
||||
COPY . .
|
||||
|
||||
# Remove the tests and other unneeded files to save space
|
||||
# Alternatively, add these files to a .dockerignore file
|
||||
RUN rm -Rf tests/
|
||||
|
||||
# Copy .env file
|
||||
RUN cp .env.example .env
|
||||
# Change APP_ENV and APP_DEBUG to be production ready
|
||||
RUN sed -i'' -e 's/^APP_ENV=.*/APP_ENV=production/' -e 's/^APP_DEBUG=.*/APP_DEBUG=false/' .env
|
||||
|
||||
# Make other changes to your .env file if needed
|
||||
|
||||
# Install the dependencies
|
||||
RUN composer install --ignore-platform-reqs --no-dev -a
|
||||
|
||||
# Build the static binary
|
||||
WORKDIR /go/src/app/
|
||||
RUN EMBED=dist/app/ ./build-static.sh
|
||||
```
|
||||
|
||||
> [!CAUTION]
|
||||
>
|
||||
> Some `.dockerignore` files
|
||||
> will ignore the `vendor/` directory and `.env` files. Be sure to adjust or remove the `.dockerignore` file before the build.
|
||||
|
||||
2. Build:
|
||||
|
||||
```console
|
||||
docker build -t static-laravel-app -f static-build.Dockerfile .
|
||||
```
|
||||
|
||||
3. Extract the binary:
|
||||
|
||||
```console
|
||||
docker cp $(docker create --name static-laravel-app-tmp static-laravel-app):/go/src/app/dist/frankenphp-linux-x86_64 frankenphp ; docker rm static-laravel-app-tmp
|
||||
```
|
||||
|
||||
4. Populate caches:
|
||||
|
||||
```console
|
||||
frankenphp php-cli artisan optimize
|
||||
```
|
||||
|
||||
5. Run database migrations (if any):
|
||||
|
||||
```console
|
||||
frankenphp php-cli artisan migrate
|
||||
```
|
||||
|
||||
6. Generate app's secret key:
|
||||
|
||||
```console
|
||||
frankenphp php-cli artisan key:generate
|
||||
```
|
||||
|
||||
7. Start the server:
|
||||
|
||||
```console
|
||||
frankenphp php-server
|
||||
```
|
||||
|
||||
Your app is now ready!
|
||||
|
||||
Learn more about the options available and how to build binaries for other OSes in the [applications embedding](embed.md)
|
||||
documentation.
|
||||
|
||||
### Changing The Storage Path
|
||||
|
||||
By default, Laravel stores uploaded files, caches, logs, etc. in the application's `storage/` directory.
|
||||
This is not suitable for embedded applications, as each new version will be extracted into a different temporary directory.
|
||||
|
||||
Set the `LARAVEL_STORAGE_PATH` environment variable (for example, in your `.env` file) or call the `Illuminate\Foundation\Application::useStoragePath()` method to use a directory outside the temporary directory.
|
||||
|
||||
### Running Octane With Standalone Binaries
|
||||
|
||||
It's even possible to package Laravel Octane apps as standalone binaries!
|
||||
|
||||
To do so, [install Octane properly](#laravel-octane) and follow the steps described in [the previous section](#laravel-apps-as-standalone-binaries).
|
||||
|
||||
Then, to start FrankenPHP in worker mode through Octane, run:
|
||||
|
||||
```console
|
||||
PATH="$PWD:$PATH" frankenphp php-cli artisan octane:frankenphp
|
||||
```
|
||||
|
||||
> [!CAUTION]
|
||||
>
|
||||
> For the command to work, the standalone binary **must** be named `frankenphp`
|
||||
> because Octane needs a program named `frankenphp` available in the path.
|
||||
BIN
docs/mercure-hub.png
Normal file
BIN
docs/mercure-hub.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1009 KiB |
@@ -1,12 +1,15 @@
|
||||
# Real-time
|
||||
|
||||
FrankenPHP comes with a built-in Mercure hub!
|
||||
Mercure allows to push event in real-time to all the connected devices: they will receive instantly a JavaScript event.
|
||||
FrankenPHP comes with a built-in [Mercure](https://mercure.rocks) hub!
|
||||
Mercure allows you to push real-time events to all the connected devices: they will receive a JavaScript event instantly.
|
||||
|
||||
No JS library or SDK required!
|
||||
No JS library or SDK is required!
|
||||
|
||||

|
||||

|
||||
|
||||
To enable the Mercure hub, update the `Caddyfile` as described [on Mercure's website](https://mercure.rocks/docs/hub/config).
|
||||
To enable the Mercure hub, update the `Caddyfile` as described [on Mercure's site](https://mercure.rocks/docs/hub/config).
|
||||
|
||||
To push Mercure updates from your code, we recommend the [Symfony Mercure Component](https://symfony.com/components/Mercure) (you don't need the Symfony full stack framework to use it).
|
||||
The path of the Mercure hub is `/.well-known/mercure`.
|
||||
When running FrankenPHP inside Docker, the full send URL would look like `http://php/.well-known/mercure` (with `php` being the container's name running FrankenPHP).
|
||||
|
||||
To push Mercure updates from your code, we recommend the [Symfony Mercure Component](https://symfony.com/components/Mercure) (you don't need the Symfony full-stack framework to use it).
|
||||
|
||||
17
docs/metrics.md
Normal file
17
docs/metrics.md
Normal file
@@ -0,0 +1,17 @@
|
||||
# Metrics
|
||||
|
||||
When [Caddy metrics](https://caddyserver.com/docs/metrics) are enabled, FrankenPHP exposes the following metrics:
|
||||
|
||||
- `frankenphp_total_threads`: The total number of PHP threads.
|
||||
- `frankenphp_busy_threads`: The number of PHP threads currently processing a request (running workers always consume a thread).
|
||||
- `frankenphp_queue_depth`: The number of regular queued requests
|
||||
- `frankenphp_total_workers{worker="[worker_name]"}`: The total number of workers.
|
||||
- `frankenphp_busy_workers{worker="[worker_name]"}`: The number of workers currently processing a request.
|
||||
- `frankenphp_worker_request_time{worker="[worker_name]"}`: The time spent processing requests by all workers.
|
||||
- `frankenphp_worker_request_count{worker="[worker_name]"}`: The number of requests processed by all workers.
|
||||
- `frankenphp_ready_workers{worker="[worker_name]"}`: The number of workers that have called `frankenphp_handle_request` at least once.
|
||||
- `frankenphp_worker_crashes{worker="[worker_name]"}`: The number of times a worker has unexpectedly terminated.
|
||||
- `frankenphp_worker_restarts{worker="[worker_name]"}`: The number of times a worker has been deliberately restarted.
|
||||
- `frankenphp_worker_queue_depth{worker="[worker_name]"}`: The number of queued requests.
|
||||
|
||||
For worker metrics, the `[worker_name]` placeholder is replaced by the worker name in the Caddyfile, otherwise absolute path of worker file will be used.
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user