eventstream pass 2 (#6170)

* match ssl timeline

* dedupe timeline logger

* fix lint

* fix timeline

* send headers
This commit is contained in:
Jack Kavanagh 2023-07-20 01:27:05 +02:00 committed by GitHub
parent 432c4a05c3
commit 00cd22c67b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 40 additions and 37 deletions

View File

@ -18,6 +18,7 @@ import { urlMatchesCertHost } from '../../network/url-matches-cert-host';
import { invariant } from '../../utils/invariant';
import { setDefaultProtocol } from '../../utils/url/protocol';
import { createConfiguredCurlInstance } from './libcurl-promise';
import { parseHeaderStrings } from './parse-header-strings';
export interface CurlConnection extends Curl {
_id: string;
@ -138,7 +139,6 @@ const openCurlConnection = async (
const clientCertificates = await models.clientCertificate.findByParentId(options.workspaceId);
const filteredClientCertificates = clientCertificates.filter(c => !c.disabled && urlMatchesCertHost(setDefaultProtocol(c.host, 'https:'), options.url));
const { curl, debugTimeline } = createConfiguredCurlInstance({
// TODO: get cookies here
req: { ...request, cookieJar: options.cookieJar, cookies: [] },
finalUrl: options.url,
settings,
@ -146,33 +146,12 @@ const openCurlConnection = async (
certificates: filteredClientCertificates,
});
debugTimeline.forEach(entry => timelineFileStreams.get(options.requestId)?.write(JSON.stringify(entry) + '\n'));
curl.enable(CurlFeature.StreamResponse);
curl.setOpt(Curl.option.DEBUGFUNCTION, (infoType, buffer) => {
const isSSLData = infoType === CurlInfoDebug.SslDataIn || infoType === CurlInfoDebug.SslDataOut;
const isEmpty = buffer.length === 0;
// Don't show cookie setting because this will display every domain in the jar
const isAddCookie = infoType === CurlInfoDebug.Text && buffer.toString('utf8').indexOf('Added cookie') === 0;
if (isSSLData || isEmpty || isAddCookie) {
return 0;
}
// NOTE: resolves "Text" from CurlInfoDebug[CurlInfoDebug.Text]
const name = CurlInfoDebug[infoType] as keyof typeof CurlInfoDebug;
let timelineMessage;
const isRequestData = infoType === CurlInfoDebug.DataOut;
if (isRequestData) {
// Ignore large post data messages
const isLessThan10KB = buffer.length / 1024 < (settings.maxTimelineDataSizeKB || 1);
timelineMessage = isLessThan10KB ? buffer.toString('utf8') : `(${describeByteSize(buffer.length)} hidden)`;
}
const isResponseData = infoType === CurlInfoDebug.DataIn;
if (!isResponseData) {
const value = timelineMessage || buffer.toString('utf8');
timelineFileStreams.get(options.requestId)?.write(JSON.stringify({ name, value, timestamp: Date.now() }) + '\n');
}
return 0;
});
curl.on('error', async (error, errorCode) => {
CurlConnections.set(options.requestId, curl);
CurlConnections.get(options.requestId)?.enable(CurlFeature.StreamResponse);
// TODO: add authHeader and request body?
const headerStrings = parseHeaderStrings({ req: request, finalUrl: options.url });
CurlConnections.get(options.requestId)?.setOpt(Curl.option.HTTPHEADER, headerStrings);
CurlConnections.get(options.requestId)?.on('error', async (error, errorCode) => {
const errorEvent: CurlErrorEvent = {
_id: uuidV4(),
requestId: options.requestId,
@ -193,7 +172,35 @@ const openCurlConnection = async (
}
});
curl.on('stream', async (stream: Readable, _code: number, [headersWithStatus]: HeaderInfo[]) => {
CurlConnections.get(options.requestId)?.setOpt(Curl.option.DEBUGFUNCTION, (infoType, buffer) => {
const isSSLData = infoType === CurlInfoDebug.SslDataIn || infoType === CurlInfoDebug.SslDataOut;
const isEmpty = buffer.length === 0;
// Don't show cookie setting because this will display every domain in the jar
const isAddCookie = infoType === CurlInfoDebug.Text && buffer.toString('utf8').indexOf('Added cookie') === 0;
if (isSSLData || isEmpty || isAddCookie) {
return 0;
}
// NOTE: resolves "Text" from CurlInfoDebug[CurlInfoDebug.Text]
let name = CurlInfoDebug[infoType] as keyof typeof CurlInfoDebug;
let timelineMessage;
const isRequestData = infoType === CurlInfoDebug.DataOut;
if (isRequestData) {
// Ignore large post data messages
const isLessThan10KB = buffer.length / 1024 < (settings.maxTimelineDataSizeKB || 1);
timelineMessage = isLessThan10KB ? buffer.toString('utf8') : `(${describeByteSize(buffer.length)} hidden)`;
}
const isResponseData = infoType === CurlInfoDebug.DataIn;
if (isResponseData) {
timelineMessage = `Received ${describeByteSize(buffer.length)} chunk`;
name = 'Text';
}
const value = timelineMessage || buffer.toString('utf8');
timelineFileStreams.get(options.requestId)?.write(JSON.stringify({ name, value, timestamp: Date.now() }) + '\n');
return 0;
});
CurlConnections.get(options.requestId)?.on('stream', async (stream: Readable, _code: number, [headersWithStatus]: HeaderInfo[]) => {
event.sender.send(readyStateChannel, true);
const { timeline, responseHeaders, statusCode, statusMessage, httpVersion } = parseHeadersAndBuildTimeline(options.url, headersWithStatus);
@ -233,10 +240,7 @@ const openCurlConnection = async (
}
timeline.map(t => timelineFileStreams.get(options.requestId)?.write(JSON.stringify(t) + '\n'));
// we are going to write the response stream to this file
const writableStream = eventLogFileStreams.get(request._id);
// two ways to go here
invariant(writableStream, 'writableStream should be defined');
invariant(eventLogFileStreams.get(request._id), 'writableStream should be defined');
for await (const chunk of stream) {
const messageEvent: CurlMessageEvent = {
_id: uuidV4(),
@ -254,7 +258,6 @@ const openCurlConnection = async (
event.sender.send(readyStateChannel, false);
});
curl.perform();
CurlConnections.set(options.requestId, curl);
} catch (e) {
console.error('unhandled error:', e);

View File

@ -122,8 +122,8 @@ export const curlRequest = (options: CurlRequestOptions) => new Promise<CurlRequ
curl.setOpt(Curl.option.CUSTOMREQUEST, method);
}
const requestBody = parseRequestBody({ body, method });
const requestBodyPath = await parseRequestBodyPath(body);
const requestBody = parseRequestBody({ body, method });
const isMultipart = body.mimeType === CONTENT_TYPE_FORM_DATA && requestBodyPath;
let requestFileDescriptor: number | undefined;
const { authentication } = req;
@ -165,7 +165,7 @@ export const curlRequest = (options: CurlRequestOptions) => new Promise<CurlRequ
responseBodyWriteStream.write(buffer);
return buffer.length;
});
// set up response logger
curl.setOpt(Curl.option.DEBUGFUNCTION, (infoType, buffer) => {
const isSSLData = infoType === CurlInfoDebug.SslDataIn || infoType === CurlInfoDebug.SslDataOut;
const isEmpty = buffer.length === 0;
@ -193,7 +193,6 @@ export const curlRequest = (options: CurlRequestOptions) => new Promise<CurlRequ
debugTimeline.push({ name, value, timestamp: Date.now() });
return 0;
});
// returns "rawHeaders" string in a buffer, rather than HeaderInfo[] type which is an object with deduped keys
// this provides support for multiple set-cookies and duplicated headers
curl.enable(CurlFeature.Raw);
@ -389,6 +388,7 @@ export const createConfiguredCurlInstance = ({
if (authentication.type === AUTH_NETRC) {
curl.setOpt(Curl.option.NETRC, CurlNetrc.Required);
}
return { curl, debugTimeline };
};