initial commit

This commit is contained in:
Kévin Dunglas
2021-09-24 18:52:20 +02:00
commit a2607e6be7
14 changed files with 2096 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
.vscode/

5
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,5 @@
# Contributing
## Compiling PHP
Pass the `--enable-debug` flag to compile PHP with debugging symbols.

38
Dockerfile Normal file
View File

@@ -0,0 +1,38 @@
FROM golang
ARG PHP_VERSION=8.0.11
# Sury doesn't provide ZTS builds for now
#RUN apt-get update && \
# apt-get -y --no-install-recommends install apt-transport-https lsb-release&& \
# wget -O /etc/apt/trusted.gpg.d/php.gpg https://packages.sury.org/php/apt.gpg && \
# sh -c 'echo "deb https://packages.sury.org/php/ $(lsb_release -sc) main" > /etc/apt/sources.list.d/php.list' && \
# apt-get update && \
# apt-get -y --no-install-recommends install php8.0-dev && \
# apt-get -y remove apt-transport-https lsb-release && \
# apt-get clean all
#ENV CGO_CFLAGS="-I /usr/include/php/20200930 -I /usr/include/php/20200930/Zend -I /usr/include/php/20200930/TSRM -I /usr/include/php/20200930/main -I /usr/include/php/20200930/sapi/embed"
# TODO: check the downloaded package using the provided GPG signatures
RUN apt-get update && \
apt-get -y --no-install-recommends install libxml2 libxml2-dev sqlite3 libsqlite3-dev && \
apt-get clean && \
curl -s -o php-${PHP_VERSION}.tar.gz https://www.php.net/distributions/php-${PHP_VERSION}.tar.gz && \
tar -xf php-${PHP_VERSION}.tar.gz && \
cd php-${PHP_VERSION}/ && \
# --enable-embed is only necessary to generate libphp.so, we don't use this SAPI directly
./configure --enable-zts --enable-embed --enable-debug && \
make && \
make install && \
rm -Rf php-${PHP_VERSION}/ php-${PHP_VERSION}.tar.gz
ENV LD_LIBRARY_PATH=/usr/local/lib/
WORKDIR /go/src/app
COPY . .
RUN go get -d -v ./...
#RUN go build -v
#RUN cd cmd/frankenphp && go install -v ./...
#CMD ["frankenphp"]

79
README.md Normal file
View File

