kopia lustrzana https://github.com/c9/core
273 wiersze
11 KiB
JavaScript
273 wiersze
11 KiB
JavaScript
define(function(require, module, exports) {
|
|
"use strict";
|
|
|
|
var XHR = XMLHttpRequest; // Grab constructor early so it can't be spoofed
|
|
|
|
main.consumes = ["Plugin"];
|
|
main.provides = ["http"];
|
|
return main;
|
|
|
|
function main(options, imports, register) {
|
|
var URL = require("url");
|
|
var qs = require("querystring");
|
|
|
|
var plugin = new imports.Plugin("Ajax.org", main.consumes);
|
|
var debug = options.debug !== false;
|
|
|
|
function request(url, options, callback) {
|
|
if (!callback)
|
|
return request(url, {}, options);
|
|
|
|
if (typeof options == "string")
|
|
return request(url, {method: options}, callback);
|
|
|
|
var method = options.method || "GET";
|
|
var headers = options.headers || {};
|
|
var body = options.body || "";
|
|
var contentType = options.contentType
|
|
|| "application/x-www-form-urlencoded; charset=UTF-8";
|
|
var timeout = options.hasOwnProperty("timeout") ? options.timeout : 10000;
|
|
var async = options.sync !== true;
|
|
var parsedUrl = parseUrl(url, options.query);
|
|
if (contentType === "application/json")
|
|
headers.Accept = headers.Accept || "application/json";
|
|
|
|
if (options.username) {
|
|
headers.Authorization = "Basic " + btoa(options.username + ":" + options.password);
|
|
}
|
|
|
|
var xhr = new XHR();
|
|
|
|
if (options.overrideMimeType)
|
|
xhr.overrideMimeType = options.overrideMimeType;
|
|
|
|
// From MDN: Note: You need to add the event listeners before
|
|
// calling open() on the request. Otherwise the progress events
|
|
// will not fire.
|
|
if (options.progress) {
|
|
var obj = method == "PUT" ? xhr.upload : xhr;
|
|
obj.onprogress = function(e) {
|
|
if (e.lengthComputable)
|
|
options.progress(e.loaded, e.total);
|
|
};
|
|
}
|
|
|
|
xhr.open(method, URL.format(parsedUrl), async);
|
|
headers["Content-Type"] = contentType;
|
|
for (var header in headers)
|
|
xhr.setRequestHeader(header, headers[header]);
|
|
|
|
// encode body
|
|
if (typeof body == "object") {
|
|
if (contentType.indexOf("application/json") === 0) {
|
|
try {
|
|
body = JSON.stringify(body);
|
|
} catch (e) {
|
|
return done(new Error("Could not serialize body as json"));
|
|
}
|
|
}
|
|
else if (contentType.indexOf("application/x-www-form-urlencoded") === 0) {
|
|
body = qs.stringify(body);
|
|
}
|
|
else if (["[object File]", "[object Blob]"].indexOf(Object.prototype.toString.call(body)) > -1) {
|
|
// pass as is
|
|
xhr.overrideMimeType = body.type;
|
|
}
|
|
else {
|
|
body = body.toString();
|
|
}
|
|
}
|
|
|
|
if (options.withCredentials) {
|
|
xhr.withCredentials = true;
|
|
}
|
|
|
|
var timer;
|
|
var timedout = false;
|
|
if (timeout) {
|
|
timer = setTimeout(function() {
|
|
timedout = true;
|
|
xhr.abort();
|
|
var err = new Error("Timeout");
|
|
err.code = "ETIMEOUT";
|
|
done(err);
|
|
}, timeout);
|
|
}
|
|
|
|
xhr.send(body || "");
|
|
|
|
var abort = xhr.abort;
|
|
xhr.abort = function(){
|
|
clearTimeout(timer);
|
|
abort.call(xhr);
|
|
};
|
|
|
|
xhr.onload = function(e) {
|
|
var res = {
|
|
body: xhr.responseText,
|
|
status: xhr.status,
|
|
headers: parseHeaders(xhr.getAllResponseHeaders()),
|
|
$reqHeaders: headers // TODO remove when bug in readFileWithMetadata is fixed
|
|
};
|
|
|
|
var data = xhr.responseText;
|
|
var contentType = options.overrideMimeType || res.headers["content-type"] || "";
|
|
if (contentType.indexOf("application/json") === 0) {
|
|
try {
|
|
data = JSON.parse(data);
|
|
} catch (e) {
|
|
return done(e);
|
|
}
|
|
}
|
|
|
|
if (this.status > 299) {
|
|
var err = new Error(xhr.responseText);
|
|
err.code = xhr.status;
|
|
if (debug)
|
|
console.error("HTTP error " + this.status + ": " + xhr.responseText);
|
|
return done(err, data, res);
|
|
}
|
|
|
|
done(null, data, res);
|
|
};
|
|
|
|
xhr.onerror = function(e) {
|
|
if (typeof XMLHttpRequestProgressEvent == "function" && e instanceof XMLHttpRequestProgressEvent) {
|
|
// No useful information in this object.
|
|
// Possibly CORS error if code was 0.
|
|
var err = new Error("Failed to retrieve resource from " + parsedUrl.host);
|
|
err.cause = e;
|
|
err.code = e.target.status;
|
|
return done(err);
|
|
}
|
|
e.code = e.target.status;
|
|
done(e);
|
|
};
|
|
|
|
var called = false;
|
|
function done(err, data, res) {
|
|
timer && clearTimeout(timer);
|
|
if (called) return;
|
|
called = true;
|
|
callback(err, data, res);
|
|
}
|
|
|
|
return xhr;
|
|
}
|
|
|
|
var callbackId = 1;
|
|
function jsonP(url, options, callback) {
|
|
if (!callback) return jsonP(url, {}, options);
|
|
|
|
var cbName = "__josnpcallback" + callbackId++;
|
|
var callbackParam = options.callbackParam || "callback";
|
|
|
|
var parsedUrl = parseUrl(url, options.query);
|
|
parsedUrl.query[callbackParam] = cbName;
|
|
|
|
window[cbName] = function(json) {
|
|
delete window.cbName;
|
|
callback(json);
|
|
};
|
|
|
|
var head = document.getElementsByTagName("head")[0] || document.documentElement;
|
|
var s = document.createElement('script');
|
|
|
|
s.src = URL.format(parsedUrl);
|
|
head.appendChild(s);
|
|
|
|
s.onload = s.onreadystatechange = function(_, isAbort) {
|
|
if (isAbort || !s.readyState || s.readyState == "loaded" || s.readyState == "complete") {
|
|
head.removeChild(s);
|
|
s = s.onload = s.onreadystatechange = null;
|
|
}
|
|
};
|
|
}
|
|
|
|
function parseUrl(url, query) {
|
|
query = query || {};
|
|
var parsedUrl = URL.parse(url, true);
|
|
for (var key in query)
|
|
parsedUrl.query[key] = query[key];
|
|
|
|
delete parsedUrl.search;
|
|
|
|
return parsedUrl;
|
|
}
|
|
|
|
function parseHeaders(headerString) {
|
|
return headerString
|
|
.split('\u000d\u000a')
|
|
.reduce(function(headers, headerPair) {
|
|
var index = headerPair.indexOf('\u003a\u0020');
|
|
if (index > 0) {
|
|
var key = headerPair.substring(0, index).toLowerCase();
|
|
var val = headerPair.substring(index + 2);
|
|
headers[key] = val;
|
|
}
|
|
return headers;
|
|
}, {});
|
|
}
|
|
|
|
/**
|
|
* Simple API for performing HTTP requests.
|
|
*
|
|
* Example:
|
|
*
|
|
* http.request("http://www.c9.io", function(err, data) {
|
|
* if (err) throw err;
|
|
* console.log(data);
|
|
* });
|
|
*
|
|
* @singleton
|
|
*/
|
|
plugin.freezePublicAPI({
|
|
/**
|
|
* Performs an HTTP request
|
|
*
|
|
* @param {String} url Target URL for the HTTP request
|
|
* @param {Object} [options] Request options
|
|
* @param {String} [options.method] HTTP method (default=GET)
|
|
* @param {Object} [options.query] URL query parameters as an object
|
|
* @param {String|Object} [options.body] HTTP body for PUT and POST
|
|
* @param {Object} [options.headers] Request headers
|
|
* @param {Object} [options.username] Basic auth username
|
|
* @param {Object} [options.password] Basic auth password
|
|
* @param {Number} [options.timeout] Timeout in ms (default=10000)
|
|
* @param {String} [options.contentType='application/x-www-form-urlencoded; charset=UTF-8'] Content type of sent data
|
|
* @param {String} [options.overrideMimeType] Overrides the MIME type returned by the server
|
|
* @param {Function} [options.progress] Progress event handler
|
|
* @param {Function} [options.progress.loaded] The amount of bytes downloaded/uploaded.
|
|
* @param {Function} [options.progress.total] The total amount of bytes to download/upload.
|
|
* @param {Function} callback Called when the request returns.
|
|
* @param {Error} callback.err Error object if an error occured.
|
|
* @param {String} callback.data The data received.
|
|
* @param {Object} callback.res
|
|
* @param {String} callback.res.body The body of the response message.
|
|
* @param {Number} callback.res.status The status of the response message.
|
|
* @param {Object} callback.res.headers The headers of the response message.
|
|
*/
|
|
request: request,
|
|
|
|
/**
|
|
* Performs a JSONP request
|
|
*
|
|
* @param {String} url Target URL for the JSONP request
|
|
* @param {Object} [options] Request options
|
|
* @param {String} [options.callbackParam="callback"] name of the callback query parameter
|
|
* @param {Object} [options.query] URL query parameters as an object
|
|
* @param {Function} callback Called when the request returns.
|
|
* @param {Error} callback.err Error object if an error occured.
|
|
* @param {String} callback.data The data received.
|
|
* @param {Object} callback.res
|
|
* @param {String} callback.res.body The body of the response message.
|
|
* @param {Number} callback.res.status The status of the response message.
|
|
*/
|
|
jsonP: jsonP
|
|
});
|
|
|
|
register(null, {
|
|
http: plugin
|
|
});
|
|
}
|
|
}); |