Skip to content

Commit 35200c5

Browse files
committed
feat: refactor. ATTENTION: breaks compatibility with VirtualHosts configured before this commit (public root has been moved to ./public)
1 parent 12d8b2d commit 35200c5

File tree

11 files changed

+605
-340
lines changed

11 files changed

+605
-340
lines changed

.env.example

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
DEBUG = true
2-
SOURCE_HOST = "https://main.origin.server.biz"
2+
SOURCE_ORIGIN = "https://main.origin.server.biz"
33
CACHEABLE_EXTENSIONS = "css,js,jpg,jpeg,png,gif,bmp,webp,ttf,woff,woff2,otf,eot,svg,ico"
44
CACHE_EXPIRY = 31536000
55
CHARSET = "UTF-8"

.gitignore

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@
55
!.gitignore
66
!.env.example
77
!.htaccess
8-
!index.php
9-
!load.php
10-
!functions.php
8+
!bootstrap.php
119
!composer.json
1210
!README.md
13-
!LICENSE
11+
!LICENSE
12+
!public/index.php
13+
!helpers/functions.php

README.md

Lines changed: 53 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,27 @@
1-
# PHP Image to WEBP Rewriter Transparent Proxy
1+
# PHP-Based "Transparent Proxy" CDN + Optional on-the-fly Image to WEBP conversion
22

33
This project can be used to serve **static assets** (like, for example, `css`, `js`, `jpg` files) of your website from a **"self-hosted CDN"**, that will act as a **transparent proxy** from the origin server to the client's browser.
44

55
The peculiarity of this project is that, for every image requested **from the transparent proxy**, it will **convert the source image in WEBP format**, serve it, and then for every other request all images will be **permanent-redirected to the webp version**.
66

7+
## What is the purpose of this project?
8+
9+
The purpose of this project is to **serve static assets from a CDN in your control**, without having to **touch the code of the origin server**.
10+
11+
This is useful, for example, if you:
12+
13+
1. Don't want to use a third-party CDN like Cloudflare or Cloudfront
14+
2. Don't want to touch the code of the origin server - in the sense that you don't want to deeply change the way static assets are referenced in the HTML, but only write a quick and dirty rewrite rule to redirect all static assets to the CDN itself (or a buffer post-processing function, if you are using a CMS)
15+
3. Want to serve **webp** images to browsers that support it, and **jpg** images to browsers that don't support it, without having to touch the code of the origin server
16+
17+
## Request lifecycle
18+
19+
1. The client requests a _Web Page_ of your website
20+
2. That _Web Page_ contains references to static assets (images, css, js, etc) that are re-routed to the CDN in a 1:1 pattern, like `https://my-project.example.com/images/whatever/resource.jpg` -> `https://static.mycdn.com/images/whatever/resource.jpg`
21+
3. The _Transparent Proxy CDN_ receives the _Request_ (file path) and checks if the requested _Resource_ is already present in its filesystem
22+
4. If the _Resource_ is physically present on the CDN's filesystem, it is served to the client and optimized via the `.htaccess` _Expires_ and _Cache-Control_ directives
23+
5. If the _Resource_ is not present on the CDN's filesystem, it is **automatically fetched from the origin server** and served to the _Client_, and then it is **cached** on the CDN's filesystem. In this case, _headers_ are blindly mirrored from the origin server to the client, and the _Expires_ and _Cache-Control_ directives are set via PHP
24+
725
## Can you give me an example of what's going on?
826

927
Suppose you:
@@ -14,17 +32,17 @@ Suppose you:
1432

1533
Then, you would only need to make sure that **images** in the **origin server** are referenced in the HTML this way:
1634

17-
```
18-
...
35+
```html
36+
<!--
1937
The HTML of a page inside https://my-project.example.com
20-
...
21-
<img src="https://static.mycdn.com/images/whatever/*.jpg" />
38+
-->
39+
<img src="https://static.mycdn.com/images/whatever/resource.jpg" />
2240
```
2341

2442
Instead of
2543

26-
```
27-
<img src="/images/whatever/*.jpg" />
44+
```html
45+
<img src="/images/whatever/resource.jpg" />
2846
```
2947

3048
Then, voilà, **your origin server is webp-enabled without touching a single line of code** (apart from redirecting static assets to the CDN itself, but **that is easy**)
@@ -35,19 +53,27 @@ You define it via the **.env** file
3553

3654
## Installation
3755

