nginx 无法从Angular客户端的localhost加载API,C#后端托管在独立单元上

csga3l58  于 2023-04-20  发布在  Nginx
关注(0)|答案(3)|浏览(149)

我正在运行一个带有.Net 6 WebAPI的Angular客户端(v14)。这些都是在Raspberry Pi上运行的单独的.Net项目。它是一个独立的kiosk,因此前端和后端在同一个盒子上运行。
我希望能够通过浏览器从同一网络上的PC访问前端。当我远程到Raspberry Pi的地址时,我可以看到Angular App的加载屏幕,但它无法解析localhost,因为它会将后端视为PC的localhost,而不是Kiosk。
我还希望能够远程访问API来控制单元的功能,这可能是通过Postman或第三方应用程序。但这可能是一个单独的问题,我可以稍后解决。
我需要Angular 的应用程序来重写localhost到当前的IP地址.我一直在努力寻找如何做到这一点的例子,事情已经相当错综复杂的道路上.下面是配置的部分,我想知道是否有人可以指出我在正确的方向或我如何可以使这一工作的一个例子?
launchSettings.json

{
  "profiles": {
    "Kiosk_ClientApp": {
      "commandName": "Project",
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      },
      "applicationUrl": "https://0.0.0.0:4200"
    }
  }
}

nginx

server {
    listen 443 ssl;
    listen [::]:443 ssl;
    include snippets/self-signed.conf;
    include snippets/ssl-params.conf;
    
    server_name kiosk;

    location / {
        proxy_pass https://localhost:4200;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection keep-alive;
        proxy_set_header Host $host;
        proxy_cache_bypass $http_upgrade;
        proxy_cookie_path / "/; SameSite = None' secure";
        proxy_read_timeout 18000s;
        proxy_send_timeout 18000s;
    }

    location /api { 
            proxy_pass https://localhost:4901;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_cache_bypass $http_upgrade;
    }
}

proxy.conf.json

{
  "/api": {
    "target": "https://0.0.0.0:4901",
    "secure": true,
    "changeOrigin": true,
    "logLevel": "debug"
  }
}

environment.ts

export const environment = {
  production: false,
  urlAddress: "https://localhost:4901"
};

environment.prod.ts

export const environment = {
  production: true,
  urlAddress: "https://localhost:4901"
};

angular.json

