public IActionResult Index() { var config = new WebAppConfiguration { ApiUrl = _configuration["ApiUrl"], PolicyDocumentsPath = _configuration["PolicyDocumentsPath"], ApimSubscriptionKey = _configuration["ApimSubscriptionKey"] }; return(View(config)); }
static public WebHostAdminInterfaceTestSetup Setup( WebAppConfiguration deployAppConfigAndInitElmState, Func <DateTimeOffset> persistentProcessHostDateTime = null, string adminRootPassword = null) => Setup( persistentProcessHostDateTime: persistentProcessHostDateTime, adminRootPassword: adminRootPassword, deployAppConfigAndInitElmState: deployAppConfigAndInitElmState == null ? null : Composition.FromTree(Composition.TreeFromSetOfBlobsWithStringPath(deployAppConfigAndInitElmState.AsFiles())));
static public WebHostTestSetup Setup( WebAppConfiguration webAppConfig, Func <IWebHostBuilder, IWebHostBuilder> webHostBuilderMap) { var testDirectory = Filesystem.CreateRandomDirectoryInTempDirectory(); var setup = new WebHostTestSetup(testDirectory, webHostBuilderMap); var webAppConfigFilePath = setup.WebAppConfigFilePath; Directory.CreateDirectory(Path.GetDirectoryName(webAppConfigFilePath)); File.WriteAllBytes(webAppConfigFilePath, ZipArchive.ZipArchiveFromEntries(webAppConfig.AsFiles())); return(setup); }
public void UpdateLauncher(string launcher, string argumentPattern, string profile) { var configuration = WebAppConfigurationRepository.GetConfiguration(profile); if (configuration == null) { configuration = new WebAppConfiguration() { Profile = profile, }; } configuration.WebAppLauncher = launcher; configuration.WebAppArgumentPattern = argumentPattern; WebAppConfigurationRepository.SaveConfiguration(configuration); }
public WebAppConfiguration GetOrCreateConfiguration(string profile) { var configuration = WebAppConfigurationRepository.GetConfiguration(profile); if (configuration == null) { configuration = new WebAppConfiguration() { Profile = profile, WebAppLauncher = "chrome.exe", WebAppArgumentPattern = "--app=\"{0}\" --profile-directory=\"Default\"" }; WebAppConfigurationRepository.SaveConfiguration(configuration); } return(WebAppConfigurationRepository.GetConfiguration(profile)); }
public void Init() { DataAccessService.Init(); WebAppItemRepository.Init(); WebAppConfigurationRepository.Init(); var configurations = WebAppConfigurationRepository.GetConfigurations(); if (configurations == null) { var configuration = new WebAppConfiguration() { Profile = "default", WebAppLauncher = "chrome.exe", WebAppArgumentPattern = "--app=\"{0}\" --profile-directory=\"Default\"" }; WebAppConfigurationRepository.SaveConfiguration(configuration); } }
public void SaveConfiguration(WebAppConfiguration configuration) { var affectedRows = DataAccessService .GetQuery("update configuration set launcher=@launcher, pattern=@pattern where profile=@profile") .WithParameter("profile", configuration.Profile) .WithParameter("launcher", configuration.WebAppLauncher) .WithParameter("pattern", configuration.WebAppArgumentPattern) .Execute(); if (affectedRows == 0) { DataAccessService .GetQuery("insert into configuration (profile, launcher, pattern) values (@profile, @launcher, @pattern)") .WithParameter("profile", configuration.Profile) .WithParameter("launcher", configuration.WebAppLauncher) .WithParameter("pattern", configuration.WebAppArgumentPattern) .Execute(); } }
static async Task StaticFilesMiddlewareFromWebAppConfig( WebAppConfiguration webAppConfig, HttpContext context, Func <Task> next) { var matchingUrlMapToStaticFile = webAppConfig?.JsonStructure?.mapsFromRequestUrlToStaticFileName ?.FirstOrDefault(conditionalMap => Regex.IsMatch(context.Request.GetDisplayUrl(), conditionalMap.matchingRegexPattern)); if (matchingUrlMapToStaticFile != null) { if (!string.Equals("get", context.Request.Method, StringComparison.InvariantCultureIgnoreCase)) { context.Response.StatusCode = 405; await context.Response.WriteAsync("This resource only supports the GET method."); return; } var staticFilePath = matchingUrlMapToStaticFile.resultString.Split('/'); var staticFile = webAppConfig?.StaticFiles ?.FirstOrDefault(staticFileNameAndContent => staticFilePath.SequenceEqual(staticFileNameAndContent.staticFilePath)); if (staticFile?.staticFileContent == null) { context.Response.StatusCode = 404; return; } context.Response.StatusCode = 200; await context.Response.Body.WriteAsync(staticFile?.staticFileContent.ToArray(), 0, staticFile.Value.staticFileContent.Count); return; } await next?.Invoke(); }
private static void BuildApp(IAppBuilder app, string publicDirectoryPath) { var container = IoCConfiguration.CreateIoCContainer(); var httpConfig = new HttpConfiguration { DependencyResolver = container.Resolve <System.Web.Http.Dependencies.IDependencyResolver>() }; WebAppConfiguration.ConfigureFileSystem(app, publicDirectoryPath); WebAppConfiguration.ConfigureAuthentication(app); WebAppConfiguration.ConfigureWebApi(app, httpConfig); WebAppConfiguration.ConfigureFilters(httpConfig, container); app.UseAutofacMiddleware(container); // must be at the end app.UseWebApi(httpConfig); }
static async Task RateLimitMiddlewareFromWebAppConfig( WebAppConfiguration webAppConfig, HttpContext context, Func <Task> next) { string ClientId() { const string defaultClientId = "MapToIPv4-failed"; try { return(context.Connection.RemoteIpAddress?.MapToIPv4().ToString() ?? defaultClientId); } catch { return(defaultClientId); } } var rateLimitFromClientId = context.RequestServices.GetService <ClientsRateLimitStateContainer>().RateLimitFromClientId; var clientRateLimitState = rateLimitFromClientId.GetOrAdd( ClientId(), _ => BuildRateLimitContainerForClient(webAppConfig?.JsonStructure)); if (clientRateLimitState?.AttemptPass(Configuration.GetDateTimeOffset(context).ToUnixTimeMilliseconds()) ?? true) { await next?.Invoke(); return; } context.Response.StatusCode = 429; await context.Response.WriteAsync(""); return; }
public void Configure( IApplicationBuilder app, IWebHostEnvironment env, WebAppConfiguration webAppConfig, ProcessStore.IProcessStoreWriter processStoreWriter, Func <DateTimeOffset> getDateTimeOffset) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } var nextHttpRequestIndex = 0; var cyclicReductionStoreLock = new object(); DateTimeOffset?cyclicReductionStoreLastTime = null; var cyclicReductionStoreDistanceSeconds = (int)TimeSpan.FromHours(1).TotalSeconds; if (webAppConfig?.JsonStructure?.letsEncryptOptions != null) { app.UseFluffySpoonLetsEncryptChallengeApprovalMiddleware(); } var createVolatileHostAttempts = 0; var volatileHosts = new ConcurrentDictionary <string, CSharpScriptContext>(); InterfaceToHost.Result <InterfaceToHost.TaskResult.RunInVolatileHostError, InterfaceToHost.TaskResult.RunInVolatileHostComplete> performProcessTaskRunInVolatileHost( InterfaceToHost.Task.RunInVolatileHost runInVolatileHost) { if (!volatileHosts.TryGetValue(runInVolatileHost.hostId, out var volatileHost)) { return(new InterfaceToHost.Result <InterfaceToHost.TaskResult.RunInVolatileHostError, InterfaceToHost.TaskResult.RunInVolatileHostComplete> { err = new InterfaceToHost.TaskResult.RunInVolatileHostError { hostNotFound = new object(), } }); } var stopwatch = System.Diagnostics.Stopwatch.StartNew(); var fromVolatileHostResult = volatileHost.RunScript(runInVolatileHost.script); stopwatch.Stop(); return(new InterfaceToHost.Result <InterfaceToHost.TaskResult.RunInVolatileHostError, InterfaceToHost.TaskResult.RunInVolatileHostComplete> { ok = new InterfaceToHost.TaskResult.RunInVolatileHostComplete { exceptionToString = fromVolatileHostResult.Exception?.ToString(), returnValueToString = fromVolatileHostResult.ReturnValue?.ToString(), durationInMilliseconds = stopwatch.ElapsedMilliseconds, } }); } InterfaceToHost.TaskResult performProcessTask(InterfaceToHost.Task task) { if (task?.createVolatileHost != null) { var volatileHostId = System.Threading.Interlocked.Increment(ref createVolatileHostAttempts).ToString(); volatileHosts[volatileHostId] = new CSharpScriptContext(BlobLibrary.GetBlobWithSHA256); return(new InterfaceToHost.TaskResult { createVolatileHostResponse = new InterfaceToHost.Result <object, InterfaceToHost.TaskResult.CreateVolatileHostComplete> { ok = new InterfaceToHost.TaskResult.CreateVolatileHostComplete { hostId = volatileHostId, }, }, }); } if (task?.releaseVolatileHost != null) { volatileHosts.TryRemove(task?.releaseVolatileHost.hostId, out var volatileHost); return(new InterfaceToHost.TaskResult { completeWithoutResult = new object(), }); } if (task?.runInVolatileHost != null) { return(new InterfaceToHost.TaskResult { runInVolatileHostResponse = performProcessTaskRunInVolatileHost(task?.runInVolatileHost), }); } throw new NotImplementedException("Unexpected task structure."); } void performProcessTaskAndFeedbackEvent(InterfaceToHost.StartTask taskWithId) { var taskResult = performProcessTask(taskWithId.task); var interfaceEvent = new InterfaceToHost.Event { taskComplete = new InterfaceToHost.ResultFromTaskWithId { taskId = taskWithId.taskId, taskResult = taskResult, } }; processEventAndResultingRequests(interfaceEvent); } var processRequestCompleteHttpResponse = new ConcurrentDictionary <string, InterfaceToHost.HttpResponse>(); var persistentProcess = app.ApplicationServices.GetService <IPersistentProcess>(); void processEventAndResultingRequests(InterfaceToHost.Event interfaceEvent) { lock (processStoreWriter) { var serializedInterfaceEvent = Newtonsoft.Json.JsonConvert.SerializeObject(interfaceEvent, jsonSerializerSettings); var(eventResponses, compositionRecord) = persistentProcess.ProcessEvents(new[] { serializedInterfaceEvent }); processStoreWriter.AppendSerializedCompositionRecord(compositionRecord.serializedCompositionRecord); var serializedResponse = eventResponses.Single(); InterfaceToHost.ResponseOverSerialInterface structuredResponse = null; try { structuredResponse = Newtonsoft.Json.JsonConvert.DeserializeObject <InterfaceToHost.ResponseOverSerialInterface>( serializedResponse); } catch (Exception parseException) { throw new Exception( "Failed to parse event response from app. Looks like the loaded elm app is not compatible with the interface.\nResponse from app follows:\n" + serializedResponse, parseException); } if (structuredResponse?.decodeEventSuccess == null) { throw new Exception("Hosted app failed to decode the event: " + structuredResponse.decodeEventError); } foreach (var requestFromProcess in structuredResponse.decodeEventSuccess) { if (requestFromProcess.completeHttpResponse != null) { processRequestCompleteHttpResponse[requestFromProcess.completeHttpResponse.httpRequestId] = requestFromProcess.completeHttpResponse.response; } if (requestFromProcess.startTask != null) { System.Threading.Tasks.Task.Run(() => performProcessTaskAndFeedbackEvent(requestFromProcess.startTask)); } } } } app .Use(async(context, next) => await Asp.MiddlewareFromWebAppConfig(webAppConfig, context, next)) .Run(async(context) => { var currentDateTime = getDateTimeOffset(); var timeMilli = currentDateTime.ToUnixTimeMilliseconds(); var httpRequestIndex = System.Threading.Interlocked.Increment(ref nextHttpRequestIndex); var httpRequestId = timeMilli.ToString() + "-" + httpRequestIndex.ToString(); { var httpEvent = AsPersistentProcessInterfaceHttpRequestEvent(context, httpRequestId, currentDateTime); var httpRequestInterfaceEvent = new InterfaceToHost.Event { httpRequest = httpEvent, }; processEventAndResultingRequests(httpRequestInterfaceEvent); } var waitForHttpResponseClock = System.Diagnostics.Stopwatch.StartNew(); while (true) { if (processRequestCompleteHttpResponse.TryRemove(httpRequestId, out var httpResponse)) { var headerContentType = httpResponse.headersToAdd ?.FirstOrDefault(header => header.name?.ToLowerInvariant() == "content-type") ?.values?.FirstOrDefault(); context.Response.StatusCode = httpResponse.statusCode; foreach (var headerToAdd in (httpResponse.headersToAdd).EmptyIfNull()) { context.Response.Headers[headerToAdd.name] = new Microsoft.Extensions.Primitives.StringValues(headerToAdd.values); } if (headerContentType != null) { context.Response.ContentType = headerContentType; } await context.Response.WriteAsync(httpResponse?.bodyAsString ?? ""); break; } if (60 <= waitForHttpResponseClock.Elapsed.TotalSeconds) { throw new TimeoutException( "Persistent process did not return a HTTP response within " + (int)waitForHttpResponseClock.Elapsed.TotalSeconds + " seconds."); } System.Threading.Thread.Sleep(100); } System.Threading.Thread.MemoryBarrier(); var cyclicReductionStoreLastAge = currentDateTime - cyclicReductionStoreLastTime; if (!(cyclicReductionStoreLastAge?.TotalSeconds < cyclicReductionStoreDistanceSeconds)) { if (System.Threading.Monitor.TryEnter(cyclicReductionStoreLock)) { try { var afterLockCyclicReductionStoreLastAge = currentDateTime - cyclicReductionStoreLastTime; if (afterLockCyclicReductionStoreLastAge?.TotalSeconds < cyclicReductionStoreDistanceSeconds) { return; } var reductionRecord = persistentProcess.ReductionRecordForCurrentState(); lock (processStoreWriter) { processStoreWriter.StoreReduction(reductionRecord); } cyclicReductionStoreLastTime = currentDateTime; System.Threading.Thread.MemoryBarrier(); } finally { System.Threading.Monitor.Exit(cyclicReductionStoreLock); } } } }); }
public void Configure( IApplicationBuilder app, IWebHostEnvironment env, IHostApplicationLifetime appLifetime, Func <DateTimeOffset> getDateTimeOffset) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } var configuration = app.ApplicationServices.GetService <IConfiguration>(); var rootPassword = configuration.GetValue <string>(Configuration.AdminRootPasswordSettingKey); var publicWebHostUrls = configuration.GetValue <string>(Configuration.PublicWebHostUrlsSettingKey)?.Split(new[] { ',', ';' }); var processStoreFileStore = app.ApplicationServices.GetService <FileStoreForProcessStore>().fileStore; object publicAppLock = new object(); PublicHostConfiguration publicAppHost = null; void stopPublicApp() { lock (publicAppLock) { if (publicAppHost != null) { publicAppHost?.webHost?.StopAsync(TimeSpan.FromSeconds(10)).Wait(); publicAppHost?.webHost?.Dispose(); publicAppHost?.processVolatileRepresentation?.Dispose(); publicAppHost = null; } } } appLifetime.ApplicationStopping.Register(() => { stopPublicApp(); }); var processStoreWriter = new ProcessStoreSupportingMigrations.ProcessStoreWriterInFileStore(processStoreFileStore); void startPublicApp() { lock (publicAppLock) { stopPublicApp(); var newPublicAppConfig = new PublicHostConfiguration { }; logger.LogInformation("Begin to build the process volatile representation."); var processVolatileRepresentation = PersistentProcess.PersistentProcessVolatileRepresentation.Restore( new ProcessStoreSupportingMigrations.ProcessStoreReaderInFileStore(processStoreFileStore), logger: logEntry => logger.LogInformation(logEntry)); logger.LogInformation("Completed building the process volatile representation."); var cyclicReductionStoreLock = new object(); DateTimeOffset?cyclicReductionStoreLastTime = null; var cyclicReductionStoreDistanceSeconds = (int)TimeSpan.FromMinutes(10).TotalSeconds; void maintainStoreReductions() { var currentDateTime = getDateTimeOffset(); System.Threading.Thread.MemoryBarrier(); var cyclicReductionStoreLastAge = currentDateTime - cyclicReductionStoreLastTime; if (!(cyclicReductionStoreLastAge?.TotalSeconds < cyclicReductionStoreDistanceSeconds)) { if (System.Threading.Monitor.TryEnter(cyclicReductionStoreLock)) { try { var afterLockCyclicReductionStoreLastAge = currentDateTime - cyclicReductionStoreLastTime; if (afterLockCyclicReductionStoreLastAge?.TotalSeconds < cyclicReductionStoreDistanceSeconds) { return; } lock (processStoreFileStore) { var reductionRecord = processVolatileRepresentation.StoreReductionRecordForCurrentState(processStoreWriter); } cyclicReductionStoreLastTime = currentDateTime; System.Threading.Thread.MemoryBarrier(); } finally { System.Threading.Monitor.Exit(cyclicReductionStoreLock); } } } } var webHost = processVolatileRepresentation?.lastAppConfig?.appConfigComponent == null ? null : buildWebHost(); IWebHost buildWebHost() { var appConfigTree = Composition.ParseAsTree( processVolatileRepresentation.lastAppConfig.Value.appConfigComponent).Ok; var appConfigFilesNamesAndContents = appConfigTree.EnumerateBlobsTransitive() .Select(blobPathAndContent => ( fileName: (IImmutableList <string>)blobPathAndContent.path.Select(name => System.Text.Encoding.UTF8.GetString(name.ToArray())).ToImmutableList(), fileContent: blobPathAndContent.blobContent)) .ToImmutableList(); return (Microsoft.AspNetCore.WebHost.CreateDefaultBuilder() .ConfigureLogging((hostingContext, logging) => { logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging")); logging.AddConsole(); logging.AddDebug(); }) .ConfigureKestrel(kestrelOptions => { kestrelOptions.ConfigureHttpsDefaults(httpsOptions => { httpsOptions.ServerCertificateSelector = (c, s) => FluffySpoon.AspNet.LetsEncrypt.LetsEncryptRenewalService.Certificate; }); }) .UseUrls(publicWebHostUrls ?? PublicWebHostUrlsDefault) .UseStartup <StartupPublicApp>() .WithSettingDateTimeOffsetDelegate(getDateTimeOffset) .ConfigureServices(services => { services.AddSingleton <WebAppAndElmAppConfig>( new WebAppAndElmAppConfig { WebAppConfiguration = WebAppConfiguration.FromFiles(appConfigFilesNamesAndContents), ProcessEventInElmApp = serializedEvent => { lock (processStoreWriter) { lock (publicAppLock) { var elmEventResponse = processVolatileRepresentation.ProcessElmAppEvent( processStoreWriter, serializedEvent); maintainStoreReductions(); return elmEventResponse; } } } }); }) .Build()); } newPublicAppConfig.processVolatileRepresentation = processVolatileRepresentation; newPublicAppConfig.webHost = webHost; webHost?.StartAsync(appLifetime.ApplicationStopping).Wait(); publicAppHost = newPublicAppConfig; } } startPublicApp(); app.Run(async(context) => { var syncIOFeature = context.Features.Get <Microsoft.AspNetCore.Http.Features.IHttpBodyControlFeature>(); if (syncIOFeature != null) { syncIOFeature.AllowSynchronousIO = true; } { var expectedAuthorization = Configuration.BasicAuthenticationForAdminRoot(rootPassword); context.Request.Headers.TryGetValue("Authorization", out var requestAuthorizationHeaderValue); AuthenticationHeaderValue.TryParse( requestAuthorizationHeaderValue.FirstOrDefault(), out var requestAuthorization); if (!(0 < rootPassword?.Length)) { context.Response.StatusCode = 403; await context.Response.WriteAsync("Forbidden"); return; } var buffer = new byte[400]; var decodedRequestAuthorizationParameter = Convert.TryFromBase64String(requestAuthorization?.Parameter ?? "", buffer, out var bytesWritten) ? Encoding.UTF8.GetString(buffer, 0, bytesWritten) : null; if (!(string.Equals(expectedAuthorization, decodedRequestAuthorizationParameter) && string.Equals("basic", requestAuthorization?.Scheme, StringComparison.OrdinalIgnoreCase))) { context.Response.StatusCode = 401; context.Response.Headers.Add( "WWW-Authenticate", @"Basic realm=""" + context.Request.Host + @""", charset=""UTF-8"""); await context.Response.WriteAsync("Unauthorized"); return; } } var requestPathIsDeployAppConfigAndInitElmAppState = context.Request.Path.Equals(new PathString(PathApiDeployAppConfigAndInitElmAppState)); if (context.Request.Path.Equals(new PathString(PathApiGetDeployedAppConfig))) { if (!string.Equals(context.Request.Method, "get", StringComparison.InvariantCultureIgnoreCase)) { context.Response.StatusCode = 405; await context.Response.WriteAsync("Method not supported."); return; } var appConfig = publicAppHost?.processVolatileRepresentation?.lastAppConfig?.appConfigComponent; if (appConfig == null) { context.Response.StatusCode = 404; await context.Response.WriteAsync("I did not find an app config in the history. Looks like no app was deployed so far."); return; } var appConfigHashBase16 = CommonConversion.StringBase16FromByteArray(Composition.GetHash(appConfig)); var appConfigTree = Composition.ParseAsTree(appConfig).Ok; var appConfigFilesNamesAndContents = appConfigTree.EnumerateBlobsTransitive() .Select(blobPathAndContent => ( fileName: (IImmutableList <string>)blobPathAndContent.path.Select(name => Encoding.UTF8.GetString(name.ToArray())).ToImmutableList(), fileContent: blobPathAndContent.blobContent)) .ToImmutableList(); var appConfigZipArchive = ZipArchive.ZipArchiveFromEntries( ElmApp.ToFlatDictionaryWithPathComparer(appConfigFilesNamesAndContents)); context.Response.StatusCode = 200; context.Response.Headers.ContentLength = appConfigZipArchive.LongLength; context.Response.Headers.Add("Content-Disposition", new ContentDispositionHeaderValue("attachment") { FileName = appConfigHashBase16 + ".zip" }.ToString()); context.Response.Headers.Add("Content-Type", new MediaTypeHeaderValue("application/zip").ToString()); await context.Response.Body.WriteAsync(appConfigZipArchive); return; } if (requestPathIsDeployAppConfigAndInitElmAppState || context.Request.Path.Equals(new PathString(PathApiDeployAppConfigAndMigrateElmAppState))) { if (!string.Equals(context.Request.Method, "post", StringComparison.InvariantCultureIgnoreCase)) { context.Response.StatusCode = 405; await context.Response.WriteAsync("Method not supported."); return; } var memoryStream = new MemoryStream(); context.Request.Body.CopyTo(memoryStream); var webAppConfigZipArchive = memoryStream.ToArray(); { try { var filesFromZipArchive = ZipArchive.EntriesFromZipArchive(webAppConfigZipArchive).ToImmutableList(); if (filesFromZipArchive.Count < 1) { throw new Exception("Contains no files."); } } catch (Exception e) { context.Response.StatusCode = 400; await context.Response.WriteAsync("Malformed web app config zip-archive:\n" + e); return; } } var appConfigTree = Composition.TreeFromSetOfBlobsWithCommonFilePath( ZipArchive.EntriesFromZipArchive(webAppConfigZipArchive)); var appConfigComponent = Composition.FromTree(appConfigTree); processStoreWriter.StoreComponent(appConfigComponent); var appConfigValueInFile = new ProcessStoreSupportingMigrations.ValueInFileStructure { HashBase16 = CommonConversion.StringBase16FromByteArray(Composition.GetHash(appConfigComponent)) }; var compositionLogEvent = requestPathIsDeployAppConfigAndInitElmAppState ? new ProcessStoreSupportingMigrations.CompositionLogRecordInFile.CompositionEvent { DeployAppConfigAndInitElmAppState = appConfigValueInFile, } : new ProcessStoreSupportingMigrations.CompositionLogRecordInFile.CompositionEvent { DeployAppConfigAndMigrateElmAppState = appConfigValueInFile, }; await attemptContinueWithCompositionEventAndSendHttpResponse(compositionLogEvent); return; } if (context.Request.Path.StartsWithSegments(new PathString(PathApiRevertProcessTo), out var revertToRemainingPath)) { if (!string.Equals(context.Request.Method, "post", StringComparison.InvariantCultureIgnoreCase)) { context.Response.StatusCode = 405; await context.Response.WriteAsync("Method not supported."); return; } var processVersionId = revertToRemainingPath.ToString().Trim('/'); var processVersionComponent = new ProcessStoreReaderInFileStore(processStoreFileStore).LoadComponent(processVersionId); if (processVersionComponent == null) { context.Response.StatusCode = 404; await context.Response.WriteAsync("Did not find process version '" + processVersionId + "'."); return; } await attemptContinueWithCompositionEventAndSendHttpResponse(new CompositionLogRecordInFile.CompositionEvent { RevertProcessTo = new ValueInFileStructure { HashBase16 = processVersionId }, }); return; } if (context.Request.Path.Equals(new PathString(PathApiElmAppState))) { if (publicAppHost == null) { context.Response.StatusCode = 400; await context.Response.WriteAsync("Not possible because there is no app (state)."); return; } if (string.Equals(context.Request.Method, "get", StringComparison.InvariantCultureIgnoreCase)) { var processVolatileRepresentation = publicAppHost?.processVolatileRepresentation; var components = new List <Composition.Component>(); var storeWriter = new DelegatingProcessStoreWriter { StoreComponentDelegate = components.Add, StoreProvisionalReductionDelegate = _ => { }, SetCompositionLogHeadRecordDelegate = _ => throw new Exception("Unexpected use of interface."), }; var reductionRecord = processVolatileRepresentation?.StoreReductionRecordForCurrentState(storeWriter); if (reductionRecord == null) { context.Response.StatusCode = 500; await context.Response.WriteAsync("Not possible because there is no Elm app deployed at the moment."); return; } var elmAppStateReductionHashBase16 = reductionRecord.elmAppState?.HashBase16; var elmAppStateReductionComponent = components.First(c => CommonConversion.StringBase16FromByteArray(Composition.GetHash(c)) == elmAppStateReductionHashBase16); var elmAppStateReductionString = Encoding.UTF8.GetString(elmAppStateReductionComponent.BlobContent.ToArray()); context.Response.StatusCode = 200; context.Response.ContentType = "application/json"; await context.Response.WriteAsync(elmAppStateReductionString); return; } else { if (string.Equals(context.Request.Method, "post", StringComparison.InvariantCultureIgnoreCase)) { var elmAppStateToSet = new StreamReader(context.Request.Body, System.Text.Encoding.UTF8).ReadToEndAsync().Result; var elmAppStateComponent = Composition.Component.Blob(Encoding.UTF8.GetBytes(elmAppStateToSet)); var appConfigValueInFile = new ProcessStoreSupportingMigrations.ValueInFileStructure { HashBase16 = CommonConversion.StringBase16FromByteArray(Composition.GetHash(elmAppStateComponent)) }; processStoreWriter.StoreComponent(elmAppStateComponent); await attemptContinueWithCompositionEventAndSendHttpResponse( new ProcessStoreSupportingMigrations.CompositionLogRecordInFile.CompositionEvent { SetElmAppState = appConfigValueInFile }); return; } else { context.Response.StatusCode = 405; await context.Response.WriteAsync("Method not supported."); return; } } } if (context.Request.Path.Equals(new PathString(PathApiReplaceProcessHistory))) { var memoryStream = new MemoryStream(); context.Request.Body.CopyTo(memoryStream); var webAppConfigZipArchive = memoryStream.ToArray(); var replacementFiles = ZipArchive.EntriesFromZipArchive(webAppConfigZipArchive) .Select(filePathAndContent => (path: filePathAndContent.name.Split(new[] { '/', '\\' }).ToImmutableList() , content: filePathAndContent.content)) .ToImmutableList(); lock (publicAppLock) { lock (processStoreFileStore) { stopPublicApp(); foreach (var filePath in processStoreFileStore.ListFilesInDirectory(ImmutableList <string> .Empty).ToImmutableList()) { processStoreFileStore.DeleteFile(filePath); } foreach (var replacementFile in replacementFiles) { processStoreFileStore.SetFileContent(replacementFile.path, replacementFile.content); } startPublicApp(); } } context.Response.StatusCode = 200; await context.Response.WriteAsync("Successfully replaced the process history."); return; } if (context.Request.Path.StartsWithSegments( new PathString(PathApiProcessHistoryFileStoreGetFileContent), out var remainingPathString)) { if (!string.Equals(context.Request.Method, "get", StringComparison.InvariantCultureIgnoreCase)) { context.Response.StatusCode = 405; await context.Response.WriteAsync("Method not supported."); return; } var filePathInStore = remainingPathString.ToString().Trim('/').Split('/').ToImmutableList(); var fileContent = processStoreFileStore.GetFileContent(filePathInStore); if (fileContent == null) { context.Response.StatusCode = 404; await context.Response.WriteAsync("No file at '" + string.Join("/", filePathInStore) + "'."); return; } context.Response.StatusCode = 200; context.Response.ContentType = "application/octet-stream"; await context.Response.Body.WriteAsync(fileContent); return; } (int statusCode, string responseBodyString)attemptContinueWithCompositionEvent( ProcessStoreSupportingMigrations.CompositionLogRecordInFile.CompositionEvent compositionLogEvent) { lock (processStoreFileStore) { lock (publicAppLock) { publicAppHost?.processVolatileRepresentation?.StoreReductionRecordForCurrentState(processStoreWriter); var(projectedFiles, projectedFileReader) = IProcessStoreReader.ProjectFileStoreReaderForAppendedCompositionLogEvent( originalFileStore: processStoreFileStore, compositionLogEvent: compositionLogEvent); using (var projectedProcess = PersistentProcess.PersistentProcessVolatileRepresentation.Restore( new ProcessStoreReaderInFileStore(projectedFileReader), _ => { })) { if (compositionLogEvent.DeployAppConfigAndMigrateElmAppState != null || compositionLogEvent.SetElmAppState != null) { if (projectedProcess.lastSetElmAppStateResult?.Ok == null) { return(statusCode: 400, responseBodyString: "Failed to migrate Elm app state for this deployment: " + projectedProcess.lastSetElmAppStateResult?.Err); } } } foreach (var projectedFilePathAndContent in projectedFiles) { processStoreFileStore.SetFileContent( projectedFilePathAndContent.filePath, projectedFilePathAndContent.fileContent); } startPublicApp(); return(statusCode: 200, responseBodyString: "Successfully deployed this configuration and started the web server."); } } } async System.Threading.Tasks.Task attemptContinueWithCompositionEventAndSendHttpResponse( ProcessStoreSupportingMigrations.CompositionLogRecordInFile.CompositionEvent compositionLogEvent) { var(statusCode, responseBodyString) = attemptContinueWithCompositionEvent(compositionLogEvent); context.Response.StatusCode = statusCode; await context.Response.WriteAsync(responseBodyString); return; } if (context.Request.Path.Equals(PathString.Empty) || context.Request.Path.Equals(new PathString("/"))) { context.Response.StatusCode = 200; await context.Response.WriteAsync( "Welcome to Elm-fullstack version " + Program.AppVersionId + ".\n" + "To learn about this admin interface, see http://elm-fullstack.org/"); return; } context.Response.StatusCode = 404; await context.Response.WriteAsync("Not Found"); return; }); }
static public async Task MiddlewareFromWebAppConfig( WebAppConfiguration webAppConfig, HttpContext context, Func <Task> next) => await StaticFilesMiddlewareFromWebAppConfig(webAppConfig, context, () => RateLimitMiddlewareFromWebAppConfig(webAppConfig, context, () => AdminPersistentProcessMiddlewareFromWebAppConfig(webAppConfig, context, next)));
static public WebHostTestSetup Setup( WebAppConfiguration webAppConfig, Func <DateTimeOffset> persistentProcessHostDateTime = null) => Setup( webAppConfig, builder => builder.WithSettingDateTimeOffsetDelegate(persistentProcessHostDateTime ?? (() => DateTimeOffset.UtcNow)));
public WebApiServer(WebAppConfiguration config) : base(config) { _config = config; }
static async Task AdminPersistentProcessMiddlewareFromWebAppConfig( WebAppConfiguration webAppConfig, HttpContext context, Func <Task> next) { if (context.Request.Path.StartsWithSegments( new PathString(Configuration.AdminPath), out var requestPathInAdmin)) { var configuration = context.RequestServices.GetService <IConfiguration>(); var rootPassword = configuration.GetValue <string>(Configuration.AdminRootPasswordSettingKey); var expectedAuthorization = Configuration.BasicAuthenticationForAdminRoot(rootPassword); context.Request.Headers.TryGetValue("Authorization", out var requestAuthorizationHeaderValue); AuthenticationHeaderValue.TryParse( requestAuthorizationHeaderValue.FirstOrDefault(), out var requestAuthorization); if (!(0 < rootPassword?.Length)) { context.Response.StatusCode = 403; await context.Response.WriteAsync("Forbidden"); return; } var buffer = new byte[400]; var decodedRequestAuthorizationParameter = Convert.TryFromBase64String(requestAuthorization?.Parameter ?? "", buffer, out var bytesWritten) ? Encoding.UTF8.GetString(buffer, 0, bytesWritten) : null; if (!(string.Equals(expectedAuthorization, decodedRequestAuthorizationParameter) && string.Equals("basic", requestAuthorization?.Scheme, StringComparison.OrdinalIgnoreCase))) { context.Response.StatusCode = 401; context.Response.Headers.Add( "WWW-Authenticate", @"Basic realm=""" + context.Request.Host + Configuration.AdminPath + @""", charset=""UTF-8"""); await context.Response.WriteAsync("Unauthorized"); return; } if (string.Equals(requestPathInAdmin, Configuration.ApiPersistentProcessStatePath)) { if (string.Equals(context.Request.Method, "post", StringComparison.InvariantCultureIgnoreCase)) { var stateToSet = new StreamReader(context.Request.Body, System.Text.Encoding.UTF8).ReadToEndAsync().Result; var processStoreWriter = context.RequestServices.GetService <ProcessStore.IProcessStoreWriter>(); var persistentProcess = context.RequestServices.GetService <IPersistentProcess>(); var compositionRecord = persistentProcess.SetState(stateToSet); processStoreWriter.AppendSerializedCompositionRecord(compositionRecord.serializedCompositionRecord); context.Response.StatusCode = 200; await context.Response.WriteAsync("Successfully set process state."); return; } if (string.Equals(context.Request.Method, "get", StringComparison.InvariantCultureIgnoreCase)) { var persistentProcess = context.RequestServices.GetService <IPersistentProcess>(); var reducedValueLiteralString = persistentProcess.ReductionRecordForCurrentState().ReducedValueLiteralString; context.Response.StatusCode = 200; await context.Response.WriteAsync(reducedValueLiteralString); return; } context.Response.StatusCode = 405; await context.Response.WriteAsync("Method not supported."); return; } context.Response.StatusCode = 404; await context.Response.WriteAsync("Not Found"); return; } await next?.Invoke(); }
BuildConfigurationZipArchive( Action <string> verboseLogWriteLine) { var currentDirectory = Environment.CurrentDirectory; Console.WriteLine("The currentDirectory is '" + currentDirectory + "'."); var elmAppFilesBeforeLowering = ElmApp.ToFlatDictionaryWithPathComparer( ElmApp.FilesFilteredForElmApp( Filesystem.GetAllFilesFromDirectory(Path.Combine(currentDirectory, ElmAppSubdirectoryName))) .Select(filePathAndContent => ((IImmutableList <string>)filePathAndContent.filePath.Split(new[] { '/', '\\' }).ToImmutableList(), filePathAndContent.fileContent))); Console.WriteLine("I found " + elmAppFilesBeforeLowering.Count + " files to build the Elm app."); var elmAppContainsFrontend = elmAppFilesBeforeLowering.ContainsKey(FrontendElmAppRootFilePath); Console.WriteLine("This Elm app contains " + (elmAppContainsFrontend ? "a" : "no") + " frontend at '" + string.Join("/", FrontendElmAppRootFilePath) + "'."); var loweredElmAppFiles = ElmApp.AsCompletelyLoweredElmApp( elmAppFilesBeforeLowering, ElmAppInterfaceConfig.Default, verboseLogWriteLine); var compileConfigFile = new Func <byte[]>(() => { WebAppConfigurationJsonStructure jsonStructure = null; var jsonFileSearchPath = Path.Combine(currentDirectory, "elm-fullstack.json"); if (File.Exists(jsonFileSearchPath)) { Console.WriteLine("I found a file at '" + jsonFileSearchPath + "'. I use this to build the configuration."); var jsonFile = File.ReadAllBytes(jsonFileSearchPath); jsonStructure = JsonConvert.DeserializeObject <WebAppConfigurationJsonStructure>(Encoding.UTF8.GetString(jsonFile)); } else { Console.WriteLine("I did not find a file at '" + jsonFileSearchPath + "'. I build the configuration without the 'elm-fullstack.json'."); } byte[] frontendWebFile = null; if (elmAppContainsFrontend) { var frontendWebHtml = ProcessFromElm019Code.CompileElmToHtml( loweredElmAppFiles, FrontendElmAppRootFilePath, elmMakeCommandAppendix: jsonStructure?.frontendWebElmMakeCommandAppendix); frontendWebFile = Encoding.UTF8.GetBytes(frontendWebHtml); } var staticFilesFromFrontendWeb = frontendWebFile == null ? Array.Empty <(IImmutableList <string> name, IImmutableList <byte> content)>() : new[] { (name: (IImmutableList <string>)ImmutableList.Create(FrontendWebStaticFileName), (IImmutableList <byte>)frontendWebFile.ToImmutableList()) }; var staticFilesSourceDirectory = Path.Combine(currentDirectory, StaticFilesSubdirectoryName); var staticFilesFromDirectory = Directory.Exists(staticFilesSourceDirectory) ? Filesystem.GetAllFilesFromDirectory(staticFilesSourceDirectory) .Select(nameAndContent => (name: (IImmutableList <string>)nameAndContent.name.Split(new[] { '/', '\\' }).ToImmutableList(), content: nameAndContent.content)) .ToImmutableList() : ImmutableList <(IImmutableList <string> name, IImmutableList <byte> content)> .Empty; Console.WriteLine("I found " + staticFilesFromDirectory.Count + " static files to include."); var staticFiles = staticFilesFromDirectory.AddRange(staticFilesFromFrontendWeb); var webAppConfig = new WebAppConfiguration() .WithElmApp(loweredElmAppFiles) .WithStaticFiles(staticFiles) .WithJsonStructure(jsonStructure); var webAppConfigFiles = webAppConfig.AsFiles(); var webAppConfigFile = ZipArchive.ZipArchiveFromEntries(webAppConfigFiles); return(webAppConfigFile); });