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

Angular18+.NET8部署IIS10遇net::ERR_HTTP2_PROTOCOL_ERROR 200问题求助

Angular 18 + .NET 8 部署AWS IIS环境出现ERR_HTTP2_PROTOCOL_ERROR 200问题排查

部署在AWS环境下IIS 10.0.17763.1(Windows Server 2019 版本1809)的Angular 18 + .NET 8应用,已实现CSP Nonce机制。在Visual Studio 2022的IIS Express中运行正常,但在目标IIS环境返回错误:GET https://[my domain].[my sandbox].com/ net::ERR_HTTP2_PROTOCOL_ERROR 200 (OK),Angular应用完全无法启动加载视图,但Nonce已正确注入index.html。

相关代码实现

CspNonceMiddleware核心代码

public async Task InvokeAsync(HttpContext context)
{
    // Generate the nonce
    string nonce = GenerateNonce();
    context.Items["csp-nonce"] = nonce;

    // Only process HTML responses
    context.Response.OnStarting(() =>
    {
        if (context.Response.ContentType?.Contains("text/html") == true)
        {
            // Remove existing CSP header if present
            if (context.Response.Headers.ContainsKey("Content-Security-Policy"))
            {
                context.Response.Headers.Remove("Content-Security-Policy");
            }

            // Add CSP header with the nonce
            context.Response.Headers.Append("Content-Security-Policy", "default-src 'self'; base-uri 'self'; form-action 'self'; img-src 'self' data:; font-src 'self' data:; connect-src https: http: data: wss: ws:; frame-src https:; script-src-elem 'self' 'nonce-{nonce}'; script-src 'self' 'nonce-{nonce}'; style-src-elem 'self' 'nonce-{nonce}'; style-src 'self' 'nonce-{nonce}';");
        }
        return Task.CompletedTask;
    });

    // Intercept the response to inject the nonce into index.html
    var originalBodyStream = context.Response.Body;
    using (var newBodyStream = new MemoryStream())
    {
        context.Response.Body = newBodyStream;

        // Call the next middleware
        await _next(context);

        // Reset the stream position to read it
        newBodyStream.Seek(0, SeekOrigin.Begin);
        string responseBody = new StreamReader(newBodyStream).ReadToEnd();

        // Replace the placeholder in index.html with the nonce
        responseBody = responseBody.Replace("{nonce}", nonce);

        // Write the updated response back
        context.Response.Body = originalBodyStream;
        await context.Response.WriteAsync(responseBody);
    }
}

private string GenerateNonce()
{
    var bytes = new byte[16];
    RandomNumberGenerator.Fill(bytes); // Generate a secure random nonce
    return Convert.ToBase64String(bytes);
}

main.ts中CSP_NONCE配置

export function getCspNonce(): string | null
{
    const meta = document.querySelector('meta[name="csp-nonce"]');
    return meta ? meta.getAttribute('content') : null;
}

const providers: Array<StaticProvider> = [
    { provide: 'BASE_URL', useFactory: getBaseUrl, deps: [] },
    { provide: 'API_URL', useFactory: getApiUrl, deps: [] },
    { provide: 'COPYRIGHT_FULL', useFactory: getCopyRightFull, deps: [] },
    { provide: 'COPYRIGHT', useValue: legalJson.copyright, deps: [] },
    { provide: 'UI_VERSION', useValue: packageJson.version, deps: [] },
    { provide: CSP_NONCE, useFactory: getCspNonce }
];

console.log(`Checking API server...`);
const apiurl = getApiUrl() + "test" + "?fromUI=true";

fetch(apiurl)
    .then(response => 
    {
        if (!response.ok) 
        {
            // Handle HTTP error responses
         [Checks for servers, edited out for brevity...]
        }
        console.log(`API server is available.`);

        // Continue with normal execution if the response is okay
        platformBrowserDynamic(providers)
            .bootstrapModule(AppModule)
            .catch(err => console.error(err));
    })
    .catch(error => 
    {
        // Handle network errors or errors thrown from the response status checks
        const img = document.getElementsByClassName('logo-img')[0];
        img?.remove();

        const h3 = document.getElementsByClassName('loading-text')[0];
        h3.innerHTML = error.message || "An error occurred while connecting to the API server.";
    });

Startup.Configure方法

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.EnvironmentName.Equals("Development"))
    {
        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();
    }

[Tried app.UseMiddleware here...]

    app.UseHttpsRedirection();

[Tried app.UseMiddleware here...]
    app.UseStaticFiles();

[Tried app.UseMiddleware here...]
    // Turned off browser check on 11/18/2023
    //app.UseSupportedBrowserMiddleware();


    app.UseSpaStaticFiles();

  [Tried app.UseMiddleware here...]
app.UseMiddleware<CspNonceMiddleware>();


    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.EnvironmentName.Equals("Development"))
        {
            spa.Options.StartupTimeout = new TimeSpan(0, 0, 480);
            spa.UseAngularCliServer(npmScript: "start");
        }
    });
}

index.html文件

<!doctype html>
<html lang="en">
<head>

    <!--x out localrefs and un-x server refs as needed to speed up dev.
        Server refs are faster and can be used if not debugging api or auth-->
    <title>MHK MarketProminence DEV</title>
    <base apiref="https://localhost:44378/"
          authref="https://localhost:44322/"
          
          href="/">
    <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
    <meta http-equiv="Pragma" content="no-cache" />
    <meta http-equiv="Expires" content="0" />
    <meta charset="utf-8"/>
    <meta name="viewport" content="width=device-width, initial-scale=1"/>
    <meta name="csp-nonce" content=""/>
    <meta http-equiv="Content-Security-Policy" content="default-src 'self' ; base-uri 'self' ; form-action 'self' ; img-src 'self' data:; font-src 'self' data:; connect-src https: http: data: wss: ws:; frame-src https:; script-src-elem 'self' 'nonce-{nonce}' ; script-src 'self' 'nonce-{nonce}' ; style-src-elem 'self' 'nonce-{nonce}' ; style-src 'self' 'nonce-{nonce}' ;" />



    <link rel="icon" type="image/x-icon" href="data:image/x-icon;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAdCAYAAAC9pNwMAAAD0klEQVR42rXWf0iTeRzA8VVqEB2VXD+g4Ojujy76Z7Z/cobtyoPKze5uLjGvWFRz5SKtOEU579nOIGGbbO2HzyzHptP2UG2mzUJz6UCmFDGjgoFBXBIUQlw7gmP2vc8TZ/f03WfpORy8/9r3eV58nt8i0Ry/gi3NX+wSW15B01SPRYv5+y7PugsQklKe5eaiwjKx5Xwa+FfhOqcBGQBF0HO37yk9uFvzXo5vMffy7pCLzt+LcS/3Ch42+xOe/fwwrv5kMK9xRdhcLdz5ziVtbrgn9AU8IadIEH/Gs2o6+KCnl7GUCErrzE7Wqo6ils0AVJaj29GX/OlCvajRhcIXefqD8dOIfCVcHGjGFAhjH40H63GJCrGFyvC+zL7CMenliA/InACbi4cuCQTiLwzG/aG+sygn8uubIFkOmUSty3mRout+F08DVA01QP/4/xD6b/GOriceyKAAAAAElFTkSuQmCC">
    <style type="text/css" nonce="{nonce}">
        body {
            background-color: #e9ebee;
            /*background-color: transparent1;
            background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAABQYAAAKfCAIAAACzD4L/AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvDQ0NDQ0OvlCYkHhoaGhoaGhoaGhoaGnqlNCHx0NDQ0NDQ0NDQ0NDQ0CulCYmHhoaGhoaGhoaGhoaGXilNSDw0NDQ0NDQ0NDQ0NDT0Kum///3/AVhVzNadZFJsAAAAAElFTkSuQmCC');
       
[Edited for brevity...]

        .logo-img {
            width: 128px;
            height: 128px;
        }
    </style>

</head>
<body>
    <app-root ngCspNonce="{nonce}">
        <div id="pre-bootstrap">
            <div class="messaging">
                <img class="mp-login-img" alt="MHK MarketProminence Logo" src="data: image/png; base64, /h0Jb39y87b+4t4ckcGtq1LQX57qhFAqGlMhzVVVVX+XO9jmihl2XS5ExO8P5UDMTZ3AnIUKEOBRQ1GvG8oGnzlQqqXzprFNn4TmXe5kVQyvaa86vWq5UUPkSphjJnwcvMG74H+5tweTLb194HRtVqRVVPgT3E8esn829LLipfefhpIweumnZI0ollQ9hVAZauWh7nHt7yMIwqo/ScHOtQoEZ3K91Jqq8pUJ+/kQllDw+f1WM1J0Zhdh983vJ1bQX4up07CJPpOor5lqycuCgr+P4T7oK92RlO0AAAAAElFTkSuQmCC" />
                <br />
                <br />
                <h3 class="loading-text">
                    Loading, please stand by...
                </h3>
                <br />
                <img class="logo-img"
                     alt="loading..."
                     src="data:image/gif;base64,R0lGODlhMAEsAQAAACH/C05FVFNDQVBFMi4wAwEAAAAh+QQBAAAZACwAAAAAMAEsAYQAAACtos+vpc+upc2upM+wptGvpM+qn8qro8uto8+upc+vpdCfj7+vpNCso8+nn8etpM+OcaqLdLmHgLiGebmAgLiLfruAQICOfbUAAAAAAAAAAAAAAAAAAAAAAAAAAAAF/2AmjmRpnmiqrmzrvnAsz3Rt33iu7FDYCJZvB0TonxGX/9TE9d0KjhXAtJWHoyU9AlkxheYhdaFU4hKLPY5J4lJc+U2JZRZCXx3/xoW6WVFx05XwEpII0C0Iu9VR2ZDZs9l9mQ6LLUJ1k6TTf6IR4u3WR3cmZRXVAWcihBamFDUdd58twQTqnCASmWEl1AAAA7" />
            </div>
        </div>
    </app-root>
</body>
</html>

解决方案与排查建议

1. 修复CSP Header中的Nonce占位符替换问题

当前CspNonceMiddleware添加CSP Header时,'nonce-{nonce}'是未替换的字符串字面量,导致浏览器收到的CSP规则无效,触发资源拦截进而引发协议错误。修改代码:

// 替换原Header赋值代码
string cspPolicy = $"default-src 'self'; base-uri 'self'; form-action 'self'; img-src 'self' data:; font-src 'self' data:; connect-src https: http: data: wss: ws:; frame-src https:; script-src-elem 'self' 'nonce-{nonce}'; script-src 'self' 'nonce-{nonce}'; style-src-elem 'self' 'nonce-{nonce}'; style-src 'self' 'nonce-{nonce}';";
context.Response.Headers.Append("Content-Security-Policy", cspPolicy);

2. 调整中间件注册顺序

将UseMiddleware<CspNonceMiddleware>移至UseStaticFiles和UseSpaStaticFiles之前,确保中间件能优先拦截并修改SPA的index.html响应:

app.UseHttpsRedirection();

// 移至此处
app.UseMiddleware<CspNonceMiddleware>();

app.UseStaticFiles();
app.UseSpaStaticFiles();

3. 验证响应内容长度处理

中间件修改响应体后未更新Content-Length头,可能导致HTTP/2协议解析错误。添加代码更新长度或移除该头:

// 在WriteAsync后添加更新长度
context.Response.ContentLength = Encoding.UTF8.GetByteCount(responseBody);
// 或移除头让IIS自动处理分块传输
context.Response.Headers.Remove("Content-Length");

4. 排查IIS HTTP/2兼容性

HTTP/2与响应流修改可能存在兼容性问题,可临时禁用HTTP/2测试:

  1. 打开IIS管理器,选择站点 -> 功能视图 -> 绑定
  2. 选择HTTPS绑定,点击"编辑"
  3. 取消勾选"启用HTTP/2"
    若禁用后问题消失,需排查AWS负载均衡器或IIS的HTTP/2配置冲突。

5. 移除重复CSP配置

index.html中同时存在meta标签和HTTP头的CSP规则,浏览器会合并取更严格规则,建议移除index.html中的meta标签CSP,仅保留HTTP头方式。

6. 检查AWS环境拦截规则

确认AWS WAF、CloudFront等服务未修改或拦截响应头/内容,同时验证SSL证书配置完整,无证书链问题导致的HTTP/2握手失败。

内容的提问来源于stack exchange,提问作者JackDogStar

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.16 11:03:11