* `app.get("^/static/.*", webframe.static("web", "/static"))` will serve all | * `app.get("^/static/.*", webframe.static("web", "/static"))` will serve all | ||||
files in the `web` directory under `/static`, so `/static/script.js` will | files in the `web` directory under `/static`, so `/static/script.js` will | ||||
serve `web/script.js`. | serve `web/script.js`. | ||||
### Transformations - app.transform(extension, mime, function) | |||||
Webframe has a way to transform files of one file type to an other, for example | |||||
transforming typescript into javascript, or sass into CSS. | |||||
Example (assumes you have the node-sass module installed): | |||||
``` | |||||
var sass = require("node-sass"); | |||||
app.transform("sass", "text/css", (path, writeStream) => { | |||||
try { | |||||
var str = fs.readFileSync(path, "utf8"); | |||||
} catch (err) { | |||||
ws.status = 404; | |||||
return ws.error(err); | |||||
} | |||||
try { | |||||
var result = sass.renderSync({ data: str }); | |||||
writeStream.end(result.css); | |||||
} catch (err) { | |||||
writeStream.error(err); | |||||
} | |||||
}); | |||||
``` | |||||
Now, any .sass file served with `webframe.static` will be compiled into CSS, | |||||
and sent with the browser with the MIME type "text/css". The result is also | |||||
cached, so the expensive sass compilation will only happen the first time the | |||||
file is requested. The result will not be cached if you call writeStream.error. |
this._routeMap = {}; | this._routeMap = {}; | ||||
this._routes = []; | this._routes = []; | ||||
this._transforms = {}; | |||||
// Add scripts | // Add scripts | ||||
if (options.client_utils) { | if (options.client_utils) { | ||||
* Args: | * Args: | ||||
* method: "GET", "POST", "PUT", "DELETE", "ALL" | * method: "GET", "POST", "PUT", "DELETE", "ALL" | ||||
* path: path, | * path: path, | ||||
* func: function | |||||
* func: function(request, response) | |||||
*/ | */ | ||||
route(method, path, middleware, func) { | route(method, path, middleware, func) { | ||||
if (method !== "GET" && method !== "POST" && method !== "PUT" && | if (method !== "GET" && method !== "POST" && method !== "PUT" && | ||||
} | } | ||||
} | } | ||||
/* | |||||
* Add a transform. | |||||
* Args: | |||||
* ext: What extension to transform | |||||
* mime: The mime-type the transformed code should be sent as | |||||
* func: function(path, writeStream) | |||||
* | |||||
* writeStream: { | |||||
* status: status code (should only be modified before write() or end()) | |||||
* headers: HTTP headers (should only be modified before write() or end()) | |||||
* write: function(data) - write data | |||||
* end: function(data) - write data and end response | |||||
* error: function(err) - end response and log error | |||||
* } | |||||
*/ | |||||
transform(ext, mime, func) { | |||||
if (typeof ext !== "string") | |||||
throw new Error("Expected ext to be string, got "+(typeof ext)+"."); | |||||
if (typeof func !== "function") | |||||
throw new Error("Expected func to be function, got "+(typeof func)+"."); | |||||
if (this._transforms[ext]) | |||||
throw new Error("Extension "+ext+" already has a transform."); | |||||
this._transforms[ext] = { | |||||
func: func, | |||||
mime: mime | |||||
}; | |||||
} | |||||
/* | /* | ||||
* Add code to be served as /webframe.js | * Add code to be served as /webframe.js | ||||
*/ | */ | ||||
*/ | */ | ||||
template(tmpl, args) { return template(tmpl, args); } | template(tmpl, args) { return template(tmpl, args); } | ||||
/* | |||||
* Utility methods for GET/POST/PUT/DELETE/ALL | |||||
*/ | |||||
get(path, middleware, func) { this.route("GET", path, middleware, func); } | get(path, middleware, func) { this.route("GET", path, middleware, func); } | ||||
post(path, middleware, func) { this.route("POST", path, middleware, func); } | post(path, middleware, func) { this.route("POST", path, middleware, func); } | ||||
put(path, middleware, func) { this.route("PUT", path, middleware, func); } | put(path, middleware, func) { this.route("PUT", path, middleware, func); } | ||||
delete(path, middleware, func) { this.route("DELETE", path, middleware, func); } | delete(path, middleware, func) { this.route("DELETE", path, middleware, func); } | ||||
all(path, middleware, func) { this.route("ALL", path, middleware, func); } | all(path, middleware, func) { this.route("ALL", path, middleware, func); } | ||||
/* | |||||
* Logging | |||||
*/ | |||||
info(str) { | info(str) { | ||||
console.log("Info: "+str); | console.log("Info: "+str); | ||||
} | } |
}); | }); | ||||
} | } | ||||
var transformCache = {}; | |||||
function handleTransform(path, req, res, app) { | |||||
var ext = pathlib.extname(req.url).substring(1); | |||||
if (ext === "") | |||||
return false; | |||||
var transform = app._transforms[ext]; | |||||
if (!transform) | |||||
return false; | |||||
if (transformCache[path]) { | |||||
var c = transformCache[path]; | |||||
res.writeHead(c.status, c.headers); | |||||
res.end(c.content); | |||||
return true; | |||||
} | |||||
var data = { content: "", headers: null, status: null }; | |||||
var writeStream = { | |||||
headersSent: false, | |||||
status: 200, | |||||
headers: { | |||||
"content-type": transform.mime | |||||
}, | |||||
write: function(d) { | |||||
if (!writeStream.headersSent) { | |||||
res.writeHead(writeStream.status, writeStream.headers); | |||||
writeStream.headersSent = true; | |||||
} | |||||
if (d != null) { | |||||
data.content += d; | |||||
res.write(d); | |||||
} | |||||
}, | |||||
end: function(d, nocache) { | |||||
writeStream.write(d); | |||||
res.end(); | |||||
if (!nocache) { | |||||
data.status = writeStream.status; | |||||
data.headers = writeStream.headers; | |||||
transformCache[path] = data; | |||||
} | |||||
}, | |||||
error: function(err) { | |||||
if (writeStream.status === 200) | |||||
writeStream.status = 500; | |||||
app.warning("Transform for "+path+": "+err.toString()); | |||||
writeStream.end(null, true); | |||||
} | |||||
} | |||||
transform.func(path, writeStream); | |||||
return true; | |||||
} | |||||
module.exports = function(root, before) { | module.exports = function(root, before) { | ||||
return function(req, res, app) { | return function(req, res, app) { | ||||
// `req.urlobj.pathname` is too long. | |||||
var pn = req.urlobj.pathname; | var pn = req.urlobj.pathname; | ||||
// Join the web root with the request's path name | |||||
var path = pathlib.join(root, pn.replace(before, "")); | |||||
// If there's a transform, let that handle it | |||||
if (handleTransform(path, req, res, app)) | |||||
return; | |||||
// Send a file | // Send a file | ||||
function send(path) { | function send(path) { | ||||
sendfile(path, app, pn, req, res); | sendfile(path, app, pn, req, res); | ||||
return; | return; | ||||
} | } | ||||
// Join the web root with the request's path name | |||||
var path = pathlib.join(root, pn.replace(before, "")); | |||||
fs.stat(path, (err, stat) => { | fs.stat(path, (err, stat) => { | ||||
// If there's an error stat'ing, just error | // If there's an error stat'ing, just error |