frankenphp_test.go 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590
  1. package frankenphp_test
  2. import (
  3. "context"
  4. "fmt"
  5. "io"
  6. "log"
  7. "net/http"
  8. "net/http/cookiejar"
  9. "net/http/httptest"
  10. "net/http/httptrace"
  11. "net/textproto"
  12. "net/url"
  13. "os"
  14. "strconv"
  15. "strings"
  16. "sync"
  17. "testing"
  18. "github.com/dunglas/frankenphp"
  19. "github.com/stretchr/testify/assert"
  20. "github.com/stretchr/testify/require"
  21. "go.uber.org/zap"
  22. "go.uber.org/zap/zaptest"
  23. "go.uber.org/zap/zaptest/observer"
  24. )
  25. type testOptions struct {
  26. workerScript string
  27. nbWorkers int
  28. env map[string]string
  29. nbParrallelRequests int
  30. realServer bool
  31. logger *zap.Logger
  32. initOpts []frankenphp.Option
  33. }
  34. func runTest(t *testing.T, test func(func(http.ResponseWriter, *http.Request), *httptest.Server, int), opts *testOptions) {
  35. if opts == nil {
  36. opts = &testOptions{}
  37. }
  38. if opts.nbParrallelRequests == 0 {
  39. opts.nbParrallelRequests = 100
  40. }
  41. cwd, _ := os.Getwd()
  42. testDataDir := cwd + "/testdata/"
  43. if opts.logger == nil {
  44. opts.logger = zaptest.NewLogger(t)
  45. }
  46. initOpts := []frankenphp.Option{frankenphp.WithLogger(opts.logger)}
  47. if opts.workerScript != "" {
  48. initOpts = append(initOpts, frankenphp.WithWorkers(testDataDir+opts.workerScript, opts.nbWorkers, opts.env))
  49. }
  50. initOpts = append(initOpts, opts.initOpts...)
  51. err := frankenphp.Init(initOpts...)
  52. require.Nil(t, err)
  53. defer frankenphp.Shutdown()
  54. handler := func(w http.ResponseWriter, r *http.Request) {
  55. req := frankenphp.NewRequestWithContext(r, testDataDir, nil)
  56. if err := frankenphp.ServeHTTP(w, req); err != nil {
  57. panic(err)
  58. }
  59. }
  60. var ts *httptest.Server
  61. if opts.realServer {
  62. ts = httptest.NewServer(http.HandlerFunc(handler))
  63. defer ts.Close()
  64. }
  65. var wg sync.WaitGroup
  66. wg.Add(opts.nbParrallelRequests)
  67. for i := 0; i < opts.nbParrallelRequests; i++ {
  68. go func(i int) {
  69. test(handler, ts, i)
  70. wg.Done()
  71. }(i)
  72. }
  73. wg.Wait()
  74. }
  75. func BenchmarkHelloWorld(b *testing.B) {
  76. if err := frankenphp.Init(frankenphp.WithLogger(zap.NewNop())); err != nil {
  77. panic(err)
  78. }
  79. defer frankenphp.Shutdown()
  80. cwd, _ := os.Getwd()
  81. testDataDir := cwd + "/testdata/"
  82. handler := func(w http.ResponseWriter, r *http.Request) {
  83. req := frankenphp.NewRequestWithContext(r, testDataDir, nil)
  84. if err := frankenphp.ServeHTTP(w, req); err != nil {
  85. panic(err)
  86. }
  87. }
  88. req := httptest.NewRequest("GET", "http://example.com/index.php", nil)
  89. w := httptest.NewRecorder()
  90. for i := 0; i < b.N; i++ {
  91. handler(w, req)
  92. }
  93. }
  94. func TestHelloWorld_module(t *testing.T) { testHelloWorld(t, nil) }
  95. func TestHelloWorld_worker(t *testing.T) {
  96. testHelloWorld(t, &testOptions{workerScript: "index.php"})
  97. }
  98. func testHelloWorld(t *testing.T, opts *testOptions) {
  99. runTest(t, func(handler func(http.ResponseWriter, *http.Request), _ *httptest.Server, i int) {
  100. req := httptest.NewRequest("GET", fmt.Sprintf("http://example.com/index.php?i=%d", i), nil)
  101. w := httptest.NewRecorder()
  102. handler(w, req)
  103. resp := w.Result()
  104. body, _ := io.ReadAll(resp.Body)
  105. assert.Equal(t, fmt.Sprintf("I am by birth a Genevese (%d)", i), string(body))
  106. }, opts)
  107. }
  108. func TestFinishRequest_module(t *testing.T) { testFinishRequest(t, nil) }
  109. func TestFinishRequest_worker(t *testing.T) {
  110. testFinishRequest(t, &testOptions{workerScript: "finish-request.php"})
  111. }
  112. func testFinishRequest(t *testing.T, opts *testOptions) {
  113. runTest(t, func(handler func(http.ResponseWriter, *http.Request), _ *httptest.Server, i int) {
  114. req := httptest.NewRequest("GET", fmt.Sprintf("http://example.com/finish-request.php?i=%d", i), nil)
  115. w := httptest.NewRecorder()
  116. handler(w, req)
  117. resp := w.Result()
  118. body, _ := io.ReadAll(resp.Body)
  119. assert.Equal(t, fmt.Sprintf("This is output %d\n", i), string(body))
  120. }, opts)
  121. }
  122. func TestServerVariable_module(t *testing.T) { testServerVariable(t, nil) }
  123. func TestServerVariable_worker(t *testing.T) {
  124. testServerVariable(t, &testOptions{workerScript: "server-variable.php"})
  125. }
  126. func testServerVariable(t *testing.T, opts *testOptions) {
  127. runTest(t, func(handler func(http.ResponseWriter, *http.Request), _ *httptest.Server, i int) {
  128. req := httptest.NewRequest("GET", fmt.Sprintf("http://example.com/server-variable.php/baz/bat?foo=a&bar=b&i=%d#hash", i), nil)
  129. req.SetBasicAuth("kevin", "password")
  130. w := httptest.NewRecorder()
  131. handler(w, req)
  132. resp := w.Result()
  133. body, _ := io.ReadAll(resp.Body)
  134. strBody := string(body)
  135. assert.Contains(t, strBody, "[REMOTE_HOST]")
  136. assert.Contains(t, strBody, "[REMOTE_USER] => kevin")
  137. assert.Contains(t, strBody, "[PHP_AUTH_USER] => kevin")
  138. assert.Contains(t, strBody, "[PHP_AUTH_PW] => password")
  139. assert.Contains(t, strBody, "[HTTP_AUTHORIZATION] => Basic a2V2aW46cGFzc3dvcmQ=")
  140. assert.Contains(t, strBody, "[DOCUMENT_ROOT]")
  141. assert.Contains(t, strBody, "[PHP_SELF] => /server-variable.php/baz/bat")
  142. assert.Contains(t, strBody, "[CONTENT_TYPE]")
  143. assert.Contains(t, strBody, fmt.Sprintf("[QUERY_STRING] => foo=a&bar=b&i=%d#hash", i))
  144. assert.Contains(t, strBody, fmt.Sprintf("[REQUEST_URI] => /server-variable.php/baz/bat?foo=a&bar=b&i=%d#hash", i))
  145. assert.Contains(t, strBody, "[CONTENT_LENGTH]")
  146. assert.Contains(t, strBody, "[REMOTE_ADDR]")
  147. assert.Contains(t, strBody, "[REMOTE_PORT]")
  148. assert.Contains(t, strBody, "[REQUEST_SCHEME] => http")
  149. assert.Contains(t, strBody, "[DOCUMENT_URI]")
  150. assert.Contains(t, strBody, "[AUTH_TYPE]")
  151. assert.Contains(t, strBody, "[REMOTE_IDENT]")
  152. assert.Contains(t, strBody, "[REQUEST_METHOD] => GET")
  153. assert.Contains(t, strBody, "[SERVER_NAME] => example.com")
  154. assert.Contains(t, strBody, "[SERVER_PROTOCOL] => HTTP/1.1")
  155. assert.Contains(t, strBody, "[SCRIPT_FILENAME]")
  156. assert.Contains(t, strBody, "[SERVER_SOFTWARE] => FrankenPHP")
  157. assert.Contains(t, strBody, "[REQUEST_TIME_FLOAT]")
  158. assert.Contains(t, strBody, "[REQUEST_TIME]")
  159. assert.Contains(t, strBody, "[SERVER_PORT] => 80")
  160. }, opts)
  161. }
  162. func TestPathInfo_module(t *testing.T) { testPathInfo(t, nil) }
  163. func TestPathInfo_worker(t *testing.T) {
  164. testPathInfo(t, &testOptions{workerScript: "server-variable.php"})
  165. }
  166. func testPathInfo(t *testing.T, opts *testOptions) {
  167. runTest(t, func(_ func(http.ResponseWriter, *http.Request), _ *httptest.Server, i int) {
  168. handler := func(w http.ResponseWriter, r *http.Request) {
  169. cwd, _ := os.Getwd()
  170. testDataDir := cwd + "/testdata/"
  171. requestURI := r.URL.RequestURI()
  172. rewriteRequest := frankenphp.NewRequestWithContext(r, testDataDir, nil)
  173. rewriteRequest.URL.Path = "/server-variable.php/pathinfo"
  174. fc, _ := frankenphp.FromContext(rewriteRequest.Context())
  175. fc.Env["REQUEST_URI"] = requestURI
  176. err := frankenphp.ServeHTTP(w, rewriteRequest)
  177. assert.NoError(t, err)
  178. }
  179. req := httptest.NewRequest("GET", fmt.Sprintf("http://example.com/pathinfo/%d", i), nil)
  180. w := httptest.NewRecorder()
  181. handler(w, req)
  182. resp := w.Result()
  183. body, _ := io.ReadAll(resp.Body)
  184. strBody := string(body)
  185. assert.Contains(t, strBody, "[PATH_INFO] => /pathinfo")
  186. assert.Contains(t, strBody, fmt.Sprintf("[REQUEST_URI] => /pathinfo/%d", i))
  187. assert.Contains(t, strBody, "[PATH_TRANSLATED] =>")
  188. assert.Contains(t, strBody, "[SCRIPT_NAME] => /server-variable.php")
  189. }, opts)
  190. }
  191. func TestHeaders_module(t *testing.T) { testHeaders(t, nil) }
  192. func TestHeaders_worker(t *testing.T) { testHeaders(t, &testOptions{workerScript: "headers.php"}) }
  193. func testHeaders(t *testing.T, opts *testOptions) {
  194. runTest(t, func(handler func(http.ResponseWriter, *http.Request), _ *httptest.Server, i int) {
  195. req := httptest.NewRequest("GET", fmt.Sprintf("http://example.com/headers.php?i=%d", i), nil)
  196. w := httptest.NewRecorder()
  197. handler(w, req)
  198. resp := w.Result()
  199. body, _ := io.ReadAll(resp.Body)
  200. assert.Equal(t, "Hello", string(body))
  201. assert.Equal(t, 201, resp.StatusCode)
  202. assert.Equal(t, "bar", resp.Header.Get("Foo"))
  203. assert.Equal(t, "bar2", resp.Header.Get("Foo2"))
  204. assert.Empty(t, resp.Header.Get("Invalid"))
  205. assert.Equal(t, fmt.Sprintf("%d", i), resp.Header.Get("I"))
  206. }, opts)
  207. }
  208. func TestInput_module(t *testing.T) { testInput(t, nil) }
  209. func TestInput_worker(t *testing.T) { testInput(t, &testOptions{workerScript: "input.php"}) }
  210. func testInput(t *testing.T, opts *testOptions) {
  211. runTest(t, func(handler func(http.ResponseWriter, *http.Request), _ *httptest.Server, i int) {
  212. req := httptest.NewRequest("POST", "http://example.com/input.php", strings.NewReader(fmt.Sprintf("post data %d", i)))
  213. w := httptest.NewRecorder()
  214. handler(w, req)
  215. resp := w.Result()
  216. body, _ := io.ReadAll(resp.Body)
  217. assert.Equal(t, fmt.Sprintf("post data %d", i), string(body))
  218. assert.Equal(t, "bar", resp.Header.Get("Foo"))
  219. }, opts)
  220. }
  221. func TestPostSuperGlobals_module(t *testing.T) { testPostSuperGlobals(t, nil) }
  222. func TestPostSuperGlobals_worker(t *testing.T) {
  223. testPostSuperGlobals(t, &testOptions{workerScript: "super-globals.php"})
  224. }
  225. func testPostSuperGlobals(t *testing.T, opts *testOptions) {
  226. runTest(t, func(handler func(http.ResponseWriter, *http.Request), _ *httptest.Server, i int) {
  227. formData := url.Values{"baz": {"bat"}, "i": {fmt.Sprintf("%d", i)}}
  228. req := httptest.NewRequest("POST", fmt.Sprintf("http://example.com/super-globals.php?foo=bar&iG=%d", i), strings.NewReader(formData.Encode()))
  229. req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
  230. w := httptest.NewRecorder()
  231. handler(w, req)
  232. resp := w.Result()
  233. body, _ := io.ReadAll(resp.Body)
  234. assert.Contains(t, string(body), "'foo' => 'bar'")
  235. assert.Contains(t, string(body), fmt.Sprintf("'i' => '%d'", i))
  236. assert.Contains(t, string(body), "'baz' => 'bat'")
  237. assert.Contains(t, string(body), fmt.Sprintf("'iG' => '%d'", i))
  238. }, opts)
  239. }
  240. func TestCookies_module(t *testing.T) { testCookies(t, nil) }
  241. func TestCookies_worker(t *testing.T) { testCookies(t, &testOptions{workerScript: "cookies.php"}) }
  242. func testCookies(t *testing.T, opts *testOptions) {
  243. runTest(t, func(handler func(http.ResponseWriter, *http.Request), _ *httptest.Server, i int) {
  244. req := httptest.NewRequest("GET", "http://example.com/cookies.php", nil)
  245. req.AddCookie(&http.Cookie{Name: "foo", Value: "bar"})
  246. req.AddCookie(&http.Cookie{Name: "i", Value: fmt.Sprintf("%d", i)})
  247. w := httptest.NewRecorder()
  248. handler(w, req)
  249. resp := w.Result()
  250. body, _ := io.ReadAll(resp.Body)
  251. assert.Contains(t, string(body), "'foo' => 'bar'")
  252. assert.Contains(t, string(body), fmt.Sprintf("'i' => '%d'", i))
  253. }, opts)
  254. }
  255. func TestSession_module(t *testing.T) { testSession(t, nil) }
  256. func TestSession_worker(t *testing.T) {
  257. testSession(t, &testOptions{workerScript: "session.php"})
  258. }
  259. func testSession(t *testing.T, opts *testOptions) {
  260. if opts == nil {
  261. opts = &testOptions{}
  262. }
  263. opts.realServer = true
  264. runTest(t, func(_ func(http.ResponseWriter, *http.Request), ts *httptest.Server, i int) {
  265. jar, err := cookiejar.New(&cookiejar.Options{})
  266. if err != nil {
  267. panic(err)
  268. }
  269. client := &http.Client{Jar: jar}
  270. resp1, err := client.Get(ts.URL + "/session.php")
  271. if err != nil {
  272. panic(err)
  273. }
  274. body1, _ := io.ReadAll(resp1.Body)
  275. assert.Equal(t, "Count: 0\n", string(body1))
  276. resp2, err := client.Get(ts.URL + "/session.php")
  277. if err != nil {
  278. panic(err)
  279. }
  280. body2, _ := io.ReadAll(resp2.Body)
  281. assert.Equal(t, "Count: 1\n", string(body2))
  282. }, opts)
  283. }
  284. func TestPhpInfo_module(t *testing.T) { testPhpInfo(t, nil) }
  285. func TestPhpInfo_worker(t *testing.T) { testPhpInfo(t, &testOptions{workerScript: "phpinfo.php"}) }
  286. func testPhpInfo(t *testing.T, opts *testOptions) {
  287. var logOnce sync.Once
  288. runTest(t, func(handler func(http.ResponseWriter, *http.Request), _ *httptest.Server, i int) {
  289. req := httptest.NewRequest("GET", fmt.Sprintf("http://example.com/phpinfo.php?i=%d", i), nil)
  290. w := httptest.NewRecorder()
  291. handler(w, req)
  292. resp := w.Result()
  293. body, _ := io.ReadAll(resp.Body)
  294. logOnce.Do(func() {
  295. t.Log(string(body))
  296. })
  297. assert.Contains(t, string(body), "frankenphp")
  298. assert.Contains(t, string(body), fmt.Sprintf("i=%d", i))
  299. }, opts)
  300. }
  301. func TestPersistentObject_module(t *testing.T) { testPersistentObject(t, nil) }
  302. func TestPersistentObject_worker(t *testing.T) {
  303. testPersistentObject(t, &testOptions{workerScript: "persistent-object.php"})
  304. }
  305. func testPersistentObject(t *testing.T, opts *testOptions) {
  306. runTest(t, func(handler func(http.ResponseWriter, *http.Request), _ *httptest.Server, i int) {
  307. req := httptest.NewRequest("GET", fmt.Sprintf("http://example.com/persistent-object.php?i=%d", i), nil)
  308. w := httptest.NewRecorder()
  309. handler(w, req)
  310. resp := w.Result()
  311. body, _ := io.ReadAll(resp.Body)
  312. assert.Equal(t, fmt.Sprintf(`request: %d
  313. class exists: 1
  314. id: obj1
  315. object id: 1`, i), string(body))
  316. }, opts)
  317. }
  318. func TestAutoloader_module(t *testing.T) { testAutoloader(t, nil) }
  319. func TestAutoloader_worker(t *testing.T) {
  320. testAutoloader(t, &testOptions{workerScript: "autoloader.php"})
  321. }
  322. func testAutoloader(t *testing.T, opts *testOptions) {
  323. runTest(t, func(handler func(http.ResponseWriter, *http.Request), _ *httptest.Server, i int) {
  324. req := httptest.NewRequest("GET", fmt.Sprintf("http://example.com/autoloader.php?i=%d", i), nil)
  325. w := httptest.NewRecorder()
  326. handler(w, req)
  327. resp := w.Result()
  328. body, _ := io.ReadAll(resp.Body)
  329. assert.Equal(t, fmt.Sprintf(`request %d
  330. my_autoloader`, i), string(body))
  331. }, opts)
  332. }
  333. func TestLog_module(t *testing.T) { testLog(t, &testOptions{}) }
  334. func TestLog_worker(t *testing.T) {
  335. testLog(t, &testOptions{workerScript: "log.php"})
  336. }
  337. func testLog(t *testing.T, opts *testOptions) {
  338. logger, logs := observer.New(zap.InfoLevel)
  339. opts.logger = zap.New(logger)
  340. runTest(t, func(handler func(http.ResponseWriter, *http.Request), _ *httptest.Server, i int) {
  341. req := httptest.NewRequest("GET", fmt.Sprintf("http://example.com/log.php?i=%d", i), nil)
  342. w := httptest.NewRecorder()
  343. handler(w, req)
  344. for logs.FilterMessage(fmt.Sprintf("request %d", i)).Len() <= 0 {
  345. }
  346. }, opts)
  347. }
  348. func TestConnectionAbort_module(t *testing.T) { testConnectionAbort(t, &testOptions{}) }
  349. func TestConnectionAbort_worker(t *testing.T) {
  350. testConnectionAbort(t, &testOptions{workerScript: "connectionStatusLog.php"})
  351. }
  352. func testConnectionAbort(t *testing.T, opts *testOptions) {
  353. testFinish := func(finish string) {
  354. t.Run(fmt.Sprintf("finish=%s", finish), func(t *testing.T) {
  355. logger, logs := observer.New(zap.InfoLevel)
  356. opts.logger = zap.New(logger)
  357. runTest(t, func(handler func(http.ResponseWriter, *http.Request), _ *httptest.Server, i int) {
  358. req := httptest.NewRequest("GET", fmt.Sprintf("http://example.com/connectionStatusLog.php?i=%d&finish=%s", i, finish), nil)
  359. w := httptest.NewRecorder()
  360. ctx, cancel := context.WithCancel(req.Context())
  361. req = req.WithContext(ctx)
  362. cancel()
  363. handler(w, req)
  364. for logs.FilterMessage(fmt.Sprintf("request %d: 1", i)).Len() <= 0 {
  365. }
  366. }, opts)
  367. })
  368. }
  369. testFinish("0")
  370. testFinish("1")
  371. }
  372. func TestException_module(t *testing.T) { testException(t, &testOptions{}) }
  373. func TestException_worker(t *testing.T) {
  374. testException(t, &testOptions{workerScript: "exception.php"})
  375. }
  376. func testException(t *testing.T, opts *testOptions) {
  377. runTest(t, func(handler func(http.ResponseWriter, *http.Request), _ *httptest.Server, i int) {
  378. req := httptest.NewRequest("GET", fmt.Sprintf("http://example.com/exception.php?i=%d", i), nil)
  379. w := httptest.NewRecorder()
  380. handler(w, req)
  381. resp := w.Result()
  382. body, _ := io.ReadAll(resp.Body)
  383. assert.Contains(t, string(body), "hello")
  384. assert.Contains(t, string(body), fmt.Sprintf(`Uncaught Exception: request %d`, i))
  385. }, opts)
  386. }
  387. func TestEarlyHints_module(t *testing.T) { testEarlyHints(t, &testOptions{}) }
  388. func TestEarlyHints_worker(t *testing.T) {
  389. testEarlyHints(t, &testOptions{workerScript: "early-hints.php"})
  390. }
  391. func testEarlyHints(t *testing.T, opts *testOptions) {
  392. runTest(t, func(handler func(http.ResponseWriter, *http.Request), _ *httptest.Server, i int) {
  393. var earlyHintReceived bool
  394. trace := &httptrace.ClientTrace{
  395. Got1xxResponse: func(code int, header textproto.MIMEHeader) error {
  396. switch code {
  397. case http.StatusEarlyHints:
  398. assert.Equal(t, "</style.css>; rel=preload; as=style", header.Get("Link"))
  399. assert.Equal(t, strconv.Itoa(i), header.Get("Request"))
  400. earlyHintReceived = true
  401. }
  402. return nil
  403. },
  404. }
  405. req := httptest.NewRequest("GET", fmt.Sprintf("http://example.com/early-hints.php?i=%d", i), nil)
  406. w := NewRecorder()
  407. w.ClientTrace = trace
  408. handler(w, req)
  409. assert.Equal(t, strconv.Itoa(i), w.Header().Get("Request"))
  410. assert.Equal(t, "", w.Header().Get("Link"))
  411. assert.True(t, earlyHintReceived)
  412. }, opts)
  413. }
  414. type streamResponseRecorder struct {
  415. *httptest.ResponseRecorder
  416. writeCallback func(buf []byte)
  417. }
  418. func (srr *streamResponseRecorder) Write(buf []byte) (int, error) {
  419. srr.writeCallback(buf)
  420. return srr.ResponseRecorder.Write(buf)
  421. }
  422. func TestFlush_module(t *testing.T) { testFlush(t, &testOptions{}) }
  423. func TestFlush_worker(t *testing.T) {
  424. testFlush(t, &testOptions{workerScript: "flush.php"})
  425. }
  426. func testFlush(t *testing.T, opts *testOptions) {
  427. runTest(t, func(handler func(http.ResponseWriter, *http.Request), _ *httptest.Server, i int) {
  428. var j int
  429. req := httptest.NewRequest("GET", fmt.Sprintf("http://example.com/flush.php?i=%d", i), nil)
  430. w := &streamResponseRecorder{httptest.NewRecorder(), func(buf []byte) {
  431. if j == 0 {
  432. assert.Equal(t, []byte("He"), buf)
  433. } else {
  434. assert.Equal(t, []byte(fmt.Sprintf("llo %d", i)), buf)
  435. }
  436. j++
  437. }}
  438. handler(w, req)
  439. assert.Equal(t, 2, j)
  440. }, opts)
  441. }
  442. func TestLargeRequest_module(t *testing.T) {
  443. testLargeRequest(t, &testOptions{})
  444. }
  445. func TestLargeRequest_worker(t *testing.T) {
  446. testLargeRequest(t, &testOptions{workerScript: "large-request.php"})
  447. }
  448. func testLargeRequest(t *testing.T, opts *testOptions) {
  449. runTest(t, func(handler func(http.ResponseWriter, *http.Request), _ *httptest.Server, i int) {
  450. req := httptest.NewRequest(
  451. "POST",
  452. fmt.Sprintf("http://example.com/large-request.php?i=%d", i),
  453. strings.NewReader(strings.Repeat("f", 1_048_576)),
  454. )
  455. w := httptest.NewRecorder()
  456. handler(w, req)
  457. resp := w.Result()
  458. body, _ := io.ReadAll(resp.Body)
  459. assert.Contains(t, string(body), fmt.Sprintf("Request body size: 1048576 (%d)", i))
  460. }, opts)
  461. }
  462. func TestVersion(t *testing.T) {
  463. v := frankenphp.Version()
  464. assert.GreaterOrEqual(t, v.MajorVersion, 8)
  465. assert.GreaterOrEqual(t, v.MinorVersion, 0)
  466. assert.GreaterOrEqual(t, v.ReleaseVersion, 0)
  467. assert.GreaterOrEqual(t, v.VersionID, 0)
  468. assert.NotEmpty(t, v.Version, 0)
  469. }
  470. func ExampleServeHTTP() {
  471. if err := frankenphp.Init(); err != nil {
  472. panic(err)
  473. }
  474. defer frankenphp.Shutdown()
  475. http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
  476. req := frankenphp.NewRequestWithContext(r, "/path/to/document/root", nil)
  477. if err := frankenphp.ServeHTTP(w, req); err != nil {
  478. panic(err)
  479. }
  480. })
  481. log.Fatal(http.ListenAndServe(":8080", nil))
  482. }
  483. func TestFiberNoCgo_module(t *testing.T) { testFiberNoCgo(t, &testOptions{}) }
  484. func TestFiberNonCgo_worker(t *testing.T) {
  485. testFiberNoCgo(t, &testOptions{workerScript: "fiber-no-cgo.php"})
  486. }
  487. func testFiberNoCgo(t *testing.T, opts *testOptions) {
  488. runTest(t, func(handler func(http.ResponseWriter, *http.Request), _ *httptest.Server, i int) {
  489. req := httptest.NewRequest("GET", fmt.Sprintf("http://example.com/fiber-no-cgo.php?i=%d", i), nil)
  490. w := httptest.NewRecorder()
  491. handler(w, req)
  492. resp := w.Result()
  493. body, _ := io.ReadAll(resp.Body)
  494. assert.Equal(t, string(body), fmt.Sprintf("Fiber %d", i))
  495. }, opts)
  496. }