UNPKG

@revoloo/cypress6

Version:

Cypress.io end to end testing tool

205 lines (161 loc) 5.9 kB
import _ from 'lodash' import { concatStream } from '@packages/network' import Debug from 'debug' import url from 'url' import { CypressIncomingRequest, RequestMiddleware, } from '@packages/proxy' import { BackendRoute, BackendRequest, NetStubbingState, } from './types' import { CyHttpMessages, NetEventFrames, SERIALIZABLE_REQ_PROPS, } from '../types' import { getRouteForRequest, matchesRoutePreflight } from './route-matching' import { sendStaticResponse, emit, setResponseFromFixture, setDefaultHeaders, } from './util' import CyServer from '@packages/server' const debug = Debug('cypress:net-stubbing:server:intercept-request') /** * Called when a new request is received in the proxy layer. */ export const InterceptRequest: RequestMiddleware = function () { if (matchesRoutePreflight(this.netStubbingState.routes, this.req)) { // send positive CORS preflight response return sendStaticResponse(this, { statusCode: 204, headers: { 'access-control-max-age': '-1', 'access-control-allow-credentials': 'true', 'access-control-allow-origin': this.req.headers.origin || '*', 'access-control-allow-methods': this.req.headers['access-control-request-method'] || '*', 'access-control-allow-headers': this.req.headers['access-control-request-headers'] || '*', }, }) } const route = getRouteForRequest(this.netStubbingState.routes, this.req) if (!route) { // not intercepted, carry on normally... return this.next() } const requestId = _.uniqueId('interceptedRequest') debug('intercepting request %o', { requestId, route, req: _.pick(this.req, 'url') }) const request: BackendRequest = { requestId, route, continueRequest: this.next, onError: this.onError, onResponse: (incomingRes, resStream) => { setDefaultHeaders(this.req, incomingRes) this.onResponse(incomingRes, resStream) }, req: this.req, res: this.res, } // attach requestId to the original req object for later use this.req.requestId = requestId this.netStubbingState.requests[requestId] = request _interceptRequest(this.netStubbingState, request, route, this.socket) } function _interceptRequest (state: NetStubbingState, request: BackendRequest, route: BackendRoute, socket: CyServer.Socket) { const notificationOnly = !route.hasInterceptor const frame: NetEventFrames.HttpRequestReceived = { routeHandlerId: route.handlerId!, requestId: request.req.requestId, req: _.extend(_.pick(request.req, SERIALIZABLE_REQ_PROPS), { url: request.req.proxiedUrl, }) as CyHttpMessages.IncomingRequest, notificationOnly, } request.res.once('finish', () => { emit(socket, 'http:request:complete', { requestId: request.requestId, routeHandlerId: route.handlerId!, }) debug('request/response finished, cleaning up %o', { requestId: request.requestId }) delete state.requests[request.requestId] }) const emitReceived = () => { emit(socket, 'http:request:received', frame) } const ensureBody = (cb: () => void) => { if (frame.req.body) { return cb() } request.req.pipe(concatStream((reqBody) => { const contentType = frame.req.headers['content-type'] const isMultipart = contentType && contentType.includes('multipart/form-data') request.req.body = frame.req.body = isMultipart ? reqBody : reqBody.toString() cb() })) } if (route.staticResponse) { const { staticResponse } = route return ensureBody(() => { emitReceived() sendStaticResponse(request, staticResponse) }) } if (notificationOnly) { return ensureBody(() => { emitReceived() const nextRoute = getNextRoute(state, request.req, frame.routeHandlerId) if (!nextRoute) { return request.continueRequest() } _interceptRequest(state, request, nextRoute, socket) }) } ensureBody(emitReceived) } /** * If applicable, return the route that is next in line after `prevRouteHandlerId` to handle `req`. */ function getNextRoute (state: NetStubbingState, req: CypressIncomingRequest, prevRouteHandlerId: string): BackendRoute | undefined { const prevRoute = _.find(state.routes, { handlerId: prevRouteHandlerId }) if (!prevRoute) { return } return getRouteForRequest(state.routes, req, prevRoute) } export async function onRequestContinue (state: NetStubbingState, frame: NetEventFrames.HttpRequestContinue, socket: CyServer.Socket) { const backendRequest = state.requests[frame.requestId] if (!backendRequest) { debug('onRequestContinue received but no backendRequest exists %o', { frame }) return } frame.req.url = url.resolve(backendRequest.req.proxiedUrl, frame.req.url) // modify the original paused request object using what the client returned _.assign(backendRequest.req, _.pick(frame.req, SERIALIZABLE_REQ_PROPS)) // proxiedUrl is used to initialize the new request backendRequest.req.proxiedUrl = frame.req.url // update problematic headers // update content-length if available if (backendRequest.req.headers['content-length'] && frame.req.body != null) { backendRequest.req.headers['content-length'] = Buffer.from(frame.req.body).byteLength.toString() } if (frame.hasResponseHandler) { backendRequest.waitForResponseContinue = true } if (frame.tryNextRoute) { const nextRoute = getNextRoute(state, backendRequest.req, frame.routeHandlerId) if (!nextRoute) { return backendRequest.continueRequest() } return _interceptRequest(state, backendRequest, nextRoute, socket) } if (frame.staticResponse) { await setResponseFromFixture(backendRequest.route.getFixture, frame.staticResponse) return sendStaticResponse(backendRequest, frame.staticResponse) } backendRequest.continueRequest() }