github.com/symfony-cli/symfony-cli@v0.0.0-20240514161054-ece2df437dfa/local/php/cgi.go (about) 1 package php 2 3 import ( 4 "bytes" 5 "io" 6 "net/http" 7 "strconv" 8 "time" 9 10 "github.com/pkg/errors" 11 fcgiclient "github.com/symfony-cli/symfony-cli/local/fcgi_client" 12 ) 13 14 type cgiTransport struct{} 15 16 func (p *cgiTransport) RoundTrip(req *http.Request) (*http.Response, error) { 17 env := req.Context().Value(environmentContextKey).(map[string]string) 18 19 // as the process might have been just created, it might not be ready yet 20 var fcgi *fcgiclient.FCGIClient 21 var err error 22 max := 10 23 i := 0 24 for { 25 if fcgi, err = fcgiclient.Dial("tcp", "127.0.0.1:"+req.URL.Port()); err == nil { 26 break 27 } 28 i++ 29 if i > max { 30 return nil, errors.Wrapf(err, "unable to connect to the PHP FastCGI process") 31 } 32 time.Sleep(time.Millisecond * 50) 33 } 34 35 // The CGI spec doesn't allow chunked requests. Go is already assembling the 36 // chunks from the request to a usable Reader (see net/http.readTransfer and 37 // net/http/internal.NewChunkedReader), so the only thing we have to 38 // do to is get the content length and add it to the header but to do so we 39 // have to read and buffer the body content. 40 if len(req.TransferEncoding) > 0 && req.TransferEncoding[0] == "chunked" { 41 bodyBuffer := &bytes.Buffer{} 42 bodyBytes, err := io.Copy(bodyBuffer, req.Body) 43 if err != nil { 44 return nil, err 45 } 46 47 req.Body = io.NopCloser(bodyBuffer) 48 req.TransferEncoding = nil 49 env["CONTENT_LENGTH"] = strconv.FormatInt(bodyBytes, 10) 50 env["HTTP_CONTENT_LENGTH"] = env["CONTENT_LENGTH"] 51 } 52 53 // fetching the response from the fastcgi backend, and check for errors 54 resp, err := fcgi.Request(env, req.Body) 55 if err != nil { 56 return nil, errors.Wrapf(err, "unable to fetch the response from the backend") 57 } 58 resp.Body = cgiBodyReadCloser{resp.Body, fcgi} 59 resp.Request = req 60 61 return resp, nil 62 } 63 64 // cgiBodyReadCloser is responsible for postponing the CGI connection 65 // termination when the client finished reading the response. This effectively 66 // allows to "stream" the CGI response from the server to the client by removing 67 // the requirement for an in-between buffer. 68 type cgiBodyReadCloser struct { 69 io.Reader 70 *fcgiclient.FCGIClient 71 } 72 73 func (f cgiBodyReadCloser) Close() error { 74 f.FCGIClient.Close() 75 return nil 76 }