Compare commits

...

15 Commits

Author SHA1 Message Date
John Hiesey 1dc9ea32f5 2.7.0 2017-04-03 23:46:27 -07:00
John Hiesey e8ed108767 Sauce labs no longer supports tests on android < 4.4 2017-04-03 23:45:47 -07:00
John Hiesey 4cf6771177 Disable broken timeout test
timeout support is fairly broken; see #66
2017-04-03 23:43:08 -07:00
John Hiesey e54de15542 Handle arrays in request headers and improve header handling
Passing an array as a header value now works as in node.
Header handling with multiple values for the same header is also
tested, and mime type test updated for safari 10.1

Fixes #73
2017-04-03 22:25:57 -07:00
John Hiesey 943f5bc89d use blacklist instead of whitelist for methods with bodies
Instead of only permitting bodies on certain methods, only
prohibit them when the browser would give an error (with fetch)
or ignore them (with XHR). This allows bodies with custom
methods and is closer to the behavior of http in node.

Fixes #69
2017-04-03 20:25:20 -07:00
John Hiesey 1c25d6f3a3 bump dependency versions 2017-04-03 20:20:47 -07:00
John Hiesey b63537f11d 2.6.3 2017-01-19 03:26:43 -08:00
John Hiesey 11d14fc642 Revert unnecessary part of previous change 2017-01-17 20:23:25 -08:00
John Hiesey 8e1f534a8a Fix Edge fetch errors 2017-01-17 18:39:22 -08:00
John Hiesey c1417c5dfa 2.6.2 2017-01-16 02:00:08 -08:00
John Hiesey 8df10e0d65 Avoid passing undefined to xhr.send()
Provides a more elegant fix to #67
2017-01-16 01:57:32 -08:00
John Hiesey 5ee30ad5cc Fix timeout test failure 2017-01-16 00:35:53 -08:00
John Hiesey 9321e56abd 2.6.1 2017-01-11 23:44:27 -08:00
John Hiesey 881e7c8f3d Merge pull request #65 from jhiesey/csp-xhr-fix
Avoid xhr request to example.com if fetch is supported
2017-01-11 22:44:35 -08:00
Feross Aboukhadijeh 43761f2799 Avoid xhr request to example.com if fetch is supported
For: https://github.com/brave/browser-laptop/issues/5981
2017-01-10 22:17:09 -08:00
9 changed files with 177 additions and 54 deletions
+1 -1
View File
@@ -15,7 +15,7 @@ browsers:
- name: iphone
version: '8.1..latest'
- name: android
version: '4.0..latest'
version: '4.4..latest'
server: ./test/server/index.js
scripts:
- "/ie8-polyfill.js"
+31 -12
View File
@@ -6,21 +6,33 @@ try {
exports.blobConstructor = true
} catch (e) {}
// Service workers don't have XHR
var xhr = null
if (global.XMLHttpRequest) {
xhr = new global.XMLHttpRequest()
// If XDomainRequest is available (ie only, where xhr might not work
// cross domain), use the page location. Otherwise use example.com
// Note: this doesn't actually make an http request.
try {
xhr.open('GET', global.XDomainRequest ? '/' : 'https://example.com')
} catch(e) {
// The xhr request to example.com may violate some restrictive CSP configurations,
// so if we're running in a browser that supports `fetch`, avoid calling getXHR()
// and assume support for certain features below.
var xhr
function getXHR () {
// Cache the xhr value
if (xhr !== undefined) return xhr
if (global.XMLHttpRequest) {
xhr = new global.XMLHttpRequest()
// If XDomainRequest is available (ie only, where xhr might not work
// cross domain), use the page location. Otherwise use example.com
// Note: this doesn't actually make an http request.
try {
xhr.open('GET', global.XDomainRequest ? '/' : 'https://example.com')
} catch(e) {
xhr = null
}
} else {
// Service workers don't have XHR
xhr = null
}
return xhr
}
function checkTypeSupport (type) {
var xhr = getXHR()
if (!xhr) return false
try {
xhr.responseType = type
@@ -34,13 +46,20 @@ function checkTypeSupport (type) {
var haveArrayBuffer = typeof global.ArrayBuffer !== 'undefined'
var haveSlice = haveArrayBuffer && isFunction(global.ArrayBuffer.prototype.slice)
exports.arraybuffer = haveArrayBuffer && checkTypeSupport('arraybuffer')
// If fetch is supported, then arraybuffer will be supported too. Skip calling
// checkTypeSupport(), since that calls getXHR().
exports.arraybuffer = exports.fetch || (haveArrayBuffer && checkTypeSupport('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 = xhr ? isFunction(xhr.overrideMimeType) : false
// If fetch is supported, then overrideMimeType will be supported too. Skip calling
// getXHR().
exports.overrideMimeType = exports.fetch || (getXHR() ? isFunction(getXHR().overrideMimeType) : false)
exports.vbArray = isFunction(global.VBArray)
function isFunction (value) {
+20 -10
View File
@@ -98,8 +98,8 @@ ClientRequest.prototype._onFinish = function () {
var opts = self._opts
var headersObj = self._headers
var body
if (opts.method === 'POST' || opts.method === 'PUT' || opts.method === 'PATCH' || opts.method === 'MERGE') {
var body = null
if (opts.method !== 'GET' && opts.method !== 'HEAD') {
if (capability.blobConstructor) {
body = new global.Blob(self._body.map(function (buffer) {
return toArrayBuffer(buffer)
@@ -112,15 +112,25 @@ ClientRequest.prototype._onFinish = function () {
}
}
if (self._mode === 'fetch') {
var headers = Object.keys(headersObj).map(function (name) {
return [headersObj[name].name, headersObj[name].value]
})
// create flattened list of headers
var headersList = []
Object.keys(headersObj).forEach(function (keyName) {
var name = headersObj[keyName].name
var value = headersObj[keyName].value
if (Array.isArray(value)) {
value.forEach(function (v) {
headersList.push([name, v])
})
} else {
headersList.push([name, value])
}
})
if (self._mode === 'fetch') {
global.fetch(self._opts.url, {
method: self._opts.method,
headers: headers,
body: body,
headers: headersList,
body: body || undefined,
mode: 'cors',
credentials: opts.withCredentials ? 'include' : 'same-origin'
}).then(function (response) {
@@ -157,8 +167,8 @@ ClientRequest.prototype._onFinish = function () {
}
}
Object.keys(headersObj).forEach(function (name) {
xhr.setRequestHeader(headersObj[name].name, headersObj[name].value)
headersList.forEach(function (header) {
xhr.setRequestHeader(header[0], header[1])
})
self._response = null
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "stream-http",
"version": "2.6.0",
"version": "2.7.0",
"description": "Streaming http in the browser",
"main": "index.js",
"repository": {
@@ -29,7 +29,7 @@
"dependencies": {
"builtin-status-codes": "^3.0.0",
"inherits": "^2.0.1",
"readable-stream": "^2.1.0",
"readable-stream": "^2.2.6",
"to-arraybuffer": "^1.0.0",
"xtend": "^4.0.0"
},
@@ -37,7 +37,7 @@
"basic-auth": "^1.0.3",
"brfs": "^1.4.0",
"cookie-parser": "^1.3.5",
"express": "^4.13.0",
"express": "^4.15.2",
"tape": "^4.0.0",
"ua-parser-js": "^0.7.7",
"webworkify": "^1.0.2",
+29
View File
@@ -0,0 +1,29 @@
var Buffer = require('buffer').Buffer
var fs = require('fs')
var test = require('tape')
var http = require('../..')
var reference = fs.readFileSync(__dirname + '/../server/static/basic.txt')
test('get body empty', function (t) {
var req = http.request({
path: '/verifyEmpty',
method: 'GET'
}, function (res) {
var buffers = []
res.on('end', function () {
console.log(Buffer.concat(buffers).toString('utf8'))
t.ok(Buffer.from('empty').equals(Buffer.concat(buffers)), 'response body indicates request body was empty')
t.end()
})
res.on('data', function (data) {
buffers.push(data)
})
})
req.write(reference)
req.end()
})
+37 -2
View File
@@ -46,6 +46,40 @@ test('headers', function (t) {
})
})
test('arrays of headers', function (t) {
http.get({
path: '/testHeaders?Response-Header=bar&Response-Header=BAR2',
headers: {
'Test-Request-Header': ['foo', 'FOO2']
}
}, function (res) {
var rawHeaders = []
for (var i = 0; i < res.rawHeaders.length; i += 2) {
var lowerKey = res.rawHeaders[i].toLowerCase()
if (lowerKey.indexOf('test-') === 0)
rawHeaders.push(lowerKey, res.rawHeaders[i + 1])
}
t.equal(rawHeaders[0], 'test-response-header', 'raw response header present')
t.equal(rawHeaders[1], 'bar, BAR2', 'raw response header value')
t.equal(rawHeaders.length, 2, 'correct number of raw headers')
t.equal(res.headers['test-response-header'], 'bar, BAR2', 'response header')
var buffers = []
res.on('end', function () {
var body = JSON.parse(Buffer.concat(buffers).toString())
t.equal(body['test-request-header'], 'foo,FOO2', 'request headers')
t.equal(Object.keys(body).length, 1, 'correct number of request headers')
t.end()
})
res.on('data', function (data) {
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')
@@ -56,10 +90,11 @@ test('content-type response header', function (t) {
var browser = (new UAParser()).setUA(navigator.userAgent).getBrowser()
var browserName = browser.name
var browserVersion = browser.major
var browserMinorVersion = browser.minor
// 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
// is passed in browsers that rely on xhr.overrideMimeType(), namely older chrome and safari 6-10.0
var wrongMimeType = ((browserName === 'Chrome' && browserVersion <= 42) ||
((browserName === 'Safari' || browserName === 'Mobile Safari') && browserVersion >= 6))
((browserName === 'Safari' || browserName === 'Mobile Safari') && browserVersion >= 6 && (browserVersion < 10 || (browserVersion === 10 && browserMinorVersion === 0))))
test('content-type response header with forced streaming', function (t) {
http.get({
-24
View File
@@ -1,24 +0,0 @@
var test = require('tape')
var http = require('../..')
var skipTimeout = ((browserName === 'Opera' && browserVersion <= 12) ||
(browserName === 'Safari' && browserVersion <= 5))
test('emits timeout events', function (t) {
if (skipTimeout) {
return t.skip('Browser does not support setting timeouts')
}
var req = http.request({
path: '/basic.txt',
timeout: 1
})
req.on('timeout', function () {
t.end() // the test will timeout if this does not happen
})
req.end()
})
+33
View File
@@ -0,0 +1,33 @@
var Buffer = require('buffer').Buffer
var fs = require('fs')
var test = require('tape')
var UAParser = require('ua-parser-js')
var url = require('url')
var http = require('../..')
var browser = (new UAParser()).setUA(navigator.userAgent).getBrowser()
var browserName = browser.name
var browserVersion = browser.major
var skipTimeout = ((browserName === 'Opera' && browserVersion <= 12) ||
(browserName === 'Safari' && browserVersion <= 5))
test('emits timeout events', function (t) {
if (skipTimeout) {
return t.skip('Browser does not support setting timeouts')
}
var req = http.request({
path: '/basic.txt',
timeout: 1
})
req.on('timeout', function () {
t.pass('timeout caught')
t.end() // the test will timeout if this does not happen
})
req.end()
})
+23 -2
View File
@@ -37,8 +37,11 @@ app.get('/testHeaders', function (req, res) {
var reqHeaders = {}
Object.keys(req.headers).forEach(function (key) {
key = key.toLowerCase()
if (key.indexOf('test-') === 0)
reqHeaders[key] = req.headers[key]
if (key.indexOf('test-') === 0) {
// different browsers format request headers with multiple values
// slightly differently, so normalize
reqHeaders[key] = req.headers[key].replace(', ', ',')
}
})
var body = JSON.stringify(reqHeaders)
@@ -71,6 +74,24 @@ app.post('/echo', function (req, res) {
req.pipe(res)
})
app.use('/verifyEmpty', function (req, res) {
var empty = true
req.on('data', function (buf) {
if (buf.length > 0) {
empty = false
}
})
req.on('end', function () {
res.setHeader('Content-Type', 'text/plain')
if (empty) {
res.end('empty')
} else {
res.end('not empty')
}
})
})
app.use(function (req, res, next) {
var parsed = url.parse(req.url, true)