{
  "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
  "version": 1,
  "newProjectRoot": "projects",
  "projects": {
    "Kiosk": {
      "root": "",
      "sourceRoot": "src",
      "projectType": "application",
      "prefix": "app",
      "schematics": {},
      "architect": {
        "build": {
          "builder": "@angular-devkit/build-angular:browser",
          "options": {
            "progress": false,
            "outputPath": "dist",
            "index": "src/index.html",
            "main": "src/main.ts",
            "polyfills": "src/polyfills.ts",
            "tsConfig": "src/tsconfig.app.json",
            "assets": [
              "src/assets"
            ],
            "styles": [
              "node_modules/bootstrap/dist/css/bootstrap.min.css",
              "src/styles.css",
              "node_modules/sweetalert2/src/sweetalert2.scss",
              "node_modules/datatables.net-bs4/css/dataTables.bootstrap4.min.css",
              "node_modules/datatables.net-dt/css/jquery.dataTables.css"
            ],
            "scripts": [
              "./node_modules/jquery/dist/jquery.min.js",
              "./node_modules/popper.js/dist/umd/popper.min.js",
              "./node_modules/bootstrap/dist/js/bootstrap.min.js",
              "./node_modules/chart.js/dist/Chart.js",
              "node_modules/jquery/dist/jquery.js",
              "node_modules/datatables.net/js/jquery.dataTables.js",
              "node_modules/datatables.net-bs4/js/dataTables.bootstrap4.min.js"
            ],
            "vendorChunk": true,
            "extractLicenses": false,
            "buildOptimizer": false,
            "sourceMap": true,
            "optimization": false,
            "namedChunks": true
          },
          "configurations": {
            "production": {
              "fileReplacements": [
                {
                  "replace": "src/environments/environment.ts",
                  "with": "src/environments/environment.prod.ts"
                }
              ],
              "optimization": true,
              "outputHashing": "all",
              "sourceMap": false,
              "namedChunks": false,
              "extractLicenses": true,
              "vendorChunk": false,
              "buildOptimizer": true,
              "budgets": [
                {
                  "type": "anyComponentStyle",
                  "maximumWarning": "6kb"
                }
              ]
            }
          },
          "defaultConfiguration": ""
        },
        "serve": {
          "builder": "@angular-devkit/build-angular:dev-server",
          "options": {
            "browserTarget": "Kiosk:build",
            "proxyConfig": "src/proxy.conf.json",
            "ssl": true,
            "sslCert": "ssl/server.crt",
            "sslKey": "ssl/server.key"
          },
          "configurations": {
            "production": {
              "browserTarget": "Kiosk:build:production"
            }
          }
        },
        "extract-i18n": {
          "builder": "@angular-devkit/build-angular:extract-i18n",
          "options": {
            "browserTarget": "Kiosk:build"
          }
        },
        "test": {
          "builder": "@angular-devkit/build-angular:karma",
          "options": {
            "main": "src/test.ts",
            "polyfills": "src/polyfills.ts",
            "tsConfig": "src/tsconfig.spec.json",
            "karmaConfig": "src/karma.conf.js",
            "styles": [
              "src/styles.css"
            ],
            "scripts": [],
            "assets": [
              "src/assets"
            ]
          }
        },
        "server": {
          "builder": "@angular-devkit/build-angular:server",
          "options": {
            "outputPath": "dist-server",
            "main": "src/main.ts",
            "tsConfig": "src/tsconfig.server.json",
            "sourceMap": true,
            "optimization": false
          },
          "configurations": {
            "dev": {
              "optimization": true,
              "outputHashing": "all",
              "sourceMap": false,
              "namedChunks": false,
              "extractLicenses": true
            },
            "production": {
              "optimization": true,
              "outputHashing": "all",
              "sourceMap": false,
              "namedChunks": false,
              "extractLicenses": true
            }
          },
          "defaultConfiguration": ""
        }
      }
    },
    "Kiosk-e2e": {
      "root": "e2e/",
      "projectType": "application",
      "architect": {
        "e2e": {
          "builder": "@angular-devkit/build-angular:protractor",
          "options": {
            "protractorConfig": "e2e/protractor.conf.js",
            "devServerTarget": "Kiosk:serve"
          }
        }
      }
    }
  },
  "cli": {
    "analytics": false
  }
}

示例帖子:

saveRecipe(recipe: IRecipe): Observable<number> {
    this.convertRecipeToMetric(recipe);
    return this.http
      .post<number>(this.createCompleteRoute("api/Recipe/AddRecipe", this.envUrl.urlAddress),
        recipe,
        { withCredentials: true }).pipe(map((s: number) => { return s; })
        );
  }

  private createCompleteRoute = (route: string, envAddress: string) => {
    return `${envAddress}/${route}`;
  };

package.json -脚本部分:

"scripts": {
    "ng": "ng",
    "start": "ng serve --proxy-config proxy.config.json",
    "build": "ng build",
    "build:ssr": "ng run Kiosk:server:dev",
    "test": "ng test",
    "lint": "ng lint",
    "e2e": "ng e2e"
  },

C# Startup.cs

