namespace Framework.Server
{
using Framework.App;
using Framework.Config;
using Framework.Json;
using Microsoft.AspNetCore.Http;
using System;
using System.IO;
using System.Threading.Tasks;
using System.Web;
internal class Request
{
///
/// Every request goes through here.
///
public async Task RunAsync(HttpContext context)
{
// await Task.Delay(500); // Simulate slow network.
UtilStopwatch.RequestBind();
try
{
UtilStopwatch.TimeStart(name: "Request");
UtilServer.Cors();
// Request path
string path = context.Request.Path;
// Get current website request from "ConfigServer.json"
AppSelector appSelector = new AppSelector();
var isRedirectHttps = appSelector.ConfigDomain.IsRedirectHttps && !context.Request.IsHttps;
if (isRedirectHttps)
{
// RedirectHttps on website level. Not on server middleware level.
string url = "https://" + context.Request.Host + context.Request.Path + context.Request.QueryString;
context.Response.Redirect(url);
}
else
{
// POST app.json
if (!await Post(context, path, appSelector))
{
// GET index.html from "Application.Server/Framework/Application.Website/Website01/browser/" (With server side rendering or serve index.html directly)
if (!await WebsiteServerSideRenderingAsync(context, path, appSelector, null))
{
// GET file from "Application.Server/Framework/Application.Website/Website01/browser/"
if (!await WebsiteFileAsync(context, path, appSelector))
{
// GET file from database or navigate to subpage.
if (!await FileDownloadAsync(context, path, appSelector))
{
context.Response.StatusCode = StatusCodes.Status404NotFound;
}
}
}
}
}
// Total time for one request.
UtilStopwatch.TimeStop(name: "Request");
// One log entry for one request.
UtilStopwatch.TimeLog();
}
finally
{
UtilStopwatch.RequestRelease();
}
}
///
/// Handle client web POST /app.json
///
private static async Task Post(HttpContext context, string path, AppSelector appSelector)
{
bool result = false;
if (path == "/app.json")
{
string jsonClient = await appSelector.ProcessAsync(context, null); // Process (Client http post)
context.Response.ContentType = UtilServer.ContentType(path);
await context.Response.WriteAsync(jsonClient);
result = true;
}
return result;
}
///
/// Divert request to "Application.Server/Framework/Application.Website/Website01/browser/"
///
private static async Task WebsiteServerSideRenderingAsync(HttpContext context, string navigatePath, AppSelector appSelector, AppJson appJson)
{
bool result = false;
// FolderNameServer
string folderNameServer = appSelector.ConfigWebsite.FolderNameServerGet(appSelector, "Application.Server/");
// FolderName
string folderName = UtilServer.FolderNameContentRoot() + folderNameServer;
if (!Directory.Exists(folderName))
{
throw new Exception(string.Format("Folder does not exis! Make sure cli build command did run. ({0})", folderName));
}
// Index.html
string pathIndexHtml = navigatePath;
if (!UtilServer.NavigatePathIsFileName(navigatePath))
{
pathIndexHtml += "index.html";
}
// FileName
string fileName = folderName + UtilFramework.FolderNameParse(null, pathIndexHtml);
if (File.Exists(fileName))
{
if (fileName.EndsWith(".html") && UtilFramework.StringNull(appSelector.AppTypeName) != null)
{
context.Response.ContentType = UtilServer.ContentType(fileName);
// Do not cache (*.html) page with included jsonBrowser. For example if user navigates to sub page (POST) and then opens an image
// and then navigates back, it forces browser to reload page and not to show an old cached page.
// See also: http://cristian.sulea.net/blog/disable-browser-caching-with-meta-html-tags/
context.Response.Headers.Add("Cache-Control", "no-cache, no-store, must-revalidate");
context.Response.Headers.Add("Pragma", "no-cache");
context.Response.Headers.Add("Expires", "0");
// Create page (*.html). Also if SSR is disabled.
string htmlIndex = await WebsiteServerSideRenderingAsync(context, appSelector, appJson);
// Google Analytics 4
if (UtilFramework.StringNull(appSelector.ConfigDomain.GoogleAnalyticsId) != null)
{
htmlIndex = htmlIndex.Replace("G-XXXXXXXXXX", appSelector.ConfigDomain.GoogleAnalyticsId);
}
// Google AdSense
if (UtilFramework.StringNull(appSelector.ConfigDomain.GoogleAdSenseId) != null)
{
htmlIndex = htmlIndex.Replace("ca-pub-XXXXXXXXXXXXXXXX", appSelector.ConfigDomain.GoogleAdSenseId);
}
await context.Response.WriteAsync(htmlIndex);
result = true;
}
}
return result;
}
///
/// Browser GET request to download file.
///
private static async Task FileDownloadAsync(HttpContext context, string path, byte[] data)
{
bool result = false;
if (data != null)
{
UtilFramework.Assert(data != null);
string fileName = UtilFramework.FolderNameParse(null, path);
context.Response.ContentType = UtilServer.ContentType(fileName);
await context.Response.Body.WriteAsync(data, 0, data.Length);
result = true;
}
return result;
}
///
/// Browser request to download file or navigate to subpage.
///
private static async Task FileDownloadAsync(HttpContext context, string navigatePath, AppSelector appSelector)
{
bool result;
var appJson = appSelector.CreateAppJson(); // Without deserialize session.
var navigateResult = await appJson.NavigateInternalAsync(navigatePath, appSelector);
if (navigateResult.RedirectPath != null)
{
context.Response.Redirect(navigateResult.RedirectPath);
result = true;
}
else
{
if (navigateResult.IsSession)
{
var appJsonSession = await appSelector.CreateAppJsonSession(context); // With deserialize session.
var navigateSessionResult = await appJsonSession.NavigateSessionInternalAsync(navigatePath, isAddHistory: false, appSelector);
if (navigateSessionResult.IsPage)
{
if (navigateSessionResult.RedirectPath != null)
{
context.Response.Redirect(navigateSessionResult.RedirectPath);
result = true;
}
else
{
// Send page together with HTTP 404 not found code
if (navigateSessionResult.IsPageNotFound)
{
// Do not serialize custom error page and reset request, response count
context.Response.StatusCode = StatusCodes.Status404NotFound;
appJsonSession.IsPageNotFound = true;
appJsonSession.RequestCount -= 1;
appJsonSession.ResponseCount -= 1;
// Custom error page rendering
await WebsiteServerSideRenderingAsync(context, "/", appSelector, appJsonSession);
result = true;
}
else
{
result = await WebsiteServerSideRenderingAsync(context, "/", appSelector, appJsonSession);
}
}
}
else
{
// File download with session
result = await FileDownloadAsync(context, navigatePath, navigateSessionResult.Data);
}
}
else
{
// File download without session
result = await FileDownloadAsync(context, navigatePath, navigateResult.Data);
}
}
return result;
}
///
/// Render first html GET request. Also if SSR is disabled. Include always jsonBrowser into (*.html) response file.
///
private static async Task WebsiteServerSideRenderingAsync(HttpContext context, AppSelector appSelector, AppJson appJson)
{
string url;
if (UtilServer.IsIssServer)
{
// Running on IIS Server.
url = context.Request.IsHttps ? "https://" : "http://";
url += context.Request.Host.ToUriComponent() + "/Framework/Application.Website/" + UtilFramework.FolderNameParse(appSelector.ConfigWebsite.FolderNameAngularWebsite) + "server/main.js"; // Url of server side rendering when running on IIS Server
}
else
{
// Running in Visual Studio. See also method StartUniversalServerAngular();
url = "http://localhost:" + (appSelector.ConfigWebsite.FolderNameAngularPort).ToString() + "/"; // Url of server side rendering when running in Visual Studio
}
// Process AppJson
string jsonClient = await appSelector.ProcessAsync(context, appJson); // Process (For first server side rendering)
bool isServerSideRendering = ConfigServer.Load().IsServerSideRendering;
string indexHtml;
if (isServerSideRendering)
{
// index.html server side rendering
indexHtml = await UtilServer.WebPost(url, jsonClient); // Server side rendering POST. http://localhost:8080/Framework/Application.Website/Website01/server/main.js
}
else
{
// index.html serve directly
string fileName = UtilServer.FolderNameContentRoot() + UtilFramework.FolderNameParse(appSelector.ConfigWebsite.FolderNameServerGet(appSelector, "Application.Server/"), "/index.html");
indexHtml = UtilFramework.FileLoad(fileName);
}
// Set jsonBrowser in index.html.
string scriptFind = ""; //" "; // For example Html5Boilerplate build process renames var jsonBrowser to a.
string scriptReplace = "";
indexHtml = UtilFramework.Replace(indexHtml, scriptFind, scriptReplace); // Send jsonBrowser with index.html to client for both SSR and not SSR.
return indexHtml;
}
///
/// Returns true, if file found in folder "Application.Server/Framework/Application.Website/Website01/browser/"
///
private async Task WebsiteFileAsync(HttpContext context, string path, AppSelector appSelector)
{
bool result = false;
if (UtilServer.NavigatePathIsFileName(path))
{
// Serve fileName
string fileName = UtilServer.FolderNameContentRoot() + UtilFramework.FolderNameParse(appSelector.ConfigWebsite.FolderNameServerGet(appSelector, "Application.Server/"), path);
if (File.Exists(fileName))
{
context.Response.ContentType = UtilServer.ContentType(fileName);
await context.Response.SendFileAsync(fileName);
return true;
}
}
return result;
}
}
}