Dan Scott, Laurentian University
2017 Evergreen International Conference
2017-04-07
Y'all got on this boat for different reasons, but y'all come to the same place. So now I'm asking more of you than I have before. Maybe all. Sure as I know anything, I know this - they will try again. […] They'll swing back to the belief that they can make [native apps]… better. And I do not hold to that. So no more runnin'. I aim to misbehave. Serenity, performance by Nathan Fillion, 2005-09-30.
certbot
to get a free TLS certificate from LetsEncryptcertbot certonly --webroot -w /openils/var/web/ \
-d shiny.example.org
vim /etc/apache2/sites-enabled/eg.conf
SSLCertificateFile /etc/letsencrypt/live/shiny.example.org/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/shiny.example.org/privkey.pem
vim /etc/apache2/eg_vhost.conf
# Uncomment the following to force SSL for everything.
# Note that this defeats caching and you will suffer
# a performance hit.
RewriteCond %{HTTPS} off
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [NE,R,L]
Zoë: We mentioned that we were out of rations, and 10 minutes later, a bunch of apples rained into the trench.
Wash: And they grew into a big tree, and they all climbed up the tree into a magical land with unicorns and a harp. "War Stories." Firefly, performance by Gina Torres and Alan Tudyk, season 1, episode 9, 2002-12-06.
The purpose of the manifest is to install web applications to the homescreen of a device, providing users with quicker access and a richer experience. "Web App Manifest." Mozilla Developer Network
<head>
{
"short_name": "Shiny Cap'n",
"name": "Shiny Cap'n",
"description": "A real shiny public catalogue!",
"start_url": "/eg/opac/home"
}
<link rel="manifest" href="/manifest.json" />
Multiple icons for multiple screen densities
{
"short_name": "Shiny Cap'n",
"icons": [{
"src": "/images/icon48.png", "type": "image/png",
"sizes": "48x48"
},
{
"src": "/images/icon96.png", "type": "image/png",
"sizes": "96x96"
},
{
"src": "/images/icon192.png", "type": "image/png",
"sizes": "192x192"
}],
}
While the app launches, give it nice colours
{
"short_name": "Shiny Cap'n",
"background_color": "#007a54",
"theme_color": "#007a54",
}
display
display
to either:
orientation
to either portrait or landscape{
"short_name": "Shiny Cap'n",
"display": "standalone",
"orientation": "portrait"
}
link
and meta
matching<head>
should contain
<link rel="icon" />
and
<meta rel="theme-color" />
elements
that match the manifest values:
<meta rel="theme-color" value="#007a54" />
<link rel="icon" sizes="192x192" href="images/icon192.png" />
'Yes. Yes, this is a fertile land, and we will thrive. We will rule over all this land, and we will call it… "This Land."' "Serenity." Firefly, performance by Alan Tudyk, season 1, episode 1, 2002-09-20. Netflix, https://www.netflix.com/watch/70133870?trackId=200257859.
/eg/opac/home
first paint: 1648.1ms
…opac/home (shiny)
…opac/semiauto.css (shiny) - 719.2ms, 14.61KB
…opac/simple.js (shiny) - 796ms, 16.4KB
…tundra/tundra.css (shiny) - 1,060.6ms, 54.93KB
…css/style.css (shiny) - 1,175.7ms, 60.35KB
…dojo/openils_dojo.js (shiny) - 1,456.5ms, 12.82KB
…opensrf/JSON_v1.js (shiny) - 1,541.4ms, 15.97KB
…opensrf/opensrf_xhr.js (shiny) - 1,571.1ms, 17.78KB
…themes/dijit.css (shiny) - 1,640.9ms, 36.78KB
…images/small_logo.png (shiny) - 1,747.4ms, 15.37KB
…opensrf/opensrf.js (shiny) - 1,840.6ms, 39.31KB
…images/eg_tiny_logo.png (shiny) - 1,926.7ms, 14.96KB
…images/main_logo.png (shiny) - 1,941.4ms, 21.25KB
…dojo/dojo.js (shiny) - 2,106ms, 92.59KB
Kaylee: Everything's shiny, Cap'n. Not to fret.
Mal: You told me those entry couplings would hold for another week!
Kaylee: That was six months ago, Cap'n. Serenity, performances by Jewel Staite and Nathan Fillion, 2005-09-30.
opac/parts/header.tt2
# Dojo is required to use the copy locations advanced search
# filter, therefore, it should always be enabled.
want_dojo = 1;
IF use_autosuggest.enabled == "t";
want_dojo = 1;
END;
IF ctx.google_books_preview;
want_dojo = 1;
END;
IF ENV.OILS_NOVELIST_URL;
want_dojo = 1;
END;
…opac/home (shiny)
…opac/semiauto.css (shiny) - 592ms, 11.48KB
…opac/simple.js (shiny) - 642.3ms, 13.26KB
…css/style.css (shiny) - 862.8ms, 58.53KB
…images/eg_tiny_logo.png (shiny) - 1,041.5ms, 11.82KB
…images/main_logo.png (shiny) - 1,071.5ms, 18.12KB
Kept in the air by love (and Kaylee)
Belching smoke and uncontained radiation...
and Dojo 1.3
<script>
if ('serviceWorker' in navigator) {
window.addEventListener('load', function() {
// Your service worker *must* be located at
// the top-level directory relative to your site.
// It won't be able to control pages unless it's
// located at the same level or higher than them.
navigator.serviceWorker.register('/sw.js')
.then(function(reg) { /* … stuff! */ })
.catch(function(e) { console.error('Error:', e); });
}
}
</script>
Adapted from Google SW registration boilerplate
serviceworke.rs offers examples of caching strategies
self.addEventListener('fetch', function(evt) {
if evt.respondWith(fromNetwork(evt.request, 400)
.catch(function () {
return fromCache(evt.request);
}));
});
function fromNetwork(request, timeout) {
return new Promise(function (fulfill, reject) {
var timeoutId = setTimeout(reject, timeout);
fetch(request).then(function (response) {
clearTimeout(timeoutId);
fulfill(response);
}, reject);
});
}
self.addEventListener('fetch', function(evt) {
evt.respondWith(fromCache(evt.request));
evt.waitUntil(update(evt.request));
});
function fromCache(request) {
return caches.open(CACHE).then(function (cache) {
return cache.match(request).then(function (matching) {
return matching || Promise.reject('no-match');
});
});
}
function update(request) {
return caches.open(CACHE).then(function (cache) {
return fetch(request).then(function (response) {
return cache.put(request, response);
});
});
}
sw-toolbox
to generate a service worker based on a configuration file{
"name": "evergreen-pwa-stuff",
"version": "1.0.0",
"description": "Evergreen PWA stuff",
"author": "Dan Scott",
"license": "GPL-2.0+",
"dependencies": {
"sw-precache": ">4.3.0"
}
}
$ npm install
module.exports = {
staticFileGlobs: [
'/openils/var/web/css/skin/default/opac/semiauto.css',
'/openils/var/web/js/ui/default/opac/simple.js',
'/openils/var/web/opac/images/small_logo.png',
'/openils/var/web/opac/images/progressbar_green.png',
'/openils/var/web/opac/images/main_logo.png',
'/openils/var/web/opac/images/eg_tiny_logo.png'
],
stripPrefix: '/openils/var/web/',
runtimeCaching: [{
urlPattern: /^https:\/\/shiny.example.org\//,
handler: 'networkFirst'
}]
};
templates/opac/parts/base.tt2
$ node_modules/sw-precache/cli.js --config sw-precache.js
Total precache size is about 18.2 kB for 6 resources.
service-worker.js has been generated.
$ cp service-worker.js /openils/var/web/sw.js
$ cp sw-register.js /openils/var/web/.
Shut. Up.
*Score may not reflect real-world experiences
cache-control: no-store, no-cache, must-revalidate
expires: -1
no-store
means "never cache this"expires: -1
means "this expires immediately"
diff --git a/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader.pm b/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader.pm
index 6aa1f58..6548edc 100644
--- a/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader.pm
+++ b/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGCatLoader.pm
@@ -184,8 +184,7 @@ sub load {
return $self->redirect_auth unless $self->editor->requestor;
# Don't cache anything requiring auth for security reasons
-$self->apache->headers_out->add("cache-control" => "no-store, no-cache, must-revalidate");
-$self->apache->headers_out->add("expires" => "-1");
+$self->apache->headers_out->add("cache-control" => "no-cache, must-revalidate");
return $self->load_email_record if $path =~ m|opac/record/email|;
If we remove those headers, Expires becomes + 5 seconds due to /etc/apache2/eg_vhost.conf
:
<Location /eg/opac>
PerlSetVar OILSWebContextLoader "OpenILS::WWW::EGCatLoader"
# Expire the HTML quickly since we're loading dynamic data
ExpiresActive On
ExpiresByType text/html "access plus 5 seconds"
</Location>
It's dynamic, but there are alternatives to Expires
diff --git a/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGWeb.pm b/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGWeb.pm
index da18d7e..21d4715 100644
--- a/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGWeb.pm
+++ b/Open-ILS/src/perlmods/lib/OpenILS/WWW/EGWeb.pm
@@ -129,11 +129,16 @@ sub handler_guts {
$vhost_processor_cache{$processor_key} = $tt;
$ctx->{encode_utf8} = sub {return encode_utf8(shift())};
-unless($tt->process($template, {ctx => $ctx, ENV => \%ENV, l => $text_handler}, $r)) {
+my $_out = '';
+unless($tt->process($template, {ctx => $ctx, ENV => \%ENV, l => $text_handler}, \$_out)) {
$r->log->warn('egweb: template error: ' . $tt->error);
return Apache2::Const::HTTP_INTERNAL_SERVER_ERROR;
}
+my $etag = md5_hex(Encode::encode_utf8($_out);)
+$r->headers_out->add('Etag' => $etag);
+$r->print($_out);
+
return Apache2::Const::OK;
Expires
?ETag
and no-cache
, the browser will always make a request to the serverExpires
can be turned off
<Location /eg/opac>
PerlSetVar OILSWebContextLoader "OpenILS::WWW::EGCatLoader"
ExpiresActive Off
</Location>
Jessie doesn't deliver a new enough version, so:
apt-get -t jessie-backports install nginx
diff --git a/examples/nginx/osrf-ws-http-proxy b/examples/nginx/osrf-ws-http-proxy
index d079230..2fbf832 100644
--- a/examples/nginx/osrf-ws-http-proxy
+++ b/examples/nginx/osrf-ws-http-proxy
@@ -19,7 +19,7 @@ server {
}
server {
- listen 443;
+ listen 443 ssl http2;
ssl on;
# Use the same SSL certificate as Apache.
$ curl --http2 -H "Accept-Encoding: gzip" \
-I https://shiny.example.org/eg/opac/home
HTTP/2 200
server: nginx/1.10.3
date: Sat, 01 Apr 2017 15:44:15 GMT
content-type: text/html; encoding=utf8
etag: 1735fc80e7ea268e656d53ac9126e438
nginx falls back to HTTP 1.1 for browsers that don't support HTTP/2
(Yes, I tested with w3m)
Not bad. Almost ready for prime time!
We've inadvertently enabled the web staff client--yay!?
But we're lacking a few things…
https://shiny.example.org/eg/opac/myopac/holds?query=potter
https://shiny.example.org/eg/opac/myopac/holds?query=harry
/myopac/
pages, at least, GET params shouldn't matter/eg/opac/
, /eg/staff/
, and /eg/myopac/
are conceptually separate apps/
require them all to use the same service worker/eg/opac/assets/
, /eg/staff/assets/
, and /eg/myopac/assets
to serve up static assets from /openils/var/web/
?/eg/myopac
?I got people with me, people who trust each other, who do for each other and ain't always looking for the advantage. There's good people in the 'verse. Not many, lord knows, but you only need a few. "Our Mrs. Reynolds." Firefly, performance by Nathan Fillion, season 1, episode 3, 2002-10-02.
This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License