Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| cd697901d1 | |||
| 934f110003 | |||
| 9e359bbc00 | |||
| ca7bd49cd6 | |||
| 8998953efd | |||
| e53322af5a | |||
| 80ef3ca4ed | |||
| a2ba46d0ab | |||
| 2c44eeb142 | |||
| 2572062eae | |||
| 681541d968 | |||
| 54f5c2caae |
+11
-2
@@ -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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# stream-http [](https://travis-ci.org/jhiesey/stream-http)
|
||||
|
||||
[](https://saucelabs.com/u/stream-http)
|
||||
[](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
|
||||
@@ -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
-6
@@ -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') {
|
||||
@@ -143,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)
|
||||
})
|
||||
@@ -201,6 +208,7 @@ ClientRequest.prototype._onFinish = function () {
|
||||
xhr.onerror = function () {
|
||||
if (self._destroyed)
|
||||
return
|
||||
self._resetTimers(true)
|
||||
self.emit('error', new Error('XHR error'))
|
||||
}
|
||||
|
||||
@@ -232,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 () {
|
||||
@@ -247,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)
|
||||
})
|
||||
@@ -262,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) {
|
||||
@@ -284,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
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
+8
-6
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "stream-http",
|
||||
"version": "3.1.1",
|
||||
"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",
|
||||
@@ -33,13 +33,15 @@
|
||||
"xtend": "^4.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"airtap": "^3.0.0",
|
||||
"airtap": "^4.0.3",
|
||||
"airtap-manual": "^1.0.0",
|
||||
"airtap-sauce": "^1.1.0",
|
||||
"basic-auth": "^2.0.1",
|
||||
"brfs": "^2.0.2",
|
||||
"cookie-parser": "^1.4.5",
|
||||
"express": "^4.17.1",
|
||||
"tape": "^5.0.0",
|
||||
"ua-parser-js": "^0.7.21",
|
||||
"tape": "^5.2.2",
|
||||
"ua-parser-js": "^0.7.28",
|
||||
"webworkify": "^1.5.0"
|
||||
}
|
||||
}
|
||||
|
||||
+24
-4
@@ -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')
|
||||
})
|
||||
})
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user