From a6447ecb3705a77c49805e8321f4f01103e2d27c Mon Sep 17 00:00:00 2001 From: John Cardinal Date: Sun, 25 Oct 2026 19:04:24 -0700 Subject: [PATCH] case 4632 - reports render under IIS: the report page's API calls are relayed by the server Inside IIS (in-process) AyaNova has no port of its own, so the report page could not reach the API: user reports called http://127.0.0.1: (reset on https), notification reports 127.0.0.1:7575 (refused). Under IIS the page's requests under the API URL are now intercepted and made by the server (util/ReportApiRelay.cs) to the local address and port a user's request arrived on, with its host name and scheme; notification reports use the latest signed-in request, else the override. Outside IIS nothing changes. The notification token check requires a request from this machine, no longer host 127.0.0.1. Manual (IIS install, override page) and changelog updated. Deferred: emailed PDF content type application/pdf/pdf. Co-Authored-By: Claude Opus 5.5 --- devdocs/modernization/NET10-PORT.md | 3 +- docs/8.0/ayanova/docs/changelog.md | 1 + ...onfig-report-rendering-api-url-override.md | 2 + .../ayanova/docs/ops-install-windows-iis.md | 10 +- .../AyaNova/Controllers/ReportController.cs | 4 +- server/AyaNova/Startup.cs | 33 ++-- server/AyaNova/biz/ReportBiz.cs | 15 +- .../generator/CoreJobCustomerNotify.cs | 4 +- server/AyaNova/util/ReportApiRelay.cs | 176 ++++++++++++++++++ server/AyaNova/util/ServerBootConfig.cs | 3 + 10 files changed, 227 insertions(+), 24 deletions(-) create mode 100644 server/AyaNova/util/ReportApiRelay.cs diff --git a/devdocs/modernization/NET10-PORT.md b/devdocs/modernization/NET10-PORT.md index 290a81b8..ff27f35c 100644 --- a/devdocs/modernization/NET10-PORT.md +++ b/devdocs/modernization/NET10-PORT.md @@ -221,7 +221,7 @@ John's "next release" cases for AyaNova (exported 2026-10-24). Each is its own s | 4636 | Docs: turn off 2FA before v7 to v8 migration | 1, docs | | 4634 | Docs: custom checkbox shows nothing for older records | 1, docs | | 4633 | Docs: pg_dump path quoting on Windows (likely fixed by the port; confirm) | 1, docs | -| 4632 | AYANOVA_REPORT_RENDER_API_URL_OVERRIDE breaks customer notification reports (do before or with 4719) | 1, investigate. **Moved to the end with 4719** (John, 2026-10-24): needs an IIS + TLS test setup to reproduce; behaves differently for customers with apparently the same setup, so a thorough investigation on a real IIS install | +| 4632 | AYANOVA_REPORT_RENDER_API_URL_OVERRIDE breaks customer notification reports (do before or with 4719) | 1. **Done.** Cause, reproduced on a Windows Server 2025 Hyper-V VM (IIS, https-only site with a host name, as for internet access): AyaNova runs inside IIS (in-process), so nothing listens on `AYANOVA_USE_URLS`; user reports called `http://127.0.0.1:` (443 over https: connection reset), notification reports `127.0.0.1:7575` (refused); the override only fixed user reports, and only where the server reaches itself by its public name. Fix: under IIS the report page's requests under the API URL are intercepted (PuppeteerSharp request interception) and made by the server (`util/ReportApiRelay.cs`) to the local address and port a user's request arrived on, with its host name and scheme (IIS binding and certificate match, no DNS/router hairpin); notification reports use the latest signed-in request, else the override; the page is opened at the API origin so its calls are same-origin; outside IIS nothing changes. The notification token check no longer requires host `127.0.0.1`, only a request from this machine. Proof: VM before (both fail) and after (user report 1.6 s and notification email with PDF, https-only with and without the override); locally 22 stock reports identical with the relay forced on (temporary switch, removed); integration suite = baseline. Manual: IIS install page and override page; changelog | | 4628 | Deleting a work order tied to a CSR gives a raw server error | 1, bug | | 4623 | Docs: migration, what is erased and what is kept | 1, docs | | 4602 | Logo upload UI: hide Upload and Delete until usable | 1, client | @@ -275,6 +275,7 @@ Found during the port, out of scope. One line each. - **Own case after the port (John):** the subscription server setup script is hard-coded in `biz-subscription-server.vue`, so every change needs a new Sockeye client build. Make it editable without a rebuild - **Improvement, own case after the port (John to create):** bring back logging of JavaScript error details from report page console errors, as Sockeye's port did. AyaNova logs only `ERROR: {message text}`; the per-argument detail (`arg instanceof Error ? arg.message : arg`) is commented out at `biz/ReportBiz.cs:873-876` since case 4586, because `IJSHandle.ExecutionContext` was removed. Sockeye 8.1.0 (`server/biz/ReportBiz.cs:575`) evaluates on the handle instead: `args.Message.Args.Select(arg => arg.EvaluateFunctionAsync("(arg) => arg instanceof Error ? arg.message : arg"))`, which also needs the console handler to be `async` again - Pre-existing, same in 8.2.4 (found in the 4579 sandbox test, John: Deferred): rendering a report for a record that does not exist (e.g. work order ids 1 and 2 in a trial without sample data) fails the job with a generic `ArgumentNullException` ("Value cannot be null. (Parameter 'o')") from `JObject.FromObject` in `WorkOrderBiz.GetReportData` (`WorkOrderBiz.cs:1030`), instead of skipping the missing record or saying it is not found. Other object types' `GetReportData` likely do the same +- Pre-existing (found in the 4632 IIS test, John: Deferred): emailed report attachments have the content type `application/pdf/pdf`, because `Mailer.cs:51` builds `new MimePart("application/pdf", "pdf")` (media type and subtype) instead of `new MimePart("application", "pdf")`. Mail programs still open the file by its `.pdf` name ## Log diff --git a/docs/8.0/ayanova/docs/changelog.md b/docs/8.0/ayanova/docs/changelog.md index b7a52457..eb742b7a 100644 --- a/docs/8.0/ayanova/docs/changelog.md +++ b/docs/8.0/ayanova/docs/changelog.md @@ -37,6 +37,7 @@ IN PROGRESS - Linux: Ubuntu 24.04 and 26.04 are now tested and supported. On Ubuntu 24.04 and newer the .NET runtime is installed from Ubuntu's own package archive. - Docs: updated the [Linux server](ops-install-linux-server.md), [Linux desktop](ops-install-linux-desktop.md), [Windows local network](ops-install-windows-lan.md) and [Windows IIS](ops-install-windows-iis.md) installation and upgrade instructions for .NET 10 and Ubuntu 24.04 / 26.04. - Docs: updated the database password guidance in the installation and [backup](ops-form-backup.md) documentation. +- Windows IIS: reports, including reports attached to customer notifications, now render under IIS with any site bindings, HTTPS included, without the [report rendering API URL override](ops-config-report-rendering-api-url-override.md) setting. See [report rendering under IIS](ops-install-windows-iis.md#report-rendering-under-iis). - Windows: the installers no longer give all local users write access to the AyaNova program folder; only administrators can change files there. Upgrading removes the earlier write access. - Windows: the local network installer and the [local network](ops-install-windows-lan.md) and [IIS](ops-install-windows-iis.md) installation instructions now link to the official PostgreSQL download page (the third-party guide linked before is no longer available), and the configuration examples assume PostgreSQL 18. - Docs: the Windows installation instructions now state that only one type of AyaNova 8 installation (single user, local network or IIS) can be on a computer at a time. diff --git a/docs/8.0/ayanova/docs/ops-config-report-rendering-api-url-override.md b/docs/8.0/ayanova/docs/ops-config-report-rendering-api-url-override.md index 20cd5a5a..db1fb47e 100644 --- a/docs/8.0/ayanova/docs/ops-config-report-rendering-api-url-override.md +++ b/docs/8.0/ayanova/docs/ops-config-report-rendering-api-url-override.md @@ -2,6 +2,8 @@ The report rendering api override setting is used in situations where report rendering code is unable to contact the local AyaNova api server via it's localhost address. +**IIS:** as of AyaNova 8.3.0 this setting is no longer needed when AyaNova runs inside IIS. AyaNova sends the report's requests to the AyaNova server itself, to the same address and site name users connect to (see [report rendering under IIS](ops-install-windows-iis.md#report-rendering-under-iis)). A value set for an earlier version does no harm and can be removed. + The symptom of this issue is reports failing to render with an error displayed containing text similar to "JobFailed: PuppeteerSharp -> Evaluation failed: TypeError: Failed to fetch at ayPostToAPI" Reports are rendered by a "headless" web browser running at the AyaNova server which executes the javascript required to render the report. Most report scripts will need to contact the AyaNova API to fetch data like translations or logos. In some scenarios such as some specific configurations when using IIS as the front end for AyaNova the local headless browser is prevented from contacting the AyaNova server due to accessing it via the `localhost` address. In these situations it's necessary to tell the script to use an alternative address. diff --git a/docs/8.0/ayanova/docs/ops-install-windows-iis.md b/docs/8.0/ayanova/docs/ops-install-windows-iis.md index df67ca43..f2606d57 100644 --- a/docs/8.0/ayanova/docs/ops-install-windows-iis.md +++ b/docs/8.0/ayanova/docs/ops-install-windows-iis.md @@ -390,15 +390,13 @@ Let's Encrypt provides free SSL certificates and lists [several Windows clients] The very first time a report is rendered in AyaNova there will be a delay while the Chromium browser report rendering components are installed. -#### Report rendering error +#### Report rendering under IIS -In some cases there may be a futher setting required for report rendering. +Reports, including reports attached to customer notifications, render under IIS with any site bindings, HTTPS included, and no further setting is needed. When AyaNova runs inside IIS, the requests a report makes to the AyaNova server are sent by AyaNova itself to the same address and site name that users connect to. -If you render a report and see this error: +Earlier versions of AyaNova needed the [AYANOVA_REPORT_RENDER_API_URL_OVERRIDE](ops-config-report-rendering-api-url-override.md) setting under some IIS configurations. If it is set from an earlier version it does no harm and can be removed. -`JobFailed: PuppeteerSharp -> Evaluation failed: TypeError: Failed to fetch at ayPostToAPI` - -You will need to set the [AYANOVA_REPORT_RENDER_API_URL_OVERRIDE](ops-config-report-rendering-api-url-override.md) setting in the [AyaNova server configuration](#5-ayanova-server-configuration) to your AyaNova server's external URL address to enable report rendering for your configuration. +If a report fails with an error that mentions "AyaNova API request ... failed", the error text says why. For example, the site's SSL certificate must be valid for the site name users connect to. #### Getting started and using AyaNova diff --git a/server/AyaNova/Controllers/ReportController.cs b/server/AyaNova/Controllers/ReportController.cs index 7c12fada..22f15adf 100644 --- a/server/AyaNova/Controllers/ReportController.cs +++ b/server/AyaNova/Controllers/ReportController.cs @@ -214,7 +214,9 @@ namespace AyaNova.Api.Controllers return BadRequest(new ApiErrorResponse(ModelState)); var httpConnectionFeature = HttpContext.Features.Get(); var API_URL = $"http://127.0.0.1:{httpConnectionFeature.LocalPort}/api/{AyaNovaVersion.CurrentApiVersion}/"; - var result = await biz.RequestRenderReport(reportRequest, DateTime.UtcNow.AddMinutes(ServerBootConfig.AYANOVA_REPORT_RENDERING_TIMEOUT), API_URL, UserNameFromContext.Name(HttpContext.Items)); + //case 4632: inside IIS the report page's API calls are relayed to where this request arrived + var relayTarget = ServerBootConfig.HOSTED_IN_IIS ? ReportApiRelay.FromRequest(HttpContext) : null; + var result = await biz.RequestRenderReport(reportRequest, DateTime.UtcNow.AddMinutes(ServerBootConfig.AYANOVA_REPORT_RENDERING_TIMEOUT), API_URL, UserNameFromContext.Name(HttpContext.Items), relayTarget); if (result == null) return BadRequest(new ApiErrorResponse(biz.Errors)); else diff --git a/server/AyaNova/Startup.cs b/server/AyaNova/Startup.cs index d5a737d3..b7337c6a 100644 --- a/server/AyaNova/Startup.cs +++ b/server/AyaNova/Startup.cs @@ -325,6 +325,11 @@ namespace AyaNova //Store a reference to the dependency injection service for static classes ServiceProviderProvider.Provider = app.ApplicationServices; + //case 4632: inside IIS the report page's API calls are relayed (ReportApiRelay) + ServerBootConfig.HOSTED_IN_IIS = app.ApplicationServices.GetService()?.GetType().FullName?.Contains(".IIS.", StringComparison.Ordinal) == true; + if (ServerBootConfig.HOSTED_IN_IIS) + _newLog.LogInformation("Running inside IIS: report API calls are relayed to the address of the users' requests"); + //Enable ability to handle reverse proxy //case 4732: a proxy on this computer (loopback) is always trusted; proxies on other computers only when named in AYANOVA_TRUSTED_PROXIES var forwardedHeadersOptions = new ForwardedHeadersOptions @@ -472,23 +477,21 @@ namespace AyaNova { //It may be a local report render request from a job + //case 4632: from this machine only; the host name is no longer required to be 127.0.0.1, inside IIS the + //request is relayed to the site's own address and host name (ReportApiRelay) bool bLocalReportRenderRequest = false; - if (context.Request.Host.Host == "127.0.0.1") + if (context.Request.IsLocal()) { - //check if token has j value set - if (context.Request.IsLocal()) + //if this is a generate report internal token this will be the override language set + //and it's existance is enough to indicate it was set internally by this server + var rpl = context.User.Claims.FirstOrDefault(c => c.Type == "rpl"); + if (rpl != null) { - //if this is a generate report internal token this will be the override language set - //and it's existance is enough to indicate it was set internally by this server - var rpl = context.User.Claims.FirstOrDefault(c => c.Type == "rpl"); - if (rpl != null) - { - //it's a local request, the jwt already passed earlier so we issued it and it has the correct claim set so allow it - bLocalReportRenderRequest = true; - //set override language - context.Request.HttpContext.Items["AY_TRANSLATION_ID"] = Convert.ToInt64(rpl.Value); - await next.Invoke(); - } + //it's a local request, the jwt already passed earlier so we issued it and it has the correct claim set so allow it + bLocalReportRenderRequest = true; + //set override language + context.Request.HttpContext.Items["AY_TRANSLATION_ID"] = Convert.ToInt64(rpl.Value); + await next.Invoke(); } } if (!bLocalReportRenderRequest) @@ -517,6 +520,8 @@ namespace AyaNova })); return; } + //case 4632: inside IIS, where this signed-in request arrived is where notification reports send their API calls + ReportApiRelay.Remember(context); await next.Invoke(); } } diff --git a/server/AyaNova/biz/ReportBiz.cs b/server/AyaNova/biz/ReportBiz.cs index 1ab5a910..553ffd91 100644 --- a/server/AyaNova/biz/ReportBiz.cs +++ b/server/AyaNova/biz/ReportBiz.cs @@ -518,7 +518,8 @@ namespace AyaNova.Biz DataListReportRequest reportRequest, DateTime renderTimeOutExpiry, string apiUrl, - string userName + string userName, + ReportApiTarget relayTarget = null ) { var log = AyaNova.Util.ApplicationLogging.CreateLogger( @@ -676,6 +677,7 @@ What could be the issue here? requestIsCustomerWorkOrderReport = RequestIsCustomerWorkOrderReport, apiUrl = apiUrl, userName = userName, + relayTarget = relayTarget, } ); @@ -711,6 +713,8 @@ What could be the issue here? .ToObject(); var apiUrl = jobData["apiUrl"].ToObject(); var userName = jobData["userName"].ToObject(); + //case 4632: set when running inside IIS, see ReportApiRelay + var relayTarget = jobData["relayTarget"]?.Type == JTokenType.Object ? jobData["relayTarget"].ToObject() : null; var report = await ct.Report.FirstOrDefaultAsync(z => z.Id == reportRequest.ReportId); if (report == null) @@ -910,6 +914,7 @@ What could be the issue here? await HandleBrowserFailure(job, log, $"started from \"{lo.ExecutablePath}\" with arguments '{string.Join(" ", lo.Args)}' (on Linux check for missing libraries with ldd)", ex); return; } + IDisposable relayClient = null; using (var browser = launchedBrowser) using (var page = (await browser.PagesAsync())[0]) { @@ -946,6 +951,13 @@ What could be the issue here? } }; + //case 4632: inside IIS the page's API calls are relayed by the server, see ReportApiRelay + if (relayTarget != null) + { + log.LogDebug($"Relaying report API calls under {apiUrl} to {relayTarget}"); + relayClient = await ReportApiRelay.AttachAsync(page, apiUrl, relayTarget, TimeSpan.FromMinutes(ServerBootConfig.AYANOVA_REPORT_RENDERING_TIMEOUT), s => PageLog.AppendLine(s), log); + } + log.LogDebug($"Preparing page: adding base reporting scripts to page"); if (!ReportRenderManager.KeepGoing(job.GId)) return; @@ -1296,6 +1308,7 @@ What could be the issue here? $"Calling ReportRenderManager.RemoveJob to stop tracking this job/process" ); await ReportRenderManager.RemoveJob(job.GId, log, false); + relayClient?.Dispose(); } } diff --git a/server/AyaNova/generator/CoreJobCustomerNotify.cs b/server/AyaNova/generator/CoreJobCustomerNotify.cs index 1e4e16bb..a33d6ebc 100644 --- a/server/AyaNova/generator/CoreJobCustomerNotify.cs +++ b/server/AyaNova/generator/CoreJobCustomerNotify.cs @@ -336,7 +336,9 @@ namespace AyaNova.Biz ServerBootConfig.AYANOVA_REPORT_RENDERING_TIMEOUT ), API_URL, - ServerBootConfig.CUSTOMER_NOTIFICATION_ATTACHED_REPORT_RENDER_USERNAME + ServerBootConfig.CUSTOMER_NOTIFICATION_ATTACHED_REPORT_RENDER_USERNAME, + //case 4632: inside IIS nothing listens on that port, the API calls are relayed to where users' requests arrive + ReportApiRelay.ForBackgroundRender() ); if (jobid == null) { diff --git a/server/AyaNova/util/ReportApiRelay.cs b/server/AyaNova/util/ReportApiRelay.cs new file mode 100644 index 00000000..32b1f696 --- /dev/null +++ b/server/AyaNova/util/ReportApiRelay.cs @@ -0,0 +1,176 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Net.Sockets; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Logging; +using PuppeteerSharp; + +namespace AyaNova.Util +{ + /// + /// Where the report page's API calls are sent when AyaNova runs inside IIS: the address and port a user's request + /// arrived on, with the host name and scheme of that request + /// + internal sealed class ReportApiTarget + { + public string Scheme { get; set; } + public string Host { get; set; }//host header value, with the port when the request had one + public string Address { get; set; } + public int Port { get; set; } + + internal string BaseUrl => $"{Scheme}://{Host}/api/{AyaNovaVersion.CurrentApiVersion}/"; + public override string ToString() => $"{Scheme}://{Host} via {Address}:{Port}"; + } + + /// + /// case 4632: inside IIS (in-process hosting) AyaNova has no port of its own: nothing listens on AYANOVA_USE_URLS, and + /// the port of the user's request is IIS's, often https-only and bound to a host name. So the report page could not + /// reach the API at http://127.0.0.1:port (user reports, or notification reports at the AYANOVA_USE_URLS port), and the + /// override setting only helped where the server can reach itself by its public name. Under IIS the report page's + /// requests to the API URL are intercepted and made here instead, to the local address and port a user's request + /// arrived on, with that request's host name and scheme: the IIS binding and certificate match, and no DNS or router + /// is involved. Outside IIS nothing changes (the page calls http://127.0.0.1:port itself, which works with Kestrel) + /// + internal static class ReportApiRelay + { + //path the page is first opened at, so it has the API's origin and its API calls are same-origin (no CORS preflight, + //which the browser would send itself, past the interception) + private const string PAGE_PATH = "ay-report-render-page"; + + private static volatile ReportApiTarget _lastSeen; + + /// + /// The address, port, host name and scheme this request arrived with, for relaying a report started by it + /// + internal static ReportApiTarget FromRequest(HttpContext context) + { + var address = context.Connection.LocalIpAddress; + if (address == null) + return null; + if (address.IsIPv4MappedToIPv6) + address = address.MapToIPv4(); + //the IIS server variable says if this connection is TLS; Request.Scheme may come from a proxy's X-Forwarded-Proto + var https = string.Equals(context.GetServerVariable("HTTPS"), "on", StringComparison.OrdinalIgnoreCase); + return new ReportApiTarget + { + Scheme = https ? "https" : "http", + Host = context.Request.Host.HasValue ? context.Request.Host.Value : address.ToString(), + Address = address.ToString(), + Port = context.Connection.LocalPort + }; + } + + /// + /// Keeps the target of the latest signed-in request, for reports rendered without one (customer notifications) + /// + internal static void Remember(HttpContext context) + { + if (!ServerBootConfig.HOSTED_IN_IIS) + return; + var target = FromRequest(context); + var last = _lastSeen; + if (target != null && (last == null || last.ToString() != target.ToString())) + _lastSeen = target; + } + + /// + /// Target for a report rendered without a user request: the latest signed-in request, else the override URL + /// sent to this machine; null outside IIS or when neither is known + /// + internal static ReportApiTarget ForBackgroundRender() + { + if (!ServerBootConfig.HOSTED_IN_IIS) + return null; + if (_lastSeen != null) + return _lastSeen; + if (Uri.TryCreate(ServerBootConfig.AYANOVA_REPORT_RENDER_API_URL_OVERRIDE?.Trim(), UriKind.Absolute, out var o)) + return new ReportApiTarget { Scheme = o.Scheme, Host = o.Authority, Address = IPAddress.Loopback.ToString(), Port = o.Port }; + return null; + } + + /// + /// Opens the page at the API URL's origin and relays the page's requests under the API URL to the target. + /// Returns the HTTP client to dispose when the page is done + /// + internal static async Task AttachAsync(IPage page, string apiUrl, ReportApiTarget target, TimeSpan timeout, Action pageLog, ILogger log) + { + var handler = new SocketsHttpHandler + { + AllowAutoRedirect = false, + UseCookies = false, + UseProxy = false, + //always the local address, whatever the URL's host name resolves to; TLS still checks the certificate for that name + ConnectCallback = async (context, cancel) => + { + var address = IPAddress.Parse(target.Address); + var socket = new Socket(address.AddressFamily, SocketType.Stream, System.Net.Sockets.ProtocolType.Tcp) { NoDelay = true }; + try + { + await socket.ConnectAsync(new IPEndPoint(address, target.Port), cancel); + return new NetworkStream(socket, ownsSocket: true); + } + catch + { + socket.Dispose(); + throw; + } + } + }; + var client = new HttpClient(handler) { Timeout = timeout }; + var pageUrl = apiUrl + PAGE_PATH; + + await page.SetRequestInterceptionAsync(true); + page.Request += async (sender, e) => + { + var request = e.Request; + try + { + if (request.Url == pageUrl) + { + await request.RespondAsync(new ResponseData { Status = HttpStatusCode.OK, ContentType = "text/html", Body = "" }); + return; + } + if (!request.Url.StartsWith(apiUrl, StringComparison.OrdinalIgnoreCase)) + { + await request.ContinueAsync(); + return; + } + using var message = new HttpRequestMessage(request.Method, target.BaseUrl + request.Url.Substring(apiUrl.Length)); + if (request.HasPostData) + message.Content = new StringContent(request.PostData ?? await request.FetchPostDataAsync() ?? string.Empty); + foreach (var header in request.Headers) + { + if (header.Key.Equals("Host", StringComparison.OrdinalIgnoreCase) || header.Key.Equals("Content-Length", StringComparison.OrdinalIgnoreCase)) + continue; + if (!message.Headers.TryAddWithoutValidation(header.Key, header.Value) && message.Content != null) + { + message.Content.Headers.Remove(header.Key); + message.Content.Headers.TryAddWithoutValidation(header.Key, header.Value); + } + } + using var response = await client.SendAsync(message); var headers = new Dictionary(StringComparer.OrdinalIgnoreCase); + foreach (var header in response.Headers.Concat(response.Content.Headers)) + { + if (header.Key.Equals("Transfer-Encoding", StringComparison.OrdinalIgnoreCase) || header.Key.Equals("Connection", StringComparison.OrdinalIgnoreCase) || header.Key.Equals("Content-Length", StringComparison.OrdinalIgnoreCase)) + continue; + headers[header.Key] = string.Join(", ", header.Value); + } + await request.RespondAsync(new ResponseData { Status = response.StatusCode, Headers = headers, BodyData = await response.Content.ReadAsByteArrayAsync() }); + } + catch (Exception ex) + { + var reason = ex.InnerException != null ? $"{ex.Message} ({ex.InnerException.Message})" : ex.Message; + pageLog($"ERROR: AyaNova API request {request.Method} {request.Url} via {target} failed: {reason}"); + log.LogWarning($"Report API request {request.Method} {request.Url} via {target} failed: {reason}"); + try { await request.AbortAsync(); } catch { } + } + }; + await page.GoToAsync(pageUrl); + return client; + } + } +} diff --git a/server/AyaNova/util/ServerBootConfig.cs b/server/AyaNova/util/ServerBootConfig.cs index d2dec973..6b3174bc 100644 --- a/server/AyaNova/util/ServerBootConfig.cs +++ b/server/AyaNova/util/ServerBootConfig.cs @@ -28,6 +28,9 @@ namespace AyaNova.Util internal const string CUSTOMER_NOTIFICATION_ATTACHED_REPORT_RENDER_USERNAME = "CUSTOMER NOTIFICATION - NO USER"; + //case 4632: true when running inside IIS (in-process hosting), where the report page's API calls are relayed (ReportApiRelay) + internal static bool HOSTED_IN_IIS { get; set; } + //############################################################################################################ //############################