Skip to content

Commit 35b4bc6

Browse files
committed
Added gRPC parsing support for display in the UI
1 parent c8b0741 commit 35b4bc6

File tree

4 files changed

+53
-3
lines changed

4 files changed

+53
-3
lines changed

src/model/events/body-formatting.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,12 @@ export const Formatters: { [key in ViewableContentType]: Formatter } = {
142142
isEditApplicable: false,
143143
render: buildAsyncRenderer('protobuf')
144144
},
145+
grpc: {
146+
language: 'grpc',
147+
cacheKey: Symbol('grpc'),
148+
isEditApplicable: false,
149+
render: buildAsyncRenderer('grpc')
150+
},
145151
'url-encoded': {
146152
layout: 'scrollable',
147153
Component: styled(ReadOnlyParams).attrs((p: FormatComponentProps) => ({

src/model/events/content-types.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@ export type ViewableContentType =
3333
| 'markdown'
3434
| 'yaml'
3535
| 'image'
36-
| 'protobuf';
36+
| 'protobuf'
37+
| 'grpc';
3738

3839
export const EditableContentTypes = [
3940
'text',
@@ -94,9 +95,10 @@ const mimeTypeToContentTypeMap: { [mimeType: string]: ViewableContentType } = {
9495
'application/vnd.google.protobuf': 'protobuf',
9596
'application/x-google-protobuf': 'protobuf',
9697
'application/proto': 'protobuf', // N.b. this covers all application/XXX+proto values
97-
'application/grpc': 'protobuf', // Used in GRPC requests
9898
'application/x-protobuffer': 'protobuf', // Commonly seen in Google apps
9999

100+
'application/grpc': 'grpc', // Used in GRPC requests (protobuf with special headers)
101+
100102
'application/octet-stream': 'raw'
101103
} as const;
102104

@@ -122,6 +124,7 @@ export function getContentEditorName(contentType: ViewableContentType): string {
122124
: contentType === 'json' ? 'JSON'
123125
: contentType === 'css' ? 'CSS'
124126
: contentType === 'url-encoded' ? 'URL-Encoded'
127+
: contentType === 'grpc' ? 'gRPC'
125128
: _.capitalize(contentType);
126129
}
127130

@@ -163,10 +166,14 @@ export function getCompatibleTypes(
163166
types.add('xml');
164167
}
165168

169+
if (!types.has('grpc') && rawContentType && rawContentType.startsWith('application/grpc')) {
170+
types.add('grpc')
171+
}
166172
if (
167173
body &&
168174
isProbablyProtobuf(body) &&
169175
!types.has('protobuf') &&
176+
!types.has('grpc') &&
170177
// If it's probably unmarked protobuf, and it's a manageable size, try
171178
// parsing it just to check:
172179
(body.length < 100_000 && isValidProtobuf(body))

src/services/ui-worker-formatters.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import {
66
import * as beautifyXml from 'xml-beautifier';
77

88
import { bufferToHex, bufferToString, getReadableSize } from '../util/buffer';
9-
import { parseRawProtobuf } from '../util/protobuf';
9+
import { parseRawProtobuf, parseGrpcProtobuf } from '../util/protobuf';
1010

1111
const truncationMarker = (size: string) => `\n[-- Truncated to ${size} --]`;
1212
const FIVE_MB = 1024 * 1024 * 5;
@@ -78,6 +78,24 @@ const WorkerFormatters = {
7878
prefix: ''
7979
});
8080

81+
return JSON.stringify(data, (_key, value) => {
82+
// Buffers have toJSON defined, so arrive here in JSONified form:
83+
if (value.type === 'Buffer' && Array.isArray(value.data)) {
84+
const buffer = Buffer.from(value.data);
85+
86+
return {
87+
"Type": `Buffer (${getReadableSize(buffer)})`,
88+
"As string": bufferToString(buffer, 'detect-encoding'),
89+
"As hex": bufferToHex(buffer)
90+
}
91+
} else {
92+
return value;
93+
}
94+
}, 2);
95+
},
96+
grpc: (content: Buffer) => {
97+
const data = parseGrpcProtobuf(content);
98+
8199
return JSON.stringify(data, (_key, value) => {
82100
// Buffers have toJSON defined, so arrive here in JSONified form:
83101
if (value.type === 'Buffer' && Array.isArray(value.data)) {

src/util/protobuf.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,25 @@ export function isProbablyProtobuf(input: Uint8Array) {
2828

2929
export const parseRawProtobuf = parseRawProto;
3030

31+
// The repeated sequence of Length-Prefixed-Message items is delivered in DATA frames
32+
33+
// Length-Prefixed-Message → Compressed-Flag Message-Length Message
34+
// Compressed-Flag → 0 / 1 ; encoded as 1 byte unsigned integer
35+
// Message-Length → {length of Message} ; encoded as 4 byte unsigned integer (big endian)
36+
// Message → *{binary octet}
37+
export const parseGrpcProtobuf = (input: Buffer) => {
38+
if (input.readInt8() != 0) {
39+
// TODO support compressed gRPC messages?
40+
throw new Error("compressed gRPC messages not yet supported")
41+
}
42+
const length = input.readInt32BE(1);
43+
input = input.slice(5, 5+length);
44+
45+
return parseRawProtobuf(input, {
46+
prefix: ''
47+
});
48+
}
49+
3150
export const isValidProtobuf = (input: Uint8Array) => {
3251
try {
3352
parseRawProtobuf(input);

0 commit comments

Comments
 (0)