mach
Version:
HTTP for JavaScript
222 lines (192 loc) • 7.07 kB
JavaScript
;
var fs = require("fs");
var d = require("describe-property");
var objectAssign = require("object-assign");
var getMimeType = require("../utils/getMimeType");
var filterProperties = require("../utils/filterProperties");
var stringifyCookie = require("../utils/stringifyCookie");
var saveToDisk = require("../utils/saveToDisk");
module.exports = function (mach) {
mach.bind = require("../utils/bindApp");
mach.createConnection = require("../utils/createConnection");
mach.serve = require("../utils/serveApp");
Object.defineProperties(mach.Connection.prototype, {
/**
* True if the request uses XMLHttpRequest, false otherwise.
*/
isXHR: d.gs(function () {
return this.request.headers["X-Requested-With"] === "XMLHttpRequest";
}),
/**
* A high-level method that returns a promise for an object that is the
* union of parameters contained in the request body and query string.
*
* The paramTypes argument may be used to filter parameters. It functions
* like a whitelist of acceptable parameters and increases the security of
* your app by not returning any parameters that you do not specify.
*
* // This function parses a list of comma-separated values in
* // a request parameter into an array.
* function parseList(value) {
* return value.split(',');
* }
*
* function app(conn) {
* return conn.getParams({
* name: String,
* age: Number,
* hobbies: parseList
* }).then(function (params) {
* // params.name will be a string, params.age a number, and
* // params.hobbies an array if they were provided in the
* // request. params won't contain any other properties.
* });
* }
*
* Of course, paramTypes may be omitted entirely to get a hash of all parameters.
*
* The maxLength argument is passed directly to the request's parseContent method.
*
* var maxUploadLimit = Math.pow(2, 20); // 1 mb
*
* function app(conn) {
* return conn.getParams(maxUploadLimit).then(function (params) {
* // params is the union of query and request content params
* });
* }
*
* Note: Content parameters take precedence over query parameters with the same name.
*/
getParams: d(function (paramTypes, maxLength) {
if (typeof paramTypes !== "object") {
maxLength = paramTypes;
paramTypes = null;
}
var request = this.request;
var queryParams = objectAssign({}, this.query);
return request.parseContent(maxLength).then(function (contentParams) {
// Content params take precedence over query params.
var params = objectAssign(queryParams, contentParams);
return paramTypes ? filterProperties(params, paramTypes) : params;
});
}),
/**
* Redirects the client to the given location. If status is not
* given, it defaults to 302 Found.
*/
redirect: d(function (status, location) {
if (typeof status !== "number") {
location = status;
status = 302;
}
this.status = status;
this.response.headers.Location = location;
}),
/**
* Redirects the client back to the URL they just came from, or
* to the given location if it isn't known.
*/
back: d(function (location) {
this.redirect(this.request.headers.Referer || location || "/");
}),
/**
* A quick way to write the status and/or content to the response.
*
* Examples:
*
* conn.send(404);
* conn.send(404, 'Not Found');
* conn.send('Hello world');
* conn.send(fs.createReadStream('welcome.txt'));
*/
send: d(function (status, content) {
if (typeof status === "number") {
this.status = status;
} else {
content = status;
}
if (content != null) this.response.content = content;
}),
/**
* Sends the given text in a text/plain response.
*/
text: d(function (status, text) {
this.response.contentType = "text/plain";
this.send(status, text);
}),
/**
* Sends the given HTML in a text/html response.
*/
html: d(function (status, html) {
this.response.contentType = "text/html";
this.send(status, html);
}),
/**
* Sends the given JSON in an application/json response.
*/
json: d(function (status, json) {
this.response.contentType = "application/json";
if (typeof status === "number") {
this.status = status;
} else {
json = status;
}
if (json != null) this.response.content = typeof json === "string" ? json : JSON.stringify(json);
}),
/**
* Sends a file to the client with the given options. The following
* options are available:
*
* - content/path The raw file content as a string, Buffer, stream, or
* path to a file on disk
* - type The Content-Type of the file. Defaults to a guess based
* on the file extension when a file path is given
* - length/size The Content-Length of the file, if it's known. Defaults
* to the size of the file when a file path is given
*
* Examples:
*
* response.file('path/to/file.txt');
* response.file(200, 'path/to/file.txt');
*/
file: d(function (status, options) {
if (typeof status === "number") {
this.status = status;
} else {
options = status;
}
var response = this.response;
if (typeof options === "string") options = { path: options };
if (options.content) {
response.content = options.content;
} else if (typeof options.path === "string") {
response.content = fs.createReadStream(options.path);
} else {
throw new Error("Missing file content/path");
}
if (options.type || options.path) response.headers["Content-Type"] = options.type || getMimeType(options.path);
if (options.length || options.size) {
response.headers["Content-Length"] = options.length || options.size;
} else if (typeof options.path === "string") {
response.headers["Content-Length"] = fs.statSync(options.path).size;
}
})
});
mach.extend(require("./multipart"));
var _handlePart = mach.Message.prototype.handlePart;
Object.defineProperties(mach.Message.prototype, {
/**
* Sets a cookie with the given name and options.
*/
setCookie: d(function (name, options) {
this.addHeader("Set-Cookie", stringifyCookie(name, options));
}),
/**
* Override the multipart extension's Message#handlePart to enable
* streaming file uploads to disk when parsing multipart messages.
*/
handlePart: d(function (part) {
return part.filename ? saveToDisk(part, "MachUpload-") : _handlePart.apply(this, arguments);
})
});
};