feat(rpc): use headers array in the protocol (#2959)

This commit is contained in:
Dmitry Gozman 2020-07-15 13:21:21 -07:00 committed by GitHub
parent 31893036e8
commit 7f6171579b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 102 additions and 67 deletions

View File

@ -348,12 +348,12 @@ class InterceptableRequest implements network.RouteDelegate {
this.request = new network.Request(allowInterception ? this : null, frame, redirectedFrom, documentId, url, type, method, postData, headersObject(headers));
}
async continue(overrides: { method?: string; headers?: types.Headers; postData?: string } = {}) {
async continue(overrides: types.NormalizedContinueOverrides) {
// In certain cases, protocol will return error if the request was already canceled
// or the page was closed. We should tolerate these errors.
await this._client._sendMayFail('Fetch.continueRequest', {
requestId: this._interceptionId!,
headers: overrides.headers ? headersArray(overrides.headers) : undefined,
headers: overrides.headers,
method: overrides.method,
postData: overrides.postData
});
@ -368,7 +368,7 @@ class InterceptableRequest implements network.RouteDelegate {
requestId: this._interceptionId!,
responseCode: response.status,
responsePhrase: network.STATUS_TEXTS[String(response.status)],
responseHeaders: headersArray(response.headers),
responseHeaders: response.headers,
body,
});
}
@ -402,15 +402,6 @@ const errorReasons: { [reason: string]: Protocol.Network.ErrorReason } = {
'failed': 'Failed',
};
function headersArray(headers: { [s: string]: string; }): { name: string; value: string; }[] {
const result = [];
for (const name in headers) {
if (!Object.is(headers[name], undefined))
result.push({name, value: headers[name] + ''});
}
return result;
}
function headersObject(headers: Protocol.Network.Headers): types.Headers {
const result: types.Headers = {};
for (const key of Object.keys(headers))

View File

@ -158,17 +158,12 @@ class InterceptableRequest implements network.RouteDelegate {
payload.url, internalCauseToResourceType[payload.internalCause] || causeToResourceType[payload.cause] || 'other', payload.method, payload.postData || null, headers);
}
async continue(overrides: { method?: string; headers?: types.Headers; postData?: string }) {
const {
method,
headers,
postData
} = overrides;
async continue(overrides: types.NormalizedContinueOverrides) {
await this._session.sendMayFail('Network.resumeInterceptedRequest', {
requestId: this._id,
method,
headers: headers ? headersArray(headers) : undefined,
postData: postData ? Buffer.from(postData).toString('base64') : undefined
method: overrides.method,
headers: overrides.headers,
postData: overrides.postData ? Buffer.from(overrides.postData).toString('base64') : undefined
});
}
@ -179,7 +174,7 @@ class InterceptableRequest implements network.RouteDelegate {
requestId: this._id,
status: response.status,
statusText: network.STATUS_TEXTS[String(response.status)] || '',
headers: headersArray(response.headers),
headers: response.headers,
base64body,
});
}

View File

