mirror of
https://github.com/trambarhq/relaks-wordpress-example.git
synced 2025-09-09 15:40:39 +02:00
Added Docker and Nginx configs.
This commit is contained in:
51
server/docker-compose.yml
Normal file
51
server/docker-compose.yml
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
version: "2"
|
||||||
|
services:
|
||||||
|
db:
|
||||||
|
image: mysql:5.7
|
||||||
|
ports:
|
||||||
|
- 3306:3306
|
||||||
|
volumes:
|
||||||
|
- db_data:/var/lib/mysql
|
||||||
|
environment:
|
||||||
|
MYSQL_ROOT_PASSWORD: somewordpress
|
||||||
|
MYSQL_DATABASE: wordpress
|
||||||
|
MYSQL_USER: wordpress
|
||||||
|
MYSQL_PASSWORD: wordpress
|
||||||
|
restart: always
|
||||||
|
wordpress:
|
||||||
|
depends_on:
|
||||||
|
- db
|
||||||
|
image: wordpress:latest
|
||||||
|
ports:
|
||||||
|
- 8888:80
|
||||||
|
environment:
|
||||||
|
WORDPRESS_DB_HOST: db:3306
|
||||||
|
WORDPRESS_DB_USER: wordpress
|
||||||
|
WORDPRESS_DB_PASSWORD: wordpress
|
||||||
|
restart: always
|
||||||
|
node:
|
||||||
|
image: node:8
|
||||||
|
ports:
|
||||||
|
- 8080:80
|
||||||
|
volumes:
|
||||||
|
- ..:/opt/example
|
||||||
|
- ./cache:/var/cache/nginx
|
||||||
|
command: [ node, /opt/example/server/index.js ]
|
||||||
|
environment:
|
||||||
|
WORDPRESS_HOST: wordpress
|
||||||
|
NGINX_HOST: nginx
|
||||||
|
NGINX_CACHE: /var/cache/nginx/data
|
||||||
|
restart: always
|
||||||
|
nginx:
|
||||||
|
depends_on:
|
||||||
|
- wordpress
|
||||||
|
- node
|
||||||
|
image: nginx:latest
|
||||||
|
volumes:
|
||||||
|
- ./nginx:/etc/nginx/conf.d
|
||||||
|
- ./cache:/var/cache/nginx
|
||||||
|
ports:
|
||||||
|
- "8000:80"
|
||||||
|
restart: always
|
||||||
|
volumes:
|
||||||
|
db_data:
|
217
server/index.js
Normal file
217
server/index.js
Normal file
@@ -0,0 +1,217 @@
|
|||||||
|
const FS = require('fs');
|
||||||
|
const OS = require('os');
|
||||||
|
const Express = require('express');
|
||||||
|
const CrossFetch = require('cross-fetch');
|
||||||
|
const DNSCache = require('dnscache');
|
||||||
|
const Crypto = require('crypto');
|
||||||
|
const SpiderDetector = require('spider-detector')
|
||||||
|
const ReactDOMServer = require('react-dom/server');
|
||||||
|
const ClientApp = require('./client/app');
|
||||||
|
|
||||||
|
// enable DNS caching
|
||||||
|
let dnsCache = DNSCache({ enable: true, ttl: 300, cachesize: 100 });
|
||||||
|
|
||||||
|
const basePath = `/`;
|
||||||
|
const perPage = 10;
|
||||||
|
const serverPort = 80;
|
||||||
|
const wordpressHost = process.env.WORDPRESS_HOST;
|
||||||
|
const nginxHost = process.env.NGINX_HOST;
|
||||||
|
const nginxCache = process.env.NGINX_CACHE;
|
||||||
|
|
||||||
|
let wordpressIP;
|
||||||
|
dnsCache.lookup(wordpressHost, (err, result) => {
|
||||||
|
if (!err) {
|
||||||
|
wordpressIP = `::ffff:${result}`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let app = Express();
|
||||||
|
app.set('json spaces', 2);
|
||||||
|
app.use(SpiderDetector.middleware());
|
||||||
|
app.use(`/`, Express.static(`${__dirname}/www`));
|
||||||
|
app.get('/.mtime', handleTimestampRequest);
|
||||||
|
app.get(`/*`, handlePageRequest);
|
||||||
|
app.purge(`/*`, handlePurgeRequest);
|
||||||
|
app.use(handleError);
|
||||||
|
app.listen(serverPort);
|
||||||
|
|
||||||
|
async function handlePageRequest(req, res, next) {
|
||||||
|
try {
|
||||||
|
let host = `http://${nginxHost}`;
|
||||||
|
let path = req.url;
|
||||||
|
let noScript = (req.query.js === '0')
|
||||||
|
let target = (req.isSpider() || noScript) ? 'seo' : 'hydrate';
|
||||||
|
let sourceURLs = [];
|
||||||
|
// create a fetch() that remembers the URLs used
|
||||||
|
let fetch = (url, options) => {
|
||||||
|
console.log(`Fetching: ${url}`);
|
||||||
|
if (url.startsWith(host)) {
|
||||||
|
var relURL = url.substr(host.length);
|
||||||
|
sourceURLs.push(relURL);
|
||||||
|
}
|
||||||
|
return CrossFetch(url, options);
|
||||||
|
};
|
||||||
|
let options = { host, path, target, fetch };
|
||||||
|
let rootNode = await ClientApp.render(options);
|
||||||
|
let appHTML = ReactDOMServer.renderToString(rootNode);
|
||||||
|
let indexHTMLPath = `${__dirname}/client/index.html`;
|
||||||
|
let html = await replaceHTMLComment(indexHTMLPath, 'APP', appHTML);
|
||||||
|
|
||||||
|
if (target === 'hydrate') {
|
||||||
|
// add <noscript> tag to redirect to SEO version
|
||||||
|
let meta = `<meta http-equiv=refresh content="0; url=?js=0">`;
|
||||||
|
html += `<noscript>${meta}</noscript>`;
|
||||||
|
} else if (target === 'seo') {
|
||||||
|
res.set({ 'X-Accel-Expires': 0 });
|
||||||
|
}
|
||||||
|
res.type('html').send(html);
|
||||||
|
|
||||||
|
recordDependencies(path, sourceURLs);
|
||||||
|
} catch (err) {
|
||||||
|
next(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleTimestampRequest(req, res, next) {
|
||||||
|
try {
|
||||||
|
let now = new Date;
|
||||||
|
let ts = now.toISOString();
|
||||||
|
res.type('text').send(ts);
|
||||||
|
|
||||||
|
let path = req.url;
|
||||||
|
recordDependencies(path, '*');
|
||||||
|
} catch (err) {
|
||||||
|
next(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleError(err, req, res, next) {
|
||||||
|
if (!res.headersSent) {
|
||||||
|
res.type('text').status(400).send(err.message);
|
||||||
|
} else {
|
||||||
|
console.error(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handlePurgeRequest(req, res) {
|
||||||
|
let remoteIP = req.connection.remoteAddress;
|
||||||
|
if (remoteIP === wordpressIP) {
|
||||||
|
let url = req.url;
|
||||||
|
let method = req.headers['x-purge-method'];
|
||||||
|
await purgeCachedFile(url, method);
|
||||||
|
|
||||||
|
let pattern = (method === 'regex') ? new RegExp(url) : url;
|
||||||
|
let isJSON;
|
||||||
|
if (pattern instanceof RegExp) {
|
||||||
|
isJSON = pattern.test('/wp-json');
|
||||||
|
} else {
|
||||||
|
isJSON = pattern.startsWith('/wp-json');
|
||||||
|
}
|
||||||
|
if (isJSON) {
|
||||||
|
await purgeDependentPages(pattern);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
res.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
let pageDependencies = {};
|
||||||
|
|
||||||
|
function recordDependencies(url, sourceURLs) {
|
||||||
|
if (sourceURLs instanceof Array) {
|
||||||
|
sourceURLs = sourceURLs.map(removeTrailingSlash);
|
||||||
|
}
|
||||||
|
pageDependencies[url] = sourceURLs;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function purgeDependentPages(host, pattern) {
|
||||||
|
for (let [ url, sourceURLs ] of Object.entries(pageDependencies)) {
|
||||||
|
let match = false;
|
||||||
|
if (sourceURLs === '*') {
|
||||||
|
match = true;
|
||||||
|
} else if (pattern instanceof RegExp) {
|
||||||
|
match = sourceURLs.some((sourceURL) => {
|
||||||
|
return pattern.test(sourceURL);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
let url = removeTrailingSlash(pattern);
|
||||||
|
if (sourceURLs.indexOf(url)) {
|
||||||
|
match = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (match) {
|
||||||
|
delete pageDependencies[pageURL];
|
||||||
|
await purgeCachedFile(pageURL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function purgeCachedFile(url, method) {
|
||||||
|
console.log(`Purging: ${url}`);
|
||||||
|
if (method === 'regex') {
|
||||||
|
// delete everything
|
||||||
|
let files = await new Promise((resolve, reject) => {
|
||||||
|
FS.readdir(nginxCache, (err, files) => {
|
||||||
|
if (!err) {
|
||||||
|
resolve(files);
|
||||||
|
} else {
|
||||||
|
resolve([]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
let isMD5 = /^[0-9a-f]{32}$/;
|
||||||
|
for (let file of files) {
|
||||||
|
if (isMD5.test(file)) {
|
||||||
|
await unlinkFile(`${nginxCache}/${file}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let hash = Crypto.createHash('md5').update(url);
|
||||||
|
let md5 = hash.digest("hex");
|
||||||
|
await unlinkFile(`${nginxCache}/${md5}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function unlinkFile(path) {
|
||||||
|
console.log(`Unlinking ${path}`);
|
||||||
|
await new Promise((resolve, reject) => {
|
||||||
|
FS.unlink(path, (err) => {
|
||||||
|
if (!err) {
|
||||||
|
resolve(true);
|
||||||
|
} else {
|
||||||
|
resolve(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function replaceHTMLComment(path, comment, newElement) {
|
||||||
|
let text = await new Promise((resolve, reject) => {
|
||||||
|
FS.readFile(path, 'utf-8', (err, text) => {
|
||||||
|
if (!err) {
|
||||||
|
resolve(text);
|
||||||
|
} else {
|
||||||
|
reject(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return text.replace(`<!--${comment}-->`, newElement).replace(`<!--${comment}-->`, newElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove trailing slash from URL
|
||||||
|
*
|
||||||
|
* @param {String} url
|
||||||
|
*
|
||||||
|
* @return {String}
|
||||||
|
*/
|
||||||
|
function removeTrailingSlash(url) {
|
||||||
|
var lc = url.charAt(url.length - 1);
|
||||||
|
if (lc === '/') {
|
||||||
|
url = url.substr(0, url.length - 1);
|
||||||
|
}
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
process.on('unhandledRejection', (err) => {
|
||||||
|
console.error(err);
|
||||||
|
});
|
14
server/nginx/caching.conf
Normal file
14
server/nginx/caching.conf
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
proxy_buffering on;
|
||||||
|
proxy_cache data;
|
||||||
|
proxy_cache_key $uri$is_args$args;
|
||||||
|
proxy_cache_min_uses 1;
|
||||||
|
proxy_cache_valid 200 301 302 120m;
|
||||||
|
proxy_cache_valid 404 1m;
|
||||||
|
proxy_hide_header Cache-Control;
|
||||||
|
proxy_hide_header Expires;
|
||||||
|
proxy_hide_header Set-Cookie;
|
||||||
|
proxy_ignore_headers Cache-Control Expires Set-Cookie;
|
||||||
|
|
||||||
|
add_header Cache-Control "public,must-revalidate";
|
||||||
|
add_header X-Cache-Date $upstream_http_date;
|
||||||
|
add_header X-Cache-Status $upstream_cache_status;
|
44
server/nginx/default.conf
Normal file
44
server/nginx/default.conf
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
gzip on;
|
||||||
|
gzip_disable "msie6";
|
||||||
|
|
||||||
|
gzip_vary on;
|
||||||
|
gzip_proxied any;
|
||||||
|
gzip_comp_level 6;
|
||||||
|
gzip_buffers 16 8k;
|
||||||
|
gzip_http_version 1.1;
|
||||||
|
gzip_types text/plain text/css application/json application/javascript application/x-javascript text/xml application/xml application/xml+rss text/javascript image/svg+xml;
|
||||||
|
gzip_min_length 256;
|
||||||
|
|
||||||
|
proxy_cache_path /var/cache/nginx/data keys_zone=data:10m max_size=1g;
|
||||||
|
proxy_temp_path /var/cache/nginx/tmp;
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name _;
|
||||||
|
|
||||||
|
location ~ ^/wp-json/wp/v2/(categories) {
|
||||||
|
proxy_pass http://wordpress;
|
||||||
|
proxy_set_header Host $http_host;
|
||||||
|
include /etc/nginx/conf.d/caching.conf;
|
||||||
|
}
|
||||||
|
|
||||||
|
location ~ ^/wp-.* {
|
||||||
|
proxy_pass http://wordpress;
|
||||||
|
proxy_set_header Host $http_host;
|
||||||
|
}
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_pass http://node;
|
||||||
|
proxy_set_header Host $http_host;
|
||||||
|
include /etc/nginx/conf.d/caching.conf;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
upstream node {
|
||||||
|
server node:80;
|
||||||
|
}
|
||||||
|
|
||||||
|
upstream wordpress {
|
||||||
|
server wordpress:80;
|
||||||
|
}
|
Reference in New Issue
Block a user