背景

程序在发布部署时候,设置环境aspnetcore_urls不生效,也没在代码里使用useurls("xxxx"),启动一直是http://localhost:5000.最后测试发现只有在appsettings.json中配置urls才生效,网上找了半天资料也没看到有什么问题。

最终翻看源代码,发现是在startup中的configure替换了全局iconfiguration导致。

平时开发大体知道程序启动时候端口启用顺序是
useurls("xxx")> 环境变量 > 默认,具体是怎么确定使用哪个配置的,没找到资料,所有才有了本文。

启动地址配置的几种方式介绍
  1. 环境变量aspnetcore_urls
#windows 
set aspnetcore_urls=http://localhost:6000
#linux 
export aspnetcore_urls=http://localhost:6000
  1. useurls("http://localhost:6000")
  2. appsettings.json新增urls或者server.urls配置
{
    "urls":"http://localhost:6000;http://localhost:6001",
    "server.urls":"http://localhost:6000;http://localhost:6001"
}
  1. 使用系统默认
说明

程序启动过程中,一个配置key会重复使用,先放这里

//webhostdefaults.serverurlskey如下
public static readonly string serverurlskey = "urls";
web项目启动地址配置说明

今天是介绍启动方式,所以web启动流程不是重点。直接进入正题。

web启动最终是调用webhost.startasync,源代码在这webhost。其中有个方法ensureserver来获取启动地址

private static readonly string deprecatedserverurlskey = "server.urls";

//省略
var urls = _config[webhostdefaults.serverurlskey] ?? _config[deprecatedserverurlskey];

是从全局iconfigration实例中获取启动地址。所以我的遇到问题这里就解决了。但环境变量useurls是如何解析并记载进来的呢?下面就开今天讲解。

环境变量配置详解

一般web程序启动代码如下:

host.createdefaultbuilder(args)
   .configurewebhostdefaults(webbuilder =>
   {
       webbuilder.usestartup<startup>();
   }).build().run();

其中configurewebhostdefaults的会用调用扩展方法configurewebhost

public static ihostbuilder configurewebhostdefaults(this ihostbuilder builder, action<iwebhostbuilder> configure)
 {
     return builder.configurewebhost(webhostbuilder =>
     {
         webhost.configurewebdefaults(webhostbuilder);

         configure(webhostbuilder);
     });
 }

以上代码都是定义在microsoft.extensions.hosting中。

继续看configurewebhost代码,这个方法就定义在microsoft.aspnetcore.hosting程序集中了。

public static ihostbuilder configurewebhost(this ihostbuilder builder, action<iwebhostbuilder> configure)
{
    //这里会加载环境变量
    var webhostbuilder = new genericwebhostbuilder(builder);
    //这里会调用useurls等扩展方法
    configure(webhostbuilder);
    builder.configureservices((context, services) => services.addhostedservice<genericwebhostservice>());
    return builder;
}

genericwebhostbuilder 构造函数里有如下代码,用来初始化配置,最终添加到全局
iconfiguration实例中,也就是hosticonfiguration实例。

builder.configureservices((context, services) => services.addhostedservice ());这个是web启动重点,有兴趣的可以看下

//加入环境变量配置
_config = new configurationbuilder()
           .addenvironmentvariables(prefix: "aspnetcore_")
           .build();
//把配置加载到host
_builder.configurehostconfiguration(config =>
{
    config.addconfiguration(_config);

    // we do this super early but still late enough that we can process the configuration
    // wired up by calls to usesetting
    executehostingstartups();
})

addenvironmentvariables环境变量解析最终会使用environmentvariablesconfigurationprovider,有兴趣的可以看下addenvironmentvariables源代码,environmentvariablesconfigurationprovider解析环境的方法如下。

public override void load()
{
    load(environment.getenvironmentvariables());
}

internal void load(idictionary envvariables)
{
    var data = new dictionary<string, string>(stringcomparer.ordinalignorecase);
    //这里是筛选aspnetcore_开头的环境变量
    var filteredenvvariables = envvariables
        .cast<dictionaryentry>()
        .selectmany(azureenvtoappenv)
        .where(entry => ((string)entry.key).startswith(_prefix, stringcomparison.ordinalignorecase));

    foreach (var envvariable in filteredenvvariables)
    {
        //这里会把前缀去掉加到配置里
        var key = ((string)envvariable.key).substring(_prefix.length);
        data[key] = (string)envvariable.value;
    }

    data = data;
}

iconfiguration中的key是不区分大小写的,所有最终的效是在全局iconfiguration中新增一条key为urls的记录。
而如果使用默认host.createdefaultbuilder()appsettings.json中的配置会先加载。
如果在appsettings.json中配置urls的话,环境变量也定义了,就会被环境变量的覆盖掉。

useurls解析

useurls解析最终会调用genericwebhostbuilder中的usesetting

//useurls代码如下
public static iwebhostbuilder useurls(this iwebhostbuilder hostbuilder, params string[] urls)
{
    if (urls == null)
    {
        throw new argumentnullexception(nameof(urls));
    }

    return hostbuilder.usesetting(webhostdefaults.serverurlskey, string.join(serverurlsseparator, urls));
}

//genericwebhostbuilder中的usesetting
public iwebhostbuilder usesetting(string key, string value)
{
    _config[key] = value;
    return this;
}

由于这个方法是在 new genericwebhostbuilder(builder);
之后调用,就是 configure(webhostbuilder);,上面代码也有说明。所以iconfigurationurls如果有值,又会被覆盖掉。所以优先级最高的是useurls()

默认地址

假如以上3种配置都没有,就是地址为空,会使用默认策略。这里是源代码,下面是默认策略使用的地址

 /// <summary>
 /// the endpoint kestrel will bind to if nothing else is specified.
 /// </summary>
 public static readonly string defaultserveraddress = "http://localhost:5000";

 /// <summary>
 /// the endpoint kestrel will bind to if nothing else is specified and a default certificate is available.
 /// </summary>
 public static readonly string defaultserverhttpsaddress = "https://localhost:5001";
结论
  1. 启动端口设置优先级如下:
    useurls("xxxx") > 环境变量 > appsetting.json配置urls>默认地址
  2. 不要随意替换全局的iconfiguration,如果不手动加入环境变量解析的话,会丢失一部分配置数据。

作者:cgyqu
出处:
本站使用「署名 4.0 国际」创作共享协议,转载请在文章明显位置注明作者及出处。