public void ConfigureServices(IServiceCollection services)
        {
            services.AddCors(options =>
            {
                options.AddPolicy(
                    "AllowMyOrigins",
                    builder =>
                        builder
                            .AllowCredentials()
                            .WithOrigins("https://localhost:4200")
                            .SetIsOriginAllowed(host => true)
                            .SetIsOriginAllowedToAllowWildcardSubdomains()
                            .AllowAnyHeader()
                            .AllowAnyMethod());
            });

            services.AddControllersWithViews();

            // In production, the Angular files will be served from this directory
            services.AddSpaStaticFiles(configuration => { configuration.RootPath = "ClientApp/dist"; });
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseExceptionHandler("/Error");

                // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
                app.UseHsts();
            }

            app.UseForwardedHeaders(new ForwardedHeadersOptions
            {
                ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto,
            });

            app.UseHttpsRedirection();

            app.UseStaticFiles();
            if (!env.IsDevelopment())
            {
                app.UseSpaStaticFiles();
            }

            app.UseRouting();
            app.UseCors("AllowMyOrigins");
            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllerRoute(
                    "default",
                    "{controller}/{action=Index}/{id?}");
            });

            app.UseSpa(spa =>
            {
                // To learn more about options for serving an Angular SPA from ASP.NET Core,
                // see https://go.microsoft.com/fwlink/?linkid=864501
                spa.Options.SourcePath = "ClientApp";

                if (env.IsDevelopment())
                {
                    spa.UseAngularCliServer("start");
                }
            });
        }

IP地址可以是任何东西,因为会有许多这样的单位,放置在任何地方,所以我需要动态的IP地址,而不是静态的。

4szc88ey

4szc88ey1#

如果你使用nginx作为入口代理,那么你不应该在前端代码中使用绝对路径。
如果您使用相对路径从javascript调用API(即“/api/something”),那么浏览器将访问您用于加载FE的任何服务器(可能是访问nginx代理的服务器URL?)

mrzz3bfm

mrzz3bfm2#

我认为你的问题分析是非常正确的,但有一个例外。你的前端不知道向哪个IP地址发送请求,这需要由你的.NET后端纠正。不像你提到的Angular 应用程序:I need the angular application to rewrite the localhost to the current IP address
由于您的前端由后端提供服务,因此您需要后端首先确定它在哪个IP上可用。这可以按照下面的示例所示完成(此内容的学分转到:https://stackoverflow.com/a/27376368/2761355

public static string GetLocalIPAddress()
{
    string localIP;
    using (Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, 0))
    {
        socket.Connect("8.8.8.8", 65530);
        IPEndPoint endPoint = socket.LocalEndPoint as IPEndPoint;
        localIP = endPoint.Address.ToString();

        return localIP;
    }
}

由于您的IP地址很少更改,因此您可以考虑仅在启动时检索它,因为此解决方案确实依赖于外部源(即Google DNS服务器8.8.8.8)。
剩下的就是你的.NET后端在你的angular文件中设置这个IP地址。

piah890a

piah890a3#

问题似乎是Angular客户端无法向运行在localhost上的单独C#后端上托管的API发出请求。这可能是由于CORS限制,该限制阻止网页向与服务页面的域不同的域发出请求。
要解决此问题,您需要配置C#后端以允许来自Angular客户端域的跨域请求。一种方法是将CORS中间件添加到C#后端。以下是如何使用Microsoft.AspNetCore.Cors包执行此操作的示例:
使用NuGet安装Microsoft.AspNetCore.Cors包。在C#后端的Startup.cs文件中,将以下代码添加到ConfigureServices方法中以配置CORS:

services.AddCors(options =>
{
    options.AddDefaultPolicy(builder =>
    {
        builder.AllowAnyOrigin()
               .AllowAnyMethod()
               .AllowAnyHeader();
    });
});

这将允许任何源向您的API发出请求,并允许任何HTTP方法和头。
在Startup.cs的Configure方法中,添加以下代码以启用CORS:

app.UseCors();

这将为应用程序中的所有端点启用CORS。
通过这些更改,您的C#后端现在应该允许来自Angular客户端域的请求。请注意,您应该谨慎允许任何来源向您的API发出请求,因为这可能会带来安全风险。通常情况下,限制允许向您的API发出请求的来源是一个好主意。

相关问题