Improve page speeds
* Defer loading of highlight css * Cache /static for up to 16 days
This commit is contained in:
parent
ad1724b47f
commit
503d2d1ad0
375
source/app.d
375
source/app.d
|
@ -1,181 +1,194 @@
|
|||
import std.experimental.logger;
|
||||
import std.range;
|
||||
import std.string;
|
||||
import std.stdio;
|
||||
import std.typecons;
|
||||
|
||||
import vibe.d;
|
||||
|
||||
import article;
|
||||
import page;
|
||||
import project;
|
||||
import watcher;
|
||||
|
||||
/**
|
||||
* Internal list of articles, pages and projects by slug.
|
||||
*/
|
||||
Article[string] articles;
|
||||
Page[string] pages;
|
||||
Project[string] projects;
|
||||
|
||||
/**
|
||||
* Default ordering and list with pointers to ordered articles.
|
||||
* (Note: this is code which will actually be compiled and passed on!)
|
||||
*/
|
||||
immutable string articleSortPred = "a.firstPublished > b.firstPublished";
|
||||
Article*[] articleList;
|
||||
|
||||
immutable string pageSortPred = "a.title < b.title";
|
||||
Page*[] pageList;
|
||||
|
||||
immutable string projectSortPred = "a.title < b.title";
|
||||
Project*[] projectList;
|
||||
|
||||
/**
|
||||
* Output types for the content.
|
||||
*/
|
||||
enum OutputType {
|
||||
HTML,
|
||||
MARKDOWN
|
||||
}
|
||||
|
||||
const string MIME_MARKDOWN = "text/markdown";
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Get's the document type for the given slug based on extension
|
||||
* and returns the slug without extension and the document type. Also removes the extension from the
|
||||
* slug.
|
||||
*/
|
||||
private OutputType getOutputType(ref string slug) {
|
||||
if (slug.endsWith(".md")) {
|
||||
slug = chomp(slug, ".md");
|
||||
return OutputType.MARKDOWN;
|
||||
} else if (slug.endsWith(".html")){
|
||||
// If explicitly asking for HTML, we'll return HTML
|
||||
slug = chomp(slug, ".html");
|
||||
return OutputType.HTML;
|
||||
} else {
|
||||
// If in the future, for any reason, we no longer use HTML
|
||||
// this allows to us to keep the current urls with an option
|
||||
// to change the output in the future.
|
||||
return OutputType.HTML;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Template method for fetching a single page for a subclass of page.
|
||||
* Params:
|
||||
* T = the data structure/class to be passed as template parameter. Must have a slug parameter.
|
||||
* templ = The template to use when rendering.
|
||||
* array = An associative array where the keys are slugs for parameters and the values the template parameter.
|
||||
* req = The server request to consume. Assumes there is an slug parameter.
|
||||
* res = The server response to write to.
|
||||
*
|
||||
*/
|
||||
void getSingle(T, string templ)(ref T[string] array, HTTPServerRequest req, HTTPServerResponse res) {
|
||||
string slug = req.params["slug"];
|
||||
OutputType outputType = getOutputType(slug);
|
||||
|
||||
enforceHTTP(slug in array, HTTPStatus.notFound, "Page not found");
|
||||
T content = array[slug];
|
||||
res.headers["Cache-Control"] = "public";
|
||||
switch(outputType) with (OutputType) {
|
||||
case MARKDOWN:
|
||||
res.writeBody(content.contentSource, MIME_MARKDOWN);
|
||||
break;
|
||||
default:
|
||||
case HTML:
|
||||
res.render!(templ, content);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates response for /posts/:slug and /palen/:slug.
|
||||
*/
|
||||
void articleGetSingle(HTTPServerRequest req, HTTPServerResponse res) {
|
||||
getSingle!(Article, "pages/article.dt")(articles, req, res);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates response for /posts and /palen
|
||||
*/
|
||||
void articleGetOverview(HTTPServerRequest req, HTTPServerResponse res) {
|
||||
res.headers["Cache-Control"] = "public";
|
||||
render!("pages/article-list.dt", articleList)(res);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates response for /projects and /projecten
|
||||
*/
|
||||
void projectGetOverview(HTTPServerRequest req, HTTPServerResponse res) {
|
||||
res.headers["Cache-Control"] = "public";
|
||||
render!("pages/project-list.dt", projectList)(res);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate response for a page
|
||||
*/
|
||||
void pageGet(HTTPServerRequest req, HTTPServerResponse res) {
|
||||
// If no slug is supplied, it will be adjusted to "index"
|
||||
if (("slug" in req.params) is null) {
|
||||
req.params.addField("slug", "index");
|
||||
}
|
||||
getSingle!(Page, "pages/page.dt")(pages, req, res);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate response for a project page
|
||||
*/
|
||||
void projectGet(HTTPServerRequest req, HTTPServerResponse res) {
|
||||
res.headers["Cache-Control"] = "public";
|
||||
getSingle!(Project, "pages/project.dt")(projects, req, res);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates response whenever an error occurs.
|
||||
*/
|
||||
@safe
|
||||
void errorPage(HTTPServerRequest req, HTTPServerResponse res, HTTPServerErrorInfo error) {
|
||||
render!("pages/error.dt", error)(res);
|
||||
}
|
||||
|
||||
void main() {
|
||||
HTTPServerSettings settings = new HTTPServerSettings;
|
||||
settings.bindAddresses = ["0.0.0.0"];
|
||||
settings.port = 3465;
|
||||
settings.serverString = "zeg ik lekker niet";
|
||||
settings.errorPageHandler = toDelegate(&errorPage);
|
||||
settings.keepAliveTimeout = dur!"seconds"(60);
|
||||
debug {
|
||||
settings.accessLogToConsole = true;
|
||||
}
|
||||
|
||||
URLRouter router = new URLRouter;
|
||||
router.get("/posts/:slug", &articleGetSingle);
|
||||
router.get("/palen/:slug", &articleGetSingle);
|
||||
router.get("/posts/", &articleGetOverview);
|
||||
router.get("/palen/", &articleGetOverview);
|
||||
router.get("/projects/", &projectGetOverview);
|
||||
router.get("/projects/:slug", &projectGet);
|
||||
router.get("/projecten/:slug", &projectGet);
|
||||
router.get("/static/*", serveStaticFiles("./public/"));
|
||||
router.get("/:slug", &pageGet);
|
||||
router.get("/", &pageGet);
|
||||
|
||||
listenHTTP(settings, router);
|
||||
|
||||
// Start indexing pages.
|
||||
runTask({
|
||||
initPages!(Page, pageSortPred)(pages, pageList, "pages");
|
||||
});
|
||||
runTask({
|
||||
initPages!(Article, articleSortPred)(articles, articleList, "articles");
|
||||
});
|
||||
runTask({
|
||||
initPages!(Project, projectSortPred)(projects, projectList, "projects");
|
||||
});
|
||||
runApplication();
|
||||
}
|
||||
import std.experimental.logger;
|
||||
import std.range;
|
||||
import std.string;
|
||||
import std.stdio;
|
||||
import std.typecons;
|
||||
|
||||
import vibe.d;
|
||||
|
||||
import article;
|
||||
import page;
|
||||
import project;
|
||||
import watcher;
|
||||
|
||||
/**
|
||||
* Internal list of articles, pages and projects by slug.
|
||||
*/
|
||||
Article[string] articles;
|
||||
Page[string] pages;
|
||||
Project[string] projects;
|
||||
|
||||
/**
|
||||
* Default ordering and list with pointers to ordered articles.
|
||||
* (Note: this is code which will actually be compiled and passed on!)
|
||||
*/
|
||||
immutable string articleSortPred = "a.firstPublished > b.firstPublished";
|
||||
Article*[] articleList;
|
||||
|
||||
immutable string pageSortPred = "a.title < b.title";
|
||||
Page*[] pageList;
|
||||
|
||||
immutable string projectSortPred = "a.title < b.title";
|
||||
Project*[] projectList;
|
||||
|
||||
/**
|
||||
* Output types for the content.
|
||||
*/
|
||||
enum OutputType {
|
||||
HTML,
|
||||
MARKDOWN
|
||||
}
|
||||
|
||||
immutable string MIME_MARKDOWN = "text/markdown";
|
||||
|
||||
immutable Duration CACHE_TIME = days(16);
|
||||
|
||||
/**
|
||||
* Get's the document type for the given slug based on extension
|
||||
* and returns the slug without extension and the document type. Also removes the extension from the
|
||||
* slug.
|
||||
*/
|
||||
private OutputType getOutputType(ref string slug) {
|
||||
if (slug.endsWith(".md")) {
|
||||
slug = chomp(slug, ".md");
|
||||
return OutputType.MARKDOWN;
|
||||
} else if (slug.endsWith(".html")){
|
||||
// If explicitly asking for HTML, we'll return HTML
|
||||
slug = chomp(slug, ".html");
|
||||
return OutputType.HTML;
|
||||
} else {
|
||||
// If in the future, for any reason, we no longer use HTML
|
||||
// this allows to us to keep the current urls with an option
|
||||
// to change the output in the future.
|
||||
return OutputType.HTML;
|
||||
}
|
||||
}
|
||||
|
||||
void addCachingHeader(bool publicCache = true)(ref HTTPServerResponse res) {
|
||||
string header = "";
|
||||
static if (publicCache) {
|
||||
header ~= "public";
|
||||
} else {
|
||||
header ~= "private";
|
||||
}
|
||||
header ~= ", max-age=" ~ to!string(CACHE_TIME.total!"seconds");
|
||||
res.headers["Cache-Control"] = header;
|
||||
}
|
||||
|
||||
/**
|
||||
* Template method for fetching a single page for a subclass of page.
|
||||
* Params:
|
||||
* T = the data structure/class to be passed as template parameter. Must have a slug parameter.
|
||||
* templ = The template to use when rendering.
|
||||
* array = An associative array where the keys are slugs for parameters and the values the template parameter.
|
||||
* req = The server request to consume. Assumes there is an slug parameter.
|
||||
* res = The server response to write to.
|
||||
*
|
||||
*/
|
||||
void getSingle(T, string templ)(ref T[string] array, HTTPServerRequest req, HTTPServerResponse res) {
|
||||
string slug = req.params["slug"];
|
||||
OutputType outputType = getOutputType(slug);
|
||||
|
||||
enforceHTTP(slug in array, HTTPStatus.notFound, "Page not found");
|
||||
T content = array[slug];
|
||||
switch(outputType) with (OutputType) {
|
||||
case MARKDOWN:
|
||||
res.writeBody(content.contentSource, MIME_MARKDOWN);
|
||||
break;
|
||||
default:
|
||||
case HTML:
|
||||
res.render!(templ, content);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates response for /posts/:slug and /palen/:slug.
|
||||
*/
|
||||
void articleGetSingle(HTTPServerRequest req, HTTPServerResponse res) {
|
||||
getSingle!(Article, "pages/article.dt")(articles, req, res);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates response for /posts and /palen
|
||||
*/
|
||||
void articleGetOverview(HTTPServerRequest req, HTTPServerResponse res) {
|
||||
addCachingHeader(res);
|
||||
render!("pages/article-list.dt", articleList)(res);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates response for /projects and /projecten
|
||||
*/
|
||||
void projectGetOverview(HTTPServerRequest req, HTTPServerResponse res) {
|
||||
addCachingHeader(res);
|
||||
render!("pages/project-list.dt", projectList)(res);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate response for a page
|
||||
*/
|
||||
void pageGet(HTTPServerRequest req, HTTPServerResponse res) {
|
||||
addCachingHeader(res);
|
||||
// If no slug is supplied, it will be adjusted to "index"
|
||||
if (("slug" in req.params) is null) {
|
||||
req.params.addField("slug", "index");
|
||||
}
|
||||
getSingle!(Page, "pages/page.dt")(pages, req, res);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate response for a project page
|
||||
*/
|
||||
void projectGet(HTTPServerRequest req, HTTPServerResponse res) {
|
||||
res.headers["Cache-Control"] = "public";
|
||||
getSingle!(Project, "pages/project.dt")(projects, req, res);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates response whenever an error occurs.
|
||||
*/
|
||||
@safe
|
||||
void errorPage(HTTPServerRequest req, HTTPServerResponse res, HTTPServerErrorInfo error) {
|
||||
render!("pages/error.dt", error)(res);
|
||||
}
|
||||
|
||||
void main() {
|
||||
HTTPServerSettings settings = new HTTPServerSettings;
|
||||
settings.bindAddresses = ["0.0.0.0"];
|
||||
settings.port = 3465;
|
||||
settings.serverString = "zeg ik lekker niet";
|
||||
settings.errorPageHandler = toDelegate(&errorPage);
|
||||
settings.keepAliveTimeout = dur!"seconds"(60);
|
||||
debug {
|
||||
settings.accessLogToConsole = true;
|
||||
}
|
||||
HTTPFileServerSettings fSettings = new HTTPFileServerSettings;
|
||||
fSettings.maxAge = days(16);
|
||||
|
||||
URLRouter router = new URLRouter;
|
||||
router.get("/posts/:slug", &articleGetSingle);
|
||||
router.get("/palen/:slug", &articleGetSingle);
|
||||
router.get("/posts/", &articleGetOverview);
|
||||
router.get("/palen/", &articleGetOverview);
|
||||
router.get("/projects/", &projectGetOverview);
|
||||
router.get("/projects/:slug", &projectGet);
|
||||
router.get("/projecten/:slug", &projectGet);
|
||||
router.get("/static/*", serveStaticFiles("./public/", fSettings));
|
||||
router.get("/:slug", &pageGet);
|
||||
router.get("/", &pageGet);
|
||||
|
||||
listenHTTP(settings, router);
|
||||
|
||||
// Start indexing pages.
|
||||
runTask({
|
||||
initPages!(Page, pageSortPred)(pages, pageList, "pages");
|
||||
});
|
||||
runTask({
|
||||
initPages!(Article, articleSortPred)(articles, articleList, "articles");
|
||||
});
|
||||
runTask({
|
||||
initPages!(Project, projectSortPred)(projects, projectList, "projects");
|
||||
});
|
||||
runApplication();
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ html(prefix="og: http://ogp.me/ns#")
|
|||
head
|
||||
meta(name="viewport", content="width=device-width; initial-scale=1")
|
||||
link(rel="stylesheet", href="/static/style/base.css")
|
||||
link(rel="stylesheet", href="/static/style/highlight.css")
|
||||
link(rel="stylesheet", href="/static/style/highlight.css", defer)
|
||||
link(rel="alternate stylesheet", href="/static/style/old.css", title="1999")
|
||||
link(rel="shortcut icon", href="/static/img/logo.png")
|
||||
link(rel="apple-touch-icon", href="/static/img/logo.png")
|
||||
|
|
Loading…
Reference in a new issue