Skip to content

Commit 10f84d0

Browse files
authored
Merge pull request alibaba#351 from alibaba/ws-header-fix
pass the headers when proxy websocket request
2 parents 11e6810 + aae5c9b commit 10f84d0

File tree

6 files changed

+115
-46
lines changed

6 files changed

+115
-46
lines changed

lib/requestHandler.js

Lines changed: 34 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -212,18 +212,39 @@ function fetchRemoteResponse(protocol, options, reqData, config) {
212212
@param @required wsClient the ws client of WebSocket
213213
*
214214
*/
215-
function getWsReqInfo(wsClient) {
216-
const upgradeReq = wsClient.upgradeReq || {};
217-
const header = upgradeReq.headers || {};
218-
const host = header.host;
215+
function getWsReqInfo(wsReq) {
216+
const headers = wsReq.headers || {};
217+
const host = headers.host;
219218
const hostName = host.split(':')[0];
220219
const port = host.split(':')[1];
221220

222221
// TODO 如果是windows机器,url是不是全路径?需要对其过滤,取出
223-
const path = upgradeReq.url || '/';
222+
const path = wsReq.url || '/';
223+
224+
const isEncript = true && wsReq.connection && wsReq.connection.encrypted;
225+
/**
226+
* construct the request headers based on original connection,
227+
* but delete the `sec-websocket-*` headers as they are already consumed by AnyProxy
228+
*/
229+
const getNoWsHeaders = () => {
230+
const originHeaders = Object.assign({}, headers);
231+
const originHeaderKeys = Object.keys(originHeaders);
232+
originHeaderKeys.forEach((key) => {
233+
// if the key matchs 'sec-websocket', delete it
234+
if (/sec-websocket/ig.test(key)) {
235+
delete originHeaders[key];
236+
}
237+
});
238+
239+
delete originHeaders.connection;
240+
delete originHeaders.upgrade;
241+
return originHeaders;
242+
}
243+
224244

225-
const isEncript = true && upgradeReq.connection && upgradeReq.connection.encrypted;
226245
return {
246+
headers: headers, // the full headers of origin ws connection
247+
noWsHeaders: getNoWsHeaders(),
227248
hostName: hostName,
228249
port: port,
229250
path: path,
@@ -664,18 +685,19 @@ function getConnectReqHandler(userRule, recorder, httpsServerMgr) {
664685
* get a websocket event handler
665686
@param @required {object} wsClient
666687
*/
667-
function getWsHandler(userRule, recorder, wsClient) {
688+
function getWsHandler(userRule, recorder, wsClient, wsReq) {
668689
const self = this;
669690
try {
670691
let resourceInfoId = -1;
671692
const resourceInfo = {
672693
wsMessages: [] // all ws messages go through AnyProxy
673694
};
674695
const clientMsgQueue = [];
675-
const serverInfo = getWsReqInfo(wsClient);
696+
const serverInfo = getWsReqInfo(wsReq);
676697
const wsUrl = `${serverInfo.protocol}://${serverInfo.hostName}:${serverInfo.port}${serverInfo.path}`;
677698
const proxyWs = new WebSocket(wsUrl, '', {
678-
rejectUnauthorized: !self.dangerouslyIgnoreUnauthorized
699+
rejectUnauthorized: !self.dangerouslyIgnoreUnauthorized,
700+
headers: serverInfo.noWsHeaders
679701
});
680702

681703
if (recorder) {
@@ -684,7 +706,7 @@ function getWsHandler(userRule, recorder, wsClient) {
684706
method: 'WebSocket',
685707
path: serverInfo.path,
686708
url: wsUrl,
687-
req: wsClient.upgradeReq || {},
709+
req: wsReq,
688710
startTime: new Date().getTime()
689711
});
690712
resourceInfoId = recorder.appendRecord(resourceInfo);
@@ -763,8 +785,9 @@ function getWsHandler(userRule, recorder, wsClient) {
763785
}
764786

765787
// this event is fired when the connection is build and headers is returned
766-
proxyWs.on('headers', (headers, response) => {
788+
proxyWs.on('upgrade', (response) => {
767789
resourceInfo.endTime = new Date().getTime();
790+
const headers = response.headers;
768791
resourceInfo.res = { //construct a self-defined res object
769792
statusCode: response.statusCode,
770793
headers: headers,

package.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@
3939
"stream-throttle": "^0.1.3",
4040
"svg-inline-react": "^1.0.2",
4141
"whatwg-fetch": "^1.0.0",
42-
"ws": "^2.2.0"
42+
"ws": "^5.1.0"
4343
},
4444
"devDependencies": {
4545
"antd": "^2.5.0",
@@ -69,7 +69,6 @@
6969
"koa-body": "^1.4.0",
7070
"koa-router": "^5.4.0",
7171
"koa-send": "^3.2.0",
72-
"koa-websocket": "^2.0.0",
7372
"less": "^2.7.1",
7473
"less-loader": "^2.2.3",
7574
"memwatch-next": "^0.3.0",

test/server/server.js

Lines changed: 30 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ const https = require('https');
77
const certMgr = require('../../lib/certMgr');
88
const fs = require('fs');
99
const nurl = require('url');
10-
const websocket = require('koa-websocket');
1110
const color = require('colorful');
1211
const WebSocketServer = require('ws').Server;
1312
const tls = require('tls');
@@ -65,6 +64,23 @@ function KoaServer() {
6564
yield next;
6665
};
6766

67+
this.logWsRequest = function (wsReq) {
68+
const headers = wsReq.headers;
69+
const host = headers.host;
70+
const isEncript = true && wsReq.connection && wsReq.connection.encrypted;
71+
const protocol = isEncript ? 'wss' : 'ws';
72+
let key = `${protocol}://${host}${wsReq.url}`;
73+
// take proxy data with 'proxy-' + url
74+
if (headers['via-proxy'] === 'true') {
75+
key = PROXY_KEY_PREFIX + key;
76+
}
77+
78+
self.requestRecordMap[key] = {
79+
headers: wsReq.headers,
80+
body: ''
81+
}
82+
};
83+
6884
this.start();
6985
}
7086

@@ -236,11 +252,14 @@ KoaServer.prototype.constructRouter = function () {
236252
return router;
237253
};
238254

239-
KoaServer.prototype.constructWsRouter = function () {
240-
const wsRouter = KoaRouter();
241-
const self = this;
242-
wsRouter.get('/test/socket', function *(next) {
243-
const ws = this.websocket;
255+
KoaServer.prototype.createWsServer = function (httpServer) {
256+
const wsServer = new WebSocketServer({
257+
server: httpServer,
258+
path: '/test/socket'
259+
});
260+
wsServer.on('connection', (ws, wsReq) => {
261+
const self = this;
262+
self.logWsRequest(wsReq);
244263
const messageObj = {
245264
type: 'initial',
246265
content: 'default message'
@@ -251,10 +270,7 @@ KoaServer.prototype.constructWsRouter = function () {
251270
printLog('message from request socket: ' + message);
252271
self.handleRecievedMessage(ws, message);
253272
});
254-
yield next;
255-
});
256-
257-
return wsRouter;
273+
})
258274
};
259275

260276
KoaServer.prototype.getRequestRecord = function (key) {
@@ -277,14 +293,13 @@ KoaServer.prototype.handleRecievedMessage = function (ws, message) {
277293
KoaServer.prototype.start = function () {
278294
printLog('Starting the server...');
279295
const router = this.constructRouter();
280-
const wsRouter = this.constructWsRouter();
281296
const self = this;
282297
const app = Koa();
283-
websocket(app);
284298

285299
app.use(router.routes());
286-
app.ws.use(wsRouter.routes());
287300
this.httpServer = app.listen(DEFAULT_PORT);
301+
this.createWsServer(this.httpServer);
302+
288303

289304
printLog('HTTP is now listening on port :' + DEFAULT_PORT);
290305

@@ -303,7 +318,8 @@ KoaServer.prototype.start = function () {
303318
server: self.httpsServer
304319
});
305320

306-
wss.on('connection', (ws) => {
321+
wss.on('connection', (ws, wsReq) => {
322+
self.logWsRequest(wsReq);
307323
ws.on('message', (message) => {
308324
printLog('received in wss: ' + message);
309325
self.handleRecievedMessage(ws, message);

test/spec_rule/no_rule_websocket_spec.js

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
const ProxyServerUtil = require('../util/ProxyServerUtil.js');
77
const { generateWsUrl, directWs, proxyWs } = require('../util/HttpUtil.js');
88
const Server = require('../server/server.js');
9-
const { printLog, isArrayEqual } = require('../util/CommonUtil.js');
9+
const { printLog, isArrayEqual, isCommonReqEqual } = require('../util/CommonUtil.js');
1010

1111
testWebsocket('ws');
1212
testWebsocket('wss');
@@ -26,6 +26,11 @@ function testWebsocket(protocol, masked = false) {
2626
'Send the message with default option4'
2727
];
2828

29+
const websocketHeaders = {
30+
referer: 'https://www.anyproxy.io/websocket/test',
31+
origin: 'www.anyproxy.io'
32+
}
33+
2934
beforeAll((done) => {
3035
jasmine.DEFAULT_TIMEOUT_INTERVAL = 200000;
3136
printLog('Start server for no_rule_websocket_spec');
@@ -47,11 +52,11 @@ function testWebsocket(protocol, masked = false) {
4752
it('Default websocket option', done => {
4853
const directMessages = []; // set the flag for direct message, compare when both direct and proxy got message
4954
const proxyMessages = [];
50-
let directHeaders;
51-
let proxyHeaders;
55+
let directResHeaders;
56+
let proxyResHeaders;
5257

53-
const ws = directWs(url);
54-
const proxyWsRef = proxyWs(url);
58+
const ws = directWs(url, websocketHeaders);
59+
const proxyWsRef = proxyWs(url, websocketHeaders);
5560
ws.on('open', () => {
5661
ws.send(testMessageArray[0], masked);
5762
for (let i = 1; i < testMessageArray.length; i++) {
@@ -74,13 +79,13 @@ function testWebsocket(protocol, masked = false) {
7479
}
7580
});
7681

77-
ws.on('headers', (headers) => {
78-
directHeaders = headers;
82+
ws.on('upgrade', (res) => {
83+
directResHeaders = res.headers;
7984
compareMessageIfReady();
8085
});
8186

82-
proxyWsRef.on('headers', (headers) => {
83-
proxyHeaders = headers;
87+
proxyWsRef.on('upgrade', (res) => {
88+
proxyResHeaders = res.headers;
8489
compareMessageIfReady();
8590
});
8691

@@ -114,12 +119,13 @@ function testWebsocket(protocol, masked = false) {
114119
const targetLen = testMessageArray.length;
115120
if (directMessages.length === targetLen
116121
&& proxyMessages.length === targetLen
117-
&& directHeaders && proxyHeaders
122+
&& directResHeaders && proxyResHeaders
118123
) {
119124
expect(isArrayEqual(directMessages, testMessageArray)).toBe(true);
120125
expect(isArrayEqual(directMessages, proxyMessages)).toBe(true);
121-
expect(directHeaders['x-anyproxy-websocket']).toBeUndefined();
122-
expect(proxyHeaders['x-anyproxy-websocket']).toBe('true');
126+
expect(directResHeaders['x-anyproxy-websocket']).toBeUndefined();
127+
expect(proxyResHeaders['x-anyproxy-websocket']).toBe('true');
128+
expect(isCommonReqEqual(url, serverInstance)).toBe(true);
123129
done();
124130
}
125131
}

test/util/CommonUtil.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ function isCommonResHeaderEqual(directHeaders, proxyHeaders, requestUrl) {
120120
*
121121
*/
122122
function isCommonReqEqual(url, serverInstance) {
123+
console.info('==> trying to get the url ', url);
123124
try {
124125
let isEqual = true;
125126

@@ -139,6 +140,27 @@ function isCommonReqEqual(url, serverInstance) {
139140
delete directReqObj.headers['transfer-encoding'];
140141
delete proxyReqObj.headers['transfer-encoding'];
141142

143+
// delete the headers that should not be passed by AnyProxy
144+
delete directReqObj.headers.connection;
145+
delete proxyReqObj.headers.connection;
146+
147+
// delete the headers related to websocket establishment
148+
const directHeaderKeys = Object.keys(directReqObj.headers);
149+
directHeaderKeys.forEach((key) => {
150+
// if the key matchs 'sec-websocket', delete it
151+
if (/sec-websocket/ig.test(key)) {
152+
delete directReqObj.headers[key];
153+
}
154+
});
155+
156+
const proxyHeaderKeys = Object.keys(proxyReqObj.headers);
157+
proxyHeaderKeys.forEach((key) => {
158+
// if the key matchs 'sec-websocaket', delete it
159+
if (/sec-websocket/ig.test(key)) {
160+
delete proxyReqObj.headers[key];
161+
}
162+
});
163+
142164
isEqual = isEqual && directReqObj.url === proxyReqObj.url;
143165
isEqual = isEqual && isObjectEqual(directReqObj.headers, proxyReqObj.headers, url);
144166
isEqual = isEqual && directReqObj.body === proxyReqObj.body;

test/util/HttpUtil.js

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -187,17 +187,20 @@ function doUpload(url, method, filepath, formParams, headers = {}, isProxy) {
187187
return requestTask;
188188
}
189189

190-
function doWebSocket(url, isProxy) {
190+
function doWebSocket(url, headers = {}, isProxy) {
191191
let ws;
192192
if (isProxy) {
193+
headers['via-proxy'] = 'true';
193194
const agent = new HttpsProxyAgent(SOCKET_PROXY_HOST);
194195
ws = new WebSocket(url, {
195196
agent,
196-
rejectUnauthorized: false
197+
rejectUnauthorized: false,
198+
headers
197199
});
198200
} else {
199201
ws = new WebSocket(url, {
200-
rejectUnauthorized: false
202+
rejectUnauthorized: false,
203+
headers
201204
});
202205
}
203206

@@ -252,12 +255,12 @@ function directOptions(url, headers = {}) {
252255
return directRequest('OPTIONS', url, {}, headers);
253256
}
254257

255-
function proxyWs(url) {
256-
return doWebSocket(url, true);
258+
function proxyWs(url, headers) {
259+
return doWebSocket(url, headers, true);
257260
}
258261

259-
function directWs(url) {
260-
return doWebSocket(url);
262+
function directWs(url, headers) {
263+
return doWebSocket(url, headers);
261264
}
262265

263266
/**

0 commit comments

Comments
 (0)