caddy_test.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546
  1. package caddy_test
  2. import (
  3. "bytes"
  4. "fmt"
  5. "github.com/dunglas/frankenphp"
  6. "github.com/prometheus/client_golang/prometheus"
  7. "github.com/prometheus/client_golang/prometheus/testutil"
  8. "github.com/stretchr/testify/require"
  9. "net/http"
  10. "strings"
  11. "sync"
  12. "testing"
  13. "github.com/caddyserver/caddy/v2/caddytest"
  14. )
  15. var testPort = "9080"
  16. func TestPHP(t *testing.T) {
  17. var wg sync.WaitGroup
  18. tester := caddytest.NewTester(t)
  19. tester.InitServer(`
  20. {
  21. skip_install_trust
  22. admin localhost:2999
  23. http_port `+testPort+`
  24. https_port 9443
  25. frankenphp
  26. }
  27. localhost:`+testPort+` {
  28. route {
  29. php {
  30. root ../testdata
  31. }
  32. }
  33. }
  34. `, "caddyfile")
  35. for i := 0; i < 100; i++ {
  36. wg.Add(1)
  37. go func(i int) {
  38. tester.AssertGetResponse(fmt.Sprintf("http://localhost:"+testPort+"/index.php?i=%d", i), http.StatusOK, fmt.Sprintf("I am by birth a Genevese (%d)", i))
  39. wg.Done()
  40. }(i)
  41. }
  42. wg.Wait()
  43. }
  44. func TestLargeRequest(t *testing.T) {
  45. tester := caddytest.NewTester(t)
  46. tester.InitServer(`
  47. {
  48. skip_install_trust
  49. admin localhost:2999
  50. http_port `+testPort+`
  51. https_port 9443
  52. frankenphp
  53. }
  54. localhost:`+testPort+` {
  55. route {
  56. php {
  57. root ../testdata
  58. }
  59. }
  60. }
  61. `, "caddyfile")
  62. tester.AssertPostResponseBody(
  63. "http://localhost:"+testPort+"/large-request.php",
  64. []string{},
  65. bytes.NewBufferString(strings.Repeat("f", 1_048_576)),
  66. http.StatusOK,
  67. "Request body size: 1048576 (unknown)",
  68. )
  69. }
  70. func TestWorker(t *testing.T) {
  71. var wg sync.WaitGroup
  72. tester := caddytest.NewTester(t)
  73. tester.InitServer(`
  74. {
  75. skip_install_trust
  76. admin localhost:2999
  77. http_port `+testPort+`
  78. https_port 9443
  79. frankenphp {
  80. worker ../testdata/index.php 2
  81. }
  82. }
  83. localhost:`+testPort+` {
  84. route {
  85. php {
  86. root ../testdata
  87. }
  88. }
  89. }
  90. `, "caddyfile")
  91. for i := 0; i < 100; i++ {
  92. wg.Add(1)
  93. go func(i int) {
  94. tester.AssertGetResponse(fmt.Sprintf("http://localhost:"+testPort+"/index.php?i=%d", i), http.StatusOK, fmt.Sprintf("I am by birth a Genevese (%d)", i))
  95. wg.Done()
  96. }(i)
  97. }
  98. wg.Wait()
  99. }
  100. func TestEnv(t *testing.T) {
  101. tester := caddytest.NewTester(t)
  102. tester.InitServer(`
  103. {
  104. skip_install_trust
  105. admin localhost:2999
  106. http_port `+testPort+`
  107. https_port 9443
  108. frankenphp {
  109. worker {
  110. file ../testdata/worker-env.php
  111. num 1
  112. env FOO bar
  113. }
  114. }
  115. }
  116. localhost:`+testPort+` {
  117. route {
  118. php {
  119. root ../testdata
  120. env FOO baz
  121. }
  122. }
  123. }
  124. `, "caddyfile")
  125. tester.AssertGetResponse("http://localhost:"+testPort+"/worker-env.php", http.StatusOK, "bazbar")
  126. }
  127. func TestJsonEnv(t *testing.T) {
  128. tester := caddytest.NewTester(t)
  129. tester.InitServer(`
  130. {
  131. "admin": {
  132. "listen": "localhost:2999"
  133. },
  134. "apps": {
  135. "frankenphp": {
  136. "workers": [
  137. {
  138. "env": {
  139. "FOO": "bar"
  140. },
  141. "file_name": "../testdata/worker-env.php",
  142. "num": 1
  143. }
  144. ]
  145. },
  146. "http": {
  147. "http_port": `+testPort+`,
  148. "https_port": 9443,
  149. "servers": {
  150. "srv0": {
  151. "listen": [
  152. ":`+testPort+`"
  153. ],
  154. "routes": [
  155. {
  156. "handle": [
  157. {
  158. "handler": "subroute",
  159. "routes": [
  160. {
  161. "handle": [
  162. {
  163. "handler": "subroute",
  164. "routes": [
  165. {
  166. "handle": [
  167. {
  168. "env": {
  169. "FOO": "baz"
  170. },
  171. "handler": "php",
  172. "root": "../testdata"
  173. }
  174. ]
  175. }
  176. ]
  177. }
  178. ]
  179. }
  180. ]
  181. }
  182. ],
  183. "match": [
  184. {
  185. "host": [
  186. "localhost"
  187. ]
  188. }
  189. ],
  190. "terminal": true
  191. }
  192. ]
  193. }
  194. }
  195. },
  196. "pki": {
  197. "certificate_authorities": {
  198. "local": {
  199. "install_trust": false
  200. }
  201. }
  202. }
  203. }
  204. }
  205. `, "json")
  206. tester.AssertGetResponse("http://localhost:"+testPort+"/worker-env.php", http.StatusOK, "bazbar")
  207. }
  208. func TestCustomCaddyVariablesInEnv(t *testing.T) {
  209. tester := caddytest.NewTester(t)
  210. tester.InitServer(`
  211. {
  212. skip_install_trust
  213. admin localhost:2999
  214. http_port `+testPort+`
  215. https_port 9443
  216. frankenphp {
  217. worker {
  218. file ../testdata/worker-env.php
  219. num 1
  220. env FOO world
  221. }
  222. }
  223. }
  224. localhost:`+testPort+` {
  225. route {
  226. map 1 {my_customvar} {
  227. default "hello "
  228. }
  229. php {
  230. root ../testdata
  231. env FOO {my_customvar}
  232. }
  233. }
  234. }
  235. `, "caddyfile")
  236. tester.AssertGetResponse("http://localhost:"+testPort+"/worker-env.php", http.StatusOK, "hello world")
  237. }
  238. func TestPHPServerDirective(t *testing.T) {
  239. tester := caddytest.NewTester(t)
  240. tester.InitServer(`
  241. {
  242. skip_install_trust
  243. admin localhost:2999
  244. http_port `+testPort+`
  245. https_port 9443
  246. frankenphp
  247. }
  248. localhost:`+testPort+` {
  249. root * ../testdata
  250. php_server
  251. }
  252. `, "caddyfile")
  253. tester.AssertGetResponse("http://localhost:"+testPort, http.StatusOK, "I am by birth a Genevese (i not set)")
  254. tester.AssertGetResponse("http://localhost:"+testPort+"/hello.txt", http.StatusOK, "Hello")
  255. tester.AssertGetResponse("http://localhost:"+testPort+"/not-found.txt", http.StatusOK, "I am by birth a Genevese (i not set)")
  256. }
  257. func TestPHPServerDirectiveDisableFileServer(t *testing.T) {
  258. tester := caddytest.NewTester(t)
  259. tester.InitServer(`
  260. {
  261. skip_install_trust
  262. admin localhost:2999
  263. http_port `+testPort+`
  264. https_port 9443
  265. frankenphp
  266. order php_server before respond
  267. }
  268. localhost:`+testPort+` {
  269. root * ../testdata
  270. php_server {
  271. file_server off
  272. }
  273. respond "Not found" 404
  274. }
  275. `, "caddyfile")
  276. tester.AssertGetResponse("http://localhost:"+testPort, http.StatusOK, "I am by birth a Genevese (i not set)")
  277. tester.AssertGetResponse("http://localhost:"+testPort+"/hello.txt", http.StatusNotFound, "Not found")
  278. }
  279. func TestMetrics(t *testing.T) {
  280. var wg sync.WaitGroup
  281. tester := caddytest.NewTester(t)
  282. tester.InitServer(`
  283. {
  284. skip_install_trust
  285. admin localhost:2999
  286. http_port `+testPort+`
  287. https_port 9443
  288. frankenphp
  289. }
  290. localhost:`+testPort+` {
  291. route {
  292. php {
  293. root ../testdata
  294. }
  295. }
  296. }
  297. `, "caddyfile")
  298. // Make some requests
  299. for i := 0; i < 10; i++ {
  300. wg.Add(1)
  301. go func(i int) {
  302. tester.AssertGetResponse(fmt.Sprintf("http://localhost:"+testPort+"/index.php?i=%d", i), http.StatusOK, fmt.Sprintf("I am by birth a Genevese (%d)", i))
  303. wg.Done()
  304. }(i)
  305. }
  306. wg.Wait()
  307. // Fetch metrics
  308. resp, err := http.Get("http://localhost:2999/metrics")
  309. if err != nil {
  310. t.Fatalf("failed to fetch metrics: %v", err)
  311. }
  312. defer resp.Body.Close()
  313. // Read and parse metrics
  314. metrics := new(bytes.Buffer)
  315. _, err = metrics.ReadFrom(resp.Body)
  316. if err != nil {
  317. t.Fatalf("failed to read metrics: %v", err)
  318. }
  319. cpus := fmt.Sprintf("%d", frankenphp.MaxThreads)
  320. // Check metrics
  321. expectedMetrics := `
  322. # HELP frankenphp_total_threads Total number of PHP threads
  323. # TYPE frankenphp_total_threads counter
  324. frankenphp_total_threads ` + cpus + `
  325. # HELP frankenphp_busy_threads Number of busy PHP threads
  326. # TYPE frankenphp_busy_threads gauge
  327. frankenphp_busy_threads 0
  328. `
  329. require.NoError(t, testutil.GatherAndCompare(prometheus.DefaultGatherer, strings.NewReader(expectedMetrics), "frankenphp_total_threads", "frankenphp_busy_threads"))
  330. }
  331. func TestWorkerMetrics(t *testing.T) {
  332. var wg sync.WaitGroup
  333. tester := caddytest.NewTester(t)
  334. tester.InitServer(`
  335. {
  336. skip_install_trust
  337. admin localhost:2999
  338. http_port `+testPort+`
  339. https_port 9443
  340. frankenphp {
  341. worker ../testdata/index.php 2
  342. }
  343. }
  344. localhost:`+testPort+` {
  345. route {
  346. php {
  347. root ../testdata
  348. }
  349. }
  350. }
  351. `, "caddyfile")
  352. // Make some requests
  353. for i := 0; i < 10; i++ {
  354. wg.Add(1)
  355. go func(i int) {
  356. tester.AssertGetResponse(fmt.Sprintf("http://localhost:"+testPort+"/index.php?i=%d", i), http.StatusOK, fmt.Sprintf("I am by birth a Genevese (%d)", i))
  357. wg.Done()
  358. }(i)
  359. }
  360. wg.Wait()
  361. // Fetch metrics
  362. resp, err := http.Get("http://localhost:2999/metrics")
  363. if err != nil {
  364. t.Fatalf("failed to fetch metrics: %v", err)
  365. }
  366. defer resp.Body.Close()
  367. // Read and parse metrics
  368. metrics := new(bytes.Buffer)
  369. _, err = metrics.ReadFrom(resp.Body)
  370. if err != nil {
  371. t.Fatalf("failed to read metrics: %v", err)
  372. }
  373. cpus := fmt.Sprintf("%d", frankenphp.MaxThreads)
  374. // Check metrics
  375. expectedMetrics := `
  376. # HELP frankenphp_total_threads Total number of PHP threads
  377. # TYPE frankenphp_total_threads counter
  378. frankenphp_total_threads ` + cpus + `
  379. # HELP frankenphp_busy_threads Number of busy PHP threads
  380. # TYPE frankenphp_busy_threads gauge
  381. frankenphp_busy_threads 2
  382. # HELP frankenphp_testdata_index_php_busy_workers Number of busy PHP workers for this worker
  383. # TYPE frankenphp_testdata_index_php_busy_workers gauge
  384. frankenphp_testdata_index_php_busy_workers 0
  385. # HELP frankenphp_testdata_index_php_total_workers Total number of PHP workers for this worker
  386. # TYPE frankenphp_testdata_index_php_total_workers gauge
  387. frankenphp_testdata_index_php_total_workers 2
  388. # HELP frankenphp_testdata_index_php_worker_request_count
  389. # TYPE frankenphp_testdata_index_php_worker_request_count counter
  390. frankenphp_testdata_index_php_worker_request_count 10
  391. `
  392. require.NoError(t,
  393. testutil.GatherAndCompare(
  394. prometheus.DefaultGatherer,
  395. strings.NewReader(expectedMetrics),
  396. "frankenphp_total_threads",
  397. "frankenphp_busy_threads",
  398. "frankenphp_testdata_index_php_busy_workers",
  399. "frankenphp_testdata_index_php_total_workers",
  400. "frankenphp_testdata_index_php_worker_request_count",
  401. ))
  402. }
  403. func TestAutoWorkerConfig(t *testing.T) {
  404. var wg sync.WaitGroup
  405. tester := caddytest.NewTester(t)
  406. tester.InitServer(`
  407. {
  408. skip_install_trust
  409. admin localhost:2999
  410. http_port `+testPort+`
  411. https_port 9443
  412. frankenphp {
  413. worker ../testdata/index.php
  414. }
  415. }
  416. localhost:`+testPort+` {
  417. route {
  418. php {
  419. root ../testdata
  420. }
  421. }
  422. }
  423. `, "caddyfile")
  424. // Make some requests
  425. for i := 0; i < 10; i++ {
  426. wg.Add(1)
  427. go func(i int) {
  428. tester.AssertGetResponse(fmt.Sprintf("http://localhost:"+testPort+"/index.php?i=%d", i), http.StatusOK, fmt.Sprintf("I am by birth a Genevese (%d)", i))
  429. wg.Done()
  430. }(i)
  431. }
  432. wg.Wait()
  433. // Fetch metrics
  434. resp, err := http.Get("http://localhost:2999/metrics")
  435. if err != nil {
  436. t.Fatalf("failed to fetch metrics: %v", err)
  437. }
  438. defer resp.Body.Close()
  439. // Read and parse metrics
  440. metrics := new(bytes.Buffer)
  441. _, err = metrics.ReadFrom(resp.Body)
  442. if err != nil {
  443. t.Fatalf("failed to read metrics: %v", err)
  444. }
  445. cpus := fmt.Sprintf("%d", frankenphp.MaxThreads)
  446. workers := fmt.Sprintf("%d", frankenphp.MaxThreads-1)
  447. // Check metrics
  448. expectedMetrics := `
  449. # HELP frankenphp_total_threads Total number of PHP threads
  450. # TYPE frankenphp_total_threads counter
  451. frankenphp_total_threads ` + cpus + `
  452. # HELP frankenphp_busy_threads Number of busy PHP threads
  453. # TYPE frankenphp_busy_threads gauge
  454. frankenphp_busy_threads ` + workers + `
  455. # HELP frankenphp_testdata_index_php_busy_workers Number of busy PHP workers for this worker
  456. # TYPE frankenphp_testdata_index_php_busy_workers gauge
  457. frankenphp_testdata_index_php_busy_workers 0
  458. # HELP frankenphp_testdata_index_php_total_workers Total number of PHP workers for this worker
  459. # TYPE frankenphp_testdata_index_php_total_workers gauge
  460. frankenphp_testdata_index_php_total_workers ` + workers + `
  461. # HELP frankenphp_testdata_index_php_worker_request_count
  462. # TYPE frankenphp_testdata_index_php_worker_request_count counter
  463. frankenphp_testdata_index_php_worker_request_count 10
  464. `
  465. require.NoError(t,
  466. testutil.GatherAndCompare(
  467. prometheus.DefaultGatherer,
  468. strings.NewReader(expectedMetrics),
  469. "frankenphp_total_threads",
  470. "frankenphp_busy_threads",
  471. "frankenphp_testdata_index_php_busy_workers",
  472. "frankenphp_testdata_index_php_total_workers",
  473. "frankenphp_testdata_index_php_worker_request_count",
  474. ))
  475. }