feat(response): expose connection details in API (#7150)

This commit is contained in:
Ross Wollman 2021-06-17 13:04:55 -07:00 committed by GitHub
parent 5e471a3ece
commit 219e5138be
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 170 additions and 1 deletions

View file

@ -48,6 +48,27 @@ Contains a boolean stating whether the response was successful (status in the ra
Returns the matching [Request] object.
## async method: Response.securityDetails
- returns: <[null]|[Object]>
- `issuer` <[string]> Common Name component of the Issuer field.
from the certificate. This should only be used for informational purposes. Optional.
- `protocol` <[string]> The specific TLS protocol used. (e.g. `TLS 1.3`). Optional.
- `subjectName` <[string]> Common Name component of the Subject
field from the certificate. This should only be used for informational purposes. Optional.
- `validFrom` <[int]> Unix timestamp (in seconds) specifying
when this cert becomes valid. Optional.
- `validTo` <[int]> Unix timestamp (in seconds) specifying
when this cert becomes invalid. Optional.
Returns SSL and other security information.
## async method: Response.serverAddr
- returns: <[null]|[Object]>
- `ipAddress` <[string]> IPv4 or IPV6 address of the server.
- `port` <[int]>
Returns the IP address and port of the server.
## method: Response.status
- returns: <[int]>

View file

@ -18,7 +18,7 @@ import { URLSearchParams } from 'url';
import * as channels from '../protocol/channels';
import { ChannelOwner } from './channelOwner';
import { Frame } from './frame';
import { Headers, WaitForEventOptions } from './types';
import { Headers, RemoteAddr, SecurityDetails, WaitForEventOptions } from './types';
import fs from 'fs';
import * as mime from 'mime';
import { isString, headersObjectToArray, headersArrayToObject } from '../utils/utils';
@ -325,6 +325,14 @@ export class Response extends ChannelOwner<channels.ResponseChannel, channels.Re
frame(): Frame {
return this._request.frame();
}
async serverAddr(): Promise<RemoteAddr|null> {
return (await this._channel.serverAddr()).value || null;
}
async securityDetails(): Promise<SecurityDetails|null> {
return (await this._channel.securityDetails()).value || null;
}
}
export class WebSocket extends ChannelOwner<channels.WebSocketChannel, channels.WebSocketInitializer> implements api.WebSocket {

View file

@ -111,3 +111,6 @@ export type SelectorEngine = {
*/
queryAll(root: HTMLElement, selector: string): HTMLElement[];
};
export type RemoteAddr = channels.RemoteAddr;
export type SecurityDetails = channels.SecurityDetails;

View file

@ -80,6 +80,14 @@ export class ResponseDispatcher extends Dispatcher<Response, channels.ResponseIn
async body(): Promise<channels.ResponseBodyResult> {
return { binary: (await this._object.body()).toString('base64') };
}
async securityDetails(): Promise<channels.ResponseSecurityDetailsResult> {
return { value: await this._object.securityDetails() || undefined };
}
async serverAddr(): Promise<channels.ResponseServerAddrResult> {
return { value: await this._object.serverAddr() || undefined };
}
}
export class RouteDispatcher extends Dispatcher<Route, channels.RouteInitializer> implements channels.RouteChannel {

View file

@ -2372,6 +2372,8 @@ export type ResponseInitializer = {
export interface ResponseChannel extends Channel {
body(params?: ResponseBodyParams, metadata?: Metadata): Promise<ResponseBodyResult>;
finished(params?: ResponseFinishedParams, metadata?: Metadata): Promise<ResponseFinishedResult>;
securityDetails(params?: ResponseSecurityDetailsParams, metadata?: Metadata): Promise<ResponseSecurityDetailsResult>;
serverAddr(params?: ResponseServerAddrParams, metadata?: Metadata): Promise<ResponseServerAddrResult>;
}
export type ResponseBodyParams = {};
export type ResponseBodyOptions = {};
@ -2383,6 +2385,29 @@ export type ResponseFinishedOptions = {};
export type ResponseFinishedResult = {
error?: string,
};
export type ResponseSecurityDetailsParams = {};
export type ResponseSecurityDetailsOptions = {};
export type ResponseSecurityDetailsResult = {
value?: SecurityDetails,
};
export type ResponseServerAddrParams = {};
export type ResponseServerAddrOptions = {};
export type ResponseServerAddrResult = {
value?: RemoteAddr,
};
export type SecurityDetails = {
issuer?: string,
protocol?: string,
subjectName?: string,
validFrom?: number,
validTo?: number,
};
export type RemoteAddr = {
ipAddress: string,
port: number,
};
// ----------- WebSocket -----------
export type WebSocketInitializer = {

View file

@ -1927,6 +1927,31 @@ Response:
returns:
error: string?
securityDetails:
returns:
value: SecurityDetails?
serverAddr:
returns:
value: RemoteAddr?
SecurityDetails:
type: object
properties:
issuer: string?
protocol: string?
subjectName: string?
validFrom: number?
validTo: number?
RemoteAddr:
type: object
properties:
ipAddress: string
port: number
WebSocket:
type: interface

View file

@ -943,6 +943,19 @@ export function createScheme(tChannel: (name: string) => Validator): Scheme {
});
scheme.ResponseBodyParams = tOptional(tObject({}));
scheme.ResponseFinishedParams = tOptional(tObject({}));
scheme.ResponseSecurityDetailsParams = tOptional(tObject({}));
scheme.ResponseServerAddrParams = tOptional(tObject({}));
scheme.SecurityDetails = tObject({
issuer: tOptional(tString),
protocol: tOptional(tString),
subjectName: tOptional(tString),
validFrom: tOptional(tNumber),
validTo: tOptional(tNumber),
});
scheme.RemoteAddr = tObject({
ipAddress: tString,
port: tNumber,
});
scheme.BindingCallRejectParams = tObject({
error: tType('SerializedError'),
});

View file

@ -363,3 +363,25 @@ it('should have connection details for failed requests', async ({ contextFactory
expect(serverIPAddress).toMatch(/^127\.0\.0\.1|\[::1\]/);
expect(port).toBe(server.PORT);
});
it('should return server address directly from response', async ({ page, server }) => {
const response = await page.goto(server.EMPTY_PAGE);
const { ipAddress, port } = await response.serverAddr();
expect(ipAddress).toMatch(/^127\.0\.0\.1|\[::1\]/);
expect(port).toBe(server.PORT);
});
it('should return security details directly from response', async ({ contextFactory, httpsServer, browserName, platform }) => {
it.fail(browserName === 'webkit' && platform === 'linux', 'https://github.com/microsoft/playwright/issues/6759');
const context = await contextFactory({ ignoreHTTPSErrors: true });
const page = await context.newPage();
const response = await page.goto(httpsServer.EMPTY_PAGE);
const securityDetails = await response.securityDetails();
if (browserName === 'webkit' && platform === 'win32')
expect(securityDetails).toEqual({subjectName: 'puppeteer-tests', validFrom: 1550084863, validTo: -1});
else if (browserName === 'webkit')
expect(securityDetails).toEqual({protocol: 'TLS 1.3', subjectName: 'puppeteer-tests', validFrom: 1550084863, validTo: 33086084863});
else
expect(securityDetails).toEqual({issuer: 'puppeteer-tests', protocol: 'TLS 1.3', subjectName: 'puppeteer-tests', validFrom: 1550084863, validTo: 33086084863});
});

44
types/types.d.ts vendored
View file

@ -10363,6 +10363,50 @@ export interface Response {
*/
request(): Request;
/**
* Returns SSL and other security information.
*/
securityDetails(): Promise<null|{
/**
* Common Name component of the Issuer field. from the certificate. This should only be used for informational purposes.
* Optional.
*/
issuer?: string;
/**
* The specific TLS protocol used. (e.g. `TLS 1.3`). Optional.
*/
protocol?: string;
/**
* Common Name component of the Subject field from the certificate. This should only be used for informational purposes.
* Optional.
*/
subjectName?: string;
/**
* Unix timestamp (in seconds) specifying when this cert becomes valid. Optional.
*/
validFrom?: number;
/**
* Unix timestamp (in seconds) specifying when this cert becomes invalid. Optional.
*/
validTo?: number;
}>;
/**
* Returns the IP address and port of the server.
*/
serverAddr(): Promise<null|{
/**
* IPv4 or IPV6 address of the server.
*/
ipAddress: string;
port: number;
}>;
/**
* Contains the status code of the response (e.g., 200 for a success).
*/