httpinvoke
Advanced tools
Comparing version 0.0.10 to 1.0.0
{ | ||
"name": "httpinvoke", | ||
"version": "0.0.10", | ||
"version": "1.0.0", | ||
"main": "httpinvoke-browser.js", | ||
"description": "a small, no-dependencies HTTP client library for browsers and Node.js with a promise-based or Node.js-style callback-based API to progress events, text and binary file upload and download, request and response headers, status code.", | ||
"ignore": [ | ||
@@ -6,0 +7,0 @@ "node_modules", |
{ | ||
"name": "httpinvoke", | ||
"repo": "jakutis/httpinvoke", | ||
"description": "HTTP client for JavaScript", | ||
"version": "0.0.10", | ||
"version": "1.0.0", | ||
"description": "a small, no-dependencies HTTP client library for browsers and Node.js with a promise-based or Node.js-style callback-based API to progress events, text and binary file upload and download, request and response headers, status code.", | ||
"keywords": [ | ||
@@ -7,0 +7,0 @@ "http", |
@@ -33,3 +33,3 @@ var http = require('http'); | ||
} else if(req.method === 'POST') { | ||
var entity = 'Ši mokykla negriūva.\n'; | ||
var entity = new Buffer('Ši mokykla negriūva.\n'); | ||
var n = 100; | ||
@@ -36,0 +36,0 @@ res.writeHead(200, { |
@@ -5,8 +5,6 @@ var cfg = require('./karma-mocha-requireHack'); | ||
cfg.host = '0.0.0.0'; | ||
cfg.port = cfg.dummyserverPort; | ||
cfg.path = '/'; | ||
// generated | ||
cfg.url = 'http://' + cfg.host + ':' + cfg.port + cfg.path; | ||
cfg.url = 'http://' + cfg.host + ':' + cfg.dummyserverPort + '/'; | ||
cfg.corsURL = cfg.url; | ||
module.exports = cfg; |
var http = require('http'); | ||
var cfg = require('./dummyserver-config'); | ||
var daemon = require('daemon'); | ||
var fs = require('fs'); | ||
var mime = require('mime'); | ||
var hello = new Buffer('Hello World\n', 'utf8'); | ||
var bigslowHello = function(res) { | ||
@@ -45,35 +50,101 @@ var entity = 'This School Is Not Falling Apart.\n'; | ||
http.createServer(function (req, res) { | ||
// on some Android devices CORS implementations are buggy | ||
// that is why there needs to be two workarounds: | ||
// 1. custom header with origin has to be passed, because they do not send Origin header on the actual request | ||
// 2. caching must be disabled on serverside, because of unknown reasons, and when developing, you should clear cache on device, after disabling the cache on serverside | ||
// read more: http://www.kinvey.com/blog/107/how-to-build-a-service-that-supports-every-android-browser | ||
var corsHeaders = function(headers, req) { | ||
headers['Access-Control-Allow-Credentials'] = 'true'; | ||
headers['Access-Control-Allow-Origin'] = '*'; | ||
headers['Access-Control-Allow-Methods'] = 'OPTIONS, POST, HEAD, PUT, DELETE, GET'; | ||
// workaround for #1: the server-side part: need X-Httpinvoke-Origin header | ||
// workaround for Safari 4.0: need Content-Type header | ||
headers['Access-Control-Allow-Headers'] = 'Content-Type, If-Modified-Since, Range, X-Httpinvoke-Origin'; | ||
// additional optional headers | ||
headers['Access-Control-Expose-Headers'] = 'Content-Length, Content-Range, Location'; | ||
if(typeof req.headers.origin !== 'undefined') { | ||
headers['Access-Control-Allow-Origin'] = req.headers.origin; | ||
} else if(typeof req.headers['x-httpinvoke-origin'] !== 'undefined') { | ||
// workaround for #1: the server-side part | ||
headers['Access-Control-Allow-Origin'] = req.headers['x-httpinvoke-origin']; | ||
} | ||
}; | ||
var entityHeaders = function(headers) { | ||
// workaround for #2: avoiding cache | ||
headers.Pragma = 'no-cache'; | ||
headers.Expires = 'Thu, 01 Jan 1970 00:00:00 GMT'; | ||
headers['Last-Modified'] = new Date().toGMTString(); | ||
headers['Cache-Control'] = 'no-store, no-cache, must-revalidate, post-check=0, pre-check=0'; | ||
}; | ||
var outputStatus = function(req, res) { | ||
var code, i, max = 0; | ||
Object.keys(cfg.status).forEach(function(code) { | ||
Object.keys(cfg.status[code]).forEach(function(method) { | ||
if(max < cfg.status[code][method].length) { | ||
max = cfg.status[code][method].length; | ||
} | ||
}); | ||
}); | ||
if(Object.keys(cfg.status).some(function(_code) { | ||
code = _code; | ||
if(endsWith(req.url, '/status/' + code)) { | ||
for(i = 0; i < max; i += 1) { | ||
if(req.url === req.proxyPath + '/' + i + '/status/' + code) { | ||
break; | ||
} | ||
} | ||
return true; | ||
} | ||
})) { | ||
if(typeof cfg.status[code][req.method] !== 'undefined') { | ||
var params = cfg.status[code][req.method][i]; | ||
var headers = {}; | ||
corsHeaders(headers, req); | ||
if(params.location) { | ||
headers.Location = 'http://' + req.headers.host + req.proxyPath + '/'; | ||
res.writeHead(Number(code), headers); | ||
res.end(); | ||
} else if(params.responseEntity) { | ||
entityHeaders(headers); | ||
headers['Content-Type'] = 'text/plain'; | ||
if(params.partialResponse) { | ||
headers['Accept-Ranges'] = 'bytes'; | ||
headers['Content-Range'] = 'bytes 0-4/' + hello.length; | ||
headers['Content-Length'] = '5'; | ||
res.writeHead(Number(code), headers); | ||
res.end(hello.slice(0, 5)); | ||
} else { | ||
headers['Content-Length'] = String(hello.length); | ||
res.writeHead(Number(code), headers); | ||
res.end(hello); | ||
} | ||
} else { | ||
if(code === '407') { | ||
headers['Proxy-Authenticate'] = 'Basic realm="httpinvoke"'; | ||
} | ||
res.writeHead(Number(code), headers); | ||
res.end(); | ||
} | ||
} | ||
return true; | ||
} | ||
return false; | ||
}; | ||
var listen = function (req, res) { | ||
req.proxyPath = req.url.substr(0, cfg.proxyPath.length) === cfg.proxyPath ? cfg.proxyPath : ''; | ||
res.useChunkedEncodingByDefault = false; | ||
var output = function(status, body, head, mimeType) { | ||
// on some Android devices CORS implementations are buggy | ||
// that is why there needs to be two workarounds: | ||
// 1. custom header with origin has to be passed, because they do not send Origin header on the actual request | ||
// 2. caching must be disabled on serverside, because of unknown reasons, and when developing, you should clear cache on device, after disabling the cache on serverside | ||
// read more: http://www.kinvey.com/blog/107/how-to-build-a-service-that-supports-every-android-browser | ||
var headers = { | ||
'Access-Control-Allow-Credentials': 'true', | ||
'Access-Control-Allow-Origin': '*', | ||
'Access-Control-Allow-Methods': 'OPTIONS, POST, HEAD, PUT, DELETE, GET', | ||
// workaround for #1: the server-side part: need X-Httpinvoke-Origin header | ||
// workaround for Safari 4.0: need Content-Type header | ||
'Access-Control-Allow-Headers': 'Content-Type, X-Httpinvoke-Origin', | ||
// workaround for #2: avoiding cache | ||
'Pragma': 'no-cache', | ||
'Expires': 'Thu, 01 Jan 1970 00:00:00 GMT', | ||
'Last-Modified': new Date().toGMTString(), | ||
'Cache-Control': 'no-store, no-cache, must-revalidate, post-check=0, pre-check=0' | ||
}; | ||
if(body !== null) { | ||
entityHeaders(headers); | ||
headers['Content-Type'] = mimeType; | ||
headers['Content-Length'] = String(body.length); | ||
} | ||
if(typeof req.headers.origin !== 'undefined') { | ||
headers['Access-Control-Allow-Origin'] = req.headers.origin; | ||
} else if(typeof req.headers['x-httpinvoke-origin'] !== 'undefined') { | ||
// workaround for #1: the server-side part | ||
headers['Access-Control-Allow-Origin'] = req.headers['x-httpinvoke-origin']; | ||
} | ||
corsHeaders(headers, req); | ||
res.writeHead(status, headers); | ||
@@ -88,19 +159,26 @@ if(body === null || head) { | ||
if(err) { | ||
return output(200, new Buffer(err.message, 'utf8'), false, 'text/plain; charset=UTF-8'); | ||
return output(200, new Buffer(err.stack, 'utf8'), false, 'text/plain; charset=UTF-8'); | ||
} | ||
output(200, new Buffer('OK', 'utf8'), false, 'text/plain; charset=UTF-8'); | ||
}; | ||
var hello = new Buffer('Hello World\n', 'utf8'); | ||
if(req.method === 'OPTIONS') { | ||
output(200, new Buffer([]), false, 'text/plain; charset=UTF-8'); | ||
} else if(req.method === 'POST') { | ||
if(endsWith(req.url, '/noentity')) { | ||
return output(200, new Buffer([]), false, 'text/plain; charset=UTF-8'); | ||
} | ||
if(outputStatus(req, res)) { | ||
return; | ||
} | ||
if(req.method === 'POST') { | ||
if(req.url === req.proxyPath + '/noentity') { | ||
output(204, null, false); | ||
} else if(endsWith(req.url, '/headers/contentType')) { | ||
} else if(req.url === req.proxyPath + '/headers/contentType') { | ||
output(200, new Buffer(typeof req.headers['content-type'] === 'undefined' ? 'undefined' : req.headers['content-type'], 'utf8'), false, 'text/plain; charset=UTF-8'); | ||
} else if(endsWith(req.url, '/bytearray')) { | ||
} else if(req.url === req.proxyPath + '/bytearray') { | ||
readEntityBody(req, false, cfg.makeByteArrayFinished(reportTest)); | ||
} else if(endsWith(req.url, '/text/utf8')) { | ||
} else if(req.url === req.proxyPath + '/text/utf8') { | ||
readEntityBody(req, true, cfg.makeTextFinished(reportTest)); | ||
} else if(req.url === req.proxyPath + '/boolean') { | ||
readEntityBody(req, false, function(err, input) { | ||
output(200, input, false, 'text/plain; charset=UTF-8'); | ||
}); | ||
} else { | ||
@@ -116,17 +194,19 @@ output(200, hello, false, 'text/plain; charset=UTF-8'); | ||
} else if(req.method === 'GET') { | ||
if(endsWith(req.url, '/bigslow')) { | ||
if(req.url === req.proxyPath + '/') { | ||
output(200, hello, false, 'text/plain; charset=UTF-8'); | ||
} else if(req.url === req.proxyPath + '/bigslow') { | ||
bigslowHello(res); | ||
} else if(endsWith(req.url, '/text/utf8')) { | ||
} else if(req.url === req.proxyPath + '/text/utf8') { | ||
output(200, new Buffer(cfg.textTest(), 'utf8'), false, 'text/plain; charset=UTF-8'); | ||
} else if(endsWith(req.url, '/text/utf8/empty')) { | ||
} else if(req.url === req.proxyPath + '/text/utf8/empty') { | ||
output(200, new Buffer('', 'utf8'), false, 'text/plain; charset=UTF-8'); | ||
} else if(endsWith(req.url, '/json')) { | ||
} else if(req.url === req.proxyPath + '/json') { | ||
output(200, new Buffer(JSON.stringify(cfg.jsonTest()), 'utf8'), false, 'application/json'); | ||
} else if(endsWith(req.url, '/json/null')) { | ||
} else if(req.url === req.proxyPath + '/json/null') { | ||
output(200, new Buffer('null', 'utf8'), false, 'application/json'); | ||
} else if(endsWith(req.url, '/bytearray')) { | ||
} else if(req.url === req.proxyPath + '/bytearray') { | ||
output(200, new Buffer(cfg.bytearrayTest()), false, 'application/octet-stream'); | ||
} else if(endsWith(req.url, '/bytearray/empty')) { | ||
} else if(req.url === req.proxyPath + '/bytearray/empty') { | ||
output(200, new Buffer([]), false, 'application/octet-stream'); | ||
} else if(endsWith(req.url, '/tenseconds')) { | ||
} else if(req.url === req.proxyPath + '/tenseconds') { | ||
setTimeout(function() { | ||
@@ -136,3 +216,13 @@ output(200, hello, false, 'text/plain; charset=UTF-8'); | ||
} else { | ||
output(200, hello, false, 'text/plain; charset=UTF-8'); | ||
fs.realpath(__dirname + req.url.substr(req.proxyPath.length), function(err, path) { | ||
if(err || path.substr(0, __dirname.length) !== __dirname) { | ||
return output(404, null, false); | ||
} | ||
fs.readFile(path, function(err, contents) { | ||
if(err) { | ||
return output(404, null, false); | ||
} | ||
output(200, contents, false, mime.lookup(path)); | ||
}); | ||
}); | ||
} | ||
@@ -142,2 +232,13 @@ } else { | ||
} | ||
}).listen(cfg.port, cfg.host); | ||
}; | ||
if(fs.existsSync('./dummyserver.pid')) { | ||
console.log('Error: file ./dummyserver.pid already exists'); | ||
process.exit(1); | ||
} else { | ||
console.log('HTML test runner available at http://localhost:' + cfg.dummyserverPort + '/test/index.html'); | ||
daemon(); | ||
fs.writeFileSync('./dummyserver.pid', String(process.pid)); | ||
http.createServer(listen).listen(cfg.dummyserverPort, cfg.host); | ||
http.createServer(listen).listen(cfg.dummyserverPortAlternative, cfg.host); | ||
} |
@@ -10,33 +10,86 @@ (function (root, factory) { | ||
}(this, function () { | ||
var global; | ||
;global = window;;var isArrayBufferView = function(input) { | ||
return typeof input === 'object' && input !== null && ( | ||
(global.ArrayBufferView && input instanceof ArrayBufferView) || | ||
(global.Int8Array && input instanceof Int8Array) || | ||
(global.Uint8Array && input instanceof Uint8Array) || | ||
(global.Uint8ClampedArray && input instanceof Uint8ClampedArray) || | ||
(global.Int16Array && input instanceof Int16Array) || | ||
(global.Uint16Array && input instanceof Uint16Array) || | ||
(global.Int32Array && input instanceof Int32Array) || | ||
(global.Uint32Array && input instanceof Uint32Array) || | ||
(global.Float32Array && input instanceof Float32Array) || | ||
(global.Float64Array && input instanceof Float64Array) | ||
); | ||
}; | ||
var isArray = function(object) { | ||
return Object.prototype.toString.call(object) === '[object Array]'; | ||
}; | ||
var isByteArray = function(input) { | ||
return typeof input === 'object' && input !== null && ( | ||
(global.Buffer && input instanceof Buffer) || | ||
(global.Blob && input instanceof Blob) || | ||
(global.File && input instanceof File) || | ||
(global.ArrayBuffer && input instanceof ArrayBuffer) || | ||
isArrayBufferView(input) || | ||
isArray(input) | ||
); | ||
}; | ||
var bytearrayMessage = 'an instance of Buffer, nor Blob, nor File, nor ArrayBuffer, nor ArrayBufferView, nor Int8Array, nor Uint8Array, nor Uint8ClampedArray, nor Int16Array, nor Uint16Array, nor Int32Array, nor Uint32Array, nor Float32Array, nor Float64Array, nor Array'; | ||
var supportedMethods = ['GET', 'HEAD', 'POST', 'PUT', 'DELETE']; | ||
var indexOf = [].indexOf ? function(array, item) { | ||
return array.indexOf(item); | ||
} : function(array, item) { | ||
var i = -1; | ||
while(++i < array.length) { | ||
if(array[i] === item) { | ||
return i; | ||
} | ||
} | ||
return -1; | ||
}; | ||
var pass = function(value) { | ||
return value; | ||
}; | ||
var nextTick = (global.process && global.process.nextTick) || global.setImmediate || global.setTimeout; | ||
var _undefined; | ||
; | ||
// this could be a simple map, but with this "compression" we save about 100 bytes, if minified (50 bytes, if also gzipped) | ||
var statusTextToCode = (function() { | ||
var group = arguments.length, map = {}; | ||
while(group--) { | ||
var texts = arguments[group].split(','), index = texts.length; | ||
while(index--) { | ||
map[texts[index]] = (group + 1) * 100 + index; | ||
} | ||
} | ||
return map; | ||
})( | ||
'Continue,Switching Protocols', | ||
'OK,Created,Accepted,Non-Authoritative Information,No Content,Reset Content,Partial Content', | ||
'Multiple Choices,Moved Permanently,Found,See Other,Not Modified,Use Proxy,_,Temporary Redirect', | ||
'Bad Request,Unauthorized,Payment Required,Forbidden,Not Found,Method Not Allowed,Not Acceptable,Proxy Authentication Required,Request Timeout,Conflict,Gone,Length Required,Precondition Failed,Request Entity Too Large,Request-URI Too Long,Unsupported Media Type,Requested Range Not Satisfiable,Expectation Failed', | ||
'Internal Server Error,Not Implemented,Bad Gateway,Service Unavailable,Gateway Time-out,HTTP Version Not Supported' | ||
); | ||
var bufferSlice = function(buffer, begin, end) { | ||
if(begin === 0 && end === buffer.byteLength) { | ||
return buffer; | ||
} | ||
return buffer.slice ? buffer.slice(begin, end) : new Uint8Array(Array.prototype.slice.call(new Uint8Array(buffer), begin, end)).buffer; | ||
}; | ||
var responseBodyToBytes, responseBodyLength; | ||
(function() { | ||
try { | ||
var vbscript = ''; | ||
vbscript += 'Function httpinvoke_BinaryExtract(Binary, Array)\r\n'; | ||
vbscript += ' Dim len, i\r\n'; | ||
vbscript += ' len = LenB(Binary)\r\n'; | ||
vbscript += ' For i = 1 to len\r\n'; | ||
vbscript += ' Array.push(AscB(MidB(Binary, i, 1)))\r\n'; | ||
vbscript += ' Next\r\n'; | ||
vbscript += 'End Function\r\n'; | ||
vbscript += '\r\n'; | ||
vbscript += 'Function httpinvoke_BinaryLength(Binary)\r\n'; | ||
vbscript += ' httpinvoke_BinaryLength = LenB(Binary)\r\n'; | ||
vbscript += 'End Function\r\n'; | ||
vbscript += '\r\n'; | ||
window.execScript(vbscript, 'vbscript'); | ||
var byteMapping = {}; | ||
for(var i = 0; i < 256; i += 1) { | ||
for (var j = 0; j < 256; j += 1) { | ||
byteMapping[String.fromCharCode(i + j * 256)] = String.fromCharCode(i) + String.fromCharCode(j); | ||
} | ||
} | ||
execScript('Function httpinvoke0(B,A)\r\nDim i\r\nFor i=1 to LenB(B)\r\nA.push(AscB(MidB(B,i,1)))\r\nNext\r\nEnd Function\r\nFunction httpinvoke1(B)\r\nhttpinvoke1=LenB(B)\r\nEnd Function', 'vbscript'); | ||
responseBodyToBytes = function(binary) { | ||
var bytes = []; | ||
window.httpinvoke_BinaryExtract(binary, bytes); | ||
httpinvoke0(binary, bytes); | ||
return bytes; | ||
}; | ||
// cannot just assign the function, because httpinvoke1 is not a javascript 'function' | ||
responseBodyLength = function(binary) { | ||
return window.httpinvoke_BinaryLength(binary); | ||
return httpinvoke1(binary); | ||
}; | ||
@@ -46,119 +99,60 @@ } catch(err) { | ||
})(); | ||
var getOutput = { | ||
'text' : function(xhr) { | ||
if(typeof xhr.response !== 'undefined') { | ||
return xhr.response; | ||
} | ||
return xhr.responseText; | ||
}, | ||
'bytearray' : function(xhr) { | ||
if(typeof xhr.response !== 'undefined') { | ||
return new Uint8Array(xhr.response); | ||
} | ||
if(typeof xhr.responseBody !== 'undefined') { | ||
return responseBodyToBytes(xhr.responseBody); | ||
} | ||
var str = xhr.responseText, n = str.length, bytearray = new Array(n); | ||
for(var i = 0; i < n; i += 1) { | ||
bytearray[i] = str.charCodeAt(i) & 0xFF; | ||
} | ||
if(typeof Uint8Array !== 'undefined') { | ||
// firefox 4 supports typed arrays but not xhr2 | ||
return new Uint8Array(bytearray); | ||
} | ||
return bytearray; | ||
var getOutputText = function(xhr) { | ||
return xhr.response || xhr.responseText; | ||
}; | ||
var binaryStringToByteArray = function(str) { | ||
var n = str.length, bytearray = new Array(n); | ||
while(n--) { | ||
bytearray[n] = str.charCodeAt(n) & 255; | ||
} | ||
return bytearray; | ||
}; | ||
var getOutputLength = { | ||
'text': function(xhr) { | ||
return countStringBytes(getOutput.text(xhr)); | ||
}, | ||
'bytearray': function(xhr) { | ||
if(typeof xhr.response !== 'undefined') { | ||
return xhr.response.byteLength; | ||
} | ||
if(typeof xhr.responseBody !== 'undefined') { | ||
return responseBodyLength(xhr.responseBody); | ||
} | ||
return xhr.responseText.length; | ||
var getOutputBinary = function(xhr) { | ||
if('response' in xhr) { | ||
return new Uint8Array(xhr.response || []); | ||
} | ||
// responseBody must be checked this way, because otherwise | ||
// it is falsy and then accessing responseText for binary data | ||
// results in the "c00ce514" error | ||
if('responseBody' in xhr) { | ||
return responseBodyToBytes(xhr.responseBody); | ||
} | ||
var bytearray = binaryStringToByteArray(xhr.responseText); | ||
// firefox 4 supports typed arrays but not xhr2 | ||
return global.Uint8Array ? new global.Uint8Array(bytearray) : bytearray; | ||
}; | ||
var trim = typeof ''.trim === 'undefined' ? function(string) { | ||
return string.replace(/^\s+|\s+$/g,''); | ||
} : function(string) { | ||
return string.trim(); | ||
var getOutputLengthText = function(xhr) { | ||
return countStringBytes(getOutputText(xhr)); | ||
}; | ||
var indexOf = typeof [].indexOf === 'undefined' ? function(array, item) { | ||
for(var i = 0; i < array.length; i += 1) { | ||
if(array[i] === item) { | ||
return i; | ||
} | ||
var getOutputLengthBinary = function(xhr) { | ||
if('response' in xhr) { | ||
return xhr.response ? xhr.response.byteLength : 0; | ||
} | ||
return -1; | ||
} : function(array, item) { | ||
return array.indexOf(item); | ||
// responseBody must be checked this way, because otherwise | ||
// it is falsy and then accessing responseText for binary data | ||
// results in the "c00ce514" error | ||
if('responseBody' in xhr) { | ||
return responseBodyLength(xhr.responseBody); | ||
} | ||
return xhr.responseText.length; | ||
}; | ||
var countStringBytes = function(string) { | ||
var n = 0; | ||
for(var i = 0; i < string.length; i += 1) { | ||
var c = string.charCodeAt(i); | ||
if (c < 128) { | ||
n += 1; | ||
} else if (c < 2048) { | ||
n += 2; | ||
} else { | ||
n += 3; | ||
} | ||
var c, n = 0, i = string.length; | ||
while(i--) { | ||
c = string.charCodeAt(i); | ||
n += c < 128 ? 1 : (c < 2048 ? 2 : 3); | ||
} | ||
return n; | ||
}; | ||
var convertByteArrayToBinaryString = function(bytearray) { | ||
var str = ''; | ||
for(var i = 0; i < bytearray.length; i += 1) { | ||
str += String.fromCharCode(bytearray[i]); | ||
} | ||
return str; | ||
}; | ||
var supportedMethods = ['GET', 'HEAD', 'POST', 'PUT', 'DELETE']; | ||
var parseHeader = function(header) { | ||
var colon = header.indexOf(':'); | ||
return { | ||
name: header.slice(0, colon).toLowerCase(), | ||
value: trim(header.slice(colon + 1)) | ||
}; | ||
}; | ||
// http://www.w3.org/TR/XMLHttpRequest/#the-setrequestheader()-method | ||
var forbiddenInputHeaders = ['accept-charset', 'accept-encoding', 'access-control-request-headers', 'access-control-request-method', 'connection', 'content-length', 'content-transfer-encoding', 'cookie', 'cookie2', 'date', 'dnt', 'expect', 'host', 'keep-alive', 'origin', 'referer', 'te', 'trailer', 'transfer-encoding', 'upgrade', 'user-agent', 'via']; | ||
var validateInputHeaders = function(headers) { | ||
for(var header in headers) { | ||
if(headers.hasOwnProperty(header)) { | ||
var headerl = header.toLowerCase(); | ||
if(indexOf(forbiddenInputHeaders, headerl) >= 0) { | ||
throw new Error('Input header ' + header + ' is forbidden to be set programmatically'); | ||
} | ||
if(headerl.substr(0, 'proxy-'.length) === 'proxy-') { | ||
throw new Error('Input header ' + header + ' (to be precise, all Proxy-*) is forbidden to be set programmatically'); | ||
} | ||
if(headerl.substr(0, 'sec-'.length) === 'sec-') { | ||
throw new Error('Input header ' + header + ' (to be precise, all Sec-*) is forbidden to be set programmatically'); | ||
} | ||
} | ||
var fillOutputHeaders = function(xhr, headers, outputHeaders) { | ||
var i, colon, header; | ||
headers = xhr.getAllResponseHeaders().split(/\r?\n/); | ||
i = headers.length; | ||
while(i-- && (colon = headers[i].indexOf(':')) >= 0) { | ||
outputHeaders[headers[i].slice(0, colon).toLowerCase()] = header.slice(colon + 2); | ||
} | ||
return i + 1 !== headers.length; | ||
}; | ||
var fillOutputHeaders = function(xhr, outputHeaders) { | ||
var headers = xhr.getAllResponseHeaders().split(/\r?\n/); | ||
var i = headers.length - 1; | ||
var header; | ||
while(i >= 0) { | ||
header = trim(headers[i]); | ||
if(header.length > 0) { | ||
header = parseHeader(header); | ||
outputHeaders[header.name] = header.value; | ||
} | ||
i -= 1; | ||
} | ||
}; | ||
@@ -171,144 +165,276 @@ var urlPartitioningRegExp = /^([\w.+-]+:)(?:\/\/([^\/?#:]*)(?::(\d+)|)|)/; | ||
}; | ||
var failWithoutRequest = function(cb, err) { | ||
setTimeout(function() { | ||
if(cb === null) { | ||
return; | ||
} | ||
cb(err); | ||
}, 0); | ||
return noop; | ||
}; | ||
var noop = function() {}; | ||
var createXHR; | ||
var httpinvoke = function(uri, method, options) { | ||
/*************** COMMON initialize parameters **************/ | ||
if(typeof method === 'undefined') { | ||
method = 'GET'; | ||
options = {}; | ||
} else if(typeof options === 'undefined') { | ||
if(typeof method === 'string') { | ||
options = {}; | ||
} else { | ||
options = method; | ||
method = 'GET'; | ||
} | ||
} | ||
options = typeof options === 'function' ? { | ||
var httpinvoke = function(uri, method, options, cb) { | ||
;var mixInPromise, promise, failWithoutRequest, uploadProgressCb, inputLength, noData, timeout, inputHeaders, corsOriginHeader, statusCb, initDownload, updateDownload, outputHeaders, exposedHeaders, status, outputBinary, input, outputLength, outputConverter; | ||
/*************** COMMON initialize parameters **************/ | ||
if(!method) { | ||
// 1 argument | ||
// method, options, cb skipped | ||
method = 'GET'; | ||
options = {}; | ||
} else if(!options) { | ||
// 2 arguments | ||
if(typeof method === 'string') { | ||
// options. cb skipped | ||
options = {}; | ||
} else if(typeof method === 'object') { | ||
// method, cb skipped | ||
options = method; | ||
method = 'GET'; | ||
} else { | ||
// method, options skipped | ||
options = { | ||
finished: method | ||
}; | ||
method = 'GET'; | ||
} | ||
} else if(!cb) { | ||
// 3 arguments | ||
if(typeof method === 'object') { | ||
// method skipped | ||
method.finished = options; | ||
options = method; | ||
method = 'GET'; | ||
} else if(typeof options === 'function') { | ||
// options skipped | ||
options = { | ||
finished: options | ||
} : options; | ||
var safeCallback = function(name) { | ||
if(typeof options[name] === 'undefined') { | ||
return noop; | ||
}; | ||
} | ||
// cb skipped | ||
} else { | ||
// 4 arguments | ||
options.finished = cb; | ||
} | ||
var safeCallback = function(name, aspect) { | ||
if(name in options) { | ||
return function(a, b, c, d) { | ||
try { | ||
options[name](a, b, c, d); | ||
} catch(_) { | ||
} | ||
return function() { | ||
try { | ||
options[name].apply(null, arguments); | ||
} catch(_) { | ||
} | ||
}; | ||
aspect(a, b, c, d); | ||
}; | ||
var uploadProgressCb = safeCallback('uploading'); | ||
var downloadProgressCb = safeCallback('downloading'); | ||
var statusCb = safeCallback('gotStatus'); | ||
var cb = safeCallback('finished'); | ||
var timeout = options.timeout || 0; | ||
var inputLength, inputHeaders = options.headers || {}; | ||
var inputType; | ||
var outputType = options.outputType || "text"; | ||
var exposedHeaders = options.corsHeaders || []; | ||
var corsOriginHeader = options.corsOriginHeader || 'X-Httpinvoke-Origin'; | ||
exposedHeaders.push.apply(exposedHeaders, ['Cache-Control', 'Content-Language', 'Content-Type', 'Expires', 'Last-Modified', 'Pragma']); | ||
/*************** COMMON convert and validate parameters **************/ | ||
if(indexOf(supportedMethods, method) < 0) { | ||
return failWithoutRequest(cb, new Error('Unsupported method ' + method)); | ||
} | ||
return aspect; | ||
}; | ||
mixInPromise = function(o) { | ||
var state = []; | ||
var chain = function(p, promise) { | ||
if(p && p.then) { | ||
p.then(promise.resolve.bind(promise), promise.reject.bind(promise), promise.notify.bind(promise)); | ||
} | ||
if(outputType !== 'text' && outputType !== 'bytearray') { | ||
return failWithoutRequest(cb, new Error('Unsupported outputType ' + outputType)); | ||
}; | ||
var loop = function(value) { | ||
if(!isArray(state)) { | ||
return; | ||
} | ||
if(typeof options.input === 'undefined') { | ||
if(typeof options.inputType !== 'undefined') { | ||
return failWithoutRequest(cb, new Error('"input" is undefined, but inputType is defined')); | ||
} | ||
if(typeof inputHeaders['Content-Type'] !== 'undefined') { | ||
return failWithoutRequest(cb, new Error('"input" is undefined, but Content-Type request header is defined')); | ||
} | ||
var name, after = function() { | ||
state = value; | ||
}; | ||
if(value instanceof Error) { | ||
name = 'onreject'; | ||
} else if(value.type) { | ||
name = 'onnotify'; | ||
after = pass; | ||
} else { | ||
if(typeof options.inputType === 'undefined') { | ||
inputType = 'auto'; | ||
} else { | ||
inputType = options.inputType; | ||
if(inputType !== 'auto' && inputType !== 'text' && inputType !== 'bytearray') { | ||
return failWithoutRequest(cb, new Error('Unsupported inputType ' + inputType)); | ||
name = 'onresolve'; | ||
} | ||
var p; | ||
for(var i = 0; i < state.length; i++) { | ||
try { | ||
p = state[i][name](value); | ||
if(after !== pass) { | ||
chain(p, state[i].promise); | ||
} | ||
} catch(_) { | ||
} | ||
if(inputType === 'auto') { | ||
if(typeof inputHeaders['Content-Type'] === 'undefined') { | ||
return failWithoutRequest(cb, new Error('inputType is auto and Content-Type request header is not specified')); | ||
} | ||
if(typeof inputHeaders['Content-Type'] === 'undefined') { | ||
inputType = 'bytearray'; | ||
} else if(inputHeaders['Content-Type'].substr(0, 'text/'.length) === 'text/') { | ||
inputType = 'text'; | ||
} else { | ||
inputType = 'bytearray'; | ||
} | ||
} | ||
if(inputType === 'text') { | ||
if(typeof options.input !== 'string') { | ||
return failWithoutRequest(cb, new Error('inputType is text, but input is not a string')); | ||
} | ||
if(typeof inputHeaders['Content-Type'] === 'undefined') { | ||
inputHeaders['Content-Type'] = 'text/plain; charset=UTF-8'; | ||
} | ||
} else if(inputType === 'bytearray') { | ||
if(httpinvoke.requestTextOnly) { | ||
return failWithoutRequest(cb, new Error('bytearray inputType is not supported on this platform, please always test using requestTextOnly feature flag')); | ||
} | ||
if(typeof options.input !== 'object' || options.input === null || !((typeof Blob !== 'undefined' && options.input instanceof Blob) || (typeof ArrayBuffer !== 'undefined' && options.input instanceof ArrayBuffer) || Object.prototype.toString.call(options.input) === '[object Array]')) { | ||
return failWithoutRequest(cb, new Error('inputType is bytearray, but input is neither an instance of ArrayBuffer, nor Array, nor Blob')); | ||
} | ||
if(typeof inputHeaders['Content-Type'] === 'undefined') { | ||
inputHeaders['Content-Type'] = 'application/octet-stream'; | ||
} | ||
} | ||
} | ||
after(); | ||
}; | ||
o.then = function(onresolve, onreject, onnotify) { | ||
var promise = mixInPromise({}); | ||
if(isArray(state)) { | ||
// TODO see if the property names are minifed | ||
state.push({ | ||
onresolve: onresolve, | ||
onreject: onreject, | ||
onnotify: onnotify, | ||
promise: promise | ||
}); | ||
} else if(state instanceof Error) { | ||
nextTick(function() { | ||
chain(onreject(state), promise); | ||
}); | ||
} else { | ||
nextTick(function() { | ||
chain(onresolve(state), promise); | ||
}); | ||
} | ||
return promise; | ||
}; | ||
o.notify = loop; | ||
o.resolve = loop; | ||
o.reject = loop; | ||
return o; | ||
}; | ||
failWithoutRequest = function(cb, err) { | ||
nextTick(function() { | ||
if(cb === null) { | ||
return; | ||
} | ||
cb(err); | ||
}); | ||
promise = function() { | ||
}; | ||
return mixInPromise(promise); | ||
}; | ||
try { | ||
validateInputHeaders(inputHeaders); | ||
} catch(err) { | ||
return failWithoutRequest(cb, err); | ||
uploadProgressCb = safeCallback('uploading', function(current, total) { | ||
promise.notify({ | ||
type: 'upload', | ||
current: current, | ||
total: total | ||
}); | ||
}); | ||
var downloadProgressCb = safeCallback('downloading', function(current, total) { | ||
promise.notify({ | ||
type: 'download', | ||
current: current, | ||
total: total | ||
}); | ||
}); | ||
statusCb = safeCallback('gotStatus', function(statusCode, headers) { | ||
promise.notify({ | ||
type: 'headers', | ||
statusCode: statusCode, | ||
headers: headers | ||
}); | ||
}); | ||
cb = safeCallback('finished', function(err, body, statusCode, headers) { | ||
if(err) { | ||
return promise.reject(err); | ||
} | ||
promise.resolve({ | ||
body: body, | ||
statusCode: statusCode, | ||
headers: headers | ||
}); | ||
}); | ||
timeout = options.timeout || 0; | ||
var converters = options.converters || {}; | ||
var inputConverter; | ||
inputLength = 0; | ||
inputHeaders = options.headers || {}; | ||
outputHeaders = {}; | ||
exposedHeaders = options.corsExposedHeaders || []; | ||
exposedHeaders.push.apply(exposedHeaders, ['Cache-Control', 'Content-Language', 'Content-Type', 'Content-Length', 'Expires', 'Last-Modified', 'Pragma', 'Content-Range']); | ||
corsOriginHeader = options.corsOriginHeader || 'X-Httpinvoke-Origin'; | ||
/*************** COMMON convert and validate parameters **************/ | ||
if(indexOf(supportedMethods, method) < 0) { | ||
return failWithoutRequest(cb, new Error('Unsupported method ' + method)); | ||
} | ||
outputBinary = options.outputType === 'bytearray'; | ||
if(!options.outputType || options.outputType === 'text' || outputBinary) { | ||
outputConverter = pass; | ||
} else if(converters['text ' + options.outputType]) { | ||
outputConverter = converters['text ' + options.outputType]; | ||
outputBinary = false; | ||
} else if(converters['bytearray ' + options.outputType]) { | ||
outputConverter = converters['bytearray ' + options.outputType]; | ||
outputBinary = true; | ||
} else { | ||
return failWithoutRequest(cb, new Error('Unsupported outputType ' + options.outputType)); | ||
} | ||
inputConverter = pass; | ||
if('input' in options) { | ||
input = options.input; | ||
if(!options.inputType || options.inputType === 'auto') { | ||
if(typeof input !== 'string' && !isByteArray(input)) { | ||
return failWithoutRequest(cb, new Error('inputType is undefined or auto and input is neither string, nor ' + bytearrayMessage)); | ||
} | ||
/*************** COMMON initialize helper variables **************/ | ||
var downloaded, outputHeaders, outputLength; | ||
var initDownload = function(total) { | ||
if(typeof outputLength !== 'undefined') { | ||
return; | ||
} | ||
outputLength = total; | ||
} else if(options.inputType === 'text') { | ||
if(typeof input !== 'string') { | ||
return failWithoutRequest(cb, new Error('inputType is text, but input is not a string')); | ||
} | ||
} else if (options.inputType === 'bytearray') { | ||
if(!isByteArray(input)) { | ||
return failWithoutRequest(cb, new Error('inputType is bytearray, but input is neither ' + bytearrayMessage)); | ||
} | ||
} else if(converters[options.inputType + ' text']) { | ||
inputConverter = converters[options.inputType + ' text']; | ||
} else if(converters[options.inputType + ' bytearray']) { | ||
inputConverter = converters[options.inputType + ' bytearray']; | ||
} else { | ||
return failWithoutRequest(cb, new Error('There is no converter for specified inputType')); | ||
} | ||
if(typeof input === 'string') { | ||
if(!inputHeaders['Content-Type']) { | ||
inputHeaders['Content-Type'] = 'text/plain; charset=UTF-8'; | ||
} | ||
} else { | ||
if(!inputHeaders['Content-Type']) { | ||
inputHeaders['Content-Type'] = 'application/octet-stream'; | ||
} | ||
if(global.ArrayBuffer && input instanceof ArrayBuffer) { | ||
input = new Uint8Array(input); | ||
} else if(isArrayBufferView(input)) { | ||
input = new Uint8Array(input.buffer, input.byteOffset, input.byteLength); | ||
} | ||
} | ||
try { | ||
input = inputConverter(input); | ||
} catch(err) { | ||
return failWithoutRequest(cb, err); | ||
} | ||
} else { | ||
if(options.inputType) { | ||
return failWithoutRequest(cb, new Error('"input" is undefined, but inputType is defined')); | ||
} | ||
if(inputHeaders['Content-Type']) { | ||
return failWithoutRequest(cb, new Error('"input" is undefined, but Content-Type request header is defined')); | ||
} | ||
} | ||
downloadProgressCb(downloaded, outputLength); | ||
}; | ||
var updateDownload = function(value) { | ||
if(value === downloaded) { | ||
/*************** COMMON initialize helper variables **************/ | ||
var downloaded; | ||
initDownload = function(total) { | ||
if(typeof outputLength === 'undefined') { | ||
downloadProgressCb(downloaded, outputLength = total); | ||
} | ||
}; | ||
updateDownload = function(value) { | ||
if(value !== downloaded) { | ||
downloadProgressCb(downloaded = value, outputLength); | ||
} | ||
}; | ||
noData = function() { | ||
initDownload(0); | ||
if(cb) { | ||
// TODO what happens if we try to call abort in cb? | ||
cb(null, _undefined, status, outputHeaders); | ||
cb = null; | ||
} | ||
}; | ||
; | ||
/*************** initialize helper variables **************/ | ||
var getOutput = outputBinary ? getOutputBinary : getOutputText; | ||
var getOutputLength = outputBinary ? getOutputLengthBinary : getOutputLengthText; | ||
var uploadProgressCbCalled = false; | ||
var uploadProgress = function(uploaded) { | ||
if(!uploadProgressCb) { | ||
return; | ||
} | ||
downloaded = value; | ||
downloadProgressCb(downloaded, outputLength); | ||
}; | ||
var noData = function() { | ||
initDownload(0); | ||
if(cb === null) { | ||
return; | ||
if(!uploadProgressCbCalled) { | ||
uploadProgressCbCalled = true; | ||
uploadProgressCb(0, inputLength); | ||
if(!cb) { | ||
return; | ||
} | ||
} | ||
updateDownload(0); | ||
if(cb === null) { | ||
return; | ||
uploadProgressCb(uploaded, inputLength); | ||
if(uploaded === inputLength) { | ||
uploadProgressCb = null; | ||
} | ||
cb(); | ||
cb = null; | ||
}; | ||
/*************** initialize helper variables **************/ | ||
var uploadProgressCbCalled = false; | ||
var output; | ||
@@ -319,8 +445,8 @@ var i; | ||
// IE may throw an exception when accessing | ||
// a field from window.location if document.domain has been set | ||
currentLocation = window.location.href; | ||
// a field from global.location if global.document.domain has been set | ||
currentLocation = global.location.href; | ||
} catch(_) { | ||
// Use the href attribute of an A element | ||
// since IE will modify it given document.location | ||
currentLocation = window.document.createElement('a'); | ||
currentLocation = global.document.createElement('a'); | ||
currentLocation.href = ''; | ||
@@ -331,2 +457,5 @@ currentLocation = currentLocation.href; | ||
/*************** start XHR **************/ | ||
if(typeof input === 'object' && httpinvoke.requestTextOnly) { | ||
return failWithoutRequest(cb, new Error('bytearray inputType is not supported on this platform, please always test using requestTextOnly feature flag')); | ||
} | ||
if(crossDomain && !httpinvoke.cors) { | ||
@@ -344,12 +473,15 @@ return failWithoutRequest(cb, new Error('Cross-origin requests are not supported')); | ||
} | ||
if(!createXHR) { | ||
return failWithoutRequest(cb, new Error('unable to construct XMLHttpRequest object')); | ||
} | ||
var xhr = createXHR(crossDomain); | ||
xhr.open(method, uri, true); | ||
if(timeout > 0) { | ||
if(typeof xhr.timeout === 'undefined') { | ||
if('timeout' in xhr) { | ||
xhr.timeout = timeout; | ||
} else { | ||
setTimeout(function() { | ||
cb(new Error('Timeout of ' + timeout + 'ms exceeded')); | ||
cb(new Error('download timeout')); | ||
cb = null; | ||
}, timeout); | ||
} else { | ||
xhr.timeout = timeout; | ||
} | ||
@@ -368,150 +500,203 @@ } | ||
// workaraound for #1: sending origin in custom header, also see the server-side part of the workaround in dummyserver.js | ||
inputHeaders[corsOriginHeader] = window.location.protocol + '//' + window.location.host; | ||
inputHeaders[corsOriginHeader] = global.location.protocol + '//' + global.location.host; | ||
} | ||
/*************** bind XHR event listeners **************/ | ||
if(typeof xhr.upload !== 'undefined') { | ||
xhr.upload.ontimeout = function(progressEvent) { | ||
if(cb === null) { | ||
return; | ||
var makeErrorCb = function(message) { | ||
return function() { | ||
// must check, because some callbacks are called synchronously, thus throwing exceptions and breaking code | ||
if(cb) { | ||
cb(new Error(message)); | ||
cb = null; | ||
} | ||
cb(new Error('upload timeout')); | ||
cb = null; | ||
}; | ||
xhr.upload.onerror = function(progressEvent) { | ||
if(cb === null) { | ||
return; | ||
} | ||
cb(new Error('upload error')); | ||
cb = null; | ||
}; | ||
xhr.upload.onprogress = function(progressEvent) { | ||
if(cb === null) { | ||
return; | ||
} | ||
if(progressEvent.lengthComputable) { | ||
if(!uploadProgressCbCalled) { | ||
uploadProgressCbCalled = true; | ||
uploadProgressCb(0, inputLength); | ||
} | ||
uploadProgressCb(progressEvent.loaded, inputLength); | ||
} | ||
}; | ||
}; | ||
var onuploadprogress = function(progressEvent) { | ||
if(cb && progressEvent.lengthComputable) { | ||
uploadProgress(progressEvent.loaded); | ||
} | ||
}; | ||
if('upload' in xhr) { | ||
xhr.upload.ontimeout = makeErrorCb('upload timeout'); | ||
xhr.upload.onerror = makeErrorCb('upload error'); | ||
xhr.upload.onprogress = onuploadprogress; | ||
} else if('onuploadprogress' in xhr) { | ||
xhr.onuploadprogress = onuploadprogress; | ||
} | ||
if(typeof xhr.ontimeout !== 'undefined') { | ||
xhr.ontimeout = function(progressEvent) { | ||
if(cb === null) { | ||
return; | ||
} | ||
cb(new Error('download timeout')); | ||
cb = null; | ||
}; | ||
if('ontimeout' in xhr) { | ||
xhr.ontimeout = makeErrorCb('download timeout'); | ||
} | ||
if(typeof xhr.onerror !== 'undefined') { | ||
if('onerror' in xhr) { | ||
xhr.onerror = function() { | ||
if(cb === null) { | ||
return; | ||
} | ||
cb(new Error('download error')); | ||
cb = null; | ||
//inspect('onerror', arguments[0]); | ||
//dbg('onerror'); | ||
// For 4XX and 5XX response codes Firefox 3.6 cross-origin request ends up here, but has correct statusText, but no status and headers | ||
onLoad(); | ||
}; | ||
} | ||
if(typeof xhr.onloadstart !== 'undefined') { | ||
if('onloadstart' in xhr) { | ||
xhr.onloadstart = function() { | ||
//dbg('onloadstart'); | ||
onHeadersReceived(false); | ||
}; | ||
} | ||
if(typeof xhr.onloadend !== 'undefined') { | ||
if('onloadend' in xhr) { | ||
xhr.onloadend = function() { | ||
//dbg('onloadend'); | ||
onHeadersReceived(false); | ||
}; | ||
} | ||
if(typeof xhr.onprogress !== 'undefined') { | ||
if('onprogress' in xhr) { | ||
xhr.onprogress = function(progressEvent) { | ||
if(cb === null) { | ||
//dbg('onprogress'); | ||
if(!cb) { | ||
return; | ||
} | ||
onHeadersReceived(false); | ||
if(statusCb !== null) { | ||
if(statusCb) { | ||
return; | ||
} | ||
if(typeof progressEvent !== 'undefined') { | ||
var total = progressEvent.total || progressEvent.totalSize || 0; | ||
var current = progressEvent.loaded || progressEvent.position || 0; | ||
if(progressEvent.lengthComputable) { | ||
initDownload(total); | ||
} | ||
if(cb === null) { | ||
return; | ||
} | ||
if(current > total) { | ||
// Opera 12 progress events has a bug - .loaded can be higher than .total | ||
// see http://dev.opera.com/articles/view/xhr2/#comment-96081222 | ||
return; | ||
} | ||
updateDownload(current); | ||
// There is a bug in Chrome 10 on 206 response with Content-Range=0-4/12 - total must be 5 | ||
// 'total', 12, 'totalSize', 12, 'loaded', 5, 'position', 5, 'lengthComputable', true, 'status', 206 | ||
// console.log('total', progressEvent.total, 'totalSize', progressEvent.totalSize, 'loaded', progressEvent.loaded, 'position', progressEvent.position, 'lengthComputable', progressEvent.lengthComputable, 'status', status); | ||
// httpinvoke does not work around this bug, because Chrome 10 is practically not used at all, as Chrome agressively auto-updates itself to latest version | ||
var total = progressEvent.total || progressEvent.totalSize || 0; | ||
var current = progressEvent.loaded || progressEvent.position || 0; | ||
if(progressEvent.lengthComputable) { | ||
initDownload(total); | ||
} | ||
if(!cb) { | ||
return; | ||
} | ||
if(current > total) { | ||
// Opera 12 progress events has a bug - .loaded can be higher than .total | ||
// see http://dev.opera.com/articles/view/xhr2/#comment-96081222 | ||
return; | ||
} | ||
updateDownload(current); | ||
}; | ||
} | ||
/* | ||
var inspect = function(name, obj) { | ||
return; | ||
console.log('INSPECT ----- ', name, uri); | ||
for(var i in obj) { | ||
try { | ||
console.log(name, 'PASS', i, typeof obj[i], typeof obj[i] === 'function' ? '[code]' : obj[i]); | ||
} catch(_) { | ||
console.log(name, 'FAIL', i); | ||
} | ||
} | ||
}; | ||
var dbg = function(name) { | ||
console.log('DBG ----- ', name, uri); | ||
inspect('xhr', xhr); | ||
try { | ||
console.log('PASS', 'headers', xhr.getAllResponseHeaders()); | ||
} catch(_) { | ||
console.log('FAIL', 'headers'); | ||
} | ||
try { | ||
console.log('PASS', 'cache-control', xhr.getResponseHeader('Cache-Control')); | ||
} catch(_) { | ||
console.log('FAIL', 'cache-control'); | ||
} | ||
}; | ||
*/ | ||
var received = { | ||
success: false, | ||
status: false, | ||
entity: false, | ||
headers: false | ||
}; | ||
var onHeadersReceived = function(lastTry) { | ||
if(cb === null) { | ||
if(!cb) { | ||
return; | ||
} | ||
if(statusCb === null) { | ||
return; | ||
try { | ||
if(xhr.status) { | ||
received.status = true; | ||
} | ||
} catch(_) { | ||
} | ||
var status; | ||
if(!crossDomain || httpinvoke.corsStatus) { | ||
try { | ||
if(typeof xhr.status === 'undefined' || xhr.status === 0) { | ||
return; | ||
} | ||
status = xhr.status; | ||
} catch(_) { | ||
return; | ||
try { | ||
if(xhr.statusText) { | ||
received.status = true; | ||
} | ||
// sometimes IE returns 1223 when it should be 204 | ||
if(status === 1223) { | ||
status = 204; | ||
} catch(_) { | ||
} | ||
try { | ||
if(xhr.responseText) { | ||
received.entity = true; | ||
} | ||
} catch(_) { | ||
} | ||
try { | ||
if(xhr.response) { | ||
received.entity = true; | ||
} | ||
} catch(_) { | ||
} | ||
outputHeaders = {}; | ||
if(crossDomain) { | ||
if(httpinvoke.corsResponseContentTypeOnly) { | ||
if(typeof xhr.contentType === 'string') { | ||
if(!statusCb) { | ||
return; | ||
} | ||
if(received.status || received.entity || received.success || lastTry) { | ||
if(typeof xhr.contentType === 'string') { | ||
if(xhr.contentType !== 'text/html' || xhr.responseText !== '') { | ||
// When no entity body and/or no Content-Type header is sent, | ||
// XDomainRequest on IE-8 defaults to text/html xhr.contentType. | ||
// Also, empty string is not a valid 'text/html' entity. | ||
outputHeaders['content-type'] = xhr.contentType; | ||
received.headers = true; | ||
} | ||
} else { | ||
for(var i = 0; i < exposedHeaders.length; i += 1) { | ||
try { | ||
var header = xhr.getResponseHeader(exposedHeaders[i]); | ||
if(header !== null) { | ||
outputHeaders[exposedHeaders[i].toLowerCase()] = header; | ||
} | ||
} catch(err) { | ||
if(!lastTry) { | ||
return; | ||
} | ||
} | ||
for(var i = 0; i < exposedHeaders.length; i += 1) { | ||
var header; | ||
try { | ||
if(header = xhr.getResponseHeader(exposedHeaders[i])) { | ||
outputHeaders[exposedHeaders[i].toLowerCase()] = header; | ||
received.headers = true; | ||
} | ||
} catch(err) { | ||
} | ||
} | ||
} else { | ||
try { | ||
fillOutputHeaders(xhr, outputHeaders); | ||
} catch(_) { | ||
if(!lastTry) { | ||
return; | ||
// note - on Opera 11.10 and 11.50 calling getAllResponseHeaders may introduce side effects on xhr and responses will timeout when server responds with some HTTP status codes | ||
if(fillOutputHeaders(xhr, outputHeaders)) { | ||
received.headers = true; | ||
} | ||
} catch(err) { | ||
} | ||
if(!status && (!crossDomain || httpinvoke.corsStatus)) { | ||
// Sometimes on IE 9 accessing .status throws an error, but .statusText does not. | ||
try { | ||
if(xhr.status) { | ||
status = xhr.status; | ||
} | ||
} catch(_) { | ||
} | ||
if(!status) { | ||
try { | ||
status = statusTextToCode[xhr.statusText]; | ||
} catch(_) { | ||
} | ||
} | ||
// sometimes IE returns 1223 when it should be 204 | ||
if(status === 1223) { | ||
status = 204; | ||
} | ||
} | ||
} | ||
if(!uploadProgressCbCalled) { | ||
uploadProgressCbCalled = true; | ||
uploadProgressCb(0, inputLength); | ||
if(!lastTry && !(received.status && received.headers)) { | ||
return; | ||
} | ||
uploadProgressCb(inputLength, inputLength); | ||
if(cb === null) { | ||
uploadProgress(inputLength); | ||
if(!cb) { | ||
return; | ||
@@ -522,62 +707,80 @@ } | ||
statusCb = null; | ||
if(cb === null) { | ||
if(!cb) { | ||
return; | ||
} | ||
// BEGIN COMMON | ||
if(typeof outputHeaders['content-length'] !== 'undefined') { | ||
if(method === 'HEAD') { | ||
return noData(); | ||
} | ||
updateDownload(0); | ||
if(!cb) { | ||
return; | ||
} | ||
if('content-length' in outputHeaders) { | ||
initDownload(Number(outputHeaders['content-length'])); | ||
if(cb === null) { | ||
if(!cb) { | ||
return; | ||
} | ||
} | ||
if(method === 'HEAD' || typeof outputHeaders['content-type'] === 'undefined' || outputLength === 0) { | ||
return noData(); | ||
} | ||
updateDownload(0); | ||
// END COMMON | ||
}; | ||
var onLoad = function() { | ||
if(cb === null) { | ||
if(!cb) { | ||
return; | ||
} | ||
if((!crossDomain || httpinvoke.corsStatus) && xhr.status === 0) { | ||
cb(new Error('"some type" of network error')); | ||
cb = null; | ||
onHeadersReceived(true); | ||
if(!cb) { | ||
return; | ||
} | ||
onHeadersReceived(true); | ||
if(cb === null) { | ||
if(!received.success && !status) { | ||
// 'finished in onerror and status code is undefined' | ||
cb(new Error('download error')); | ||
cb = null; | ||
return; | ||
} | ||
if(typeof xhr.response !== 'undefined' && xhr.response === null) { | ||
return noData(); | ||
} | ||
var length; | ||
try { | ||
initDownload(getOutputLength[outputType](xhr)); | ||
if(cb === null) { | ||
return; | ||
} | ||
length = getOutputLength(xhr); | ||
} catch(_) { | ||
return noData(); | ||
} | ||
if(outputLength === 0) { | ||
return noData(); | ||
if(!outputLength) { | ||
initDownload(length); | ||
} else if(length !== outputLength) { | ||
// 'output length ' + outputLength + ' is not equal to actually received entity length ' + length | ||
cb(new Error('download error')); | ||
cb = null; | ||
} | ||
if(!cb) { | ||
return; | ||
} | ||
output = getOutput[outputType](xhr); | ||
updateDownload(outputLength); | ||
if(cb === null) { | ||
if(!cb) { | ||
return; | ||
} | ||
cb(null, output); | ||
try { | ||
cb(null, !received.entity && outputLength === 0 && typeof outputHeaders['content-type'] === 'undefined' ? _undefined : outputConverter(getOutput(xhr)), status, outputHeaders); | ||
} catch(err) { | ||
cb(err); | ||
} | ||
cb = null; | ||
}; | ||
var onloadBound = false; | ||
if(typeof xhr.onload !== 'undefined') { | ||
onloadBound = true; | ||
xhr.onload = function() { | ||
received.success = true; | ||
//dbg('onload'); | ||
onLoad(); | ||
}; | ||
} | ||
if(typeof xhr.onreadystatechange !== 'undefined') { | ||
xhr.onreadystatechange = function() { | ||
//dbg('onreadystatechange ' + xhr.readyState); | ||
if(xhr.readyState === 2) { | ||
@@ -588,11 +791,16 @@ // HEADERS_RECEIVED | ||
// LOADING | ||
received.success = true; | ||
onHeadersReceived(false); | ||
if(statusCb !== null) { | ||
if(statusCb) { | ||
return; | ||
} | ||
try { | ||
updateDownload(getOutputLength[outputType](xhr)); | ||
updateDownload(getOutputLength(xhr)); | ||
} catch(err) { | ||
} | ||
} else if(xhr.readyState === 4) { | ||
// Instead of 'typeof xhr.onload === "undefined"', we must use | ||
// onloadBound variable, because otherwise Firefox 3.5 synchronously | ||
// throws a "Permission denied for <> to create wrapper for | ||
// object of class UnnamedClass" error | ||
} else if(xhr.readyState === 4 && !onloadBound) { | ||
// DONE | ||
@@ -603,3 +811,2 @@ onLoad(); | ||
} | ||
xhr.onload = onLoad; | ||
@@ -610,3 +817,7 @@ /*************** set XHR request headers **************/ | ||
if(inputHeaders.hasOwnProperty(inputHeaderName)) { | ||
xhr.setRequestHeader(inputHeaderName, inputHeaders[inputHeaderName]); | ||
try { | ||
xhr.setRequestHeader(inputHeaderName, inputHeaders[inputHeaderName]); | ||
} catch(err) { | ||
return failWithoutRequest(cb, err); | ||
} | ||
} | ||
@@ -616,44 +827,52 @@ } | ||
/*************** invoke XHR request process **************/ | ||
setTimeout(function() { | ||
if(cb === null) { | ||
nextTick(function() { | ||
if(!cb) { | ||
return; | ||
} | ||
try { | ||
xhr.responseType = outputType === 'bytearray' ? 'arraybuffer' : 'text'; | ||
} catch(err) { | ||
} | ||
try { | ||
// must override mime type before receiving headers - at least for Safari 5.0.4 | ||
if(outputType === 'bytearray') { | ||
xhr.overrideMimeType('text/plain; charset=x-user-defined'); | ||
} else if(outputType === 'text') { | ||
if(outputHeaders['content-type'].substr(0, 'text/'.length) !== 'text/') { | ||
xhr.overrideMimeType('text/plain'); | ||
if('response' in xhr) { | ||
try { | ||
xhr.responseType = outputBinary ? 'arraybuffer' : 'text'; | ||
} catch(err) { | ||
} | ||
} else { | ||
try { | ||
// mime type override must be done before receiving headers - at least for Safari 5.0.4 | ||
if(outputBinary) { | ||
xhr.overrideMimeType('text/plain; charset=x-user-defined'); | ||
} | ||
} catch(err) { | ||
} | ||
} catch(err) { | ||
} | ||
// Content-Length header is set automatically | ||
if(inputType === 'bytearray') { | ||
var triedSendArrayBuffer = typeof ArrayBuffer === 'undefined'; | ||
var triedSendBlob = typeof Blob === 'undefined'; | ||
if(typeof input === 'object') { | ||
var triedSendArrayBufferView = false; | ||
var triedSendBlob = false; | ||
var triedSendBinaryString = false; | ||
var BlobBuilder = window.BlobBuilder || window.WebKitBlobBuilder || window.MozBlobBuilder || window.MSBlobBuilder; | ||
if(Object.prototype.toString.call(options.input) === '[object Array]') { | ||
if(typeof Uint8Array === 'undefined') { | ||
options.input = convertByteArrayToBinaryString(options.input); | ||
} else { | ||
options.input = new Uint8Array(options.input).buffer; | ||
var BlobBuilder = global.BlobBuilder || global.WebKitBlobBuilder || global.MozBlobBuilder || global.MSBlobBuilder; | ||
if(isArray(input)) { | ||
input = global.Uint8Array ? new Uint8Array(input) : String.fromCharCode.apply(String, input); | ||
} | ||
var toBlob = BlobBuilder ? function() { | ||
var bb = new BlobBuilder(); | ||
bb.append(input); | ||
input = bb.getBlob(inputHeaders['Content-Type']); | ||
} : function() { | ||
try { | ||
input = new Blob([input], { | ||
type: inputHeaders['Content-Type'] | ||
}); | ||
} catch(_) { | ||
triedSendBlob = true; | ||
} | ||
} | ||
}; | ||
var go = function() { | ||
if(triedSendBlob && triedSendArrayBuffer && triedSendBinaryString) { | ||
var reader; | ||
if(triedSendBlob && triedSendArrayBufferView && triedSendBinaryString) { | ||
return failWithoutRequest(cb, new Error('Unable to send')); | ||
} | ||
if(typeof ArrayBuffer !== 'undefined' && options.input instanceof ArrayBuffer) { | ||
if(triedSendArrayBuffer) { | ||
if(isArrayBufferView(input)) { | ||
if(triedSendArrayBufferView) { | ||
if(!triedSendBinaryString) { | ||
try { | ||
options.input = convertByteArrayToBinaryString(new Uint8Array(options.input)); | ||
input = String.fromCharCode.apply(String, input); | ||
} catch(_) { | ||
@@ -663,46 +882,39 @@ triedSendBinaryString = true; | ||
} else if(!triedSendBlob) { | ||
if(typeof BlobBuilder === 'undefined') { | ||
try { | ||
options.input = new Blob([options.input], { | ||
type: inputHeaders['Content-Type'] | ||
}); | ||
} catch(_) { | ||
triedSendBlob = true; | ||
} | ||
} else { | ||
var bb = new BlobBuilder(); | ||
bb.append(options.input); | ||
options.input = bb.getBlob(inputHeaders['Content-Type']); | ||
} | ||
toBlob(); | ||
} | ||
} else { | ||
try { | ||
xhr.send(options.input); | ||
outputLength = options.input.byteLength; | ||
inputLength = input.byteLength; | ||
// if there is ArrayBufferView, then the browser supports sending instances of subclasses of ArayBufferView, otherwise we must send an ArrayBuffer | ||
xhr.send(global.ArrayBufferView ? input : bufferSlice(input.buffer, input.byteOffset, input.byteOffset + input.byteLength)); | ||
return; | ||
} catch(_) { | ||
triedSendArrayBuffer = true; | ||
triedSendArrayBufferView = true; | ||
} | ||
} | ||
} else if(typeof Blob !== 'undefined' && options.input instanceof Blob) { | ||
} else if(global.Blob && input instanceof Blob) { | ||
if(triedSendBlob) { | ||
if(!triedSendArrayBuffer) { | ||
if(!triedSendArrayBufferView) { | ||
try { | ||
var reader = new FileReader(); | ||
reader = new FileReader(); | ||
reader.onerror = function() { | ||
triedSendArrayBuffer = true; | ||
triedSendArrayBufferView = true; | ||
go(); | ||
}; | ||
reader.onload = function() { | ||
options.input = reader.result; | ||
try { | ||
input = new Uint8Array(reader.result); | ||
} catch(_) { | ||
triedSendArrayBufferView = true; | ||
} | ||
go(); | ||
}; | ||
reader.readAsArrayBuffer(options.input); | ||
reader.readAsArrayBuffer(input); | ||
return; | ||
} catch(_) { | ||
triedSendArrayBuffer = true; | ||
triedSendArrayBufferView = true; | ||
} | ||
} else if(!triedSendBinaryString) { | ||
try { | ||
var reader = new FileReader(); | ||
reader = new FileReader(); | ||
reader.onerror = function() { | ||
@@ -713,6 +925,6 @@ triedSendBinaryString = true; | ||
reader.onload = function() { | ||
options.input = reader.result; | ||
input = reader.result; | ||
go(); | ||
}; | ||
reader.readAsBinaryString(options.input); | ||
reader.readAsBinaryString(input); | ||
return; | ||
@@ -725,4 +937,4 @@ } catch(_) { | ||
try { | ||
xhr.send(options.input); | ||
outputLength = options.input.size; | ||
inputLength = input.size; | ||
xhr.send(input); | ||
return; | ||
@@ -735,32 +947,15 @@ } catch(_) { | ||
if(triedSendBinaryString) { | ||
if(!triedSendArrayBuffer) { | ||
if(!triedSendArrayBufferView) { | ||
try { | ||
var a = new ArrayBuffer(options.input.length); | ||
var b = new Uint8Array(a); | ||
for(var i = 0; i < options.input.length; i += 1) { | ||
b[i] = options.input[i] & 0xFF; | ||
} | ||
options.input = a; | ||
input = binaryStringToByteArray(input); | ||
} catch(_) { | ||
triedSendArrayBuffer = true; | ||
triedSendArrayBufferView = true; | ||
} | ||
} else if(!triedSendBlob) { | ||
if(typeof BlobBuilder === 'undefined') { | ||
try { | ||
options.input = new Blob([options.input], { | ||
type: inputHeaders['Content-Type'] | ||
}); | ||
} catch(_) { | ||
triedSendBlob = true; | ||
} | ||
} else { | ||
var bb = new BlobBuilder(); | ||
bb.append(options.input); | ||
options.input = bb.getBlob(inputHeaders['Content-Type']); | ||
} | ||
toBlob(); | ||
} | ||
} else { | ||
try { | ||
xhr.sendAsBinary(options.input); | ||
outputLength = options.input.length; | ||
inputLength = input.length; | ||
xhr.sendAsBinary(input); | ||
return; | ||
@@ -772,20 +967,25 @@ } catch(_) { | ||
} | ||
setTimeout(go, 0); | ||
nextTick(go); | ||
}; | ||
go(); | ||
} else if(inputType === 'text') { | ||
inputLength = countStringBytes(options.input); | ||
xhr.send(options.input); | ||
} else { | ||
xhr.send(null); | ||
try { | ||
if(typeof input === 'string') { | ||
inputLength = countStringBytes(input); | ||
xhr.send(input); | ||
} else { | ||
xhr.send(null); | ||
} | ||
} catch(err) { | ||
var _cb = cb; | ||
cb = null; | ||
return failWithoutRequest(cb, new Error('Unable to send')); | ||
} | ||
} | ||
if(!uploadProgressCbCalled) { | ||
uploadProgressCbCalled = true; | ||
uploadProgressCb(0, inputLength); | ||
} | ||
}, 0); | ||
uploadProgress(0); | ||
}); | ||
/*************** return "abort" function **************/ | ||
return function() { | ||
if(cb === null) { | ||
promise = function() { | ||
if(!cb) { | ||
return; | ||
@@ -804,2 +1004,3 @@ } | ||
}; | ||
return mixInPromise(promise); | ||
}; | ||
@@ -819,3 +1020,3 @@ httpinvoke.corsResponseContentTypeOnly = false; | ||
createXHR = function() { | ||
return new window.XMLHttpRequest(); | ||
return new global.XMLHttpRequest(); | ||
}; | ||
@@ -825,12 +1026,11 @@ var tmpxhr = createXHR(); | ||
httpinvoke.cors = 'withCredentials' in tmpxhr; | ||
if(!httpinvoke.cors) { | ||
throw ''; | ||
if(httpinvoke.cors) { | ||
httpinvoke.corsRequestHeaders = true; | ||
httpinvoke.corsCredentials = true; | ||
httpinvoke.corsDELETE = true; | ||
httpinvoke.corsPUT = true; | ||
httpinvoke.corsHEAD = true; | ||
httpinvoke.corsStatus = true; | ||
return; | ||
} | ||
httpinvoke.corsRequestHeaders = true; | ||
httpinvoke.corsCredentials = true; | ||
httpinvoke.corsDELETE = true; | ||
httpinvoke.corsPUT = true; | ||
httpinvoke.corsHEAD = true; | ||
httpinvoke.corsStatus = true; | ||
return; | ||
} catch(err) { | ||
@@ -841,3 +1041,3 @@ } | ||
createXHR = function() { | ||
return new window.XMLHttpRequest(); | ||
return new global.XMLHttpRequest(); | ||
}; | ||
@@ -847,3 +1047,3 @@ createXHR(); | ||
createXHR = function(cors) { | ||
return cors ? new window.XDomainRequest() : new window.XMLHttpRequest(); | ||
return cors ? new global.XDomainRequest() : new global.XMLHttpRequest(); | ||
}; | ||
@@ -868,5 +1068,6 @@ createXHR(true); | ||
createXHR = function() { | ||
return new window.ActiveXObject(candidates[i]); | ||
return new global.ActiveXObject(candidates[i]); | ||
}; | ||
createXHR(); | ||
httpinvoke.requestTextOnly = true; | ||
return; | ||
@@ -877,5 +1078,3 @@ } catch(err) { | ||
} | ||
createXHR = function() { | ||
throw new Error('Cannot construct XMLHttpRequest'); | ||
}; | ||
createXHR = _undefined; | ||
})(); | ||
@@ -882,0 +1081,0 @@ |
var http = require('http'); | ||
var url = require('url'); | ||
var noop = function() {}; | ||
;;var isArrayBufferView = function(input) { | ||
return typeof input === 'object' && input !== null && ( | ||
(global.ArrayBufferView && input instanceof ArrayBufferView) || | ||
(global.Int8Array && input instanceof Int8Array) || | ||
(global.Uint8Array && input instanceof Uint8Array) || | ||
(global.Uint8ClampedArray && input instanceof Uint8ClampedArray) || | ||
(global.Int16Array && input instanceof Int16Array) || | ||
(global.Uint16Array && input instanceof Uint16Array) || | ||
(global.Int32Array && input instanceof Int32Array) || | ||
(global.Uint32Array && input instanceof Uint32Array) || | ||
(global.Float32Array && input instanceof Float32Array) || | ||
(global.Float64Array && input instanceof Float64Array) | ||
); | ||
}; | ||
var isArray = function(object) { | ||
return Object.prototype.toString.call(object) === '[object Array]'; | ||
}; | ||
var isByteArray = function(input) { | ||
return typeof input === 'object' && input !== null && ( | ||
(global.Buffer && input instanceof Buffer) || | ||
(global.Blob && input instanceof Blob) || | ||
(global.File && input instanceof File) || | ||
(global.ArrayBuffer && input instanceof ArrayBuffer) || | ||
isArrayBufferView(input) || | ||
isArray(input) | ||
); | ||
}; | ||
var bytearrayMessage = 'an instance of Buffer, nor Blob, nor File, nor ArrayBuffer, nor ArrayBufferView, nor Int8Array, nor Uint8Array, nor Uint8ClampedArray, nor Int16Array, nor Uint16Array, nor Int32Array, nor Uint32Array, nor Float32Array, nor Float64Array, nor Array'; | ||
var supportedMethods = ['GET', 'HEAD', 'POST', 'PUT', 'DELETE']; | ||
var failWithoutRequest = function(cb, err) { | ||
process.nextTick(function() { | ||
if(cb === null) { | ||
return; | ||
} | ||
cb(err); | ||
}); | ||
return noop; | ||
}; | ||
var indexOf = function(array, item) { | ||
var indexOf = [].indexOf ? function(array, item) { | ||
return array.indexOf(item); | ||
}; | ||
var countStringBytes = function(string) { | ||
var n = 0; | ||
for(var i = 0; i < string.length; i += 1) { | ||
var c = string.charCodeAt(i); | ||
if (c < 128) { | ||
n += 1; | ||
} else if (c < 2048) { | ||
n += 2; | ||
} else { | ||
n += 3; | ||
} : function(array, item) { | ||
var i = -1; | ||
while(++i < array.length) { | ||
if(array[i] === item) { | ||
return i; | ||
} | ||
} | ||
return n; | ||
return -1; | ||
}; | ||
var convertByteArrayToBinaryString = function(bytearray) { | ||
var str = ''; | ||
for(var i = 0; i < bytearray.length; i += 1) { | ||
str += String.fromCharCode(bytearray[i]); | ||
} | ||
return str; | ||
var pass = function(value) { | ||
return value; | ||
}; | ||
var nextTick = (global.process && global.process.nextTick) || global.setImmediate || global.setTimeout; | ||
var _undefined; | ||
; | ||
// http://www.w3.org/TR/XMLHttpRequest/#the-setrequestheader()-method | ||
@@ -45,3 +59,3 @@ var forbiddenInputHeaders = ['accept-charset', 'accept-encoding', 'access-control-request-headers', 'access-control-request-method', 'connection', 'content-length', 'content-transfer-encoding', 'cookie', 'cookie2', 'date', 'dnt', 'expect', 'host', 'keep-alive', 'origin', 'referer', 'te', 'trailer', 'transfer-encoding', 'upgrade', 'user-agent', 'via']; | ||
var headerl = header.toLowerCase(); | ||
if(indexOf(forbiddenInputHeaders, headerl) >= 0) { | ||
if(forbiddenInputHeaders.indexOf(headerl) >= 0) { | ||
throw new Error('Input header ' + header + ' is forbidden to be set programmatically'); | ||
@@ -59,146 +73,264 @@ } | ||
var httpinvoke = function(uri, method, options) { | ||
/*************** COMMON initialize parameters **************/ | ||
if(typeof method === 'undefined') { | ||
var httpinvoke = function(uri, method, options, cb) { | ||
;var mixInPromise, promise, failWithoutRequest, uploadProgressCb, inputLength, noData, timeout, inputHeaders, corsOriginHeader, statusCb, initDownload, updateDownload, outputHeaders, exposedHeaders, status, outputBinary, input, outputLength, outputConverter; | ||
/*************** COMMON initialize parameters **************/ | ||
if(!method) { | ||
// 1 argument | ||
// method, options, cb skipped | ||
method = 'GET'; | ||
options = {}; | ||
} else if(!options) { | ||
// 2 arguments | ||
if(typeof method === 'string') { | ||
// options. cb skipped | ||
options = {}; | ||
} else if(typeof method === 'object') { | ||
// method, cb skipped | ||
options = method; | ||
method = 'GET'; | ||
options = {}; | ||
} else if(typeof options === 'undefined') { | ||
if(typeof method === 'string') { | ||
options = {}; | ||
} else { | ||
options = method; | ||
method = 'GET'; | ||
} | ||
} else { | ||
// method, options skipped | ||
options = { | ||
finished: method | ||
}; | ||
method = 'GET'; | ||
} | ||
options = typeof options === 'function' ? { | ||
finished: options | ||
} : options; | ||
var safeCallback = function(name) { | ||
if(typeof options[name] === 'undefined') { | ||
return noop; | ||
} | ||
return function() { | ||
} else if(!cb) { | ||
// 3 arguments | ||
if(typeof method === 'object') { | ||
// method skipped | ||
method.finished = options; | ||
options = method; | ||
method = 'GET'; | ||
} else if(typeof options === 'function') { | ||
// options skipped | ||
options = { | ||
finished: options | ||
}; | ||
} | ||
// cb skipped | ||
} else { | ||
// 4 arguments | ||
options.finished = cb; | ||
} | ||
var safeCallback = function(name, aspect) { | ||
if(name in options) { | ||
return function(a, b, c, d) { | ||
try { | ||
options[name].apply(null, arguments); | ||
options[name](a, b, c, d); | ||
} catch(_) { | ||
} | ||
aspect(a, b, c, d); | ||
}; | ||
} | ||
return aspect; | ||
}; | ||
mixInPromise = function(o) { | ||
var state = []; | ||
var chain = function(p, promise) { | ||
if(p && p.then) { | ||
p.then(promise.resolve.bind(promise), promise.reject.bind(promise), promise.notify.bind(promise)); | ||
} | ||
}; | ||
var uploadProgressCb = safeCallback('uploading'); | ||
var downloadProgressCb = safeCallback('downloading'); | ||
var statusCb = safeCallback('gotStatus'); | ||
var cb = safeCallback('finished'); | ||
var timeout = options.timeout || 0; | ||
var input, inputLength, inputHeaders = options.headers || {}; | ||
var inputType, inputIsArray; | ||
var outputType = options.outputType || "text"; | ||
var exposedHeaders = options.corsHeaders || []; | ||
var corsOriginHeader = options.corsOriginHeader || 'X-Httpinvoke-Origin'; | ||
exposedHeaders.push.apply(exposedHeaders, ['Cache-Control', 'Content-Language', 'Content-Type', 'Expires', 'Last-Modified', 'Pragma']); | ||
var loop = function(value) { | ||
if(!isArray(state)) { | ||
return; | ||
} | ||
var name, after = function() { | ||
state = value; | ||
}; | ||
if(value instanceof Error) { | ||
name = 'onreject'; | ||
} else if(value.type) { | ||
name = 'onnotify'; | ||
after = pass; | ||
} else { | ||
name = 'onresolve'; | ||
} | ||
var p; | ||
for(var i = 0; i < state.length; i++) { | ||
try { | ||
p = state[i][name](value); | ||
if(after !== pass) { | ||
chain(p, state[i].promise); | ||
} | ||
} catch(_) { | ||
} | ||
} | ||
after(); | ||
}; | ||
o.then = function(onresolve, onreject, onnotify) { | ||
var promise = mixInPromise({}); | ||
if(isArray(state)) { | ||
// TODO see if the property names are minifed | ||
state.push({ | ||
onresolve: onresolve, | ||
onreject: onreject, | ||
onnotify: onnotify, | ||
promise: promise | ||
}); | ||
} else if(state instanceof Error) { | ||
nextTick(function() { | ||
chain(onreject(state), promise); | ||
}); | ||
} else { | ||
nextTick(function() { | ||
chain(onresolve(state), promise); | ||
}); | ||
} | ||
return promise; | ||
}; | ||
o.notify = loop; | ||
o.resolve = loop; | ||
o.reject = loop; | ||
return o; | ||
}; | ||
failWithoutRequest = function(cb, err) { | ||
nextTick(function() { | ||
if(cb === null) { | ||
return; | ||
} | ||
cb(err); | ||
}); | ||
promise = function() { | ||
}; | ||
return mixInPromise(promise); | ||
}; | ||
/*************** COMMON convert and validate parameters **************/ | ||
if(indexOf(supportedMethods, method) < 0) { | ||
return failWithoutRequest(cb, new Error('Unsupported method ' + method)); | ||
uploadProgressCb = safeCallback('uploading', function(current, total) { | ||
promise.notify({ | ||
type: 'upload', | ||
current: current, | ||
total: total | ||
}); | ||
}); | ||
var downloadProgressCb = safeCallback('downloading', function(current, total) { | ||
promise.notify({ | ||
type: 'download', | ||
current: current, | ||
total: total | ||
}); | ||
}); | ||
statusCb = safeCallback('gotStatus', function(statusCode, headers) { | ||
promise.notify({ | ||
type: 'headers', | ||
statusCode: statusCode, | ||
headers: headers | ||
}); | ||
}); | ||
cb = safeCallback('finished', function(err, body, statusCode, headers) { | ||
if(err) { | ||
return promise.reject(err); | ||
} | ||
if(outputType !== 'text' && outputType !== 'bytearray') { | ||
return failWithoutRequest(cb, new Error('Unsupported outputType ' + outputType)); | ||
} | ||
if(typeof options.input === 'undefined') { | ||
if(typeof options.inputType !== 'undefined') { | ||
return failWithoutRequest(cb, new Error('"input" is undefined, but inputType is defined')); | ||
promise.resolve({ | ||
body: body, | ||
statusCode: statusCode, | ||
headers: headers | ||
}); | ||
}); | ||
timeout = options.timeout || 0; | ||
var converters = options.converters || {}; | ||
var inputConverter; | ||
inputLength = 0; | ||
inputHeaders = options.headers || {}; | ||
outputHeaders = {}; | ||
exposedHeaders = options.corsExposedHeaders || []; | ||
exposedHeaders.push.apply(exposedHeaders, ['Cache-Control', 'Content-Language', 'Content-Type', 'Content-Length', 'Expires', 'Last-Modified', 'Pragma', 'Content-Range']); | ||
corsOriginHeader = options.corsOriginHeader || 'X-Httpinvoke-Origin'; | ||
/*************** COMMON convert and validate parameters **************/ | ||
if(indexOf(supportedMethods, method) < 0) { | ||
return failWithoutRequest(cb, new Error('Unsupported method ' + method)); | ||
} | ||
outputBinary = options.outputType === 'bytearray'; | ||
if(!options.outputType || options.outputType === 'text' || outputBinary) { | ||
outputConverter = pass; | ||
} else if(converters['text ' + options.outputType]) { | ||
outputConverter = converters['text ' + options.outputType]; | ||
outputBinary = false; | ||
} else if(converters['bytearray ' + options.outputType]) { | ||
outputConverter = converters['bytearray ' + options.outputType]; | ||
outputBinary = true; | ||
} else { | ||
return failWithoutRequest(cb, new Error('Unsupported outputType ' + options.outputType)); | ||
} | ||
inputConverter = pass; | ||
if('input' in options) { | ||
input = options.input; | ||
if(!options.inputType || options.inputType === 'auto') { | ||
if(typeof input !== 'string' && !isByteArray(input)) { | ||
return failWithoutRequest(cb, new Error('inputType is undefined or auto and input is neither string, nor ' + bytearrayMessage)); | ||
} | ||
if(typeof inputHeaders['Content-Type'] !== 'undefined') { | ||
return failWithoutRequest(cb, new Error('"input" is undefined, but Content-Type request header is defined')); | ||
} else if(options.inputType === 'text') { | ||
if(typeof input !== 'string') { | ||
return failWithoutRequest(cb, new Error('inputType is text, but input is not a string')); | ||
} | ||
} else if (options.inputType === 'bytearray') { | ||
if(!isByteArray(input)) { | ||
return failWithoutRequest(cb, new Error('inputType is bytearray, but input is neither ' + bytearrayMessage)); | ||
} | ||
} else if(converters[options.inputType + ' text']) { | ||
inputConverter = converters[options.inputType + ' text']; | ||
} else if(converters[options.inputType + ' bytearray']) { | ||
inputConverter = converters[options.inputType + ' bytearray']; | ||
} else { | ||
if(typeof options.inputType === 'undefined') { | ||
inputType = 'auto'; | ||
} else { | ||
inputType = options.inputType; | ||
if(inputType !== 'auto' && inputType !== 'text' && inputType !== 'bytearray') { | ||
return failWithoutRequest(cb, new Error('Unsupported inputType ' + inputType)); | ||
} | ||
return failWithoutRequest(cb, new Error('There is no converter for specified inputType')); | ||
} | ||
if(typeof input === 'string') { | ||
if(!inputHeaders['Content-Type']) { | ||
inputHeaders['Content-Type'] = 'text/plain; charset=UTF-8'; | ||
} | ||
if(inputType === 'auto') { | ||
if(typeof inputHeaders['Content-Type'] === 'undefined') { | ||
return failWithoutRequest(cb, new Error('inputType is auto and Content-Type request header is not specified')); | ||
} | ||
if(typeof inputHeaders['Content-Type'] === 'undefined') { | ||
inputType = 'bytearray'; | ||
} else if(inputHeaders['Content-Type'].substr(0, 'text/'.length) === 'text/') { | ||
inputType = 'text'; | ||
} else { | ||
inputType = 'bytearray'; | ||
} | ||
} else { | ||
if(!inputHeaders['Content-Type']) { | ||
inputHeaders['Content-Type'] = 'application/octet-stream'; | ||
} | ||
if(inputType === 'text') { | ||
if(typeof options.input !== 'string') { | ||
return failWithoutRequest(cb, new Error('inputType is text, but input is not a string')); | ||
} | ||
input = options.input; | ||
inputLength = countStringBytes(input); | ||
if(typeof inputHeaders['Content-Type'] === 'undefined') { | ||
inputHeaders['Content-Type'] = 'text/plain; charset=UTF-8'; | ||
} | ||
} else if(inputType === 'bytearray') { | ||
if(typeof options.input !== 'object' || options.input === null) { | ||
return failWithoutRequest(cb, new Error('inputType is bytearray, but input is not a non-null object')); | ||
} | ||
if(typeof inputHeaders['Content-Type'] === 'undefined') { | ||
inputHeaders['Content-Type'] = 'application/octet-stream'; | ||
} | ||
if(global.ArrayBuffer && input instanceof ArrayBuffer) { | ||
input = new Uint8Array(input); | ||
} else if(isArrayBufferView(input)) { | ||
input = new Uint8Array(input.buffer, input.byteOffset, input.byteLength); | ||
} | ||
} | ||
try { | ||
validateInputHeaders(inputHeaders); | ||
input = inputConverter(input); | ||
} catch(err) { | ||
return failWithoutRequest(cb, err); | ||
} | ||
/*************** COMMON initialize helper variables **************/ | ||
var downloaded, outputHeaders, outputLength; | ||
var initDownload = function(total) { | ||
if(typeof outputLength !== 'undefined') { | ||
return; | ||
} | ||
outputLength = total; | ||
} else { | ||
if(options.inputType) { | ||
return failWithoutRequest(cb, new Error('"input" is undefined, but inputType is defined')); | ||
} | ||
if(inputHeaders['Content-Type']) { | ||
return failWithoutRequest(cb, new Error('"input" is undefined, but Content-Type request header is defined')); | ||
} | ||
} | ||
downloadProgressCb(downloaded, outputLength); | ||
}; | ||
var updateDownload = function(value) { | ||
if(value === downloaded) { | ||
return; | ||
} | ||
downloaded = value; | ||
downloadProgressCb(downloaded, outputLength); | ||
}; | ||
var noData = function() { | ||
initDownload(0); | ||
if(cb === null) { | ||
return; | ||
} | ||
updateDownload(0); | ||
if(cb === null) { | ||
return; | ||
} | ||
cb(); | ||
/*************** COMMON initialize helper variables **************/ | ||
var downloaded; | ||
initDownload = function(total) { | ||
if(typeof outputLength === 'undefined') { | ||
downloadProgressCb(downloaded, outputLength = total); | ||
} | ||
}; | ||
updateDownload = function(value) { | ||
if(value !== downloaded) { | ||
downloadProgressCb(downloaded = value, outputLength); | ||
} | ||
}; | ||
noData = function() { | ||
initDownload(0); | ||
if(cb) { | ||
// TODO what happens if we try to call abort in cb? | ||
cb(null, _undefined, status, outputHeaders); | ||
cb = null; | ||
}; | ||
} | ||
}; | ||
; | ||
/*************** initialize helper variables **************/ | ||
if(inputType === 'bytearray') { | ||
if(options.input instanceof Buffer) { | ||
input = options.input; | ||
} else if(options.input instanceof ArrayBuffer) { | ||
input = new Buffer(new Uint8Array(options.input)); | ||
} else if(Object.prototype.toString.call(options.input) === '[object Array]') { | ||
input = new Buffer(options.input); | ||
} else { | ||
return failWithoutRequest(cb, new Error('inputType is bytearray, but input is neither a Buffer, nor Array, nor ArrayBuffer')); | ||
} | ||
inputLength = input.length; | ||
try { | ||
validateInputHeaders(inputHeaders); | ||
} catch(err) { | ||
return failWithoutRequest(cb, err); | ||
} | ||
var ignoringlyConsume = function(res) { | ||
res.on('data', noop); | ||
res.on('end', noop); | ||
var ignorantlyConsume = function(res) { | ||
res.on('data', pass); | ||
res.on('end', pass); | ||
}; | ||
@@ -220,16 +352,17 @@ uri = url.parse(uri); | ||
if(cb === null) { | ||
ignoringlyConsume(res); | ||
ignorantlyConsume(res); | ||
return; | ||
} | ||
outputHeaders = res.headers; | ||
status = res.statusCode; | ||
uploadProgressCb(inputLength, inputLength); | ||
if(cb === null) { | ||
ignoringlyConsume(res); | ||
ignorantlyConsume(res); | ||
return; | ||
} | ||
statusCb(res.statusCode, outputHeaders); | ||
statusCb(status, outputHeaders); | ||
if(cb === null) { | ||
ignoringlyConsume(res); | ||
ignorantlyConsume(res); | ||
return; | ||
@@ -239,17 +372,17 @@ } | ||
updateDownload(0); | ||
// BEGIN COMMON | ||
if(typeof outputHeaders['content-length'] === 'string') { | ||
initDownload(Number(outputHeaders['content-length'])); | ||
} else { | ||
initDownload(); | ||
} | ||
if(cb === null) { | ||
ignoringlyConsume(res); | ||
ignorantlyConsume(res); | ||
return; | ||
} | ||
if(method === 'HEAD' || typeof outputHeaders['content-type'] === 'undefined' || outputLength === 0) { | ||
ignoringlyConsume(res); | ||
if(typeof outputHeaders['content-length'] !== 'undefined') { | ||
initDownload(Number(outputHeaders['content-length'])); | ||
if(cb === null) { | ||
ignorantlyConsume(res); | ||
return; | ||
} | ||
} | ||
if(method === 'HEAD' || typeof outputHeaders['content-type'] === 'undefined') { | ||
ignorantlyConsume(res); | ||
return noData(); | ||
} | ||
// END COMMON | ||
@@ -280,28 +413,12 @@ var output = [], downloaded = 0; | ||
} | ||
if(outputLength === 0) { | ||
return noData(); | ||
} | ||
if(outputType === 'auto') { | ||
if(outputHeaders['content-type'] === 'application/json') { | ||
outputType = 'json'; | ||
} else if(outputHeaders['content-type'].substr(0, 'text/'.length) === 'text/') { | ||
outputType = 'text'; | ||
} else { | ||
outputType = 'bytearray'; | ||
} | ||
} | ||
output = Buffer.concat(output, downloaded); | ||
if(outputType === 'bytearray') { | ||
cb(null, output); | ||
} else if(outputType === 'text') { | ||
cb(null, output.toString('utf8')); | ||
} else if(outputType === 'json') { | ||
try { | ||
cb(null, JSON.parse(output.toString('utf8'))); | ||
} catch(err) { | ||
cb(err); | ||
} | ||
if(!outputBinary) { | ||
output = output.toString('utf8'); | ||
} | ||
try { | ||
cb(null, outputConverter(output), status, outputHeaders); | ||
} catch(err) { | ||
cb(err); | ||
} | ||
cb = null; | ||
@@ -311,3 +428,3 @@ }); | ||
process.nextTick(function() { | ||
nextTick(function() { | ||
if(cb === null) { | ||
@@ -318,6 +435,5 @@ return; | ||
}); | ||
if(typeof options.input !== 'undefined') { | ||
if(input instanceof Array) { | ||
input = new Buffer(input); | ||
} | ||
if(typeof input !== 'undefined') { | ||
input = new Buffer(input); | ||
inputLength = input.length; | ||
req.write(input); | ||
@@ -333,3 +449,3 @@ } | ||
req.end(); | ||
return function() { | ||
promise = function() { | ||
if(cb === null) { | ||
@@ -344,2 +460,3 @@ return; | ||
}; | ||
return mixInPromise(promise); | ||
}; | ||
@@ -346,0 +463,0 @@ httpinvoke.corsResponseContentTypeOnly = false; |
if(typeof window === 'undefined') { | ||
window = {}; | ||
} else { | ||
global = window; | ||
} | ||
if(typeof location === 'undefined') { | ||
@@ -12,6 +15,268 @@ location = {}; | ||
window._cfg = { | ||
proxyPath: '/dummyserver', | ||
dummyserverPort: 1337, | ||
dummyserverPortAlternative: 1338, | ||
host: location.hostname, | ||
port: location.port || (location.protocol === 'https:' ? 443 : 80), | ||
path: '/dummyserver/', | ||
port: Number(location.port) || (location.protocol === 'https:' ? 443 : 80), | ||
/* # Statuses from RFC 2616 | ||
* | ||
* ## Not tested statuses, with reasons | ||
* | ||
* 100 Continue: technical status, not semantic, irrelevant for practical use | ||
* 101 Switching Protocols: technical status, not semantic, irrelevant for practical use | ||
* 205 Reset Content: Roy T. Fielding: "The most common use of 205 is within custom HTTP systems, not browsers.": http://w3-org.9356.n7.nabble.com/p2-deprecating-205-Reset-Content-tp253298p253354.html | ||
* 401 Unauthorized: Chrome 29.0 throws an authorization dialog, when the mandatory header 'WWW-Authenticate' is set | ||
* 407 Proxy Authentication Required: not useful, Chrome 29.0 sends "some type" of network error | ||
* 411 Length Required: Content-Length is always sent by browsers | ||
* 417 Expectation Failed: the required header 'Expect' is not allowed by browsers | ||
* | ||
* ## Statuses that should work, but should not be used, with reasons | ||
* | ||
* ### 3XX with Location header set | ||
* | ||
* Causes various unexpected behaviors - browsers are supposed to redirect, | ||
* also Karma test runner proxy failure. | ||
* | ||
* ### 3XX without Location header set | ||
* | ||
* 301, 302, 303, 307 GET same-origin: | ||
* - IE 8.0 returns status 12150, which is WinInet error code ERROR_HTTP_HEADER_NOT_FOUND The requested header could not be located | ||
* | ||
* 301, 302, 303, 307 POST: | ||
* - fails ("write EPIPE" error) on node | ||
* | ||
* 300, 305 POST: | ||
* - Karma test runner proxy failure | ||
* | ||
* 305 GET: | ||
* - Opera 12.10 responseText: <HTML><HEAD><TITLE>Redirect to alternative proxy</TITLE></HEAD><BODY>The server tried to redirect Opera to the alternative proxy "". For security reasons this is no longer supported.<BR><BR><HR>Generated by Opera ©</BODY></HTML> | ||
* | ||
* 300 GET: | ||
* - Opera 12.10 times out | ||
* | ||
* 304 POST: | ||
* - By RFC2616 impossible | ||
* | ||
*/ | ||
// TODO PUT POST - verify headers and input on server | ||
// TODO HEAD POST PUT DELETE | ||
status: { | ||
// OK | ||
'200': { | ||
GET: [{ | ||
requestEntity: false, | ||
partialResponse: false, | ||
responseEntity: true | ||
}] | ||
}, | ||
// Created | ||
'201': { | ||
GET: [{ | ||
requestEntity: false, | ||
partialResponse: false, | ||
responseEntity: true | ||
}] | ||
}, | ||
// Accepted | ||
'202': { | ||
GET: [{ | ||
requestEntity: false, | ||
partialResponse: false, | ||
responseEntity: true | ||
}] | ||
}, | ||
// Non-Authoritative Information | ||
'203': { | ||
GET: [{ | ||
requestEntity: false, | ||
partialResponse: false, | ||
responseEntity: true | ||
}] | ||
}, | ||
// No Content | ||
'204': { | ||
GET: [{ | ||
requestEntity: false, | ||
partialResponse: false, | ||
responseEntity: false | ||
}] | ||
}, | ||
// Partial Content | ||
'206': { | ||
GET: [{ | ||
requestEntity: false, | ||
partialResponse: true, | ||
responseEntity: true | ||
}] | ||
}, | ||
// Not Modified | ||
'304': { | ||
GET: [{ | ||
ifModified: true, | ||
requestEntity: false, | ||
partialResponse: false, | ||
responseEntity: false | ||
}] | ||
}, | ||
// Bad Request | ||
'400': { | ||
GET: [{ | ||
requestEntity: false, | ||
partialResponse: false, | ||
responseEntity: false | ||
}] | ||
}, | ||
// Payment Required | ||
'402': { | ||
GET: [{ | ||
requestEntity: false, | ||
partialResponse: false, | ||
responseEntity: false | ||
}] | ||
}, | ||
// Forbidden | ||
'403': { | ||
GET: [{ | ||
requestEntity: false, | ||
partialResponse: false, | ||
responseEntity: false | ||
}] | ||
}, | ||
// Not Found | ||
'404': { | ||
GET: [{ | ||
requestEntity: false, | ||
partialResponse: false, | ||
responseEntity: false | ||
}] | ||
}, | ||
// Method Not Allowed | ||
'405': { | ||
GET: [{ | ||
requestEntity: false, | ||
partialResponse: false, | ||
responseEntity: false | ||
}] | ||
}, | ||
// Not Acceptable | ||
'406': { | ||
GET: [{ | ||
requestEntity: false, | ||
partialResponse: false, | ||
responseEntity: false | ||
}] | ||
}, | ||
// Request Timeout | ||
'408': { | ||
GET: [{ | ||
requestEntity: false, | ||
partialResponse: false, | ||
responseEntity: false | ||
}] | ||
}, | ||
// Conflict | ||
'409': { | ||
GET: [{ | ||
requestEntity: false, | ||
partialResponse: false, | ||
responseEntity: false | ||
}] | ||
}, | ||
// Gone | ||
'410': { | ||
GET: [{ | ||
requestEntity: false, | ||
partialResponse: false, | ||
responseEntity: false | ||
}] | ||
}, | ||
// Precondition Failed | ||
'412': { | ||
GET: [{ | ||
requestEntity: false, | ||
partialResponse: false, | ||
responseEntity: false | ||
}] | ||
}, | ||
// Request Entity Too Large | ||
'413': { | ||
GET: [{ | ||
requestEntity: false, | ||
partialResponse: false, | ||
responseEntity: false | ||
}] | ||
}, | ||
// Request-URI Too Long | ||
'414': { | ||
GET: [{ | ||
requestEntity: false, | ||
partialResponse: false, | ||
responseEntity: false | ||
}] | ||
}, | ||
// Unsupported Media Type | ||
'415': { | ||
GET: [{ | ||
requestEntity: false, | ||
partialResponse: false, | ||
responseEntity: false | ||
}] | ||
}, | ||
// Requested Range Not Satisfiable | ||
'416': { | ||
GET: [{ | ||
requestEntity: false, | ||
partialResponse: true, | ||
responseEntity: false | ||
}] | ||
}, | ||
// Internal Server Error | ||
'500': { | ||
GET: [{ | ||
requestEntity: false, | ||
partialResponse: false, | ||
responseEntity: false | ||
}] | ||
}, | ||
// Not Implemented | ||
'501': { | ||
GET: [{ | ||
requestEntity: false, | ||
partialResponse: false, | ||
responseEntity: false | ||
}] | ||
}, | ||
// Bad Gateway | ||
'502': { | ||
GET: [{ | ||
requestEntity: false, | ||
partialResponse: false, | ||
responseEntity: false | ||
}] | ||
}, | ||
// Service Unavailable | ||
'503': { | ||
GET: [{ | ||
requestEntity: false, | ||
partialResponse: false, | ||
responseEntity: false | ||
}] | ||
}, | ||
// Gateway Timeout | ||
'504': { | ||
GET: [{ | ||
requestEntity: false, | ||
partialResponse: false, | ||
responseEntity: false | ||
}] | ||
}, | ||
// HTTP Version Not Supported | ||
'505': { | ||
GET: [{ | ||
requestEntity: false, | ||
partialResponse: false, | ||
responseEntity: false | ||
}] | ||
} | ||
}, | ||
makeTextFinished: function(done) { | ||
@@ -113,3 +378,3 @@ var cfg = require('./dummyserver-config'); | ||
} | ||
for(i = 255; i > 0; i -= 1) { | ||
for(i = 255; i >= 0; i -= 1) { | ||
bytes.push(i); | ||
@@ -124,4 +389,4 @@ } | ||
// generated | ||
window._cfg.corsURL = 'http://' + window._cfg.host + ':' + window._cfg.dummyserverPort + '/'; | ||
window._cfg.url = 'http://' + window._cfg.host + ':' + window._cfg.port + window._cfg.path; | ||
window._cfg.corsURL = 'http://' + window._cfg.host + ':' + (window._cfg.port === window._cfg.dummyserverPort ? window._cfg.dummyserverPortAlternative : window._cfg.dummyserverPort) + '/'; | ||
window._cfg.url = 'http://' + window._cfg.host + ':' + window._cfg.port + (window._cfg.port === window._cfg.dummyserverPort ? '' : window._cfg.proxyPath) + '/'; | ||
@@ -138,1 +403,10 @@ window.require = function(module) { | ||
} | ||
if(!global.console) { | ||
global.console = { | ||
_log: [], | ||
log: function() { | ||
this._log.push([].slice.call(arguments)); | ||
} | ||
}; | ||
} |
@@ -6,3 +6,3 @@ // Karma configuration | ||
module.exports = function(config) { | ||
config.set({ | ||
var cfg = { | ||
@@ -21,2 +21,3 @@ // base path, that will be used to resolve files and exclude | ||
'karma-mocha-requireHack.js', | ||
'node_modules/es5-shim/es5-shim.js', | ||
'test/*.js' | ||
@@ -74,6 +75,7 @@ ], | ||
proxies: { | ||
'/dummyserver/': dummyserverCfg.url | ||
} | ||
}); | ||
}; | ||
cfg.proxies[dummyserverCfg.proxyPath + '/'] = dummyserverCfg.url; | ||
config.set(cfg); | ||
}; |
{ | ||
"name": "httpinvoke", | ||
"version": "0.0.10", | ||
"description": "HTTP client for JavaScript", | ||
"version": "1.0.0", | ||
"description": "a small, no-dependencies HTTP client library for browsers and Node.js with a promise-based or Node.js-style callback-based API to progress events, text and binary file upload and download, request and response headers, status code.", | ||
"keywords": [ | ||
@@ -25,7 +25,17 @@ "http", | ||
], | ||
"main": "./httpinvoke-generated-commonjs.js", | ||
"main": "./httpinvoke-commonjs.js", | ||
"private": false, | ||
"dependencies": {}, | ||
"devDependencies": { | ||
"mocha": "1.12.x", | ||
"mime": "1.2.x", | ||
"sizzle": "1.1.x", | ||
"JSON2": "0.1.x", | ||
"es5-shim": "2.1.x", | ||
"grunt": "0.4.x", | ||
"grunt-cli": "0.1.x", | ||
"grunt-contrib-uglify": "0.2.x", | ||
"grunt-contrib-concat": "0.3.x", | ||
"grunt-mocha-test": "0.7.x", | ||
"daemon": "1.1.x", | ||
"mocha": "1.13.x", | ||
"karma": "0.10.x", | ||
@@ -35,4 +45,5 @@ "karma-mocha": "0.1.x" | ||
"scripts": { | ||
"install": "make", | ||
"test": "make test" | ||
"test": "./node_modules/.bin/grunt;node ./dummyserver.js;./node_modules/.bin/grunt test;kill $(cat ./dummyserver.pid);rm ./dummyserver.pid", | ||
"test-browser": "./node_modules/.bin/grunt;node ./dummyserver.js;./node_modules/.bin/karma start;kill $(cat ./dummyserver.pid);rm ./dummyserver.pid", | ||
"test-node": "./node_modules/.bin/grunt;node ./dummyserver.js;./node_modules/.bin/mocha --watch;kill $(cat ./dummyserver.pid);rm ./dummyserver.pid" | ||
}, | ||
@@ -39,0 +50,0 @@ "repository": { |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Uses eval
Supply chain riskPackage uses dynamic code execution (e.g., eval()), which is a dangerous practice. This can prevent the code from running in certain environments and increases the risk that the code may contain exploits or malicious behavior.
Found 1 instance in 1 package
Install scripts
Supply chain riskInstall scripts are run when the package is installed. The majority of malware in npm is hidden in install scripts.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
299734
41
6834
1
212
0
13
3
7