Update response-timeline-viewer to show response body. (#4346)

Co-authored-by: Dimitri Mitropoulos <dimitrimitropoulos@gmail.com>
This commit is contained in:
Paul Johnson 2022-02-24 14:38:38 +00:00 committed by GitHub
parent 8198992b68
commit 41a2fd6379
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 77 additions and 58 deletions

View File

@ -466,3 +466,14 @@ export function isNotNullOrUndefined<ValueType>(
}
export const kebabCase = (value: string) => value.replace(/ /g, '-');
// Because node-libcurl changed some names that we used in the timeline
export const LIBCURL_DEBUG_MIGRATION_MAP = {
HeaderIn: 'HEADER_IN',
DataIn: 'DATA_IN',
SslDataIn: 'SSL_DATA_IN',
HeaderOut: 'HEADER_OUT',
DataOut: 'DATA_OUT',
SslDataOut: 'SSL_DATA_OUT',
Text: 'TEXT',
} as const;

View File

@ -3,10 +3,12 @@ import fs from 'fs';
import mkdirp from 'mkdirp';
import path from 'path';
import { Readable } from 'stream';
import { ValueOf } from 'type-fest';
import zlib from 'zlib';
import { database as db, Query } from '../common/database';
import { getDataDirectory } from '../common/electron-helpers';
import { LIBCURL_DEBUG_MIGRATION_MAP } from '../common/misc';
import type { BaseModel } from './index';
import * as models from './index';
@ -26,7 +28,7 @@ export interface ResponseHeader {
}
export interface ResponseTimelineEntry {
name: string;
name: ValueOf<typeof LIBCURL_DEBUG_MIGRATION_MAP>;
timestamp: number;
value: string;
}
@ -228,8 +230,30 @@ export const getBodyBuffer = <TFail = null>(
readFailureValue,
);
export function getTimeline(response: Response) {
return getTimelineFromPath(response.timelinePath || '');
export function getTimeline(response: Response, showBody?: boolean) {
const { timelinePath, bodyPath } = response;
// No body, so return empty Buffer
if (!timelinePath) {
return [];
}
try {
const rawBuffer = fs.readFileSync(timelinePath);
const timeline = JSON.parse(rawBuffer.toString()) as ResponseTimelineEntry[];
const body: ResponseTimelineEntry[] = showBody ? [
{
name: LIBCURL_DEBUG_MIGRATION_MAP.DataOut,
timestamp: Date.now(),
value: fs.readFileSync(bodyPath).toString(),
},
] : [];
const output = [...timeline, ...body];
return output;
} catch (err) {
console.warn('Failed to read response body', err.message);
return [];
}
}
function getBodyStreamFromPath<TFail extends Readable>(
@ -282,21 +306,6 @@ function getBodyBufferFromPath<T>(
}
}
function getTimelineFromPath(timelinePath: string) {
// No body, so return empty Buffer
if (!timelinePath) {
return [];
}
try {
const rawBuffer = fs.readFileSync(timelinePath);
return JSON.parse(rawBuffer.toString()) as ResponseTimelineEntry[];
} catch (err) {
console.warn('Failed to read response body', err.message);
return [];
}
}
async function migrateBodyToFileSystem(doc: Response) {
if (doc.hasOwnProperty('body') && doc._id && !doc.bodyPath) {
// @ts-expect-error -- TSCONVERSION previously doc.body and doc.encoding did exist but are now removed, and if they exist we want to migrate away from them

View File

@ -48,6 +48,7 @@ import {
hasAuthHeader,
hasContentTypeHeader,
hasUserAgentHeader,
LIBCURL_DEBUG_MIGRATION_MAP,
waitForStreamToFinish,
} from '../common/misc';
import type { ExtraRenderInfo, RenderedRequest } from '../common/render';
@ -96,18 +97,6 @@ const MAX_DELAY_TIME = 1000;
// Special header value that will prevent the header being sent
const DISABLE_HEADER_VALUE = '__Di$aB13d__';
// Because node-libcurl changed some names that we used in the timeline
const LIBCURL_DEBUG_MIGRATION_MAP = {
HeaderIn: 'HEADER_IN',
DataIn: 'DATA_IN',
SslDataIn: 'SSL_DATA_IN',
HeaderOut: 'HEADER_OUT',
DataOut: 'DATA_OUT',
SslDataOut: 'SSL_DATA_OUT',
Text: 'TEXT',
'': '',
};
const cancelRequestFunctionMap = {};
let lastUserInteraction = Date.now();
@ -163,17 +152,15 @@ export async function _actuallySend(
return new Promise<ResponsePatch>(async resolve => {
const timeline: ResponseTimelineEntry[] = [];
function addTimeline(name, value) {
const addTimelineItem = (name: ResponseTimelineEntry['name']) => (value: string) => {
timeline.push({
name,
value,
timestamp: Date.now(),
});
}
};
function addTimelineText(value) {
addTimeline('TEXT', value);
}
const addTimelineText = addTimelineItem(LIBCURL_DEBUG_MIGRATION_MAP.Text);
// Initialize the curl handle
const curl = new Curl();
@ -310,6 +297,7 @@ export async function _actuallySend(
const content = contentBuffer.toString('utf8');
const rawName = Object.keys(CurlInfoDebug).find(k => CurlInfoDebug[k] === infoType) || '';
const name = LIBCURL_DEBUG_MIGRATION_MAP[rawName] || rawName;
const addToTimeline = addTimelineItem(name);
if (infoType === CurlInfoDebug.SslDataIn || infoType === CurlInfoDebug.SslDataOut) {
return 0;
@ -320,9 +308,9 @@ export async function _actuallySend(
if (contentBuffer.length === 0) {
// Sometimes this happens, but I'm not sure why. Just ignore it.
} else if (contentBuffer.length / 1024 < settings.maxTimelineDataSizeKB) {
addTimeline(name, content);
addToTimeline(content);
} else {
addTimeline(name, `(${describeByteSize(contentBuffer.length)} hidden)`);
addToTimeline(`(${describeByteSize(contentBuffer.length)} hidden)`);
}
return 0;
@ -338,9 +326,10 @@ export async function _actuallySend(
return 0;
}
addTimeline(name, content);
addToTimeline(content);
return 0; // Must be here
});
// Set the headers (to be modified as we go)
const headers = clone(renderedRequest.headers);
// Set the URL, including the query parameters

View File

@ -1,3 +1,4 @@
import { Button } from 'insomnia-components';
import React, { ChangeEvent, FC, ReactNode, useCallback, useMemo, useState } from 'react';
import { useSelector } from 'react-redux';
@ -378,17 +379,18 @@ const OAuth2Error: FC = () => {
showModal(ResponseDebugModal, {
responseId: token.xResponseId,
showBody: true,
});
};
const debugButton = token?.xResponseId ? (
<button
<Button
onClick={debug}
className="icon icon--success space-left"
className="margin-top-sm"
title="View response timeline"
>
<i className="fa fa-bug" />
</button>
<i className="fa fa-bug space-right" /> Response Timeline
</Button>
) : null;
const errorUriButton = token?.errorUri ? (
@ -407,8 +409,8 @@ const OAuth2Error: FC = () => {
<p>
{errorDescription || 'no description provided'}
{errorUriButton}
{debugButton}
</p>
{debugButton}
</div>
);
}

View File

@ -11,6 +11,7 @@ import { ModalHeader } from '../base/modal-header';
interface State {
response: Response | null;
showBody?: boolean;
title: string | null;
}
@ -20,6 +21,7 @@ export class ResponseDebugModal extends PureComponent<{}, State> {
state: State = {
response: null,
showBody: false,
title: '',
};
@ -31,19 +33,20 @@ export class ResponseDebugModal extends PureComponent<{}, State> {
this.modal?.hide();
}
async show(options: { responseId?: string; response?: Response; title?: string }) {
async show(options: { responseId?: string; response?: Response; title?: string; showBody?: boolean }) {
const response = options.response
? options.response
: await models.response.getById(options.responseId || 'n/a');
this.setState({
response,
title: options.title || null,
showBody: options.showBody,
});
this.modal?.show();
}
render() {
const { response, title } = this.state;
const { response, title, showBody } = this.state;
return (
<Modal ref={this._setModalRef} tall>
<ModalHeader>{title || 'Response Timeline'}</ModalHeader>
@ -57,6 +60,7 @@ export class ResponseDebugModal extends PureComponent<{}, State> {
{response ? (
<ResponseTimelineViewer
response={response}
showBody={showBody}
/>
) : (
<div>No response found</div>

View File

@ -134,7 +134,7 @@ export class ResponsePane extends PureComponent<Props> {
return;
}
const timeline = await models.response.getTimeline(response);
const timeline = models.response.getTimeline(response);
const headers = timeline
.filter(v => v.name === 'HEADER_IN')
.map(v => v.value)

View File

@ -1,16 +1,18 @@
import React, { PureComponent } from 'react';
import { clickLink } from '../../../common/electron-helpers';
import { LIBCURL_DEBUG_MIGRATION_MAP } from '../../../common/misc';
import * as models from '../../../models';
import { Response } from '../../../models/response';
import { Response, ResponseTimelineEntry } from '../../../models/response';
import { CodeEditor } from '../codemirror/code-editor';
interface Props {
showBody?: boolean;
response: Response;
}
interface State {
timeline: any[];
timeline: ResponseTimelineEntry[];
timelineKey: string;
}
@ -33,45 +35,46 @@ export class ResponseTimelineViewer extends PureComponent<Props, State> {
}
async refreshTimeline() {
const { response } = this.props;
const timeline = await models.response.getTimeline(response);
const { response, showBody } = this.props;
const timeline = models.response.getTimeline(response, showBody);
this.setState({
timeline,
timelineKey: response._id,
});
}
renderRow(row, i, all) {
renderRow(row: ResponseTimelineEntry, i: number, all: ResponseTimelineEntry[]) {
const { name, value } = row;
const previousName = i > 0 ? all[i - 1].name : '';
let prefix: string | null = null;
switch (name) {
case 'HEADER_IN':
case LIBCURL_DEBUG_MIGRATION_MAP.HeaderIn:
prefix = '< ';
break;
case 'DATA_IN':
case LIBCURL_DEBUG_MIGRATION_MAP.DataIn:
prefix = '| ';
break;
case 'SSL_DATA_IN':
case LIBCURL_DEBUG_MIGRATION_MAP.SslDataIn:
prefix = '<< ';
break;
case 'HEADER_OUT':
case LIBCURL_DEBUG_MIGRATION_MAP.HeaderOut:
prefix = '> ';
break;
case 'DATA_OUT':
case LIBCURL_DEBUG_MIGRATION_MAP.DataOut:
prefix = '| ';
break;
case 'SSL_DATA_OUT':
case LIBCURL_DEBUG_MIGRATION_MAP.SslDataOut:
prefix = '>> ';
break;
case 'TEXT':
case LIBCURL_DEBUG_MIGRATION_MAP.Text:
prefix = '* ';
break;
@ -99,6 +102,7 @@ export class ResponseTimelineViewer extends PureComponent<Props, State> {
.filter(r => r !== null)
.join('\n')
.trim();
return (
<CodeEditor
key={timelineKey}