blob: bfcc05d050ccfe23abde98152f424881f36dfd4d [file] [log] [blame]
Constantin Zieschefa612082020-04-03 09:54:56 +02001/*******************************************************************************
2* Copyright (c) 2020 Robert Bosch GmbH
3* Author: Constantin Ziesche (constantin.ziesche@bosch.com)
4*
5* This program and the accompanying materials are made available under the
6* terms of the Eclipse Public License 2.0 which is available at
7* http://www.eclipse.org/legal/epl-2.0
8*
9* SPDX-License-Identifier: EPL-2.0
10*******************************************************************************/
Constantin Zieschece276602020-10-27 09:36:00 +010011using BaSyx.Components.Common.Abstractions;
Constantin Ziesche0399d412020-09-24 14:31:15 +020012using BaSyx.Utils.AssemblyHandling;
Constantin Ziesche687f8882020-10-02 16:17:44 +020013using BaSyx.Utils.DependencyInjection;
Constantin Zieschefa612082020-04-03 09:54:56 +020014using BaSyx.Utils.Settings.Types;
15using Microsoft.AspNetCore.Builder;
16using Microsoft.AspNetCore.Hosting;
Constantin Ziesche687f8882020-10-02 16:17:44 +020017using Microsoft.AspNetCore.Http;
18using Microsoft.AspNetCore.Rewrite;
Constantin Zieschefa612082020-04-03 09:54:56 +020019using Microsoft.Extensions.DependencyInjection;
Constantin Ziesche0399d412020-09-24 14:31:15 +020020using Microsoft.Extensions.FileProviders;
21using Microsoft.Extensions.Hosting;
Constantin Zieschefa612082020-04-03 09:54:56 +020022using Microsoft.Extensions.Logging;
23using NLog;
24using NLog.Web;
25using System;
Constantin Ziesche687f8882020-10-02 16:17:44 +020026using System.Collections.Generic;
27using System.Diagnostics;
Constantin Zieschefa612082020-04-03 09:54:56 +020028using System.IO;
29using System.Linq;
Constantin Ziesche02817f12020-08-04 21:40:43 +020030using System.Reflection;
Constantin Ziesche687f8882020-10-02 16:17:44 +020031using System.Text.RegularExpressions;
Constantin Zieschefa612082020-04-03 09:54:56 +020032using System.Threading;
33using System.Threading.Tasks;
Constantin Ziesche687f8882020-10-02 16:17:44 +020034using System.Web;
Constantin Ziesche02817f12020-08-04 21:40:43 +020035using static BaSyx.Utils.Settings.Types.ServerSettings;
Constantin Zieschefa612082020-04-03 09:54:56 +020036
37namespace BaSyx.Components.Common
38{
Constantin Zieschece276602020-10-27 09:36:00 +010039 public abstract class ServerApplication : IServerApplication
Constantin Zieschefa612082020-04-03 09:54:56 +020040 {
41 private static readonly Logger logger = NLogBuilder.ConfigureNLog("NLog.config").GetCurrentClassLogger();
42
Constantin Ziesche0399d412020-09-24 14:31:15 +020043 private string _contentRoot;
44 private string _webRoot;
Constantin Ziesche687f8882020-10-02 16:17:44 +020045 private bool _secure = false;
46
Constantin Ziesche09dcb6b2020-10-07 13:47:39 +020047 private readonly List<Action<IApplicationBuilder>> AppBuilderPipeline;
48 private readonly List<Action<IServiceCollection>> ServiceBuilderPipeline;
Constantin Ziesche0399d412020-09-24 14:31:15 +020049
50 public const string DEFAULT_CONTENT_ROOT = "Content";
51 public const string DEFAULT_WEB_ROOT = "wwwroot";
Constantin Ziesche687f8882020-10-02 16:17:44 +020052 public const string UI_RELATIVE_PATH = "/ui";
53 public const string CONTROLLER_ASSEMBLY_NAME = "BaSyx.API.Http.Controllers";
Constantin Ziesche0399d412020-09-24 14:31:15 +020054
Constantin Ziesche687f8882020-10-02 16:17:44 +020055 public Assembly ControllerAssembly { get; private set; }
Constantin Zieschefa612082020-04-03 09:54:56 +020056 public ServerSettings Settings { get; protected set; }
57 public IWebHostBuilder WebHostBuilder { get; protected set; }
Constantin Ziesche687f8882020-10-02 16:17:44 +020058
Constantin Ziesche0399d412020-09-24 14:31:15 +020059 public string ExecutionPath { get; }
Constantin Zieschefa612082020-04-03 09:54:56 +020060
61 public Action ApplicationStarted { get; set; }
62
63 public Action ApplicationStopping { get; set; }
64
65 public Action ApplicationStopped { get; set; }
66
Constantin Ziesche687f8882020-10-02 16:17:44 +020067 protected ServerApplication() : this(null, null)
Constantin Zieschefa612082020-04-03 09:54:56 +020068 { }
69
Constantin Ziesche687f8882020-10-02 16:17:44 +020070 protected ServerApplication(ServerSettings settings) : this(settings, null)
Constantin Zieschefa612082020-04-03 09:54:56 +020071 { }
72
Constantin Ziesche687f8882020-10-02 16:17:44 +020073 protected ServerApplication(ServerSettings settings, string[] webHostBuilderArgs)
Constantin Zieschefa612082020-04-03 09:54:56 +020074 {
Constantin Ziesche0399d412020-09-24 14:31:15 +020075 ExecutionPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
Constantin Ziesche687f8882020-10-02 16:17:44 +020076 ControllerAssembly = Assembly.Load(CONTROLLER_ASSEMBLY_NAME);
Constantin Ziesche0399d412020-09-24 14:31:15 +020077
Constantin Ziesche687f8882020-10-02 16:17:44 +020078 if (!EmbeddedResource.CheckOrWriteRessourceToFile(typeof(ServerApplication).Assembly, Path.Combine(ExecutionPath, "NLog.config")))
Constantin Ziesche0399d412020-09-24 14:31:15 +020079 logger.Error("NLog.config cannot be loaded or written");
80
Constantin Ziesche687f8882020-10-02 16:17:44 +020081 if (settings == null && !EmbeddedResource.CheckOrWriteRessourceToFile(typeof(ServerApplication).Assembly, Path.Combine(ExecutionPath, "ServerSettings.xml")))
Constantin Ziesche0399d412020-09-24 14:31:15 +020082 logger.Error("ServerSettings.xml cannot be loaded or written");
83
84 Settings = settings ?? ServerSettings.LoadSettingsFromFile("ServerSettings.xml") ?? throw new ArgumentNullException(nameof(settings));
85
Constantin Ziesche687f8882020-10-02 16:17:44 +020086 WebHostBuilder = DefaultWebHostBuilder.CreateWebHostBuilder(webHostBuilderArgs, Settings);
87 AppBuilderPipeline = new List<Action<IApplicationBuilder>>();
88 ServiceBuilderPipeline = new List<Action<IServiceCollection>>();
89
Constantin Ziesche09dcb6b2020-10-07 13:47:39 +020090 WebHostBuilder.ConfigureServices( (context, services) =>
Constantin Ziesche687f8882020-10-02 16:17:44 +020091 {
Constantin Ziesche09dcb6b2020-10-07 13:47:39 +020092 ConfigureServices(context, services);
Constantin Ziesche687f8882020-10-02 16:17:44 +020093 });
94
95 WebHostBuilder.Configure(app =>
96 {
97 Configure(app);
98 });
99
100 WebHostBuilder.UseNLog();
101 ConfigureLogging(logger.GetLogLevel());
102
Constantin Ziesche0399d412020-09-24 14:31:15 +0200103 if (string.IsNullOrEmpty(Settings.ServerConfig.Hosting.ContentPath))
104 _contentRoot = Path.Join(ExecutionPath, DEFAULT_CONTENT_ROOT);
105 else if (Path.IsPathRooted(Settings.ServerConfig.Hosting.ContentPath))
106 _contentRoot = Settings.ServerConfig.Hosting.ContentPath;
107 else
108 _contentRoot = Path.Join(ExecutionPath, Settings.ServerConfig.Hosting.ContentPath);
109
Constantin Ziesche0399d412020-09-24 14:31:15 +0200110 try
111 {
112 if (!Directory.Exists(_contentRoot))
113 Directory.CreateDirectory(_contentRoot);
114 WebHostBuilder.UseContentRoot(_contentRoot);
115 }
116 catch (Exception e)
117 {
118 logger.Error(e, $"ContentRoot path {_contentRoot} cannot be created ");
119 }
120
Constantin Ziesche687f8882020-10-02 16:17:44 +0200121 _webRoot = Path.Join(ExecutionPath, DEFAULT_WEB_ROOT);
Constantin Ziesche0399d412020-09-24 14:31:15 +0200122
123 try
124 {
125 if (!Directory.Exists(_webRoot))
126 Directory.CreateDirectory(_webRoot);
127 WebHostBuilder.UseWebRoot(_webRoot);
Constantin Ziesche687f8882020-10-02 16:17:44 +0200128 logger.Info($"wwwroot-Path: {_webRoot}");
Constantin Ziesche0399d412020-09-24 14:31:15 +0200129 }
130 catch (Exception e)
131 {
132 logger.Error(e, $"WebRoot path {_webRoot} cannot be created ");
Constantin Ziesche687f8882020-10-02 16:17:44 +0200133 }
Constantin Zieschefa612082020-04-03 09:54:56 +0200134 }
135
136 public virtual void Run()
137 {
Constantin Ziesche687f8882020-10-02 16:17:44 +0200138 logger.Info("Starting Server...");
Constantin Zieschefa612082020-04-03 09:54:56 +0200139
Constantin Ziesche0399d412020-09-24 14:31:15 +0200140 WebHostBuilder.Build().Run();
Constantin Zieschefa612082020-04-03 09:54:56 +0200141 }
142
143 public virtual async Task RunAsync(CancellationToken cancellationToken = default)
144 {
Constantin Ziesche687f8882020-10-02 16:17:44 +0200145 logger.Info("Starting Server asynchronously...");
Constantin Zieschefa612082020-04-03 09:54:56 +0200146
147 await WebHostBuilder.Build().RunAsync(cancellationToken);
148 }
149
150 public virtual void ConfigureLogging(Microsoft.Extensions.Logging.LogLevel logLevel)
151 {
152 WebHostBuilder.ConfigureLogging(logging =>
Constantin Ziesche0399d412020-09-24 14:31:15 +0200153 {
154 logging.ClearProviders();
155 logging.SetMinimumLevel(logLevel);
156 });
Constantin Zieschefa612082020-04-03 09:54:56 +0200157 }
158
Constantin Ziesche687f8882020-10-02 16:17:44 +0200159 public virtual void Configure(Action<IApplicationBuilder> app) => AppBuilderPipeline.Add(app);
160 public virtual void ConfigureServices(Action<IServiceCollection> services) => ServiceBuilderPipeline.Add(services);
Constantin Ziesche0399d412020-09-24 14:31:15 +0200161 public virtual void UseContentRoot(string contentRoot)
162 {
163 _contentRoot = contentRoot;
164 WebHostBuilder.UseContentRoot(contentRoot);
165 }
166 public virtual void UseWebRoot(string webRoot)
167 {
168 _webRoot = webRoot;
169 WebHostBuilder.UseWebRoot(webRoot);
170 }
Constantin Zieschefa612082020-04-03 09:54:56 +0200171 public virtual void UseUrls(params string[] urls)
172 {
173 WebHostBuilder.UseUrls(urls);
174 if (Settings?.ServerConfig?.Hosting != null)
175 Settings.ServerConfig.Hosting.Urls = urls?.ToList();
176 }
177
178 public virtual void ProvideContent(Uri relativeUri, Stream content)
179 {
180 try
181 {
182 using (Stream stream = content)
183 {
184 string fileName = Path.GetFileName(relativeUri.ToString());
Constantin Zieschee837f992020-08-19 12:04:32 +0200185 logger.Info("FileName: " + fileName);
186 string directory = Path.GetDirectoryName(relativeUri.ToString()).TrimStart('\\');
187 logger.Info("Directory: " + directory);
188
Constantin Ziesche0399d412020-09-24 14:31:15 +0200189 string hostingDirectory = Path.Join(_contentRoot, directory);
Constantin Zieschee837f992020-08-19 12:04:32 +0200190
191 logger.Info($"Try creating hosting directory if not existing: {hostingDirectory}");
Constantin Zieschefa612082020-04-03 09:54:56 +0200192 Directory.CreateDirectory(hostingDirectory);
193
Constantin Zieschee837f992020-08-19 12:04:32 +0200194 string filePath = Path.Join(hostingDirectory, fileName);
195 logger.Info($"Try writing file: {filePath}");
Constantin Zieschefa612082020-04-03 09:54:56 +0200196
197 using (FileStream fileStream = File.OpenWrite(filePath))
198 {
199 stream.CopyTo(fileStream);
200 }
201 }
202 }
203 catch (Exception e)
204 {
205 logger.Error(e, $"Error providing content {relativeUri}");
206 }
207 }
Constantin Ziesche02817f12020-08-04 21:40:43 +0200208
209 public virtual void MapControllers(ControllerConfiguration controllerConfig)
210 {
211 this.ConfigureServices(services =>
212 {
213 if (controllerConfig?.Controllers?.Count > 0)
214 {
215 var mvcBuilder = services.AddMvc();
Constantin Ziesche0399d412020-09-24 14:31:15 +0200216 foreach (var controllerAssemblyString in controllerConfig.Controllers)
Constantin Ziesche02817f12020-08-04 21:40:43 +0200217 {
Constantin Ziesche181770b2020-08-20 20:32:39 +0200218 Assembly controllerAssembly = null;
219 try
220 {
Constantin Ziesche0399d412020-09-24 14:31:15 +0200221 controllerAssembly = Assembly.Load(controllerAssemblyString);
Constantin Ziesche181770b2020-08-20 20:32:39 +0200222 }
223 catch (Exception e)
224 {
Constantin Ziesche0399d412020-09-24 14:31:15 +0200225 logger.Warn(e, $"Assembly {controllerAssemblyString} cannot be loaded - maybe it is not referenced. Try reading from file...");
Constantin Ziesche181770b2020-08-20 20:32:39 +0200226 try
227 {
Constantin Ziesche0399d412020-09-24 14:31:15 +0200228 if (File.Exists(controllerAssemblyString))
229 controllerAssembly = Assembly.LoadFile(controllerAssemblyString);
230 else if (File.Exists(controllerAssemblyString + ".dll"))
231 controllerAssembly = Assembly.LoadFile(controllerAssemblyString + ".dll");
Constantin Ziesche181770b2020-08-20 20:32:39 +0200232 else
Constantin Ziesche0399d412020-09-24 14:31:15 +0200233 controllerAssembly = Assembly.LoadFrom(controllerAssemblyString);
Constantin Ziesche181770b2020-08-20 20:32:39 +0200234 }
235 catch (Exception exp)
236 {
Constantin Ziesche0399d412020-09-24 14:31:15 +0200237 logger.Warn(exp, $"Assembly {controllerAssemblyString} can finally not be loaded");
238 }
Constantin Ziesche181770b2020-08-20 20:32:39 +0200239 }
Constantin Ziesche08215502020-09-21 19:08:32 +0200240 if (controllerAssembly != null)
241 {
Constantin Ziesche181770b2020-08-20 20:32:39 +0200242 mvcBuilder.AddApplicationPart(controllerAssembly);
Constantin Ziesche0399d412020-09-24 14:31:15 +0200243 string controllerAssemblyName = controllerAssembly.GetName().Name;
244 string xmlDocFile = $"{controllerAssemblyName}.xml";
245 string xmlDocFilePath = Path.Combine(ExecutionPath, xmlDocFile);
246
247 if (File.Exists(xmlDocFilePath))
248 continue;
249
250 try
251 {
252 ManifestEmbeddedFileProvider embeddedFileProvider = new ManifestEmbeddedFileProvider(controllerAssembly);
253 IFileInfo fileInfo = embeddedFileProvider.GetFileInfo(xmlDocFile);
254 if (fileInfo == null)
255 {
256 logger.Warn($"{xmlDocFile} of Assembly {controllerAssemblyName} not found");
257 continue;
258 }
259 using (Stream stream = fileInfo.CreateReadStream())
260 {
261 using (FileStream fileStream = File.OpenWrite(xmlDocFilePath))
262 {
263 stream.CopyTo(fileStream);
264 }
265 }
266 logger.Info($"{xmlDocFile} of Assembly {controllerAssemblyName} has been created successfully");
267 }
268 catch (Exception e)
269 {
270 logger.Warn(e, $"{xmlDocFile} of Assembly {controllerAssemblyName} cannot be read");
271 }
Constantin Ziesche08215502020-09-21 19:08:32 +0200272 }
Constantin Ziesche02817f12020-08-04 21:40:43 +0200273 }
274 mvcBuilder.AddControllersAsServices();
275 }
276 });
277 }
Constantin Ziesche687f8882020-10-02 16:17:44 +0200278
Constantin Ziesche09dcb6b2020-10-07 13:47:39 +0200279 protected virtual void ConfigureServices(WebHostBuilderContext context, IServiceCollection services)
Constantin Ziesche687f8882020-10-02 16:17:44 +0200280 {
281 services.AddSingleton(typeof(ServerSettings), Settings);
282 services.AddSingleton<IServerApplicationLifetime>(this);
283
Constantin Ziesche09dcb6b2020-10-07 13:47:39 +0200284
Constantin Ziesche687f8882020-10-02 16:17:44 +0200285 var urls = Settings.ServerConfig.Hosting.Urls;
286 var secureUrl = urls.Find(s => s.StartsWith("https"));
Constantin Ziesche09dcb6b2020-10-07 13:47:39 +0200287 if (!string.IsNullOrEmpty(secureUrl) && !context.HostingEnvironment.IsDevelopment())
Constantin Ziesche687f8882020-10-02 16:17:44 +0200288 {
289 secureUrl = secureUrl.Replace("+", "0.0.0.0");
290 Uri secureUri = new Uri(secureUrl);
291 _secure = true;
292 services.AddHttpsRedirection(opts =>
293 {
294 opts.HttpsPort = secureUri.Port;
295 });
296 }
297
298 services.AddStandardImplementation();
299
300 services.AddCors();
301 services.AddMvc()
302 .AddApplicationPart(ControllerAssembly)
303 .AddControllersAsServices()
304 .AddNewtonsoftJson(options => options.GetDefaultMvcJsonOptions(services));
305
306 services.AddRazorPages(opts =>
307 {
308 logger.Info("Pages-RootDirectory: " + opts.RootDirectory);
309 });
310
311 services.AddDirectoryBrowser();
312
313 foreach (var serviceBuider in ServiceBuilderPipeline)
314 {
315 serviceBuider.Invoke(services);
316 }
317 }
318
319 protected virtual void Configure(IApplicationBuilder app)
320 {
321 var env = app.ApplicationServices.GetRequiredService<IWebHostEnvironment>();
322 var applicationLifetime = app.ApplicationServices.GetRequiredService<IHostApplicationLifetime>();
323
324 if (env.IsDevelopment() || Debugger.IsAttached)
325 {
326 app.UseDeveloperExceptionPage();
327 }
328 else
329 {
330 app.UseExceptionHandler("/Error");
331 app.UseHsts();
332 }
333
Constantin Ziesche09dcb6b2020-10-07 13:47:39 +0200334 if(_secure && !env.IsDevelopment())
Constantin Ziesche687f8882020-10-02 16:17:44 +0200335 app.UseHttpsRedirection();
336
337 app.UseStaticFiles();
338
339 string path = env.ContentRootPath;
340 if (Directory.Exists(path))
341 {
342 app.UseStaticFiles(new StaticFileOptions()
343 {
344 FileProvider = new PhysicalFileProvider(@path),
345 RequestPath = new PathString("")
346 });
347
348 app.UseDirectoryBrowser(new DirectoryBrowserOptions
349 {
350 FileProvider = new PhysicalFileProvider(@path),
351 RequestPath = new PathString("/browse")
352 });
353 }
354
355 app.Use((context, next) =>
356 {
357 string requestPath = context.Request.Path.ToUriComponent();
358
359 if (requestPath.Contains("submodelElements/"))
360 {
361 Match valueMatch = Regex.Match(requestPath, "(?<=submodelElements/)(.*)(?=/value|/invoke|/invocationList)");
Constantin Ziescheeb74d642020-11-04 17:57:12 +0100362 if(valueMatch.Success && !string.IsNullOrEmpty(valueMatch.Value))
Constantin Ziesche687f8882020-10-02 16:17:44 +0200363 {
364 string elementPath = HttpUtility.UrlEncode(valueMatch.Value);
365 requestPath = requestPath.Replace(valueMatch.Value, elementPath);
366 context.Request.Path = new PathString(requestPath);
367 }
368 else
369 {
370 Match baseMatch = Regex.Match(requestPath, "(?<=submodelElements/)(.*)");
Constantin Ziescheeb74d642020-11-04 17:57:12 +0100371 if(baseMatch.Success && !string.IsNullOrEmpty(baseMatch.Value))
Constantin Ziesche687f8882020-10-02 16:17:44 +0200372 {
373 string elementPath = HttpUtility.UrlEncode(baseMatch.Value);
374 requestPath = requestPath.Replace(baseMatch.Value, elementPath);
375 context.Request.Path = new PathString(requestPath);
376 }
377 }
378 }
379 return next();
380 });
381
382 app.UseRouting();
383
384 app.UseCors(
385 options => options
386 .AllowAnyHeader()
387 .AllowAnyMethod()
388 .AllowAnyOrigin()
389 );
390 app.UseAuthorization();
391
392 app.UseEndpoints(endpoints =>
393 {
394 endpoints.MapRazorPages();
395 endpoints.MapControllers();
396 });
397
398 var options = new RewriteOptions().AddRedirect("^$", UI_RELATIVE_PATH);
399 app.UseRewriter(options);
400
401 if (ApplicationStarted != null)
402 applicationLifetime.ApplicationStarted.Register(ApplicationStarted);
403 if (ApplicationStopping != null)
404 applicationLifetime.ApplicationStopping.Register(ApplicationStopping);
405 if (ApplicationStopped != null)
406 applicationLifetime.ApplicationStopped.Register(ApplicationStopped);
407
408 foreach (var appBuilder in AppBuilderPipeline)
409 {
410 appBuilder.Invoke(app);
411 }
412 }
Constantin Zieschefa612082020-04-03 09:54:56 +0200413 }
414}