mirror of
https://github.com/thinkjs/thinkjs.git
synced 2026-01-25 14:42:47 +00:00
347 lines
10 KiB
JavaScript
347 lines
10 KiB
JavaScript
/**
|
||
* 对HttpRequest和HttpResponse 2个对象重新包装
|
||
* @type {Object}
|
||
*/
|
||
var querystring = require('querystring');
|
||
var url = require('url');
|
||
var cookie = thinkRequire('Cookie');
|
||
var EventEmitter = require('events').EventEmitter;
|
||
var multiparty = require('multiparty');
|
||
|
||
var localIp = '127.0.0.1';
|
||
module.exports = Class(function(){
|
||
'use strict';
|
||
return {
|
||
init: function(req, res){
|
||
this.req = req;
|
||
this.res = res;
|
||
//http对象为EventEmitter的实例
|
||
this.http = new EventEmitter();
|
||
//记录当前请求的开始时间
|
||
this.http.startTime = Date.now();
|
||
},
|
||
/**
|
||
* 执行
|
||
* @param {Function} callback [description]
|
||
* @return Promise [description]
|
||
*/
|
||
run: function(){
|
||
this._request();
|
||
this._response();
|
||
//数组的indexOf要比字符串的indexOf略快
|
||
var methods = ['POST', 'PUT', 'PATCH'];
|
||
if (methods.indexOf(this.req.method) > -1) {
|
||
return this.getPostData();
|
||
}
|
||
return getPromise(this.http);
|
||
},
|
||
/**
|
||
* 检测是否含有post数据
|
||
* @return {Boolean} [description]
|
||
*/
|
||
hasPostData: function(){
|
||
if ('transfer-encoding' in this.req.headers) {
|
||
return true;
|
||
}
|
||
var contentLength = this.req.headers['content-length'] | 0;
|
||
return contentLength > 0;
|
||
},
|
||
/**
|
||
* 含有文件的表单上传
|
||
* @return {[type]} [description]
|
||
*/
|
||
_filePost: function(){
|
||
var deferred = getDefer();
|
||
var self = this;
|
||
var form = new multiparty.Form({
|
||
maxFieldsSize: C('post_max_fields_size'),
|
||
maxFields: C('post_max_fields'),
|
||
maxFilesSize: C('post_max_file_size')
|
||
});
|
||
form.on('file', function(name, value){
|
||
self.http.file[name] = value;
|
||
});
|
||
form.on('field', function(name, value){
|
||
self.http.post[name] = value;
|
||
});
|
||
form.on('close', function(){
|
||
deferred.resolve(self.http);
|
||
});
|
||
//有错误后直接拒绝当前请求
|
||
form.on('error', function(){
|
||
self.res.statusCode = 413;
|
||
self.res.end();
|
||
});
|
||
form.parse(this.req);
|
||
return deferred.promise;
|
||
},
|
||
/**
|
||
* 普通的表单上传
|
||
* @return {[type]} [description]
|
||
*/
|
||
_commonPost: function(){
|
||
var buffers = [];
|
||
var length = 0;
|
||
var self = this;
|
||
var deferred = getDefer();
|
||
this.req.on('data', function(chunk){
|
||
buffers.push(chunk);
|
||
length += chunk.length;
|
||
});
|
||
this.req.on('end', function(){
|
||
//如果长度超过限制,直接拒绝
|
||
if (length > C('post_max_fields_size')) {
|
||
self.res.statusCode = 413;
|
||
self.res.end();
|
||
return;
|
||
}
|
||
self.http.payload = Buffer.concat(buffers).toString();
|
||
tag('form_parse', self.http).then(function(){
|
||
//默认使用querystring.parse解析
|
||
if (isEmpty(self.http.post) && self.http.payload) {
|
||
self.http.post = querystring.parse(self.http.payload) || {}
|
||
}
|
||
deferred.resolve(self.http);
|
||
})
|
||
});
|
||
return deferred.promise;
|
||
},
|
||
/**
|
||
* 获取POST过来的数据,包含上传的文件
|
||
* 依赖multiparty库
|
||
* @return {[type]} [description]
|
||
*/
|
||
getPostData: function(){
|
||
//没有post数据,直接返回
|
||
if (!this.hasPostData()) {
|
||
return getPromise(this.http);
|
||
}
|
||
//上传的数据中是否含有文件的检测正则
|
||
var multiReg = /^multipart\/(form-data|related);\s*boundary=(?:"([^"]+)"|([^;]+))$/i;
|
||
if (multiReg.test(this.http.contentType)) {
|
||
return this._filePost();
|
||
}else{
|
||
return this._commonPost();
|
||
}
|
||
},
|
||
/**
|
||
* HttpRequest增强
|
||
* @return {[type]} [description]
|
||
*/
|
||
_request: function(){
|
||
var req = {
|
||
//http版本号
|
||
version: this.req.httpVersion,
|
||
//请求方法
|
||
method: this.req.method,
|
||
//请求头
|
||
headers: this.req.headers,
|
||
getHeader: function(name){
|
||
return this.headers[name] || '';
|
||
},
|
||
//请求的Content-Type
|
||
contentType: (this.req.headers['content-type'] || '').split(';')[0].trim(),
|
||
//post信息
|
||
post: {},
|
||
//上传的文件信息
|
||
file: {},
|
||
//请求用户的ip
|
||
ip: function(){
|
||
var connection = this.req.connection;
|
||
var socket = this.req.socket;
|
||
var ip = (connection && connection.remoteAddress) || (socket && socket.remoteAddress);
|
||
if (ip && ip !== localIp) {
|
||
return ip;
|
||
}
|
||
return this.headers['x-forwarded-for'] || this.headers['x-real-ip'] || localIp;
|
||
},
|
||
//请求的cookie
|
||
cookie: cookie.parse(this.req.headers.cookie || '')
|
||
};
|
||
extend(this.http, req);
|
||
|
||
//解析url中的参数
|
||
var urlInfo = url.parse('//' + req.headers.host + this.req.url, true, true);
|
||
this.http.pathname = urlInfo.pathname;
|
||
//query只记录?后面的参数
|
||
this.http.query = urlInfo.query;
|
||
//get包含路由解析追加的参数
|
||
this.http.get = extend({}, urlInfo.query);
|
||
//主机名,带端口
|
||
this.http.host = urlInfo.host;
|
||
//主机名,不带端口
|
||
this.http.hostname = urlInfo.hostname;
|
||
//将原生的request对象放在http上,方便后续在controller等地方使用
|
||
this.http.req = this.req;
|
||
},
|
||
/**
|
||
* HttpResponse增强
|
||
* @return {[type]} [description]
|
||
*/
|
||
_response: function(){
|
||
var res = {
|
||
/**
|
||
* 一次请求下,可能会发送多个Cookie,所以这里不能立即发送
|
||
* 需要临时存起来,到输出内容前统一发送
|
||
* @type {Object}
|
||
*/
|
||
_cookie: {},
|
||
/**
|
||
* 发送header
|
||
* @param {[type]} name [description]
|
||
* @param {[type]} value [description]
|
||
*/
|
||
setHeader: function(name, value){
|
||
if (this.res.headersSent) {
|
||
if (APP_DEBUG) {
|
||
console.log('headers has been sent.', name, value);
|
||
}
|
||
return;
|
||
}
|
||
this.res.setHeader(name, value);
|
||
},
|
||
/**
|
||
* 设置cookie
|
||
* @param {[type]} name [description]
|
||
* @param {[type]} value [description]
|
||
* @param {[type]} options [description]
|
||
*/
|
||
setCookie: function(name, value, options){
|
||
options = options || {};
|
||
if (typeof options === 'number') {
|
||
options = {timeout: options};
|
||
}
|
||
var timeout = options.timeout;
|
||
if (timeout === undefined) {
|
||
timeout = C('cookie_timeout');
|
||
}
|
||
delete options.timeout;
|
||
//if value is null, remove cookie
|
||
if (value === null) {
|
||
timeout = -1000;
|
||
}
|
||
var defaultOptions = {
|
||
path: C('cookie_path'),
|
||
domain: C('cookie_domain'),
|
||
expires: new Date (Date.now() + timeout * 1000)
|
||
};
|
||
if (timeout === 0) {
|
||
delete defaultOptions.expires;
|
||
}
|
||
for(var key in options){
|
||
defaultOptions[key.toLowerCase()] = options[key];
|
||
}
|
||
defaultOptions.name = name;
|
||
defaultOptions.value = encodeURIComponent(value + '');
|
||
this._cookie[name] = defaultOptions;
|
||
},
|
||
/**
|
||
* 将队列中的cookie发送出去
|
||
* @return {[type]} [description]
|
||
*/
|
||
sendCookie: function(){
|
||
var cookies = Object.values(this._cookie).map(function(item){
|
||
return cookie.stringify(item.name, item.value, item);
|
||
});
|
||
if (cookies.length) {
|
||
this.setHeader('Set-Cookie', cookies);
|
||
//发送Cookie后不清除_cookie内容,websocket里需要读取
|
||
//this._cookie = {};
|
||
}
|
||
},
|
||
/**
|
||
* url跳转
|
||
* @param {[type]} url [description]
|
||
* @param {[type]} code [description]
|
||
* @return {[type]} [description]
|
||
*/
|
||
redirect: function(url, code){
|
||
this.res.statusCode = code || 302;
|
||
this.setHeader('Location', url || '/');
|
||
this.end();
|
||
},
|
||
/**
|
||
* 发送执行时间
|
||
* @param {[type]} name [description]
|
||
* @return {[type]} [description]
|
||
*/
|
||
sendTime: function(name){
|
||
var time = Date.now() - this.startTime;
|
||
this.setHeader('X-' + name, time + 'ms');
|
||
},
|
||
/**
|
||
* 输出内容
|
||
* @param {[type]} obj [description]
|
||
* @param {[type]} encoding [description]
|
||
* @return {[type]} [description]
|
||
*/
|
||
echo: function(obj, encoding){
|
||
this.sendCookie();
|
||
if (isArray(obj) || isObject(obj)) {
|
||
obj = JSON.stringify(obj);
|
||
}
|
||
if (!isString(obj) && !(obj instanceof Buffer)) {
|
||
obj += '';
|
||
}
|
||
this.res.write(obj, encoding || C('encoding'));
|
||
},
|
||
/**
|
||
* 结束URL
|
||
* @return {[type]} [description]
|
||
*/
|
||
end: function(){
|
||
this.emit('beforeEnd', this);
|
||
this.sendCookie();
|
||
this.res.end();
|
||
this.emit('afterEnd', this);
|
||
}
|
||
};
|
||
extend(this.http, res);
|
||
//将原生的response对象放在http上,方便后续controller等地方使用
|
||
this.http.res = this.res;
|
||
}
|
||
};
|
||
});
|
||
/**
|
||
* 获取默认的http信息
|
||
* @param {[type]} data [description]
|
||
* @return {[type]} [description]
|
||
*/
|
||
module.exports.getDefaultHttp = function(data){
|
||
'use strict';
|
||
data = data || {};
|
||
if (isString(data)) {
|
||
if (data[0] === '{') {
|
||
data = JSON.parse(data);
|
||
}else if (/^[\w]+\=/.test(data)) {
|
||
data = querystring.parse(data);
|
||
}else{
|
||
data = {url: data};
|
||
}
|
||
}
|
||
var fn = function(){
|
||
return '';
|
||
};
|
||
var url = data.url || '';
|
||
if (url.indexOf('/') !== 0) {
|
||
url = '/' + url;
|
||
}
|
||
return {
|
||
req: {
|
||
httpVersion: '1.1',
|
||
method: data.method || 'GET',
|
||
url: url,
|
||
headers: extend({
|
||
host: data.host || localIp
|
||
}, data.headers || {}),
|
||
connection: {
|
||
remoteAddress: data.ip || localIp
|
||
}
|
||
},
|
||
res: {
|
||
end: data.end || data.close || fn,
|
||
write: data.write || data.send || fn,
|
||
setHeader: fn
|
||
}
|
||
};
|
||
}; |