123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261 |
- // Package frankenphp provides a PHP module for Caddy.
- // 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 (
- "log"
- "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 {
- log.Print("Destructor called")
- 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 {
- var opts []frankenphp.Option
- if f.NumThreads != 0 {
- opts = append(opts, frankenphp.WithNumThreads(f.NumThreads))
- }
- for _, w := range f.Workers {
- num := 1
- if w.Num > 1 {
- num = w.Num
- }
- opts = append(opts, frankenphp.WithWorkers(w.FileName, 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
- }
- }
- log.Print("FrankenPHP started")
- return nil
- }
- func (*FrankenPHPApp) Stop() error {
- //frankenphp.Shutdown()
- log.Print("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)
- 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)
- )
|