如何使用带有信号器协商的 Azure 函数应用程序刷新角度视图

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

我在使用@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);
  }
}

我真的在这个问题上卡了两个星期,希望能在这里找到答案。这是服务器和客户端的启动。

angular azure-functions signalr
1个回答
0
投票

您传递给

signalR.HubConnectionBuilder().withUrl()
的 URL 应该是您的中心的基本 URL,因此没有
negotiate
部分。

目前,看起来 SDK 正在直接连接到协商端点,而不是连接到 Azure SignalR 服务,可能是因为它发出的

/negotiate
调用实际上是去
/negociate/negotiate
而失败了。

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