1# Dispatcher 2 3Extends: `events.EventEmitter` 4 5Dispatcher is the core API used to dispatch requests. 6 7Requests are not guaranteed to be dispatched in order of invocation. 8 9## Instance Methods 10 11### `Dispatcher.close([callback]): Promise` 12 13Closes the dispatcher and gracefully waits for enqueued requests to complete before resolving. 14 15Arguments: 16 17* **callback** `(error: Error | null, data: null) => void` (optional) 18 19Returns: `void | Promise<null>` - Only returns a `Promise` if no `callback` argument was passed 20 21```js 22dispatcher.close() // -> Promise 23dispatcher.close(() => {}) // -> void 24``` 25 26#### Example - Request resolves before Client closes 27 28```js 29import { createServer } from 'http' 30import { Client } from 'undici' 31import { once } from 'events' 32 33const server = createServer((request, response) => { 34 response.end('undici') 35}).listen() 36 37await once(server, 'listening') 38 39const client = new Client(`http://localhost:${server.address().port}`) 40 41try { 42 const { body } = await client.request({ 43 path: '/', 44 method: 'GET' 45 }) 46 body.setEncoding('utf8') 47 body.on('data', console.log) 48} catch (error) {} 49 50await client.close() 51 52console.log('Client closed') 53server.close() 54``` 55 56### `Dispatcher.connect(options[, callback])` 57 58Starts two-way communications with the requested resource using [HTTP CONNECT](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/CONNECT). 59 60Arguments: 61 62* **options** `ConnectOptions` 63* **callback** `(err: Error | null, data: ConnectData | null) => void` (optional) 64 65Returns: `void | Promise<ConnectData>` - Only returns a `Promise` if no `callback` argument was passed 66 67#### Parameter: `ConnectOptions` 68 69* **path** `string` 70* **headers** `UndiciHeaders` (optional) - Default: `null` 71* **signal** `AbortSignal | events.EventEmitter | null` (optional) - Default: `null` 72* **opaque** `unknown` (optional) - This argument parameter is passed through to `ConnectData` 73 74#### Parameter: `ConnectData` 75 76* **statusCode** `number` 77* **headers** `Record<string, string | string[] | undefined>` 78* **socket** `stream.Duplex` 79* **opaque** `unknown` 80 81#### Example - Connect request with echo 82 83```js 84import { createServer } from 'http' 85import { Client } from 'undici' 86import { once } from 'events' 87 88const server = createServer((request, response) => { 89 throw Error('should never get here') 90}).listen() 91 92server.on('connect', (req, socket, head) => { 93 socket.write('HTTP/1.1 200 Connection established\r\n\r\n') 94 95 let data = head.toString() 96 socket.on('data', (buf) => { 97 data += buf.toString() 98 }) 99 100 socket.on('end', () => { 101 socket.end(data) 102 }) 103}) 104 105await once(server, 'listening') 106 107const client = new Client(`http://localhost:${server.address().port}`) 108 109try { 110 const { socket } = await client.connect({ 111 path: '/' 112 }) 113 const wanted = 'Body' 114 let data = '' 115 socket.on('data', d => { data += d }) 116 socket.on('end', () => { 117 console.log(`Data received: ${data.toString()} | Data wanted: ${wanted}`) 118 client.close() 119 server.close() 120 }) 121 socket.write(wanted) 122 socket.end() 123} catch (error) { } 124``` 125 126### `Dispatcher.destroy([error, callback]): Promise` 127 128Destroy the dispatcher abruptly with the given error. All the pending and running requests will be asynchronously aborted and error. Since this operation is asynchronously dispatched there might still be some progress on dispatched requests. 129 130Both arguments are optional; the method can be called in four different ways: 131 132Arguments: 133 134* **error** `Error | null` (optional) 135* **callback** `(error: Error | null, data: null) => void` (optional) 136 137Returns: `void | Promise<void>` - Only returns a `Promise` if no `callback` argument was passed 138 139```js 140dispatcher.destroy() // -> Promise 141dispatcher.destroy(new Error()) // -> Promise 142dispatcher.destroy(() => {}) // -> void 143dispatcher.destroy(new Error(), () => {}) // -> void 144``` 145 146#### Example - Request is aborted when Client is destroyed 147 148```js 149import { createServer } from 'http' 150import { Client } from 'undici' 151import { once } from 'events' 152 153const server = createServer((request, response) => { 154 response.end() 155}).listen() 156 157await once(server, 'listening') 158 159const client = new Client(`http://localhost:${server.address().port}`) 160 161try { 162 const request = client.request({ 163 path: '/', 164 method: 'GET' 165 }) 166 client.destroy() 167 .then(() => { 168 console.log('Client destroyed') 169 server.close() 170 }) 171 await request 172} catch (error) { 173 console.error(error) 174} 175``` 176 177### `Dispatcher.dispatch(options, handler)` 178 179This is the low level API which all the preceding APIs are implemented on top of. 180This API is expected to evolve through semver-major versions and is less stable than the preceding higher level APIs. 181It is primarily intended for library developers who implement higher level APIs on top of this. 182 183Arguments: 184 185* **options** `DispatchOptions` 186* **handler** `DispatchHandler` 187 188Returns: `Boolean` - `false` if dispatcher is busy and further dispatch calls won't make any progress until the `'drain'` event has been emitted. 189 190#### Parameter: `DispatchOptions` 191 192* **origin** `string | URL` 193* **path** `string` 194* **method** `string` 195* **reset** `boolean` (optional) - Default: `false` - If `false`, the request will attempt to create a long-living connection by sending the `connection: keep-alive` header,otherwise will attempt to close it immediately after response by sending `connection: close` within the request and closing the socket afterwards. 196* **body** `string | Buffer | Uint8Array | stream.Readable | Iterable | AsyncIterable | null` (optional) - Default: `null` 197* **headers** `UndiciHeaders | string[]` (optional) - Default: `null`. 198* **query** `Record<string, any> | null` (optional) - Default: `null` - Query string params to be embedded in the request URL. Note that both keys and values of query are encoded using `encodeURIComponent`. If for some reason you need to send them unencoded, embed query params into path directly instead. 199* **idempotent** `boolean` (optional) - Default: `true` if `method` is `'HEAD'` or `'GET'` - Whether the requests can be safely retried or not. If `false` the request won't be sent until all preceding requests in the pipeline has completed. 200* **blocking** `boolean` (optional) - Default: `false` - Whether the response is expected to take a long time and would end up blocking the pipeline. When this is set to `true` further pipelining will be avoided on the same connection until headers have been received. 201* **upgrade** `string | null` (optional) - Default: `null` - Upgrade the request. Should be used to specify the kind of upgrade i.e. `'Websocket'`. 202* **bodyTimeout** `number | null` (optional) - The timeout after which a request will time out, in milliseconds. Monitors time between receiving body data. Use `0` to disable it entirely. Defaults to 300 seconds. 203* **headersTimeout** `number | null` (optional) - The amount of time the parser will wait to receive the complete HTTP headers while not sending the request. Defaults to 300 seconds. 204* **throwOnError** `boolean` (optional) - Default: `false` - Whether Undici should throw an error upon receiving a 4xx or 5xx response from the server. 205 206#### Parameter: `DispatchHandler` 207 208* **onConnect** `(abort: () => void, context: object) => void` - Invoked before request is dispatched on socket. May be invoked multiple times when a request is retried when the request at the head of the pipeline fails. 209* **onError** `(error: Error) => void` - Invoked when an error has occurred. May not throw. 210* **onUpgrade** `(statusCode: number, headers: Buffer[], socket: Duplex) => void` (optional) - Invoked when request is upgraded. Required if `DispatchOptions.upgrade` is defined or `DispatchOptions.method === 'CONNECT'`. 211* **onHeaders** `(statusCode: number, headers: Buffer[], resume: () => void, statusText: string) => boolean` - Invoked when statusCode and headers have been received. May be invoked multiple times due to 1xx informational headers. Not required for `upgrade` requests. 212* **onData** `(chunk: Buffer) => boolean` - Invoked when response payload data is received. Not required for `upgrade` requests. 213* **onComplete** `(trailers: Buffer[]) => void` - Invoked when response payload and trailers have been received and the request has completed. Not required for `upgrade` requests. 214* **onBodySent** `(chunk: string | Buffer | Uint8Array) => void` - Invoked when a body chunk is sent to the server. Not required. For a stream or iterable body this will be invoked for every chunk. For other body types, it will be invoked once after the body is sent. 215 216#### Example 1 - Dispatch GET request 217 218```js 219import { createServer } from 'http' 220import { Client } from 'undici' 221import { once } from 'events' 222 223const server = createServer((request, response) => { 224 response.end('Hello, World!') 225}).listen() 226 227await once(server, 'listening') 228 229const client = new Client(`http://localhost:${server.address().port}`) 230 231const data = [] 232 233client.dispatch({ 234 path: '/', 235 method: 'GET', 236 headers: { 237 'x-foo': 'bar' 238 } 239}, { 240 onConnect: () => { 241 console.log('Connected!') 242 }, 243 onError: (error) => { 244 console.error(error) 245 }, 246 onHeaders: (statusCode, headers) => { 247 console.log(`onHeaders | statusCode: ${statusCode} | headers: ${headers}`) 248 }, 249 onData: (chunk) => { 250 console.log('onData: chunk received') 251 data.push(chunk) 252 }, 253 onComplete: (trailers) => { 254 console.log(`onComplete | trailers: ${trailers}`) 255 const res = Buffer.concat(data).toString('utf8') 256 console.log(`Data: ${res}`) 257 client.close() 258 server.close() 259 } 260}) 261``` 262 263#### Example 2 - Dispatch Upgrade Request 264 265```js 266import { createServer } from 'http' 267import { Client } from 'undici' 268import { once } from 'events' 269 270const server = createServer((request, response) => { 271 response.end() 272}).listen() 273 274await once(server, 'listening') 275 276server.on('upgrade', (request, socket, head) => { 277 console.log('Node.js Server - upgrade event') 278 socket.write('HTTP/1.1 101 Web Socket Protocol Handshake\r\n') 279 socket.write('Upgrade: WebSocket\r\n') 280 socket.write('Connection: Upgrade\r\n') 281 socket.write('\r\n') 282 socket.end() 283}) 284 285const client = new Client(`http://localhost:${server.address().port}`) 286 287client.dispatch({ 288 path: '/', 289 method: 'GET', 290 upgrade: 'websocket' 291}, { 292 onConnect: () => { 293 console.log('Undici Client - onConnect') 294 }, 295 onError: (error) => { 296 console.log('onError') // shouldn't print 297 }, 298 onUpgrade: (statusCode, headers, socket) => { 299 console.log('Undici Client - onUpgrade') 300 console.log(`onUpgrade Headers: ${headers}`) 301 socket.on('data', buffer => { 302 console.log(buffer.toString('utf8')) 303 }) 304 socket.on('end', () => { 305 client.close() 306 server.close() 307 }) 308 socket.end() 309 } 310}) 311``` 312 313#### Example 3 - Dispatch POST request 314 315```js 316import { createServer } from 'http' 317import { Client } from 'undici' 318import { once } from 'events' 319 320const server = createServer((request, response) => { 321 request.on('data', (data) => { 322 console.log(`Request Data: ${data.toString('utf8')}`) 323 const body = JSON.parse(data) 324 body.message = 'World' 325 response.end(JSON.stringify(body)) 326 }) 327}).listen() 328 329await once(server, 'listening') 330 331const client = new Client(`http://localhost:${server.address().port}`) 332 333const data = [] 334 335client.dispatch({ 336 path: '/', 337 method: 'POST', 338 headers: { 339 'content-type': 'application/json' 340 }, 341 body: JSON.stringify({ message: 'Hello' }) 342}, { 343 onConnect: () => { 344 console.log('Connected!') 345 }, 346 onError: (error) => { 347 console.error(error) 348 }, 349 onHeaders: (statusCode, headers) => { 350 console.log(`onHeaders | statusCode: ${statusCode} | headers: ${headers}`) 351 }, 352 onData: (chunk) => { 353 console.log('onData: chunk received') 354 data.push(chunk) 355 }, 356 onComplete: (trailers) => { 357 console.log(`onComplete | trailers: ${trailers}`) 358 const res = Buffer.concat(data).toString('utf8') 359 console.log(`Response Data: ${res}`) 360 client.close() 361 server.close() 362 } 363}) 364``` 365 366### `Dispatcher.pipeline(options, handler)` 367 368For easy use with [stream.pipeline](https://nodejs.org/api/stream.html#stream_stream_pipeline_source_transforms_destination_callback). The `handler` argument should return a `Readable` from which the result will be read. Usually it should just return the `body` argument unless some kind of transformation needs to be performed based on e.g. `headers` or `statusCode`. The `handler` should validate the response and save any required state. If there is an error, it should be thrown. The function returns a `Duplex` which writes to the request and reads from the response. 369 370Arguments: 371 372* **options** `PipelineOptions` 373* **handler** `(data: PipelineHandlerData) => stream.Readable` 374 375Returns: `stream.Duplex` 376 377#### Parameter: PipelineOptions 378 379Extends: [`RequestOptions`](#parameter-requestoptions) 380 381* **objectMode** `boolean` (optional) - Default: `false` - Set to `true` if the `handler` will return an object stream. 382 383#### Parameter: PipelineHandlerData 384 385* **statusCode** `number` 386* **headers** `Record<string, string | string[] | undefined>` 387* **opaque** `unknown` 388* **body** `stream.Readable` 389* **context** `object` 390* **onInfo** `({statusCode: number, headers: Record<string, string | string[]>}) => void | null` (optional) - Default: `null` - Callback collecting all the info headers (HTTP 100-199) received. 391 392#### Example 1 - Pipeline Echo 393 394```js 395import { Readable, Writable, PassThrough, pipeline } from 'stream' 396import { createServer } from 'http' 397import { Client } from 'undici' 398import { once } from 'events' 399 400const server = createServer((request, response) => { 401 request.pipe(response) 402}).listen() 403 404await once(server, 'listening') 405 406const client = new Client(`http://localhost:${server.address().port}`) 407 408let res = '' 409 410pipeline( 411 new Readable({ 412 read () { 413 this.push(Buffer.from('undici')) 414 this.push(null) 415 } 416 }), 417 client.pipeline({ 418 path: '/', 419 method: 'GET' 420 }, ({ statusCode, headers, body }) => { 421 console.log(`response received ${statusCode}`) 422 console.log('headers', headers) 423 return pipeline(body, new PassThrough(), () => {}) 424 }), 425 new Writable({ 426 write (chunk, _, callback) { 427 res += chunk.toString() 428 callback() 429 }, 430 final (callback) { 431 console.log(`Response pipelined to writable: ${res}`) 432 callback() 433 } 434 }), 435 error => { 436 if (error) { 437 console.error(error) 438 } 439 440 client.close() 441 server.close() 442 } 443) 444``` 445 446### `Dispatcher.request(options[, callback])` 447 448Performs a HTTP request. 449 450Non-idempotent requests will not be pipelined in order 451to avoid indirect failures. 452 453Idempotent requests will be automatically retried if 454they fail due to indirect failure from the request 455at the head of the pipeline. This does not apply to 456idempotent requests with a stream request body. 457 458All response bodies must always be fully consumed or destroyed. 459 460Arguments: 461 462* **options** `RequestOptions` 463* **callback** `(error: Error | null, data: ResponseData) => void` (optional) 464 465Returns: `void | Promise<ResponseData>` - Only returns a `Promise` if no `callback` argument was passed. 466 467#### Parameter: `RequestOptions` 468 469Extends: [`DispatchOptions`](#parameter-dispatchoptions) 470 471* **opaque** `unknown` (optional) - Default: `null` - Used for passing through context to `ResponseData`. 472* **signal** `AbortSignal | events.EventEmitter | null` (optional) - Default: `null`. 473* **onInfo** `({statusCode: number, headers: Record<string, string | string[]>}) => void | null` (optional) - Default: `null` - Callback collecting all the info headers (HTTP 100-199) received. 474 475The `RequestOptions.method` property should not be value `'CONNECT'`. 476 477#### Parameter: `ResponseData` 478 479* **statusCode** `number` 480* **headers** `Record<string, string | string[]>` - Note that all header keys are lower-cased, e. g. `content-type`. 481* **body** `stream.Readable` which also implements [the body mixin from the Fetch Standard](https://fetch.spec.whatwg.org/#body-mixin). 482* **trailers** `Record<string, string>` - This object starts out 483 as empty and will be mutated to contain trailers after `body` has emitted `'end'`. 484* **opaque** `unknown` 485* **context** `object` 486 487`body` contains the following additional [body mixin](https://fetch.spec.whatwg.org/#body-mixin) methods and properties: 488 489- `text()` 490- `json()` 491- `arrayBuffer()` 492- `body` 493- `bodyUsed` 494 495`body` can not be consumed twice. For example, calling `text()` after `json()` throws `TypeError`. 496 497`body` contains the following additional extensions: 498 499- `dump({ limit: Integer })`, dump the response by reading up to `limit` bytes without killing the socket (optional) - Default: 262144. 500 501Note that body will still be a `Readable` even if it is empty, but attempting to deserialize it with `json()` will result in an exception. Recommended way to ensure there is a body to deserialize is to check if status code is not 204, and `content-type` header starts with `application/json`. 502 503#### Example 1 - Basic GET Request 504 505```js 506import { createServer } from 'http' 507import { Client } from 'undici' 508import { once } from 'events' 509 510const server = createServer((request, response) => { 511 response.end('Hello, World!') 512}).listen() 513 514await once(server, 'listening') 515 516const client = new Client(`http://localhost:${server.address().port}`) 517 518try { 519 const { body, headers, statusCode, trailers } = await client.request({ 520 path: '/', 521 method: 'GET' 522 }) 523 console.log(`response received ${statusCode}`) 524 console.log('headers', headers) 525 body.setEncoding('utf8') 526 body.on('data', console.log) 527 body.on('end', () => { 528 console.log('trailers', trailers) 529 }) 530 531 client.close() 532 server.close() 533} catch (error) { 534 console.error(error) 535} 536``` 537 538#### Example 2 - Aborting a request 539 540> Node.js v15+ is required to run this example 541 542```js 543import { createServer } from 'http' 544import { Client } from 'undici' 545import { once } from 'events' 546 547const server = createServer((request, response) => { 548 response.end('Hello, World!') 549}).listen() 550 551await once(server, 'listening') 552 553const client = new Client(`http://localhost:${server.address().port}`) 554const abortController = new AbortController() 555 556try { 557 client.request({ 558 path: '/', 559 method: 'GET', 560 signal: abortController.signal 561 }) 562} catch (error) { 563 console.error(error) // should print an RequestAbortedError 564 client.close() 565 server.close() 566} 567 568abortController.abort() 569``` 570 571Alternatively, any `EventEmitter` that emits an `'abort'` event may be used as an abort controller: 572 573```js 574import { createServer } from 'http' 575import { Client } from 'undici' 576import EventEmitter, { once } from 'events' 577 578const server = createServer((request, response) => { 579 response.end('Hello, World!') 580}).listen() 581 582await once(server, 'listening') 583 584const client = new Client(`http://localhost:${server.address().port}`) 585const ee = new EventEmitter() 586 587try { 588 client.request({ 589 path: '/', 590 method: 'GET', 591 signal: ee 592 }) 593} catch (error) { 594 console.error(error) // should print an RequestAbortedError 595 client.close() 596 server.close() 597} 598 599ee.emit('abort') 600``` 601 602Destroying the request or response body will have the same effect. 603 604```js 605import { createServer } from 'http' 606import { Client } from 'undici' 607import { once } from 'events' 608 609const server = createServer((request, response) => { 610 response.end('Hello, World!') 611}).listen() 612 613await once(server, 'listening') 614 615const client = new Client(`http://localhost:${server.address().port}`) 616 617try { 618 const { body } = await client.request({ 619 path: '/', 620 method: 'GET' 621 }) 622 body.destroy() 623} catch (error) { 624 console.error(error) // should print an RequestAbortedError 625 client.close() 626 server.close() 627} 628``` 629 630### `Dispatcher.stream(options, factory[, callback])` 631 632A faster version of `Dispatcher.request`. This method expects the second argument `factory` to return a [`stream.Writable`](https://nodejs.org/api/stream.html#stream_class_stream_writable) stream which the response will be written to. This improves performance by avoiding creating an intermediate [`stream.Readable`](https://nodejs.org/api/stream.html#stream_readable_streams) stream when the user expects to directly pipe the response body to a [`stream.Writable`](https://nodejs.org/api/stream.html#stream_class_stream_writable) stream. 633 634As demonstrated in [Example 1 - Basic GET stream request](#example-1---basic-get-stream-request), it is recommended to use the `option.opaque` property to avoid creating a closure for the `factory` method. This pattern works well with Node.js Web Frameworks such as [Fastify](https://fastify.io). See [Example 2 - Stream to Fastify Response](#example-2---stream-to-fastify-response) for more details. 635 636Arguments: 637 638* **options** `RequestOptions` 639* **factory** `(data: StreamFactoryData) => stream.Writable` 640* **callback** `(error: Error | null, data: StreamData) => void` (optional) 641 642Returns: `void | Promise<StreamData>` - Only returns a `Promise` if no `callback` argument was passed 643 644#### Parameter: `StreamFactoryData` 645 646* **statusCode** `number` 647* **headers** `Record<string, string | string[] | undefined>` 648* **opaque** `unknown` 649* **onInfo** `({statusCode: number, headers: Record<string, string | string[]>}) => void | null` (optional) - Default: `null` - Callback collecting all the info headers (HTTP 100-199) received. 650 651#### Parameter: `StreamData` 652 653* **opaque** `unknown` 654* **trailers** `Record<string, string>` 655* **context** `object` 656 657#### Example 1 - Basic GET stream request 658 659```js 660import { createServer } from 'http' 661import { Client } from 'undici' 662import { once } from 'events' 663import { Writable } from 'stream' 664 665const server = createServer((request, response) => { 666 response.end('Hello, World!') 667}).listen() 668 669await once(server, 'listening') 670 671const client = new Client(`http://localhost:${server.address().port}`) 672 673const bufs = [] 674 675try { 676 await client.stream({ 677 path: '/', 678 method: 'GET', 679 opaque: { bufs } 680 }, ({ statusCode, headers, opaque: { bufs } }) => { 681 console.log(`response received ${statusCode}`) 682 console.log('headers', headers) 683 return new Writable({ 684 write (chunk, encoding, callback) { 685 bufs.push(chunk) 686 callback() 687 } 688 }) 689 }) 690 691 console.log(Buffer.concat(bufs).toString('utf-8')) 692 693 client.close() 694 server.close() 695} catch (error) { 696 console.error(error) 697} 698``` 699 700#### Example 2 - Stream to Fastify Response 701 702In this example, a (fake) request is made to the fastify server using `fastify.inject()`. This request then executes the fastify route handler which makes a subsequent request to the raw Node.js http server using `undici.dispatcher.stream()`. The fastify response is passed to the `opaque` option so that undici can tap into the underlying writable stream using `response.raw`. This methodology demonstrates how one could use undici and fastify together to create fast-as-possible requests from one backend server to another. 703 704```js 705import { createServer } from 'http' 706import { Client } from 'undici' 707import { once } from 'events' 708import fastify from 'fastify' 709 710const nodeServer = createServer((request, response) => { 711 response.end('Hello, World! From Node.js HTTP Server') 712}).listen() 713 714await once(nodeServer, 'listening') 715 716console.log('Node Server listening') 717 718const nodeServerUndiciClient = new Client(`http://localhost:${nodeServer.address().port}`) 719 720const fastifyServer = fastify() 721 722fastifyServer.route({ 723 url: '/', 724 method: 'GET', 725 handler: (request, response) => { 726 nodeServerUndiciClient.stream({ 727 path: '/', 728 method: 'GET', 729 opaque: response 730 }, ({ opaque }) => opaque.raw) 731 } 732}) 733 734await fastifyServer.listen() 735 736console.log('Fastify Server listening') 737 738const fastifyServerUndiciClient = new Client(`http://localhost:${fastifyServer.server.address().port}`) 739 740try { 741 const { statusCode, body } = await fastifyServerUndiciClient.request({ 742 path: '/', 743 method: 'GET' 744 }) 745 746 console.log(`response received ${statusCode}`) 747 body.setEncoding('utf8') 748 body.on('data', console.log) 749 750 nodeServerUndiciClient.close() 751 fastifyServerUndiciClient.close() 752 fastifyServer.close() 753 nodeServer.close() 754} catch (error) { } 755``` 756 757### `Dispatcher.upgrade(options[, callback])` 758 759Upgrade to a different protocol. Visit [MDN - HTTP - Protocol upgrade mechanism](https://developer.mozilla.org/en-US/docs/Web/HTTP/Protocol_upgrade_mechanism) for more details. 760 761Arguments: 762 763* **options** `UpgradeOptions` 764 765* **callback** `(error: Error | null, data: UpgradeData) => void` (optional) 766 767Returns: `void | Promise<UpgradeData>` - Only returns a `Promise` if no `callback` argument was passed 768 769#### Parameter: `UpgradeOptions` 770 771* **path** `string` 772* **method** `string` (optional) - Default: `'GET'` 773* **headers** `UndiciHeaders` (optional) - Default: `null` 774* **protocol** `string` (optional) - Default: `'Websocket'` - A string of comma separated protocols, in descending preference order. 775* **signal** `AbortSignal | EventEmitter | null` (optional) - Default: `null` 776 777#### Parameter: `UpgradeData` 778 779* **headers** `http.IncomingHeaders` 780* **socket** `stream.Duplex` 781* **opaque** `unknown` 782 783#### Example 1 - Basic Upgrade Request 784 785```js 786import { createServer } from 'http' 787import { Client } from 'undici' 788import { once } from 'events' 789 790const server = createServer((request, response) => { 791 response.statusCode = 101 792 response.setHeader('connection', 'upgrade') 793 response.setHeader('upgrade', request.headers.upgrade) 794 response.end() 795}).listen() 796 797await once(server, 'listening') 798 799const client = new Client(`http://localhost:${server.address().port}`) 800 801try { 802 const { headers, socket } = await client.upgrade({ 803 path: '/', 804 }) 805 socket.on('end', () => { 806 console.log(`upgrade: ${headers.upgrade}`) // upgrade: Websocket 807 client.close() 808 server.close() 809 }) 810 socket.end() 811} catch (error) { 812 console.error(error) 813 client.close() 814 server.close() 815} 816``` 817 818## Instance Events 819 820### Event: `'connect'` 821 822Parameters: 823 824* **origin** `URL` 825* **targets** `Array<Dispatcher>` 826 827### Event: `'disconnect'` 828 829Parameters: 830 831* **origin** `URL` 832* **targets** `Array<Dispatcher>` 833* **error** `Error` 834 835### Event: `'connectionError'` 836 837Parameters: 838 839* **origin** `URL` 840* **targets** `Array<Dispatcher>` 841* **error** `Error` 842 843Emitted when dispatcher fails to connect to 844origin. 845 846### Event: `'drain'` 847 848Parameters: 849 850* **origin** `URL` 851 852Emitted when dispatcher is no longer busy. 853 854## Parameter: `UndiciHeaders` 855 856* `Record<string, string | string[] | undefined> | string[] | null` 857 858Header arguments such as `options.headers` in [`Client.dispatch`](Client.md#clientdispatchoptions-handlers) can be specified in two forms; either as an object specified by the `Record<string, string | string[] | undefined>` (`IncomingHttpHeaders`) type, or an array of strings. An array representation of a header list must have an even length or an `InvalidArgumentError` will be thrown. 859 860Keys are lowercase and values are not modified. 861 862Response headers will derive a `host` from the `url` of the [Client](Client.md#class-client) instance if no `host` header was previously specified. 863 864### Example 1 - Object 865 866```js 867{ 868 'content-length': '123', 869 'content-type': 'text/plain', 870 connection: 'keep-alive', 871 host: 'mysite.com', 872 accept: '*/*' 873} 874``` 875 876### Example 2 - Array 877 878```js 879[ 880 'content-length', '123', 881 'content-type', 'text/plain', 882 'connection', 'keep-alive', 883 'host', 'mysite.com', 884 'accept', '*/*' 885] 886``` 887