refactor: rewrite the net module to simplify state tracking (#21244)

This commit is contained in:
Jeremy Apthorp 2019-11-26 17:01:13 -08:00 committed by GitHub
parent 4149d76890
commit d25256dcf5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 1092 additions and 1033 deletions

View file

@ -2,7 +2,7 @@ import { expect } from 'chai'
import { net, session, ClientRequest, BrowserWindow } from 'electron'
import * as http from 'http'
import * as url from 'url'
import { AddressInfo } from 'net'
import { AddressInfo, Socket } from 'net'
import { emittedOnce } from './events-helpers'
const kOneKiloByte = 1024
@ -22,6 +22,13 @@ function randomString (length: number) {
return buffer.toString()
}
const cleanupTasks: (() => void)[] = []
function cleanUp () {
cleanupTasks.forEach(t => t())
cleanupTasks.length = 0
}
function respondOnce (fn: http.RequestListener): Promise<string> {
return new Promise((resolve) => {
const server = http.createServer((request, response) => {
@ -32,6 +39,12 @@ function respondOnce (fn: http.RequestListener): Promise<string> {
server.listen(0, '127.0.0.1', () => {
resolve(`http://127.0.0.1:${(server.address() as AddressInfo).port}`)
})
const sockets: Socket[] = []
server.on('connection', s => sockets.push(s))
cleanupTasks.push(() => {
server.close()
sockets.forEach(s => s.destroy())
})
})
}
@ -57,6 +70,10 @@ respondOnce.toSingleURL = (fn: http.RequestListener) => {
}
describe('net module', () => {
afterEach(cleanUp)
afterEach(async () => {
await session.defaultSession.clearCache()
})
describe('HTTP basics', () => {
it('should be able to issue a basic GET request', (done) => {
respondOnce.toSingleURL((request, response) => {
@ -325,6 +342,36 @@ describe('net module', () => {
request.end()
})
})
it('should upload body when 401', async () => {
const [user, pass] = ['user', 'pass']
const serverUrl = await respondOnce.toSingleURL((request, response) => {
if (!request.headers.authorization) {
return response.writeHead(401, { 'WWW-Authenticate': 'Basic realm="Foo"' }).end()
}
response.writeHead(200)
request.on('data', (chunk) => { response.write(chunk) })
request.on('end', () => {
response.end()
})
})
const requestData = randomString(kOneKiloByte)
const responseData = await new Promise((resolve, reject) => {
const request = net.request({ method: 'GET', url: serverUrl })
request.on('response', (response) => {
response.on('error', reject)
let data = ''
response.on('data', (chunk) => { data += chunk.toString() })
response.on('end', () => resolve(data))
})
request.on('login', (authInfo, cb) => {
cb(user, pass)
})
request.on('error', reject)
request.end(requestData)
})
expect(responseData).to.equal(requestData)
})
})
describe('ClientRequest API', () => {
@ -955,10 +1002,10 @@ describe('net module', () => {
})
it('should follow redirect when no redirect handler is provided', (done) => {
const requestUrl = '/301'
const requestUrl = '/302'
respondOnce.toRoutes({
'/301': (request, response) => {
response.statusCode = 301
'/302': (request, response) => {
response.statusCode = 302
response.setHeader('Location', '/200')
response.end()
},
@ -981,12 +1028,12 @@ describe('net module', () => {
it('should follow redirect chain when no redirect handler is provided', (done) => {
respondOnce.toRoutes({
'/redirectChain': (request, response) => {
response.statusCode = 301
response.setHeader('Location', '/301')
response.statusCode = 302
response.setHeader('Location', '/302')
response.end()
},
'/301': (request, response) => {
response.statusCode = 301
'/302': (request, response) => {
response.statusCode = 302
response.setHeader('Location', '/200')
response.end()
},
@ -1008,7 +1055,7 @@ describe('net module', () => {
it('should not follow redirect when request is canceled in redirect handler', async () => {
const serverUrl = await respondOnce.toSingleURL((request, response) => {
response.statusCode = 301
response.statusCode = 302
response.setHeader('Location', '/200')
response.end()
})
@ -1017,18 +1064,33 @@ describe('net module', () => {
})
urlRequest.end()
urlRequest.on('redirect', () => { urlRequest.abort() })
urlRequest.on('error', () => {})
await emittedOnce(urlRequest, 'abort')
})
it('should not follow redirect when mode is error', async () => {
const serverUrl = await respondOnce.toSingleURL((request, response) => {
response.statusCode = 302
response.setHeader('Location', '/200')
response.end()
})
const urlRequest = net.request({
url: serverUrl,
redirect: 'error'
})
urlRequest.end()
await emittedOnce(urlRequest, 'error')
})
it('should follow redirect when handler calls callback', async () => {
const serverUrl = await respondOnce.toRoutes({
'/redirectChain': (request, response) => {
response.statusCode = 301
response.setHeader('Location', '/301')
response.statusCode = 302
response.setHeader('Location', '/302')
response.end()
},
'/301': (request, response) => {
response.statusCode = 301
'/302': (request, response) => {
response.statusCode = 302
response.setHeader('Location', '/200')
response.end()
},
@ -1037,7 +1099,7 @@ describe('net module', () => {
response.end()
}
})
const urlRequest = net.request(`${serverUrl}/redirectChain`)
const urlRequest = net.request({ url: `${serverUrl}/redirectChain`, redirect: 'manual' })
const redirects: string[] = []
urlRequest.on('redirect', (status, method, url) => {
redirects.push(url)
@ -1047,7 +1109,7 @@ describe('net module', () => {
const [response] = await emittedOnce(urlRequest, 'response')
expect(response.statusCode).to.equal(200)
expect(redirects).to.deep.equal([
`${serverUrl}/301`,
`${serverUrl}/302`,
`${serverUrl}/200`
])
})
@ -1276,9 +1338,9 @@ describe('net module', () => {
await emittedOnce(response, 'end')
})
it('should be able to pipe a net response into a writable stream', (done) => {
it('should be able to pipe a net response into a writable stream', async () => {
const bodyData = randomString(kOneKiloByte)
Promise.all([
const [netServerUrl, nodeServerUrl] = await Promise.all([
respondOnce.toSingleURL((request, response) => response.end(bodyData)),
respondOnce.toSingleURL((request, response) => {
let receivedBodyData = ''
@ -1293,8 +1355,10 @@ describe('net module', () => {
response.end()
})
})
]).then(([netServerUrl, nodeServerUrl]) => {
const netRequest = net.request(netServerUrl)
])
const netRequest = net.request(netServerUrl)
netRequest.end()
await new Promise((resolve) => {
netRequest.on('response', (netResponse) => {
const serverUrl = url.parse(nodeServerUrl)
const nodeOptions = {
@ -1305,7 +1369,7 @@ describe('net module', () => {
const nodeRequest = http.request(nodeOptions, res => {
res.on('data', () => {})
res.on('end', () => {
done()
resolve()
})
});
// TODO: IncomingMessage should properly extend ReadableStream in the
@ -1360,14 +1424,11 @@ describe('net module', () => {
const urlRequest = net.request(serverUrl)
urlRequest.on('response', (response) => {
response.on('data', () => {})
response.on('end', () => {})
response.on('end', () => { done() })
})
urlRequest.on('close', () => {
process.nextTick(() => {
const v8Util = process.electronBinding('v8_util')
v8Util.requestGarbageCollectionForTesting()
done()
})
process.nextTick(() => {
const v8Util = process.electronBinding('v8_util')
v8Util.requestGarbageCollectionForTesting()
})
urlRequest.end()
})
@ -1427,5 +1488,84 @@ describe('net module', () => {
urlRequest.end(randomBuffer(kOneMegaByte))
})
})
it('should finish sending data when urlRequest is unreferenced before close event for chunked encoding', (done) => {
respondOnce.toSingleURL((request, response) => {
let received = Buffer.alloc(0)
request.on('data', (data) => {
received = Buffer.concat([received, data])
})
request.on('end', () => {
response.end()
expect(received.length).to.equal(kOneMegaByte)
done()
})
}).then(serverUrl => {
const urlRequest = net.request(serverUrl)
urlRequest.on('response', (response) => {
response.on('data', () => {})
response.on('end', () => {})
})
urlRequest.chunkedEncoding = true
urlRequest.end(randomBuffer(kOneMegaByte))
const v8Util = process.electronBinding('v8_util')
v8Util.requestGarbageCollectionForTesting()
})
})
it('should finish sending data when urlRequest is unreferenced', (done) => {
respondOnce.toSingleURL((request, response) => {
let received = Buffer.alloc(0)
request.on('data', (data) => {
received = Buffer.concat([received, data])
})
request.on('end', () => {
response.end()
expect(received.length).to.equal(kOneMegaByte)
done()
})
}).then(serverUrl => {
const urlRequest = net.request(serverUrl)
urlRequest.on('response', (response) => {
response.on('data', () => {})
response.on('end', () => {})
})
urlRequest.on('close', () => {
process.nextTick(() => {
const v8Util = process.electronBinding('v8_util')
v8Util.requestGarbageCollectionForTesting()
})
})
urlRequest.end(randomBuffer(kOneMegaByte))
})
})
it('should finish sending data when urlRequest is unreferenced for chunked encoding', (done) => {
respondOnce.toSingleURL((request, response) => {
let received = Buffer.alloc(0)
request.on('data', (data) => {
received = Buffer.concat([received, data])
})
request.on('end', () => {
response.end()
expect(received.length).to.equal(kOneMegaByte)
done()
})
}).then(serverUrl => {
const urlRequest = net.request(serverUrl)
urlRequest.on('response', (response) => {
response.on('data', () => {})
response.on('end', () => {})
})
urlRequest.on('close', () => {
process.nextTick(() => {
const v8Util = process.electronBinding('v8_util')
v8Util.requestGarbageCollectionForTesting()
})
})
urlRequest.chunkedEncoding = true
urlRequest.end(randomBuffer(kOneMegaByte))
})
})
})
})