fix: "bypass" proxying network requests from extra browser tabs/windows (#28188)

Co-authored-by: Ryan Manuel <ryanm@cypress.io>
Co-authored-by: Matt Schile <mschile@cypress.io>
This commit is contained in:
Chris Breiding
2023-11-02 13:55:13 -04:00
committed by GitHub
parent becb893f07
commit a0cfed5044
9 changed files with 527 additions and 47 deletions

View File

@@ -106,6 +106,7 @@ const READONLY_MIDDLEWARE_KEYS: (keyof HttpMiddlewareThis<{}>)[] = [
'onResponse',
'onError',
'skipMiddleware',
'onlyRunMiddleware',
]
export type HttpMiddlewareThis<T> = HttpMiddlewareCtx<T> & ServerCtx & Readonly<{
@@ -119,6 +120,7 @@ export type HttpMiddlewareThis<T> = HttpMiddlewareCtx<T> & ServerCtx & Readonly<
onResponse: (incomingRes: IncomingMessage, resStream: Readable) => void
onError: (error: Error) => void
skipMiddleware: (name: string) => void
onlyRunMiddleware: (names: string[]) => void
}>
export function _runStage (type: HttpStages, ctx: any, onError: Function) {
@@ -220,9 +222,12 @@ export function _runStage (type: HttpStages, ctx: any, onError: Function) {
_end()
},
onError: _onError,
skipMiddleware: (name) => {
skipMiddleware: (name: string) => {
ctx.middleware[type] = _.omit(ctx.middleware[type], name)
},
onlyRunMiddleware: (names: string[]) => {
ctx.middleware[type] = _.pick(ctx.middleware[type], names)
},
...ctx,
}

View File

@@ -10,6 +10,7 @@ import { doesTopNeedToBeSimulated } from './util/top-simulation'
import type { HttpMiddleware } from './'
import type { CypressIncomingRequest } from '../types'
// do not use a debug namespace in this file - use the per-request `this.debug` instead
// available as cypress-verbose:proxy:http
// eslint-disable-next-line @typescript-eslint/no-unused-vars
@@ -31,15 +32,30 @@ const ExtractCypressMetadataHeaders: RequestMiddleware = function () {
const span = telemetry.startSpan({ name: 'extract:cypress:metadata:headers', parentSpan: this.reqMiddlewareSpan, isVerbose })
this.req.isAUTFrame = !!this.req.headers['x-cypress-is-aut-frame']
span?.setAttributes({
isAUTFrame: this.req.isAUTFrame,
})
this.req.isFromExtraTarget = !!this.req.headers['x-cypress-is-from-extra-target']
if (this.req.headers['x-cypress-is-aut-frame']) {
delete this.req.headers['x-cypress-is-aut-frame']
}
span?.setAttributes({
isAUTFrame: this.req.isAUTFrame,
isFromExtraTarget: this.req.isFromExtraTarget,
})
// we only want to intercept requests from the main target and not ones from
// extra tabs or windows, so run the bare minimum request/response middleware
// to send the request/response directly through
if (this.req.isFromExtraTarget) {
this.debug('request for [%s %s] is from an extra target', this.req.method, this.req.proxiedUrl)
delete this.req.headers['x-cypress-is-from-extra-target']
this.onlyRunMiddleware([
'SendRequestOutgoing',
])
}
span?.end()
this.next()
}

View File

@@ -167,6 +167,27 @@ const LogResponse: ResponseMiddleware = function () {
this.next()
}
const FilterNonProxiedResponse: ResponseMiddleware = function () {
// if the request is from an extra target (i.e. not the main Cypress tab, but
// an extra tab/window), we want to skip any manipulation of the response and
// only run the middleware necessary to get it back to the browser
if (this.req.isFromExtraTarget) {
this.debug('response for [%s %s] is from extra target', this.req.method, this.req.proxiedUrl)
this.onlyRunMiddleware([
'AttachPlainTextStreamFn',
'PatchExpressSetHeader',
'MaybeSendRedirectToClient',
'CopyResponseStatusCode',
'MaybeEndWithEmptyBody',
'GzipBody',
'SendResponseBodyToClient',
])
}
this.next()
}
const AttachPlainTextStreamFn: ResponseMiddleware = function () {
this.makeResStreamPlainText = function () {
const span = telemetry.startSpan({ name: 'make:res:stream:plain:text', parentSpan: this.resMiddlewareSpan, isVerbose })
@@ -869,6 +890,7 @@ const SendResponseBodyToClient: ResponseMiddleware = function () {
export default {
LogResponse,
FilterNonProxiedResponse,
AttachPlainTextStreamFn,
InterceptResponse,
PatchExpressSetHeader,

View File

@@ -18,6 +18,7 @@ export type CypressIncomingRequest = Request & {
followRedirect?: boolean
isAUTFrame: boolean
credentialsLevel?: RequestCredentialLevel
isFromExtraTarget: boolean
/**
* Resource type from browserPreRequest. Copied to req so intercept matching can work.
*/