Node.js + Fastify + Twilio + Google Cloud Run + WebSocket 服务器 + 返回 415 不支持的媒体类型

问题描述 投票:0回答:1

我正在尝试在 Google Cloud Run 上设置 WebSocket 服务器,以使用 Twilio 的媒体流功能接收来自 Twilio 的媒体流。目标是让 Twilio 将实时音频数据传输到 Google Cloud Run 上托管的 WebSocket 服务器。

我们正在努力实现的目标:

  • 收到呼叫时,Twilio 应向我们的 /incoming-call 端点发出初始 POST 请求。
  • 我们的服务器应使用 TwiML 响应进行响应,指示 Twilio 连接到 /media-stream 处的 WebSocket 流。
  • 连接后,Twilio 应将音频数据实时传输到 WebSocket 端点。

技术堆栈:

  • 使用 Fastify 框架的 Node.js。
  • Google Cloud Run 用于托管服务器。
  • Twilio 用于处理电话呼叫和媒体流。

问题:

我们不断收到以下错误:

Unsupported Media Type: application/x-www-form-urlencoded; charset=UTF-8

当 Twilio 向我们的 /incoming-call 端点发送 POST 请求时,会发生此错误。尽管尝试了几种不同的方法来解析传入的数据,我们的 Fastify 服务器还是拒绝了它并返回 415 不支持的媒体类型响应。

我尝试过的:

  1. 注册了 Fastify 的 @fastify/formbody 插件来处理 application/x-www-form-urlencoded 内容:
fastify.register(require('@fastify/formbody'));
  1. 添加了自定义内容类型解析器来手动解析传入的表单数据:
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));
    });
});
  1. 尝试通过手动读取请求流来使用原始正文解析:
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));
    }
});
  1. 使用 Twilio Node.js SDK 验证传入请求,但仍然收到相同的错误。

当前代码:

这是当前的实现:

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}`);
});

什么有效:

  • 服务器在 Google Cloud Run 上成功启动。
  • 在本地测试时,WebSocket 端点 (/media-stream) 连接正确。

什么不起作用:

  • 当 Twilio 向 /incoming-call 发送 POST 请求时,我们收到 415 不支持的媒体类型错误。
  • 尽管尝试了多种方法来处理 application/x-www-form-urlencoded 内容类型,但 Fastify 似乎不接受来自 Twilio 的数据。

问题:

  • 有人使用 Fastify 在 Google Cloud Run 上成功处理了 Twilio 的 POST 请求吗?
  • 是否有更好的方法来解析我们缺少的 Twilio 的 application/x-www-form-urlencoded 请求?
  • 问题是否与 Google Cloud Run 处理标头或内容类型的方式有关?

任何帮助或指导将不胜感激!预先感谢您!

node.js google-cloud-run twilio-api fastify twilio-twiml
1个回答
0
投票

我已成功让 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 电话号码配置如下

Twilio Voice Configuration

Cloud Run 实例还配置为

Allow unauthenticated invocations

希望这有帮助

© www.soinside.com 2019 - 2024. All rights reserved.