- enabled "HTTP/2 Server Push" for static resources, closed #685

This commit is contained in:
Mark Friedrich
2018-09-14 17:25:07 +02:00
parent 827e2aae5d
commit bffd22fc27
6 changed files with 293 additions and 30 deletions

View File

@@ -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');
}
}

View File

@@ -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

View File

@@ -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;
}

View File

@@ -8,7 +8,6 @@
namespace lib;
use controller\LogController;
use Exception;

240
app/main/lib/resource.php Normal file
View File

@@ -0,0 +1,240 @@
<?php
/**
* Created by PhpStorm.
* User: exodu
* Date: 08.09.2018
* Time: 10:58
*/
namespace lib;
class Resource extends \Prefab {
/**
* default link "rel" attribute
* @link https://w3c.github.io/preload/#x2.link-type-preload
*/
const ATTR_REL = 'preload';
/**
* default link "as" attributes
*/
const ATTR_AS = [
'style' => '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 <link> 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 <link> tags for resources
* @return string
*/
public function buildLinks(){
$this->build();
$links = [];
foreach($this->resources as $group => $resources){
foreach($resources as $file => $conf){
$resourceHeader = '<link';
foreach($conf['options'] as $attr => $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] : '';
}
}

View File

@@ -43,45 +43,28 @@
<meta property="og:description" content="PATHFINDER is an 'open source' mapping tool for EVE ONLINE,
primarily developed to enrich the gameplay of small scale PvP and PvE.">
<meta name="theme-color" content="#2b2b2b"> {* Chrome, Firefox OS and Opera *}
<meta name="msapplication-navbutton-color" content="#2b2b2b"> {* Windows Phone *}
<meta name="apple-mobile-web-app-status-bar-style" content="#2b2b2b"> {* iOS Safari *}
<meta name="google-site-verification" content="sHoh0gfMw3x1wiwLTK5OsKsxt7kRgxi69hRgWEGh9DQ" /> {* Youtube verification code *}
<set pathCSS="{{ @BASE . '/public/css/' . @PATHFINDER.VERSION . '/pathfinder.css' }}" />
<set pathJSApp="{{ @BASE . '/' . @tplPathJs . '/app' }}" />
<set pathJSRequire="{{ @BASE . '/' . @tplPathJs . '/lib/require.js' }}" />
<check if="{{ @tplResource->getOption('output') === 'inline' }}">
{{ @tplResource->buildLinks() }} {* Prefetch / Preload *}
</check>
{* Prefetch / Preload *}
<link rel="preload" href="{{@pathCSS}}" as="style">
<link rel="preload" href="{{@pathJSRequire}}" as="script">
<link rel="preload" href="{{@pathJSApp}}.js" as="script">
<link rel="preload" href="{{@pathJSApp}}/{{@tplJsView}}.js" as="script">
<link rel="prerender" href="//login.eveonline.com">
<link rel="dns-prefetch" href="//image.eveonline.com">
<link rel="dns-prefetch" href="//i.ytimg.com">
<check if="{{ @tplJsView != 'mappage' }}">
<link rel="prefetch" href="{{@pathJSApp}}/mappage.js" as="script">
</check>
<link rel="preload" href="/public/fonts/oxygen-regular-webfont.woff2" as="font" type="font/woff2" crossorigin="anonymous">
<link rel="preload" href="/public/fonts/oxygen-bold-webfont.woff2" as="font" type="font/woff2" crossorigin="anonymous">
<link rel="preload" href="/public/fonts/fa-regular-400.woff2" as="font" type="font/woff2" crossorigin="anonymous">
<link rel="preload" href="/public/fonts/fa-solid-900.woff2" as="font" type="font/woff2" crossorigin="anonymous">
<link rel="preload" href="/public/fonts/fa-brands-400.woff2" as="font" type="font/woff2" crossorigin="anonymous">
<link rel="stylesheet" href="{{@pathCSS}}" type="text/css" media="screen">
<link rel="stylesheet" href="{{ @tplResource->getLink('style', 'pathfinder') }}" type="text/css" media="screen">
</head>
<body class="pf-body {{ @tplBodyClass }}" data-js-path="{{ @BASE }}/{{ @tplPathJs }}" data-script="{{ @tplJsView }}" <check if="{{ @tplCharacterId }}">data-character-id="{{ @tplCharacterId }}"</check> data-version="{{ @PATHFINDER.VERSION }}">
<body class="pf-body {{ @tplBodyClass }}" data-js-path="{{ @tplResource->getPath('script') }}" data-script="{{ @tplJsView }}" <check if="{{ @tplCharacterId }}">data-character-id="{{ @tplCharacterId }}"</check> data-version="{{ @PATHFINDER.VERSION }}">
<include if="{{ @tplPageContent }}" href="{{ @tplPageContent }}"/>
<!-- Hey dude! Where is all the magic? -->
<script data-main="{{@pathJSApp}}" src="{{@pathJSRequire}}" ></script>
<script data-main="{{ @tplResource->getLink('script', 'app') }}" src="{{ @tplResource->getLink('script', 'lib/require') }}" ></script>
</body>
</html>