move under own dispatcher
This commit is contained in:
parent
048f6d95cf
commit
10646b1c25
|
|
@ -66,7 +66,6 @@ export const slowMoActions = new Set([
|
||||||
|
|
||||||
export const commandsWithTracingSnapshots = new Set([
|
export const commandsWithTracingSnapshots = new Set([
|
||||||
'EventTarget.waitForEventInfo',
|
'EventTarget.waitForEventInfo',
|
||||||
'LocalUtils.waitForEventInfo',
|
|
||||||
'MockingProxy.waitForEventInfo',
|
'MockingProxy.waitForEventInfo',
|
||||||
'BrowserContext.waitForEventInfo',
|
'BrowserContext.waitForEventInfo',
|
||||||
'Page.waitForEventInfo',
|
'Page.waitForEventInfo',
|
||||||
|
|
|
||||||
|
|
@ -236,7 +236,6 @@ scheme.EventTargetWaitForEventInfoParams = tObject({
|
||||||
error: tOptional(tString),
|
error: tOptional(tString),
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
scheme.LocalUtilsWaitForEventInfoParams = tType('EventTargetWaitForEventInfoParams');
|
|
||||||
scheme.MockingProxyWaitForEventInfoParams = tType('EventTargetWaitForEventInfoParams');
|
scheme.MockingProxyWaitForEventInfoParams = tType('EventTargetWaitForEventInfoParams');
|
||||||
scheme.BrowserContextWaitForEventInfoParams = tType('EventTargetWaitForEventInfoParams');
|
scheme.BrowserContextWaitForEventInfoParams = tType('EventTargetWaitForEventInfoParams');
|
||||||
scheme.PageWaitForEventInfoParams = tType('EventTargetWaitForEventInfoParams');
|
scheme.PageWaitForEventInfoParams = tType('EventTargetWaitForEventInfoParams');
|
||||||
|
|
@ -244,7 +243,6 @@ scheme.WebSocketWaitForEventInfoParams = tType('EventTargetWaitForEventInfoParam
|
||||||
scheme.ElectronApplicationWaitForEventInfoParams = tType('EventTargetWaitForEventInfoParams');
|
scheme.ElectronApplicationWaitForEventInfoParams = tType('EventTargetWaitForEventInfoParams');
|
||||||
scheme.AndroidDeviceWaitForEventInfoParams = tType('EventTargetWaitForEventInfoParams');
|
scheme.AndroidDeviceWaitForEventInfoParams = tType('EventTargetWaitForEventInfoParams');
|
||||||
scheme.EventTargetWaitForEventInfoResult = tOptional(tObject({}));
|
scheme.EventTargetWaitForEventInfoResult = tOptional(tObject({}));
|
||||||
scheme.LocalUtilsWaitForEventInfoResult = tType('EventTargetWaitForEventInfoResult');
|
|
||||||
scheme.MockingProxyWaitForEventInfoResult = tType('EventTargetWaitForEventInfoResult');
|
scheme.MockingProxyWaitForEventInfoResult = tType('EventTargetWaitForEventInfoResult');
|
||||||
scheme.BrowserContextWaitForEventInfoResult = tType('EventTargetWaitForEventInfoResult');
|
scheme.BrowserContextWaitForEventInfoResult = tType('EventTargetWaitForEventInfoResult');
|
||||||
scheme.PageWaitForEventInfoResult = tType('EventTargetWaitForEventInfoResult');
|
scheme.PageWaitForEventInfoResult = tType('EventTargetWaitForEventInfoResult');
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ import path from 'path';
|
||||||
import os from 'os';
|
import os from 'os';
|
||||||
import type * as channels from '@protocol/channels';
|
import type * as channels from '@protocol/channels';
|
||||||
import { ManualPromise } from '../../utils/manualPromise';
|
import { ManualPromise } from '../../utils/manualPromise';
|
||||||
import { assert, calculateSha1, createGuid, HttpServer, removeFolders, urlMatches } from '../../utils';
|
import { assert, calculateSha1, createGuid, removeFolders } from '../../utils';
|
||||||
import type { RootDispatcher } from './dispatcher';
|
import type { RootDispatcher } from './dispatcher';
|
||||||
import { Dispatcher } from './dispatcher';
|
import { Dispatcher } from './dispatcher';
|
||||||
import { yazl, yauzl } from '../../zipBundle';
|
import { yazl, yauzl } from '../../zipBundle';
|
||||||
|
|
@ -41,14 +41,13 @@ import type { Playwright } from '../playwright';
|
||||||
import { SdkObject } from '../../server/instrumentation';
|
import { SdkObject } from '../../server/instrumentation';
|
||||||
import { serializeClientSideCallMetadata } from '../../utils';
|
import { serializeClientSideCallMetadata } from '../../utils';
|
||||||
import { deviceDescriptors as descriptors } from '../deviceDescriptors';
|
import { deviceDescriptors as descriptors } from '../deviceDescriptors';
|
||||||
import { APIRequestContextDispatcher, RequestDispatcher, ResponseDispatcher, RouteDispatcher } from './networkDispatchers';
|
|
||||||
import type { APIRequestContext } from '../fetch';
|
import type { APIRequestContext } from '../fetch';
|
||||||
import { GlobalAPIRequestContext } from '../fetch';
|
import { GlobalAPIRequestContext } from '../fetch';
|
||||||
import { MockingProxy, ServerInterceptionRegistry } from '../mockingProxy';
|
import { MockingProxy } from '../mockingProxy';
|
||||||
|
import { MockingProxyDispatcher } from './mockingProxyDispatcher';
|
||||||
|
|
||||||
export class LocalUtilsDispatcher extends Dispatcher<SdkObject, channels.LocalUtilsChannel, RootDispatcher> implements channels.LocalUtilsChannel {
|
export class LocalUtilsDispatcher extends Dispatcher<SdkObject, channels.LocalUtilsChannel, RootDispatcher> implements channels.LocalUtilsChannel {
|
||||||
_type_LocalUtils: boolean;
|
_type_LocalUtils: boolean;
|
||||||
_type_EventTarget: boolean;
|
|
||||||
private _harBackends = new Map<string, HarBackend>();
|
private _harBackends = new Map<string, HarBackend>();
|
||||||
private _stackSessions = new Map<string, {
|
private _stackSessions = new Map<string, {
|
||||||
file: string,
|
file: string,
|
||||||
|
|
@ -56,9 +55,7 @@ export class LocalUtilsDispatcher extends Dispatcher<SdkObject, channels.LocalUt
|
||||||
tmpDir: string | undefined,
|
tmpDir: string | undefined,
|
||||||
callStacks: channels.ClientSideCallMetadata[]
|
callStacks: channels.ClientSideCallMetadata[]
|
||||||
}>();
|
}>();
|
||||||
_requestContext: APIRequestContext;
|
private _requestContext: APIRequestContext;
|
||||||
private _interceptionRegistry;
|
|
||||||
private _server?: WorkerHttpServer;
|
|
||||||
|
|
||||||
constructor(scope: RootDispatcher, playwright: Playwright) {
|
constructor(scope: RootDispatcher, playwright: Playwright) {
|
||||||
const localUtils = new SdkObject(playwright, 'localUtils', 'localUtils');
|
const localUtils = new SdkObject(playwright, 'localUtils', 'localUtils');
|
||||||
|
|
@ -68,40 +65,9 @@ export class LocalUtilsDispatcher extends Dispatcher<SdkObject, channels.LocalUt
|
||||||
const requestContext = new GlobalAPIRequestContext(playwright, {});
|
const requestContext = new GlobalAPIRequestContext(playwright, {});
|
||||||
super(scope, localUtils, 'LocalUtils', {
|
super(scope, localUtils, 'LocalUtils', {
|
||||||
deviceDescriptors,
|
deviceDescriptors,
|
||||||
requestContext: APIRequestContextDispatcher.from(scope, requestContext),
|
|
||||||
});
|
});
|
||||||
this._requestContext = requestContext;
|
this._requestContext = requestContext;
|
||||||
this._type_LocalUtils = true;
|
this._type_LocalUtils = true;
|
||||||
this._type_EventTarget = true;
|
|
||||||
|
|
||||||
this._interceptionRegistry = new ServerInterceptionRegistry(this._object, this._requestContext, {
|
|
||||||
onRequest: request => {
|
|
||||||
this._dispatchEvent('request', { request: RequestDispatcher.from(this.parentScope() as any, request) });
|
|
||||||
},
|
|
||||||
onRequestFinished: (request, response) => {
|
|
||||||
this._dispatchEvent('requestFinished', {
|
|
||||||
request: RequestDispatcher.from(this.parentScope() as any, request),
|
|
||||||
response: ResponseDispatcher.fromNullable(this.parentScope() as any, response ?? null),
|
|
||||||
responseEndTiming: request._responseEndTiming,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
onRequestFailed: request => {
|
|
||||||
this._dispatchEvent('requestFailed', {
|
|
||||||
request: RequestDispatcher.from(this.parentScope() as any, request),
|
|
||||||
responseEndTiming: request._responseEndTiming,
|
|
||||||
failureText: request._failureText ?? undefined
|
|
||||||
});
|
|
||||||
},
|
|
||||||
onResponse: (request, response) => {
|
|
||||||
this._dispatchEvent('response', {
|
|
||||||
request: RequestDispatcher.from(this.parentScope() as any, request),
|
|
||||||
response: ResponseDispatcher.from(this.parentScope() as any, response),
|
|
||||||
});
|
|
||||||
},
|
|
||||||
onRoute: (route, request) => {
|
|
||||||
this._dispatchEvent('route', { route: RouteDispatcher.from(RequestDispatcher.from(this.parentScope() as any, request), route) });
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async zip(params: channels.LocalUtilsZipParams): Promise<void> {
|
async zip(params: channels.LocalUtilsZipParams): Promise<void> {
|
||||||
|
|
@ -316,24 +282,10 @@ export class LocalUtilsDispatcher extends Dispatcher<SdkObject, channels.LocalUt
|
||||||
this._stackSessions.delete(stacksId!);
|
this._stackSessions.delete(stacksId!);
|
||||||
}
|
}
|
||||||
|
|
||||||
async setServerNetworkInterceptionPatterns(params: channels.LocalUtilsSetServerNetworkInterceptionPatternsParams, metadata?: CallMetadata): Promise<channels.LocalUtilsSetServerNetworkInterceptionPatternsResult> {
|
async newMockingProxy(params: channels.LocalUtilsNewMockingProxyParams, metadata?: CallMetadata): Promise<channels.LocalUtilsNewMockingProxyResult> {
|
||||||
if (!this._server) {
|
const mockingProxy = new MockingProxy(this._object, this._requestContext);
|
||||||
this._server = new WorkerHttpServer();
|
await mockingProxy.start(params.port);
|
||||||
new MockingProxy(this._interceptionRegistry).install(this._server);
|
return { mockingProxy: MockingProxyDispatcher.from(this.parentScope(), mockingProxy) };
|
||||||
await this._server.start({ port: params.port });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (params.patterns.length === 0)
|
|
||||||
return this._interceptionRegistry.setRequestInterceptor(undefined);
|
|
||||||
|
|
||||||
const urlMatchers = params.patterns.map(pattern => pattern.regexSource ? new RegExp(pattern.regexSource, pattern.regexFlags!) : pattern.glob!);
|
|
||||||
this._interceptionRegistry.setRequestInterceptor(url => urlMatchers.some(urlMatch => urlMatches(undefined, url, urlMatch)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class WorkerHttpServer extends HttpServer {
|
|
||||||
override _handleCORS(request: http.IncomingMessage, response: http.ServerResponse): boolean {
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,27 +14,33 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
import type { CallMetadata } from '@protocol/callMetadata';
|
import type { CallMetadata } from '@protocol/callMetadata';
|
||||||
import type { MockingProxy, ServerInterceptionRegistry } from '../mockingProxy';
|
import type { MockingProxy } from '../mockingProxy';
|
||||||
import type { RootDispatcher } from './dispatcher';
|
import type { RootDispatcher } from './dispatcher';
|
||||||
import { Dispatcher, existingDispatcher } from './dispatcher';
|
import { Dispatcher, existingDispatcher } from './dispatcher';
|
||||||
import type * as channels from '@protocol/channels';
|
import type * as channels from '@protocol/channels';
|
||||||
import { SdkObject } from '../instrumentation';
|
import { APIRequestContextDispatcher } from './networkDispatchers';
|
||||||
|
import { urlMatches } from '@isomorphic/urlMatch';
|
||||||
|
|
||||||
export class MockingProxyDispatcher extends Dispatcher<ServerInterceptionRegistry, channels.MockingProxyChannel, RootDispatcher> implements channels.MockingProxyChannel {
|
export class MockingProxyDispatcher extends Dispatcher<MockingProxy, channels.MockingProxyChannel, RootDispatcher> implements channels.MockingProxyChannel {
|
||||||
_type_MockingProxy = true;
|
_type_MockingProxy = true;
|
||||||
_type_EventTarget = true;
|
_type_EventTarget = true;
|
||||||
|
|
||||||
static from(scope: RootDispatcher, mockingProxy: ServerInterceptionRegistry): MockingProxyDispatcher {
|
static from(scope: RootDispatcher, mockingProxy: MockingProxy): MockingProxyDispatcher {
|
||||||
return existingDispatcher<MockingProxyDispatcher>(mockingProxy) || new MockingProxyDispatcher(scope, mockingProxy);
|
return existingDispatcher<MockingProxyDispatcher>(mockingProxy) || new MockingProxyDispatcher(scope, mockingProxy);
|
||||||
}
|
}
|
||||||
|
|
||||||
private constructor(scope: RootDispatcher, mockingProxy: ServerInterceptionRegistry) {
|
private constructor(scope: RootDispatcher, mockingProxy: MockingProxy) {
|
||||||
super(scope, mockingProxy, 'MockingProxy', {
|
super(scope, mockingProxy, 'MockingProxy', {
|
||||||
port: mockingProxy.port(),
|
port: mockingProxy.port,
|
||||||
|
requestContext: APIRequestContextDispatcher.from(scope, mockingProxy.fetchRequest),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
setInterceptionPatterns(params: channels.MockingProxySetInterceptionPatternsParams, metadata?: CallMetadata): Promise<channels.MockingProxySetInterceptionPatternsResult> {
|
async setInterceptionPatterns(params: channels.MockingProxySetInterceptionPatternsParams, metadata?: CallMetadata): Promise<channels.MockingProxySetInterceptionPatternsResult> {
|
||||||
throw new Error('Method not implemented.');
|
if (params.patterns.length === 0)
|
||||||
|
return this._object.setInterceptionPatterns(undefined);
|
||||||
|
|
||||||
|
const urlMatchers = params.patterns.map(pattern => pattern.regexSource ? new RegExp(pattern.regexSource, pattern.regexFlags!) : pattern.glob!);
|
||||||
|
this._object.setInterceptionPatterns(url => urlMatchers.some(urlMatch => urlMatches(undefined, url, urlMatch)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,144 +19,45 @@ import https from 'https';
|
||||||
import url from 'url';
|
import url from 'url';
|
||||||
import type { APIRequestContext } from './fetch';
|
import type { APIRequestContext } from './fetch';
|
||||||
import { SdkObject } from './instrumentation';
|
import { SdkObject } from './instrumentation';
|
||||||
import type { RemoteAddr, RequestContext, ResourceTiming, SecurityDetails } from './network';
|
import type { RequestContext, ResourceTiming, SecurityDetails } from './network';
|
||||||
import { Request, Response, Route } from './network';
|
import { Request, Response, Route } from './network';
|
||||||
import type { HeadersArray, NormalizedContinueOverrides, NormalizedFulfillResponse } from './types';
|
import type { HeadersArray, } from './types';
|
||||||
import { ManualPromise, monotonicTime } from 'playwright-core/lib/utils';
|
import { HttpServer, ManualPromise, monotonicTime } from '../utils';
|
||||||
import type { WorkerHttpServer } from './dispatchers/localUtilsDispatcher';
|
|
||||||
import { TLSSocket } from 'tls';
|
import { TLSSocket } from 'tls';
|
||||||
import type { AddressInfo } from 'net';
|
import type { AddressInfo } from 'net';
|
||||||
import { pipeline } from 'stream/promises';
|
import { pipeline } from 'stream/promises';
|
||||||
import { Transform } from 'stream';
|
import { Transform } from 'stream';
|
||||||
|
|
||||||
type InterceptorResult =
|
export class MockingProxy extends SdkObject implements RequestContext {
|
||||||
| { result: 'continue', request: Request, overrides?: NormalizedContinueOverrides }
|
|
||||||
| { result: 'abort', request: Request, errorCode: string }
|
|
||||||
| { result: 'fulfill', request: Request, response: NormalizedFulfillResponse };
|
|
||||||
|
|
||||||
interface EventDelegate {
|
|
||||||
onRequest(request: Request): void;
|
|
||||||
onRequestFinished(request: Request, response: Response): void;
|
|
||||||
onRequestFailed(request: Request): void;
|
|
||||||
onResponse(request: Request, response: Response): void;
|
|
||||||
onRoute(route: Route, request: Request): void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ServerInterceptionRegistry extends SdkObject implements RequestContext {
|
|
||||||
private _eventDelegate: EventDelegate;
|
|
||||||
fetchRequest: APIRequestContext;
|
fetchRequest: APIRequestContext;
|
||||||
private _matches?: (url: string) => boolean;
|
private _matches?: (url: string) => boolean;
|
||||||
|
private _httpServer = new WorkerHttpServer();
|
||||||
|
|
||||||
constructor(parent: SdkObject, requestContext: APIRequestContext, eventDelegate: EventDelegate) {
|
constructor(parent: SdkObject, requestContext: APIRequestContext) {
|
||||||
super(parent, 'serverInterceptionRegistry');
|
super(parent, 'MockingProxy');
|
||||||
this._eventDelegate = eventDelegate;
|
|
||||||
this.fetchRequest = requestContext;
|
this.fetchRequest = requestContext;
|
||||||
}
|
|
||||||
|
|
||||||
setRequestInterceptor(matches?: (url: string) => boolean) {
|
this._httpServer.routePrefix('/', (req, res) => {
|
||||||
this._matches = matches;
|
|
||||||
}
|
|
||||||
|
|
||||||
handle(url: string, method: string, body: Buffer | null, headers: HeadersArray): Promise<InterceptorResult> {
|
|
||||||
const request = new Request(this, null, null, null, undefined, url, '', method, body, headers);
|
|
||||||
request.setRawRequestHeaders(headers);
|
|
||||||
this._eventDelegate.onRequest(request);
|
|
||||||
|
|
||||||
if (!this._matches?.(url))
|
|
||||||
return Promise.resolve({ result: 'continue', request });
|
|
||||||
|
|
||||||
return new Promise(resolve => {
|
|
||||||
const route = new Route(request, {
|
|
||||||
async abort(errorCode) {
|
|
||||||
resolve({ result: 'abort', request, errorCode });
|
|
||||||
},
|
|
||||||
async continue(overrides) {
|
|
||||||
resolve({ result: 'continue', request, overrides });
|
|
||||||
},
|
|
||||||
async fulfill(response) {
|
|
||||||
resolve({ result: 'fulfill', request, response });
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
this._eventDelegate.onRoute(route, request);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
failed(request: Request, error: string) {
|
|
||||||
request._setFailureText(error);
|
|
||||||
this._eventDelegate.onRequestFailed(request);
|
|
||||||
}
|
|
||||||
|
|
||||||
response(request: Request, status: number, statusText: string, headers: HeadersArray, body: () => Promise<Buffer>, httpVersion: string, timing: ResourceTiming, securityDetails: SecurityDetails | undefined, serverAddr: RemoteAddr | undefined) {
|
|
||||||
const response = new Response(request, status, statusText, headers, timing, body, false, httpVersion);
|
|
||||||
response.setRawResponseHeaders(headers);
|
|
||||||
response._securityDetailsFinished(securityDetails);
|
|
||||||
response._serverAddrFinished(serverAddr);
|
|
||||||
this._eventDelegate.onResponse(request, response);
|
|
||||||
|
|
||||||
return {
|
|
||||||
finished: async (responseEndTiming: number, transferSize: number, encodedBodySize: number) => {
|
|
||||||
response._requestFinished(responseEndTiming);
|
|
||||||
response.setTransferSize(transferSize);
|
|
||||||
response.setEncodedBodySize(encodedBodySize);
|
|
||||||
response.setResponseHeadersSize(transferSize - encodedBodySize);
|
|
||||||
this._eventDelegate.onRequestFinished(request, response);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
addRouteInFlight(route: Route): void {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
removeRouteInFlight(route: Route): void {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function headersArray(req: Pick<http.IncomingMessage, 'headersDistinct'>): HeadersArray {
|
|
||||||
return Object.entries(req.headersDistinct).flatMap(([name, values = []]) => values.map(value => ({ name, value })));
|
|
||||||
}
|
|
||||||
|
|
||||||
function headersArrayToOutgoingHeaders(headers: HeadersArray) {
|
|
||||||
const result: http.OutgoingHttpHeaders = {};
|
|
||||||
for (const { name, value } of headers) {
|
|
||||||
if (result[name] === undefined)
|
|
||||||
result[name] = value;
|
|
||||||
else if (Array.isArray(result[name]))
|
|
||||||
result[name].push(value);
|
|
||||||
else
|
|
||||||
result[name] = [result[name] as string, value];
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function collectBody(req: http.IncomingMessage) {
|
|
||||||
return await new Promise<Buffer>((resolve, reject) => {
|
|
||||||
const chunks: Buffer[] = [];
|
|
||||||
req.on('data', chunk => chunks.push(chunk));
|
|
||||||
req.on('end', () => resolve(Buffer.concat(chunks)));
|
|
||||||
req.on('error', reject);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export class MockingProxy {
|
|
||||||
private readonly _registry: ServerInterceptionRegistry;
|
|
||||||
|
|
||||||
constructor(registry: ServerInterceptionRegistry) {
|
|
||||||
this._registry = registry;
|
|
||||||
}
|
|
||||||
|
|
||||||
install(server: WorkerHttpServer) {
|
|
||||||
server.routePrefix('/', (req, res) => {
|
|
||||||
this._proxy(req, res);
|
this._proxy(req, res);
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
server.server().on('connect', (req, socket, head) => {
|
this._httpServer.server().on('connect', (req, socket, head) => {
|
||||||
socket.end('HTTP/1.1 405 Method Not Allowed\r\n\r\n');
|
socket.end('HTTP/1.1 405 Method Not Allowed\r\n\r\n');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async start(port?: number): Promise<void> {
|
||||||
|
await this._httpServer.start({ port });
|
||||||
|
}
|
||||||
|
|
||||||
|
get port() {
|
||||||
|
return this._httpServer.port();
|
||||||
|
}
|
||||||
|
|
||||||
|
setInterceptionPatterns(matches?: (url: string) => boolean) {
|
||||||
|
this._matches = matches;
|
||||||
|
}
|
||||||
|
|
||||||
private async _proxy(req: http.IncomingMessage, res: http.ServerResponse) {
|
private async _proxy(req: http.IncomingMessage, res: http.ServerResponse) {
|
||||||
if (req.url?.startsWith('/'))
|
if (req.url?.startsWith('/'))
|
||||||
req.url = req.url.substring(1);
|
req.url = req.url.substring(1);
|
||||||
|
|
@ -170,14 +71,14 @@ export class MockingProxy {
|
||||||
delete req.headersDistinct.host;
|
delete req.headersDistinct.host;
|
||||||
const headers = headersArray(req);
|
const headers = headersArray(req);
|
||||||
const body = await collectBody(req);
|
const body = await collectBody(req);
|
||||||
const result = await this._registry.handle(req.url!, req.method!, body, headers);
|
const request = new Request(this, null, null, null, undefined, req.url!, '', req.method!, body, headers);
|
||||||
switch (result.result) {
|
request.setRawRequestHeaders(headers);
|
||||||
case 'abort': {
|
|
||||||
req.destroy(result.errorCode ? new Error(result.errorCode) : undefined);
|
const route = new Route(request, {
|
||||||
return;
|
abort: async errorCode => {
|
||||||
}
|
req.destroy(errorCode ? new Error(errorCode) : undefined);
|
||||||
case 'continue': {
|
},
|
||||||
const { overrides } = result;
|
continue: async overrides => {
|
||||||
const proxyUrl = url.parse(overrides?.url ?? req.url!);
|
const proxyUrl = url.parse(overrides?.url ?? req.url!);
|
||||||
const httpLib = proxyUrl.protocol === 'https:' ? https : http;
|
const httpLib = proxyUrl.protocol === 'https:' ? https : http;
|
||||||
const proxyHeaders = overrides?.headers ?? headers;
|
const proxyHeaders = overrides?.headers ?? headers;
|
||||||
|
|
@ -225,16 +126,11 @@ export class MockingProxy {
|
||||||
|
|
||||||
const address = socket.address() as AddressInfo;
|
const address = socket.address() as AddressInfo;
|
||||||
const responseBodyPromise = new ManualPromise<Buffer>();
|
const responseBodyPromise = new ManualPromise<Buffer>();
|
||||||
const response = this._registry.response(
|
const response = new Response(request, proxyRes.statusCode!, proxyRes.statusMessage!, headersArray(proxyRes), timings, () => responseBodyPromise, false, proxyRes.httpVersion);
|
||||||
result.request,
|
response.setRawResponseHeaders(headers);
|
||||||
proxyRes.statusCode!,
|
response._securityDetailsFinished(securityDetails);
|
||||||
proxyRes.statusMessage!, headersArray(proxyRes),
|
response._serverAddrFinished({ ipAddress: address.family === 'IPv6' ? `[${address.address}]` : address.address, port: address.port });
|
||||||
() => responseBodyPromise,
|
this.emit('response', response);
|
||||||
proxyRes.httpVersion,
|
|
||||||
timings,
|
|
||||||
securityDetails,
|
|
||||||
{ ipAddress: address.family === 'IPv6' ? `[${address.address}]` : address.address, port: address.port },
|
|
||||||
);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
res.writeHead(proxyRes.statusCode!, proxyRes.headers);
|
res.writeHead(proxyRes.statusCode!, proxyRes.headers);
|
||||||
|
|
@ -253,20 +149,24 @@ export class MockingProxy {
|
||||||
const body = Buffer.concat(chunks);
|
const body = Buffer.concat(chunks);
|
||||||
responseBodyPromise.resolve(body);
|
responseBodyPromise.resolve(body);
|
||||||
|
|
||||||
response.finished(
|
const transferSize = socket.bytesRead - socketBytesReadStart;
|
||||||
monotonicTime() - startAt,
|
const encodedBodySize = body.byteLength;
|
||||||
socket.bytesRead - socketBytesReadStart,
|
response._requestFinished(monotonicTime() - startAt);
|
||||||
body.byteLength
|
response.setTransferSize(transferSize);
|
||||||
);
|
response.setEncodedBodySize(encodedBodySize);
|
||||||
|
response.setResponseHeadersSize(transferSize - encodedBodySize);
|
||||||
|
this.emit('requestFinished', response);
|
||||||
resolve();
|
resolve();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this._registry.failed(result.request, error.toString());
|
request._setFailureText('' + error);
|
||||||
|
this.emit('failed', request);
|
||||||
resolve();
|
resolve();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
proxyReq.on('error', error => {
|
proxyReq.on('error', error => {
|
||||||
this._registry.failed(result.request, error.toString());
|
request._setFailureText('' + error);
|
||||||
|
this.emit('failed', request);
|
||||||
res.statusCode = 502;
|
res.statusCode = 502;
|
||||||
res.end(resolve);
|
res.end(resolve);
|
||||||
});
|
});
|
||||||
|
|
@ -283,19 +183,51 @@ export class MockingProxy {
|
||||||
});
|
});
|
||||||
proxyReq.end(proxyBody);
|
proxyReq.end(proxyBody);
|
||||||
});
|
});
|
||||||
}
|
},
|
||||||
case 'fulfill': {
|
fulfill: async ({ status, headers, body, isBase64 }) => {
|
||||||
const { response: { status, headers, body, isBase64 } } = result;
|
|
||||||
res.statusCode = status;
|
res.statusCode = status;
|
||||||
for (const { name, value } of headers)
|
for (const { name, value } of headers)
|
||||||
res.appendHeader(name, value);
|
res.appendHeader(name, value);
|
||||||
res.sendDate = false;
|
res.sendDate = false;
|
||||||
res.end(Buffer.from(body, isBase64 ? 'base64' : 'utf-8'));
|
res.end(Buffer.from(body, isBase64 ? 'base64' : 'utf-8'));
|
||||||
return;
|
},
|
||||||
}
|
});
|
||||||
default: {
|
|
||||||
throw new Error('Unexpected result');
|
if (this._matches?.(req.url!))
|
||||||
}
|
this.emit('route', { route, request });
|
||||||
}
|
else
|
||||||
|
await route.continue({ isFallback: false });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function headersArray(req: Pick<http.IncomingMessage, 'headersDistinct'>): HeadersArray {
|
||||||
|
return Object.entries(req.headersDistinct).flatMap(([name, values = []]) => values.map(value => ({ name, value })));
|
||||||
|
}
|
||||||
|
|
||||||
|
function headersArrayToOutgoingHeaders(headers: HeadersArray) {
|
||||||
|
const result: http.OutgoingHttpHeaders = {};
|
||||||
|
for (const { name, value } of headers) {
|
||||||
|
if (result[name] === undefined)
|
||||||
|
result[name] = value;
|
||||||
|
else if (Array.isArray(result[name]))
|
||||||
|
result[name].push(value);
|
||||||
|
else
|
||||||
|
result[name] = [result[name] as string, value];
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function collectBody(req: http.IncomingMessage) {
|
||||||
|
return await new Promise<Buffer>((resolve, reject) => {
|
||||||
|
const chunks: Buffer[] = [];
|
||||||
|
req.on('data', chunk => chunks.push(chunk));
|
||||||
|
req.on('end', () => resolve(Buffer.concat(chunks)));
|
||||||
|
req.on('error', reject);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export class WorkerHttpServer extends HttpServer {
|
||||||
|
override _handleCORS(request: http.IncomingMessage, response: http.ServerResponse): boolean {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -89,10 +89,10 @@ export function stripFragmentFromUrl(url: string): string {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RequestContext extends SdkObject {
|
export interface RequestContext extends SdkObject {
|
||||||
addRouteInFlight(route: Route): void;
|
|
||||||
removeRouteInFlight(route: Route): void;
|
|
||||||
|
|
||||||
fetchRequest: APIRequestContext;
|
fetchRequest: APIRequestContext;
|
||||||
|
|
||||||
|
addRouteInFlight?(route: Route): void;
|
||||||
|
removeRouteInFlight?(route: Route): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Request extends SdkObject {
|
export class Request extends SdkObject {
|
||||||
|
|
@ -261,7 +261,7 @@ export class Route extends SdkObject {
|
||||||
super(request._frame || request._context, 'route');
|
super(request._frame || request._context, 'route');
|
||||||
this._request = request;
|
this._request = request;
|
||||||
this._delegate = delegate;
|
this._delegate = delegate;
|
||||||
this._request._context.addRouteInFlight(this);
|
this._request._context.addRouteInFlight?.(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
request(): Request {
|
request(): Request {
|
||||||
|
|
@ -347,7 +347,7 @@ export class Route extends SdkObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
private _endHandling() {
|
private _endHandling() {
|
||||||
this._request._context.removeRouteInFlight(this);
|
this._request._context.removeRouteInFlight?.(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
2
packages/protocol/src/channels.d.ts
vendored
2
packages/protocol/src/channels.d.ts
vendored
|
|
@ -455,7 +455,7 @@ export type LocalUtilsInitializer = {
|
||||||
};
|
};
|
||||||
export interface LocalUtilsEventTarget {
|
export interface LocalUtilsEventTarget {
|
||||||
}
|
}
|
||||||
export interface LocalUtilsChannel extends LocalUtilsEventTarget, EventTargetChannel {
|
export interface LocalUtilsChannel extends LocalUtilsEventTarget, Channel {
|
||||||
_type_LocalUtils: boolean;
|
_type_LocalUtils: boolean;
|
||||||
zip(params: LocalUtilsZipParams, metadata?: CallMetadata): Promise<LocalUtilsZipResult>;
|
zip(params: LocalUtilsZipParams, metadata?: CallMetadata): Promise<LocalUtilsZipResult>;
|
||||||
harOpen(params: LocalUtilsHarOpenParams, metadata?: CallMetadata): Promise<LocalUtilsHarOpenResult>;
|
harOpen(params: LocalUtilsHarOpenParams, metadata?: CallMetadata): Promise<LocalUtilsHarOpenResult>;
|
||||||
|
|
|
||||||
|
|
@ -551,8 +551,6 @@ EventTarget:
|
||||||
LocalUtils:
|
LocalUtils:
|
||||||
type: interface
|
type: interface
|
||||||
|
|
||||||
extends: EventTarget
|
|
||||||
|
|
||||||
initializer:
|
initializer:
|
||||||
deviceDescriptors:
|
deviceDescriptors:
|
||||||
type: array
|
type: array
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue