Verified Commit efbc274e authored by Aral Balkan's avatar Aral Balkan
Browse files

Implement feature: dynamic routes (closes #20)

parents cc6042e3 a9872578
......@@ -8,6 +8,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
- Nothing yet.
## [10.2.0] - 2019-05-15
## Added
- Dynamic routes feature. You can now specify simple dynamic routes by including middleware functions in JS files within a folder named _.dynamic_ in your web folder.
## [10.1.0] - 2019-05-14
### Added
......
......@@ -328,6 +328,55 @@ If you do not create custom error pages, the built-in default error pages will b
When creating your own servers (see [API](#API)), you can generate the default error pages programmatically using the static methods `WebServer.default404ErrorPage()` and `WebServer.default500ErrorPage()`, passing in the missing path and the error message as the argument, respectively to get the HTML string of the error page returned.
## Dynamic routes
You can include very basic dynamic routes by including JavaScript files that export middleware-style functions in a special _.dynamic_ folder in the root folder of your web content. The syntax and conventions are [detailed here](https://source.ind.ie/hypha/tools/web-routes-from-files).
So, for example, if you wanted to have a dynamic route that showed the server CPU load and free memory, you could create a file called _.dynamic/server-stats.js_ in your web folder with the following content:
```js
const os = require('os')
function serverStats (request, response, next) {
const loadAverages = `<p> ${os.loadavg().reduce((a, c, i) => `${a}\n<li><strong>CPU ${i+1}:</strong> ${c}</li>`, '<ul>') + '</ul>'}</p>`
const freeMemory = `<p>${os.freemem()} bytes</p>`
const page = `<html><head><title>Server statistics</title><style>body {font-family: sans-serif;}</style></head><body><h1>Server statistics</h1><h2>Load averages</h2>${loadAverages}<h2>Free memory</h2>${freeMemory}</body></html>`
response.end(page)
}
module.exports = serverStats
```
Indie Web Server will load your dynamic route at startup and you can test it by hitting _https://localhost/server-stats_ using a local web server. Each time you refresh, you should get the latest dynamic content.
If you need to use custom Node modules, initialise your _.dynamic_ folder using `npm init` and use `npm install` as usual. And modules you require from your routes will be properly loaded and used.
### Directories
Your dynamic web routes are running within Indie Web Server, which is a Node application compiled into a native binary.
- `os.homedir()`: __(writable)__ This is the home folder of the account running Indie Web Server. You can write to it to store persistent objects (e.g., save data).
- `os.tmpdir()`: __(writable)__ Path to the system temporary folder. Use for content you can afford to lose and can recreate (e.g., cache API calls).
- `.`: __(writable)__ Path to the root of your web content. Since you can write here, you can, if you want to, create content dynamically that will then automatically be served by the static web server.
- `__dirname`: __(writeable) Path to the `.routes` folder.
- `/`: __(read-only)__ Path to the `/usr` folder (Indie Web Server is installed in `/usr/local/web-server`). You should not have any reason to use this.
### Security
The code within your JavaScript routes is executed on the server. Exercise the same caution as you would when creating any Node.js app (sanitise input, etc.)
### Intended usage
You shouldn’t use this functionality to create your latest amazing web app. For that, include Indie Web Server as a node module in your project and extend it that way. This is to add tiny bits of dynamic functionality. There is currently only support for `get` routes. Again, if you need custom modules, extend Indie Web Server using Node.js.
## API
Indie Web Server’s `createServer` method behaves like the built-in _https_ module’s `createServer` function. Anywhere you use `require('https').createServer`, you can simply replace it with `require('@ind.ie/web-server').createServer`.
......
......@@ -14,6 +14,7 @@ const Graceful = require('node-graceful')
const AcmeTLS = require('@ind.ie/acme-tls')
const nodecert = require('@ind.ie/nodecert')
const getRoutes = require('@ind.ie/web-routes-from-files')
const ensure = require('./bin/lib/ensure')
......@@ -207,6 +208,20 @@ class WebServer {
}
})
// Add dynamic routes, if any, if a <pathToServe>/.dynamic/ folder exists.
// If there are errors in any of your dynamic routes, you will get 500 (server) errors.
const dynamicRoutesDirectory = path.join(pathToServe, '.dynamic')
if (fs.existsSync(dynamicRoutesDirectory)) {
const dynamicRoutes = getRoutes(dynamicRoutesDirectory)
dynamicRoutes.forEach(route => {
console.log(` 🐁 Dynamic route loaded: ${route.path}`)
app.get(route.path, require(route.callback))
})
}
// Add static routes.
// (Note: directories that begin with a dot (hidden directories) will be ignored.)
app.use(express.static(pathToServe))
// Serve the archive cascade (if there is one).
......
This diff is collapsed.
{
"name": "@ind.ie/web-server",
"version": "10.1.0",
"version": "10.2.0",
"description": "A secure and seamless Small Tech personal web server.",
"main": "index.js",
"bin": "bin/web-server.js",
......@@ -25,17 +25,18 @@
"dependencies": {
"@ind.ie/acme-tls": "^2.1.2",
"@ind.ie/nodecert": "^3.0.1",
"@ind.ie/web-routes-from-files": "^1.1.1",
"ansi-escape-sequences": "^4.1.0",
"chokidar": "^2.1.5",
"copy-concurrently": "^1.0.5",
"debounce": "^1.2.0",
"express": "^4.16.4",
"helmet": "^3.16.0",
"helmet": "^3.18.0",
"http-proxy-middleware": "^0.19.1",
"minimist": "^1.2.0",
"morgan": "^1.9.1",
"node-graceful": "^1.0.1",
"prompts": "aral/prompts#custom-symbols-for-confirm",
"prompts": "github:aral/prompts#custom-symbols-for-confirm",
"recursive-copy": "^2.0.10",
"redirect-https": "^1.3.0",
"rsync": "^0.6.1",
......@@ -44,8 +45,8 @@
"tcp-port-used": "^1.0.1"
},
"devDependencies": {
"nexe": "^3.1.0",
"nyc": "^14.1.0",
"nexe": "^3.2.0",
"nyc": "^14.1.1",
"tap-spec": "^5.0.0",
"tape": "^4.10.1"
}
......
const getCows = require('cows')
const cows = getCows()
function paveTheCowPaths(request, response, next) {
const randomCowIndex = Math.round(Math.random()*cows.length)-1
const randomCow = cows[randomCowIndex]
response.end(`<pre>${randomCow}</pre>`)
}
module.exports = paveTheCowPaths
const md = require('markdown-it')()
function markdown (request, response, next) {
const html = md.render(`
# Markdown
- Is
- Lots
- Of
- Fun!
`)
response.end(html)
}
module.exports = markdown
const os = require('os')
function serverStats (request, response, next) {
const loadAverages = `<p> ${os.loadavg().reduce((a, c, i) => `${a}\n<li><strong>CPU ${i+1}:</strong> ${c}</li>`, '<ul>') + '</ul>'}</p>`
const freeMemory = `<p>${os.freemem()} bytes</p>`
const page = `<html><head><title>Server statistics</title><style>body {font-family: sans-serif;}</style></head><body><h1>Server statistics</h1><h2>Load averages</h2>${loadAverages}<h2>Free memory</h2>${freeMemory}</body></html>`
response.end(page)
}
module.exports = serverStats
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