我的 Cors 预检选项请求似乎很慢

Posted

技术标签:

【中文标题】我的 Cors 预检选项请求似乎很慢【英文标题】:My Cors Preflight Options Request Seems Slow 【发布时间】:2019-10-13 10:10:48 【问题描述】:

我注意到“cors”检查花费的时间比我预期的要长。这在 localhost、qa 和生产中以不同的速度发生。

我正在使用 axios (^0.18.0),它使用 mobx/MST/reactjs 和 asp.net core api 2.2

我可以有从 20 毫秒到 10 秒不等的预检选项,它会随机变化。

比如我有

https://localhost:44391/api/Countries

这是一个获取请求,它可能需要 20 毫秒连续 9 次(我 ctrl + F5),但在第 10 次它决定需要几秒钟(我在 localhost 上没有真正得到几秒钟,但有时是一秒钟) .

所以这个测试,204(cors 请求)需要 215 毫秒,而带回数据的实际请求需要一半时间。这似乎倒退了。

这是我的 ajax 请求

 const axiosInstance = axios.create(
      baseURL: 'https://localhost:44391/api/Countries',
      timeout: 120000,
      headers: 
        contentType: 'application/json',
      )

      axiosInstance.get();

这是我的启动。我将 cors 全部打开,并希望在解决此问题后对其进行改进。

public class Startup
    
        public IHostingEnvironment HostingEnvironment  get; 
        private readonly ILogger<Startup> logger;
        public Startup(IConfiguration configuration, IHostingEnvironment env, ILogger<Startup> logger)
        
            Configuration = configuration;
            HostingEnvironment = env;
            this.logger = logger;
        

        public IConfiguration Configuration  get; 

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        
            services.AddCors();

            services.AddDbContext<ApplicationDbContext>(options =>
            
                options.UseLazyLoadingProxies();
                options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"));

            );

            services.AddIdentity<Employee, IdentityRole>(opts =>
            
                opts.Password.RequireDigit = false;
                opts.Password.RequireLowercase = false;
                opts.Password.RequireUppercase = false;
                opts.Password.RequireNonAlphanumeric = false;
                opts.Password.RequiredLength = 4;
                opts.User.RequireUniqueEmail = true;

            ).AddEntityFrameworkStores<ApplicationDbContext>().AddDefaultTokenProviders();

            services.AddAuthentication(opts =>
            
                opts.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
                opts.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                opts.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
            ).AddJwtBearer(cfg =>
            
                cfg.RequireHttpsMetadata = false;
                cfg.SaveToken = true;
                cfg.TokenValidationParameters = new TokenValidationParameters()
                
                    // standard configuration
                    ValidIssuer = Configuration["Auth:Jwt:Issuer"],
                    ValidAudience = Configuration["Auth:Jwt:Audience"],
                    IssuerSigningKey = new SymmetricSecurityKey(
                    Encoding.UTF8.GetBytes(Configuration["Auth:Jwt:Key"])),
                    ClockSkew = TimeSpan.Zero,

                    // security switches
                    RequireExpirationTime = true,
                    ValidateIssuer = true,
                    ValidateIssuerSigningKey = true,
                    ValidateAudience = true
                ;
            );

            services.AddAuthorization(options =>
            
                options.AddPolicy("CanManageCompany", policyBuilder =>
                
                    policyBuilder.RequireRole(DbSeeder.CompanyAdminRole, DbSeeder.SlAdminRole);
                );

                options.AddPolicy("CanViewInventory", policyBuilder =>
                
                    policyBuilder.RequireRole(DbSeeder.CompanyAdminRole, DbSeeder.SlAdminRole, DbSeeder.GeneralUserRole);
                );

                options.AddPolicy("AdminArea", policyBuilder =>
                
                    policyBuilder.RequireRole(DbSeeder.SlAdminRole);
                );
            );


            // do di injection about 30 of these here
            services.AddTransient<IService, MyService>();


            services.AddSingleton(HostingEnvironment);

            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);


            services.AddTransient<IValidator<CompanyDto>, CompanyDtoValidator> ();
            services.AddTransient<IValidator<BranchDto>, BranchDtoValidator>();
            services.AddTransient<IValidator<RegistrationDto>, RegistrationDtoValidator>();

            JsonConvert.DefaultSettings = () => 
                return new JsonSerializerSettings()
                
                    NullValueHandling = NullValueHandling.Ignore,
                    MissingMemberHandling = MissingMemberHandling.Ignore,
                    ContractResolver = new CamelCasePropertyNamesContractResolver()
                ;
            ;

        

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        
            if (env.IsDevelopment())
            
                app.UseDeveloperExceptionPage();
            
            else
            
                app.UseHsts();
            

            //TODO: Change this.
            app.UseCors(builder => builder
                .AllowAnyOrigin()
                .AllowAnyMethod()
                .AllowAnyHeader()
                .AllowCredentials());


            app.UseHttpsRedirection();
            app.UseAuthentication();
            app.UseMvc();

        
    

