You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Angular14基于WebSocket+Ngrok集成SignalR遇TypeError: Failed to fetch错误

Angular SignalR 连接失败问题:TypeError: Failed to fetch

问题背景

我需要开发一个服务,用来展示后端(.Net开发)任务完成后推送的消息。后端定义了3种消息类型,要根据完成的功能展示对应消息。但在Angular应用中实现SignalR服务时遇到了以下错误:

[2024-03-12T09:38:41.384Z] Warning: Error from HTTP request. TypeError: Failed to fetch.

我的代码实现

SignalR服务代码

import { Injectable } from '@angular/core';
import {
    HubConnectionBuilder,
    HubConnection,
    LogLevel,
} from '@microsoft/signalr';
import { Subject } from 'rxjs';
import { IPonudaKreiranaNotifikacijaDto } from 'app/models/IPonudaKreiranaNotifikacijaDto';
import { environment } from 'environments/environment';
import { IIzvrsenaAkcijaNotifikacijaDto } from 'app/models/IIzvrsenaAkcijaNotifikacijaDto';
import { IZahtjevKreiranPorukaDto } from 'app/models/IZahtjevKreiranPorukaDto';

@Injectable({
    providedIn: 'root',
})
export class SignalRService {
    private connection: HubConnection;
    public ponudaKreirana$ = new Subject<IPonudaKreiranaNotifikacijaDto>();
    public izvrsenaAkcija$ = new Subject<IIzvrsenaAkcijaNotifikacijaDto>();
    public kreiranZahtjev$ = new Subject<IZahtjevKreiranPorukaDto>();

    constructor() {}

    startConnection() {
        console.log('start connection');
        const signalRUrl = `${environment.APS_API_URL}messageHub`;
        this.connection = new HubConnectionBuilder()
            .withUrl(signalRUrl)
            .build();

        this.connection
            .start()
            .then(() => {
                console.log('SignalR connection started');
                this.registerEventHandlers();
            })
            .catch((err) =>
                console.error('Error while starting SignalR connection: ', err),
            );
    }

    private registerEventHandlers() {
        this.connection.on(
            'PonudaKreirana',
            (operaterIme: string, poruka: IPonudaKreiranaNotifikacijaDto) => {
                console.log(
                    'Received PonudaKreirana message:',
                    operaterIme,
                    poruka,
                );
                this.ponudaKreirana$.next(poruka);
            },
        );

        this.connection.on(
            'IzvrsenAkcija',
            (operaterIme: string, poruka: IIzvrsenaAkcijaNotifikacijaDto) => {
                console.log(
                    'Received IzvrsenAkcija message:',
                    operaterIme,
                    poruka,
                );
                this.izvrsenaAkcija$.next(poruka);
            },
        );

        this.connection.on(
            'KreiranZahtjev',
            (operaterIme: string, poruka: IZahtjevKreiranPorukaDto) => {
                console.log(
                    'Received KreiranZahtjev message:',
                    operaterIme,
                    poruka,
                );
                this.kreiranZahtjev$.next(poruka);
            },
        );
    }

    stopConnection() {
        this.connection
            .stop()
            .then(() => console.log('SignalR connection stopped'))
            .catch((err) =>
                console.error('Error while stopping SignalR connection: ', err),
            );
    }
}

AppComponent代码

import { Component } from '@angular/core';
import { Subscription } from 'rxjs';
import { SignalRService } from './services/signalr.service';
import { IPonudaKreiranaNotifikacijaDto } from './models/IPonudaKreiranaNotifikacijaDto';
import { IIzvrsenaAkcijaNotifikacijaDto } from './models/IIzvrsenaAkcijaNotifikacijaDto';
import { IZahtjevKreiranPorukaDto } from './models/IZahtjevKreiranPorukaDto';
import { ToastrService } from 'ngx-toastr';

@Component({
    selector: 'app-root',
    template: ` <router-outlet></router-outlet> `,
})
export class AppComponent {
    public title = $localize`Učitavam Smart Aps sistem...`;
    public ponudaKreiranaSubscription: Subscription;
    public izvrsenaAkcijaSubscription: Subscription;
    public kreiranZahtjevSubscription: Subscription;

