1
0
mirror of https://github.com/Jermolene/TiddlyWiki5 synced 2025-11-21 17:54:51 +00:00

Module-ize server routes and add static file support (#2510)

* Refactor server routes to modules

New module type: serverroute

Caveats: Loading order is not deterministic but this would only matter
if two route modules attempted to use the same path regexp (that would
be silly).

* Add static assets plugin

This plugin allows the node server to fetch static assets in the /assets
directory. I felt that this was a feature that goes above the core
functionality. That is why I added it as a plugin. with the modular
route extensions this was a breeze.

* Add serverroute description to ModuleTypes
This commit is contained in:
Devin Weaver
2016-08-08 09:11:47 -04:00
committed by Jeremy Ruston
parent 1da8a32837
commit 1ae3927c28
12 changed files with 289 additions and 121 deletions

View File

@@ -9,6 +9,7 @@ isfilteroperator: Operands for the ''is'' filter operator.
macro: JavaScript macro definitions.
parser: Parsers for different content types.
saver: Savers handle different methods for saving files from the browser.
serverroute: Server routes handle different HTTP requests when using the `--server` command.
startup: Startup functions.
storyview: Story views customise the animation and behaviour of list widgets.
tiddlerdeserializer: Converts different content types into tiddlers.

View File

@@ -145,6 +145,7 @@ SimpleServer.prototype.listen = function(port,host) {
};
var Command = function(params,commander,callback) {
var _this = this;
this.params = params;
this.commander = commander;
this.callback = callback;
@@ -153,127 +154,9 @@ var Command = function(params,commander,callback) {
wiki: this.commander.wiki
});
// Add route handlers
this.server.addRoute({
method: "PUT",
path: /^\/recipes\/default\/tiddlers\/(.+)$/,
handler: function(request,response,state) {
var title = decodeURIComponent(state.params[0]),
fields = JSON.parse(state.data);
// Pull up any subfields in the `fields` object
if(fields.fields) {
$tw.utils.each(fields.fields,function(field,name) {
fields[name] = field;
});
delete fields.fields;
}
// Remove any revision field
if(fields.revision) {
delete fields.revision;
}
state.wiki.addTiddler(new $tw.Tiddler(state.wiki.getCreationFields(),fields,{title: title},state.wiki.getModificationFields()));
var changeCount = state.wiki.getChangeCount(title).toString();
response.writeHead(204, "OK",{
Etag: "\"default/" + encodeURIComponent(title) + "/" + changeCount + ":\"",
"Content-Type": "text/plain"
});
response.end();
}
});
this.server.addRoute({
method: "DELETE",
path: /^\/bags\/default\/tiddlers\/(.+)$/,
handler: function(request,response,state) {
var title = decodeURIComponent(state.params[0]);
state.wiki.deleteTiddler(title);
response.writeHead(204, "OK", {
"Content-Type": "text/plain"
});
response.end();
}
});
this.server.addRoute({
method: "GET",
path: /^\/$/,
handler: function(request,response,state) {
response.writeHead(200, {"Content-Type": state.server.get("serveType")});
var text = state.wiki.renderTiddler(state.server.get("renderType"),state.server.get("rootTiddler"));
response.end(text,"utf8");
}
});
this.server.addRoute({
method: "GET",
path: /^\/status$/,
handler: function(request,response,state) {
response.writeHead(200, {"Content-Type": "application/json"});
var text = JSON.stringify({
username: state.server.get("username"),
space: {
recipe: "default"
},
tiddlywiki_version: $tw.version
});
response.end(text,"utf8");
}
});
this.server.addRoute({
method: "GET",
path: /^\/favicon.ico$/,
handler: function(request,response,state) {
response.writeHead(200, {"Content-Type": "image/x-icon"});
var buffer = state.wiki.getTiddlerText("$:/favicon.ico","");
response.end(buffer,"base64");
}
});
this.server.addRoute({
method: "GET",
path: /^\/recipes\/default\/tiddlers.json$/,
handler: function(request,response,state) {
response.writeHead(200, {"Content-Type": "application/json"});
var tiddlers = [];
state.wiki.forEachTiddler({sortField: "title"},function(title,tiddler) {
var tiddlerFields = {};
$tw.utils.each(tiddler.fields,function(field,name) {
if(name !== "text") {
tiddlerFields[name] = tiddler.getFieldString(name);
}
});
tiddlerFields.revision = state.wiki.getChangeCount(title);
tiddlerFields.type = tiddlerFields.type || "text/vnd.tiddlywiki";
tiddlers.push(tiddlerFields);
});
var text = JSON.stringify(tiddlers);
response.end(text,"utf8");
}
});
this.server.addRoute({
method: "GET",
path: /^\/recipes\/default\/tiddlers\/(.+)$/,
handler: function(request,response,state) {
var title = decodeURIComponent(state.params[0]),
tiddler = state.wiki.getTiddler(title),
tiddlerFields = {},
knownFields = [
"bag", "created", "creator", "modified", "modifier", "permissions", "recipe", "revision", "tags", "text", "title", "type", "uri"
];
if(tiddler) {
$tw.utils.each(tiddler.fields,function(field,name) {
var value = tiddler.getFieldString(name);
if(knownFields.indexOf(name) !== -1) {
tiddlerFields[name] = value;
} else {
tiddlerFields.fields = tiddlerFields.fields || {};
tiddlerFields.fields[name] = value;
}
});
tiddlerFields.revision = state.wiki.getChangeCount(title);
tiddlerFields.type = tiddlerFields.type || "text/vnd.tiddlywiki";
response.writeHead(200, {"Content-Type": "application/json"});
response.end(JSON.stringify(tiddlerFields),"utf8");
} else {
response.writeHead(404);
response.end();
}
}
$tw.modules.forEachModuleOfType("serverroute", function(title,routeDefinition) {
// console.log("Loading server route " + title);
_this.server.addRoute(routeDefinition);
});
};

View File

@@ -0,0 +1,23 @@
/*\
title: $:/core/modules/serverroute/delete-tiddlers.js
type: application/javascript
module-type: serverroute
DELETE /recipes/default/tiddlers/:title
\*/
(function() {
module.exports = {
method: "DELETE",
path: /^\/bags\/default\/tiddlers\/(.+)$/,
handler: function(request,response,state) {
var title = decodeURIComponent(state.params[0]);
state.wiki.deleteTiddler(title);
response.writeHead(204, "OK", {
"Content-Type": "text/plain"
});
response.end();
}
};
}());

View File

@@ -0,0 +1,20 @@
/*\
title: $:/core/modules/serverroute/get-favicon.js
type: application/javascript
module-type: serverroute
GET /favicon.ico
\*/
(function() {
module.exports = {
method: "GET",
path: /^\/favicon.ico$/,
handler: function(request,response,state) {
response.writeHead(200, {"Content-Type": "image/x-icon"});
var buffer = state.wiki.getTiddlerText("$:/favicon.ico","");
response.end(buffer,"base64");
}
};
}());

View File

@@ -0,0 +1,20 @@
/*\
title: $:/core/modules/serverroute/get-index.js
type: application/javascript
module-type: serverroute
GET /
\*/
(function() {
module.exports = {
method: "GET",
path: /^\/$/,
handler: function(request,response,state) {
response.writeHead(200, {"Content-Type": state.server.get("serveType")});
var text = state.wiki.renderTiddler(state.server.get("renderType"),state.server.get("rootTiddler"));
response.end(text,"utf8");
}
};
}());

View File

@@ -0,0 +1,26 @@
/*\
title: $:/core/modules/serverroute/get-status.js
type: application/javascript
module-type: serverroute
GET /status
\*/
(function() {
module.exports = {
method: "GET",
path: /^\/status$/,
handler: function(request,response,state) {
response.writeHead(200, {"Content-Type": "application/json"});
var text = JSON.stringify({
username: state.server.get("username"),
space: {
recipe: "default"
},
tiddlywiki_version: $tw.version
});
response.end(text,"utf8");
}
};
}());

View File

@@ -0,0 +1,41 @@
/*\
title: $:/core/modules/serverroute/get-tiddler.js
type: application/javascript
module-type: serverroute
GET /recipes/default/tiddlers/:title
\*/
(function() {
module.exports = {
method: "GET",
path: /^\/recipes\/default\/tiddlers\/(.+)$/,
handler: function(request,response,state) {
var title = decodeURIComponent(state.params[0]),
tiddler = state.wiki.getTiddler(title),
tiddlerFields = {},
knownFields = [
"bag", "created", "creator", "modified", "modifier", "permissions", "recipe", "revision", "tags", "text", "title", "type", "uri"
];
if(tiddler) {
$tw.utils.each(tiddler.fields,function(field,name) {
var value = tiddler.getFieldString(name);
if(knownFields.indexOf(name) !== -1) {
tiddlerFields[name] = value;
} else {
tiddlerFields.fields = tiddlerFields.fields || {};
tiddlerFields.fields[name] = value;
}
});
tiddlerFields.revision = state.wiki.getChangeCount(title);
tiddlerFields.type = tiddlerFields.type || "text/vnd.tiddlywiki";
response.writeHead(200, {"Content-Type": "application/json"});
response.end(JSON.stringify(tiddlerFields),"utf8");
} else {
response.writeHead(404);
response.end();
}
}
};
}());

View File

@@ -0,0 +1,32 @@
/*\
title: $:/core/modules/serverroute/get-tiddlers-json.js
type: application/javascript
module-type: serverroute
GET /recipes/default/tiddlers/tiddlers.json
\*/
(function() {
module.exports = {
method: "GET",
path: /^\/recipes\/default\/tiddlers.json$/,
handler: function(request,response,state) {
response.writeHead(200, {"Content-Type": "application/json"});
var tiddlers = [];
state.wiki.forEachTiddler({sortField: "title"},function(title,tiddler) {
var tiddlerFields = {};
$tw.utils.each(tiddler.fields,function(field,name) {
if(name !== "text") {
tiddlerFields[name] = tiddler.getFieldString(name);
}
});
tiddlerFields.revision = state.wiki.getChangeCount(title);
tiddlerFields.type = tiddlerFields.type || "text/vnd.tiddlywiki";
tiddlers.push(tiddlerFields);
});
var text = JSON.stringify(tiddlers);
response.end(text,"utf8");
}
};
}());

View File

@@ -0,0 +1,37 @@
/*\
title: $:/core/modules/serverroute/put-tiddler.js
type: application/javascript
module-type: serverroute
PUT /recipes/default/tiddlers/:title
\*/
(function() {
module.exports = {
method: "PUT",
path: /^\/recipes\/default\/tiddlers\/(.+)$/,
handler: function(request,response,state) {
var title = decodeURIComponent(state.params[0]),
fields = JSON.parse(state.data);
// Pull up any subfields in the `fields` object
if(fields.fields) {
$tw.utils.each(fields.fields,function(field,name) {
fields[name] = field;
});
delete fields.fields;
}
// Remove any revision field
if(fields.revision) {
delete fields.revision;
}
state.wiki.addTiddler(new $tw.Tiddler(state.wiki.getCreationFields(),fields,{title: title},state.wiki.getModificationFields()));
var changeCount = state.wiki.getChangeCount(title).toString();
response.writeHead(204, "OK",{
Etag: "\"default/" + encodeURIComponent(title) + "/" + changeCount + ":\"",
"Content-Type": "text/plain"
});
response.end();
}
};
}());

View File

@@ -0,0 +1,60 @@
/*\
title: $:/plugins/tiddlywiki/server-static-assets/get-server-asset.js
type: application/javascript
module-type: serverroute
GET /assets/:server_asset
\*/
/*jshint maxlen:false */
(function() {
if(!$tw.node) { return; }
var path = require("path");
var fs = require("fs");
var util = require("util");
var RESPONSES = {
_ok: function(filename,content) {
var extension = path.extname(filename);
return {
status: 200,
content: content,
type: $tw.config.fileExtensionInfo[extension] || "application/octet-stream"
};
},
_error: function(filename,err) {
return {status: 500, content: err.toString(), type: "text/plain"};
},
ENOENT: function(filename) {
return {
status: 404,
content: "File '" + filename + "' not found.",
type: "text/plain"
};
},
EACCES: function(filename) {
return {
status: 403,
content: "File '" + filename + "' is forbidden (permissions).",
type: "text/plain"
};
}
};
module.exports = {
method: "GET",
path: /^\/assets\/(.+)$/,
handler: function(request,response,state) {
var filename = path.join($tw.boot.wikiPath, "assets", state.params[0]);
var extension = path.extname(filename);
fs.readFile(filename,function(err,content) {
var contentInfo = err ? RESPONSES[err.code] || RESPONSES._error : RESPONSES._ok;
var responseData = contentInfo(filename, err || content);
response.writeHead(responseData.status, {"Content-Type": responseData.type});
response.end(responseData.content);
});
}
};
}());

View File

@@ -0,0 +1,7 @@
{
"title": "$:/plugins/tiddlywiki/server-static-assets",
"description": "Static Server Assets",
"author": "Sukima",
"core-version": ">=5.0.0",
"list": "readme"
}

View File

@@ -0,0 +1,18 @@
title: $:/plugins/tiddlywiki/server-static-assets/readme
This plugin allows the Node.JS server to serve static files from the filesystem.
Any files that are placed in `assets` can be served to the browser directly.
For example, if you had a wiki editions with the following directory structure:
```
editions/tw5.com-server
├── assets
│   └── pony.png
├── tiddlers
│   └── (All the tiddler files)
└── tiddlywiki.info
```
And the command `tiddlywiki editions/tw5.com-server --server` then pointing your browser to `http://localhost:8080/assets/pony.png` would display the image.