我不知道这是否是一个有效的测试,但它确实模仿了我在随机时间在质量保证/生产中看到的情况。

我把 axios 请求改成...

 const axiosInstance = axios.create(
      baseURL: 'https://localhost:44391/api/Countries/get',
      timeout: 120000,
      headers: 
        contentType: 'application/json',
      )

      axiosInstance.get();

基本上我把/get,这会导致404

然而,当我用完全相同的场景刷新页面时,它又在几毫秒内完成(尽管仍然比 404 慢)

编辑

我创建了一个与我的真实网站几乎相同的托管网站。唯一的区别是这个只使用http而不是https。

http://52.183.76.195:82/

它没有我的真实站点那么慢,但是现在的预检可能需要 40 毫秒,而真正的请求需要 50 毫秒。

我正在最新版本的 chrome 中对其进行测试,您必须加载网络选项卡并加载/单击按钮(不显示视觉输出)。

【问题讨论】:

【参考方案1】:

我不确定您使用的是负载均衡器还是其他代理服务,但一种快速解决方法是将 CORS 响应标头移动到负载均衡器级别。这将最大限度地减少您的应用在任何给定时间可能增加的开销。如何将 nginx 设置为 cors 代理服务可以参考How to enable CORS in Nginx proxy server?

【讨论】:

不,我没有使用任何负载均衡器,我确实尝试了代理服务,但它似乎没有做任何事情。虽然平衡器会有所帮助,但现在没有人在使用该网站,但我和我正在使用它,所以我不确定为什么负载已经如此之大。 我知道目前没有人在使用该站点,但我认为要创建一个面向未来的架构,尽早将其添加为一个层并不是一个坏主意。大多数云提供商允许您只需单击一个按钮即可添加它。但是,对于您所说的本地开发,代理可能更容易。我在 nginx 代理服务上取得了成功。像enable-cors.org/server_nginx.html 这样的东西本质上是这样。您从自己的应用程序中禁用 CORS,并使代理服务/负载均衡器返回所需的标头。 什么是ngix代理服务?我不太确定我在你的链接中看到了什么。 对不起。你是对的,那个链接没有给出完整的图片。本质上,您可以在本地启动一个 nginx 服务器,并使其作为代理运行。这样当它将传入请求转发到您的应用程序时。在返回响应时,它可以添加您寻找的 CORS 标头。 ***.com/a/45994114/1588156 好像提供了一个更完整的例子。您可以根据需要更改所有标题。由于 CORS 请求将由 nginx 处理,因此开销最低,因此您需要更快且一致的性能。 好的,我会更多地研究这个代理服务器,认为他指向的链接似乎是针对我使用 iis 的 node.js 等(尽管我确实在第一次看到了一些关于 iis 7 的信息关联)。这会和cors-anywhere.herokuapp.com 一样吗?【参考方案2】:

最近我尝试使用 Azure Front Door 作为负载均衡器来解决这个 CORS 问题。 你可以在这里阅读:

https://www.devcompost.com/post/using-azure-front-door-for-eliminating-preflight-calls-cors

虽然我使用了 Azure Front Door,但您可以使用任何可以重新用作入口控制器(如 ng-inx)的负载均衡器来解决相同的问题。

基本前提是我正在对同一域下的 UI 托管域和 API 进行负载平衡,从而诱使浏览器认为它是同源调用。因此不再发出 OPTIONS 请求。

【讨论】:

以上是关于我的 Cors 预检选项请求似乎很慢的主要内容,如果未能解决你的问题,请参考以下文章

Rails 在 CORS 预检选项请求中以 404 响应

Spring Data REST CORS - 如何处理预检选项请求?

401 未经授权响应 CORS 预检选项请求

Cors - 如何处理需要自定义标头的预检选项请求? (AWS:使用 vpc 端点的私有 API 网关)

过滤掉 chrome 开发工具中的预检/选项请求

由于 CORS 选项预检,ReactJS 无法发送 POST 请求