Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
add a Service Worker to serve /search from cache
For more details about Service Workers, see
https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API

The new /placeholder.html handler serves the placeholder template with
inlined CSS and the %q%/%q_escaped% placeholders which will then be
replaced by the service worker javascript code with the current query.

instant.js registers the service worker, which will download assets to
cache and handle any subsequent requests to /search, getting rid of an
entire round trip.

fixes #69
  • Loading branch information
stapelberg committed Jul 7, 2016
1 parent 9dbf468 commit 7f31aef
Show file tree
Hide file tree
Showing 6 changed files with 124 additions and 2 deletions.
12 changes: 12 additions & 0 deletions cmd/dcs-web/dcs-web.go
Expand Up @@ -391,6 +391,18 @@ func main() {
http.HandleFunc("/queryz", QueryzHandler)
http.HandleFunc("/track", Track)
http.HandleFunc("/events/", EventsHandler)
// Used by the service worker.
http.HandleFunc("/placeholder.html", func(w http.ResponseWriter, r *http.Request) {
if err := common.Templates.ExecuteTemplate(w, "placeholder.html", map[string]interface{}{
"criticalcss": common.CriticalCss,
"version": common.Version,
"q": "%q%",
"q_escaped": "%q_escaped%",
}); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
})

http.Handle("/instantws", websocket.Handler(InstantServer))
http.Handle("/metrics", prometheus.Handler())
Expand Down
2 changes: 1 addition & 1 deletion cmd/dcs-web/templates/footer.html
Expand Up @@ -17,6 +17,6 @@
<script type="text/javascript" src="/loadCSS.min.js"></script>
<script type="text/javascript" src="/cssrelpreload.min.js"></script>
<script type="text/javascript" src="/jquery.min.js"></script>
<script type="text/javascript" src="/instant.min.js?5"></script>
<script type="text/javascript" src="/instant.min.js?6"></script>
</body>
</html>
4 changes: 4 additions & 0 deletions nginx.example
Expand Up @@ -190,6 +190,10 @@ server {
proxy_pass http://dcsweb;
}

location /placeholder.html {
proxy_pass http://dcsweb;
}

# Everything else must be a static page, so we directly deliver (with
# appropriate caching headers).
location /research/ {
Expand Down
2 changes: 1 addition & 1 deletion static/Makefile
@@ -1,4 +1,4 @@
all: instant.min.js instant.min.js.gz debcodesearch.min.css.gz non-critical.min.css non-critical.min.css.gz critical.min.css critical.min.css.gz loadCSS.min.js loadCSS.min.js.gz cssrelpreload.min.js cssrelpreload.min.js.gz url-search-params.min.js.gz
all: instant.min.js instant.min.js.gz debcodesearch.min.css.gz non-critical.min.css non-critical.min.css.gz critical.min.css critical.min.css.gz loadCSS.min.js loadCSS.min.js.gz cssrelpreload.min.js cssrelpreload.min.js.gz url-search-params.min.js.gz service-worker.min.js service-worker.min.js.gz

%.gz: %
echo "ZÖPFLI $<"
Expand Down
4 changes: 4 additions & 0 deletions static/instant.js
Expand Up @@ -533,6 +533,10 @@ function changeGrouping() {
}

$(window).load(function() {
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/service-worker.min.js');
}

// Pressing “/” anywhere on the page focuses the search field.
$(document).keydown(function(e) {
if (e.which == 191) {
Expand Down
102 changes: 102 additions & 0 deletions static/service-worker.js
@@ -0,0 +1,102 @@
// vim:ts=4:sw=4:et
var version = 'v1/';

var assets = {
'/non-critical.min.css': true,
'/Pics/openlogo-50.svg': true,
'/Pics/rackspace.svg': true,
'/jquery.min.js': true,
'/url-search-params.min.js': true,
'/loadCSS.min.js': true,
'/cssrelpreload.min.js': true,
'/instant.min.js?6': true,
// Only cache fonts in woff2 format, all browsers which support service
// workers also support woff2.
'/Inconsolata.woff2': true,
'/Roboto-Regular.woff2': true,
'/Roboto-Bold.woff2': true,
'/placeholder.html': true
};

self.addEventListener("install", function(event) {
event.waitUntil(
caches
.open(version + 'assets')
.then(function(cache) {
return cache.addAll(Object.keys(assets));
})
);
});

self.addEventListener("activate", function(event) {
// The new version of the service worker is activated, superseding any old
// version. Go through the cache and delete all assets whose key doesn’t
// start with the current version prefix.
event.waitUntil(
caches
.keys()
.then(function(keys) {
return Promise.all(
keys
.filter(function(key) {
return !key.startsWith(version);
})
.map(function(key) {
return caches['delete'](key);
})
);
})
);
});

self.addEventListener("fetch", function(event) {
if (event.request.method !== 'GET') {
return;
}
var u = new URL(event.request.url);
if (assets[u.pathname + u.search] === true) {
event.respondWith(caches.match(event.request).then(function(response) {
// Defense in depth: in case the cache request fails, fall back to
// fetching the request.
return response || fetch(event.request);
}));
return;
}
if (u.pathname === '/search') {
event.respondWith(caches.match('/placeholder.html').then(function(response) {
if (!response) {
return fetch(event.request);
}
// URLSearchParams is not available on all browsers yet.
var searchParams = u.search.slice(1).split('&');
var q;
var qEscaped;
for (var i = 0, len = searchParams.length; i < len; i++) {
if (searchParams[i].indexOf('q=') === 0) {
qEscaped = searchParams[i].substr('q='.length);
q = decodeURIComponent(qEscaped.replace(/\+/g, ' '));
break;
}
}
if (q === undefined) {
return response;
}

var init = {
status: response.status,
statusText: response.statusText,
headers: {},
};
response.headers.forEach(function(v, k) {
init.headers[k] = v;
});
return response.text().then(function(body) {
var replaced = body.replace(/%q%/g, q);
replaced = replaced.replace(/%q_escaped%/g, qEscaped);
return new Response(replaced, init);
});
}));
return;
}
return;
});

0 comments on commit 7f31aef

Please sign in to comment.