Skip to content

Commit 3bec05a

Browse files
committed
Fixing WEBSITE_HOSTNAME issue (#4162)
1 parent 630d8e2 commit 3bec05a

22 files changed

+333
-117
lines changed

src/WebJobs.Script.WebHost/Diagnostics/AzureMonitorDiagnosticLogger.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,30 +18,30 @@ public class AzureMonitorDiagnosticLogger : ILogger
1818
private readonly string _regionName;
1919
private readonly string _category;
2020
private readonly string _hostInstanceId;
21-
private readonly string _websiteHostName;
2221

22+
private readonly HostNameProvider _hostNameProvider;
2323
private readonly IEventGenerator _eventGenerator;
2424
private readonly IEnvironment _environment;
2525
private readonly IExternalScopeProvider _scopeProvider;
2626

27-
public AzureMonitorDiagnosticLogger(string category, string hostInstanceId, IEventGenerator eventGenerator, IEnvironment environment, IExternalScopeProvider scopeProvider)
27+
public AzureMonitorDiagnosticLogger(string category, string hostInstanceId, IEventGenerator eventGenerator, IEnvironment environment, IExternalScopeProvider scopeProvider, HostNameProvider hostNameProvider)
2828
{
2929
_category = category ?? throw new ArgumentNullException(nameof(category));
3030
_hostInstanceId = hostInstanceId ?? throw new ArgumentNullException(nameof(hostInstanceId));
3131
_eventGenerator = eventGenerator ?? throw new ArgumentNullException(nameof(eventGenerator));
3232
_environment = environment ?? throw new ArgumentNullException(nameof(environment));
3333
_scopeProvider = scopeProvider ?? throw new ArgumentNullException(nameof(scopeProvider));
34+
_hostNameProvider = hostNameProvider ?? throw new ArgumentNullException(nameof(hostNameProvider));
3435

3536
_regionName = _environment.GetEnvironmentVariable(EnvironmentSettingNames.RegionName) ?? string.Empty;
36-
_websiteHostName = _environment.GetEnvironmentVariable(EnvironmentSettingNames.AzureWebsiteHostName);
3737
}
3838

3939
public IDisposable BeginScope<TState>(TState state) => _scopeProvider.Push(state);
4040

4141
public bool IsEnabled(LogLevel logLevel)
4242
{
4343
// We want to instantiate this Logger in placeholder mode to warm it up, but do not want to log anything.
44-
return !string.IsNullOrEmpty(_websiteHostName) && !_environment.IsPlaceholderModeEnabled();
44+
return !string.IsNullOrEmpty(_hostNameProvider.Value) && !_environment.IsPlaceholderModeEnabled();
4545
}
4646

4747
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
@@ -82,7 +82,7 @@ public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Except
8282
writer.WriteEndObject();
8383
}
8484

85-
_eventGenerator.LogAzureMonitorDiagnosticLogEvent(logLevel, _websiteHostName, AzureMonitorOperationName, AzureMonitorCategoryName, _regionName, sw.ToString());
85+
_eventGenerator.LogAzureMonitorDiagnosticLogEvent(logLevel, _hostNameProvider.Value, AzureMonitorOperationName, AzureMonitorCategoryName, _regionName, sw.ToString());
8686
}
8787

8888
private static void WritePropertyIfNotNull(JsonTextWriter writer, string propertyName, string propertyValue)

src/WebJobs.Script.WebHost/Diagnostics/AzureMonitorDiagnosticLoggerProvider.cs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,23 +12,25 @@ public class AzureMonitorDiagnosticLoggerProvider : ILoggerProvider, ISupportExt
1212
private readonly string _hostInstanceId;
1313
private readonly IEventGenerator _eventGenerator;
1414
private readonly IEnvironment _environment;
15+
private readonly HostNameProvider _hostNameProvider;
1516
private IExternalScopeProvider _scopeProvider;
1617

17-
public AzureMonitorDiagnosticLoggerProvider(IOptions<ScriptJobHostOptions> scriptOptions, IEventGenerator eventGenerator, IEnvironment environment)
18-
: this(scriptOptions.Value.InstanceId, eventGenerator, environment)
18+
public AzureMonitorDiagnosticLoggerProvider(IOptions<ScriptJobHostOptions> scriptOptions, IEventGenerator eventGenerator, IEnvironment environment, HostNameProvider hostNameProvider)
19+
: this(scriptOptions.Value.InstanceId, eventGenerator, environment, hostNameProvider)
1920
{
2021
}
2122

