Skip to content

Commit bd9d252

Browse files
committed
Add a 'Forward to host' websocket rule
1 parent 702c95b commit bd9d252

File tree

4 files changed

+107
-29
lines changed

4 files changed

+107
-29
lines changed

src/components/mock/handler-config.tsx

Lines changed: 64 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,8 @@ import {
5050
WebSocketPassThroughHandler,
5151
EchoWebSocketHandlerDefinition,
5252
RejectWebSocketHandlerDefinition,
53-
ListenWebSocketHandlerDefinition
53+
ListenWebSocketHandlerDefinition,
54+
WebSocketForwardToHostHandler
5455
} from '../../model/rules/definitions/websocket-rule-definitions';
5556
import {
5657
EthereumCallResultHandler,
@@ -151,7 +152,11 @@ export function HandlerConfiguration(props: {
151152
case 'file':
152153
return <FromFileResponseHandlerConfig {...configProps} />;
153154
case 'forward-to-host':
154-
return <ForwardToHostHandlerConfig {...configProps} />;
155+
case 'ws-forward-to-host':
156+
return <ForwardToHostHandlerConfig
157+
{...configProps}
158+
handlerKey={handlerKey}
159+
/>;
155160
case 'passthrough':
156161
case 'ws-passthrough':
157162
return <PassThroughHandlerConfig {...configProps} />;
@@ -657,9 +662,14 @@ const UrlInput = styled(TextInput)`
657662

658663
@inject('rulesStore')
659664
@observer
660-
class ForwardToHostHandlerConfig extends HandlerConfig<ForwardToHostHandler, {
661-
rulesStore?: RulesStore
662-
}> {
665+
class ForwardToHostHandlerConfig extends HandlerConfig<
666+
| ForwardToHostHandler
667+
| WebSocketForwardToHostHandler,
668+
{
669+
rulesStore?: RulesStore,
670+
handlerKey: 'forward-to-host' | 'ws-forward-to-host'
671+
}
672+
> {
663673

664674
@observable
665675
private error: Error | undefined;
@@ -682,9 +692,19 @@ class ForwardToHostHandlerConfig extends HandlerConfig<ForwardToHostHandler, {
682692
}
683693

684694
render() {
685-
const { targetHost, updateHostHeader, error, onTargetChange, onUpdateHeaderChange } = this;
695+
const {
696+
targetHost,
697+
updateHostHeader,
698+
error,
699+
onTargetChange,
700+
onUpdateHeaderChange
701+
} = this;
686702
const { targetHost: savedTargetHost } = this.props.handler.forwarding!;
687703

704+
const messageType = this.props.handlerKey === 'ws-forward-to-host'
705+
? 'WebSocket'
706+
: 'request';
707+
688708
return <ConfigContainer>
689709
<SectionLabel>Replacement host</SectionLabel>
690710
<UrlInput
@@ -699,7 +719,7 @@ class ForwardToHostHandlerConfig extends HandlerConfig<ForwardToHostHandler, {
699719
value={updateHostHeader.toString()}
700720
onChange={onUpdateHeaderChange}
701721
title={dedent`
702-
Most servers will not accept requests that arrive
722+
Most servers will not accept ${messageType}s that arrive
703723
with the wrong host header, so it's typically useful
704724
to automatically change it to match the new host
705725
`}
@@ -709,7 +729,7 @@ class ForwardToHostHandlerConfig extends HandlerConfig<ForwardToHostHandler, {
709729
</ConfigSelect>
710730
{ savedTargetHost &&
711731
<ConfigExplanation>
712-
All matching requests will be forwarded to {savedTargetHost},
732+
All matching {messageType}s will be forwarded to {savedTargetHost},
713733
keeping their existing path{
714734
!savedTargetHost.includes('://') ? ', protocol,' : ''
715735
} and query string.{
@@ -726,26 +746,47 @@ class ForwardToHostHandlerConfig extends HandlerConfig<ForwardToHostHandler, {
726746
try {
727747
if (!this.targetHost) throw new Error('A target host is required');
728748

729-
const protocolMatch = this.targetHost.match(/^\w+:\/\//);
730-
if (protocolMatch) {
731-
const pathWithoutProtocol = this.targetHost.slice(protocolMatch[0].length);
749+
let urlWithoutProtocol: string;
732750

733-
if (pathWithoutProtocol.includes('/')) {
734-
throw new Error('The replacement host shouldn\'t include a path, since it won\'t be used');
735-
}
736-
if (pathWithoutProtocol.includes('?')) {
737-
throw new Error('The replacement host shouldn\'t include a query string, since it won\'t be used');
751+
const protocolMatch = this.targetHost.match(/^(\w+):\/\//);
752+
if (protocolMatch) {
753+
const validProtocols = this.props.handlerKey === 'ws-forward-to-host'
754+
? ['ws', 'wss']
755+
: ['http', 'https'];
756+
757+
if (!validProtocols.includes(protocolMatch[1].toLowerCase())) {
758+
throw new Error(
759+
`The protocol must be either ${validProtocols[0]} or ${validProtocols[1]}`
760+
);
738761
}
762+
763+
urlWithoutProtocol = this.targetHost.slice(protocolMatch[0].length);
739764
} else {
740-
if (this.targetHost.includes('/')) {
741-
throw new Error('The replacement host shouldn\'t include a path, since it won\'t be used');
742-
}
743-
if (this.targetHost.includes('?')) {
744-
throw new Error('The replacement host shouldn\'t include a query string, since it won\'t be used');
745-
}
765+
urlWithoutProtocol = this.targetHost;
766+
}
767+
768+
if (urlWithoutProtocol.includes('/')) {
769+
throw new Error(
770+
'The replacement host shouldn\'t include a path, since it won\'t be used'
771+
);
772+
}
773+
if (urlWithoutProtocol.includes('?')) {
774+
throw new Error(
775+
'The replacement host shouldn\'t include a query string, since it won\'t be used'
776+
);
746777
}
747778

748-
this.props.onChange(new ForwardToHostHandler(this.targetHost, this.updateHostHeader, this.props.rulesStore!));
779+
const HandlerClass = this.props.handlerKey === 'ws-forward-to-host'
780+
? WebSocketForwardToHostHandler
781+
: ForwardToHostHandler;
782+
783+
this.props.onChange(
784+
new HandlerClass(
785+
this.targetHost,
786+
this.updateHostHeader,
787+
this.props.rulesStore!
788+
)
789+
);
749790
this.error = undefined;
750791
} catch (e) {
751792
console.log(e);

src/components/mock/handler-selection.tsx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@ import {
3333
WebSocketPassThroughHandler,
3434
EchoWebSocketHandlerDefinition,
3535
RejectWebSocketHandlerDefinition,
36-
ListenWebSocketHandlerDefinition
36+
ListenWebSocketHandlerDefinition,
37+
WebSocketForwardToHostHandler
3738
} from '../../model/rules/definitions/websocket-rule-definitions';
3839
import {
3940
EthereumCallResultHandler,
@@ -97,8 +98,6 @@ const instantiateHandler = (
9798
return new FromFileResponseHandler(200, undefined, '');
9899
case 'passthrough':
99100
return new PassThroughHandler(rulesStore);
100-
case 'ws-passthrough':
101-
return new WebSocketPassThroughHandler(rulesStore);
102101
case 'forward-to-host':
103102
return new ForwardToHostHandler('', true, rulesStore);
104103
case 'req-res-transformer':
@@ -116,6 +115,10 @@ const instantiateHandler = (
116115
case 'reset-connection':
117116
return new ResetConnectionHandler();
118117

118+
case 'ws-passthrough':
119+
return new WebSocketPassThroughHandler(rulesStore);
120+
case 'ws-forward-to-host':
121+
return new WebSocketForwardToHostHandler('', true, rulesStore);
119122
case 'ws-echo':
120123
return new EchoWebSocketHandlerDefinition();
121124
case 'ws-reject':

src/model/rules/definitions/websocket-rule-definitions.ts

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import * as serializr from 'serializr';
1111
import { RulesStore } from '../rules-store';
1212

1313
import { MethodNames } from '../../http/methods';
14+
import { serializeAsTag } from '../../serialization';
1415
import {
1516
HttpMatcherLookup,
1617
HttpMatcher,
@@ -66,6 +67,35 @@ serializr.createModelSchema(WebSocketPassThroughHandler, {
6667
type: serializr.primitive()
6768
}, (context) => new WebSocketPassThroughHandler(context.args.rulesStore));
6869

70+
export class WebSocketForwardToHostHandler extends wsHandlers.PassThroughWebSocketHandlerDefinition {
71+
72+
readonly uiType = 'ws-forward-to-host';
73+
74+
constructor(forwardToLocation: string, updateHostHeader: boolean, rulesStore: RulesStore) {
75+
super({
76+
...rulesStore.activePassthroughOptions,
77+
forwarding: {
78+
targetHost: forwardToLocation,
79+
updateHostHeader: updateHostHeader
80+
}
81+
});
82+
}
83+
84+
}
85+
86+
serializr.createModelSchema(WebSocketForwardToHostHandler, {
87+
uiType: serializeAsTag(() => 'ws-forward-to-host'),
88+
type: serializr.primitive(),
89+
forwarding: serializr.map(serializr.primitive())
90+
}, (context) => {
91+
const data = context.json;
92+
return new WebSocketForwardToHostHandler(
93+
data.forwarding.targetHost,
94+
data.forwarding.updateHostHeader,
95+
context.args.rulesStore
96+
);
97+
});
98+
6999
export const WebSocketMatcherLookup = {
70100
..._.omit(HttpMatcherLookup, MethodNames),
71101
'method': WebSocketMethodMatcher, // Unlike HTTP rules, WS uses a single method matcher
@@ -81,7 +111,8 @@ export const WebSocketInitialMatcherClasses = [
81111

82112
export const WebSocketHandlerLookup = {
83113
...wsHandlers.WsHandlerDefinitionLookup,
84-
'ws-passthrough': WebSocketPassThroughHandler
114+
'ws-passthrough': WebSocketPassThroughHandler,
115+
'ws-forward-to-host': WebSocketForwardToHostHandler
85116
};
86117

87118
type WebSocketHandlerClass = typeof WebSocketHandlerLookup[keyof typeof WebSocketHandlerLookup];

src/model/rules/rule-descriptions.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ export function nameHandlerClass(key: HandlerClassKey): string {
109109
case 'file':
110110
return "file response";
111111
case 'forward-to-host':
112+
case 'ws-forward-to-host':
112113
return "forwarding";
113114
case 'passthrough':
114115
case 'ws-passthrough':
@@ -184,8 +185,6 @@ export function summarizeHandlerClass(key: HandlerClassKey): string {
184185
return "Forward the request to a different host";
185186
case 'passthrough':
186187
return "Pass the request on to its destination";
187-
case 'ws-passthrough':
188-
return "Pass the WebSocket through to its destination";
189188
case 'req-res-transformer':
190189
return "Transform the real request or response automatically";
191190
case 'request-breakpoint':
@@ -201,6 +200,10 @@ export function summarizeHandlerClass(key: HandlerClassKey): string {
201200
case 'reset-connection':
202201
return "Forcibly reset the connection";
203202

203+
case 'ws-passthrough':
204+
return "Pass the WebSocket through to its destination";
205+
case 'ws-forward-to-host':
206+
return "Forward the WebSocket to a different host";
204207
case 'ws-reject':
205208
return "Reject the WebSocket setup request";
206209
case 'ws-listen':

0 commit comments

Comments
 (0)