jii
Version:
Jii - Full-Stack JavaScript Framework
473 lines (414 loc) • 15.6 kB
JavaScript
/**
* @author <a href="http://www.affka.ru">Vladimir Kozhin</a>
* @license MIT
*/
'use strict';
const Jii = require('../../BaseJii');
const Url = require('../../helpers/Url');
const InvalidParamException = require('../../exceptions/InvalidParamException');
const HeaderCollection = require('../../request/HeaderCollection');
const InvalidConfigException = require('../../exceptions/InvalidConfigException');
const _indexOf = require('lodash/indexOf');
const _isObject = require('lodash/isObject');
const _isString = require('lodash/isString');
const _each = require('lodash/each');
const _has = require('lodash/has');
const BaseResponse = require('../../base/Response');
class Response extends BaseResponse {
preInit(nativeResponse) {
/**
* @var {object|null}
*/
this._cookies = null;
/**
* @var {object}
*/
this._headers = null;
/**
* @var {number} the HTTP status code to send with the response.
*/
this._statusCode = 200;
/**
* @var {string} the version of the HTTP protocol to use
*/
this.version = null;
/**
* @var {string} the HTTP status description that comes together with the status code.
* @see httpStatuses
*/
this.statusText = 'OK';
/**
* @var {string} the charset of the text response. If not set, it will use
* the value of [[Application::charset]].
*/
this.charset = null;
/**
* @var {string} the response content. When [[data]] is not null, it will be converted into [[content]]
* according to [[format]] when the response is being sent out.
* @see data
*/
this.content = null;
/**
* @var {array} the formatters for converting data into the response content of the specified [[format]].
* The array keys are the format names, and the array values are the corresponding configurations
* for creating the formatter objects.
* @see format
*/
this.formatters = null;
/**
* @var string the response format. This determines how to convert [[data]] into [[content]]
* when the latter is not set. By default, the following formats are supported:
*
* - [[FORMAT_RAW]]: the data will be treated as the response content without any conversion.
* No extra HTTP header will be added.
* - [[FORMAT_HTML]]: the data will be treated as the response content without any conversion.
* The "Content-Type" header will set as "text/html" if it is not set previously.
* - [[FORMAT_JSON]]: the data will be converted into JSON format, and the "Content-Type"
* header will be set as "application/json".
* - [[FORMAT_JSONP]]: the data will be converted into JSONP format, and the "Content-Type"
* header will be set as "text/javascript". Note that in this case `$data` must be an array
* with "data" and "callback" elements. The former refers to the actual data to be sent,
* while the latter refers to the name of the JavaScript callback.
* - [[FORMAT_XML]]: the data will be converted into XML format. Please refer to [[XmlResponseFormatter]]
* for more details.
*
* You may customize the formatting process or support additional formats by configuring [[formatters]].
* @see formatters
*/
this.format = null;
this._nativeResponse = nativeResponse;
this.init();
}
init() {
// Set default format
this.format = Response.FORMAT_HTML;
// Detect http version (1.0 or 1.1)
this.version = this._nativeResponse.req.httpVersion;
// Set default charset
if (this.charset === null) {
this.charset = Jii.app.charset;
}
}
/**
* @return {number} the HTTP status code to send with the response.
*/
getStatusCode() {
return this._statusCode;
}
/**
* Sets the response status code.
* This method will set the corresponding status text if `text` is null.
* @param {number} [value] the status code
* @param {string} [text] the status text. If not set, it will be set automatically based on the status code.
* @throws {InvalidParamException} if the status code is invalid.
*/
setStatusCode(value, text) {
value = value || 200;
text = text || null;
this._statusCode = parseInt(value);
if (this.isInvalid()) {
throw new InvalidParamException();
}
this.statusText = text || Response.httpStatuses[this._statusCode] || '';
}
/**
* Returns the header collection.
* The header collection contains the currently registered HTTP headers.
* @return {HeaderCollection} the header collection
*/
getHeaders() {
if (this._headers === null) {
this._headers = new HeaderCollection();
}
return this._headers;
}
/**
* Sends the response to the client.
*/
send() {
if (this.isSent) {
return;
}
//this.trigger(self::EVENT_BEFORE_SEND);
this._prepare();
//this.trigger(self::EVENT_AFTER_PREPARE);
this._sendHeaders();
this._sendContent();
//this.trigger(self::EVENT_AFTER_SEND);
this.isSent = true;
}
/**
* Clears the headers, cookies, content, status code of the response.
*/
clear() {
this._headers = null;
this._cookies = null;
this._statusCode = 200;
this.statusText = 'OK';
this.data = null;
this.content = null;
this.isSent = false;
}
/**
* Sends the response headers to the client
*/
_sendHeaders() {
this._nativeResponse.status(this.getStatusCode());
this._nativeResponse.set(this.getHeaders().toJSON());
this._sendCookies();
}
/**
* Sends the cookies to the client.
*/
_sendCookies() {
if (this._cookies === null) {
return;
}
// @todo Also need set age, path, ..
_each(this._cookies, (value, key) => {
});
}
/**
* Sends the response content to the client
*/
_sendContent() {
this._nativeResponse.send(this.content);
}
/**
* Sends a file to the browser.
*
* Note that this method only prepares the response for file sending. The file is not sent
* until [[send()]] is called explicitly or implicitly. The latter is done after you return from a controller action.
*
* @param {string} filePath the path of the file to be sent.
* @param {string} [attachmentName] the file name shown to the user. If null, it will be determined from `filePath`.
* @param {string} [mimeType] the MIME type of the content. If null, it will be guessed based on `filePath`
* @return static the response object itself
*/
sendFile(filePath, attachmentName, mimeType) {
attachmentName = attachmentName || null;
mimeType = mimeType || null;
// @todo
this._nativeResponse.sendFile(filePath);
}
/**
* Redirects the browser to the specified URL.
* @param {string|array|object} url the URL to be redirected to. This can be in one of the following formats:
*
* - a string representing a URL (e.g. "http://example.com")
* - a string representing a URL alias (e.g. "@example.com")
* - an array in the format of `[$route, ...name-value pairs...]` (e.g. `['site/index', 'ref' => 1]`).
* Note that the route is with respect to the whole application, instead of relative to a controller or module.
* [[Html::url()]] will be used to convert the array into a URL.
*
* Any relative URL will be converted into an absolute one by prepending it with the host info
* of the current request.
*
* @param {number} [statusCode] the HTTP status code. Defaults to 302.
* See <http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html>
* for details about HTTP status code
* @return static the response object itself
*/
redirect(url, statusCode) {
statusCode = statusCode || 302;
url = Url.to(url, this.owner, true);
// @todo
/*if (Yii::$app->getRequest()->getIsPjax()) {
this.getHeaders()->set('X-Pjax-Url', $url);
} elseif (Yii::$app->getRequest()->getIsAjax()) {
this.getHeaders()->set('X-Redirect', $url);
} else {*/
this.getHeaders().set('Location', url);
//}
this.setStatusCode(statusCode);
return null;
}
refresh() {
}
/**
* @return {boolean} whether this response has a valid [[statusCode]].
*/
isInvalid() {
return this.getStatusCode() < 100 || this.getStatusCode() >= 600;
}
/**
* @return {boolean} whether this response is informational
*/
isInformational() {
return this.getStatusCode() >= 100 && this.getStatusCode() < 200;
}
/**
* @return {boolean} whether this response is successful
*/
isSuccessful() {
return this.getStatusCode() >= 200 && this.getStatusCode() < 300;
}
/**
* @return {boolean} whether this response is a redirection
*/
isRedirection() {
return this.getStatusCode() >= 300 && this.getStatusCode() < 400;
}
/**
* @return {boolean} whether this response indicates a client error
*/
isClientError() {
return this.getStatusCode() >= 400 && this.getStatusCode() < 500;
}
/**
* @return {boolean} whether this response indicates a server error
*/
isServerError() {
return this.getStatusCode() >= 500 && this.getStatusCode() < 600;
}
/**
* @return {boolean} whether this response is OK
*/
isOk() {
return this.getStatusCode() == 200;
}
/**
* @return {boolean} whether this response indicates the current request is forbidden
*/
isForbidden() {
return this.getStatusCode() == 403;
}
/**
* @return {boolean} whether this response indicates the currently requested resource is not found
*/
isNotFound() {
return this.getStatusCode() == 404;
}
/**
* @return {boolean} whether this response is empty
*/
isEmpty() {
return _indexOf([
201,
204,
304
], this.getStatusCode()) !== -1;
}
/**
* Prepares for sending the response.
* The default implementation will convert [[data]] into [[content]] and set headers accordingly.
* @throws {InvalidConfigException} if the formatter for the specified format is invalid or [[format]] is not supported
*/
_prepare() {
if (this.data === null) {
return;
}
if (_has(this.formatters, this.format)) {
var formatter = this.formatters[this.format];
// Lazy create instance
// @todo
} else {
switch (this.format) {
case Response.FORMAT_HTML:
this.getHeaders().setDefault('Content-Type', 'text/html; charset=' + this.charset);
this.content = this.data;
break;
case Response.FORMAT_RAW:
this.content = this.data;
break;
case Response.FORMAT_JSON:
this.getHeaders().set('Content-Type', 'application/json; charset=UTF-8');
this.content = JSON.stringify(this.data);
break;
case Response.FORMAT_JSONP:
this.getHeaders().set('Content-Type', 'text/javascript; charset=' + this.charset);
if (_isObject(this.data) && _has(this.data, 'data') && _has(this.data, 'callback')) {
this.content = this.data.callback + '(' + JSON.stringify(this.data.data) + ');';
} else {
this.content = ''; //Yii::warning("The 'jsonp' response requires that the data be an array consisting of both 'data' and 'callback' elements.", __METHOD__);
}
break;
case Response.FORMAT_XML:
// @todo
//Yii::createObject(XmlResponseFormatter::className())->format($this);
break;
default:
throw new InvalidConfigException('Unsupported response format: ' + this.format);
}
}
if (!_isString(this.content)) {
throw new InvalidParamException('Response content must be a string.');
}
if (_isObject(this.content)) {
this.content = this.content.toString();
}
}
}
Response.httpStatuses = {
100: 'Continue',
101: 'Switching Protocols',
102: 'Processing',
118: 'Connection timed out',
200: 'OK',
201: 'Created',
202: 'Accepted',
203: 'Non-Authoritative',
204: 'No Content',
205: 'Reset Content',
206: 'Partial Content',
207: 'Multi-Status',
208: 'Already Reported',
210: 'Content Different',
226: 'IM Used',
300: 'Multiple Choices',
301: 'Moved Permanently',
302: 'Found',
303: 'See Other',
304: 'Not Modified',
305: 'Use Proxy',
306: 'Reserved',
307: 'Temporary Redirect',
308: 'Permanent Redirect',
310: 'Too many Redirect',
400: 'Bad Request',
401: 'Unauthorized',
402: 'Payment Required',
403: 'Forbidden',
404: 'Not Found',
405: 'Method Not Allowed',
406: 'Not Acceptable',
407: 'Proxy Authentication Required',
408: 'Request Time-out',
409: 'Conflict',
410: 'Gone',
411: 'Length Required',
412: 'Precondition Failed',
413: 'Request Entity Too Large',
414: 'Request-URI Too Long',
415: 'Unsupported Media Type',
416: 'Requested range unsatisfiable',
417: 'Expectation failed',
418: 'I\'m a teapot',
422: 'Unprocessable entity',
423: 'Locked',
424: 'Method failure',
425: 'Unordered Collection',
426: 'Upgrade Required',
428: 'Precondition Required',
429: 'Too Many Requests',
431: 'Request Header Fields Too Large',
449: 'Retry With',
450: 'Blocked by Windows Parental Controls',
500: 'Internal Server Error',
501: 'Not Implemented',
502: 'Bad Gateway ou Proxy Error',
503: 'Service Unavailable',
504: 'Gateway Time-out',
505: 'HTTP Version not supported',
507: 'Insufficient storage',
508: 'Loop Detected',
509: 'Bandwidth Limit Exceeded',
510: 'Not Extended',
511: 'Network Authentication Required'
}
Response.FORMAT_XML = 'xml';
Response.FORMAT_JSONP = 'jsonp';
Response.FORMAT_JSON = 'json';
Response.FORMAT_HTML = 'html';
Response.FORMAT_RAW = 'raw';
module.exports = Response;