mirror of
https://github.com/dunglas/frankenphp
synced 2024-10-16 17:14:59 +00:00
253 lines
6.2 KiB
Go
253 lines
6.2 KiB
Go
// Package caddy provides a PHP module for the Caddy web server.
|
|
// FrankenPHP embeds the PHP interpreter directly in Caddy, giving it the ability to run your PHP scripts directly.
|
|
// No PHP FPM required!
|
|
package caddy
|
|
|
|
import (
|
|
"net/http"
|
|
"strconv"
|
|
|
|
"github.com/caddyserver/caddy/v2"
|
|
"github.com/caddyserver/caddy/v2/caddyconfig"
|
|
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
|
"github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile"
|
|
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
|
"github.com/dunglas/frankenphp"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
func init() {
|
|
caddy.RegisterModule(&FrankenPHPApp{})
|
|
caddy.RegisterModule(FrankenPHPModule{})
|
|
httpcaddyfile.RegisterGlobalOption("frankenphp", parseGlobalOption)
|
|
httpcaddyfile.RegisterHandlerDirective("php", parseCaddyfile)
|
|
}
|
|
|
|
type mainPHPinterpreterKeyType int
|
|
|
|
var mainPHPInterpreterKey mainPHPinterpreterKeyType
|
|
|
|
var phpInterpreter = caddy.NewUsagePool()
|
|
|
|
type phpInterpreterDestructor struct{}
|
|
|
|
func (phpInterpreterDestructor) Destruct() error {
|
|
frankenphp.Shutdown()
|
|
|
|
return nil
|
|
}
|
|
|
|
type workerConfig struct {
|
|
FileName string `json:"file_name,omitempty"`
|
|
Num int `json:"num,omitempty"`
|
|
}
|
|
|
|
type FrankenPHPApp struct {
|
|
NumThreads int `json:"num_threads,omitempty"`
|
|
Workers []workerConfig `json:"workers,omitempty"`
|
|
}
|
|
|
|
// CaddyModule returns the Caddy module information.
|
|
func (a *FrankenPHPApp) CaddyModule() caddy.ModuleInfo {
|
|
return caddy.ModuleInfo{
|
|
ID: "frankenphp",
|
|
New: func() caddy.Module { return a },
|
|
}
|
|
}
|
|
|
|
func (f *FrankenPHPApp) Start() error {
|
|
repl := caddy.NewReplacer()
|
|
logger := caddy.Log()
|
|
|
|
opts := []frankenphp.Option{frankenphp.WithNumThreads(f.NumThreads), frankenphp.WithLogger(logger)}
|
|
for _, w := range f.Workers {
|
|
opts = append(opts, frankenphp.WithWorkers(repl.ReplaceKnown(w.FileName, ""), w.Num))
|
|
}
|
|
|
|
_, loaded, err := phpInterpreter.LoadOrNew(mainPHPInterpreterKey, func() (caddy.Destructor, error) {
|
|
if err := frankenphp.Init(opts...); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return phpInterpreterDestructor{}, nil
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if loaded {
|
|
frankenphp.Shutdown()
|
|
if err := frankenphp.Init(opts...); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
logger.Info("FrankenPHP started 🐘", zap.String("php_version", frankenphp.Version().Version))
|
|
|
|
return nil
|
|
}
|
|
|
|
func (*FrankenPHPApp) Stop() error {
|
|
caddy.Log().Info("FrankenPHP stopped 🐘")
|
|
|
|
return nil
|
|
}
|
|
|
|
// UnmarshalCaddyfile implements caddyfile.Unmarshaler.
|
|
func (f *FrankenPHPApp) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
|
for d.Next() {
|
|
for d.NextBlock(0) {
|
|
switch d.Val() {
|
|
case "num_threads":
|
|
if !d.NextArg() {
|
|
return d.ArgErr()
|
|
}
|
|
|
|
v, err := strconv.Atoi(d.Val())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
f.NumThreads = v
|
|
|
|
case "worker":
|
|
if !d.NextArg() {
|
|
return d.ArgErr()
|
|
}
|
|
|
|
wc := workerConfig{FileName: d.Val()}
|
|
if d.NextArg() {
|
|
v, err := strconv.Atoi(d.Val())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
wc.Num = v
|
|
}
|
|
|
|
f.Workers = append(f.Workers, wc)
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func parseGlobalOption(d *caddyfile.Dispenser, _ interface{}) (interface{}, error) {
|
|
app := &FrankenPHPApp{}
|
|
if err := app.UnmarshalCaddyfile(d); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// tell Caddyfile adapter that this is the JSON for an app
|
|
return httpcaddyfile.App{
|
|
Name: "frankenphp",
|
|
Value: caddyconfig.JSON(app, nil),
|
|
}, nil
|
|
}
|
|
|
|
type FrankenPHPModule struct {
|
|
Root string `json:"root,omitempty"`
|
|
SplitPath []string `json:"split_path,omitempty"`
|
|
ResolveRootSymlink bool `json:"resolve_root_symlink,omitempty"`
|
|
Env map[string]string `json:"env,omitempty"`
|
|
logger *zap.Logger
|
|
}
|
|
|
|
// CaddyModule returns the Caddy module information.
|
|
func (FrankenPHPModule) CaddyModule() caddy.ModuleInfo {
|
|
return caddy.ModuleInfo{
|
|
ID: "http.handlers.php",
|
|
New: func() caddy.Module { return new(FrankenPHPModule) },
|
|
}
|
|
}
|
|
|
|
// Provision sets up the module.
|
|
func (f *FrankenPHPModule) Provision(ctx caddy.Context) error {
|
|
f.logger = ctx.Logger(f)
|
|
|
|
if f.Root == "" {
|
|
f.Root = "{http.vars.root}"
|
|
}
|
|
if len(f.SplitPath) == 0 {
|
|
f.SplitPath = []string{".php"}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// ServeHTTP implements caddyhttp.MiddlewareHandler.
|
|
// TODO: Expose TLS versions as env vars, as Apache's mod_ssl: https://github.com/caddyserver/caddy/blob/master/modules/caddyhttp/reverseproxy/fastcgi/fastcgi.go#L298
|
|
func (f FrankenPHPModule) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error {
|
|
origReq := r.Context().Value(caddyhttp.OriginalRequestCtxKey).(http.Request)
|
|
repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
|
|
|
|
documentRoot := repl.ReplaceKnown(f.Root, "")
|
|
fr := frankenphp.NewRequestWithContext(r, documentRoot, f.logger)
|
|
fc, _ := frankenphp.FromContext(fr.Context())
|
|
fc.ResolveRootSymlink = f.ResolveRootSymlink
|
|
fc.SplitPath = f.SplitPath
|
|
|
|
fc.Env["REQUEST_URI"] = origReq.URL.RequestURI()
|
|
for k, v := range f.Env {
|
|
fc.Env[k] = repl.ReplaceKnown(v, "")
|
|
}
|
|
|
|
return frankenphp.ServeHTTP(w, fr)
|
|
}
|
|
|
|
// UnmarshalCaddyfile implements caddyfile.Unmarshaler.
|
|
func (f *FrankenPHPModule) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
|
for d.Next() {
|
|
for d.NextBlock(0) {
|
|
switch d.Val() {
|
|
case "root":
|
|
if !d.NextArg() {
|
|
return d.ArgErr()
|
|
}
|
|
f.Root = d.Val()
|
|
|
|
case "split":
|
|
f.SplitPath = d.RemainingArgs()
|
|
if len(f.SplitPath) == 0 {
|
|
return d.ArgErr()
|
|
}
|
|
|
|
case "env":
|
|
args := d.RemainingArgs()
|
|
if len(args) != 2 {
|
|
return d.ArgErr()
|
|
}
|
|
if f.Env == nil {
|
|
f.Env = make(map[string]string)
|
|
}
|
|
f.Env[args[0]] = args[1]
|
|
|
|
case "resolve_root_symlink":
|
|
if d.NextArg() {
|
|
return d.ArgErr()
|
|
}
|
|
f.ResolveRootSymlink = true
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// parseCaddyfile unmarshals tokens from h into a new Middleware.
|
|
func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) {
|
|
var m FrankenPHPModule
|
|
err := m.UnmarshalCaddyfile(h.Dispenser)
|
|
|
|
return m, err
|
|
}
|
|
|
|
// Interface guards
|
|
var (
|
|
_ caddy.App = (*FrankenPHPApp)(nil)
|
|
_ caddy.Provisioner = (*FrankenPHPModule)(nil)
|
|
_ caddyhttp.MiddlewareHandler = (*FrankenPHPModule)(nil)
|
|
_ caddyfile.Unmarshaler = (*FrankenPHPModule)(nil)
|
|
)
|