@ -18,7 +18,7 @@ import * as frames from './frames';
import * as types from './types';
import { assert, helper } from './helper';
import { URLSearchParams } from 'url';
import { normalizeFulfillParameters } from './rpc/serializers';
import { normalizeFulfillParameters, normalizeContinueOverrides } from './rpc/serializers';
export function filterCookies(cookies: types.NetworkCookie[], urls: string[]): types.NetworkCookie[] {
const parsedURLs = urls.map(s => new URL(s));
@ -221,9 +221,9 @@ export class Route {
await this._delegate.fulfill(await normalizeFulfillParameters(response));
}
async continue(overrides: { method?: string; headers?: types.Headers; postData?: string } = {}) {
async continue(overrides: types.ContinueOverrides = {}) {
assert(!this._handled, 'Route is already handled!');
await this._delegate.continue(overrides);
await this._delegate.continue(normalizeContinueOverrides(overrides));
}
}
@ -316,7 +316,7 @@ export class Response {
export interface RouteDelegate {
abort(errorCode: string): Promise<void>;
fulfill(response: types.NormalizedFulfillResponse): Promise<void>;
continue(overrides: { method?: string; headers?: types.Headers; postData?: string; }): Promise<void>;
continue(overrides: types.NormalizedContinueOverrides): Promise<void>;
}
// List taken from https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml with extra 306 and 418 codes.

View File

@ -95,7 +95,7 @@ export interface BrowserContextChannel extends Channel {
newPage(): Promise<{ page: PageChannel }>;
setDefaultNavigationTimeoutNoReply(params: { timeout: number }): void;
setDefaultTimeoutNoReply(params: { timeout: number }): void;
setExtraHTTPHeaders(params: { headers: types.Headers }): Promise<void>;
setExtraHTTPHeaders(params: { headers: types.HeadersArray }): Promise<void>;
setGeolocation(params: { geolocation: types.Geolocation | null }): Promise<void>;
setHTTPCredentials(params: { httpCredentials: types.Credentials | null }): Promise<void>;
setNetworkInterceptionEnabled(params: { enabled: boolean }): Promise<void>;
@ -143,7 +143,7 @@ export interface PageChannel extends Channel {
opener(): Promise<{ page: PageChannel | null }>;
reload(params: types.NavigateOptions): Promise<{ response: ResponseChannel | null }>;
screenshot(params: types.ScreenshotOptions): Promise<{ binary: Binary }>;
setExtraHTTPHeaders(params: { headers: types.Headers }): Promise<void>;
setExtraHTTPHeaders(params: { headers: types.HeadersArray }): Promise<void>;
setNetworkInterceptionEnabled(params: { enabled: boolean }): Promise<void>;
setViewportSize(params: { viewportSize: types.Size }): Promise<void>;
@ -282,7 +282,7 @@ export type RequestInitializer = {
resourceType: string,
method: string,
postData: string | null,
headers: types.Headers,
headers: types.HeadersArray,
isNavigationRequest: boolean,
redirectedFrom: RequestChannel | null,
};
@ -290,13 +290,8 @@ export type RequestInitializer = {
export interface RouteChannel extends Channel {
abort(params: { errorCode: string }): Promise<void>;
continue(params: { method?: string, headers?: types.Headers, postData?: string }): Promise<void>;
fulfill(params: {
status?: number,
headers?: types.Headers,
body: string,
isBase64: boolean,
}): Promise<void>;
continue(params: types.NormalizedContinueOverrides): Promise<void>;
fulfill(params: types.NormalizedFulfillResponse): Promise<void>;
}
export type RouteInitializer = {
request: RequestChannel,
@ -312,7 +307,7 @@ export type ResponseInitializer = {
url: string,
status: number,
statusText: string,
headers: types.Headers,
headers: types.HeadersArray,
};

View File

@ -27,6 +27,7 @@ import { Events } from '../../events';
import { TimeoutSettings } from '../../timeoutSettings';
import { Waiter } from './waiter';
import { TimeoutError } from '../../errors';
import { headersObjectToArray } from '../serializers';
export class BrowserContext extends ChannelOwner<BrowserContextChannel, BrowserContextInitializer> {
_pages = new Set<Page>();
@ -131,7 +132,7 @@ export class BrowserContext extends ChannelOwner<BrowserContextChannel, BrowserC
}
async setExtraHTTPHeaders(headers: types.Headers): Promise<void> {
await this._channel.setExtraHTTPHeaders({ headers });
await this._channel.setExtraHTTPHeaders({ headers: headersObjectToArray(headers) });
}
async setOffline(offline: boolean): Promise<void> {

View File

@ -19,7 +19,7 @@ import * as types from '../../types';
import { RequestChannel, ResponseChannel, RouteChannel, RequestInitializer, ResponseInitializer, RouteInitializer } from '../channels';
import { ChannelOwner } from './channelOwner';
import { Frame } from './frame';
import { normalizeFulfillParameters } from '../serializers';
import { normalizeFulfillParameters, headersArrayToObject, normalizeContinueOverrides } from '../serializers';
export type NetworkCookie = {
name: string,
@ -48,6 +48,7 @@ export class Request extends ChannelOwner<RequestChannel, RequestInitializer> {
private _redirectedFrom: Request | null = null;
private _redirectedTo: Request | null = null;
_failureText: string | null = null;
private _headers: types.Headers;
static from(request: RequestChannel): Request {
return (request as any)._object;
@ -62,6 +63,7 @@ export class Request extends ChannelOwner<RequestChannel, RequestInitializer> {
this._redirectedFrom = Request.fromNullable(initializer.redirectedFrom);
if (this._redirectedFrom)
this._redirectedFrom._redirectedTo = this;
this._headers = headersArrayToObject(initializer.headers);
}
url(): string {
@ -99,8 +101,8 @@ export class Request extends ChannelOwner<RequestChannel, RequestInitializer> {
return JSON.parse(this._initializer.postData);
}
headers(): {[key: string]: string} {
return { ...this._initializer.headers };
headers(): types.Headers {
return { ...this._headers };
}
async response(): Promise<Response | null> {
@ -154,14 +156,16 @@ export class Route extends ChannelOwner<RouteChannel, RouteInitializer> {
await this._channel.fulfill(normalized);
}
async continue(overrides: { method?: string; headers?: types.Headers; postData?: string } = {}) {
await this._channel.continue(overrides);
async continue(overrides: types.ContinueOverrides = {}) {
await this._channel.continue(normalizeContinueOverrides(overrides));
}
}
export type RouteHandler = (route: Route, request: Request) => void;
export class Response extends ChannelOwner<ResponseChannel, ResponseInitializer> {
private _headers: types.Headers;
static from(response: ResponseChannel): Response {
return (response as any)._object;
}
@ -172,6 +176,7 @@ export class Response extends ChannelOwner<ResponseChannel, ResponseInitializer>
constructor(parent: ChannelOwner, type: string, guid: string, initializer: ResponseInitializer) {
super(parent, type, guid, initializer);
this._headers = headersArrayToObject(initializer.headers);
}
url(): string {
@ -190,8 +195,8 @@ export class Response extends ChannelOwner<ResponseChannel, ResponseInitializer>
return this._initializer.statusText;
}
headers(): object {
return { ...this._initializer.headers };
headers(): types.Headers {
return { ...this._headers };
}
async finished(): Promise<Error | null> {

View File

@ -21,7 +21,7 @@ import { assert, assertMaxArguments, helper, Listener } from '../../helper';
import { TimeoutSettings } from '../../timeoutSettings';
import * as types from '../../types';
import { BindingCallChannel, BindingCallInitializer, PageChannel, PageInitializer, PDFOptions } from '../channels';
import { parseError, serializeError } from '../serializers';
import { parseError, serializeError, headersObjectToArray } from '../serializers';
import { Accessibility } from './accessibility';
import { BrowserContext } from './browserContext';
import { ChannelOwner } from './channelOwner';
@ -282,7 +282,7 @@ export class Page extends ChannelOwner<PageChannel, PageInitializer> {
}
async setExtraHTTPHeaders(headers: types.Headers) {
await this._channel.setExtraHTTPHeaders({ headers });
await this._channel.setExtraHTTPHeaders({ headers: headersObjectToArray(headers) });
}
url(): string {

View File

@ -20,7 +20,7 @@ import * as path from 'path';
import * as util from 'util';
import { TimeoutError } from '../errors';
import * as types from '../types';
import { helper } from '../helper';
import { helper, assert } from '../helper';
export function serializeError(e: any): types.Error {
@ -82,7 +82,7 @@ export async function normalizeFulfillParameters(params: types.FulfillResponse &
isBase64 = true;
length = params.body.length;
}
const headers: { [s: string]: string; } = {};
const headers: types.Headers = {};
for (const header of Object.keys(params.headers || {}))
headers[header.toLowerCase()] = String(params.headers![header]);
if (params.contentType)
@ -94,8 +94,35 @@ export async function normalizeFulfillParameters(params: types.FulfillResponse &
return {
status: params.status || 200,
headers,
headers: headersObjectToArray(headers),
body,
isBase64
};
}
export function normalizeContinueOverrides(overrides: types.ContinueOverrides): types.NormalizedContinueOverrides {
return {
method: overrides.method,
headers: overrides.headers ? headersObjectToArray(overrides.headers) : undefined,
postData: overrides.postData,
};
}
export function headersObjectToArray(headers: types.Headers): types.HeadersArray {
const result: types.HeadersArray = [];
for (const name in headers) {
if (!Object.is(headers[name], undefined)) {
const value = headers[name];
assert(helper.isString(value), `Expected value of header "${name}" to be String, but "${typeof value}" is found.`);
result.push({ name, value });
}
}
return result;
}
export function headersArrayToObject(headers: types.HeadersArray): types.Headers {
const result: types.Headers = {};
for (const { name, value } of headers)
result[name] = value;
return result;
}

View File

@ -24,6 +24,7 @@ import { RouteDispatcher, RequestDispatcher } from './networkDispatchers';
import { CRBrowserContext } from '../../chromium/crBrowser';
import { CDPSessionDispatcher } from './cdpSessionDispatcher';
import { Events as ChromiumEvents } from '../../chromium/events';
import { headersArrayToObject } from '../serializers';
export class BrowserContextDispatcher extends Dispatcher<BrowserContext, BrowserContextInitializer> implements BrowserContextChannel {
private _context: BrowserContextBase;
@ -94,8 +95,8 @@ export class BrowserContextDispatcher extends Dispatcher<BrowserContext, Browser
await this._context.setGeolocation(params.geolocation);
}
async setExtraHTTPHeaders(params: { headers: types.Headers }): Promise<void> {
await this._context.setExtraHTTPHeaders(params.headers);
async setExtraHTTPHeaders(params: { headers: types.HeadersArray }): Promise<void> {
await this._context.setExtraHTTPHeaders(headersArrayToObject(params.headers));
}
async setOffline(params: { offline: boolean }): Promise<void> {

View File

@ -15,10 +15,11 @@
*/
import { Request, Response, Route } from '../../network';
import * as types from '../../types';
import { RequestChannel, ResponseChannel, RouteChannel, ResponseInitializer, RequestInitializer, RouteInitializer, Binary } from '../channels';
import { Dispatcher, DispatcherScope, lookupNullableDispatcher, existingDispatcher } from './dispatcher';
import { FrameDispatcher } from './frameDispatcher';
import { headersObjectToArray, headersArrayToObject } from '../serializers';
import * as types from '../../types';
export class RequestDispatcher extends Dispatcher<Request, RequestInitializer> implements RequestChannel {
@ -38,7 +39,7 @@ export class RequestDispatcher extends Dispatcher<Request, RequestInitializer> i
resourceType: request.resourceType(),
method: request.method(),
postData: request.postData(),
headers: request.headers(),
headers: headersObjectToArray(request.headers()),
isNavigationRequest: request.isNavigationRequest(),
redirectedFrom: RequestDispatcher.fromNullable(scope, request.redirectedFrom()),
});
@ -58,7 +59,7 @@ export class ResponseDispatcher extends Dispatcher<Response, ResponseInitializer
url: response.url(),
status: response.status(),
statusText: response.statusText(),
headers: response.headers(),
headers: headersObjectToArray(response.headers()),
});
}
@ -80,14 +81,18 @@ export class RouteDispatcher extends Dispatcher<Route, RouteInitializer> impleme
});
}
async continue(params: { method?: string, headers?: types.Headers, postData?: string }): Promise<void> {
await this._object.continue(params);
async continue(params: types.NormalizedContinueOverrides): Promise<void> {
await this._object.continue({
method: params.method,
headers: params.headers ? headersArrayToObject(params.headers) : undefined,
postData: params.postData,
});
}
async fulfill(params: { status?: number, headers?: types.Headers, contentType?: string, body: string, isBase64: boolean }): Promise<void> {
async fulfill(params: types.NormalizedFulfillResponse): Promise<void> {
await this._object.fulfill({
status: params.status,
headers: params.headers,
headers: params.headers ? headersArrayToObject(params.headers) : undefined,
body: params.isBase64 ? Buffer.from(params.body, 'base64') : params.body,
});
}

View File

@ -22,7 +22,7 @@ import { Page, Worker } from '../../page';
import * as types from '../../types';
import { BindingCallChannel, BindingCallInitializer, ElementHandleChannel, PageChannel, PageInitializer, ResponseChannel, WorkerInitializer, WorkerChannel, JSHandleChannel, Binary, PDFOptions } from '../channels';
import { Dispatcher, DispatcherScope, lookupDispatcher, lookupNullableDispatcher } from './dispatcher';
import { parseError, serializeError } from '../serializers';
import { parseError, serializeError, headersArrayToObject } from '../serializers';
import { ConsoleMessageDispatcher } from './consoleMessageDispatcher';
import { DialogDispatcher } from './dialogDispatcher';
import { DownloadDispatcher } from './downloadDispatcher';
@ -91,8 +91,8 @@ export class PageDispatcher extends Dispatcher<Page, PageInitializer> implements
});
}
async setExtraHTTPHeaders(params: { headers: types.Headers }): Promise<void> {
await this._page.setExtraHTTPHeaders(params.headers);
async setExtraHTTPHeaders(params: { headers: types.HeadersArray }): Promise<void> {
await this._page.setExtraHTTPHeaders(headersArrayToObject(params.headers));
}
async reload(params: types.NavigateOptions): Promise<{ response: ResponseChannel | null }> {

View File

@ -202,6 +202,7 @@ export type MouseMultiClickOptions = PointerActionOptions & {
export type World = 'main' | 'utility';
export type Headers = { [key: string]: string };
export type HeadersArray = { name: string, value: string }[];
export type GotoOptions = NavigateOptions & {
referer?: string,
@ -216,11 +217,23 @@ export type FulfillResponse = {
export type NormalizedFulfillResponse = {
status: number,
headers: Headers,
headers: HeadersArray,
body: string,
isBase64: boolean,
};
export type ContinueOverrides = {
method?: string,
headers?: Headers,
postData?: string,
};
export type NormalizedContinueOverrides = {
method?: string,
headers?: HeadersArray,
postData?: string,
};
export type NetworkCookie = {
name: string,
value: string,

View File

@ -21,6 +21,7 @@ import * as network from '../network';
import * as types from '../types';
import { Protocol } from './protocol';
import { WKSession } from './wkConnection';
import { headersArrayToObject } from '../rpc/serializers';
const errorReasons: { [reason: string]: Protocol.Network.ResourceErrorType } = {
'aborted': 'Cancellation',
@ -72,7 +73,8 @@ export class WKInterceptableRequest implements network.RouteDelegate {
// In certain cases, protocol will return error if the request was already canceled
// or the page was closed. We should tolerate these errors.
let mimeType = response.isBase64 ? 'application/octet-stream' : 'text/plain';
const contentType = response.headers['content-type'];
const headers = headersArrayToObject(response.headers);
const contentType = headers['content-type'];
if (contentType)
mimeType = contentType.split(';')[0].trim();
await this._session.sendMayFail('Network.interceptRequestWithResponse', {
@ -80,20 +82,20 @@ export class WKInterceptableRequest implements network.RouteDelegate {
status: response.status,
statusText: network.STATUS_TEXTS[String(response.status)],
mimeType,
headers: response.headers,
headers,
base64Encoded: response.isBase64,
content: response.body
});
}
async continue(overrides: { method?: string; headers?: types.Headers; postData?: string }) {
async continue(overrides: types.NormalizedContinueOverrides) {
await this._interceptedPromise;
// In certain cases, protocol will return error if the request was already canceled
// or the page was closed. We should tolerate these errors.
await this._session.sendMayFail('Network.interceptWithRequest', {
requestId: this._requestId,
method: overrides.method,
headers: overrides.headers,
headers: overrides.headers ? headersArrayToObject(overrides.headers) : undefined,
postData: overrides.postData ? Buffer.from(overrides.postData).toString('base64') : undefined
});
}