38-
1. A lamp server with a valid https virtualhost
39-
2. Clone this repo to the virtualhost's root dir
40-
3. Make sure `mod_rewrite` and `mod_headers` are enabled ( `sudo a2enmod rewrite headers` )
41-
4. Make sure you have `curl` support in your PHP enabled modules
42-
5. Run `composer install`
43-
6. Install my other project, `https://github.com/mauriziofonte/php-image-to-webp-conversion-api` into a virtualhost of its own (maybe in another dev server with PHP's `exec()` enabled) and set it up reading the project's readme
44-
7. Clone the `.env.example` into `.env` and modify it accordingly to your needs
56+
1. Clone this repo inside your _LAMP_ server with a VirtualHost pointed to `/proj/dir/public`
57+
2. Make sure `mod_rewrite`, `mod_headers` and `mod_expires` are enabled ( `sudo a2enmod rewrite headers expires` )
58+
3. Make sure you have `curl` support in your _PHP enabled modules_
59+
4. Make sure you're running at least **PHP 7.2.5**
60+
5. Run `composer install`
61+
6. Install my other project [https://github.com/mauriziofonte/php-image-to-webp-conversion-api](https://github.com/mauriziofonte/php-image-to-webp-conversion-api) into a VirtualHost of its own - _even better in another dev server with PHP's `exec()` enabled_ and configure it accordingly to the project's readme
62+
7. Clone the `.env.example` into `.env` and modify it accordingly to your needs
63+
64+
Specifically, if you want to use the **Image to WEBP conversion API**, you need to set the `WEBP_API_SERVER` and `WEBP_API_KEY` variables in the `.env` file.
65+
66+
> **Heads up!**
67+
>
68+
> The application **automatically caches** the `.env` file into `.cached.env.php` to avoid reading the `.env` file on every request.
69+
>
70+
> If you modify the `.env` file, you need to **manually delete** the `.cached.env.php` file to force the application to re-read the `.env` file.
4571
4672
## What to do on the frontend, to rewrite static assets to the CDN
4773

4874
It depends on your application. If it is a CMS, refer to the CMS's output buffering and/or output filter functionalities. If it's something custom, you can do the following (untested!):
4975

50-
```
76+
```php
5177
<?php
5278
ob_start('rewrite_static_assets');
5379
... app ...
@@ -77,20 +103,26 @@ It depends on your application. If it is a CMS, refer to the CMS's output buffer
77103
78104
## How to configure the .env
79105
80-
```
106+
```text
81107
DEBUG = true|false
82108
Defines the error reporting of the application. NEEDS TO BE TURNED OFF IN PRODUCTION, otherwise static assets output may be poisoned from the PHP error output
83-
SOURCE_HOST = "https://main.origin.server.biz"
109+
110+
SOURCE_ORIGIN = "https://main.origin.server.biz"
84111
Defines the main host where the "original" static assets are physically present and can be cloned from
112+
85113
CACHEABLE_EXTENSIONS = "css,js,jpg,jpeg,png,gif,bmp,webp,ttf,woff,woff2,otf,eot,svg,ico"
86114
Define the extensions that will be enabled on the transparent proxy. Potentially, you could use this to cache also HTML or ZIP files
115+
87116
CACHE_EXPIRY = 2592000
88117
Defines the expiry time of static assets served from the trasparent CDN. Please keep in mind that this directive will be set via PHP only the "first time" a static asset does not exists on the CDN filesystem. Subsequent calls will be cached accordingly to what the .htaccess defines. (1 month)
89-
CHARSET = "UTF-8"
90-
TIMEZONE = "Europe/Rome"
91-
LOCALE = "en_US"
92-
WEBP_API_SERVER = "https://github.com/mauriziofonte/php-image-to-webp-conversion-api"
118+
119+
CHARSET = "UTF-8" # Self-explanatory. Valid values are "UTF-8" or "ISO-8859-1"
120+
TIMEZONE = "Europe/Rome" # Self-explanatory. Valid values are the ones defined in https://www.php.net/manual/en/timezones.php
121+
LOCALE = "en_US" # Self-explanatory. Valid values are the ones defined in https://www.php.net/manual/en/function.setlocale.php
122+
123+
WEBP_API_SERVER = "https://my.webp.conversion.microservice.biz"
93124
Defines the Image to WEBP api url, that can be cloned from https://github.com/mauriziofonte/php-image-to-webp-conversion-api . Refer to the documentation of that project to know more.
125+
94126
WEBP_API_KEY = "a-very-strong-api-key"
95127
Same as WEBP_API_SERVER. Refer to the documentation of "php-image-to-webp-conversion-api" to know more.
96128
```

bootstrap.php

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
<?php
2+
3+
if (function_exists('get_included_files') && count(get_included_files()) == 1) {
4+
exit();
5+
}
6+
7+
// check for availability of curl_ functions
8+
if (! function_exists('curl_init')) {
9+
die('cURL extension is not installed');
10+
}
11+
12+
define('DS', DIRECTORY_SEPARATOR);
13+
define('ROOT_DIR', rtrim(__DIR__, DS).DS);
14+
define('CONF_FILE', ROOT_DIR.'.env');
15+
define('HELP_DIR', ROOT_DIR.'helpers'.DS);
16+
define('VENDOR_DIR', ROOT_DIR.'vendor'.DS);
17+
define('PUBLIC_DIR', ROOT_DIR.'public'.DS);
18+
define(
19+
'IS_HTTPS',
20+
(
21+
(isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on') ||
22+
(isset($_SERVER['SERVER_PORT']) && (int) ($_SERVER['SERVER_PORT']) === 443) ||
23+
(isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https') ||
24+
(isset($_SERVER['REQUEST_SCHEME']) && $_SERVER['REQUEST_SCHEME'] === 'https')
25+
) ? true : false
26+
);
27+
define('FROM_CLI', (php_sapi_name() === 'cli' || defined('STDIN')) ? true : false);
28+
29+
if (! is_file(CONF_FILE)) {
30+
die('Cannot find application .env. Please, read the attached README.md for instructions on how to install this application');
31+
}
32+
if (! is_file(VENDOR_DIR.'autoload.php')) {
33+
die('Cannot find application vendor autoload. Please, read the attached README.md for instructions on how to install this application');
34+
}
35+
if (! is_file(HELP_DIR.'functions.php')) {
36+
die('Cannot find application helpers/functions.php. Please, read the attached README.md for instructions on how to install this application');
37+
}
38+
39+
// require autoload and helpers
40+
require_once VENDOR_DIR.'autoload.php';
41+
require_once HELP_DIR.'functions.php';
42+
43+
// load (and eventually cache) the .env config
44+
loadConfig(ROOT_DIR);
45+
46+
// debug mode?
47+
if (DEBUG) {
48+
@error_reporting(E_ALL);
49+
@ini_set('display_errors', 1);
50+
} else {
51+
error_reporting(0);
52+
@ini_set('display_errors', 'Off');
53+
}
54+
55+
// charset, timezone, locale
56+
@ini_set('default_charset', CHARSET);
57+
date_default_timezone_set(TIMEZONE);
58+
setlocale(LC_TIME, LOCALE);
59+
setlocale(LC_MONETARY, implode('.', [LOCALE, CHARSET]));
60+
61+
/*
62+
63+
$dotenv = Dotenv\Dotenv::createUnsafeImmutable(__DIR__);
64+
$dotenv->load();
65+
try {
66+
$dotenv->required([
67+
'SOURCE_HOST',
68+
'CHARSET',
69+
'TIMEZONE',
70+
'LOCALE',
71+
'CACHEABLE_EXTENSIONS',
72+
'WEBP_API_SERVER',
73+
'WEBP_API_KEY',
74+
]);
75+
} catch (Exception $ex) {
76+
die('Error in .env variables: '.$ex->getMessage());
77+
}
78+
79+
$APP_DEBUG = (in_array(getenv('DEBUG'), [true, 'true', 1])) ? true : false;
80+
if ($APP_DEBUG) {
81+
@error_reporting(E_ALL);
82+
@ini_set('display_errors', 1);
83+
} else {
84+
error_reporting(0);
85+
@ini_set('display_errors', 'Off');
86+
}
87+
88+
@ini_set('default_charset', getenv('CHARSET'));
89+
date_default_timezone_set(getenv('TIMEZONE'));
90+
setlocale(LC_TIME, getenv('LOCALE'));
91+
setlocale(LC_MONETARY, getenv('LOCALE').'.'.getenv('CHARSET'));
92+
93+
$SOURCE_HOST = rtrim(getenv('SOURCE_HOST'), '/');
94+
$CACHEABLE_EXTENSIONS = array_filter(array_map(function ($v) {
95+
$v = trim(strtolower($v));
96+
if ($v) {
97+
return $v;
98+
}
99+
100+
return null;
101+
}, explode(',', getenv('CACHEABLE_EXTENSIONS'))));
102+
$CACHE_EXPIRY = getenv('CACHE_EXPIRY');
103+
$WEBP_API_SERVER = getenv('WEBP_API_SERVER');
104+
$WEBP_API_KEY = getenv('WEBP_API_KEY');
105+
*/

composer.json

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
{
22
"require": {
3-
"symfony/http-foundation": "^5.1",
4-
"vlucas/phpdotenv": "^5.2"
3+
"symfony/http-foundation": "^5.4",
4+
"vlucas/phpdotenv": "^5.5"
5+
},
6+
"require-dev": {
7+
"mfonte/coding-standard": "^1.3"
58
}
69
}

functions.php

Lines changed: 0 additions & 108 deletions
This file was deleted.

0 commit comments

Comments
 (0)