AWS API Gatewat WebSocket:未选择代理集成和使用请求模板时连接失败
AWS API Gatewat WebSocket: connection fails when not selecting Proxy Integration and use Request Templates
我正在 AWS 上构建我的应用程序,yy 应用程序使用这样的 websocket:
前端 WebSocket 客户端 ---> AWS API 网关 Websocket API ----> EC2 实例后端
这是它的工作原理:
使用 AWS API 网关,WebSocket API 执行由我的 $connect
集成设置的操作。在我当前的配置中,我在目标 url 上设置了 VPC Link 与 HTTP 方法 Any 的集成。当前端尝试与 API 网关建立 websocket 连接时,WebSocket API 的 $connect
方法被触发并且 AWS WebSocket API 调用我的后端 HTTP 端点 <BACKEND_URL>/connect
.
前端:ReactJS / Javascript 本机 Websocket:
在我使用 websocket 的组件中:
useEffect(() => {
const orgId = localData.get('currentOrganizationId');
const username = localData.get('username');
let socket = new WebSocket(process.env.REACT_APP_WEBSOCKET_URL); // this is the AWS WebSocket URL after I have deployed it.
socket.onopen = function(e) {
console.log('socket on onopen');
const info = JSON.stringify({orgId:orgId, username: username, action: "message"});
socket.send(info);
};
socket.onmessage = function(event) {
console.log(`[message] Data received from server: ${event.data}`);
};
socket.onclose = function(event) {
if (event.wasClean) {
console.log(`[close] Connection closed cleanly, code=${event.code} reason=${event.reason}`);
} else {
console.log(`[close] Connection died; code=${event.code}`);
}
};
socket.onerror = function(error) {
console.log(`[error] ${error.message}`);
};
}, [])
AWS WebSocket API 配置:
后端:NodeJS / ExpressJS,在 index.ts
中:
app.get('/connect', function(_req, res) {
logger.info(`/connect _req: ${Object.keys(_req)}`);
logger.info(`/connect _req.query: ${JSON.stringify(_req.query)}`);
logger.info(`/connect _req.params: ${JSON.stringify(_req.params)}`);
logger.info(`/connect _req.body: ${JSON.stringify(_req.body)}`);
logger.info(`/connect _req.headers: ${JSON.stringify(_req.headers)}`);
res.send('/connect hahaha success');
});
app.put('/default', function(_req, res) {
logger.info(`/default _req.query: ${JSON.stringify(_req.query)}`);
logger.info(`/default _req.params: ${JSON.stringify(_req.params)}`);
logger.info(`/default _req.body: ${JSON.stringify(_req.body)}`);
logger.info(`/default _req.headers: ${JSON.stringify(_req.headers)}`);
res.send('/default hahaha default');
});
现在,这很完美。当我在我的浏览器中加载前端时,在 EC2 实例中,我可以看到 Express 的日志显示 \connect
被触发,并且当 socket.onopen()
在前端代码中成功时打印内容:
2022-Jan-17 11:51:29:5129 info: /connect _req: _readableState,_events,_eventsCount,_maxListeners,socket,httpVersionMajor,httpVersionMinor,httpVersion,complete,rawHeaders,rawTrailers,aborted,upgrade,url,method,statusCode,statusMessage,client,_consuming,_dumped,next,baseUrl,originalUrl,_parsedUrl,params,query,res,_startAt,_startTime,_remoteAddress,body,_parsedOriginalUrl,route
2022-Jan-17 11:51:29:5129 info: /connect _req.query: {}
2022-Jan-17 11:51:29:5129 info: /connect _req.params: {}
2022-Jan-17 11:51:29:5129 info: /connect _req.body: {}
2022-Jan-17 11:51:29:5129 info: /connect _req.headers: {"accept-encoding":"gzip, deflate, br","accept-language":"en-US,en;q=0.9","cache-control":"no-cache","origin":"http://127.0.0.1:3000","pragma":"no-cache","sec-websocket-extensions":"permessage-deflate; client_max_window_bits","sec-websocket-key":"w0HoFw7+RtvLi3KWgT2OBw==","sec-websocket-version":"13","user-agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.71 Safari/537.36","x-amzn-trace-id":"Root=1-61e4d9b1-671cf2d36097a75435133215","x-forwarded-for":"219.102.102.145","x-forwarded-port":"443","x-forwarded-proto":"https","x-amzn-apigateway-api-id":"hd5zymklr8","host":"NLB-docloud-internal-ea0692d1e2c8186c.elb.ap-northeast-1.amazonaws.com","connection":"Keep-Alive"}
2022-Jan-17 11:51:29:5129 info: /default _req.query: {}
2022-Jan-17 11:51:29:5129 info: /default _req.params: {}
2022-Jan-17 11:51:29:5129 info: /default _req.body: {"orgId":"1","username":"staff_a","action":"message"}
2022-Jan-17 11:51:29:5129 info: /default _req.headers: {"user-agent":"AmazonAPIGateway_hd5zymklr8","x-amzn-apigateway-api-id":"hd5zymklr8","host":"NLB-docloud-internal-ea0692d1e2c8186c.elb.ap-northeast-1.amazonaws.com","content-length":"53","content-type":"application/json; charset=UTF-8","connection":"Keep-Alive"}
此外,/default
会立即触发并收到一条消息 {"orgId":"1","username":"staff_a","action":"message"}
,因为在前端代码中我正在调用:
const info = JSON.stringify({orgId:orgId, username: username, action: "message"});
socket.send(info);
socket.onopen()
成功后立即。
到目前为止一切顺利。
现在,为了让我的后端 Express 代码知道如何向特定客户端发送消息,我让它知道 websocket 客户端/用户的 connectionId
。我正在关注这两个答案:
里面解释的很清楚了。
基本上我需要de-select Use Proxy Integration
和配置Request Templates
。
这是我的配置:
但是,连接失败。我已经尝试将模板密钥设置为 $default
和 $default
,但它们都失败了。请注意,这不要与 $connect
路线旁边的 $default
路线混淆。我们现在完全关注 $connect
路由,它的 Request Template Key 值恰好是 $default
以匹配所有请求。
EC2实例中的Express日志为:
2022-Jan-17 12:04:49:449 info: /connect _req: _readableState,_events,_eventsCount,_maxListeners,socket,httpVersionMajor,httpVersionMinor,httpVersion,complete,rawHeaders,rawTrailers,aborted,upgrade,url,method,statusCode,statusMessage,client,_consuming,_dumped,next,baseUrl,originalUrl,_parsedUrl,params,query,res,_startAt,_startTime,_remoteAddress,body,_parsedOriginalUrl,route
2022-Jan-17 12:04:49:449 info: /connect _req.query: {}
2022-Jan-17 12:04:49:449 info: /connect _req.params: {}
2022-Jan-17 12:04:49:449 info: /connect _req.body: {}
2022-Jan-17 12:04:49:449 info: /connect _req.headers: {"x-amzn-apigateway-api-id":"hd5zymklr8","x-amzn-trace-id":"Root=1-61e4dccd-254aa4f9581373b00f8ef54d","user-agent":"AmazonAPIGateway_hd5zymklr8","content-type":"application/json","accept":"application/json","host":"NLB-docloud-internal-ea0692d1e2c8186c.elb.ap-northeast-1.amazonaws.com","connection":"Keep-Alive"}
\connect
端点仍然像以前一样被触发,但是连接失败并且由于 socket.onopen()
不成功,socket.send(info);
没有发送消息
在chrome开发模式下,我可以看到以下错误消息:
尽管 \connect
端点仍然像以前一样被触发,但为什么连接失败了?
我也注意到 _req.headers
比以前短了很多:
{
"x-amzn-apigateway-api-id":"hd5zymklr8",
"x-amzn-trace-id":"Root=1-61e4dccd-254aa4f9581373b00f8ef54d",
"user-agent":"AmazonAPIGateway_hd5zymklr8",
"content-type":"application/json",
"accept":"application/json",
"host":"NLB-docloud-internal-ea0692d1e2c8186c.elb.ap-northeast-1.amazonaws.com",
"connection":"Keep-Alive"
}
_req.headers
当一切正常时:
{
"accept-encoding":"gzip, deflate, br",
"accept-language":"en-US,en;q=0.9",
"cache-control":"no-cache",
"origin":"http://127.0.0.1:3000",
"pragma":"no-cache",
"sec-websocket-extensions":"permessage-deflate; client_max_window_bits",
"sec-websocket-key":"w0HoFw7+RtvLi3KWgT2OBw==",
"sec-websocket-version":"13",
"user-agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.71 Safari/537.36",
"x-amzn-trace-id":"Root=1-61e4d9b1-671cf2d36097a75435133215",
"x-forwarded-for":"219.102.102.145",
"x-forwarded-port":"443",
"x-forwarded-proto":"https",
"x-amzn-apigateway-api-id":"hd5zymklr8",
"host":"NLB-docloud-internal-ea0692d1e2c8186c.elb.ap-northeast-1.amazonaws.com",
"connection":"Keep-Alive"
}
我自己回答,以防万一这可能对某人有所帮助。
其实非常非常琐碎。在将消息发送到客户端之前,我必须对从后端集成收到的响应配置模板化转换!
我将 Response Key 配置为 $default
且没有进一步说明:
而且有效。
看来 API Gateway 的 WebSocket API 确实适合使用 Lambda 作为后端集成。
使用其他类型的后端集成,您将面临许多痛苦的问题,AWS 文档/教程对 Lambda 以外的后端集成类型毫无帮助。
我正在 AWS 上构建我的应用程序,yy 应用程序使用这样的 websocket:
前端 WebSocket 客户端 ---> AWS API 网关 Websocket API ----> EC2 实例后端
这是它的工作原理:
使用 AWS API 网关,WebSocket API 执行由我的 $connect
集成设置的操作。在我当前的配置中,我在目标 url 上设置了 VPC Link 与 HTTP 方法 Any 的集成。当前端尝试与 API 网关建立 websocket 连接时,WebSocket API 的 $connect
方法被触发并且 AWS WebSocket API 调用我的后端 HTTP 端点 <BACKEND_URL>/connect
.
前端:ReactJS / Javascript 本机 Websocket: 在我使用 websocket 的组件中:
useEffect(() => {
const orgId = localData.get('currentOrganizationId');
const username = localData.get('username');
let socket = new WebSocket(process.env.REACT_APP_WEBSOCKET_URL); // this is the AWS WebSocket URL after I have deployed it.
socket.onopen = function(e) {
console.log('socket on onopen');
const info = JSON.stringify({orgId:orgId, username: username, action: "message"});
socket.send(info);
};
socket.onmessage = function(event) {
console.log(`[message] Data received from server: ${event.data}`);
};
socket.onclose = function(event) {
if (event.wasClean) {
console.log(`[close] Connection closed cleanly, code=${event.code} reason=${event.reason}`);
} else {
console.log(`[close] Connection died; code=${event.code}`);
}
};
socket.onerror = function(error) {
console.log(`[error] ${error.message}`);
};
}, [])
AWS WebSocket API 配置:
后端:NodeJS / ExpressJS,在 index.ts
中:
app.get('/connect', function(_req, res) {
logger.info(`/connect _req: ${Object.keys(_req)}`);
logger.info(`/connect _req.query: ${JSON.stringify(_req.query)}`);
logger.info(`/connect _req.params: ${JSON.stringify(_req.params)}`);
logger.info(`/connect _req.body: ${JSON.stringify(_req.body)}`);
logger.info(`/connect _req.headers: ${JSON.stringify(_req.headers)}`);
res.send('/connect hahaha success');
});
app.put('/default', function(_req, res) {
logger.info(`/default _req.query: ${JSON.stringify(_req.query)}`);
logger.info(`/default _req.params: ${JSON.stringify(_req.params)}`);
logger.info(`/default _req.body: ${JSON.stringify(_req.body)}`);
logger.info(`/default _req.headers: ${JSON.stringify(_req.headers)}`);
res.send('/default hahaha default');
});
现在,这很完美。当我在我的浏览器中加载前端时,在 EC2 实例中,我可以看到 Express 的日志显示 \connect
被触发,并且当 socket.onopen()
在前端代码中成功时打印内容:
2022-Jan-17 11:51:29:5129 info: /connect _req: _readableState,_events,_eventsCount,_maxListeners,socket,httpVersionMajor,httpVersionMinor,httpVersion,complete,rawHeaders,rawTrailers,aborted,upgrade,url,method,statusCode,statusMessage,client,_consuming,_dumped,next,baseUrl,originalUrl,_parsedUrl,params,query,res,_startAt,_startTime,_remoteAddress,body,_parsedOriginalUrl,route
2022-Jan-17 11:51:29:5129 info: /connect _req.query: {}
2022-Jan-17 11:51:29:5129 info: /connect _req.params: {}
2022-Jan-17 11:51:29:5129 info: /connect _req.body: {}
2022-Jan-17 11:51:29:5129 info: /connect _req.headers: {"accept-encoding":"gzip, deflate, br","accept-language":"en-US,en;q=0.9","cache-control":"no-cache","origin":"http://127.0.0.1:3000","pragma":"no-cache","sec-websocket-extensions":"permessage-deflate; client_max_window_bits","sec-websocket-key":"w0HoFw7+RtvLi3KWgT2OBw==","sec-websocket-version":"13","user-agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.71 Safari/537.36","x-amzn-trace-id":"Root=1-61e4d9b1-671cf2d36097a75435133215","x-forwarded-for":"219.102.102.145","x-forwarded-port":"443","x-forwarded-proto":"https","x-amzn-apigateway-api-id":"hd5zymklr8","host":"NLB-docloud-internal-ea0692d1e2c8186c.elb.ap-northeast-1.amazonaws.com","connection":"Keep-Alive"}
2022-Jan-17 11:51:29:5129 info: /default _req.query: {}
2022-Jan-17 11:51:29:5129 info: /default _req.params: {}
2022-Jan-17 11:51:29:5129 info: /default _req.body: {"orgId":"1","username":"staff_a","action":"message"}
2022-Jan-17 11:51:29:5129 info: /default _req.headers: {"user-agent":"AmazonAPIGateway_hd5zymklr8","x-amzn-apigateway-api-id":"hd5zymklr8","host":"NLB-docloud-internal-ea0692d1e2c8186c.elb.ap-northeast-1.amazonaws.com","content-length":"53","content-type":"application/json; charset=UTF-8","connection":"Keep-Alive"}
此外,/default
会立即触发并收到一条消息 {"orgId":"1","username":"staff_a","action":"message"}
,因为在前端代码中我正在调用:
const info = JSON.stringify({orgId:orgId, username: username, action: "message"});
socket.send(info);
socket.onopen()
成功后立即。
到目前为止一切顺利。
现在,为了让我的后端 Express 代码知道如何向特定客户端发送消息,我让它知道 websocket 客户端/用户的 connectionId
。我正在关注这两个答案:
里面解释的很清楚了。
基本上我需要de-select Use Proxy Integration
和配置Request Templates
。
这是我的配置:
但是,连接失败。我已经尝试将模板密钥设置为 $default
和 $default
,但它们都失败了。请注意,这不要与 $connect
路线旁边的 $default
路线混淆。我们现在完全关注 $connect
路由,它的 Request Template Key 值恰好是 $default
以匹配所有请求。
EC2实例中的Express日志为:
2022-Jan-17 12:04:49:449 info: /connect _req: _readableState,_events,_eventsCount,_maxListeners,socket,httpVersionMajor,httpVersionMinor,httpVersion,complete,rawHeaders,rawTrailers,aborted,upgrade,url,method,statusCode,statusMessage,client,_consuming,_dumped,next,baseUrl,originalUrl,_parsedUrl,params,query,res,_startAt,_startTime,_remoteAddress,body,_parsedOriginalUrl,route
2022-Jan-17 12:04:49:449 info: /connect _req.query: {}
2022-Jan-17 12:04:49:449 info: /connect _req.params: {}
2022-Jan-17 12:04:49:449 info: /connect _req.body: {}
2022-Jan-17 12:04:49:449 info: /connect _req.headers: {"x-amzn-apigateway-api-id":"hd5zymklr8","x-amzn-trace-id":"Root=1-61e4dccd-254aa4f9581373b00f8ef54d","user-agent":"AmazonAPIGateway_hd5zymklr8","content-type":"application/json","accept":"application/json","host":"NLB-docloud-internal-ea0692d1e2c8186c.elb.ap-northeast-1.amazonaws.com","connection":"Keep-Alive"}
\connect
端点仍然像以前一样被触发,但是连接失败并且由于 socket.onopen()
不成功,socket.send(info);
在chrome开发模式下,我可以看到以下错误消息:
尽管 \connect
端点仍然像以前一样被触发,但为什么连接失败了?
我也注意到 _req.headers
比以前短了很多:
{
"x-amzn-apigateway-api-id":"hd5zymklr8",
"x-amzn-trace-id":"Root=1-61e4dccd-254aa4f9581373b00f8ef54d",
"user-agent":"AmazonAPIGateway_hd5zymklr8",
"content-type":"application/json",
"accept":"application/json",
"host":"NLB-docloud-internal-ea0692d1e2c8186c.elb.ap-northeast-1.amazonaws.com",
"connection":"Keep-Alive"
}
_req.headers
当一切正常时:
{
"accept-encoding":"gzip, deflate, br",
"accept-language":"en-US,en;q=0.9",
"cache-control":"no-cache",
"origin":"http://127.0.0.1:3000",
"pragma":"no-cache",
"sec-websocket-extensions":"permessage-deflate; client_max_window_bits",
"sec-websocket-key":"w0HoFw7+RtvLi3KWgT2OBw==",
"sec-websocket-version":"13",
"user-agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.71 Safari/537.36",
"x-amzn-trace-id":"Root=1-61e4d9b1-671cf2d36097a75435133215",
"x-forwarded-for":"219.102.102.145",
"x-forwarded-port":"443",
"x-forwarded-proto":"https",
"x-amzn-apigateway-api-id":"hd5zymklr8",
"host":"NLB-docloud-internal-ea0692d1e2c8186c.elb.ap-northeast-1.amazonaws.com",
"connection":"Keep-Alive"
}
我自己回答,以防万一这可能对某人有所帮助。
其实非常非常琐碎。在将消息发送到客户端之前,我必须对从后端集成收到的响应配置模板化转换!
我将 Response Key 配置为 $default
且没有进一步说明:
而且有效。
看来 API Gateway 的 WebSocket API 确实适合使用 Lambda 作为后端集成。
使用其他类型的后端集成,您将面临许多痛苦的问题,AWS 文档/教程对 Lambda 以外的后端集成类型毫无帮助。