mirror of
https://github.com/sourcegraph/sourcegraph.git
synced 2026-02-06 12:51:55 +00:00
Fix local dev server streaming resources (to unblock streaming search and Cody streaming) (#50725)
This PR fixes the local dev server for both the webpack and esbuild setups to no longer buffer requests to streaming endpoints. This fixes streaming search and Cody in the `web-standalone` run options. ## Test plan <!-- All pull requests REQUIRE a test plan: https://docs.sourcegraph.com/dev/background-information/testing_principles --> https://user-images.githubusercontent.com/458591/232517302-d6993a12-9c59-4435-8073-fde8c89d8536.mov
This commit is contained in:
parent
0e5ffb8772
commit
a53643b9bb
@ -1,6 +1,10 @@
|
||||
import type * as http from 'http'
|
||||
import * as zlib from 'zlib'
|
||||
|
||||
import { Options, responseInterceptor } from 'http-proxy-middleware'
|
||||
|
||||
import { ENVIRONMENT_CONFIG, HTTPS_WEB_SERVER_URL } from './environment-config'
|
||||
import { STREAMING_ENDPOINTS } from './should-compress-response'
|
||||
|
||||
// One of the API routes: "/-/sign-in".
|
||||
const PROXY_ROUTES = ['/.api', '/search/stream', '/-', '/.auth']
|
||||
@ -34,7 +38,7 @@ export function getAPIProxySettings(options: GetAPIProxySettingsOptions): ProxyS
|
||||
// Prevent automatic call of res.end() in `onProxyRes`. It is handled by `responseInterceptor`.
|
||||
selfHandleResponse: true,
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises, @typescript-eslint/require-await
|
||||
onProxyRes: responseInterceptor(async (responseBuffer, proxyRes) => {
|
||||
onProxyRes: conditionalResponseInterceptor(STREAMING_ENDPOINTS, async (responseBuffer, proxyRes) => {
|
||||
// Propagate cookies to enable authentication on the remote server.
|
||||
if (proxyRes.headers['set-cookie']) {
|
||||
// Remove `Secure` and `SameSite` from `set-cookie` headers.
|
||||
@ -108,3 +112,78 @@ function getRemoteJsContextScript(remoteIndexHTML: string): string {
|
||||
|
||||
return remoteIndexHTML.slice(remoteJsContextStart, remoteJsContextEnd) + jsContextChanges
|
||||
}
|
||||
|
||||
type Interceptor = (
|
||||
buffer: Buffer,
|
||||
proxyRes: http.IncomingMessage,
|
||||
req: http.IncomingMessage,
|
||||
res: http.ServerResponse
|
||||
) => Promise<Buffer | string>
|
||||
|
||||
function conditionalResponseInterceptor(
|
||||
ignoredRoutes: string[],
|
||||
interceptor: Interceptor
|
||||
): (proxyRes: http.IncomingMessage, req: http.IncomingMessage, res: http.ServerResponse) => Promise<void> {
|
||||
const unconditionalResponseInterceptor = responseInterceptor(interceptor)
|
||||
|
||||
return async function proxyResResponseInterceptor(
|
||||
proxyRes: http.IncomingMessage,
|
||||
req: http.IncomingMessage,
|
||||
res: http.ServerResponse
|
||||
): Promise<void> {
|
||||
let shouldStream = false
|
||||
for (const route of ignoredRoutes) {
|
||||
if (req.url?.startsWith(route)) {
|
||||
shouldStream = true
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldStream) {
|
||||
return new Promise(resolve => {
|
||||
res.setHeader('content-type', 'text/event-stream')
|
||||
const _proxyRes = decompress(proxyRes, proxyRes.headers['content-encoding'])
|
||||
|
||||
_proxyRes.on('data', (chunk: any) => res.write(chunk))
|
||||
_proxyRes.on('end', () => {
|
||||
res.end()
|
||||
resolve()
|
||||
})
|
||||
_proxyRes.on('error', () => {
|
||||
res.end()
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
return unconditionalResponseInterceptor(proxyRes, req, res)
|
||||
}
|
||||
}
|
||||
|
||||
function decompress<TReq extends http.IncomingMessage = http.IncomingMessage>(
|
||||
proxyRes: TReq,
|
||||
contentEncoding?: string
|
||||
): TReq | zlib.Gunzip | zlib.Inflate | zlib.BrotliDecompress {
|
||||
let _proxyRes: TReq | zlib.Gunzip | zlib.Inflate | zlib.BrotliDecompress = proxyRes
|
||||
let decompress
|
||||
|
||||
switch (contentEncoding) {
|
||||
case 'gzip':
|
||||
decompress = zlib.createGunzip()
|
||||
break
|
||||
case 'br':
|
||||
decompress = zlib.createBrotliDecompress()
|
||||
break
|
||||
case 'deflate':
|
||||
decompress = zlib.createInflate()
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
if (decompress) {
|
||||
_proxyRes.pipe(decompress)
|
||||
_proxyRes = decompress
|
||||
}
|
||||
|
||||
return _proxyRes
|
||||
}
|
||||
|
||||
@ -1,14 +1,15 @@
|
||||
import compression, { CompressionFilter } from 'compression'
|
||||
|
||||
export const STREAMING_ENDPOINTS = ['/search/stream', '/.api/compute/stream', '/.api/completions/stream']
|
||||
|
||||
export const shouldCompressResponse: CompressionFilter = (request, response) => {
|
||||
// Disable compression because gzip buffers the full response
|
||||
// before sending it, blocking streaming on some endpoints.
|
||||
if (request.path.startsWith('/search/stream')) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (request.path.startsWith('/.api/compute/stream')) {
|
||||
return false
|
||||
for (const endpoint of STREAMING_ENDPOINTS) {
|
||||
if (request.path.startsWith(endpoint)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// fallback to standard filter function
|
||||
|
||||
@ -268,7 +268,7 @@
|
||||
"graphql": "^15.4.0",
|
||||
"graphql-schema-linter": "^2.0.1",
|
||||
"gulp": "^4.0.2",
|
||||
"http-proxy-middleware": "^1.1.2",
|
||||
"http-proxy-middleware": "^2.0.6",
|
||||
"identity-obj-proxy": "^3.0.0",
|
||||
"jest": "^28.1.0",
|
||||
"jest-canvas-mock": "^2.3.0",
|
||||
|
||||
669
pnpm-lock.yaml
669
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user