Compare commits

...

49 Commits

Author SHA1 Message Date
John Hiesey 3c8b44379c 2.0.5 2016-01-09 03:56:05 +01:00
John Hiesey c36da3bade Use 'http:' as a default protocol
If global.location.protocol isn't 'http:' or 'https:', use
'http:'. This makes behavior reasonable when the page is
opened directly without using a server.
2016-01-09 03:55:14 +01:00
John Hiesey cbc0bd803c 2.0.4 2016-01-09 00:22:46 +01:00
John Hiesey d6de2ce3a5 Add docs on running tests 2016-01-09 00:20:56 +01:00
John Hiesey 4bcff63d46 Don't run browser tests in travis for PRs
These tests fail anyway, since the sauce labs credentials
aren't available. In all other cases running the browser
tests is desirable.
2016-01-09 00:20:26 +01:00
John Hiesey a69310d8fc Run travis tests using node 4.1
This is the latest version currently supported according
to the travis docs. The version used here shouldn't actually
affect the results of the tests, but we might as well use
something more recent.
2016-01-08 23:27:14 +01:00
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
John Hiesey 166795fc05 1.6.0 2015-09-11 21:19:23 -07:00
John Hiesey ff339bd64c Merge pull request #21 from jhiesey/browser-resolve
Add support for relative paths in urls
2015-09-11 20:40:14 -07:00
John Hiesey 5986071393 Allow newer versions of some deps 2015-09-11 20:40:00 -07:00
John Hiesey bbf1fd81f0 Test handling of relative paths in urls
This adds the test from #17.
2015-09-11 20:09:20 -07:00
John Hiesey 6b9d3d107c Drastically simplify and improve url handling
Make options handling logic match node's `http` module more
closely and let the browser handle all of the relative url
cases itself.