@@ -0,0 +1,79 @@
# Caddy PHP
## Install
### Docker
The easiest way to get started is to use our Docker image:
```
docker build -t frankenphp .
```
### Compile fron Sources
#### Install PHP
Most distributions doesn't provide packages containing ZTS builds of PHP.
Because the Go HTTP server uses goroutines, a ZTS build is needed.
Start by [downloading the latest version of PHP](https://www.php.net/downloads.php),
then follow the instructions according to your operating system.
##### Linux
```
./configure --enable-embed --enable-zts
make
make install
```
##### Mac
```
brew install libiconv
./configure \
--enable-zts \
--enable-embed=dylib \
--with-iconv=/opt/homebrew/opt/libiconv/ \
--without-pcre-jit
make
make install
```
Then, you also need to build a Mac-compatible PHP shared library.
As the standard PHP distribution doesn't provide one, you need to do a few extra steps:
Start by adding those lines at the end of the `Makefile`:
```
libs/libphp.dylib: $(PHP_GLOBAL_OBJS) $(PHP_SAPI_OBJS)
$(LIBTOOL) --mode=link $(CC) -dynamiclib $(LIBPHP_CFLAGS) $(CFLAGS_CLEAN) $(EXTRA_CFLAGS) -rpath $(phptempdir) $(EXTRA_LDFLAGS) $(LDFLAGS) $(PHP_RPATHS) $(PHP_GLOBAL_OBJS) $(PHP_SAPI_OBJS) $(EXTRA_LIBS) $(ZEND_EXTRA_LIBS) -o $@
-@$(LIBTOOL) --silent --mode=install cp $@ $(phptempdir)/$@ >/dev/null 2>&1
```
Then run:
```
make libs/libphp.dylib
sudo cp libs/libphp.dylib /usr/local/lib/
```
#### Compile the Go App
```
go get -d -v ./...
go build -v
```
## Misc Dev Resources
* [PHP embedding in uWSGI](https://github.com/unbit/uwsgi/blob/master/plugins/php/php_plugin.c)
* [PHP embedding in NGINX Unit](https://github.com/nginx/unit/blob/master/src/nxt_php_sapi.c)
* [PHP embedding in Go (go-php)](https://github.com/deuill/go-php)
* [PHP embedding in Go (GoEmPHP)](https://github.com/mikespook/goemphp)
* [PHP embedding in C++](https://gist.github.com/paresy/3cbd4c6a469511ac7479aa0e7c42fea7)
* [Extending and Embedding PHP by Sara Golemon](https://books.google.fr/books?id=zMbGvK17_tYC&pg=PA254&lpg=PA254#v=onepage&q&f=false)
* [What the heck is TSRMLS_CC, anyway?](http://blog.golemon.com/2006/06/what-heck-is-tsrmlscc-anyway.html)
* [PHP embedding on Mac](https://gist.github.com/jonnywang/61427ffc0e8dde74fff40f479d147db4)

71
caddy.go Normal file
View File

@@ -0,0 +1,71 @@
// Package frankenphp provide 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 frankenphp
import (
"net/http"
"github.com/caddyserver/caddy/v2"
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
"github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile"
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
"go.uber.org/zap"
)
func init() {
caddy.RegisterModule(FrankenPHP{})
httpcaddyfile.RegisterHandlerDirective("php", parseCaddyfile)
}
type FrankenPHP struct {
logger *zap.Logger
}
// CaddyModule returns the Caddy module information.
func (FrankenPHP) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
ID: "http.handlers.php",
New: func() caddy.Module { return new(FrankenPHP) },
}
}
// Provision sets up the module.
func (f *FrankenPHP) Provision(ctx caddy.Context) error {
f.logger = ctx.Logger(f)
Startup()
return nil
}
func (m *FrankenPHP) Cleanup() error {
Shutdown()
return nil
}
// ServeHTTP implements caddyhttp.MiddlewareHandler.
func (f FrankenPHP) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error {
w.Write([]byte("Hello World"))
return nil
}
// UnmarshalCaddyfile implements caddyfile.Unmarshaler.
func (m *FrankenPHP) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
return nil
}
// parseCaddyfile unmarshals tokens from h into a new Middleware.
func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) {
var m FrankenPHP
err := m.UnmarshalCaddyfile(h.Dispenser)
return m, err
}
// Interface guards
var (
_ caddy.Provisioner = (*FrankenPHP)(nil)
_ caddy.CleanerUpper = (*FrankenPHP)(nil)
_ caddyhttp.MiddlewareHandler = (*FrankenPHP)(nil)
_ caddyfile.Unmarshaler = (*FrankenPHP)(nil)
)

27
caddy_test.go Normal file
View File

@@ -0,0 +1,27 @@
package frankenphp_test
import (
"net/http"
"testing"
"github.com/caddyserver/caddy/v2/caddytest"
)
func TestPHP(t *testing.T) {
tester := caddytest.NewTester(t)
tester.InitServer(`
{
http_port 9080
https_port 9443
}
localhost:9080 {
route {
php
respond 404
}
}
`, "caddyfile")
tester.AssertGetResponse("http://localhost:9080", http.StatusOK, "Hello World")
}

4
cmd/frankenphp/main.go Normal file
View File

@@ -0,0 +1,4 @@
package main
func main() {
}

146
frankenphp.c Normal file
View File

@@ -0,0 +1,146 @@
#include <errno.h>
#include "php.h"
#include "SAPI.h"
#include "php_main.h"
#include "php_variables.h"
static int frankenphp_startup(sapi_module_struct *sapi_module)
{
return php_module_startup(sapi_module, NULL, 0);
}
static int frankenphp_deactivate(void)
{
// TODO: flush everything
return SUCCESS;
}
static size_t frankenphp_ub_write(const char *str, size_t str_length)
{
// TODO: call the Go writer
return str_length;
}
static void frankenphp_flush(void *server_context)
{
// TODO: call the Go flusher
}
static void frankenphp_send_header(sapi_header_struct *sapi_header, void *server_context)
{
// TODO: call the Go set header
}
static char* frankenphp_read_cookies(void)
{
return NULL;
}
static void frankenphp_register_variables(zval *track_vars_array)
{
php_import_environment_variables(track_vars_array);
}
static void frankenphp_log_message(const char *message, int syslog_type_int)
{
// TODO: call Go logger
}
sapi_module_struct frankenphp_module = {
"frankenphp", /* name */
"FrankenPHP SAPI for Go and Caddy", /* pretty name */
frankenphp_startup, /* startup */
php_module_shutdown_wrapper, /* shutdown */
NULL, /* activate */
frankenphp_deactivate, /* deactivate */
frankenphp_ub_write, /* unbuffered write */
frankenphp_flush, /* flush */
NULL, /* get uid */
NULL, /* getenv */
php_error, /* error handler */
NULL, /* header handler */
NULL, /* send headers handler */
frankenphp_send_header, /* send header handler */
NULL, /* read POST data */
frankenphp_read_cookies, /* read Cookies */
frankenphp_register_variables, /* register server variables */
frankenphp_log_message, /* Log message */
NULL, /* Get request time */
NULL, /* Child terminate */
STANDARD_SAPI_MODULE_PROPERTIES
};
int frankenphp_init() {
#ifndef ZTS
return FAILURE;
#endif
// TODO: copied from php_embed.c, check if this is really necessary in our case
signal(SIGPIPE, SIG_IGN); /* ignore SIGPIPE in standalone mode so
that sockets created via fsockopen()
don't kill PHP if the remote site
closes it. in apache|apxs mode apache
does that for us! thies@thieso.net
20000419 */
php_tsrm_startup();
# ifdef PHP_WIN32
ZEND_TSRMLS_CACHE_UPDATE();
# endif
zend_signal_startup();
sapi_startup(&frankenphp_module);
if (frankenphp_module.startup(&frankenphp_module) == FAILURE) {
return FAILURE;
}
return SUCCESS;
}
void frankenphp_shutdown()
{
php_module_shutdown();
sapi_shutdown();
tsrm_shutdown();
}
int frankenphp_request_startup() {
if (php_request_startup() == FAILURE) {
php_request_shutdown(NULL);
return FAILURE;
}
return SUCCESS;
}
int frankenphp_execute_script(const char* file_name)
{
int status = FAILURE;
zend_file_handle file_handle;
zend_stream_init_filename(&file_handle, file_name);
zend_first_try {
status = php_execute_script(&file_handle);
} zend_catch {
/* int exit_status = EG(exit_status); */ \
} zend_end_try();
return status;
}
void frankenphp_request_shutdown()
{
php_request_shutdown(NULL);
}

38
frankenphp.go Normal file
View File

@@ -0,0 +1,38 @@
package frankenphp
// #cgo CFLAGS: -Wall -Wno-unused-variable -g
// #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
// #include <stdlib.h>
// #include "frankenphp.h"
import "C"
import (
"fmt"
"unsafe"
)
func Startup() error {
if C.frankenphp_init() < 0 {
return fmt.Errorf(`ZTS is not enabled, recompile PHP using the "--enable-zts" configuration option`)
}
return nil
}
func Shutdown() {
C.frankenphp_shutdown()
}
func ExecuteScript(fileName string) error {
if C.frankenphp_request_startup() < 0 {
return fmt.Errorf("error during PHP request startup")
}
cFileName := C.CString(fileName)
defer C.free(unsafe.Pointer(cFileName))
C.frankenphp_execute_script(cFileName)
C.frankenphp_request_shutdown()
return nil
}

13
frankenphp.h Normal file
View File

@@ -0,0 +1,13 @@
#ifndef _FRANKENPPHP_H
#define _FRANKENPPHP_H
int frankenphp_init();
void frankenphp_shutdown();
int frankenphp_request_startup();
int frankenphp_request_shutdown();
int frankenphp_execute_script(const char* file_name);
int frankenphp_request_shutdown();
#endif

21
frankenphp_test.go Normal file
View File

@@ -0,0 +1,21 @@
package frankenphp_test
import (
"testing"
"github.com/dunglas/frankenphp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestStartup(t *testing.T) {
defer frankenphp.Shutdown()
assert.Nil(t, frankenphp.Startup())
}
func TestExecuteString(t *testing.T) {
defer frankenphp.Shutdown()
require.Nil(t, frankenphp.Startup())
assert.Nil(t, frankenphp.ExecuteScript("testdata/index.php"))
}

113
go.mod Normal file
View File

@@ -0,0 +1,113 @@
module github.com/dunglas/frankenphp
go 1.17
require (
github.com/caddyserver/caddy/v2 v2.4.5
go.uber.org/zap v1.19.1
)
require (
github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 // indirect
github.com/Masterminds/goutils v1.1.1 // indirect
github.com/Masterminds/semver/v3 v3.1.1 // indirect
github.com/Masterminds/sprig/v3 v3.2.2 // indirect
github.com/alecthomas/chroma v0.9.2 // indirect
github.com/antlr/antlr4 v0.0.0-20200503195918-621b933c7a7f // indirect
github.com/aryann/difflib v0.0.0-20210328193216-ff5ff6dc229b // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/caddyserver/certmagic v0.14.5 // indirect
github.com/cespare/xxhash v1.1.0 // indirect
github.com/cespare/xxhash/v2 v2.1.1 // indirect
github.com/cheekybits/genny v1.0.0 // indirect
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect
github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dgraph-io/badger v1.6.2 // indirect
github.com/dgraph-io/badger/v2 v2.2007.4 // indirect
github.com/dgraph-io/ristretto v0.0.4-0.20200906165740-41ebdbffecfd // indirect
github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 // indirect
github.com/dlclark/regexp2 v1.4.0 // indirect
github.com/dustin/go-humanize v1.0.1-0.20200219035652-afde56e7acac // indirect
github.com/fsnotify/fsnotify v1.4.9 // indirect
github.com/go-chi/chi v4.1.2+incompatible // indirect
github.com/go-kit/kit v0.10.0 // indirect
github.com/go-logfmt/logfmt v0.5.0 // indirect
github.com/go-sql-driver/mysql v1.6.0 // indirect
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/golang/snappy v0.0.3 // indirect
github.com/google/cel-go v0.7.3 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/huandu/xstrings v1.3.1 // indirect
github.com/imdario/mergo v0.3.11 // indirect
github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a // indirect
github.com/klauspost/compress v1.13.4 // indirect
github.com/klauspost/cpuid/v2 v2.0.9 // indirect
github.com/libdns/libdns v0.2.1 // indirect
github.com/lucas-clemente/quic-go v0.23.0 // indirect
github.com/lunixbochs/vtclean v1.0.0 // indirect
github.com/manifoldco/promptui v0.8.0 // indirect
github.com/marten-seemann/qpack v0.2.1 // indirect
github.com/marten-seemann/qtls-go1-16 v0.1.4 // indirect
github.com/marten-seemann/qtls-go1-17 v0.1.0 // indirect
github.com/mattn/go-colorable v0.1.8 // indirect
github.com/mattn/go-isatty v0.0.13 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
github.com/mholt/acmez v1.0.0 // indirect
github.com/micromdm/scep/v2 v2.0.0 // indirect
github.com/miekg/dns v1.1.42 // indirect
github.com/mitchellh/copystructure v1.0.0 // indirect
github.com/mitchellh/reflectwalk v1.0.1 // indirect
github.com/naoina/go-stringutil v0.1.0 // indirect
github.com/naoina/toml v0.1.1 // indirect
github.com/nxadm/tail v1.4.8 // indirect
github.com/onsi/ginkgo v1.16.4 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_golang v1.11.0 // indirect
github.com/prometheus/client_model v0.2.0 // indirect
github.com/prometheus/common v0.26.0 // indirect
github.com/prometheus/procfs v0.6.0 // indirect
github.com/rs/xid v1.2.1 // indirect
github.com/russross/blackfriday/v2 v2.0.1 // indirect
github.com/samfoo/ansi v0.0.0-20160124022901-b6bd2ded7189 // indirect
github.com/shopspring/decimal v1.2.0 // indirect
github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
github.com/sirupsen/logrus v1.7.0 // indirect
github.com/smallstep/certificates v0.16.4 // indirect
github.com/smallstep/cli v0.16.1 // indirect
github.com/smallstep/nosql v0.3.8 // indirect
github.com/smallstep/truststore v0.9.6 // indirect
github.com/spf13/cast v1.3.1 // indirect
github.com/stoewer/go-strcase v1.2.0 // indirect
github.com/stretchr/testify v1.7.0 // indirect
github.com/urfave/cli v1.22.5 // indirect
github.com/yuin/goldmark v1.4.0 // indirect
github.com/yuin/goldmark-highlighting v0.0.0-20210516132338-9216f9c5aa01 // indirect
go.etcd.io/bbolt v1.3.6 // indirect
go.mozilla.org/pkcs7 v0.0.0-20200128120323-432b2356ecb1 // indirect
go.step.sm/cli-utils v0.4.1 // indirect
go.step.sm/crypto v0.9.0 // indirect
go.step.sm/linkedca v0.0.0-20210611183751-27424aae8d25 // indirect
go.uber.org/atomic v1.7.0 // indirect
go.uber.org/multierr v1.6.0 // indirect
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e // indirect
golang.org/x/mod v0.4.2 // indirect
golang.org/x/net v0.0.0-20210614182718-04defd469f4e // indirect
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c // indirect
golang.org/x/term v0.0.0-20210503060354-a79de5458b56 // indirect
golang.org/x/text v0.3.6 // indirect
golang.org/x/tools v0.1.5 // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08 // indirect
google.golang.org/grpc v1.38.0 // indirect
google.golang.org/protobuf v1.27.1 // indirect
gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect
gopkg.in/square/go-jose.v2 v2.5.1 // indirect
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
howett.net/plist v0.0.0-20181124034731-591f970eefbb // indirect
)

1537
go.sum Normal file

File diff suppressed because it is too large Load Diff

3
testdata/index.php vendored Normal file
View File

@@ -0,0 +1,3 @@
<?php
echo 'I am by birth a Genevese';