Angular连接Azure SignalR与v4版TypeScript函数的问题求助
Angular 12 + Azure SignalR + Functions v4 连接问题排查
问题概述
连接Angular 12前端与Azure SignalR、TypeScript无服务器Azure Functions后端时,遇到两个核心问题:
- Azure Functions v4编程模型的SignalR绑定语法存在什么错误?
- 当
negotiate()函数返回硬编码值时,如何解决Angular连接时出现的**Error: None of the transports supported by the client are supported by the server.**错误?
核心现象
- 无额外输入绑定时,
negotiate()函数可执行,但Angular调用this.hubConnection.start()时返回传输兼容错误 - 添加SignalR输入绑定后,函数启动前直接返回500状态码崩溃
- 上游触发(POST请求)无法正常工作
技术栈
- 无服务器Azure Functions(TypeScript,v4编程模型)
- 免费层无服务器Azure SignalR
- Angular 12前端(部署在Azure静态Web应用,带自定义域名)
客户端代码示例
const options: any = { endpoint: this.config?.signalRHostName, authType: 'azure.msi;Version=1.0;', // accessKey: 'ZYp9h52IfYouCanReadThisYouAreTooClosenweLQDoXMg=' }; let functionAppUrl = this.config?.functionHostName + '/api/'; this.hubConnection = new HubConnectionBuilder() .configureLogging(LogLevel.Debug) .withUrl(functionAppUrl, options) .build(); this.hubConnection .start() .then(() => { console.log('************* SignalR connection started'); this.sendHi(); }) .catch((err: any) => { console.log('************* Error while starting SignalR connection', err) });
服务端negotiate()函数示例
import { app, input, output, HttpResponseInit, InvocationContext } from '@azure/functions'; import { Log, IRequest, AuthUtils, AzureADHelper } from '@wetware/viewdu-fxa'; export async function negotiate(request: IRequest, context: InvocationContext): Promise<HttpResponseInit> { await Log.setup(context); Log.debug(`>>> Http function processed request for url "${request.url}"`); context.log('************* signalR context.extraInputs', context.extraInputs); const connection = { hubName: 'deafult', // 拼写错误:deafult -> default // "connectionStringSetting": "Endpoint=https://sgr-xxxxx.service.signalr.net;AccessKey=ZYp9h52IfYouCanReadThisYouAreTooClosenweLQDoXMg=;Version=1.0;" connectionStringSetting: 'Endpoint=https://sgr-xxxxx.service.signalr.net;AuthType=azure.msi;Version=1.0;' }; return { status: 200, body: JSON.stringify(connection, null, 2) }; }; const signalRInput = input.generic({ type: 'signalRConnectionInfo', name: 'connectionInfo', hubName: 'default', // "connectionStringSetting": "AzureSignalRConnectionString", // "connectionStringSetting": "Endpoint=https://sgr-xxxxx.service.signalr.net;AccessKey=ZYp9h52IfYouCanReadThisYouAreTooClosenweLQDoXMg=;Version=1.0;" connectionStringSetting: 'Endpoint=https://sgr-xxxxx.service.signalr.net;AuthType=azure.msi;Version=1.0;' }) app.http('negotiate', { methods: ['POST', 'GET'], authLevel: 'anonymous', handler: negotiate, extraInputs: [signalRInput] });
服务端SignalR触发函数示例
import { app, input, output, HttpResponseInit, InvocationContext } from '@azure/functions'; import { Log, IRequest, AuthUtils, AzureADHelper } from '@wetware/viewdu-fxa'; export async function signalRTrigger(request: IRequest, context: InvocationContext): Promise<HttpResponseInit> { await Log.setup(context); Log.debug(`>>> Http function processed request for url "${request.url}"`); context.log('************* signalR context.extraInputs', context.extraInputs); const connection = { hubName: 'default', // connectionStringSetting: 'Endpoint=https://sgr-xxxxx.service.signalr.net;AccessKey=ZYp9h52IfYouCanReadThisYouAreTooClosenweLQDoXMg=;Version=1.0;', connectionStringSetting: 'Endpoint=https://sgr-xxxxx.service.signalr.net;AuthType=azure.msi;Version=1.0;' }; return { status: 200, body: JSON.stringify(connection, null, 2) }; }; // This should provide the signalR upstream target to trigge const signalrInput = input.generic({ type: 'signalRTrigger', name: 'invocation', hubName: 'default', category: 'messages', event: 'SendMessage', parameterNames: [ 'message' ] }); /* "type": "signalRTrigger", "name": "invocation", "hubName": "SignalRTest", "category": "messages", "event": "SendMessage", "parameterNames": [ "message" ], "direction": "in" */ app.http('signalr-trigger', { methods: ['POST', 'GET'], authLevel: 'anonymous', handler: signalRTrigger, extraInputs: [signalrInput] });
问题解答
1. Azure Functions v4编程模型的SignalR绑定语法错误
核心错误点:
- SignalRConnectionInfo绑定配置错误:
connectionStringSetting应填写Function App应用设置的键名,而非直接硬编码连接字符串或MSI格式字符串,硬编码会导致绑定初始化失败触发500错误。 - SignalRTrigger绑定类型错误:SignalRTrigger是触发型绑定,需通过
app.signalR()注册,不能作为HTTP函数的extraInputs配置,当前写法完全不符合绑定逻辑,导致函数无法启动。 - 拼写错误:
negotiate函数中hubName拼写为deafult,与绑定中的default不一致,会导致连接不匹配。
修正后的negotiate函数示例:
import { app, input, HttpResponseInit, InvocationContext } from '@azure/functions'; import { Log, IRequest } from '@wetware/viewdu-fxa'; export async function negotiate(request: IRequest, context: InvocationContext): Promise<HttpResponseInit> { await Log.setup(context); Log.debug(`>>> Http function processed request for url "${request.url}"`); // 从绑定中获取生成的有效连接信息 const connectionInfo = context.extraInputs.get('connectionInfo'); return { status: 200, body: JSON.stringify(connectionInfo, null, 2), headers: { 'Content-Type': 'application/json' } }; }; // 正确配置:connectionStringSetting指向应用设置键名 const signalRInput = input.generic({ type: 'signalRConnectionInfo', name: 'connectionInfo', hubName: 'default', connectionStringSetting: 'AzureSignalRConnectionString' }) app.http('negotiate', { methods: ['POST'], // SignalR客户端默认用POST请求协商 authLevel: 'anonymous', handler: negotiate, extraInputs: [signalRInput] });
修正后的SignalRTrigger函数示例:
import { app, InvocationContext } from '@azure/functions'; import { Log } from '@wetware/viewdu-fxa'; // 使用app.signalR()注册触发函数 app.signalR('signalRTrigger', { hubName: 'default', category: 'messages', event: 'SendMessage', handler: async (context: InvocationContext, invocation: any) => { await Log.setup(context); Log.debug(`Received SignalR message: ${invocation.args[0]}`); // 配置输出绑定广播消息 context.extraOutputs.set('broadcast', { target: 'ReceiveMessage', arguments: [invocation.args[0]] }); }, extraOutputs: [ output.generic({ type: 'signalR', name: 'broadcast', hubName: 'default', connectionStringSetting: 'AzureSignalRConnectionString' }) ] });
2. 解决传输兼容错误(Error: None of the transports supported...)
错误原因:
硬编码返回的{hubName, connectionStringSetting}不是Azure SignalR客户端期望的有效格式,客户端需要包含url和accessToken的响应,错误格式会导致客户端无法获取正确的连接端点和令牌,进而无法协商兼容的传输方式。
解决方案:
- 使用SignalRConnectionInfo绑定生成有效响应:如上述修正后的
negotiate函数,通过绑定自动生成包含url和accessToken的正确响应,而非手动构造内容。 - 修正客户端配置:确保
withUrl传入的是negotiate函数的完整路径(例如${functionAppUrl}/negotiate),而非根路径;若使用MSI认证,需确保Function App已被授予Azure SignalR的SignalR App Server角色权限。 - 检查CORS配置:确保Function App的CORS允许前端域名访问,避免跨域拦截请求。
- 启用详细日志:客户端开启
LogLevel.Trace,查看协商过程中的详细错误,确认是传输方式被拒绝还是令牌无效。
内容的提问来源于stack exchange,提问作者Aiden Dipple
相关产品推荐
相关产品推荐