22-
public AzureMonitorDiagnosticLoggerProvider(string hostInstanceId, IEventGenerator eventGenerator, IEnvironment environment)
23+
public AzureMonitorDiagnosticLoggerProvider(string hostInstanceId, IEventGenerator eventGenerator, IEnvironment environment, HostNameProvider hostNameProvider)
2324
{
2425
_hostInstanceId = hostInstanceId ?? throw new ArgumentNullException(hostInstanceId);
2526
_eventGenerator = eventGenerator ?? throw new ArgumentNullException(nameof(eventGenerator));
2627
_environment = environment ?? throw new ArgumentException(nameof(environment));
28+
_hostNameProvider = hostNameProvider ?? throw new ArgumentException(nameof(hostNameProvider));
2729
}
2830

2931
public ILogger CreateLogger(string categoryName)
3032
{
31-
return new AzureMonitorDiagnosticLogger(categoryName, _hostInstanceId, _eventGenerator, _environment, _scopeProvider);
33+
return new AzureMonitorDiagnosticLogger(categoryName, _hostInstanceId, _eventGenerator, _environment, _scopeProvider, _hostNameProvider);
3234
}
3335

3436
public void Dispose()
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the MIT License. See License.txt in the project root for license information.
3+
4+
using System;
5+
using Microsoft.AspNetCore.Http;
6+
using Microsoft.Extensions.Logging;
7+
8+
namespace Microsoft.Azure.WebJobs.Script.WebHost
9+
{
10+
/// <summary>
11+
/// Provides the current HostName for the Function App.
12+
/// <remarks>
13+
/// The environment value for WEBSITE_HOSTNAME is unreliable and shouldn't be used directly. AppService site swaps change
14+
/// the site’s hostname under the covers, and the worker process is NOT recycled (for performance reasons). That means the
15+
/// site will continue to run with the same hostname environment variable, leading to an incorrect host name.
16+
///
17+
/// WAS_DEFAULT_HOSTNAME is a header injected by front end on every request which provides the correct hostname. We check
18+
/// this header on all http requests, and updated the cached hostname value as needed.
19+
/// </remarks>
20+
/// </summary>
21+
public class HostNameProvider
22+
{
23+
private readonly IEnvironment _environment;
24+
private readonly ILogger _logger;
25+
private string _hostName;
26+
27+
public HostNameProvider(IEnvironment environment, ILogger<HostNameProvider> logger)
28+
{
29+
_environment = environment ?? throw new ArgumentNullException(nameof(environment));
30+
_logger = logger;
31+
}
32+
33+
public virtual string Value
34+
{
35+
get
36+
{
37+
if (string.IsNullOrEmpty(_hostName))
38+
{
39+
// default to the the value specified in environment
40+
_hostName = _environment.GetEnvironmentVariable(EnvironmentSettingNames.AzureWebsiteHostName);
41+
if (string.IsNullOrEmpty(_hostName))
42+
{
43+
// Linux Dedicated on AppService doesn't have WEBSITE_HOSTNAME
44+
string websiteName = _environment.GetEnvironmentVariable(EnvironmentSettingNames.AzureWebsiteName);
45+
if (!string.IsNullOrEmpty(websiteName))
46+
{
47+
_hostName = $"{websiteName}.azurewebsites.net";
48+
}
49+
}
50+
}
51+
return _hostName;
52+
}
53+
}
54+
55+
public virtual void Synchronize(HttpRequest request)
56+
{
57+
string hostNameHeaderValue = request.Headers[ScriptConstants.AntaresDefaultHostNameHeader];
58+
if (!string.IsNullOrEmpty(hostNameHeaderValue) &&
59+
string.Compare(Value, hostNameHeaderValue) != 0)
60+
{
61+
_logger.LogInformation("HostName updated from '{0}' to '{1}'", Value, hostNameHeaderValue);
62+
_hostName = hostNameHeaderValue;
63+
}
64+
}
65+
66+
// for testing only
67+
internal void Reset()
68+
{
69+
_hostName = null;
70+
}
71+
}
72+
}

