fix happy eyeballs
This commit is contained in:
parent
e17585819a
commit
95ff76a95c
|
|
@ -313,10 +313,10 @@ export abstract class APIRequestContext extends SdkObject {
|
||||||
const startAt = monotonicTime();
|
const startAt = monotonicTime();
|
||||||
const timings: Record<'startAt' | 'requestFinishAt' | 'dnsLookupAt' | 'tcpConnectionAt' | 'tlsHandshakeAt' | 'firstByteAt' | 'endAt', number | undefined> = {
|
const timings: Record<'startAt' | 'requestFinishAt' | 'dnsLookupAt' | 'tcpConnectionAt' | 'tlsHandshakeAt' | 'firstByteAt' | 'endAt', number | undefined> = {
|
||||||
startAt,
|
startAt,
|
||||||
requestFinishAt: undefined,
|
|
||||||
dnsLookupAt: undefined,
|
dnsLookupAt: undefined,
|
||||||
tcpConnectionAt: undefined,
|
tcpConnectionAt: undefined,
|
||||||
tlsHandshakeAt: undefined,
|
tlsHandshakeAt: undefined,
|
||||||
|
requestFinishAt: undefined,
|
||||||
firstByteAt: undefined,
|
firstByteAt: undefined,
|
||||||
endAt: undefined
|
endAt: undefined
|
||||||
};
|
};
|
||||||
|
|
@ -326,12 +326,15 @@ export abstract class APIRequestContext extends SdkObject {
|
||||||
response.once('end', () => { timings.endAt = monotonicTime(); });
|
response.once('end', () => { timings.endAt = monotonicTime(); });
|
||||||
|
|
||||||
const notifyRequestFinished = (body?: Buffer) => {
|
const notifyRequestFinished = (body?: Buffer) => {
|
||||||
const send = timings.requestFinishAt! - startAt;
|
const harTimings: har.Timings = {
|
||||||
const dnsLookup = timings.dnsLookupAt ? startAt - timings.dnsLookupAt : undefined;
|
send: timings.requestFinishAt! - startAt,
|
||||||
const tcpConnection = timings.tcpConnectionAt! - (timings.dnsLookupAt ?? startAt);
|
wait: timings.firstByteAt! - timings.requestFinishAt!,
|
||||||
const tlsHandshake = timings.tlsHandshakeAt ? (timings.tlsHandshakeAt - timings.tcpConnectionAt!) : undefined;
|
receive: timings.endAt! - timings.firstByteAt!,
|
||||||
const firstByte = timings.firstByteAt! - (timings.tlsHandshakeAt ?? timings.tcpConnectionAt!);
|
dns: timings.dnsLookupAt ? timings.dnsLookupAt - startAt : -1,
|
||||||
const contentTransfer = timings.endAt! - timings.firstByteAt!;
|
connect: (timings.tlsHandshakeAt ?? timings.tcpConnectionAt!) - startAt,
|
||||||
|
ssl: timings.tlsHandshakeAt ? timings.tlsHandshakeAt - timings.tcpConnectionAt! : -1,
|
||||||
|
blocked: -1, // TODO: time spent in queue waiting for a network connection
|
||||||
|
};
|
||||||
|
|
||||||
const requestFinishedEvent: APIRequestFinishedEvent = {
|
const requestFinishedEvent: APIRequestFinishedEvent = {
|
||||||
requestEvent,
|
requestEvent,
|
||||||
|
|
@ -342,15 +345,7 @@ export abstract class APIRequestContext extends SdkObject {
|
||||||
rawHeaders: response.rawHeaders,
|
rawHeaders: response.rawHeaders,
|
||||||
cookies,
|
cookies,
|
||||||
body,
|
body,
|
||||||
timings: {
|
timings: harTimings,
|
||||||
send,
|
|
||||||
wait: firstByte,
|
|
||||||
receive: contentTransfer,
|
|
||||||
dns: dnsLookup,
|
|
||||||
connect: tcpConnection,
|
|
||||||
ssl: tlsHandshake,
|
|
||||||
blocked: firstByte,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
this.emit(APIRequestContext.Events.RequestFinished, requestFinishedEvent);
|
this.emit(APIRequestContext.Events.RequestFinished, requestFinishedEvent);
|
||||||
};
|
};
|
||||||
|
|
@ -497,6 +492,11 @@ export abstract class APIRequestContext extends SdkObject {
|
||||||
request.on('close', () => this.off(APIRequestContext.Events.Dispose, disposeListener));
|
request.on('close', () => this.off(APIRequestContext.Events.Dispose, disposeListener));
|
||||||
|
|
||||||
request.on('socket', socket => {
|
request.on('socket', socket => {
|
||||||
|
// happy eyeballs don't emit lookup and connect events, so we use our custom ones
|
||||||
|
timings.dnsLookupAt = (socket as any).dnsLookupAt;
|
||||||
|
timings.tcpConnectionAt = (socket as any).tcpConnectionAt;
|
||||||
|
|
||||||
|
// standard case
|
||||||
socket.on('lookup', () => { timings.dnsLookupAt = monotonicTime(); });
|
socket.on('lookup', () => { timings.dnsLookupAt = monotonicTime(); });
|
||||||
socket.on('connect', () => { timings.tcpConnectionAt = monotonicTime(); });
|
socket.on('connect', () => { timings.tcpConnectionAt = monotonicTime(); });
|
||||||
socket.on('secureConnect', () => { timings.tlsHandshakeAt = monotonicTime(); });
|
socket.on('secureConnect', () => { timings.tlsHandshakeAt = monotonicTime(); });
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ import * as net from 'net';
|
||||||
import * as tls from 'tls';
|
import * as tls from 'tls';
|
||||||
import { ManualPromise } from './manualPromise';
|
import { ManualPromise } from './manualPromise';
|
||||||
import { assert } from './debug';
|
import { assert } from './debug';
|
||||||
|
import { monotonicTime } from './time';
|
||||||
|
|
||||||
// Implementation(partial) of Happy Eyeballs 2 algorithm described in
|
// Implementation(partial) of Happy Eyeballs 2 algorithm described in
|
||||||
// https://www.rfc-editor.org/rfc/rfc8305
|
// https://www.rfc-editor.org/rfc/rfc8305
|
||||||
|
|
@ -107,6 +108,7 @@ export async function createConnectionAsync(
|
||||||
const lookup = (options as any).__testHookLookup || lookupAddresses;
|
const lookup = (options as any).__testHookLookup || lookupAddresses;
|
||||||
const hostname = clientRequestArgsToHostName(options);
|
const hostname = clientRequestArgsToHostName(options);
|
||||||
const addresses = await lookup(hostname);
|
const addresses = await lookup(hostname);
|
||||||
|
const dnsLookupAt = monotonicTime();
|
||||||
const sockets = new Set<net.Socket>();
|
const sockets = new Set<net.Socket>();
|
||||||
let firstError;
|
let firstError;
|
||||||
let errorCount = 0;
|
let errorCount = 0;
|
||||||
|
|
@ -132,9 +134,13 @@ export async function createConnectionAsync(
|
||||||
port: options.port as number,
|
port: options.port as number,
|
||||||
host: address });
|
host: address });
|
||||||
|
|
||||||
|
(socket as any).dnsLookupAt = dnsLookupAt;
|
||||||
|
|
||||||
// Each socket may fire only one of 'connect', 'timeout' or 'error' events.
|
// Each socket may fire only one of 'connect', 'timeout' or 'error' events.
|
||||||
// None of these events are fired after socket.destroy() is called.
|
// None of these events are fired after socket.destroy() is called.
|
||||||
socket.on('connect', () => {
|
socket.on('connect', () => {
|
||||||
|
(socket as any).tcpConnectionAt = monotonicTime();
|
||||||
|
|
||||||
connected.resolve();
|
connected.resolve();
|
||||||
oncreate?.(null, socket);
|
oncreate?.(null, socket);
|
||||||
// TODO: Cache the result?
|
// TODO: Cache the result?
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue