Skip to content

Commit 9f9dd69

Browse files
committed
Add support for the core non-interactive WebSocket rules
1 parent ca23c7b commit 9f9dd69

13 files changed

+398
-129
lines changed

src/components/mock/handler-config.tsx

Lines changed: 98 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,14 @@ import { Headers } from '../../types';
88
import { css, styled } from '../../styles';
99
import { WarningIcon } from '../../icons';
1010
import { uploadFile } from '../../util/ui';
11-
import { asError } from '../../util/error';
11+
import { asError, UnreachableCheck } from '../../util/error';
12+
import { byteLength, asBuffer, isProbablyUtf8 } from '../../util';
1213

1314
import {
1415
Handler,
15-
RuleType
16+
RuleType,
17+
getRulePartKey,
18+
AvailableHandlerKey
1619
} from '../../model/rules/rules';
1720
import {
1821
StaticResponseHandler,
@@ -29,9 +32,12 @@ import {
2932
FromFileResponseHandler
3033
} from '../../model/rules/definitions/http-rule-definitions';
3134
import {
32-
WebSocketPassThroughHandler
35+
WebSocketPassThroughHandler,
36+
EchoWebSocketHandlerDefinition,
37+
RejectWebSocketHandlerDefinition,
38+
ListenWebSocketHandlerDefinition
3339
} from '../../model/rules/definitions/websocket-rule-definitions';
34-
import { HEADER_NAME_REGEX } from '../../model/http/http-docs';
40+
import { getStatusMessage, HEADER_NAME_REGEX } from '../../model/http/http-docs';
3541
import { MethodName, MethodNames } from '../../model/http/methods';
3642
import {
3743
getDefaultMimeType,
@@ -45,7 +51,6 @@ import { TextInput, Select, Button } from '../common/inputs';
4551
import { EditableHeaders } from '../common/editable-headers';
4652
import { EditableStatus } from '../common/editable-status';
4753
import { FormatButton } from '../common/format-button';
48-
import { byteLength, asBuffer, isProbablyUtf8 } from '../../util';
4954

5055
type HandlerConfigProps<H extends Handler> = {
5156
ruleType: RuleType;
@@ -86,32 +91,39 @@ export function HandlerConfiguration(props: {
8691
onInvalidState: onInvalidState || _.noop
8792
};
8893

89-
if (handler instanceof StaticResponseHandler) {
90-
return <StaticResponseHandlerConfig {...configProps} />;
91-
} else if (handler instanceof FromFileResponseHandler) {
92-
return <FromFileResponseHandlerConfig {...configProps} />;
93-
} else if (handler instanceof ForwardToHostHandler) {
94-
return <ForwardToHostHandlerConfig {...configProps} />;
95-
} else if (
96-
handler instanceof PassThroughHandler ||
97-
handler instanceof WebSocketPassThroughHandler
98-
) {
99-
return <PassThroughHandlerConfig {...configProps} />;
100-
} else if (handler instanceof TransformingHandler) {
101-
return <TransformingHandlerConfig {...configProps} />;
102-
} else if (handler instanceof RequestBreakpointHandler) {
103-
return <RequestBreakpointHandlerConfig {...configProps} />;
104-
} else if (handler instanceof ResponseBreakpointHandler) {
105-
return <ResponseBreakpointHandlerConfig {...configProps} />;
106-
} else if (handler instanceof RequestAndResponseBreakpointHandler) {
107-
return <RequestAndResponseBreakpointHandlerConfig {...configProps} />;
108-
} else if (handler instanceof TimeoutHandler) {
109-
return <TimeoutHandlerConfig {...configProps} />;
110-
} else if (handler instanceof CloseConnectionHandler) {
111-
return <CloseConnectionHandlerConfig {...configProps} />;
94+
const handlerKey = getRulePartKey(handler) as AvailableHandlerKey;
95+
96+
switch (handlerKey) {
97+
case 'simple':
98+
return <StaticResponseHandlerConfig {...configProps} />;
99+
case 'file':
100+
return <FromFileResponseHandlerConfig {...configProps} />;
101+
case 'forward-to-host':
102+
return <ForwardToHostHandlerConfig {...configProps} />;
103+
case 'passthrough':
104+
case 'ws-passthrough':
105+
return <PassThroughHandlerConfig {...configProps} />;
106+
case 'req-res-transformer':
107+
return <TransformingHandlerConfig {...configProps} />;
108+
case 'request-breakpoint':
109+
return <RequestBreakpointHandlerConfig {...configProps} />;
110+
case 'response-breakpoint':
111+
return <ResponseBreakpointHandlerConfig {...configProps} />;
112+
case 'request-and-response-breakpoint':
113+
return <RequestAndResponseBreakpointHandlerConfig {...configProps} />;
114+
case 'timeout':
115+
return <TimeoutHandlerConfig {...configProps} />;
116+
case 'close-connection':
117+
return <CloseConnectionHandlerConfig {...configProps} />;
118+
case 'ws-echo':
119+
return <WebSocketEchoHandlerConfig {...configProps} />;
120+
case 'ws-reject':
121+
return <StaticResponseHandlerConfig {...configProps} />;
122+
case 'ws-listen':
123+
return <WebSocketListenHandlerConfig {...configProps} />;
124+
default:
125+
throw new UnreachableCheck(handlerKey);
112126
}
113-
114-
throw new Error('Unknown handler: ' + handler.type);
115127
}
116128

117129
const SectionLabel = styled.h2`
@@ -169,10 +181,12 @@ function getHeaderValue(headers: Headers, headerName: string): string | undefine
169181
}
170182

171183
@observer
172-
class StaticResponseHandlerConfig extends React.Component<HandlerConfigProps<StaticResponseHandler>> {
184+
class StaticResponseHandlerConfig extends HandlerConfig<StaticResponseHandler | RejectWebSocketHandlerDefinition> {
173185

174186
@observable
175-
statusCode: number | undefined = this.props.handler.status;
187+
statusCode: number | undefined = (this.props.handler instanceof StaticResponseHandler)
188+
? this.props.handler.status
189+
: this.props.handler.statusCode;
176190

177191
@observable
178192
statusMessage = this.props.handler.statusMessage;
@@ -184,7 +198,10 @@ class StaticResponseHandlerConfig extends React.Component<HandlerConfigProps<Sta
184198
contentType: EditableContentType = 'text';
185199

186200
@observable
187-
body = asBuffer(this.props.handler.data);
201+
body = asBuffer(this.props.handler instanceof StaticResponseHandler
202+
? this.props.handler.data
203+
: this.props.handler.body
204+
);
188205

189206
componentDidMount() {
190207
// If any of our data fields change, rebuild & update the handler
@@ -194,7 +211,10 @@ class StaticResponseHandlerConfig extends React.Component<HandlerConfigProps<Sta
194211

195212
// If the handler changes (or when its set initially), update our data fields
196213
disposeOnUnmount(this, autorun(() => {
197-
const { status, statusMessage, headers, data } = this.props.handler;
214+
const { status, statusMessage, headers, data } = this.props.handler instanceof StaticResponseHandler
215+
? this.props.handler
216+
: { ...this.props.handler, status: this.props.handler.statusCode, data: this.props.handler.body };
217+
198218
runInAction(() => {
199219
this.statusCode = status;
200220
this.statusMessage = statusMessage;
@@ -342,12 +362,19 @@ class StaticResponseHandlerConfig extends React.Component<HandlerConfigProps<Sta
342362
) return this.props.onInvalidState();
343363

344364
this.props.onChange(
345-
new StaticResponseHandler(
365+
this.props.ruleType === 'http'
366+
? new StaticResponseHandler(
346367
this.statusCode,
347368
this.statusMessage,
348369
this.body,
349370
this.headers
350371
)
372+
: new RejectWebSocketHandlerDefinition(
373+
this.statusCode,
374+
this.statusMessage ?? getStatusMessage(this.statusCode),
375+
this.headers,
376+
this.body
377+
)
351378
);
352379
}
353380
}
@@ -376,7 +403,7 @@ const BodyFilePath = styled.div`
376403
`;
377404

378405
@observer
379-
class FromFileResponseHandlerConfig extends React.Component<HandlerConfigProps<FromFileResponseHandler>> {
406+
class FromFileResponseHandlerConfig extends HandlerConfig<FromFileResponseHandler> {
380407

381408
@observable
382409
statusCode: number | undefined = this.props.handler.status;
@@ -668,9 +695,7 @@ const SelectTransform = styled(Select)`
668695

669696
@inject('rulesStore')
670697
@observer
671-
class TransformingHandlerConfig extends React.Component<HandlerConfigProps<TransformingHandler> & {
672-
rulesStore?: RulesStore
673-
}> {
698+
class TransformingHandlerConfig extends HandlerConfig<TransformingHandler, { rulesStore?: RulesStore }> {
674699

675700
@observable
676701
transformRequest = this.props.handler.transformRequest || {};
@@ -1091,11 +1116,16 @@ const JsonUpdateTransformConfig = (props: {
10911116
};
10921117

10931118
@observer
1094-
class PassThroughHandlerConfig extends HandlerConfig<PassThroughHandler> {
1119+
class PassThroughHandlerConfig extends HandlerConfig<PassThroughHandler | WebSocketPassThroughHandler> {
10951120
render() {
10961121
return <ConfigContainer>
10971122
<ConfigExplanation>
1098-
All matching traffic will be transparently passed through to the upstream target host.
1123+
All matching {
1124+
this.props.ruleType === 'http'
1125+
? 'requests'
1126+
// ruleType === 'websocket'
1127+
: 'WebSockets'
1128+
} will be transparently passed through to the upstream target host.
10991129
</ConfigExplanation>
11001130
</ConfigContainer>;
11011131
}
@@ -1160,7 +1190,7 @@ class TimeoutHandlerConfig extends HandlerConfig<TimeoutHandler> {
11601190
<ConfigExplanation>
11611191
When a matching {
11621192
this.props.ruleType === 'http'
1163-
? 'HTTP'
1193+
? 'request'
11641194
// ruleType === 'websocket'
11651195
: 'WebSocket'
11661196
} is received, the server will keep the connection open but do nothing.
@@ -1178,11 +1208,36 @@ class CloseConnectionHandlerConfig extends HandlerConfig<CloseConnectionHandler>
11781208
<ConfigExplanation>
11791209
As soon as a matching {
11801210
this.props.ruleType === 'http'
1181-
? 'HTTP'
1211+
? 'request'
11821212
// ruleType === 'websocket'
11831213
: 'WebSocket'
11841214
} is received, the connection will be closed, with no response.
11851215
</ConfigExplanation>
11861216
</ConfigContainer>;
11871217
}
1218+
}
1219+
1220+
@observer
1221+
class WebSocketEchoHandlerConfig extends HandlerConfig<EchoWebSocketHandlerDefinition> {
1222+
render() {
1223+
return <ConfigContainer>
1224+
<ConfigExplanation>
1225+
The WebSocket will be opened successfully, but not forwarded upstream, and every
1226+
message that's sent will be echoed back to the client until the client closes
1227+
the connection.
1228+
</ConfigExplanation>
1229+
</ConfigContainer>;
1230+
}
1231+
}
1232+
1233+
@observer
1234+
class WebSocketListenHandlerConfig extends HandlerConfig<ListenWebSocketHandlerDefinition> {
1235+
render() {
1236+
return <ConfigContainer>
1237+
<ConfigExplanation>
1238+
The WebSocket will be opened successfully, but not forwarded upstream. All
1239+
messages from the client will be accepted, but no responses will be sent.
1240+
</ConfigExplanation>
1241+
</ConfigContainer>;
1242+
}
11881243
}

src/components/mock/handler-selection.tsx

Lines changed: 34 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,16 @@ import * as React from 'react';
33
import { inject, observer } from 'mobx-react';
44

55
import { styled } from '../../styles';
6+
import { UnreachableCheck } from '../../util/error';
67

78
import { RulesStore } from '../../model/rules/rules-store';
89
import { AccountStore } from '../../model/account/account-store';
910
import {
1011
HandlerClass,
1112
Handler,
12-
HandlerClassKey,
13+
AvailableHandlerKey,
1314
HandlerClassKeyLookup,
14-
HandlerLookup,
15-
isPaidHandlerClass
15+
isPaidHandlerClass,
1616
} from '../../model/rules/rules';
1717
import { summarizeHandlerClass } from '../../model/rules/rule-descriptions';
1818
import {
@@ -27,12 +27,17 @@ import {
2727
CloseConnectionHandler,
2828
FromFileResponseHandler
2929
} from '../../model/rules/definitions/http-rule-definitions';
30+
import {
31+
WebSocketPassThroughHandler,
32+
EchoWebSocketHandlerDefinition,
33+
RejectWebSocketHandlerDefinition,
34+
ListenWebSocketHandlerDefinition
35+
} from '../../model/rules/definitions/websocket-rule-definitions';
3036

3137
import { Select } from '../common/inputs';
3238

3339
const getHandlerKey = (h: HandlerClass | Handler) =>
3440
HandlerClassKeyLookup.get(h as any) || HandlerClassKeyLookup.get(h.constructor as any);
35-
const getHandlerClassByKey = (k: HandlerClassKey) => HandlerLookup[k];
3641

3742
const HandlerOptions = (p: { handlers: Array<HandlerClass> }) => <>{
3843
p.handlers.map((handler): JSX.Element | null => {
@@ -50,30 +55,40 @@ const HandlerSelect = styled(Select)`
5055
`;
5156

5257
const instantiateHandler = (
53-
handlerClass: HandlerClass,
58+
handlerKey: AvailableHandlerKey,
5459
rulesStore: RulesStore
55-
): Handler | undefined => {
56-
switch (handlerClass) {
57-
case StaticResponseHandler:
60+
): Handler => {
61+
switch (handlerKey) {
62+
case 'simple':
5863
return new StaticResponseHandler(200);
59-
case FromFileResponseHandler:
64+
case 'file':
6065
return new FromFileResponseHandler(200, undefined, '');
61-
case PassThroughHandler:
66+
case 'passthrough':
6267
return new PassThroughHandler(rulesStore);
63-
case ForwardToHostHandler:
68+
case 'ws-passthrough':
69+
return new WebSocketPassThroughHandler(rulesStore);
70+
case 'forward-to-host':
6471
return new ForwardToHostHandler('', true, rulesStore);
65-
case TransformingHandler:
72+
case 'req-res-transformer':
6673
return new TransformingHandler(rulesStore, {}, {});
67-
case RequestBreakpointHandler:
74+
case 'request-breakpoint':
6875
return new RequestBreakpointHandler(rulesStore);
69-
case ResponseBreakpointHandler:
76+
case 'response-breakpoint':
7077
return new ResponseBreakpointHandler(rulesStore);
71-
case RequestAndResponseBreakpointHandler:
78+
case 'request-and-response-breakpoint':
7279
return new RequestAndResponseBreakpointHandler(rulesStore);
73-
case TimeoutHandler:
80+
case 'timeout':
7481
return new TimeoutHandler();
75-
case CloseConnectionHandler:
82+
case 'close-connection':
7683
return new CloseConnectionHandler();
84+
case 'ws-echo':
85+
return new EchoWebSocketHandlerDefinition();
86+
case 'ws-reject':
87+
return new RejectWebSocketHandlerDefinition(400);
88+
case 'ws-listen':
89+
return new ListenWebSocketHandlerDefinition();
90+
default:
91+
throw new UnreachableCheck(handlerKey);
7792
}
7893
}
7994

@@ -97,9 +112,8 @@ export const HandlerSelector = inject('rulesStore', 'accountStore')(observer((p:
97112
return <HandlerSelect
98113
value={getHandlerKey(p.value)}
99114
onChange={(event) => {
100-
const handlerClass = getHandlerClassByKey(event.target.value as HandlerClassKey);
101-
const handler = instantiateHandler(handlerClass, p.rulesStore!);
102-
if (!handler) return;
115+
const handlerKey = event.target.value as AvailableHandlerKey;
116+
const handler = instantiateHandler(handlerKey, p.rulesStore!);
103117
p.onChange(handler);
104118
}}
105119
>

0 commit comments

Comments
 (0)