    constructor(private signalRService: SignalRService, private toastr: ToastrService) {}

    ngOnInit() {
        this.signalRService.startConnection();

        this.ponudaKreiranaSubscription = this.signalRService.ponudaKreirana$.subscribe(
            (poruka: IPonudaKreiranaNotifikacijaDto) => {
                this.toastr.success(`Primljena poruka: ${poruka}`);
            },
        );

        this.izvrsenaAkcijaSubscription = this.signalRService.izvrsenaAkcija$.subscribe(
            (poruka: IIzvrsenaAkcijaNotifikacijaDto) => {
                this.toastr.success(`Primljena poruka: ${poruka}`);
            },
        );

        this.kreiranZahtjevSubscription = this.signalRService.kreiranZahtjev$.subscribe(
            (poruka: IZahtjevKreiranPorukaDto) => {
                this.toastr.success(`Primljena poruka: ${poruka}`);
            },
        );
    }

    ngOnDestroy() {
        this.signalRService.stopConnection();
        this.ponudaKreiranaSubscription.unsubscribe();
        this.izvrsenaAkcijaSubscription.unsubscribe();
        this.kreiranZahtjevSubscription.unsubscribe();
    }
}

需求

我已经参照Angular官方文档及相关技术资料实现,但仍未解决问题,期望SignalR服务能正常建立连接,AppComponent可成功订阅服务事件。


解决方案
  • 检查SignalR端点URL正确性
    确认environment.APS_API_URL是否包含正确的协议(http/https)、域名和端口,且末尾是否带有斜杠。比如如果API地址是http://localhost:5000/,拼接后的地址应为http://localhost:5000/messageHub,若API地址末尾无斜杠,会变成无效地址http://localhost:5000messageHub。直接在浏览器访问http://your-api-url/messageHub/negotiate,若返回404或错误,说明后端Hub配置有误。

  • 跨域(CORS)配置检查
    Angular前端和.Net后端通常跨端口运行,需确保后端正确配置CORS:

    • 在.Net后端Program.cs中添加允许前端源的CORS策略,并开启AllowCredentials(SignalR必需):
      builder.Services.AddCors(options =>
      {
          options.AddPolicy("AllowAngularApp",
              policy =>
              {
                  policy.WithOrigins("http://localhost:4200") // 替换为你的Angular地址
                        .AllowAnyHeader()
                        .AllowAnyMethod()
                        .AllowCredentials();
              });
      });
      
      app.UseCors("AllowAngularApp"); // 放在app.UseRouting()之后
      
    • 确认SignalR Hub路由配置正确:app.MapHub<MessageHub>("/messageHub");
  • 浏览器网络请求排查
    打开浏览器开发者工具(F12),切换到Network标签,过滤Fetch/XHR请求,查看SignalR的negotiate请求:

    • 若状态码403:检查身份验证配置,比如JWT令牌是否正确携带,后端是否允许SignalR Hub匿名访问(未配置权限时)。
    • 若状态码0或CORS错误:重新检查跨域配置。
  • 增强SignalR服务的错误处理和日志
    在SignalR服务中添加调试日志和连接关闭监听,便于定位问题:

    this.connection = new HubConnectionBuilder()
        .withUrl(signalRUrl)
        .configureLogging(LogLevel.Debug)
        .build();
    
    this.connection.onclose(error => {
        console.error('SignalR连接关闭:', error);
        // 可选:自动重连
        setTimeout(() => this.startConnection(), 5000);
    });
    
  • 检查Dto模型兼容性
    确保Angular中的IPonudaKreiranaNotifikacijaDto等模型与.Net后端Dto字段完全匹配(包括大小写,JavaScript大小写敏感),字段不匹配会导致消息解析失败。

  • 身份验证配置(若需)
    若后端SignalR Hub需要身份验证,建立连接时携带令牌:

    this.connection = new HubConnectionBuilder()
        .withUrl(signalRUrl, {
            accessTokenFactory: () => localStorage.getItem('authToken') || ''
        })
        .build();
    

内容的提问来源于stack exchange,提问作者Irfan Bučan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.28 03:15:56