Commit 30b2faf7 authored by Lukas Niegsch's avatar Lukas Niegsch

uploaded prototype to gitlab

parent 2d542e61
# Progressive Web Server
This software release takes a local PWA and starts a global server with
some additional configuration options.
## Parameters
The following instance parameters can be configured:
- target-pwa-url: Path of the PWA source.
- publicity: Server availability on other machines.
- server-side-renderering: Prerendering of the PWA via headless chrome.
- persistence: Caching of the PWS state.
- replication-count: Number of times this server is replicated.
- nginx-proxy-port: Port for Ningx proxy to listen on.
- python-proxy-port: Port for Python proxy to listen on.
- monitor-httpd-port: Port for monitoring.
- remote-debugging-port: Port for Chromium to listen on.
- webserver-proxy-port: Port for the NodeJS proxy to listen on.
## Examples
Serve static content of PWA (eg. html server):
-> publicity = public
-> server-side-renderering = none
-> persistence = none
-> replication-count = 1
Background task of the PWA (eg. database):
-> publicity = private
-> server-side-renderering = full
-> persistence = full
-> replication-count = 1
# THIS IS NOT A BUILDOUT FILE, despite purposedly using a compatible syntax.
# The only allowed lines here are (regexes):
# - "^#" comments, copied verbatim
# - "^[" section beginings, copied verbatim
# - lines containing an "=" sign which must fit in the following categorie.
# - "^\s*filename\s*=\s*path\s*$" where "path" is relative to this file
# Copied verbatim.
# - "^\s*hashtype\s*=.*" where "hashtype" is one of the values supported
# by the re-generation script.
# Re-generated.
# - other lines are copied verbatim
# Substitution (${...:...}), extension ([buildout] extends = ...) and
# section inheritance (< = ...) are NOT supported (but you should really
# not need these here).
[template-cfg]
filename = instance.cfg.in
md5sum = 860cab2326f6247315b2b89d9ea35e04
[template-nginx-conf]
_update_hash_filename_ = templates/nginx.conf.in
md5sum = 182fbe802dacacd37d93df73d4041109
[template-mime-types]
_update_hash_filename_ = templates/mime-types.in
md5sum = 1acea1bf3517978b8192547fa43bfd84
[template-webserver]
_update_hash_filename_ = templates/webserver.js.in
md5sum = f5bb415140249f8af94b05fac5d97d39
[template-package-json]
_update_hash_filename_ = templates/package.json.in
md5sum = 54840ef119654a902d6611d031c1eb61
[template-instance-pws]
_update_hash_filename_ = instance-pws.cfg.in
md5sum = 3c110a2f31fa7944fd335810e10a7c30
<!DOCTYPE html>
<html>
<head>
<title>Fibonacci</title>
</head>
<body>
<button id="next-button" type="button">Next Fibonacci number!</button>
<span id="number-debug"></span>
</body>
<script>
function initialize_fibonacci() {
localStorage.x = 0;
localStorage.y = 1;
};
function calculate_next_fibonacci() {
var x = Number(localStorage.x);
var y = Number(localStorage.y);
localStorage.x = y;
localStorage.y = x + y;
};
function get_current_fibonacci() {
var x = Number(localStorage.x);
return x;
};
function update_site(number) {
document.getElementById("number-debug").textContent=number;
};
initialize_fibonacci();
update_site(get_current_fibonacci());
var button = document.getElementById('next-button');
button.addEventListener('click', (event) => {
calculate_next_fibonacci();
update_site(get_current_fibonacci());
});
</script>
</html>
const http = require('http');
const fs = require('fs');
const index = fs.readFileSync('index.html');
async function requestListener(request, response) {
response.writeHead(200, {'Content-Type': 'html'})
response.end(index)
}
const server = http.createServer(requestListener)
server.listen(9000)
\ No newline at end of file
{% set parameter_dict = dict(default_parameter_dict, **slapparameter_dict) %}
[buildout]
extends =
{{ parameter_list['template-monitor'] }}
parts =
nginx-config
nginx-mime-types
nginx-launcher
nginx-graceful
nginx-logrotate
webserver
webserver-package
webserver-launcher
webserver-install-dependencies
promise-port-listening
publish-information
[directory]
recipe = slapos.cookbook:mkdirectory
home = ${buildout:directory}
tmp = ${:home}/tmp
log = ${:home}/log
etc = ${:home}/etc
ssl = ${:etc}/ssl
run = ${:etc}/run
bin = ${:etc}/bin
service = ${:etc}/service
webserver = ${:bin}/webserver
[param-common]
path-chrome = {{ parameter_list['path-chrome'] }}
path-nodejs = {{ parameter_list['path-nodejs'] }}/bin/node
path-npm = {{ parameter_list['path-nodejs'] }}/bin/npm
path-shell = {{ parameter_list['path-dash'] }}/bin/dash
path-nginx = {{ parameter_list['path-nginx'] }}/sbin/nginx
path-nginx-conf = ${directory:etc}/nginx.conf
path-nginx-mime-types = ${directory:etc}/mime-types
path-nginx-launcher = ${directory:service}/nginx-launcher
path-nginx-graceful = ${directory:run}/graceful
path-webserver = ${directory:webserver}/webserver.js
path-webserver-package = ${directory:webserver}/package.json
path-webserver-launcher = ${directory:service}/webserver-launcher
[param-nginx]
<= param-common
ip = {{ parameter_list['partition-ipv6'] }}
port = {{ parameter_dict['nginx-proxy-port'] }}
proxy-address = [${:ip}]:${:port}
webserver-port = ${param-webserver:port}
pid = ${directory:log}/nginx.pid
error-log = ${directory:log}/nginx-error.log
access-log = ${directory:log}/nginx-access.log
mime-types = ${directory:etc}/mime-types
worker-connections = 1024
server-name = _
nginx-key-file = ${frontend-instance-certificate:key-file}
nginx-cert-file = ${frontend-instance-certificate:cert-file}
pwa-root-path = {{ parameter_dict['target-pwa-path'] }}
pws-services-path = ${directory:etc}/pws
client_body_temp_path = ${directory:tmp}/client_body_temp_path
proxy_temp_path = ${directory:tmp}/proxy_temp_path
fastcgi_temp_path = ${directory:tmp}/fastcgi_temp_path
uwsgi_temp_path = ${directory:tmp}/uwsgi_temp_path
scgi_temp_path = ${directory:tmp}/scgi_temp_path
[param-webserver]
<= param-common
port = {{ parameter_dict['webserver-proxy-port'] }}
remote-debugging-address = {{ parameter_list['partition-ipv4'] }}:${:remote-debugging-port}
remote-debugging-port = {{ parameter_dict['remote-debugging-port'] }}
target-url = {{ parameter_dict['target-pwa-path'] }}
remote-debugging-address = ${param-nginx:proxy-address}
[nginx-config]
recipe = slapos.recipe.template:jinja2
template = {{ parameter_list['template-nginx-conf'] }}
rendered = ${param-common:path-nginx-conf}
mode = 600
context = section param_nginx param-nginx
[nginx-mime-types]
recipe = slapos.recipe.template:jinja2
template = {{ parameter_list['template-mime-types'] }}
rendered = ${param-common:path-nginx-mime-types}
mode = 600
[nginx-launcher]
recipe = collective.recipe.template
input = inline:
#! ${param-common:path-shell}
exec ${param-common:path-nginx} -c ${param-common:path-nginx-conf}
output = ${param-common:path-nginx-launcher}
mode = 700
[nginx-graceful]
recipe = collective.recipe.template
input = inline:
#! ${param-common:path-shell}
exec kill -s SIGHUP $(cat ${param-nginx:pid})
output = ${param-common:path-nginx-graceful}
mode = 700
[nginx-logrotate]
<= logrotate-entry-base
name = nginx
log = ${param-nginx:access-log} ${param-nginx:error-log}
post = kill -USR1 $(cat ${param-nginx:pid})
[webserver]
recipe = slapos.recipe.template:jinja2
template = {{ parameter_list['template-webserver'] }}
rendered = ${param-common:path-webserver}
mode = 500
[webserver-package]
recipe = slapos.recipe.template:jinja2
template = {{ parameter_list['template-webserver-package'] }}
rendered = ${param-common:path-webserver-package}
mode = 600
[webserver-launcher]
recipe = collective.recipe.template
input = inline:
#! ${param-common:path-shell}
exec ${param-common:path-nodejs} ${param-common:path-webserver} \
--port=${param-webserver:port} \
--remote-debugging-port=${param-webserver:remote-debugging-port} \
--target-url=${param-webserver:target-url} \
--chrome=${param-webserver:path-chrome}
output = ${param-common:path-webserver-launcher}
mode = 700
[webserver-install-dependencies]
recipe = plone.recipe.command
stop-on-error = true
command = ${param-common:path-npm} --prefix ${directory:webserver} install ${directory:webserver}
update-command = ${:command}
[promise-port-listening]
<= monitor-promise-base
promise = check_socket_listening
name = nginx-port-listening.py
config-host = ${param-nginx:ip}
config-port = ${param-nginx:port}
[publish-information]
recipe = slapos.cookbook:publish
access-url = https://${param-nginx:proxy-address}
target-pwa-url = {{ parameter_dict['target-pwa-path'] }}
publicity = {{ parameter_dict['publicity'] }}
server-side-rendering = {{ parameter_dict['server-side-rendering'] }}
persistence = {{ parameter_dict['persistence'] }}
replication-count = {{ parameter_dict['replication-count'] }}
nginx-proxy-port = {{ parameter_dict['nginx-proxy-port'] }}
monitor-httpd-port = {{ parameter_dict['monitor-httpd-port'] }}
remote-debugging-port = {{ parameter_dict['remote-debugging-port'] }}
[frontend-instance-certificate]
recipe = plone.recipe.command
command =
if [ ! -e ${:key-file} ]
then
openssl req -x509 -nodes -days 3650 \
-subj "/C=AA/ST=X/L=X/O=Dis/CN=${:common-name}" \
-newkey rsa:1024 -keyout ${:key-file} \
-out ${:cert-file}
openssl x509 -addtrust serverAuth \
-in ${:cert-file} \
-out ${:cert-file}
fi
update-command = ${:command}
key-file = ${directory:ssl}/${:_buildout_section_name_}.key
cert-file = ${directory:ssl}/${:_buildout_section_name_}.cert
common-name = ${param-nginx:ip}
environment =
PATH={{ parameter_list['path-openssl'] }}/bin:%(PATH)s
[buildout]
parts =
switch-softwaretype
eggs-directory = {{ buildout['eggs-directory'] }}
develop-eggs-directory = {{ buildout['develop-eggs-directory'] }}
offline = true
[slap-configuration]
recipe = slapos.cookbook:slapconfiguration
computer = ${slap-connection:computer-id}
partition = ${slap-connection:partition-id}
url = ${slap-connection:server-url}
key = ${slap-connection:key-file}
cert = ${slap-connection:cert-file}
[profile-common]
partition-ipv4 = ${slap-configuration:ipv4-random}
partition-ipv6 = ${slap-configuration:ipv6-random}
path-openssl = {{ path_openssl }}
path-nodejs = {{ path_nodejs }}
path-nginx = {{ path_nginx }}
path-dash = {{ path_dash }}
path-chrome = {{ path_chrome }}
template-monitor = {{ template_monitor }}
template-nginx-conf = {{ template_nginx_conf }}
template-mime-types = {{ template_mime_types }}
template-webserver = {{ template_webserver }}
template-webserver-package = {{ template_package_json }}
[instance-base]
recipe = slapos.recipe.template:jinja2
rendered = ${buildout:directory}/${:filename}
context =
section buildout buildout
section parameter_list profile-common
key slapparameter_dict slap-configuration:configuration
jsonkey default_parameter_dict :default-parameters
default-parameters =
{
"target-pwa-path": "",
"publicity": "public",
"server-side-rendering": "none",
"persistence": "none",
"replication-count": 1,
"nginx-proxy-port": 8081,
"monitor-httpd-port": 8082,
"remote-debugging-port": 8083,
"webserver-proxy-port": 8084
}
[instance-pws]
<= instance-base
template = {{ template_instance_pws }}
filename = instance-pws.cfg
[switch-softwaretype]
recipe = slapos.cookbook:switch-softwaretype
RootSoftwareInstance = ${:default}
default = :instance-pws
instance-pws = instance-pws:rendered
[buildout]
extends =
buildout.hash.cfg
../../../stack/slapos.cfg
../../../stack/monitor/buildout.cfg
../../../stack/nodejs.cfg
../../../component/headless-chromium/buildout.cfg
../../../component/openssl/buildout.cfg
../../../component/nginx/buildout.cfg
../../../component/dash/buildout.cfg
parts =
slapos-cookbook
template-cfg
[nodejs]
<= nodejs-14.16.0
[template-cfg]
recipe = slapos.recipe.template:jinja2
rendered = ${buildout:directory}/template.cfg
template = ${:_profile_base_location_}/${:filename}
mode = 0644
context =
section buildout buildout
key path_openssl openssl:location
key path_nodejs nodejs:location
key path_nginx nginx:location
key path_dash dash:location
key path_chrome headless-chromium-wrapper:rendered
key template_monitor monitor2-template:rendered
key template_nginx_conf template-nginx-conf:target
key template_mime_types template-mime-types:target
key template_webserver template-webserver:target
key template_package_json template-package-json:target
key template_instance_pws template-instance-pws:target
[download-base]
recipe = slapos.recipe.build:download
url = ${:_profile_base_location_}/${:_update_hash_filename_}
[template-nginx-conf]
<= download-base
[template-mime-types]
<= download-base
[template-webserver]
<= download-base
[template-package-json]
<= download-base
[template-instance-pws]
<= download-base
types {
text/html html htm shtml;
text/css css;
text/xml xml rss;
image/gif gif;
image/jpeg jpeg jpg;
application/x-javascript js;
text/plain txt;
text/x-component htc;
text/mathml mml;
image/png png;
image/x-icon ico;
image/x-jng jng;
image/vnd.wap.wbmp wbmp;
application/java-archive jar war ear;
application/mac-binhex40 hqx;
application/pdf pdf;
application/x-cocoa cco;
application/x-java-archive-diff jardiff;
application/x-java-jnlp-file jnlp;
application/x-makeself run;
application/x-perl pl pm;
application/x-pilot prc pdb;
application/x-rar-compressed rar;
application/x-redhat-package-manager rpm;
application/x-sea sea;
application/x-shockwave-flash swf;
application/x-stuffit sit;
application/x-tcl tcl tk;
application/x-x509-ca-cert der pem crt;
application/x-xpinstall xpi;
application/zip zip;
application/octet-stream deb;
application/octet-stream bin exe dll;
application/octet-stream dmg;
application/octet-stream eot;
application/octet-stream iso img;
application/octet-stream msi msp msm;
audio/mpeg mp3;
audio/x-realaudio ra;
video/mpeg mpeg mpg;
video/quicktime mov;
video/x-flv flv;
video/x-msvideo avi;
video/x-ms-wmv wmv;
video/x-ms-asf asx asf;
video/x-mng mng;
}
pid {{ param_nginx['pid'] }};
error_log {{ param_nginx['error-log'] }};
events {
worker_connections {{ param_nginx['worker-connections'] }};
}
http {
access_log {{ param_nginx['access-log'] }};
include {{ param_nginx['mime-types'] }};
default_type application/octet-stream;
index index.html;
server {
listen {{ param_nginx['proxy-address'] }} ssl;
server_name {{ param_nginx['server-name'] }};
keepalive_timeout 5;
ssl on;
ssl_certificate {{ param_nginx['nginx-cert-file'] }};
ssl_certificate_key {{ param_nginx['nginx-key-file'] }};
location / {
proxy_pass http://localhost:{{ param_nginx['webserver-port'] }};
}
client_body_temp_path {{ param_nginx['client_body_temp_path'] }};
proxy_temp_path {{ param_nginx['proxy_temp_path'] }};
fastcgi_temp_path {{ param_nginx['fastcgi_temp_path'] }};
uwsgi_temp_path {{ param_nginx['uwsgi_temp_path'] }};
scgi_temp_path {{ param_nginx['scgi_temp_path'] }};
}
}
{
"name": "ProgressiveWebserver",
"version": "22.05.00",
"description": "Opens a chromium to prerender a given url.",
"license": "GPL-3.0-or-later",
"private": true,
"main": "webserver.js",
"dependencies": {
"chrome-remote-interface": "^0.31.2",
"minimist": "^1.2.6"
}
}
const http = require('http')
const {spawn} = require('child_process')
const cdp = require('chrome-remote-interface')
const argv = require('minimist')(process.argv.slice(2))
function isChromiumOpen(port) {
return new Promise((resolve) => {
const options = {
method: 'GET',
host: 'localhost',
path: '/json/version',
port: port
}
const request = http.request(options, (response) => {
const status = response.statusCode
resolve(status < 400 || status >= 500)
})
request.on('error', (err) => { resolve(false) })
request.end()
})
}
async function startChromium(chromium, port) {
console.log(chromium)
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms))
const instance = spawn(chromium, ['--headless', `--remote-debugging-port=${port}`])
while(true) {
if (await isChromiumOpen(port)) {
return instance
}
console.log(argv.chrome)
await delay(10)
}
}
async function stopChromium(instance) {
instance.stdin.pause()
instance.kill()
}
async function renderOnServer(url) {
let client
try {
client = await cdp({port: argv['remote-debugging-port']})
const {DOM, Network, Page} = client
await Network.enable()
await Page.enable()
await Page.navigate({url: url})
await Page.loadEventFired()
node = await DOM.getDocument()
html = await DOM.getOuterHTML({backendNodeId: node.root.backendNodeId})
return html.outerHTML
} catch (error) {
console.error(error)
} finally {
if (client) {
await client.close()
}
}
}
async function requestListener(request, response) {
const instance = await startChromium(argv['chrome'], argv['remote-debugging-port'])
let website
try {
website_html = await renderOnServer(argv['target-url'])
} catch (error) {
console.log(error)
} finally {
await stopChromium(instance)
}
response.writeHead(200)
response.end(website_html)
}
const server = http.createServer(requestListener)
server.listen(argv.port, 'localhost')
console.log(`Webserver listening at port ${argv.port}`)
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment