mattvb91 / caddy-php
Control your Caddy instance through PHP
Installs: 7 931
Dependents: 0
Suggesters: 0
Security: 0
Stars: 29
Watchers: 5
Forks: 5
Open Issues: 1
Type:package
Requires
- php: ^8.1
- guzzlehttp/guzzle: ^7.0
- symfony/http-foundation: ^7.1
Requires (Dev)
- dms/phpunit-arraysubset-asserts: ^0.5.0
- phpstan/phpstan: ^1.10
- phpunit/phpunit: ^10
- povils/phpmnd: ^3.5
- rector/rector: ^1.0
- squizlabs/php_codesniffer: ^3.7
This package is auto-updated.
Last update: 2024-12-17 18:12:04 UTC
README
Control your Caddy instance through PHP
This is more of a proof of concept rather than a fully working project. This tries to replicate the caddy JSON API structure to work through chainable PHP classes.
At the moment there is only a tiny subset of commands available from Caddy 2.0 that covered my currently needed use case.
Install
composer require mattvb91/caddy-php
Basic Usage
A basic example of a http server with a static response:
$caddy = new Caddy(); $caddy->addApp( (new Http())->addServer( 'server1', (new Http\Server())->addRoute( (new Route())->addHandle( new StaticResponse('Hello world', 200) ) )) ); $caddy->load();
This will result in the following Caddy config:
{ "admin": { "disabled": false, "listen": ":2019" }, "apps": { "http": { "servers": { "server1": { "listen": [ ":80" ], "routes": [ { "handle": [ { "handler": "static_response", "body": "Hello world", "status_code": 200 } ] } ] } } } } }
curl -v localhost ----- < HTTP/1.1 200 OK < Server: Caddy Hello world
Managing Hostnames
If you are managing hostnames dynamically (in a database) and can't build out the config with a list of existing hostnames because you need to manage them at runtime you can do the following:
The important part in this example is the host_group_name
identifier which is later
used to add / remove domains to this host.
$caddy = new Caddy(); $caddy->addApp( (new Http())->addServer( 'server1', (new Http\Server())->addRoute( (new Route())->addHandle( new StaticResponse('host test', 200) )->addMatch((new Host('host_group_name')) ->setHosts(['localhost']) ) )->addRoute((new Route()) ->addHandle(new StaticResponse('Not found', 404)) ->addMatch((new Host('notFound')) ->setHosts(['*.localhost']) ) )) ); $caddy->load();
Adding Hostnames
Now later on in a script or event on your system you can get your caddy configuration object and post a new domain to it under that route:
$caddy->addHostname('host_group_name', 'new.localhost') $caddy->addHostname('host_group_name', 'another.localhost')
curl -v new.localhost > GET / HTTP/1.1 > Host: new.localhost > < HTTP/1.1 200 OK curl -v another.localhost > GET / HTTP/1.1 > Host: another.localhost > < HTTP/1.1 200 OK
Removing Hostnames
$caddy->syncHosts('host_group_name'); //Sync from caddy current hostname list $caddy->removeHostname('host_group_name', 'new.localhost'); $caddy->removeHostname('host_group_name', 'another.localhost');
curl -v new.localhost > GET / HTTP/1.1 > Host: new.localhost > < HTTP/1.1 404 Not Found curl -v another.localhost > GET / HTTP/1.1 > Host: another.localhost > < HTTP/1.1 404 Not Found
Advanced Example
Let's take a case where you want to have a Node frontend and a PHP backend taking requests on the /api/*
route.
In this case the example breaks down to 2 reverse proxy's with a route matcher to filter the /api/*
to the PHP
upstream.
This assumes the 3 hosts (Caddy, Node, PHP) are all docker containers and accessible by container name within the same docker network, so you may have to adjust your hostnames as required.
use mattvb91\CaddyPhp\Caddy; use mattvb91\CaddyPhp\Config\Apps\Http; use mattvb91\CaddyPhp\Config\Apps\Http\Server; use mattvb91\CaddyPhp\Config\Apps\Http\Server\Route; use mattvb91\CaddyPhp\Config\Apps\Http\Server\Routes\Handle\ReverseProxy; use mattvb91\CaddyPhp\Config\Apps\Http\Server\Routes\Handle\ReverseProxy\Transport\FastCGI; use mattvb91\CaddyPhp\Config\Apps\Http\Server\Routes\Handle\ReverseProxy\Upstream; use mattvb91\CaddyPhp\Config\Apps\Http\Server\Routes\Handle\Subroute; use mattvb91\CaddyPhp\Config\Apps\Http\Server\Routes\Match\Host; use mattvb91\CaddyPhp\Config\Apps\Http\Server\Routes\Match\Path; $apiReverseProxy = (new ReverseProxy()) ->addUpstream((new Upstream()) ->setDial('laravel-api:9000') )->addTransport((new FastCGI()) ->setRoot('/app/public/index.php') ->setSplitPath(['']) ); $apiMatchPath = (new Path()) ->setPaths([ '/api/*', ]); $backendAPIRoute = (new Route()) ->addHandle($apiReverseProxy) ->addMatch($apiMatchPath); $route = new Route(); $route->addHandle((new Subroute()) ->addRoute($backendAPIRoute) ->addRoute((new Route()) ->addHandle((new ReverseProxy()) ->addUpstream((new Upstream()) ->setDial('nextjs:3000') ) ) ) )->addMatch((new Host()) ->setHosts([ 'localhost', ]) )->setTerminal(true); $caddy = new Caddy(); $caddy->addApp((new Http()) ->addServer('myplatform', (new Server()) ->addRoute($route) ) ); $caddy->load();
This will post the following caddy config:
{ "admin": { "disabled": false, "listen": ":2019" }, "apps": { "http": { "servers": { "myplatform": { "listen": [ ":80" ], "routes": [ { "handle": [ { "handler": "subroute", "routes": [ { "handle": [ { "handler": "reverse_proxy", "transport": { "protocol": "fastcgi", "root": "/app/public/index.php", "split_path": [ "" ] }, "upstreams": [ { "dial": "laravel-api:9000" } ] } ], "match": [ { "path": [ "/api/*" ] } ] }, { "handle": [ { "handler": "reverse_proxy", "upstreams": [ { "dial": "nextjs:3000" } ] } ] } ] } ], "match": [ { "host": [ "localhost" ] } ], "terminal": true } ] } } } } }
curl -v localhost < HTTP/1.1 200 OK < Content-Type: text/html; charset=utf-8 < Server: Caddy < X-Powered-By: Next.js < Transfer-Encoding: chunked < <!DOCTYPE html><html>....
curl -v localhost/api/testroute < HTTP/1.1 200 OK < Content-Type: application/json < Server: Caddy < X-Powered-By: PHP/8.1.7 < {"status":200}
Take a look in the tests for more examples.