src/WebJobs.Script.WebHost/Management/FunctionsSyncManager.cs

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,12 @@ public class FunctionsSyncManager : IFunctionsSyncManager, IDisposable
4242
private readonly IHostIdProvider _hostIdProvider;
4343
private readonly IScriptWebHostEnvironment _webHostEnvironment;
4444
private readonly IEnvironment _environment;
45+
private readonly HostNameProvider _hostNameProvider;
4546
private readonly SemaphoreSlim _syncSemaphore = new SemaphoreSlim(1, 1);
4647

4748
private CloudBlockBlob _hashBlob;
4849

49-
public FunctionsSyncManager(IConfiguration configuration, IHostIdProvider hostIdProvider, IOptionsMonitor<ScriptApplicationHostOptions> applicationHostOptions, IOptions<LanguageWorkerOptions> languageWorkerOptions, ILoggerFactory loggerFactory, HttpClient httpClient, ISecretManagerProvider secretManagerProvider, IScriptWebHostEnvironment webHostEnvironment, IEnvironment environment)
50+
public FunctionsSyncManager(IConfiguration configuration, IHostIdProvider hostIdProvider, IOptionsMonitor<ScriptApplicationHostOptions> applicationHostOptions, IOptions<LanguageWorkerOptions> languageWorkerOptions, ILoggerFactory loggerFactory, HttpClient httpClient, ISecretManagerProvider secretManagerProvider, IScriptWebHostEnvironment webHostEnvironment, IEnvironment environment, HostNameProvider hostNameProvider)
5051
{
5152
_applicationHostOptions = applicationHostOptions;
5253
_logger = loggerFactory?.CreateLogger(ScriptConstants.LogCategoryHostGeneral);
@@ -57,6 +58,7 @@ public FunctionsSyncManager(IConfiguration configuration, IHostIdProvider hostId
5758
_hostIdProvider = hostIdProvider;
5859
_webHostEnvironment = webHostEnvironment;
5960
_environment = environment;
61+
_hostNameProvider = hostNameProvider;
6062
}
6163

6264
public async Task<SyncTriggersResult> TrySyncTriggersAsync(bool checkHash = false)
@@ -329,21 +331,16 @@ private async Task<Dictionary<string, string>> ReadDurableTaskConfig()
329331
return config;
330332
}
331333

