|
@@ -2,7 +2,7 @@ package frankenphp
|
|
|
|
|
|
// #cgo CFLAGS: -Wall -Wno-unused-variable
|
|
|
// #cgo CFLAGS: -I/usr/local/include/php -I/usr/local/include/php/Zend -I/usr/local/include/php/TSRM -I/usr/local/include/php/main
|
|
|
-// #cgo LDFLAGS: -L/usr/local/lib -lphp
|
|
|
+// #cgo LDFLAGS: -L/usr/local/lib -L/opt/homebrew/opt/libiconv/lib -L/usr/lib -lphp -lxml2 -liconv -lresolv -lsqlite3
|
|
|
// #include <stdlib.h>
|
|
|
// #include <stdint.h>
|
|
|
// #include "php_variables.h"
|
|
@@ -10,13 +10,11 @@ package frankenphp
|
|
|
import "C"
|
|
|
import (
|
|
|
"context"
|
|
|
- "crypto/tls"
|
|
|
"fmt"
|
|
|
"io"
|
|
|
"log"
|
|
|
- "net"
|
|
|
"net/http"
|
|
|
- "path/filepath"
|
|
|
+ "runtime"
|
|
|
"runtime/cgo"
|
|
|
"strconv"
|
|
|
"strings"
|
|
@@ -26,9 +24,13 @@ import (
|
|
|
|
|
|
var started int32
|
|
|
|
|
|
-type ContextKey string
|
|
|
+type key int
|
|
|
|
|
|
-const FrankenPHPContextKey ContextKey = "frankenphp"
|
|
|
+var contextKey key
|
|
|
+
|
|
|
+func init() {
|
|
|
+ log.SetFlags(log.LstdFlags | log.Lshortfile)
|
|
|
+}
|
|
|
|
|
|
// FrankenPHP executes PHP scripts.
|
|
|
type FrankenPHPContext struct {
|
|
@@ -56,10 +58,16 @@ type FrankenPHPContext struct {
|
|
|
// CGI-like environment variables that will be available in $_SERVER.
|
|
|
// This map is populated automatically, exisiting key are never replaced.
|
|
|
Env map[string]string
|
|
|
+
|
|
|
+ populated bool
|
|
|
+ authPassword string
|
|
|
+
|
|
|
+ responseWriter http.ResponseWriter
|
|
|
+ done chan interface{}
|
|
|
}
|
|
|
|
|
|
func NewRequestWithContext(r *http.Request, documentRoot string) *http.Request {
|
|
|
- ctx := context.WithValue(r.Context(), FrankenPHPContextKey, &FrankenPHPContext{
|
|
|
+ ctx := context.WithValue(r.Context(), contextKey, &FrankenPHPContext{
|
|
|
DocumentRoot: documentRoot,
|
|
|
SplitPath: []string{".php"},
|
|
|
Env: make(map[string]string),
|
|
@@ -68,67 +76,62 @@ func NewRequestWithContext(r *http.Request, documentRoot string) *http.Request {
|
|
|
return r.WithContext(ctx)
|
|
|
}
|
|
|
|
|
|
+func FromContext(ctx context.Context) (fctx *FrankenPHPContext, ok bool) {
|
|
|
+ fctx, ok = ctx.Value(contextKey).(*FrankenPHPContext)
|
|
|
+ return
|
|
|
+}
|
|
|
+
|
|
|
// Startup starts the PHP engine.
|
|
|
+// Startup and Shutdown must be called in the same goroutine (ideally in the main function).
|
|
|
func Startup() error {
|
|
|
if atomic.LoadInt32(&started) > 0 {
|
|
|
return nil
|
|
|
}
|
|
|
+ atomic.StoreInt32(&started, 1)
|
|
|
+
|
|
|
+ runtime.LockOSThread()
|
|
|
|
|
|
if C.frankenphp_init() < 0 {
|
|
|
return fmt.Errorf(`ZTS is not enabled, recompile PHP using the "--enable-zts" configuration option`)
|
|
|
}
|
|
|
- atomic.StoreInt32(&started, 1)
|
|
|
|
|
|
return nil
|
|
|
}
|
|
|
|
|
|
// Shutdown stops the PHP engine.
|
|
|
+// Shutdown and Startup must be called in the same goroutine (ideally in the main function).
|
|
|
func Shutdown() {
|
|
|
if atomic.LoadInt32(&started) < 1 {
|
|
|
return
|
|
|
}
|
|
|
+ atomic.StoreInt32(&started, 0)
|
|
|
|
|
|
C.frankenphp_shutdown()
|
|
|
- atomic.StoreInt32(&started, 0)
|
|
|
}
|
|
|
|
|
|
-func ExecuteScript(responseWriter http.ResponseWriter, request *http.Request) error {
|
|
|
- if atomic.LoadInt32(&started) < 1 {
|
|
|
- if err := Startup(); err != nil {
|
|
|
- return err
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- authPassword, err := populateEnv(request)
|
|
|
- if err != nil {
|
|
|
+func updateServerContext(request *http.Request) error {
|
|
|
+ if err := populateEnv(request); err != nil {
|
|
|
return err
|
|
|
}
|
|
|
|
|
|
- fc := request.Context().Value(FrankenPHPContextKey).(*FrankenPHPContext)
|
|
|
+ fc, ok := FromContext(request.Context())
|
|
|
+ if !ok {
|
|
|
+ panic("not a FrankenPHP request")
|
|
|
+ }
|
|
|
|
|
|
var cAuthUser, cAuthPassword *C.char
|
|
|
- if authPassword != "" {
|
|
|
- cAuthPassword = C.CString(authPassword)
|
|
|
- defer C.free(unsafe.Pointer(cAuthPassword))
|
|
|
+ if fc.authPassword != "" {
|
|
|
+ cAuthPassword = C.CString(fc.authPassword)
|
|
|
}
|
|
|
|
|
|
if authUser := fc.Env["REMOTE_USER"]; authUser != "" {
|
|
|
cAuthUser = C.CString(authUser)
|
|
|
- defer C.free(unsafe.Pointer(cAuthUser))
|
|
|
}
|
|
|
|
|
|
- wh := cgo.NewHandle(responseWriter)
|
|
|
- defer wh.Delete()
|
|
|
-
|
|
|
rh := cgo.NewHandle(request)
|
|
|
- defer rh.Delete()
|
|
|
|
|
|
cMethod := C.CString(request.Method)
|
|
|
- defer C.free(unsafe.Pointer(cMethod))
|
|
|
-
|
|
|
cQueryString := C.CString(request.URL.RawQuery)
|
|
|
- defer C.free(unsafe.Pointer(cQueryString))
|
|
|
-
|
|
|
contentLengthStr := request.Header.Get("Content-Length")
|
|
|
contentLength := 0
|
|
|
if contentLengthStr != "" {
|
|
@@ -139,20 +142,16 @@ func ExecuteScript(responseWriter http.ResponseWriter, request *http.Request) er
|
|
|
var cContentType *C.char
|
|
|
if contentType != "" {
|
|
|
cContentType = C.CString(contentType)
|
|
|
- defer C.free(unsafe.Pointer(cContentType))
|
|
|
}
|
|
|
|
|
|
var cPathTranslated *C.char
|
|
|
if pathTranslated := fc.Env["PATH_TRANSLATED"]; pathTranslated != "" {
|
|
|
cPathTranslated = C.CString(pathTranslated)
|
|
|
- defer C.free(unsafe.Pointer(cPathTranslated))
|
|
|
}
|
|
|
|
|
|
cRequestUri := C.CString(request.URL.RequestURI())
|
|
|
- defer C.free(unsafe.Pointer(cRequestUri))
|
|
|
|
|
|
- if C.frankenphp_request_startup(
|
|
|
- C.uintptr_t(wh),
|
|
|
+ C.frankenphp_update_server_context(
|
|
|
C.uintptr_t(rh),
|
|
|
|
|
|
cMethod,
|
|
@@ -164,289 +163,77 @@ func ExecuteScript(responseWriter http.ResponseWriter, request *http.Request) er
|
|
|
cAuthUser,
|
|
|
cAuthPassword,
|
|
|
C.int(request.ProtoMajor*1000+request.ProtoMinor),
|
|
|
- ) < 0 {
|
|
|
- return fmt.Errorf("error during PHP request startup")
|
|
|
- }
|
|
|
-
|
|
|
- cFileName := C.CString(fc.Env["SCRIPT_FILENAME"])
|
|
|
- defer C.free(unsafe.Pointer(cFileName))
|
|
|
-
|
|
|
- C.frankenphp_execute_script(cFileName)
|
|
|
- C.frankenphp_request_shutdown()
|
|
|
+ )
|
|
|
|
|
|
return nil
|
|
|
}
|
|
|
|
|
|
-// buildEnv 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) (authPassword string, err error) {
|
|
|
- fc := request.Context().Value(FrankenPHPContextKey).(*FrankenPHPContext)
|
|
|
-
|
|
|
- _, 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["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"
|
|
|
- }
|
|
|
-
|
|
|
- // 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
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- _, serverNameOk := fc.Env["SERVER_NAME"]
|
|
|
- _, serverPortOk := fc.Env["SERVER_PORT"]
|
|
|
- if !serverNameOk || !serverPortOk {
|
|
|
- reqHost, reqPort, err := net.SplitHostPort(request.Host)
|
|
|
- if err == nil {
|
|
|
- if !serverNameOk {
|
|
|
- fc.Env["SERVER_NAME"] = reqHost
|
|
|
- }
|
|
|
-
|
|
|
- // compliance with the CGI specification requires that
|
|
|
- // SERVER_PORT should only exist if it's a valid numeric value.
|
|
|
- // Info: https://www.ietf.org/rfc/rfc3875 Page 18
|
|
|
- if !serverPortOk && reqPort != "" {
|
|
|
- fc.Env["SERVER_PORT"] = reqPort
|
|
|
- }
|
|
|
- } else if !serverNameOk {
|
|
|
- // whatever, just assume there was no port
|
|
|
- fc.Env["SERVER_NAME"] = request.Host
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- // 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()
|
|
|
+func ExecuteScript(responseWriter http.ResponseWriter, request *http.Request) error {
|
|
|
+ if atomic.LoadInt32(&started) < 1 {
|
|
|
+ panic("FrankenPHP isn't started, call frankenphp.Startup()")
|
|
|
}
|
|
|
|
|
|
- // 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
|
|
|
- }
|
|
|
+ runtime.LockOSThread()
|
|
|
+ // todo: check if it's ok or not to call runtime.UnlockOSThread() to reuse this thread
|
|
|
|
|
|
- // 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, ", ")
|
|
|
- }
|
|
|
+ if C.frankenphp_create_server_context(0, nil) < 0 {
|
|
|
+ return fmt.Errorf("error during request context creation")
|
|
|
}
|
|
|
|
|
|
- if _, ok := fc.Env["REMOTE_USER"]; !ok {
|
|
|
- var (
|
|
|
- authUser string
|
|
|
- ok bool
|
|
|
- )
|
|
|
- authUser, authPassword, ok = request.BasicAuth()
|
|
|
- if ok {
|
|
|
- fc.Env["REMOTE_USER"] = authUser
|
|
|
- }
|
|
|
+ if err := updateServerContext(request); err != nil {
|
|
|
+ return err
|
|
|
}
|
|
|
|
|
|
- return authPassword, nil
|
|
|
-}
|
|
|
-
|
|
|
-// splitPos returns the index where path should
|
|
|
-// be split based on SplitPath.
|
|
|
-//
|
|
|
-// 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 {
|
|
|
- return 0
|
|
|
+ if C.frankenphp_request_startup() < 0 {
|
|
|
+ return fmt.Errorf("error during PHP request startup")
|
|
|
}
|
|
|
|
|
|
- lowerPath := strings.ToLower(path)
|
|
|
- for _, split := range fc.SplitPath {
|
|
|
- if idx := strings.Index(lowerPath, strings.ToLower(split)); idx > -1 {
|
|
|
- return idx + len(split)
|
|
|
- }
|
|
|
- }
|
|
|
- return -1
|
|
|
-}
|
|
|
+ fc := request.Context().Value(contextKey).(*FrankenPHPContext)
|
|
|
+ fc.responseWriter = responseWriter
|
|
|
|
|
|
-// Map of supported protocols to Apache ssl_mod format
|
|
|
-// Note that these are slightly different from SupportedProtocols in caddytls/config.go
|
|
|
-var tlsProtocolStrings = map[uint16]string{
|
|
|
- tls.VersionTLS10: "TLSv1",
|
|
|
- tls.VersionTLS11: "TLSv1.1",
|
|
|
- tls.VersionTLS12: "TLSv1.2",
|
|
|
- tls.VersionTLS13: "TLSv1.3",
|
|
|
-}
|
|
|
+ cFileName := C.CString(fc.Env["SCRIPT_FILENAME"])
|
|
|
+ defer C.free(unsafe.Pointer(cFileName))
|
|
|
|
|
|
-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
|
|
|
-// in the implementation of http.Dir. The root is assumed to
|
|
|
-// be a trusted path, but reqPath is not; and the output will
|
|
|
-// never be outside of root. The resulting path can be used
|
|
|
-// with the local file system.
|
|
|
-//
|
|
|
-// Adapted from https://github.com/caddyserver/caddy/blob/master/modules/caddyhttp/reverseproxy/fastcgi/fastcgi.go
|
|
|
-// Copyright 2015 Matthew Holt and The Caddy Authors
|
|
|
-func sanitizedPathJoin(root, reqPath string) string {
|
|
|
- if root == "" {
|
|
|
- root = "."
|
|
|
+ if C.frankenphp_execute_script(cFileName) < 0 {
|
|
|
+ return fmt.Errorf("error during PHP script execution")
|
|
|
}
|
|
|
|
|
|
- path := filepath.Join(root, filepath.Clean("/"+reqPath))
|
|
|
+ rh := C.frankenphp_clean_server_context()
|
|
|
+ C.frankenphp_request_shutdown()
|
|
|
|
|
|
- // filepath.Join also cleans the path, and cleaning strips
|
|
|
- // the trailing slash, so we need to re-add it afterwards.
|
|
|
- // 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 {
|
|
|
- path += separator
|
|
|
- }
|
|
|
+ cgo.Handle(rh).Delete()
|
|
|
|
|
|
- return path
|
|
|
+ return nil
|
|
|
}
|
|
|
|
|
|
-const separator = string(filepath.Separator)
|
|
|
-
|
|
|
//export go_ub_write
|
|
|
-func go_ub_write(wh C.uintptr_t, cString *C.char, length C.int) C.size_t {
|
|
|
- w := cgo.Handle(wh).Value().(http.ResponseWriter)
|
|
|
- i, _ := w.Write([]byte(C.GoStringN(cString, length)))
|
|
|
+func go_ub_write(rh C.uintptr_t, cString *C.char, length C.int) C.size_t {
|
|
|
+ r := cgo.Handle(rh).Value().(*http.Request)
|
|
|
+ fc := r.Context().Value(contextKey).(*FrankenPHPContext)
|
|
|
+
|
|
|
+ i, _ := fc.responseWriter.Write([]byte(C.GoStringN(cString, length)))
|
|
|
|
|
|
return C.size_t(i)
|
|
|
}
|
|
|
|
|
|
//export go_register_variables
|
|
|
func go_register_variables(rh C.uintptr_t, trackVarsArray *C.zval) {
|
|
|
- r := cgo.Handle(rh).Value().(*http.Request)
|
|
|
- for k, v := range r.Context().Value(FrankenPHPContextKey).(*FrankenPHPContext).Env {
|
|
|
+ var env map[string]string
|
|
|
+ if rh == 0 {
|
|
|
+ // Worker mode, waiting for a request, initialize some useful variables
|
|
|
+ env = map[string]string{"FRANKENPHP_WORKER": "1"}
|
|
|
+ } else {
|
|
|
+ r := cgo.Handle(rh).Value().(*http.Request)
|
|
|
+ env = r.Context().Value(contextKey).(*FrankenPHPContext).Env
|
|
|
+ }
|
|
|
+
|
|
|
+ env[fmt.Sprintf("REQUEST_%d", rh)] = "on"
|
|
|
+
|
|
|
+ for k, v := range env {
|
|
|
ck := C.CString(k)
|
|
|
cv := C.CString(v)
|
|
|
- C.php_register_variable_safe(ck, cv, C.size_t(len(v)), trackVarsArray)
|
|
|
+
|
|
|
+ C.php_register_variable(ck, cv, trackVarsArray)
|
|
|
|
|
|
C.free(unsafe.Pointer(ck))
|
|
|
C.free(unsafe.Pointer(cv))
|
|
@@ -454,8 +241,9 @@ func go_register_variables(rh C.uintptr_t, trackVarsArray *C.zval) {
|
|
|
}
|
|
|
|
|
|
//export go_add_header
|
|
|
-func go_add_header(wh C.uintptr_t, cString *C.char, length C.int) {
|
|
|
- w := cgo.Handle(wh).Value().(http.ResponseWriter)
|
|
|
+func go_add_header(rh C.uintptr_t, cString *C.char, length C.int) {
|
|
|
+ r := cgo.Handle(rh).Value().(*http.Request)
|
|
|
+ fc := r.Context().Value(contextKey).(*FrankenPHPContext)
|
|
|
|
|
|
parts := strings.SplitN(C.GoStringN(cString, length), ": ", 2)
|
|
|
if len(parts) != 2 {
|
|
@@ -464,13 +252,15 @@ func go_add_header(wh C.uintptr_t, cString *C.char, length C.int) {
|
|
|
return
|
|
|
}
|
|
|
|
|
|
- w.Header().Add(parts[0], parts[1])
|
|
|
+ fc.responseWriter.Header().Add(parts[0], parts[1])
|
|
|
}
|
|
|
|
|
|
//export go_write_header
|
|
|
-func go_write_header(wh C.uintptr_t, status C.int) {
|
|
|
- w := cgo.Handle(wh).Value().(http.ResponseWriter)
|
|
|
- w.WriteHeader(int(status))
|
|
|
+func go_write_header(rh C.uintptr_t, status C.int) {
|
|
|
+ r := cgo.Handle(rh).Value().(*http.Request)
|
|
|
+ fc := r.Context().Value(contextKey).(*FrankenPHPContext)
|
|
|
+
|
|
|
+ fc.responseWriter.WriteHeader(int(status))
|
|
|
}
|
|
|
|
|
|
//export go_read_post
|
|
@@ -484,6 +274,7 @@ func go_read_post(rh C.uintptr_t, cBuf *C.char, countBytes C.size_t) C.size_t {
|
|
|
}
|
|
|
|
|
|
if readBytes != 0 {
|
|
|
+ // todo: memory leak?
|
|
|
C.memcpy(unsafe.Pointer(cBuf), unsafe.Pointer(&p[0]), C.size_t(readBytes))
|
|
|
}
|
|
|
|