UNPKG

mach

Version:
232 lines (199 loc) 7.36 kB
"use strict"; /* jshint -W058 */ var d = require("describe-property"); var isBinary = require("bodec").isBinary; var decodeBase64 = require("./utils/decodeBase64"); var encodeBase64 = require("./utils/encodeBase64"); var stringifyQuery = require("./utils/stringifyQuery"); var Promise = require("./utils/Promise"); var Location = require("./Location"); var Message = require("./Message"); function locationPropertyAlias(name) { return d.gs(function () { return this.location[name]; }, function (value) { this.location[name] = value; }); } function defaultErrorHandler(error) { if (typeof console !== "undefined" && console.error) { console.error(error && error.stack || error); } else { throw error; // Don't silently swallow errors! } } function defaultCloseHandler() {} function defaultApp(conn) { conn.status = 404; conn.response.contentType = "text/plain"; conn.response.content = "Not found: " + conn.method + " " + conn.path; } /** * An HTTP connection that acts as the asynchronous primitive for * the duration of the request/response cycle. * * Important features are: * * - request A Message representing the request being made. In * a server environment, this is an "incoming" message * that was probably generated by a web browser or some * other consumer. In a client environment, this is an * "outgoing" message that we send to a remote server. * - response A Message representing the response to the request. * In a server environment, this is an "outgoing" message * that will be sent back to the client. In a client * environment, this is the response that was received * from the remote server. * - method The HTTP method that the request uses * - location The URL of the request. In a server environment, this * is derived from the URL path used in the request as * well as a combination of the Host, X-Forwarded-* and * other relevant headers. * - version The version of HTTP used in the request * - status The HTTP status code of the response * - statusText The HTTP status text that corresponds to the status * - responseText This is a special property that contains the entire * content of the response. It is present by default when * making client requests for convenience, but may also be * disabled when you need to stream the response. * * Options may be any of the following: * * - content The request content, defaults to "" * - headers The request headers, defaults to {} * - method The request HTTP method, defaults to "GET" * - location/url The request Location or URL * - params The request params * - onError A function that is called when there is an error * - onClose A function that is called when the request closes * * The options may also be a URL string to specify the URL. */ function Connection(options) { options = options || {}; var location; if (typeof options === "string") { location = options; // options may be a URL string. } else if (options.location || options.url) { location = options.location || options.url; } else if (typeof window === "object") { location = window.location.href; } this.location = location; this.version = options.version || "1.1"; this.method = options.method; this.onError = (options.onError || defaultErrorHandler).bind(this); this.onClose = (options.onClose || defaultCloseHandler).bind(this); this.request = new Message(options.content, options.headers); this.response = new Message(); // Params may be given as an object. if (options.params) { if (this.method === "GET" || this.method === "HEAD") { this.query = options.params; } else { this.request.contentType = "application/x-www-form-urlencoded"; this.request.content = stringifyQuery(options.params); } } this.withCredentials = options.withCredentials || false; this.remoteHost = options.remoteHost || null; this.remoteUser = options.remoteUser || null; this.basename = ""; this.responseText = null; this.status = 200; } Object.defineProperties(Connection.prototype, { /** * The method used in the request. */ method: d.gs(function () { return this._method; }, function (value) { this._method = typeof value === "string" ? value.toUpperCase() : "GET"; }), /** * The Location of the request. */ location: d.gs(function () { return this._location; }, function (value) { this._location = value instanceof Location ? value : new Location(value); }), href: locationPropertyAlias("href"), protocol: locationPropertyAlias("protocol"), host: locationPropertyAlias("host"), hostname: locationPropertyAlias("hostname"), port: locationPropertyAlias("port"), search: locationPropertyAlias("search"), queryString: locationPropertyAlias("queryString"), query: locationPropertyAlias("query"), /** * True if the request uses SSL, false otherwise. */ isSSL: d.gs(function () { return this.protocol === "https:"; }), /** * The username:password used in the request, an empty string * if no auth was provided. */ auth: d.gs(function () { var header = this.request.headers.Authorization; if (header) { var parts = header.split(" ", 2); var scheme = parts[0]; if (scheme.toLowerCase() === "basic") return decodeBase64(parts[1]); return header; } return this.location.auth; }, function (value) { var headers = this.request.headers; if (value && typeof value === "string") { headers.Authorization = "Basic " + encodeBase64(value); } else { delete headers.Authorization; } }), /** * The portion of the original URL path that is still relevant * for request processing. */ pathname: d.gs(function () { return this.location.pathname.replace(this.basename, "") || "/"; }, function (value) { this.location.pathname = this.basename + value; }), /** * The URL path with query string. */ path: d.gs(function () { return this.pathname + this.search; }, function (value) { this.location.path = this.basename + value; }), /** * Calls the given `app` with this connection as the only argument. * as the first argument and returns a promise for a Response. */ call: d(function (app) { app = app || defaultApp; var conn = this; try { return Promise.resolve(app(conn)).then(function (value) { if (value == null) return; if (typeof value === "number") { conn.status = value; } else if (typeof value === "string" || isBinary(value) || typeof value.pipe === "function") { conn.response.content = value; } else { if (value.headers != null) conn.response.headers = value.headers; if (value.content != null) conn.response.content = value.content; if (value.status != null) conn.status = value.status; } }); } catch (error) { return Promise.reject(error); } }) }); module.exports = Connection;