Skip to content
This repository was archived by the owner on Feb 12, 2024. It is now read-only.

Commit 83b6f60

Browse files
committed
feat: allow passing a http.Agent to the grpc client
Follows on from #3474 and allows using http.Agents with node.js to control the behaviour of the underlying node http client.
1 parent fe93ba0 commit 83b6f60

File tree

15 files changed

+110
-13
lines changed

15 files changed

+110
-13
lines changed

packages/ipfs-client/README.md

+1
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ An optional object which may have the following keys:
2828
| ---- | ---- | ------- | ----------- |
2929
| grpc | `Multiaddr` or `string` or `URL` | `undefined` | The address of a [ipfs-grpc-server][] to connect to |
3030
| http | `Multiaddr` or `string` or `URL` | `undefined` | The address of a [ipfs-http-server][] to connect to |
31+
| agent | [http.Agent](https://nodejs.org/api/http.html#http_class_http_agent) | `undefined` | A http.Agent used to control HTTP client behaviour (node.js only) |
3132

3233
### Returns
3334

packages/ipfs-grpc-client/README.md

+1
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ An optional object which may have the following keys:
3535
| Name | Type | Default | Description |
3636
| ---- | ---- | ------- | ----------- |
3737
| url | `Multiaddr` or `string` or `URL` | `undefined` | The address of a [ipfs-grpc-server][] to connect to |
38+
| agent | [http.Agent](https://nodejs.org/api/http.html#http_class_http_agent) | `undefined` | A http.Agent used to control HTTP client behaviour (node.js only) |
3839

3940
### Returns
4041

packages/ipfs-grpc-client/src/core-api/add-all.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,8 @@ module.exports = function grpcAddAll (grpc, service, opts = {}) {
7979
} = bidiToDuplex(grpc, service, {
8080
host: opts.url,
8181
debug: Boolean(process.env.DEBUG),
82-
metadata: options
82+
metadata: options,
83+
agent: opts.agent
8384
})
8485

8586
sendFiles(stream, sink)

packages/ipfs-grpc-client/src/core-api/files/ls.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ module.exports = function grpcMfsLs (grpc, service, opts = {}) {
1313
for await (const result of serverStreamToIterator(grpc, service, request, {
1414
host: opts.url,
1515
debug: Boolean(process.env.DEBUG),
16-
metadata: options
16+
metadata: options,
17+
agent: opts.agent
1718
})) {
1819
yield {
1920
name: result.name,

packages/ipfs-grpc-client/src/core-api/files/write.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,8 @@ module.exports = function grpcMfsWrite (grpc, service, opts = {}) {
3838
await clientStreamToPromise(grpc, service, stream(path, content), {
3939
host: opts.url,
4040
debug: Boolean(process.env.DEBUG),
41-
metadata: options
41+
metadata: options,
42+
agent: opts.agent
4243
})
4344
}
4445

packages/ipfs-grpc-client/src/core-api/id.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ module.exports = function grpcId (grpc, service, opts = {}) {
1111

1212
const res = await unaryToPromise(grpc, service, request, {
1313
host: opts.url,
14-
metadata: toHeaders(options)
14+
metadata: toHeaders(options),
15+
agent: opts.agent
1516
})
1617

1718
return {

packages/ipfs-grpc-client/src/grpc/transport.node.js

+26-5
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,32 @@ const WebsocketSignal = {
1313

1414
const finishSendFrame = new Uint8Array([1])
1515

16-
function WebsocketTransport () {
17-
return (opts) => {
18-
return websocketRequest(opts)
16+
/**
17+
* @param {object} options
18+
* @param {import('http').Agent} [options.agent] - http.Agent used to control HTTP client behaviour
19+
*/
20+
function WebsocketTransport (options) {
21+
/**
22+
* @param {import('@improbable-eng/grpc-web').grpc.TransportOptions} opts
23+
*/
24+
const websocketTransportFactory = (opts) => {
25+
return websocketRequest({
26+
...options,
27+
...opts
28+
})
1929
}
30+
31+
return websocketTransportFactory
2032
}
2133

34+
/**
35+
* @typedef {object} NodeTransportOptions
36+
* @property {import('http').Agent} [options.agent]
37+
*
38+
* @typedef {NodeTransportOptions & import('@improbable-eng/grpc-web').grpc.TransportOptions} WebSocketTransportOptions
39+
*
40+
* @param {WebSocketTransportOptions} options
41+
*/
2242
function websocketRequest (options) {
2343
const webSocketAddress = constructWebSocketAddress(options.url)
2444

@@ -54,7 +74,7 @@ function websocketRequest (options) {
5474
}
5575
},
5676
start: (metadata) => {
57-
ws = new WebSocket(webSocketAddress, ['grpc-websockets'])
77+
ws = new WebSocket(webSocketAddress, ['grpc-websockets'], options)
5878
ws.binaryType = 'arraybuffer'
5979
ws.onopen = function () {
6080
options.debug && debug('websocketRequest.onopen')
@@ -93,7 +113,8 @@ function constructWebSocketAddress (url) {
93113
} else if (url.substr(0, 7) === 'http://') {
94114
return `ws://${url.substr(7)}`
95115
}
96-
throw new Error('Websocket transport constructed with non-https:// or http:// host.')
116+
117+
throw new Error('Websocket transport url must start with ws:// or wss:// or http:// or https://')
97118
}
98119

99120
function headersToBytes (headers) {

packages/ipfs-grpc-client/src/index.js

+6-3
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
11
'use strict'
22

3-
const transport = require('./grpc/transport')
43
const toUrlString = require('ipfs-core-utils/src/to-url-string')
54
const loadServices = require('./utils/load-services')
65
const { grpc } = require('@improbable-eng/grpc-web')
7-
grpc.setDefaultTransport(transport())
86

97
const service = loadServices()
108

@@ -21,7 +19,12 @@ function normaliseUrls (opts) {
2119
})
2220
}
2321

24-
module.exports = function createClient (opts = {}) {
22+
/**
23+
* @param {object} opts
24+
* @param {string} opts.url - The URL to connect to as a URL or Multiaddr
25+
* @param {import('http').Agent} [opts.agent] - http.Agent used to control HTTP client behaviour (node.js only)
26+
*/
27+
module.exports = function createClient (opts = { url: '' }) {
2528
opts.url = toUrlString(opts.url)
2629

2730
// @improbable-eng/grpc-web requires http:// protocol URLs, not ws://

packages/ipfs-grpc-client/src/utils/bidi-to-duplex.js

+8-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
const pushable = require('it-pushable')
44
const errCode = require('err-code')
55
const toHeaders = require('./to-headers')
6+
const transport = require('../grpc/transport')
67

78
async function sendMessages (service, client, source) {
89
for await (const obj of source) {
@@ -23,6 +24,7 @@ async function sendMessages (service, client, source) {
2324
* @param {string} options.host - The remote host
2425
* @param {boolean} [options.debug] - Whether to print debug messages
2526
* @param {object} [options.metadata] - Metadata sent as headers
27+
* @param {import('http').Agent} [options.agent] - http.Agent used to control HTTP client behaviour (node.js only)
2628
* @returns {{ source: AsyncIterable<object>, sink: { push: Function, end: Function } }}
2729
**/
2830
module.exports = function bidiToDuplex (grpc, service, options) {
@@ -32,7 +34,12 @@ module.exports = function bidiToDuplex (grpc, service, options) {
3234
// @ts-ignore
3335
const sink = pushable()
3436

35-
const client = grpc.client(service, options)
37+
const client = grpc.client(service, {
38+
...options,
39+
transport: transport({
40+
agent: options.agent
41+
})
42+
})
3643
client.onMessage(message => {
3744
sink.push(message)
3845
})

packages/ipfs-grpc-client/src/utils/client-stream-to-promise.js

+1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ const bidiToDuplex = require('./bidi-to-duplex')
1515
* @param {string} options.host - The remote host
1616
* @param {boolean} [options.debug] - Whether to print debug messages
1717
* @param {object} [options.metadata] - Metadata sent as headers
18+
* @param {import('http').Agent} [options.agent] - http.Agent used to control HTTP client behaviour (node.js only)
1819
* @returns {Promise<Object>} - A promise that resolves to a response object
1920
**/
2021
module.exports = async function clientStreamToPromise (grpc, service, source, options) {

packages/ipfs-grpc-client/src/utils/server-stream-to-iterator.js

+1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ const bidiToDuplex = require('./bidi-to-duplex')
1414
* @param {string} options.host - The remote host
1515
* @param {boolean} [options.debug] - Whether to print debug messages
1616
* @param {object} [options.metadata] - Metadata sent as headers
17+
* @param {import('http').Agent} [options.agent] - http.Agent used to control HTTP client behaviour (node.js only)
1718
* @returns {AsyncIterable<object>}
1819
**/
1920
module.exports = function serverStreamToIterator (grpc, service, request, options) {

packages/ipfs-grpc-client/src/utils/unary-to-promise.js

+1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ const bidiToDuplex = require('./bidi-to-duplex')
1515
* @param {string} options.host - The remote host
1616
* @param {boolean} [options.debug] - Whether to print debug messages
1717
* @param {object} [options.metadata] - Metadata sent as headers
18+
* @param {import('http').Agent} [options.agent] - http.Agent used to control HTTP client behaviour (node.js only)
1819
* @returns {Promise<Object>} - A promise that resolves to a response object
1920
**/
2021
module.exports = function unaryToPromise (grpc, service, request, options) {
+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/* eslint-env mocha */
2+
'use strict'
3+
4+
const grpcClient = require('../src')
5+
const WebSocket = require('ws')
6+
7+
function startServer () {
8+
return new Promise((resolve) => {
9+
const wss = new WebSocket.Server({ port: 0 })
10+
11+
wss.on('listening', () => {
12+
resolve({
13+
port: wss.address().port,
14+
close: () => wss.close()
15+
})
16+
})
17+
18+
wss.on('connection', (ws) => {
19+
ws.once('message', () => {
20+
ws.send('')
21+
ws.end()
22+
})
23+
})
24+
})
25+
}
26+
27+
describe('agent', function () {
28+
it('uses the passed agent', async () => {
29+
const server = await startServer()
30+
31+
try {
32+
await new Promise((resolve) => {
33+
const ipfs = grpcClient({
34+
url: `http://localhost:${server.port}`,
35+
agent: {
36+
addRequest () {
37+
// an agent method was invoked
38+
resolve()
39+
}
40+
}
41+
})
42+
43+
ipfs.id().catch(() => {})
44+
})
45+
} finally {
46+
server.close()
47+
}
48+
})
49+
})
+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
'use strict'
2+
3+
require('./agent')

packages/ipfs-http-client/test/node/agent.js

+5
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,11 @@ describe('agent', function () {
8282

8383
break
8484
}
85+
86+
if (i === 4) {
87+
// should have first two responses by now
88+
expect(responses).to.have.lengthOf(2)
89+
}
8590
}
8691

8792
// wait for the final request to arrive

0 commit comments

Comments
 (0)