我在使用@aspnet/signalr-client 时遇到了问题,我才刚刚开始,我不明白在哪里
SignalR connection error: Error: WebSocket failed to connect 来自。在服务器上找不到连接,终结点可能不是 SignalR 终结点,服务器上不存在连接 ID,或者存在阻止 WebSockets 的代理。如果您有多个服务器,请检查是否启用了粘性会话。
所以我想在每次令牌信号器过期时刷新页面。这是我在 Azure Function App 中的设置。 那是我协商的 function.json 文件
{
"disabled": false,
"bindings": [
{
"authLevel": "anonymous",
"type": "httpTrigger",
"direction": "in",
"name": "req",
"methods": ["get"]
},
{
"type": "http",
"direction": "out",
"name": "res"
},
{
"type": "signalRConnectionInfo",
"name": "connectionInfo",
"userId": "",
"hubName": "measurements",
"direction": "in"
}
]
}
这里是 negociate index.js 文件
module.exports = async function (context, req, connectionInfo) {
context.res.body = connectionInfo;
};
我的 local.settings.json 文件
{
"IsEncrypted": false,
"Values": {
"AzureWebJobsStorage": "DefaultEndpointsProtocol=https;AccountName=mcdashboardfunctionsprd;AccountKey=????",
"AzureWebJobsCosmosDBConnectionString": "AccountEndpoint=https://prd-cosmos-db-account.documents.azure.com:443/;AccountKey=???",
"AzureSignalRConnectionString": "Endpoint=https://signalr-dashboard-prd.service.signalr.net;AccessKey=?????;Version=1.0;",
"FUNCTIONS_WORKER_RUNTIME": "node"
},
"Host": {
"LocalHttpPort": 7071,
"CORS": "*"
}
}
我决定用??替换任何键。
这是我的环境变量:
// The list of file replacements can be found in `angular.json`.
export const environment = {
authentication: {
stsServerUrl: 'https://dev-sf-ztc2f.us.auth0.com',
clientId: 'GoPIRYXScZWIMLxqfVuj2p4OwhSabh0l',
},
bff: {
serverUrl: 'http://localhost:7071',
basePath: '/api',
},
googleAnalyticsTagId: `G-TLMFNVQ6XY`,
instrumentationKey: '',
};
/*
* For easier debugging in development mode, you can import the following file
* to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`.
*
* This import should be commented out in production mode because it will have a negative impact
* on performance if an error is thrown.
*/
// import 'zone.js/plugins/zone-error'; // Included with Angular CLI.
使用 AngularJS 的客户端,我创建了一个服务文件:signalr.service.ts。那是我的服务等级:
import * as signalR from '@microsoft/signalr';
import { BehaviorSubject, Observable, from, of } from 'rxjs';
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { delay, map, retryWhen, switchMap, take } from 'rxjs/operators';
import { environment } from '@env/environment';
@Injectable()
export class SignalRService {
private hubConnection: signalR.HubConnection;
private connectionAttempts = 0;
private negociateUrl!: string;
connection!: signalR.HubConnection;
hubMessage: BehaviorSubject<string>;
constructor(private http: HttpClient) {
this.negociateUrl = `${environment.bff.serverUrl}${environment.bff.basePath}/negociate`;
this.hubMessage = new BehaviorSubject<string>(null);
}
private setSignalrClientMethods(): void {
this.connection.on('DisplayMessage', (message: string) => {
this.hubMessage.next(message);
});
}
public async initiateSignalrConnection(): Promise<void> {
try {
this.connection = this.hubConnectionBuilder(
this.negociateUrl,
{
skipNegotiation: true,
transport: signalR.HttpTransportType.WebSockets,
}
);
await this.connection.start();
this.setSignalrClientMethods();
console.log(
`SignalR connection success! connectionId: ${this.connection.connectionId}`
);
} catch (error) {
console.log(`SignalR connection error: ${error}`);
}
}
private hubConnectionBuilder(url: string, options: any = null) {
return new signalR.HubConnectionBuilder()
.withUrl(url, options)
.withAutomaticReconnect({
nextRetryDelayInMilliseconds: (retryContext) => {
if (retryContext.elapsedMilliseconds < 6000) {
// If we've been reconnecting for less than 60 seconds so far,
// wait between 0 and 10 seconds before the next reconnect attempt.
return Math.random() * 1000;
} else {
// If we've been reconnecting for more than 60 seconds so far, stop reconnecting.
return null;
}
},
})
.configureLogging(
environment.production ? signalR.LogLevel.None : signalR.LogLevel.Debug
)
.build();
}
public hubReconnection() {
this.connection = this.hubConnectionBuilder(this.negociateUrl);
if (this.connection.state === signalR.HubConnectionState.Disconnected) {
console.log(`Connection lost due to error. Reconnecting.....`);
this.connection.onreconnecting((error: any) => {
console.log(`Connection lost due to error "${error}". Reconnecting.`);
});
this.connection.onreconnected((connectionId: any) => {
console.log(
`Connection reestablished. Connected with connectionId "${connectionId}".`
);
});
} else {
console.log(this.connection.state);
}
}
public getHubConnection = (): Observable<any> => {
this.connectionAttempts++;
if (
(this.hubConnection !== undefined && this.isConnected()) ||
this.connectionAttempts > 1
) {
return of(true);
}
// TODO save token in local storage and handle token expiration
// const accessToken = localStorage.getItem('socketsToken')
// const url = localStorage.getItem('socketsUrl')
// if(accessToken && url && !this.isConnected()) {
// console.log('Using local storage')
// return this.startConnection(url, accessToken)
// }
return this.getAccessToken();
};
public isConnected() {
if (this.hubConnection) console.log('state', this.hubConnection.state);
return this.hubConnection && this.hubConnection.state !== 'Disconnected';
}
public getAccessToken() {
if (this.isConnected()) {
return of(true);
}
return this.http
.post(
`${environment.bff.serverUrl}${environment.bff.basePath}/negociate`,
{}
)
.pipe(
map((resp) => {
return this.startConnection(resp['url'], resp['accessToken']);
})
);
}
public startConnection(url, accessToken) {
const options = {
accessTokenFactory: () => accessToken,
};
this.hubConnection = this.hubConnectionBuilder(url, options);
this.hubConnection.serverTimeoutInMilliseconds = 1000 * 60 * 10;
this.hubConnection.keepAliveIntervalInMilliseconds = 1000 * 60 * 10;
return from(this.hubConnection.start());
}
public getRealtimeData(eventName): Observable<any> {
return this.getHubConnection().pipe(
switchMap(() => {
return this.onEvent(eventName).pipe(
retryWhen((errors) => errors.pipe(delay(1000), take(10)))
);
})
);
}
public onEvent(eventName): Observable<any> {
const that = this;
return new Observable((observer) => {
this.hubConnection.on(eventName, (data) => {
return observer.next({
name: data.process_time,
value: data.sensor_value,
});
});
// Function executed when the unsubscribe is called
return function () {
if (that.hubConnection) that.hubConnection.off(eventName);
};
});
}
public closeConnection() {
this.hubConnection.stop().then(() => {
console.log('connection closed');
});
}
}
然后我在我的 home.component.ts 导入我的服务
import { Component, OnInit, ViewEncapsulation } from '@angular/core';
import { Observable, tap } from 'rxjs';
import { SignalRService } from '@app/core/signalr/signalr.service';
import { ClockService } from './clock.service';
@Component({
selector: 'app-home',
templateUrl: './home.component.html',
styleUrls: ['./home.component.scss'],
encapsulation: ViewEncapsulation.None,
})
export class HomeComponent implements OnInit {
dayIcon = 'assets/images/icon-day.svg';
nightIcon = 'assets/images/icon-night.svg';
themeIcon = this.dayIcon;
clock: Observable<Date>;
measurementCollection = 'real';
hubMessage: string;
constructor(
private clockService: ClockService,
private signalrService: SignalRService
) {
this.hubMessage = '';
}
ngOnInit(): void {
this.setClock(this.measurementCollection);
this.signalrService.getHubConnection().pipe(
tap((value: any) => console.log(value))
)
this.signalrService
.initiateSignalrConnection()
.then((reason: any) => {
console.log(reason);
})
.catch((err) => {
console.error(err);
});
this.signalrService.hubReconnection()
}
toggleTheme() {
const bodyEl = document.getElementsByTagName('body')[0];
bodyEl.classList.toggle('light-theme');
this.themeIcon =
this.themeIcon === this.dayIcon ? this.nightIcon : this.dayIcon;
}
setClock(collection) {
this.clock = this.clockService.getClock(collection);
}
toggleSimulated() {
this.measurementCollection =
this.measurementCollection === 'real' ? 'simulated' : 'real';
this.setClock(this.measurementCollection);
}
}
我真的在这个问题上卡了两个星期,希望能在这里找到答案。这是服务器和客户端的启动。
您传递给
signalR.HubConnectionBuilder().withUrl()
的 URL 应该是您的中心的基本 URL,因此没有 negotiate
部分。
目前,看起来 SDK 正在直接连接到协商端点,而不是连接到 Azure SignalR 服务,可能是因为它发出的
/negotiate
调用实际上是去 /negociate/negotiate
而失败了。