我正在尝试在 Google Cloud Run 上设置 WebSocket 服务器,以使用 Twilio 的媒体流功能接收来自 Twilio 的媒体流。目标是让 Twilio 将实时音频数据传输到 Google Cloud Run 上托管的 WebSocket 服务器。
我们不断收到以下错误:
Unsupported Media Type: application/x-www-form-urlencoded; charset=UTF-8
当 Twilio 向我们的 /incoming-call 端点发送 POST 请求时,会发生此错误。尽管尝试了几种不同的方法来解析传入的数据,我们的 Fastify 服务器还是拒绝了它并返回 415 不支持的媒体类型响应。
fastify.register(require('@fastify/formbody'));
fastify.addContentTypeParser('application/x-www-form-urlencoded', (req, payload, done) => {
let body = '';
req.on('data', chunk => { body += chunk });
req.on('end', () => {
const parsed = new URLSearchParams(body);
done(null, Object.fromEntries(parsed));
});
});
fastify.addHook('preHandler', async (request, reply) => {
if (request.headers['content-type']?.includes('application/x-www-form-urlencoded')) {
let rawBody = '';
for await (const chunk of request.raw) {
rawBody += chunk;
}
request.body = Object.fromEntries(new URLSearchParams(rawBody));
}
});
这是当前的实现:
import Fastify from 'fastify';
import fastifyWs from '@fastify/websocket';
const fastify = Fastify({ logger: true });
fastify.register(fastifyWs);
const PORT = process.env.PORT || 8080;
// Custom content type parser for Twilio requests
fastify.addContentTypeParser('application/x-www-form-urlencoded', (req, payload, done) => {
done(null, req); // Pass the raw request object
});
// Pre-handler to parse form data manually
fastify.addHook('preHandler', async (request, reply) => {
if (typeof request.body === 'object' && request.body.readable) {
let rawBody = '';
for await (const chunk of request.body) {
rawBody += chunk;
}
request.body = Object.fromEntries(new URLSearchParams(rawBody));
}
});
// Handle Twilio's initial call request
fastify.post('/incoming-call', async (request, reply) => {
console.log('Received POST request on /incoming-call');
console.log('Parsed body:', request.body);
const twimlResponse = `<?xml version="1.0" encoding="UTF-8"?>
<Response>
<Say>Please wait while we connect your call to the AI assistant.</Say>
<Pause length="1"/>
<Connect>
<Stream url="wss://${request.headers.host}/media-stream" />
</Connect>
</Response>`;
reply.type('text/xml').send(twimlResponse);
});
// WebSocket route to handle media stream
fastify.get('/media-stream', { websocket: true }, (connection) => {
console.log('WebSocket connection established');
connection.socket.on('message', (message) => {
console.log('Received audio data from Twilio:', message);
});
connection.socket.on('close', () => {
console.log('WebSocket connection closed');
});
});
// Start the server
fastify.listen({ port: PORT, host: '0.0.0.0' }, (err) => {
if (err) {
fastify.log.error(err);
process.exit(1);
}
console.log(`Server is running on port ${PORT}`);
});
任何帮助或指导将不胜感激!预先感谢您!
我已成功让 Twilio Media 流与 Cloud Run 配合使用,使用
@fastify/formbody
依赖项来处理解析来自 Twilio 的入站请求正文。
我修改了您上面分享的代码;更新后的版本如下所示。 speech-assistant-openai-realtime-api-node 示例用作参考。
import Fastify from 'fastify';
import fastifyFormBody from '@fastify/formbody';
import fastifyWs from '@fastify/websocket';
const fastify = Fastify({ logger: true });
fastify.register(fastifyFormBody);
fastify.register(fastifyWs);
const PORT = process.env.PORT || 8080;
// Handle Twilio's initial call request
fastify.post('/incoming-call', async (request, reply) => {
console.log('Received POST request on /incoming-call');
console.log('Parsed body:', request.body);
const twimlResponse = `<?xml version="1.0" encoding="UTF-8"?>
<Response>
<Say>Please wait while we connect your call to the AI assistant.</Say>
<Pause length="1"/>
<Connect>
<Stream url="wss://${request.headers.host}/media-stream" />
</Connect>
</Response>`;
reply.type('text/xml').send(twimlResponse);
});
// WebSocket route to handle media stream
fastify.register(async (fastify) => {
fastify.get('/media-stream', { websocket: true }, (connection) => {
console.log('WebSocket connection established');
connection.on('message', (message) => {
console.log('Received audio data from Twilio:', message);
});
connection.on('close', () => {
console.log('WebSocket connection closed');
});
});
});
// Start the server
fastify.listen({ port: PORT, host: '0.0.0.0' }, (err) => {
if (err) {
fastify.log.error(err);
process.exit(1);
}
console.log(`Server is running on port ${PORT}`);
});
这是使用
node:22-alpine
作为基础映像运行的,并将依赖项设置为以下版本。
{
"dependencies": {
"@fastify/formbody": "^8.0.1",
"@fastify/websocket": "^11.0.1",
"fastify": "^5.1.0"
}
}
我的 Twilio 电话号码配置如下
Cloud Run 实例还配置为
Allow unauthenticated invocations
希望这有帮助