332-
internal static HttpRequestMessage BuildSetTriggersRequest()
334+
internal HttpRequestMessage BuildSetTriggersRequest()
333335
{
334336
var protocol = "https";
335-
// On private stamps with no ssl certificate use http instead.
336-
if (Environment.GetEnvironmentVariable(EnvironmentSettingNames.SkipSslValidation) == "1")
337+
if (_environment.GetEnvironmentVariable(EnvironmentSettingNames.SkipSslValidation) == "1")
337338
{
339+
// On private stamps with no ssl certificate use http instead.
338340
protocol = "http";
339341
}
340342

341-
var hostname = Environment.GetEnvironmentVariable(EnvironmentSettingNames.AzureWebsiteHostName);
342-
// Linux Dedicated on AppService doesn't have WEBSITE_HOSTNAME
343-
hostname = string.IsNullOrWhiteSpace(hostname)
344-
? $"{Environment.GetEnvironmentVariable(EnvironmentSettingNames.AzureWebsiteName)}.azurewebsites.net"
345-
: hostname;
346-
343+
var hostname = _hostNameProvider.Value;
347344
var url = $"{protocol}://{hostname}/operations/settriggers";
348345

349346
return new HttpRequestMessage(HttpMethod.Post, url);

src/WebJobs.Script.WebHost/Middleware/SystemTraceMiddleware.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,19 @@ internal class SystemTraceMiddleware
1818
{
1919
private readonly ILogger _logger;
2020
private readonly RequestDelegate _next;
21+
private readonly HostNameProvider _hostNameProvider;
2122

22-
public SystemTraceMiddleware(RequestDelegate next, ILogger<SystemTraceMiddleware> logger)
23+
public SystemTraceMiddleware(RequestDelegate next, ILogger<SystemTraceMiddleware> logger, HostNameProvider hostNameProvider)
2324
{
2425
_logger = logger;
2526
_next = next;
27+
_hostNameProvider = hostNameProvider;
2628
}
2729

2830
public async Task Invoke(HttpContext context)
2931
{
3032
SetRequestId(context.Request);
33+
_hostNameProvider.Synchronize(context.Request);
3134

3235
var sw = new Stopwatch();
3336
sw.Start();

src/WebJobs.Script.WebHost/Security/KeyManagement/DefaultSecretManagerProvider.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,11 @@ public sealed class DefaultSecretManagerProvider : ISecretManagerProvider
2121
private readonly IHostIdProvider _hostIdProvider;
2222
private readonly IConfiguration _configuration;
2323
private readonly IEnvironment _environment;
24+
private readonly HostNameProvider _hostNameProvider;
2425
private Lazy<ISecretManager> _secretManagerLazy;
2526

2627
public DefaultSecretManagerProvider(IOptionsMonitor<ScriptApplicationHostOptions> options, IHostIdProvider hostIdProvider,
27-
IConfiguration configuration, IEnvironment environment, ILoggerFactory loggerFactory, IMetricsLogger metricsLogger)
28+
IConfiguration configuration, IEnvironment environment, ILoggerFactory loggerFactory, IMetricsLogger metricsLogger, HostNameProvider hostNameProvider)
2829
{
2930
if (loggerFactory == null)
3031
{
@@ -35,6 +36,7 @@ public DefaultSecretManagerProvider(IOptionsMonitor<ScriptApplicationHostOptions
3536
_hostIdProvider = hostIdProvider ?? throw new ArgumentNullException(nameof(hostIdProvider));
3637
_configuration = configuration ?? throw new ArgumentNullException(nameof(configuration));
3738
_environment = environment ?? throw new ArgumentNullException(nameof(environment));
39+
_hostNameProvider = hostNameProvider ?? throw new ArgumentNullException(nameof(hostNameProvider));
3840

3941
_logger = loggerFactory.CreateLogger(ScriptConstants.LogCategoryHostGeneral);
4042
_metricsLogger = metricsLogger ?? throw new ArgumentNullException(nameof(metricsLogger));
@@ -48,7 +50,7 @@ public DefaultSecretManagerProvider(IOptionsMonitor<ScriptApplicationHostOptions
4850

4951
private void ResetSecretManager() => Interlocked.Exchange(ref _secretManagerLazy, new Lazy<ISecretManager>(Create));
5052

51-
private ISecretManager Create() => new SecretManager(CreateSecretsRepository(), _logger, _metricsLogger);
53+
private ISecretManager Create() => new SecretManager(CreateSecretsRepository(), _logger, _metricsLogger, _hostNameProvider);
5254

5355
internal ISecretsRepository CreateSecretsRepository()
5456
{

src/WebJobs.Script.WebHost/Security/KeyManagement/ScriptSecrets.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ public abstract class ScriptSecrets
1313
{
1414
protected ScriptSecrets()
1515
{
16-
HostName = ScriptSettingsManager.Instance.GetSetting(EnvironmentSettingNames.AzureWebsiteHostName);
1716
InstanceId = ScriptSettingsManager.Instance.AzureWebsiteInstanceId;
1817
Source = ScriptConstants.Runtime;
1918
}

src/WebJobs.Script.WebHost/Security/KeyManagement/SecretManager.cs

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ public class SecretManager : IDisposable, ISecretManager
2525
private readonly IKeyValueConverterFactory _keyValueConverterFactory;
2626
private readonly ILogger _logger;
2727
private readonly ISecretsRepository _repository;
28+
private readonly HostNameProvider _hostNameProvider;
2829
private HostSecretsInfo _hostSecrets;
2930
private SemaphoreSlim _hostSecretsLock = new SemaphoreSlim(1, 1);
3031
private IMetricsLogger _metricsLogger;
@@ -35,24 +36,24 @@ public SecretManager()
3536
{
3637
}
3738

38-
public SecretManager(ISecretsRepository repository, ILogger logger, IMetricsLogger metricsLogger, bool createHostSecretsIfMissing = false)
39-
: this(repository, new DefaultKeyValueConverterFactory(repository.IsEncryptionSupported), logger, metricsLogger, createHostSecretsIfMissing)
39+
public SecretManager(ISecretsRepository repository, ILogger logger, IMetricsLogger metricsLogger, HostNameProvider hostNameProvider, bool createHostSecretsIfMissing = false)
40+
: this(repository, new DefaultKeyValueConverterFactory(repository.IsEncryptionSupported), logger, metricsLogger, hostNameProvider, createHostSecretsIfMissing)
4041
{
4142
}
4243

43-
public SecretManager(ISecretsRepository repository, IKeyValueConverterFactory keyValueConverterFactory, ILogger logger, IMetricsLogger metricsLogger, bool createHostSecretsIfMissing = false)
44+
public SecretManager(ISecretsRepository repository, IKeyValueConverterFactory keyValueConverterFactory, ILogger logger, IMetricsLogger metricsLogger, HostNameProvider hostNameProvider, bool createHostSecretsIfMissing = false)
4445
{
4546
_repository = repository;
4647
_keyValueConverterFactory = keyValueConverterFactory;
4748
_repository.SecretsChanged += OnSecretsChanged;
4849
_logger = logger;
4950
_metricsLogger = metricsLogger ?? throw new ArgumentNullException(nameof(metricsLogger));
5051
_repositoryClassName = _repository.GetType().Name.ToLower();
52+
_hostNameProvider = hostNameProvider;
5153

5254
if (createHostSecretsIfMissing)
5355
{
54-
// The SecretManager implementation of GetHostSecrets will
55-
// create a host secret if one is not present.
56+
// GetHostSecrets will create host secrets if not present
5657
GetHostSecretsAsync().GetAwaiter().GetResult();
5758
}
5859
}
@@ -450,6 +451,11 @@ private Task RefreshSecretsAsync<T>(T secrets, string keyScope = null) where T :
450451

451452
private async Task PersistSecretsAsync<T>(T secrets, string keyScope = null, bool isNonDecryptable = false) where T : ScriptSecrets
452453
{
454+
if (secrets != null)
455+
{
456+
secrets.HostName = _hostNameProvider.Value;
457+
}
458+
453459
ScriptSecretsType secretsType = secrets.SecretsType;
454460
if (isNonDecryptable)
455461
{

src/WebJobs.Script.WebHost/WebHooks/DefaultScriptWebHookProvider.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,15 @@ namespace Microsoft.Azure.WebJobs.Script.WebHost
1717
internal class DefaultScriptWebHookProvider : IScriptWebHookProvider
1818
{
1919
private readonly ISecretManagerProvider _secretManagerProvider;
20+
private readonly HostNameProvider _hostNameProvider;
2021

2122
// Map from an extension name to a http handler.
2223
private IDictionary<string, HttpHandler> _customHttpHandlers = new Dictionary<string, HttpHandler>(StringComparer.OrdinalIgnoreCase);
2324

24-
public DefaultScriptWebHookProvider(ISecretManagerProvider secretManagerProvider)
25+
public DefaultScriptWebHookProvider(ISecretManagerProvider secretManagerProvider, HostNameProvider hostNameProvider)
2526
{
2627
_secretManagerProvider = secretManagerProvider;
28+
_hostNameProvider = hostNameProvider;
2729
}
2830

2931
public bool TryGetHandler(string name, out HttpHandler handler)
@@ -53,7 +55,7 @@ public Uri GetUrl(IExtensionConfigProvider extension)
5355
private Uri GetExtensionWebHookRoute(string extensionName)
5456
{
5557
var settings = ScriptSettingsManager.Instance;
56-
var hostName = settings.GetSetting(EnvironmentSettingNames.AzureWebsiteHostName);
58+
var hostName = _hostNameProvider.Value;
5759
if (hostName == null)
5860
{
5961
return null;

src/WebJobs.Script.WebHost/WebHostServiceCollectionExtensions.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ public static void AddWebJobsScriptHost(this IServiceCollection services, IConfi
104104
services.AddSingleton<IWebFunctionsManager, WebFunctionsManager>();
105105
services.AddSingleton<IInstanceManager, InstanceManager>();
106106
services.AddSingleton(_ => new HttpClient());
107+
services.AddSingleton<HostNameProvider>();
107108
services.AddSingleton<IFileSystem>(_ => FileUtility.Instance);
108109
services.AddTransient<VirtualFileSystem>();
109110
services.AddTransient<VirtualFileSystemMiddleware>();

0 commit comments

Comments
 (0)