This has a few important advantages:
* Behavior is closer to native `http` modula
* Relative paths in urls are handled properly (fixes #16)
* Code is simpler and more obviously correct
2015-09-11 20:06:13 -07:00
John Hiesey 3fda0048d7 Minor whitespace fixes in node test 2015-09-09 20:25:14 -07:00
John Hiesey bef2b95708 Fix IPv6 addresses in requests
Fixes #18
2015-09-09 20:25:14 -07:00
John Hiesey efed69e6e2 Merge pull request #13 from regular/patch-1
Update README.md
2015-08-28 10:46:20 -07:00
Jan Bölsche caa40a0950 Update README.md
from the code it looks like if withCredentials defaults to false now.
2015-08-28 19:40:15 +02:00
John Hiesey 168dbac90c 1.5.0 2015-08-20 01:21:15 -07:00
John Hiesey a91ed0db64 Modify feature testing to avoid warnings in Chrome
Previously, testing for 'ms-stream' and 'moz-chunked-arraybuffer'
support on the xhr object caused warnings to appear in Chrome.
Since fetch will always be used instead of these when available,
short-circuit and return false when fetch is available.
2015-08-20 01:06:34 -07:00
John Hiesey da4085e33d Make content-type header correct by default
The default behavior now preserves the 'content-type' header, at
the expense of not providing pseudo-streming in Safari, generic
WebKit, and older Chrome. Applications that require streaming
and already use 'prefer-stream' mode will not be affected.

The 'prefer-fast' mode is now deprecated, as that behavior now
matches the default. The 'allow-wrong-content-type' mode is new;
it gives the same behavior as the old default in case binary
streaming is essential.

Fixes #8
2015-08-20 01:01:40 -07:00
John Hiesey 6d17b406a9 1.4.1 2015-08-07 18:18:02 -07:00
John Hiesey 1a4a89a5e4 Add test for data in request.end() 2015-08-07 18:02:33 -07:00
John Hiesey 838aaffac7 Fix passing data to request.end()
Previously this would result in an exception.
Thanks @magnuslundstedt for finding the bug!
2015-08-07 17:55:43 -07:00
John Hiesey f70ac39bf4 1.4.0 2015-08-06 22:44:10 -07:00
John Hiesey ed525a4a2d Merge pull request #11 from tjmehta/add-http-methods-from-node
Add http methods from node v0.12
2015-08-06 22:33:45 -07:00
Tejesh Mehta b162e21d92 Add http methods from node v0.12 2015-08-06 17:39:17 -07:00
John Hiesey c8c0966c63 1.3.0 2015-07-20 16:10:29 -07:00
John Hiesey efaad2a3c7 Add keywords 2015-07-20 16:07:30 -07:00
John Hiesey 9059849673 Make withCredentials default to false
This may break compatibility with code that was written for
`http-browserify`. Fixes #7
2015-07-20 16:01:24 -07:00
John Hiesey aeabb2cc70 Merge pull request #6 from cesarandreu/consistent-fetch
Fix inconsistency between XMLHttpRequest and fetch
2015-07-20 00:32:46 -07:00
Cesar Andreu 588e5874f3 Add test for cookie behavior 2015-07-19 17:50:30 -07:00
Cesar Andreu 5fc4858d6d Use same-origin instead of omit with fetch credentials
In order to have parity with XMLHttpRequest fetch credentials should use same-origin instead of omit
Setting XMLHttpRequest.withCredentials to false has no effect on same-site request
2015-07-19 17:06:28 -07:00
John Hiesey 22b3cc7acb v1.2.1 2015-07-17 00:50:31 -07:00
John Hiesey 5ffc869490 Fix readme to point to new saucelabs account 2015-07-17 00:35:03 -07:00
20 changed files with 515 additions and 116 deletions
+1 -1
View File
@@ -1,3 +1,3 @@
language: node_js
node_js:
- "0.12"
- "4.1"
+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"
+37 -8
View File
@@ -1,6 +1,6 @@
# stream-http [![Build Status](https://travis-ci.org/jhiesey/stream-http.svg?branch=master)](https://travis-ci.org/jhiesey/stream-http)
[![Sauce Test Status](https://saucelabs.com/browser-matrix/jhiesey.svg)](https://saucelabs.com/u/jhiesey)
[![Sauce Test Status](https://saucelabs.com/browser-matrix/stream-http.svg)](https://saucelabs.com/u/stream-http)
This module is an implementation of node's native `http` module for the browser.
It tries to match node's api and behavior as closely as possible, but some features
@@ -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
@@ -42,7 +49,7 @@ node docs for how these work.
### Extra features compared to node
* The `options.withCredentials` boolean flag, used to indicate if the browser should send
cookies or authentication information with a CORS request. Default true.
cookies or authentication information with a CORS request. Default false.
This module has to make some tradeoffs to support binary data and/or streaming. Generally,
the module can make a fairly good decision about which underlying browser features to use,
@@ -51,14 +58,21 @@ but sometimes it helps to get a little input from the user.
* The `options.mode` field passed into `http.request` or `http.get` can take on one of the
following values:
* 'default' (or any falsy value, including undefined): Try to provide partial data before
the equest completes, but not at the cost of correctness for binary data. In some cases
the implementation may be a bit slow.
the request completes, but not at the cost of correctness for binary data or correctness of
the 'content-type' response header. This mode will also avoid slower code paths whenever
possible, which is particularly useful when making large requests in a browser like Safari
that has a weaker javascript engine.
* 'allow-wrong-content-type': Provides partial data in more cases than 'default', but
at the expense of causing the 'content-type' response header to be incorrectly reported
(as 'text/plain; charset=x-user-defined') in some browsers, notably Safari and Chrome 42
and older. Preserves binary data whenever possible. In some cases the implementation may
also be a bit slow. This was the default in versions of this module before 1.5.
* 'prefer-stream': Provide data before the request completes even if binary data (anything
that isn't a single-byte ASCII or utf8 character) will be corrupted. Of course, this option
is only safe for text data.
* 'prefer-fast': Use an implementation that does less processing even if it means that
partial data isn't available. This is particularly useful when making large requests in
a browser like Safari that has a weaker javascript engine.
is only safe for text data. May also cause the 'content-type' response header to be
incorrectly reported (as 'text/plain; charset=x-user-defined').
* 'prefer-fast': Deprecated; now a synonym for 'default', which has the same performance
characteristics as this mode did in versions before 1.5.
### Features missing compared to node
@@ -90,6 +104,21 @@ http.get('/bundle.js', function (res) {
})
```
## Running tests
There are two sets of tests: the tests that run in node (found in `test/node`) and the tests
that run in the browser (found in `test/browser`). Normally the browser tests run on
[Sauce Labs](http://saucelabs.com/).
Running `npm test` will run both sets of tests, but in order for the Sauce Labs tests to run
you will need to sign up for an account (free for open source projects) and put the
credentials in [a `.zuulrc` file](https://github.com/defunctzombie/zuul/wiki/zuulrc).
To run just the node tests, run `npm run test-node`.
To run the browser tests locally, run `npm run test-browser-local` and point your browser to
`http://localhost:8080/__zuul`
## License
MIT. Copyright (C) John Hiesey and other contributors.
+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;
};
}
+40 -16
View File
@@ -11,22 +11,24 @@ http.request = function (opts, cb) {
else
opts = extend(opts)
// Split opts.host into its components
var hostHostname = opts.host ? opts.host.split(':')[0] : null
var hostPort = opts.host ? parseInt(opts.host.split(':')[1], 10) : null
// Normally, the page is loaded from http or https, so not specifying a protocol
// will result in a (valid) protocol-relative url. However, this won't work if
// the protocol is something else, like 'file:'
var defaultProtocol = global.location.protocol.search(/^https?:$/) === -1 ? 'http:' : ''
opts.method = opts.method || 'GET'
var protocol = opts.protocol || defaultProtocol
var host = opts.hostname || opts.host
var port = opts.port
var path = opts.path || '/'
// Necessary for IPv6 addresses
if (host && host.indexOf(':') !== -1)
host = '[' + host + ']'
// This may be a relative url. The browser should always be able to interpret it correctly.
opts.url = (host ? (protocol + '//' + host) : '') + (port ? ':' + port : '') + path
opts.method = (opts.method || 'GET').toUpperCase()
opts.headers = opts.headers || {}
opts.path = opts.path || '/'
opts.protocol = opts.protocol || window.location.protocol
// If the hostname is provided, use the default port for the protocol. If
// the url is instead relative, use window.location.port
var defaultPort = (opts.hostname || hostHostname) ? (opts.protocol === 'https:' ? 443 : 80) : window.location.port
opts.hostname = opts.hostname || hostHostname || window.location.hostname
opts.port = opts.port || hostPort || defaultPort
if (opts.withCredentials === undefined)
opts.withCredentials = true
// Also valid opts.auth, opts.mode
@@ -48,8 +50,30 @@ http.Agent.defaultMaxSockets = 4
http.STATUS_CODES = statusCodes
http.METHODS = [
'CHECKOUT',
'CONNECT',
'COPY',
'DELETE',
'GET',
'HEAD',
'LOCK',
'M-SEARCH',
'MERGE',
'MKACTIVITY',
'MKCOL',
'MOVE',
'NOTIFY',
'OPTIONS',
'PATCH',
'POST',
'PROPFIND',
'PROPPATCH',
'PURGE',
'PUT',
'DELETE' // TODO: include the methods from RFC 2616 and 2518?
]
'REPORT',
'SEARCH',
'SUBSCRIBE',
'TRACE',
'UNLOCK',
'UNSUBSCRIBE'
]
+15 -8
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,14 +19,19 @@ function checkTypeSupport (type) {
return false
}
var haveArrayBuffer = isFunction(window.ArrayBuffer)
var haveSlice = haveArrayBuffer && isFunction(window.ArrayBuffer.prototype.slice)
// For some strange reason, Safari 7.0 reports typeof global.ArrayBuffer === 'object'.
// Safari 7.1 appears to have fixed this bug.
var haveArrayBuffer = typeof global.ArrayBuffer !== 'undefined'
var haveSlice = haveArrayBuffer && isFunction(global.ArrayBuffer.prototype.slice)
exports.arraybuffer = haveArrayBuffer && checkTypeSupport('arraybuffer')
exports.msstream = haveSlice && checkTypeSupport('ms-stream')
exports.mozchunkedarraybuffer = haveArrayBuffer && checkTypeSupport('moz-chunked-arraybuffer')
// These next two tests unavoidably show warnings in Chrome. Since fetch will always
// be used if it's available, just return false for these to avoid the warnings.
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'
+24 -28
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')
@@ -31,25 +28,25 @@ var ClientRequest = module.exports = function (opts) {
stream.Writable.call(self)
self._opts = opts
self._url = opts.protocol + '//' + opts.hostname + ':' + opts.port + opts.path
self._body = []
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])
})
var preferBinary
if (opts.mode === 'prefer-streaming') {
// If streaming is a high priority but binary compatibility isn't
// If streaming is a high priority but binary compatibility and
// the accuracy of the 'content-type' header aren't
preferBinary = false
} else if (opts.mode === 'prefer-fast') {
// If binary is preferred for speed
preferBinary = true
} else if (!opts.mode || opts.mode === 'default') {
// By default, use binary if text streaming may corrupt data
} else if (opts.mode === 'allow-wrong-content-type') {
// If streaming is more important than preserving the 'content-type' header
preferBinary = !capability.overrideMimeType
} else if (!opts.mode || opts.mode === 'default' || opts.mode === 'prefer-fast') {
// Use binary if text streaming may corrupt data or the content-type header, or for speed
preferBinary = true
} else {
throw new Error('Invalid value for opts.mode')
}
@@ -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,26 +107,26 @@ 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._url, {
global.fetch(self._opts.url, {
method: self._opts.method,
headers: headers,
body: body,
mode: 'cors',
credentials: opts.withCredentials ? 'include' : 'omit'
credentials: opts.withCredentials ? 'include' : 'same-origin'
}).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._url, true)
xhr.open(self._opts.method, self._opts.url, true)
} catch (err) {
process.nextTick(function () {
self.emit('error', 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
}
@@ -244,10 +243,7 @@ ClientRequest.prototype.end = function (data, encoding, cb) {
data = undefined
}
if (data)
stream.Writable.push.call(self, data, encoding)
stream.Writable.prototype.end.call(self, cb)
stream.Writable.prototype.end.call(self, data, encoding, cb)
}
ClientRequest.prototype.flushHeaders = function () {}
+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))))
+13 -7
View File
@@ -1,6 +1,6 @@
{
"name": "stream-http",
"version": "1.2.0",
"version": "2.0.5",
"description": "Streaming http in the browser",
"main": "index.js",
"repository": {
@@ -8,27 +8,33 @@
"url": "git://github.com/jhiesey/stream-http.git"
},
"scripts": {
"test": "npm run test-node && npm run test-browser",
"test": "npm run test-node && ([ -n \"${TRAVIS_PULL_REQUEST}\" -a \"${TRAVIS_PULL_REQUEST}\" != 'false' ] || 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",
"license": "MIT",
"keywords": [
"http",
"stream",
"streaming",
"xhr",
"http-browserify"
],
"dependencies": {
"builtin-status-codes": "~1.0.0",
"foreach": "^2.0.5",
"indexof": "0.0.1",
"builtin-status-codes": "^1.0.0",
"inherits": "^2.0.1",
"object-keys": "1.0.4",
"xtend": "^4.0.0"
},
"devDependencies": {
"basic-auth": "^1.0.3",
"brfs": "^1.4.0",
"cookie-parser": "^1.3.5",
"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
})
}
})
})
+5 -2
View File
@@ -26,7 +26,10 @@ for(var i = 0; i < COPIES; i++) {
}
test('binary streaming', function (t) {
http.get('/browserify.png?copies=' + COPIES, function (res) {
http.get({
path: '/browserify.png?copies=' + COPIES,
mode: 'allow-wrong-content-type'
}, function (res) {
var buffers = []
res.on('end', function () {
if (skipVerification)
@@ -50,7 +53,7 @@ test('binary streaming', function (t) {
test('large binary request without streaming', function (t) {
http.get({
path: '/browserify.png?copies=' + COPIES,
mode: 'prefer-fast',
mode: 'default',
}, function (res) {
var buffers = []
res.on('end', function () {
+25
View File
@@ -0,0 +1,25 @@
var Buffer = require('buffer').Buffer
var test = require('tape')
var http = require('../..')
test('cookie', function (t) {
var cookie = 'hello=world'
window.document.cookie = cookie
http.get({
path: '/cookie',
withCredentials: false
}, function (res) {
var buffers = []
res.on('end', function () {
t.ok(new Buffer(cookie).equals(Buffer.concat(buffers)), 'hello cookie echoed')
t.end()
})
res.on('data', function (data) {
buffers.push(data)
})
})
})
+32 -13
View File
@@ -1,19 +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',
@@ -28,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')
@@ -45,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()
})
@@ -53,4 +44,32 @@ test('headers', function (t) {
buffers.push(data)
})
})
})
test('content-type response header', function (t) {
http.get('/testHeaders', function (res) {
t.equal(res.headers['content-type'], 'application/json', 'content-type preserved')
t.end()
})
})
var browser = (new UAParser()).setUA(navigator.userAgent).getBrowser()
var browserName = browser.name
var browserVersion = browser.major
// The content-type header is broken when 'prefer-streaming' or 'allow-wrong-content-type'
// is passed in browsers that rely on xhr.overrideMimeType(), namely older chrome and newer safari
var wrongMimeType = ((browserName === 'Chrome' && browserVersion <= 42) ||
((browserName === 'Safari' || browserName === 'Mobile Safari') && browserVersion >= 6))
test('content-type response header with forced streaming', function (t) {
http.get({
path: '/testHeaders',
mode: 'prefer-streaming'
}, function (res) {
if (wrongMimeType)
t.equal(res.headers['content-type'], 'text/plain; charset=x-user-defined', 'content-type overridden')
else
t.equal(res.headers['content-type'], 'application/json', 'content-type preserved')
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)
})
})
})
}
+20
View File
@@ -25,4 +25,24 @@ test('post text', function (t) {
req.write(reference)
req.end()
})
test('post text with data in end()', function (t) {
var req = http.request({
path: '/echo',
method: 'POST'
}, function (res) {
var buffers = []
res.on('end', function () {
t.ok(reference.equals(Buffer.concat(buffers)), 'echoed contents match')
t.end()
})
res.on('data', function (data) {
buffers.push(data)
})
})
req.end(reference)
})
+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)
})
+59 -23
View File
@@ -1,16 +1,13 @@
// These tests are teken from http-browserify to ensure compatibility with
// that module
var test = require('tape')
var url = require('url')
global.window = {}
window.location = {
hostname: 'localhost',
port: 8081,
protocol: 'http:'
}
var location = 'http://localhost:8081/foo/123'
var noop = function() {}
window.XMLHttpRequest = function() {
global.location = url.parse(location)
global.XMLHttpRequest = function() {
this.open = noop
this.send = noop
this.withCredentials = false
@@ -21,16 +18,17 @@ delete require.cache[moduleName]
var http = require('../../')
test('Test simple url string', function(t) {
var url = { path: '/api/foo' }
var request = http.get(url, noop)
var testUrl = { path: '/api/foo' }
var request = http.get(testUrl, noop)
t.equal( request._url, 'http://localhost:8081/api/foo', 'Url should be correct')
var resolved = url.resolve(location, request._opts.url)
t.equal(resolved, 'http://localhost:8081/api/foo', 'Url should be correct')
t.end()
})
test('Test full url object', function(t) {
var url = {
var testUrl = {
host: "localhost:8081",
hostname: "localhost",
href: "http://localhost:8081/api/foo?bar=baz",
@@ -44,11 +42,11 @@ test('Test full url object', function(t) {
slashes: true
}
var request = http.get(url, noop)
var request = http.get(testUrl, noop)
t.equal( request._url, 'http://localhost:8081/api/foo?bar=baz', 'Url should be correct')
var resolved = url.resolve(location, request._opts.url)
t.equal(resolved, 'http://localhost:8081/api/foo?bar=baz', 'Url should be correct')
t.end()
})
test('Test alt protocol', function(t) {
@@ -61,37 +59,75 @@ test('Test alt protocol', function(t) {
var request = http.get(params, noop)
t.equal( request._url, 'foo://localhost:3000/bar', 'Url should be correct')
var resolved = url.resolve(location, request._opts.url)
t.equal(resolved, 'foo://localhost:3000/bar', 'Url should be correct')
t.end()
})
test('Test page with \'file:\' protocol', function (t) {
var params = {
hostname: 'localhost',
port: 3000,
path: '/bar'
}
var fileLocation = 'file:///home/me/stuff/index.html'
var normalLocation = global.location
global.location = url.parse(fileLocation) // Temporarily change the location
var request = http.get(params, noop)
global.location = normalLocation // Reset the location
var resolved = url.resolve(fileLocation, request._opts.url)
t.equal(resolved, 'http://localhost:3000/bar', 'Url should be correct')
t.end()
})
test('Test string as parameters', function(t) {
var url = '/api/foo'
var request = http.get(url, noop)
var testUrl = '/api/foo'
var request = http.get(testUrl, noop)
t.equal( request._url, 'http://localhost:8081/api/foo', 'Url should be correct')
var resolved = url.resolve(location, request._opts.url)
t.equal(resolved, 'http://localhost:8081/api/foo', 'Url should be correct')
t.end()
})
test('Test withCredentials param', function(t) {
var url = '/api/foo'
var request = http.get({ url: url, withCredentials: false }, noop)
t.equal( request._xhr.withCredentials, false, 'xhr.withCredentials should be false')
t.equal(request._xhr.withCredentials, false, 'xhr.withCredentials should be false')
var request = http.get({ url: url, withCredentials: true }, noop)
t.equal( request._xhr.withCredentials, true, 'xhr.withCredentials should be true')
t.equal(request._xhr.withCredentials, true, 'xhr.withCredentials should be true')
var request = http.get({ url: url }, noop)
t.equal( request._xhr.withCredentials, true, 'xhr.withCredentials should be true')
t.equal(request._xhr.withCredentials, false, 'xhr.withCredentials should be false')
t.end()
})
test('Test ipv6 address', function(t) {
var testUrl = 'http://[::1]:80/foo'
var request = http.get(testUrl, noop)
var resolved = url.resolve(location, request._opts.url)
t.equal(resolved, 'http://[::1]:80/foo', 'Url should be correct')
t.end()
})
test('Test relative path in url', function(t) {
var params = { path: './bar' }
var request = http.get(params, noop)
var resolved = url.resolve(location, request._opts.url)
t.equal(resolved, 'http://localhost:8081/foo/bar', 'Url should be correct')
t.end()
})
test('Cleanup', function (t) {
delete global.window
delete global.location
delete global.XMLHttpRequest
delete require.cache[moduleName]
t.end()
})
+7
View File
@@ -1,3 +1,4 @@
var cookieParser = require('cookie-parser')
var basicAuth = require('basic-auth')
var express = require('express')
var fs = require('fs')
@@ -46,6 +47,12 @@ app.get('/testHeaders', function (req, res) {
res.end()
})
app.get('/cookie', cookieParser(), function (req, res) {
res.setHeader('Content-Type', 'text/plain')
res.write('hello=' + req.cookies.hello)
res.end()
})
app.get('/auth', function (req, res) {
var user = basicAuth(req)
+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