Compare commits

..

20 Commits

Author SHA1 Message Date
John Hiesey cd697901d1 3.2.0 2021-04-14 12:39:41 -07:00
John Hiesey 934f110003 Re-enable Safari 11 tests 2021-04-14 12:06:02 -07:00
John Hiesey 9e359bbc00 Fix sauce matrix 2021-04-14 11:52:46 -07:00
John Hiesey ca7bd49cd6 Disable tests on Safari 11 2021-04-14 11:49:56 -07:00
John Hiesey 8998953efd Reset timers on abort as well 2021-04-14 11:08:39 -07:00
John Hiesey e53322af5a Merge pull request #121 from jhiesey/socket-timeout
Socket timeout
2021-04-13 22:30:11 -07:00
John Hiesey 80ef3ca4ed Improve test reliability and bump deps 2021-04-13 22:29:31 -07:00
John Hiesey a2ba46d0ab Also support opts.timeout 2021-04-13 21:05:59 -07:00
John Hiesey 2c44eeb142 Update readme 2021-04-13 17:12:33 -07:00
John Hiesey 2572062eae Add timeout test 2021-04-13 16:27:08 -07:00
John Hiesey 681541d968 Emit error on destroy(err) 2021-04-13 16:23:47 -07:00
John Hiesey 54f5c2caae Add ClientRequest.setTimeout 2021-04-13 14:55:08 -07:00
Feross Aboukhadijeh e84db22d08 3.1.1 2020-05-05 10:37:01 -07:00
Feross Aboukhadijeh c05cfbbae8 bump all deps 2020-05-05 09:57:13 -07:00
Feross Aboukhadijeh 0657afd6ce tape@5 2020-05-05 09:57:13 -07:00
Feross Aboukhadijeh 36f13468b4 airtap@3 2020-05-05 09:57:13 -07:00
Feross Aboukhadijeh 0e1f2f30fc Merge pull request #112 from jhiesey/unused-var 2020-05-05 09:54:37 -07:00
Feross Aboukhadijeh f20f3cc30a Merge pull request #114 from cusspvz/patch-1 2020-05-05 09:54:24 -07:00
José Moreira 8320c894b9 fix: typo for the mode prefer-streaming at the readme.md file 2019-10-30 23:20:40 +00:00
Feross Aboukhadijeh 79f7465418 Remove unused variable 2019-08-12 18:16:38 -07:00
7 changed files with 107 additions and 37 deletions
+11 -2
View File
@@ -1,11 +1,15 @@
sauce_connect: true
providers:
- airtap-sauce
browsers:
- name: chrome
version: -2..latest
- name: firefox
version: -2..latest
- name: safari
version: 8..latest
version: [8..10, 12..latest]
- name: safari
version: 11
platform: Mac 10.13 # Configure separately due to https://github.com/airtap/sauce-browsers/issues/3
- name: MicrosoftEdge
version: -2..latest
- name: ie
@@ -20,3 +24,8 @@ scripts:
browserify:
- options:
dedupe: false
presets:
local:
providers: airtap-manual
browsers:
- name: manual
+3 -5
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/stream-http.svg)](https://saucelabs.com/u/stream-http)
[![Sauce Test Status](https://app.saucelabs.com/browser-matrix/stream-http.svg)](https://app.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
@@ -65,7 +65,7 @@ at the expense of causing the 'content-type' response header to be incorrectly r
(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
* 'prefer-streaming': 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. May also cause the 'content-type' response header to be
incorrectly reported (as 'text/plain; charset=x-user-defined').
@@ -84,7 +84,7 @@ option, which applies to opening the connection.
* `http.Agent` is only a stub
* The 'socket', 'connect', 'upgrade', and 'continue' events on `http.ClientRequest`.
* Any operations, including `request.setTimeout`, that operate directly on the underlying
* Any operations, other than `request.setTimeout`, that operate directly on the underlying
socket.
* Any options that are disallowed for security reasons. This includes setting or getting
certain headers.
@@ -94,8 +94,6 @@ the server.
* `message.trailers` and `message.rawTrailers` will remain empty.
* Redirects are followed silently by the browser, so it isn't possible to access the 301/302
redirect pages.
* The `timeout` event/option and `setTimeout` functions, which operate on the underlying
socket, are not available. However, see `options.requestTimeout` above.
## Example
+44 -7
View File
@@ -54,6 +54,8 @@ var ClientRequest = module.exports = function (opts) {
}
self._mode = decideMode(preferBinary, useFetch)
self._fetchTimer = null
self._socketTimeout = null
self._socketTimer = null
self.on('finish', function () {
self._onFinish()
@@ -96,6 +98,10 @@ ClientRequest.prototype._onFinish = function () {
return
var opts = self._opts
if ('timeout' in opts && opts.timeout !== 0) {
self.setTimeout(opts.timeout)
}
var headersObj = self._headers
var body = null
if (opts.method !== 'GET' && opts.method !== 'HEAD') {
@@ -120,7 +126,6 @@ ClientRequest.prototype._onFinish = function () {
if (self._mode === 'fetch') {
var signal = null
var fetchTimer = null
if (capability.abortController) {
var controller = new AbortController()
signal = controller.signal
@@ -144,9 +149,10 @@ ClientRequest.prototype._onFinish = function () {
signal: signal
}).then(function (response) {
self._fetchResponse = response
self._resetTimers(false)
self._connect()
}, function (reason) {
global.clearTimeout(self._fetchTimer)
self._resetTimers(true)
if (!self._destroyed)
self.emit('error', reason)
})
@@ -202,6 +208,7 @@ ClientRequest.prototype._onFinish = function () {
xhr.onerror = function () {
if (self._destroyed)
return
self._resetTimers(true)
self.emit('error', new Error('XHR error'))
}
@@ -233,13 +240,15 @@ function statusValid (xhr) {
ClientRequest.prototype._onXHRProgress = function () {
var self = this
self._resetTimers(false)
if (!statusValid(self._xhr) || self._destroyed)
return
if (!self._response)
self._connect()
self._response._onXHRProgress()
self._response._onXHRProgress(self._resetTimers.bind(self))
}
ClientRequest.prototype._connect = function () {
@@ -248,7 +257,7 @@ ClientRequest.prototype._connect = function () {
if (self._destroyed)
return
self._response = new IncomingMessage(self._xhr, self._fetchResponse, self._mode, self._fetchTimer)
self._response = new IncomingMessage(self._xhr, self._fetchResponse, self._mode, self._resetTimers.bind(self))
self._response.on('error', function(err) {
self.emit('error', err)
})
@@ -263,16 +272,35 @@ ClientRequest.prototype._write = function (chunk, encoding, cb) {
cb()
}
ClientRequest.prototype.abort = ClientRequest.prototype.destroy = function () {
ClientRequest.prototype._resetTimers = function (done) {
var self = this
global.clearTimeout(self._socketTimer)
self._socketTimer = null
if (done) {
global.clearTimeout(self._fetchTimer)
self._fetchTimer = null
} else if (self._socketTimeout) {
self._socketTimer = global.setTimeout(function () {
self.emit('timeout')
}, self._socketTimeout)
}
}
ClientRequest.prototype.abort = ClientRequest.prototype.destroy = function (err) {
var self = this
self._destroyed = true
global.clearTimeout(self._fetchTimer)
self._resetTimers(true)
if (self._response)
self._response._destroyed = true
if (self._xhr)
self._xhr.abort()
else if (self._fetchAbortController)
self._fetchAbortController.abort()
if (err)
self.emit('error', err)
}
ClientRequest.prototype.end = function (data, encoding, cb) {
@@ -285,8 +313,17 @@ ClientRequest.prototype.end = function (data, encoding, cb) {
stream.Writable.prototype.end.call(self, data, encoding, cb)
}
ClientRequest.prototype.setTimeout = function (timeout, cb) {
var self = this
if (cb)
self.once('timeout', cb)
self._socketTimeout = timeout
self._resetTimers(false)
}
ClientRequest.prototype.flushHeaders = function () {}
ClientRequest.prototype.setTimeout = function () {}
ClientRequest.prototype.setNoDelay = function () {}
ClientRequest.prototype.setSocketKeepAlive = function () {}
+10 -6
View File
@@ -10,7 +10,7 @@ var rStates = exports.readyStates = {
DONE: 4
}
var IncomingMessage = exports.IncomingMessage = function (xhr, response, mode, fetchTimer) {
var IncomingMessage = exports.IncomingMessage = function (xhr, response, mode, resetTimers) {
var self = this
stream.Readable.call(self)
@@ -43,6 +43,7 @@ var IncomingMessage = exports.IncomingMessage = function (xhr, response, mode, f
if (capability.writableStream) {
var writable = new WritableStream({
write: function (chunk) {
resetTimers(false)
return new Promise(function (resolve, reject) {
if (self._destroyed) {
reject()
@@ -54,11 +55,12 @@ var IncomingMessage = exports.IncomingMessage = function (xhr, response, mode, f
})
},
close: function () {
global.clearTimeout(fetchTimer)
resetTimers(true)
if (!self._destroyed)
self.push(null)
},
abort: function (err) {
resetTimers(true)
if (!self._destroyed)
self.emit('error', err)
}
@@ -66,7 +68,7 @@ var IncomingMessage = exports.IncomingMessage = function (xhr, response, mode, f
try {
response.body.pipeTo(writable).catch(function (err) {
global.clearTimeout(fetchTimer)
resetTimers(true)
if (!self._destroyed)
self.emit('error', err)
})
@@ -79,15 +81,15 @@ var IncomingMessage = exports.IncomingMessage = function (xhr, response, mode, f
reader.read().then(function (result) {
if (self._destroyed)
return
resetTimers(result.done)
if (result.done) {
global.clearTimeout(fetchTimer)
self.push(null)
return
}
self.push(Buffer.from(result.value))
read()
}).catch(function (err) {
global.clearTimeout(fetchTimer)
resetTimers(true)
if (!self._destroyed)
self.emit('error', err)
})
@@ -146,7 +148,7 @@ IncomingMessage.prototype._read = function () {
}
}
IncomingMessage.prototype._onXHRProgress = function () {
IncomingMessage.prototype._onXHRProgress = function (resetTimers) {
var self = this
var xhr = self._xhr
@@ -193,6 +195,7 @@ IncomingMessage.prototype._onXHRProgress = function () {
}
}
reader.onload = function () {
resetTimers(true)
self.push(null)
}
// reader.onerror = ??? // TODO: this
@@ -202,6 +205,7 @@ IncomingMessage.prototype._onXHRProgress = function () {
// The ms-stream case handles end separately in reader.onload()
if (self._xhr.readyState === rStates.DONE && self._mode !== 'ms-stream') {
resetTimers(true)
self.push(null)
}
}
+14 -12
View File
@@ -1,6 +1,6 @@
{
"name": "stream-http",
"version": "3.1.0",
"version": "3.2.0",
"description": "Streaming http in the browser",
"main": "index.js",
"repository": {
@@ -10,8 +10,8 @@
"scripts": {
"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": "airtap --loopback airtap.local -- test/browser/*.js",
"test-browser-local": "airtap --no-instrument --local 8080 -- test/browser/*.js"
"test-browser": "airtap --concurrency 1 -- test/browser/*.js",
"test-browser-local": "airtap --preset local -- test/browser/*.js"
},
"author": "John Hiesey",
"license": "MIT",
@@ -28,18 +28,20 @@
],
"dependencies": {
"builtin-status-codes": "^3.0.0",
"inherits": "^2.0.1",
"readable-stream": "^3.0.6",
"xtend": "^4.0.0"
"inherits": "^2.0.4",
"readable-stream": "^3.6.0",
"xtend": "^4.0.2"
},
"devDependencies": {
"airtap": "^2.0.3",
"airtap": "^4.0.3",
"airtap-manual": "^1.0.0",
"airtap-sauce": "^1.1.0",
"basic-auth": "^2.0.1",
"brfs": "^2.0.1",
"cookie-parser": "^1.4.3",
"express": "^4.16.3",
"tape": "^4.9.0",
"ua-parser-js": "^0.7.18",
"brfs": "^2.0.2",
"cookie-parser": "^1.4.5",
"express": "^4.17.1",
"tape": "^5.2.2",
"ua-parser-js": "^0.7.28",
"webworkify": "^1.5.0"
}
}
+24 -4
View File
@@ -4,7 +4,7 @@ var test = require('tape')
var http = require('../..')
test('timeout', function (t) {
test('requestTimeout', function (t) {
var req = http.get({
path: '/browserify.png?copies=5',
requestTimeout: 10 // ms
@@ -16,14 +16,14 @@ test('timeout', function (t) {
})
})
req.on('requestTimeout', function () {
t.pass('got timeout')
t.pass('got requestTimeout')
t.end()
})
})
// TODO: reenable this if there's a way to make it simultaneously
// fast and reliable
test.skip('no timeout after success', function (t) {
test.skip('no requestTimeout after success', function (t) {
var req = http.get({
path: '/basic.txt',
requestTimeout: 50000 // ms
@@ -38,6 +38,26 @@ test.skip('no timeout after success', function (t) {
})
})
req.on('requestTimeout', function () {
t.fail('unexpected timeout')
t.fail('unexpected requestTimeout')
})
})
test('setTimeout', function (t) {
t.plan(2)
var req = http.get({
path: '/browserify.png?copies=5'
}, function (res) {
res.on('data', function (data) {
})
res.on('end', function () {
t.fail('request completed (should have timed out)')
})
})
req.setTimeout(10, function () {
t.pass('got setTimeout callback')
})
req.on('timeout', function () {
t.pass('got timeout')
})
})
+1 -1
View File
@@ -132,6 +132,6 @@ app.use(function (req, res, next) {
app.use(express.static(path.join(__dirname, 'static')))
var port = parseInt(process.env.AIRTAP_PORT) || 8199
var port = parseInt(process.env.AIRTAP_SUPPORT_PORT) || 8199
console.log('Test server listening on port', port)
server.listen(port)