latte_lib
Version:
1,002 lines (963 loc) • 27.6 kB
JavaScript
(function() {
//class Request
var latte_lib = require("../basic/lib.js");
var events = require("../basic/events.js");
var utils = {
type: function(str) {
return str.split(/ *; */).shift();
},
params: function(str) {
return str.split(/ *; */).reduce(function(obj, str){
var parts = str.split(/ *= */);
var key = parts.shift();
var val = parts.shift();
if (key && val) obj[key] = val;
return obj;
}, {});
},
parseLinks: function(str) {
return str.split(/ *, */).reduce(function(obj, str){
var parts = str.split(/ *; */);
var url = parts[0].slice(1, -1);
var rel = parts[1].split(/ *= */)[1].slice(1, -1);
obj[rel] = url;
return obj;
}, {});
}
};
var Response;
var STATUS_CODES = { '100': 'Continue',
'101': 'Switching Protocols',
'102': 'Processing',
'200': 'OK',
'201': 'Created',
'202': 'Accepted',
'203': 'Non-Authoritative Information',
'204': 'No Content',
'205': 'Reset Content',
'206': 'Partial Content',
'207': 'Multi-Status',
'208': 'Already Reported',
'226': 'IM Used',
'300': 'Multiple Choices',
'301': 'Moved Permanently',
'302': 'Found',
'303': 'See Other',
'304': 'Not Modified',
'305': 'Use Proxy',
'307': 'Temporary Redirect',
'308': 'Permanent 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 Timeout',
'409': 'Conflict',
'410': 'Gone',
'411': 'Length Required',
'412': 'Precondition Failed',
'413': 'Payload Too Large',
'414': 'URI Too Long',
'415': 'Unsupported Media Type',
'416': 'Range Not Satisfiable',
'417': 'Expectation Failed',
'418': 'I\'m a teapot',
'421': 'Misdirected Request',
'422': 'Unprocessable Entity',
'423': 'Locked',
'424': 'Failed Dependency',
'425': 'Unordered Collection',
'426': 'Upgrade Required',
'428': 'Precondition Required',
'429': 'Too Many Requests',
'431': 'Request Header Fields Too Large',
'451': 'Unavailable For Legal Reasons',
'500': 'Internal Server Error',
'501': 'Not Implemented',
'502': 'Bad Gateway',
'503': 'Service Unavailable',
'504': 'Gateway Timeout',
'505': 'HTTP Version Not Supported',
'506': 'Variant Also Negotiates',
'507': 'Insufficient Storage',
'508': 'Loop Detected',
'509': 'Bandwidth Limit Exceeded',
'510': 'Not Extended',
'511': 'Network Authentication Required'
};
var Request = function(method, url) {
this.method = method;
this.url = url;
//对外保存的小写头属性
this._headers = {};
//保存原来header头属性
this.headers = {};
};
latte_lib.extends(Request, events);
(function() {
var self = this;
var setsMap = {
type: "Content-type",
accept: "Accept"
};
Object.keys(setsMap).forEach(function(i) {
self[i] = function(type) {
this.set(setsMap[i], request.types[type] || type);
return this;
};
});
//设置头文件
this.set = this.setHeader= function(field, val) {
if(latte_lib.isObject(field)) {
for(var key in field) {
this.set(key, field[key]);
}
return this;
}
this._headers[field.toLowerCase()] = val;
this.headers[field] = val;
}
this.get = this.getHeader = function(field) {
this._headers[field.toLowerCase()];
}
var escape = function(str) {
return encodeURIComponent(str);
}
var stringifyPrimitive = function(v) {
switch(typeof v) {
case "string":
return v;
case "boolean":
return v? "true": "false";
case "number":
return isFinite(v)? v: "";
case "object":
return JSON.stringify(v);
default:
return "";
}
}
var serialize = function(obj, sep, eq) {
sep = sep || "&";
eq = eq || "=";
if(obj === null) {
obj = undefined;
}
if(typeof obj === "object") {
return Object.keys(obj).map(function(k) {
var ks = escape(stringifyPrimitive(k)) + eq;
if(Array.isArray(obj[k])) {
return ks + escape(JSON.stringify(obj[k]));
} else {
return ks + escape(stringifyPrimitive(obj[k]));
}
}).join(sep);
}
}
var serialize = function() {
if(!latte_lib.isObject(obj)) return obj;
var pairs = [];
for(var key in obj) {
pushEncodeKeyValuePair(pairs, key, object[key]);
}
return pairs.join("&");
}
this.query = function(val) {
if(!latte_lib.isString(val)) {
val = serialize(val);
}
if(val) {
this._query.push(val);
}
return this;
}
this.send = function(data) {
var isObj = latte_lib.isObject(data);
var type = this._headers["content-type"];
if (this._formData) {
console.error(".send() can't be used if .attach() or .field() is used. Please use only\
.send() or only .field() &.attach()");
}
if (isObj && !this._data) {
if(Array.isArray(data)) {
this._data = [];
}else if(!this._isHost(data)){
this._data = {};
}
}else if(data && this._data && this._isHost(this.data)) {
throw Error("Can't merge these send calls");
}
if(isObj && latte_lib.isObject(this._data)) {
for(var key in data) {
this._data[key] = data[key];
}
}else if(latte_lib.isString(data)) {
if(!type) {
this.type("form");
}
type = this._headers["content-type"];
if("application/x-www-form-urlencoded" == type) {
this._data = this._data ? this._data + "&" + data : data;
}else {
this._data = (this._data || "") + data;
}
}else{
this._data = data;
}
if(!isObj || this._isHost(data)) {
return this;
}
if(!type) {
this.type("json");
}
return this;
}
this.end = function(fn) {
if(this._endCalled) {
console.warn("Warning: .end() was called twice. This is not supported in superagent");
}
this._endCalled = true;
this._callback = fn || noop;
this._appendQueryString();
return this._end();
}
var escape = function(str) {
return encodeURIComponent(str);
}
var stringifyPrimitive = function(v) {
switch(typeof v) {
case "string":
return v;
case "boolean":
return v? "true": "false";
case "number":
return isFinite(v)? v: "";
case "object":
return JSON.stringify(v);
default:
return "";
}
}
this._appendQueryString = function() {
this._queryString = urlencode(this.qs);
}
this._setTimeouts = function() {
var self = this;
if(this._timeout && !this._timer) {
this._timer = setTimeout(function() {
self._timeoutError('Timeout of ', self._timeout, 'ETIME');
}, this._timeout);
}
if(this._responseTimeout && !this._responseTimeoutTimer) {
this._responseTimeoutTimer = setTimeout(function() {
self._timeoutError('Response timeout of ', self._responseTimeout, 'ETIMEDOUT');
}, this._responseTimeout);
}
}
this.callback = function(err, res) {
if(this._maxRedirects && this._retries ++ < this._maxRedirects && shouldRetry(err, res)) {
return this._retry();
}
var fn = this._callback || noop;
this.clearTimeout();
if(this.called) {
return console.warn("superagent: double callback bug");
}
this.called = true;
if(!err) {
if(this._isResponseOK(res)) {
return fn(err, res);
}
var msg = "Unsuccessful HTTP response";
if(res) {
msg = STATUS_CODES[res.status] || msg;
}
err = new Error(msg);
err.status = res ? res.status : undefined;
}
err.response = res;
if(this._maxRedirects) {
err.retries = this._retries - 1;
}
if(err && this.hasListeners("error")) {
this.emit("error", err);
}
fn(err, res);
}
this.clearTimeout = function() {
clearTimeout(this._timer);
clearTimeout(this._responseTimeout);
delete this._timer;
delete this._responseTimeoutTimer;
return;
}
this._isResponseOK = function(res) {
if(!res) {
return false;
}
if(this._okCallback) {
return this._okCallback(res);
}
return res.status >= 200 && res.status < 300;
}
}).call(Request.prototype);
var urlencode = function(obj, sep, eq) {
sep = sep || "&";
eq = eq || "=";
if(obj === null) {
obj = undefined;
}
if(typeof obj === "object") {
return Object.keys(obj).map(function(k) {
var ks = escape(stringifyPrimitive(k)) + eq;
if(Array.isArray(obj[k])) {
return ks + escape(JSON.stringify(obj[k]));
} else {
return ks + escape(stringifyPrimitive(obj[k]));
}
}).join(sep);
}
}
var request = function(method, url) {
if(latte_lib.isFunction(url)) {
return new Request("GET", method).end(url);
}
if(1 == arguments.length) {
return new Request(method, url);
}
return new Request(method, url);
};
request.types = {
html: 'text/html',
json: 'application/json',
xml: 'application/xml',
urlencoded: 'application/x-www-form-urlencoded',
'form': 'application/x-www-form-urlencoded',
'form-data': 'application/x-www-form-urlencoded',
text:"text/plain"
};
var self = this;
this.request = function(method, url, data, fn) {
var req = request(method, url);
if(latte_lib.isFunction(data)) {
fn = data;
data = null;
}
if(data) {
req.send(data);
}
if(fn) {
req.end(fn);
}
return req;
};
["HEAD","GET", "POST", "PUT","PATCH", "DELETE"].forEach(function(o) {
self[o.toLocaleLowerCase()] = function() {
var args = Array.prototype.concat.call([o], Array.prototype.slice.call(arguments, 0));
return self.request.apply(self, args);
};
});
var mimeTypeV = {
isJSON: function(mime) {
return /[\/+]json\b/.test(mime);
},
isText: function(mime) {
var parts = mime.split('/');
var type = parts[0];
var subtype = parts[1];
return 'text' == type
|| 'x-www-form-urlencoded' == subtype;
},
isImageOrVide: function(mime) {
var type = mime.split('/')[0];
return 'image' == type || 'video' == type;
}
}
var serializes = {
"application/x-www-form-urlencoded": urlencode,
"application/json" : JSON.stringify
};
function isRedirect(code) {
return ~[301, 302, 303, 305, 307, 308].indexOf(code);
};
if(latte_lib.env == "web" ) {
(function() {
var getXHR = function() {
return new XMLHttpRequest();
}
var trim = "".trim? function(s) {
return s.trim();
}: function(s) {
return s.replace(/(^\s*|\s*$)/g, "");
}
var parses = {
"application/x-www-form-urlencoded": urlencode,
"application/json": JSON.parse
}
var parseHeader = function(str) {
var lines = str.split(/\r?\n/);
var fields = {};
var index;
var line;
var field;
var val;
lines.pop();
for(var i = 0, len = lines.length; i < len; ++i) {
line = lines[i];
index = line.indexOf(":");
field = line.slice(0, index).toLowerCase();
val = trim(line.slice(index + 1));
fields[field] = val;
}
return fields;
};
Response = function(req) {
this.req = req;
this.xhr = this.req.xhr;
this.text = ((this.req.method != "HEAD") && (this.xhr.responseType === "" || this.xhr.responseType === "text" || typeof this.xhr.responseType === 'undefined'))
? this.xhr.responseText : null;
this.statusText = this.req.xhr.statusText;
var status = this.xhr.status;
if(status == 1223) {
status = 204;
}
this._setStatusProperties(status);
this.header = this.headers = parseHeader(this.xhr.getAllResponseHeaders());
this.header["content-type"] = this.xhr.getResponseHeader("content-type");
this._setHeaderProperties(this.header);
if(null === this.text && req._responseType) {
this.body = this.xhr.response;
} else {
this.body = this.req.method != "HEAD" ?
this._parseBody(this.text ? this.text : this.xhr.response) : null;
}
};
(function() {
this._parseBody = function(str) {
var parse = parses[this.type];
if(this.req._parser) {
return this.req._parser(this, str);
}
if(!parse && mimeTypeV.isJSON(this.type)) {
parse = parses["application/json"];
}
if(parse && str && (str.length || str instanceof Object)) {
try {
return parse(str)
}catch(err) {
if(parse == parses["application/json"]) {
str = str.replace(/\[,/img,"[null,").replace(/,\]/img, ",null]").replace(/,,/igm,",null,").replace(/,,/img,",null,");
return parse(str);
}
}
}else{
return null;
}
}
}).call(Response.prototype);
this._end = function() {
var self = this;
this.once("end", function() {
var err = null;
var res = null;
try {
res = new Response(self);
}catch(err) {
//err = new Error("Parser is unable to parse the response");
err.parse = true;
//err.original = e;
if(self.xhr) {
err.rawResponse = typeof self.xhr.responseType == "undefined" ? self.xhr.responseText : self.xhr.response;
err.status = self.xhr.status ? self.xhr.status : null;
err.statusCode = err.status;
} else {
err.rawResponse = null;
err.status = null;
}
return self.callback(err);
}
self.emit("response", res);
var new_err;
try {
if(!self._isResponseOK(res)) {
new_err = new Error(res.statusText || "Unsuccessful HTTP response");
new_err.original = err;
new_err.response = res;
new_err.status = res.status;
}
}catch(e) {
new_err = e;
}
if(new_err) {
self.callback(new_err, res);
}else{
self.callback(null, res);
}
});
var xhr = this.xhr = getXHR();
var data = this._formData || this._data;
this._setTimeouts();
xhr.onreadystatechange = function() {
var readyState = xhr.readyState;
if(readyState >= 2 && self._responseTimeoutTimer) {
clearTimeout(self._responseTimeoutTimer);
}
if(4 != readyState) {
return;
}
var status;
try {
status = xhr.status;
}catch(e) {
status = 0;
}
if(!status) {
if(self.timeout || self._aborted) {
return;
}
return self.crossDomainError();
}
self.emit("end");
};
var handleProgress = function(direction, e) {
if(e.total > 0) {
e.percent = e.loaded / e.total * 100;
}
e.direction = direction;
self.emit("progress", e);
}
if(this.hasListeners("progress")) {
try {
xhr.onprogress = handleProgress.bind(null, "download");
if(xhr.upload) {
xhr.upload.onprogress = handleProgress.bind(null, "upload");
}
}catch(e) {
}
}
try {
if(this.username && this.password) {
xhr.open(this.method, this.url, true, this.username, this.password);
}else{
xhr.open(this.method, this.url, true);
}
}catch(err) {
return this.callback(err);
}
if(this._withCredentials) {
xhr.withCredentials = true;
}
if(!this._formData && "GET" != this.method && "HEAD" != this.method
&& "string" != typeof data && !this._isHost(data)) {
var contentType = this._headers["content-type"];
var serialize = this._serializer || serializes[contentType ? contentType.split(";")[0]:""];
if(!serialize && mimeTypeV.isJSON(contentType)) {
serialize = serializes["application/json"];
}
if(serialize) {
data = serialize(data);
}
}
for(var field in this.headers) {
if(null == this.headers[field]) {
continue;
}
xhr.setRequestHeader(field, this.headers[field]);
}
if(this._responseType) {
xhr.responseType = this._responseType;
}
this.emit("request", this);
xhr.send(typeof data !== "undefined" ? data: null);
return this;
}
this.crossDomainError = function() {
var err = new Error('Request has been terminated\nPossible causes: the network is offline, Origin is not allowed by Access-Control-Allow-Origin, the page is being unloaded, etc.');
err.crossDomain = true;
err.status = this.status;
err.method = this.method;
err.url = this.url;
this.callback(err);
}
this._isHost = function _isHost(obj) {
// Native objects stringify to [object File], [object Blob], [object FormData], etc.
return obj && 'object' === typeof obj && !Array.isArray(obj) && Object.prototype.toString.call(obj) !== '[object Object]';
}
}).call(Request.prototype);
}else{
var binary = function(res, fn) {
var data = [];
res.on("data", function(chunk) {
data.push(chunk);
});
res.on("end", function() {
fn(null, Buffer.concat(data));
});
};
var qs = require("querystring");
var parses = {
"application/x-www-form-urlencoded": function(res, fn) {
res.text = "";
res.setEncoding("ascii");
res.on("data", function(chunk) {
res.text += chunk;
; });
res.on("end", function() {
try {
fn(null, qs.parse(res.text));
}catch(err) {
fn(err);
}
});
},
"application/json": function(res, fn) {
res.text = "";
res.setEncoding("utf8");
res.on("data", function(chunk) {
res.text += chunk;
});
res.on("end", function() {
try {
var body = res.text && JSON.parse(res.text);
}catch(e) {
var err = e;
err. rawResponse = res.text || null;
err.statusCode = res.statusCode;
} finally {
fn(err, body);
}
});
},
"text": function(res, fn) {
res.text = "";
res.setEncoding("utf8");
res.on("data", function(chunk) {
res.text += chunk;
});
res.on("end", fn);
},
"application/octet-stream": binary,
"image": binary
};
(function() {
var protocols = {
"http:":require("http"),
"https:": require("https")
};
Response = function(req) {
var res = this.res = req.res;
this.request = req;
this.req = req.req;
this.text = res.text;
this.body = res.body != undefined ? res.body: {};
this.header = this.headers = res.headers;
this.files = res.files || {};
this._setStatusProperties(res.statusCode);
this._setHeaderProperties(this.header);
};
(function() {
}).call(Response.prototype);
this._emitResponse = function(body, files) {
var response = new Response(this);
this.response = response;
response.redirects = this._redirectList;
if(undefined !== body) {
response.body = body;
}
response.files = files;
this.emit("response", response);
return response;
}
this.createReq = function() {
if(this.req) {
return this.req;
}
var self = this;
var options = {};
var url = this.url;
var retres = this._retries;
if( 0 != url.indexOf("http")) {
url = "http://" + url;
}
url = require("url").parse(url);
if(/^https?\+unix:/.test(url.protocol) === true) {
url.protocol = url.protocol.split("+")[0] + ":";
options.socketPath = unixParts[1].replace(/%2F/g, '/');
url.pathname = unixParts[2];
}
options.method = this.method;
options.port = url.port;
options.path = url.pathname;
options.host = url.hostname;
options.ca = this._ca;
options.key = this._key;
options.pfx = this._pfx;
options.cert = this._cert;
options.agent = this._agent;
var mod = protocols[url.protocol];
var req = this.req = mod.request(options);
if("HEAD" != options.method) {
req.setHeader('Accept-Encoding', 'gzip, deflate');
}
this.protocol = url.protocol;
this.host = url.host;
req.once("drain", function() {
self.emit('drain');
});
req.once("error", function(e) {
if (self._aborted) return;
if (self._retries !== retries) return;
if (self.response) return;
self.callback(err);
});
if(url.auth) {
var auth = url.auth.split(":");
this.auth(auth[0], auth[1]);
}
//if(url.search) {
// this.query(url.search.substr(1));
//}
if(this.cookies) {
req.setHeader('Cookie', this.cookies);
}
for(var key in this.headers) {
req.setHeader(key, this.headers[key]);
}
try {
this._appendQueryString(req);
}catch(e) {
return this.emit("error", e);
}
return req;
}
this._shouldUnzip = function(res) {
if (res.statusCode === 204 || res.statusCode === 304) {
// These aren't supposed to have any body
return false;
}
if("0" == res.headers["content-length"]) {
return false;
}
return /^\s*(?:deflate|gzip)\s*$/.test(res.headers['content-encoding']);
}
var URL = require("url");
this._end = function() {
var self = this;
var data = this._data;
this.createReq();
var req = this.req;
var buffer = this._buffer;
var method = this.method;
//设置定时器
this._setTimeouts();
//创建发送器
//感觉这里可以放在end 函数里面
//查看发送方式 不是head 和headerSent
if ("HEAD" != method && !req.headerSent) {
//判断data
if(!latte_lib.isString(data)) {
var contentType = req.getHeader("Content-Type");
if(contentType) {
contentType = contentType.split(';')[0]
}
var serialize = serializes[contentType];
if (!serialize && mimeTypeV.isJSON(contentType)) {
serialize = serializes['application/json'];
}
if (serialize) data = serialize(data);
}
//设置length
if(data && !req.getHeader("Content-Length")) {
req.setHeader("Content-Length", Buffer.isBuffer(data) ? data.length : Buffer.byteLength(data));
}
}
req.once("response", function(res) {
if(self._responseTimeoutTimer) {
clearTimeout(self._responseTimeoutTimer);
}
if(self.piped) {
return;
}
//重定向次数
var max = self._maxRedirects;
//mime 类型
var mime = utils.type(res.headers['content-type'] || '') || 'text/plain';
var type = mime.split("/")[0];
var multipart = "multipart" == type;
//判断是否为重定项
var redirect = isRedirect(res.statusCode);
//重定向
if (redirect && self._redirects++ != max) {
return self._redirect(res);
}
var parser = self._parser;
if("HEAD" == self.method) {
self.emit("end");
self.callback(null, self._emitResponse());
}
//暂时不支持unzip
//if(self._shouldUnzip(res)) {
// unzip(req, res);
//}
if(!parser) {
if(this._responseType) {
parser = parses.image;
buffer = true;
} else if (multipart) {
var form = new formidable.IncomingForm();
parser = form.parse.bind(form);
buffer = true;
} else if(mimeTypeV.isImageOrVide(mime)) {
parser = parses.image;
buffer = true;
} else if(parses[mime]) {
parser = parses[mime];
} else if("text" == type) {
parser = parses.text;
buffer = (buffer !== false);
} else if(mimeTypeV.isJSON(mime)) {
parser = parses["application/json"];
buffer = (buffer !== false);
} else if(buffer) {
parser = parses.text;
}
}
//判断mime类型 设置buffer
if(undefined === buffer && isText(mime) || mimeTypeV.isJSON(mime)) {
buffer = true;
}
var parserHandlesEnd = false;
if(parser) {
try {
parserHandlesEnd = buffer;
//解析
parser(res, function(err, obj, files) {
if(self.timedout) {
return;
}
if(err && !self._aborted) {
return self.callback(err);
}
if(parserHandlesEnd) {
self.emit("end");
self.callback(null, self._emitResponse(obj, files));
}
});
}catch(err) {
self.callback(err);
return;
}
}
self.res = res;
if(!buffer) {
self.callback(null, self._emitResponse());
if(multipart) return;
res.once("end", function() {
self.emit("end");
});
return;
}
res.once("error", function(err) {
self.callback(err, null);
});
if(!parserHandlesEnd) {
res.once("end", function() {
self.emit("end");
self.callback(null, self._emitResponse());
});
}
});
this.emit("request", this);
var formData = this._formData;
//formData 是个对象 暂时还没弄清楚是个什么对象
if(formData) {
var headers = formData.getHeaders();
for(var i in headers) {
req.setHeader(i, headers[i]);
}
formData.getLength(function(err, length) {
//设置content -length
if("number" == typeof length) {
req.setHeader("Content-Length", length);
}
var getProgressMonitor = function() {
var lengthComputable = true;
var total = req.getHeader("Content-Length");
var loaded = 0;
var progress = new Stream.Transform();
progress._transform = function(chunk, encoding, cb) {
loaded += chunk.length;
self.emit("progress", {
direction: "upload",
lengthComputable: lengthComputable,
loaded: loaded,
total: total
});
cb(null, chunk);
};
return progress;
};
formData.pipe(getProgressMonitor()).pipe(req);
});
}else {
req.end(data);
}
}
}).call(Request.prototype);
};
(function() {
this._setHeaderProperties = function(header) {
var ct = header["content-type"] || "";
this.type = utils.type(ct);
var params = utils.params(ct);
for(var key in params) {
this[key] = params[key];
}
this.links = {};
try {
if(header.link) {
this.links = utils.parseLinks(header.link);
}
}catch(err) {
}
}
this._setStatusProperties = function(status){
var type = status / 100 | 0;
// status / class
this.status = this.statusCode = status;
this.statusType = type;
// basics
this.info = 1 == type;
this.ok = 2 == type;
this.redirect = 3 == type;
this.clientError = 4 == type;
this.serverError = 5 == type;
this.error = (4 == type || 5 == type)
? this.toError()
: false;
// sugar
this.accepted = 202 == status;
this.noContent = 204 == status;
this.badRequest = 400 == status;
this.unauthorized = 401 == status;
this.notAcceptable = 406 == status;
this.forbidden = 403 == status;
this.notFound = 404 == status;
};
this.toError = function(){
var req = this.req;
var method = req.method;
var url = req.url;
var msg = 'cannot ' + method + ' ' + url + ' (' + this.status + ')';
var err = new Error(msg);
err.status = this.status;
err.method = method;
err.url = url;
return err;
};
}).call(Response.prototype);
}).call(module.exports);