From bffd22fc27b4808a25f63712dbdceadc58d34e92 Mon Sep 17 00:00:00 2001 From: Mark Friedrich Date: Fri, 14 Sep 2018 17:25:07 +0200 Subject: [PATCH] - enabled "HTTP/2 Server Push" for static resources, closed #685 --- app/main/controller/appcontroller.php | 9 +- app/main/controller/controller.php | 39 ++++- app/main/controller/setup.php | 5 +- app/main/lib/config.php | 1 - app/main/lib/resource.php | 240 ++++++++++++++++++++++++++ public/templates/view/index.html | 29 +--- 6 files changed, 293 insertions(+), 30 deletions(-) create mode 100644 app/main/lib/resource.php diff --git a/app/main/controller/appcontroller.php b/app/main/controller/appcontroller.php index d730a82e..305b2c10 100644 --- a/app/main/controller/appcontroller.php +++ b/app/main/controller/appcontroller.php @@ -10,6 +10,7 @@ namespace Controller; use Controller\Ccp as Ccp; use lib\Config; +use lib\Resource; class AppController extends Controller { @@ -58,7 +59,13 @@ class AppController extends Controller { * @param \Base $f3 */ public function init(\Base $f3) { - + $resource = Resource::instance(); + $resource->register('script', 'app/login'); + $resource->register('script', 'app/mappage', 'prefetch'); + $resource->register('image', 'pf-bg.jpg'); + $resource->register('image', 'pf-header-bg.jpg'); + $resource->register('image', 'landing/eve_sso_login_buttons_large_black.png'); + $resource->register('image', 'landing/eve_sso_login_buttons_large_black_hover.png'); } } \ No newline at end of file diff --git a/app/main/controller/controller.php b/app/main/controller/controller.php index acfb5e76..0f785c74 100644 --- a/app/main/controller/controller.php +++ b/app/main/controller/controller.php @@ -7,8 +7,10 @@ */ namespace Controller; + use Controller\Api as Api; use lib\Config; +use lib\Resource; use lib\Monolog; use lib\Socket; use lib\Util; @@ -76,8 +78,7 @@ class Controller { if($f3->get('AJAX')){ header('Content-type: application/json'); }else{ - // js path (build/minified or raw uncompressed files) - $f3->set('tplPathJs', 'public/js/' . Config::getPathfinderData('version') ); + $this->initResource($f3); $this->setTemplate( Config::getPathfinderData('view.index') ); } @@ -91,6 +92,12 @@ class Controller { * @param \Base $f3 */ public function afterroute(\Base $f3){ + // send preload/prefetch headers + $resource = Resource::instance(); + if($resource->getOption('output') === 'header'){ + header($resource->buildHeader(), false); + } + if($this->getTemplate()){ // Ajax calls don“t need a page render.. // this happens on client side @@ -145,6 +152,34 @@ class Controller { } + /** + * init new Resource handler + * @param \Base $f3 + * @throws \Exception\PathfinderException + */ + protected function initResource(\Base $f3){ + $resource = Resource::instance(); + $resource->setOption('filePath', [ + 'style' => $f3->get('BASE') . '/public/css/' . Config::getPathfinderData('version'), + 'script' => $f3->get('BASE') . '/public/js/' . Config::getPathfinderData('version'), + 'font' => $f3->get('BASE') . '/public/fonts', + 'image' => $f3->get('BASE') . '/public/img' + ]); + + $resource->register('style', 'pathfinder'); + + $resource->register('script', 'lib/require'); + $resource->register('script', 'app'); + + $resource->register('font', 'oxygen-regular-webfont'); + $resource->register('font', 'oxygen-bold-webfont'); + $resource->register('font', 'fa-regular-400'); + $resource->register('font', 'fa-solid-900'); + $resource->register('font', 'fa-brands-400'); + + $f3->set('tplResource', $resource); + } + /** * get cookies "state" information * -> whether user accepts cookies diff --git a/app/main/controller/setup.php b/app/main/controller/setup.php index ec08ce9d..db954059 100644 --- a/app/main/controller/setup.php +++ b/app/main/controller/setup.php @@ -140,6 +140,8 @@ class Setup extends Controller { * @throws \Exception\PathfinderException */ function beforeroute(\Base $f3, $params): bool { + $this->initResource($f3); + // init dbLib class. Manages all DB connections $this->dbLib = DB\Database::instance(); @@ -152,9 +154,6 @@ class Setup extends Controller { // body element class $f3->set('tplBodyClass', 'pf-landing'); - // js path (build/minified or raw uncompressed files) - $f3->set('tplPathJs', 'public/js/' . Config::getPathfinderData('version') ); - return true; } diff --git a/app/main/lib/config.php b/app/main/lib/config.php index 02ebe7e7..5479883c 100644 --- a/app/main/lib/config.php +++ b/app/main/lib/config.php @@ -8,7 +8,6 @@ namespace lib; - use controller\LogController; use Exception; diff --git a/app/main/lib/resource.php b/app/main/lib/resource.php new file mode 100644 index 00000000..1187f563 --- /dev/null +++ b/app/main/lib/resource.php @@ -0,0 +1,240 @@ + 'style', + 'script' => 'script', + 'font' => 'font', + 'image' => 'image' + ]; + + /** + * default link "type" attributes + */ + const ATTR_TYPE = [ + 'font' => 'font/woff2' + ]; + + /** + * default additional attributes by $group + */ + const ATTR_ADD = [ + 'font' => ['crossorigin' => 'anonymous'] + ]; + + /** + * absolute file path -> use setOption() for update + * @var array + */ + private $filePath = [ + 'style' => '', + 'script' => '', + 'font' => '', + 'image' => '' + ]; + + /** + * default file extensions by $group + * -> used if no fileExtension found in $file + * @var array + */ + private $fileExt = [ + 'style' => 'css', + 'script' => 'js', + 'font' => 'woff2' + ]; + + /** + * output type + * -> 'inline' -> render inline HTML tags + * -> 'header' -> send "Link" HTTP Header with request + * @see buildLinks() + * @see buildHeader() + * @var string + */ + private $output = 'inline'; + + /** + * resource file cache + * @var array + */ + private $resources = []; + + /** + * set option + * @param string $option + * @param $value + */ + public function setOption(string $option, $value){ + $this->$option = $value; + } + + /** + * get option + * @param string $option + * @return mixed|null + */ + public function getOption(string $option){ + return isset($this->$option) ? $this->$option : null; + } + + /** + * register new resource $file + * @param string $group + * @param string $file + * @param string $rel + */ + public function register(string $group, string $file, string $rel = self::ATTR_REL){ + $this->resources[$group][$file] = ['options' => ['rel' => $rel]]; + } + + /** + * get resource path/file.ext + * @param string $group + * @param string $file + * @return string + */ + public function getLink(string $group, string $file) : string { + $link = $this->getPath($group) . '/' . $file; + // add extension if not already part of the file + // -> allows switching between extensions (e.g. .jpg, .png) for the same image + $link .= empty(pathinfo($file, PATHINFO_EXTENSION)) ? '.' . $this->getFileExtension($group) : ''; + return $link; + } + + /** + * get resource path + * @param string $group + * @return string + */ + public function getPath(string $group) : string { + return $this->filePath[$group]; + } + + /** + * build inline HTML tags for resources + * @return string + */ + public function buildLinks(){ + $this->build(); + $links = []; + foreach($this->resources as $group => $resources){ + foreach($resources as $file => $conf){ + $resourceHeader = ' $value){ + $resourceHeader .= ' ' . $attr . '="' . $value . '"'; + // insert href attr after rel attr -> better readability + if($attr == 'rel'){ + $resourceHeader .= ' href="' . $conf['link'] . '"'; + } + } + $links[] = $resourceHeader . '>'; + } + } + return "\n\t" . implode("\n\t", $links); + } + + /** + * build HTTP header for resource preload + * -> all registered resources combined in a single header + * @link https://www.nginx.com/blog/nginx-1-13-9-http2-server-push/#automatic-push + * @return string + */ + public function buildHeader() : string { + $this->build(); + $headers = []; + foreach($this->resources as $group => $resources){ + foreach($resources as $file => $conf){ + $resourceHeader = '<' . $conf['link'] . '>'; + foreach($conf['options'] as $attr => $value){ + $resourceHeader .= '; ' . $attr . '="' . $value . '"'; + } + $headers[] = $resourceHeader; + } + } + return 'Link: ' . implode(', ', $headers); + } + + /** + * build resource data + * -> add missing attributes to resources + */ + protected function build(){ + foreach($this->resources as $group => &$resources){ + foreach($resources as $file => &$conf){ + if(empty($conf['link'])){ + $conf['link'] = $this->getLink($group, $file); + } + + if( empty($conf['options']['rel']) ){ + $conf['options']['rel'] = self::ATTR_REL; + } + if( empty($conf['options']['as']) ){ + $conf['options']['as'] = $group; + } + if( empty($conf['options']['type']) && !empty($attrType = $this->getLinkAttrType($group)) ){ + $conf['options']['type'] = $attrType; + } + + if( !empty($additionalAttr = $this->getAdditionalAttrs($group)) ){ + $conf['options'] = $conf['options'] + $additionalAttr; + } + } + unset($options); // unset ref + } + unset($resources); // unset ref + } + + /** + * get 'as' attribute (potential destination) by resource $group + * @link https://w3c.github.io/preload/#as-attribute + * @param string $group + * @return string + */ + protected function getLinkAttrAs(string $group) : string { + return isset(self::ATTR_AS[$group]) ? self::ATTR_AS[$group] : ''; + } + + /** + * get 'type' attribute by resource $group + * @link https://w3c.github.io/preload/#early-fetch-of-critical-resources + * @param string $group + * @return string + */ + protected function getLinkAttrType(string $group) : string { + return isset(self::ATTR_TYPE[$group]) ? self::ATTR_TYPE[$group] : ''; + } + + /** + * get additional attributes by $group + * -> e.g. or fonts + * @param string $group + * @return array + */ + protected function getAdditionalAttrs(string $group) : array { + return isset(self::ATTR_ADD[$group]) ? self::ATTR_ADD[$group] : []; + } + + protected function getFileExtension(string $group) : string { + return isset($this->fileExt[$group]) ? $this->fileExt[$group] : ''; + } +} \ No newline at end of file diff --git a/public/templates/view/index.html b/public/templates/view/index.html index 35b5004d..91bc8712 100644 --- a/public/templates/view/index.html +++ b/public/templates/view/index.html @@ -43,45 +43,28 @@ - {* Chrome, Firefox OS and Opera *} {* Windows Phone *} {* iOS Safari *} {* Youtube verification code *} - - - + + {{ @tplResource->buildLinks() }} {* Prefetch / Preload *} + - {* Prefetch / Preload *} - - - - - - - - - - - - - - - - + -data-character-id="{{ @tplCharacterId }}" data-version="{{ @PATHFINDER.VERSION }}"> +data-character-id="{{ @tplCharacterId }}" data-version="{{ @PATHFINDER.VERSION }}"> - +