UNPKG

mach

Version:
236 lines (203 loc) 7.41 kB
/* 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;