Compare commits

...

17 Commits

Author SHA1 Message Date
John Hiesey ae48c2c71c 2.0.3 2016-01-08 22:31:45 +01:00
John Hiesey 570f85ec18 Check xhr.status is non-zero to avoid running onXHRProgress on error
Previously, onXHRProgress was being run when readyState was being
set to DONE on error, which was causing exceptions.
2015-10-13 15:46:01 -07:00
John Hiesey 85381f0e3d 2.0.2 2015-10-01 23:01:00 -07:00
John Hiesey 3bea2a493f Don't turn exceptions thrown in the callback into 'error' events
When using the fetch api, exceptions thrown inside
ClientRequest._onFinish, which also calls the user callback,
were being caught and turned into 'error' events. This is
not the correct behavior; these exceptions should remain
uncaught to let the browser print them to the console.
2015-10-01 22:26:59 -07:00
John Hiesey 016f4be96f 2.0.1 2015-09-30 13:23:16 -07:00
John Hiesey 9e5e75b5ff Merge pull request #27 from mbcrute/patch-body
Don't ignore PATCH request bodies
2015-09-30 13:10:09 -07:00
Matt Crute 031b8aab89 Stop ignoring PATCH request bodies 2015-09-30 11:01:26 -07:00
John Hiesey dc421eb355 2.0.0 2015-09-15 17:24:23 -07:00
John Hiesey 7d1dfd2ab1 Merge pull request #25 from jhiesey/polyfills
Require polyfills for IE8 support to reduce bundle size
2015-09-15 17:23:40 -07:00
John Hiesey 75817ba930 Fix tests 2015-09-15 16:54:17 -07:00
John Hiesey 2152af414a Remove implementations of ES5 functions not provided in ie8
From now on, IE8 is only supported if polyfills for `Object.keys`,
`Array.prototype.forEach`, and `Array.prototype.indexOf` are
provided. The suggested polyfills from MDN are provided in
ie8-polyfill.js, or you can use https://github.com/es-shims/es5-shim
for a more complete set of polyfills.
2015-09-15 16:54:17 -07:00
John Hiesey a10d19b3b2 Fix occasional test errors on Chrome 45
If multiple chunks of data got queued up, the abort on data test
would erroneously fail. Wait till the end of the tick before
considering 'data' events as errors.
2015-09-15 16:52:11 -07:00
John Hiesey a169930a20 1.7.1 2015-09-15 14:47:03 -07:00
John Hiesey 43d1322dd6 1.7.0 2015-09-15 00:22:13 -07:00
John Hiesey f72d22b2a9 Disable IE8 tests due to intermittent failures 2015-09-14 23:37:43 -07:00
John Hiesey bde26be949 Add WebWorker test using webworkify 2015-09-14 23:25:24 -07:00
John Hiesey 8cab16f26e Change window to global for WebWorker support 2015-09-14 18:18:53 -07:00
14 changed files with 277 additions and 53 deletions
+3 -2
View File
@@ -7,7 +7,7 @@ browsers:
- name: safari
version: 5..latest
- name: ie
version: 8..latest
version: 9..latest
- name: opera
version: 11..latest
- name: iphone
@@ -16,4 +16,5 @@ browsers:
version: 4.0..latest
server: ./test/server/index.js
scripts:
- "/polyfill.js"
- "/ie8-polyfill.js"
- "/test-polyfill.js"
+7
View File
@@ -29,6 +29,13 @@ All browsers newer than IE8 support binary responses. All of the above browsers
support true streaming or pseudo-streaming support that for binary data as well
except for IE10. Old (presto-based) Opera also does not support binary streaming either.
### IE8 note:
As of version 2.0.0, IE8 support requires the user to supply polyfills for
`Object.keys`, `Array.prototype.forEach`, and `Array.prototype.indexOf`. Example
implementations are provided in [ie8-polyfill.js](ie8-polyfill.js); alternately,
you may want to consider using [es5-shim](https://github.com/es-shims/es5-shim).
All browsers with full ES5 support shouldn't require any polyfills.
## How do you use it?
The intent is to have the same api as the client part of the
+168
View File
@@ -0,0 +1,168 @@
// These polyfills taken from MDN (developer.mozilla.org)
// From https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/keys
if (!Object.keys) {
Object.keys = (function() {
'use strict';
var hasOwnProperty = Object.prototype.hasOwnProperty,
hasDontEnumBug = !({ toString: null }).propertyIsEnumerable('toString'),
dontEnums = [
'toString',
'toLocaleString',
'valueOf',
'hasOwnProperty',
'isPrototypeOf',
'propertyIsEnumerable',
'constructor'
],
dontEnumsLength = dontEnums.length;
return function(obj) {
if (typeof obj !== 'object' && (typeof obj !== 'function' || obj === null)) {
throw new TypeError('Object.keys called on non-object');
}
var result = [], prop, i;
for (prop in obj) {
if (hasOwnProperty.call(obj, prop)) {
result.push(prop);
}
}
if (hasDontEnumBug) {
for (i = 0; i < dontEnumsLength; i++) {
if (hasOwnProperty.call(obj, dontEnums[i])) {
result.push(dontEnums[i]);
}
}
}
return result;
};
}());
}
// Production steps of ECMA-262, Edition 5, 15.4.4.18
// Reference: http://es5.github.io/#x15.4.4.18
if (!Array.prototype.forEach) {
Array.prototype.forEach = function(callback, thisArg) {
var T, k;
if (this == null) {
throw new TypeError(' this is null or not defined');
}
// 1. Let O be the result of calling ToObject passing the |this| value as the argument.
var O = Object(this);
// 2. Let lenValue be the result of calling the Get internal method of O with the argument "length".
// 3. Let len be ToUint32(lenValue).
var len = O.length >>> 0;
// 4. If IsCallable(callback) is false, throw a TypeError exception.
// See: http://es5.github.com/#x9.11
if (typeof callback !== "function") {
throw new TypeError(callback + ' is not a function');
}
// 5. If thisArg was supplied, let T be thisArg; else let T be undefined.
if (arguments.length > 1) {
T = thisArg;
}
// 6. Let k be 0
k = 0;
// 7. Repeat, while k < len
while (k < len) {
var kValue;
// a. Let Pk be ToString(k).
// This is implicit for LHS operands of the in operator
// b. Let kPresent be the result of calling the HasProperty internal method of O with argument Pk.
// This step can be combined with c
// c. If kPresent is true, then
if (k in O) {
// i. Let kValue be the result of calling the Get internal method of O with argument Pk.
kValue = O[k];
// ii. Call the Call internal method of callback with T as the this value and
// argument list containing kValue, k, and O.
callback.call(T, kValue, k, O);
}
// d. Increase k by 1.
k++;
}
// 8. return undefined
};
}
// Production steps of ECMA-262, Edition 5, 15.4.4.14
// Reference: http://es5.github.io/#x15.4.4.14
if (!Array.prototype.indexOf) {
Array.prototype.indexOf = function(searchElement, fromIndex) {
var k;
// 1. Let O be the result of calling ToObject passing
// the this value as the argument.
if (this == null) {
throw new TypeError('"this" is null or not defined');
}
var O = Object(this);
// 2. Let lenValue be the result of calling the Get
// internal method of O with the argument "length".
// 3. Let len be ToUint32(lenValue).
var len = O.length >>> 0;
// 4. If len is 0, return -1.
if (len === 0) {
return -1;
}
// 5. If argument fromIndex was passed let n be
// ToInteger(fromIndex); else let n be 0.
var n = +fromIndex || 0;
if (Math.abs(n) === Infinity) {
n = 0;
}
// 6. If n >= len, return -1.
if (n >= len) {
return -1;
}
// 7. If n >= 0, then Let k be n.
// 8. Else, n<0, Let k be len - abs(n).
// If k is less than 0, then let k be 0.
k = Math.max(n >= 0 ? n : len - Math.abs(n), 0);
// 9. Repeat, while k < len
while (k < len) {
// a. Let Pk be ToString(k).
// This is implicit for LHS operands of the in operator
// b. Let kPresent be the result of calling the
// HasProperty internal method of O with argument Pk.
// This step can be combined with c
// c. If kPresent is true, then
// i. Let elementK be the result of calling the Get
// internal method of O with the argument ToString(k).
// ii. Let same be the result of applying the
// Strict Equality Comparison Algorithm to
// searchElement and elementK.
// iii. If same is true, return k.
if (k in O && O[k] === searchElement) {
return k;
}
k++;
}
return -1;
};
}
+9 -7
View File
@@ -1,4 +1,4 @@
exports.fetch = isFunction(window.fetch) && isFunction(window.ReadableByteStream)
exports.fetch = isFunction(global.fetch) && isFunction(global.ReadableByteStream)
exports.blobConstructor = false
try {
@@ -6,8 +6,10 @@ try {
exports.blobConstructor = true
} catch (e) {}
var xhr = new window.XMLHttpRequest()
xhr.open('GET', '/')
var xhr = new global.XMLHttpRequest()
// If location.host is empty, e.g. if this page/worker was loaded
// from a Blob, then use example.com to avoid an error
xhr.open('GET', global.location.host ? '/' : 'https://example.com')
function checkTypeSupport (type) {
try {
@@ -17,10 +19,10 @@ function checkTypeSupport (type) {
return false
}
// For some strange reason, Safari 7.0 reports typeof window.ArrayBuffer === 'object'.
// For some strange reason, Safari 7.0 reports typeof global.ArrayBuffer === 'object'.
// Safari 7.1 appears to have fixed this bug.
var haveArrayBuffer = typeof window.ArrayBuffer !== 'undefined'
var haveSlice = haveArrayBuffer && isFunction(window.ArrayBuffer.prototype.slice)
var haveArrayBuffer = typeof global.ArrayBuffer !== 'undefined'
var haveSlice = haveArrayBuffer && isFunction(global.ArrayBuffer.prototype.slice)
exports.arraybuffer = haveArrayBuffer && checkTypeSupport('arraybuffer')
// These next two tests unavoidably show warnings in Chrome. Since fetch will always
@@ -29,7 +31,7 @@ exports.msstream = !exports.fetch && haveSlice && checkTypeSupport('ms-stream')
exports.mozchunkedarraybuffer = !exports.fetch && haveArrayBuffer &&
checkTypeSupport('moz-chunked-arraybuffer')
exports.overrideMimeType = isFunction(xhr.overrideMimeType)
exports.vbArray = isFunction(window.VBArray)
exports.vbArray = isFunction(global.VBArray)
function isFunction (value) {
return typeof value === 'function'
+14 -15
View File
@@ -1,9 +1,6 @@
// var Base64 = require('Base64')
var capability = require('./capability')
var foreach = require('foreach')
var indexOf = require('indexof')
var inherits = require('inherits')
var keys = require('object-keys')
var response = require('./response')
var stream = require('stream')
@@ -35,7 +32,7 @@ var ClientRequest = module.exports = function (opts) {
self._headers = {}
if (opts.auth)
self.setHeader('Authorization', 'Basic ' + new Buffer(opts.auth).toString('base64'))
foreach(keys(opts.headers), function (name) {
Object.keys(opts.headers).forEach(function (name) {
self.setHeader(name, opts.headers[name])
})
@@ -68,7 +65,7 @@ ClientRequest.prototype.setHeader = function (name, value) {
// This check is not necessary, but it prevents warnings from browsers about setting unsafe
// headers. To be honest I'm not entirely sure hiding these warnings is a good thing, but
// http-browserify did it, so I will too.
if (indexOf(unsafeHeaders, lowerName) !== -1)
if (unsafeHeaders.indexOf(lowerName) !== -1)
return
self._headers[lowerName] = {
@@ -96,9 +93,9 @@ ClientRequest.prototype._onFinish = function () {
var headersObj = self._headers
var body
if (opts.method === 'POST' || opts.method === 'PUT') {
if (opts.method === 'POST' || opts.method === 'PUT' || opts.method === 'PATCH') {
if (capability.blobConstructor) {
body = new window.Blob(self._body.map(function (buffer) {
body = new global.Blob(self._body.map(function (buffer) {
return buffer.toArrayBuffer()
}), {
type: (headersObj['content-type'] || {}).value || ''
@@ -110,11 +107,11 @@ ClientRequest.prototype._onFinish = function () {
}
if (self._mode === 'fetch') {
var headers = keys(headersObj).map(function (name) {
var headers = Object.keys(headersObj).map(function (name) {
return [headersObj[name].name, headersObj[name].value]
})
window.fetch(self._opts.url, {
global.fetch(self._opts.url, {
method: self._opts.method,
headers: headers,
body: body,
@@ -123,11 +120,11 @@ ClientRequest.prototype._onFinish = function () {
}).then(function (response) {
self._fetchResponse = response
self._connect()
}).then(undefined, function (reason) {
}, function (reason) {
self.emit('error', reason)
})
} else {
var xhr = self._xhr = new window.XMLHttpRequest()
var xhr = self._xhr = new global.XMLHttpRequest()
try {
xhr.open(self._opts.method, self._opts.url, true)
} catch (err) {
@@ -147,7 +144,7 @@ ClientRequest.prototype._onFinish = function () {
if (self._mode === 'text' && 'overrideMimeType' in xhr)
xhr.overrideMimeType('text/plain; charset=x-user-defined')
foreach(keys(headersObj), function (name) {
Object.keys(headersObj).forEach(function (name) {
xhr.setRequestHeader(headersObj[name].name, headersObj[name].value)
})
@@ -186,12 +183,14 @@ ClientRequest.prototype._onFinish = function () {
}
/**
* Checks if xhr.status is readable. Even though the spec says it should
* be available in readyState 3, accessing it throws an exception in IE8
* Checks if xhr.status is readable and non-zero, indicating no error.
* Even though the spec says it should be available in readyState 3,
* accessing it throws an exception in IE8
*/
function statusValid (xhr) {
try {
return (xhr.status !== null)
var status = xhr.status
return (status !== null && status !== 0)
} catch (e) {
return false
}
+3 -4
View File
@@ -1,5 +1,4 @@
var capability = require('./capability')
var foreach = require('foreach')
var inherits = require('inherits')
var stream = require('stream')
@@ -64,7 +63,7 @@ var IncomingMessage = exports.IncomingMessage = function (xhr, response, mode) {
self.statusCode = xhr.status
self.statusMessage = xhr.statusText
var headers = xhr.getAllResponseHeaders().split(/\r?\n/)
foreach(headers, function (header) {
headers.forEach(function (header) {
var matches = header.match(/^([^:]+):\s*(.*)/)
if (matches) {
var key = matches[1].toLowerCase()
@@ -107,7 +106,7 @@ IncomingMessage.prototype._onXHRProgress = function () {
break
try {
// This fails in IE8
response = new window.VBArray(xhr.responseBody).toArray()
response = new global.VBArray(xhr.responseBody).toArray()
} catch (e) {}
if (response !== null) {
self.push(new Buffer(response))
@@ -151,7 +150,7 @@ IncomingMessage.prototype._onXHRProgress = function () {
response = xhr.response
if (xhr.readyState !== rStates.LOADING)
break
var reader = new window.MSStreamReader()
var reader = new global.MSStreamReader()
reader.onprogress = function () {
if (reader.result.byteLength > self._pos) {
self.push(new Buffer(new Uint8Array(reader.result.slice(self._pos))))
+3 -5
View File
@@ -1,6 +1,6 @@
{
"name": "stream-http",
"version": "1.6.0",
"version": "2.0.3",
"description": "Streaming http in the browser",
"main": "index.js",
"repository": {
@@ -10,7 +10,7 @@
"scripts": {
"test": "npm run test-node && npm run test-browser",
"test-node": "tape test/node/*.js",
"test-browser": "zuul -- test/browser/*.js",
"test-browser": "zuul --no-coverage -- test/browser/*.js",
"test-browser-local": "zuul --local 8080 --no-coverage -- test/browser/*.js"
},
"author": "John Hiesey",
@@ -24,10 +24,7 @@
],
"dependencies": {
"builtin-status-codes": "^1.0.0",
"foreach": "^2.0.5",
"indexof": "0.0.1",
"inherits": "^2.0.1",
"object-keys": "^1.0.4",
"xtend": "^4.0.0"
},
"devDependencies": {
@@ -37,6 +34,7 @@
"express": "^4.13.0",
"tape": "^4.0.0",
"ua-parser-js": "^0.7.7",
"webworkify": "^1.0.2",
"zuul": "^3.1.0"
}
}
+10 -3
View File
@@ -30,18 +30,25 @@ test('abort on response', function (t) {
test('abort on data', function (t) {
var req = http.get('/browserify.png?copies=5', function (res) {
var firstData = true
var failOnData = false
res.on('end', function () {
t.fail('unexpected end')
})
res.on('data', function (data) {
if (firstData) {
if (failOnData)
t.fail('unexpected data')
else if (firstData) {
firstData = false
req.abort()
t.end()
} else {
t.fail('unexpected data')
process.nextTick(function () {
// Wait for any data that may have been queued
// in the stream before considering data events
// as errors
failOnData = true
})
}
})
})
+3 -13
View File
@@ -1,20 +1,10 @@
var Buffer = require('buffer').Buffer
var fs = require('fs')
var keys = require('object-keys')
var test = require('tape')
var UAParser = require('ua-parser-js')
var http = require('../..')
function indexOf (arr, item) {
var len = arr.length
for (var i = 0; i < len; i++) {
if (arr[i] === item)
return i
}
return -1
}
test('headers', function (t) {
http.get({
path: '/testHeaders?Response-Header=bar&Response-Header-2=BAR2',
@@ -29,10 +19,10 @@ test('headers', function (t) {
if (lowerKey.indexOf('test-') === 0)
rawHeaders.push(lowerKey, res.rawHeaders[i + 1])
}
var header1Pos = indexOf(rawHeaders, 'test-response-header')
var header1Pos = rawHeaders.indexOf('test-response-header')
t.ok(header1Pos >= 0, 'raw response header 1 present')
t.equal(rawHeaders[header1Pos + 1], 'bar', 'raw response header value 1')
var header2Pos = indexOf(rawHeaders, 'test-response-header-2')
var header2Pos = rawHeaders.indexOf('test-response-header-2')
t.ok(header2Pos >= 0, 'raw response header 2 present')
t.equal(rawHeaders[header2Pos + 1], 'BAR2', 'raw response header value 2')
t.equal(rawHeaders.length, 4, 'correct number of raw headers')
@@ -46,7 +36,7 @@ test('headers', function (t) {
var body = JSON.parse(Buffer.concat(buffers).toString())
t.equal(body['test-request-header'], 'foo', 'request header 1')
t.equal(body['test-request-header-2'], 'FOO2', 'request header 2')
t.equal(keys(body).length, 2, 'correct number of request headers')
t.equal(Object.keys(body).length, 2, 'correct number of request headers')
t.end()
})
+20
View File
@@ -0,0 +1,20 @@
var Buffer = require('buffer').Buffer
var http = require('../../..')
module.exports = function (self) {
self.addEventListener('message', function (ev) {
var url = ev.data
http.get(url, function (res) {
var buffers = []
res.on('end', function () {
self.postMessage(Buffer.concat(buffers).toArrayBuffer())
})
res.on('data', function (data) {
buffers.push(data)
})
})
})
}
+32
View File
@@ -0,0 +1,32 @@
var fs = require('fs')
var test = require('tape')
var UAParser = require('ua-parser-js')
var url = require('url')
var work = require('webworkify')
var browser = (new UAParser()).setUA(navigator.userAgent).getBrowser()
var browserName = browser.name
var browserVersion = browser.major
// Skip browsers with poor or nonexistant WebWorker support
var skip = ((browserName === 'Opera' && browserVersion <= 12) ||
(browserName === 'IE' && browserVersion <= 10) ||
(browserName === 'Safari' && browserVersion <= 5) ||
(browserName === 'WebKit' && browserVersion <= 534) || // Old mobile safari
(browserName === 'Android Browser' && browserVersion <= 4))
var reference = fs.readFileSync(__dirname + '/../server/static/browserify.png')
test('binary download in WebWorker', {
skip: skip
}, function (t) {
// We have to use a global url, since webworkify puts the worker in a Blob,
// which doesn't have a proper location
var testUrl = url.resolve(global.location.href, '/browserify.png')
var worker = work(require('./lib/webworker-worker.js'))
worker.addEventListener('message', function (ev) {
var data = new Buffer(new Uint8Array(ev.data))
t.ok(reference.equals(data), 'contents match')
t.end()
})
worker.postMessage(testUrl)
})
+4 -3
View File
@@ -6,8 +6,8 @@ var url = require('url')
var location = 'http://localhost:8081/foo/123'
var noop = function() {}
global.window = {}
window.XMLHttpRequest = function() {
global.location = url.parse(location)
global.XMLHttpRequest = function() {
this.open = noop
this.send = noop
this.withCredentials = false
@@ -107,7 +107,8 @@ test('Test relative path in url', function(t) {
})
test('Cleanup', function (t) {
delete global.window
delete global.location
delete global.XMLHttpRequest
delete require.cache[moduleName]
t.end()
})
+1
View File
@@ -0,0 +1 @@
../../../ie8-polyfill.js
@@ -1,4 +1,3 @@
// TODO: send a PR to url to remove .trim() so this isn't necessary
if (!String.prototype.trim) {
(function() {
// Make sure we trim BOM and NBSP