Files
cypress/packages/proxy/lib/http/request-middleware.ts
T
Zach Bloomquist b0378dc04e Refactor proxy into own package, implement middleware pattern (#5136)
* renames

* Refactor proxy into own package, implement middleware pattern

don't need these mocha opts anymore

fix test

no more zunder

READMEs

fix test

* pass request by reference

* fix cors path

* Move replace_stream to proxy, concat-stream util in network

* Pin dependency versions

* Revert addDefaultPort behavior

* Add READMEs for proxy, network

* Update README.md

* eslint --fix

* set to null not undefined

* use delete and bump node types

* import cors from package now

* parse-domain@2.3.4

* proxy package needs common-tags

* move pumpify dep

* load through where it's needed, remove unused passthru_stream

* remove unneeded getbuffer call


Co-authored-by: Gleb Bahmutov <gleb.bahmutov@gmail.com>
2019-11-27 19:16:15 -05:00

157 lines
4.4 KiB
TypeScript

import _ from 'lodash'
import { blacklist, cors } from '@packages/network'
import debugModule from 'debug'
import { HttpMiddleware } from '.'
export type RequestMiddleware = HttpMiddleware<{
outgoingReq: any
}>
const debug = debugModule('cypress:proxy:http:request-middleware')
const LogRequest: RequestMiddleware = function () {
debug('proxying request %o', {
req: _.pick(this.req, 'method', 'proxiedUrl', 'headers'),
})
this.next()
}
const RedirectToClientRouteIfUnloaded: RequestMiddleware = function () {
// if we have an unload header it means our parent app has been navigated away
// directly and we need to automatically redirect to the clientRoute
if (this.req.cookies['__cypress.unload']) {
this.res.redirect(this.config.clientRoute)
return this.end()
}
this.next()
}
// TODO: is this necessary? it seems to be for requesting Cypress w/o the proxy,
// which isn't currently supported
const RedirectToClientRouteIfNotProxied: RequestMiddleware = function () {
// when you access cypress from a browser which has not had its proxy setup then
// req.url will match req.proxiedUrl and we'll know to instantly redirect them
// to the correct client route
if (this.req.url === this.req.proxiedUrl && !this.getRemoteState().visiting) {
// if we dont have a remoteState.origin that means we're initially requesting
// the cypress app and we need to redirect to the root path that serves the app
this.res.redirect(this.config.clientRoute)
return this.end()
}
this.next()
}
const EndRequestsToBlacklistedHosts: RequestMiddleware = function () {
const { blacklistHosts } = this.config
if (blacklistHosts) {
const matches = blacklist.matches(this.req.proxiedUrl, blacklistHosts)
if (matches) {
this.res.set('x-cypress-matched-blacklisted-host', matches)
debug('blacklisting request %o', {
url: this.req.proxiedUrl,
matches,
})
this.res.status(503).end()
return this.end()
}
}
this.next()
}
const MaybeEndRequestWithBufferedResponse: RequestMiddleware = function () {
const buffer = this.buffers.take(this.req.proxiedUrl)
if (buffer) {
debug('got a buffer %o', buffer)
this.res.wantsInjection = 'full'
return this.onResponse(buffer.response, buffer.stream)
}
this.next()
}
const StripUnsupportedAcceptEncoding: RequestMiddleware = function () {
// Cypress can only support plaintext or gzip, so make sure we don't request anything else
const acceptEncoding = this.req.headers['accept-encoding']
if (acceptEncoding) {
if (acceptEncoding.includes('gzip')) {
this.req.headers['accept-encoding'] = 'gzip'
} else {
delete this.req.headers['accept-encoding']
}
}
this.next()
}
function reqNeedsBasicAuthHeaders (req, { auth, origin }) {
//if we have auth headers, this request matches our origin, protection space, and the user has not supplied auth headers
return auth && !req.headers['authorization'] && cors.urlMatchesOriginProtectionSpace(req.proxiedUrl, origin)
}
const MaybeSetBasicAuthHeaders: RequestMiddleware = function () {
const remoteState = this.getRemoteState()
if (reqNeedsBasicAuthHeaders(this.req, remoteState)) {
const { auth } = remoteState
const base64 = Buffer.from(`${auth.username}:${auth.password}`).toString('base64')
this.req.headers['authorization'] = `Basic ${base64}`
}
this.next()
}
const SendRequestOutgoing: RequestMiddleware = function () {
const requestOptions = {
timeout: this.config.responseTimeout,
strictSSL: false,
followRedirect: false,
retryIntervals: [0, 100, 200, 200],
url: this.req.proxiedUrl,
}
const remoteState = this.getRemoteState()
if (remoteState.strategy === 'file' && requestOptions.url.startsWith(remoteState.origin)) {
requestOptions.url = requestOptions.url.replace(remoteState.origin, remoteState.fileServer)
}
const req = this.request.create(requestOptions)
req.on('error', this.onError)
req.on('response', (incomingRes) => this.onResponse(incomingRes, req))
this.req.on('aborted', () => {
debug('request aborted')
req.abort()
})
// pipe incoming request body, headers to new request
this.req.pipe(req)
this.outgoingReq = req
}
export default {
LogRequest,
RedirectToClientRouteIfUnloaded,
RedirectToClientRouteIfNotProxied,
EndRequestsToBlacklistedHosts,
MaybeEndRequestWithBufferedResponse,
StripUnsupportedAcceptEncoding,
MaybeSetBasicAuthHeaders,
SendRequestOutgoing,
}