mirror of
https://github.com/OPCFoundation/UA-.NETStandard.git
synced 2026-10-29 07:07:24 +08:00
Client: apply TransportQuotas in ManagedSessionBuilder URL shortcuts (#4906)
# Description `ManagedSessionBuilder.UseEndpoint(string url, ...)`, `UseWebApiEndpoint(...)` and `UseWssOpenApiEndpoint(...)` created their endpoint with `new ConfiguredEndpoint(null, description, null)`. The `null` endpoint configuration falls back to `EndpointConfiguration.Create()` defaults (`MaxStringLength = 65535`, `MaxArrayLength = 65535`, `MaxMessageSize = 2 MiB`, ...). The transport channels build their message context from the endpoint configuration, so the builder's `ApplicationConfiguration.TransportQuotas` were silently ignored and `session.MessageContext` kept the defaults. The three shortcuts now build the endpoint with `EndpointConfiguration.Create(configuration)`, the same as `AddDiscoveryAndConnect` since #4810. Reported in #4905. That report was about `AddDiscoveryAndConnect`, which is already fixed on master. While investigating it I found the same pattern in these builder shortcuts. ## Tests - `ManagedSessionBuilderExtendedTests.UrlShortcutUsesApplicationTransportQuotas`: unit test for all three shortcuts, checks the built endpoint configuration. - `ManagedSessionBuilderTransportQuotaTests.UseEndpointUrlSessionUsesApplicationTransportQuotasAsync`: connects to an in-process reference server through `UseEndpoint(url)` and checks `session.MessageContext`. All four new test cases fail without the fix (they get the 65535 / 2 MiB defaults). Local runs on net10.0: - Full `Opc.Ua.Client.Tests`: 3327 passed, 2 skipped, 1 failed. The failure was `ManagedSessionServerRedundancyTests.ConnectWithProbingSelectsMemberWithHigherServiceLevelAsync`, which doesn't use the changed shortcuts. That fixture passed 3/3 when rerun on its own. - `Opc.Ua.Sessions.Tests` `HttpsWebApiIntegrationTests` and `WssOpenApiIntegrationTests`, which use `UseWebApiEndpoint` / `UseWssOpenApiEndpoint`: 19/19 passed. `Opc.Ua.Client.Tests` also compiles for net48. ## Related Issues - Related to #4905 ## Checklist - [x] I have signed the [CLA](https://opcfoundation.org/license/cla/ContributorLicenseAgreementv1.0.pdf) and read the [CONTRIBUTING](https://github.com/OPCFoundation/UA-.NETStandard/blob/master/CONTRIBUTING.md) doc. - [x] I have added tests that prove my fix is effective or that my feature works and increased code coverage. - [x] I have added all necessary documentation. - [x] I have verified that my changes do not introduce (new) build or analyzer warnings. - [ ] I ran **all** tests locally using the **UA.slnx** solution against at least .net **framework** and .net **10**, and all passed. - [ ] I fixed **all** failing and flaky tests in the CI pipelines and **all** CodeQL warnings. - [ ] I have addressed **all** PR feedback received. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
3c5aa421d3
commit
9e48f3d04d
@@ -103,6 +103,9 @@ namespace Opc.Ua.Client
|
||||
|
||||
/// <summary>
|
||||
/// Use a discovery endpoint URL with optional security mode/policy.
|
||||
/// The endpoint's encoding and message limits are taken from the
|
||||
/// <see cref="ApplicationConfiguration.TransportQuotas"/> of the
|
||||
/// configuration this builder was created with.
|
||||
/// </summary>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="url"/> is <c>null</c>.</exception>
|
||||
public ManagedSessionBuilder UseEndpoint(
|
||||
@@ -120,7 +123,8 @@ namespace Opc.Ua.Client
|
||||
SecurityMode = securityMode,
|
||||
SecurityPolicyUri = securityPolicyUri
|
||||
};
|
||||
return UseEndpoint(new ConfiguredEndpoint(null, endpointDescription, null));
|
||||
return UseEndpoint(new ConfiguredEndpoint(
|
||||
null, endpointDescription, EndpointConfiguration.Create(m_configuration)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -137,7 +141,9 @@ namespace Opc.Ua.Client
|
||||
/// Certificate + IssuedToken / JWT) so the client can present
|
||||
/// any identity supported by the server; the server-side
|
||||
/// <c>DefaultSessionlessIdentityProvider</c> still enforces what
|
||||
/// it accepts.
|
||||
/// it accepts. As with <see cref="UseEndpoint(string, MessageSecurityMode, string)"/>,
|
||||
/// the endpoint's limits come from the builder configuration's
|
||||
/// <see cref="ApplicationConfiguration.TransportQuotas"/>.
|
||||
/// </summary>
|
||||
/// <param name="url">
|
||||
/// The server's base URL, e.g. <c>https://server:4843/</c>.
|
||||
@@ -189,7 +195,8 @@ namespace Opc.Ua.Client
|
||||
};
|
||||
m_webApiOptions ??= new WebApiClientOptions();
|
||||
m_webApiOptions.Encoding = encoding;
|
||||
return UseEndpoint(new ConfiguredEndpoint(null, endpointDescription, null));
|
||||
return UseEndpoint(new ConfiguredEndpoint(
|
||||
null, endpointDescription, EndpointConfiguration.Create(m_configuration)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -198,7 +205,9 @@ namespace Opc.Ua.Client
|
||||
/// surfaced via <see cref="Profiles.WssOpenApiTransport"/>,
|
||||
/// OPC Foundation profile/2339). Same envelope as
|
||||
/// <see cref="UseWebApiEndpoint(string, WebApiEncoding)"/> but
|
||||
/// multiplexed over a WebSocket text-frame transport.
|
||||
/// multiplexed over a WebSocket text-frame transport, including
|
||||
/// the limits taken from
|
||||
/// <see cref="ApplicationConfiguration.TransportQuotas"/>.
|
||||
/// </summary>
|
||||
/// <param name="url">
|
||||
/// The server's WSS URL, e.g. <c>wss://server:4843/</c>.
|
||||
@@ -250,7 +259,8 @@ namespace Opc.Ua.Client
|
||||
};
|
||||
m_webApiOptions ??= new WebApiClientOptions();
|
||||
m_webApiOptions.Encoding = encoding;
|
||||
return UseEndpoint(new ConfiguredEndpoint(null, endpointDescription, null));
|
||||
return UseEndpoint(new ConfiguredEndpoint(
|
||||
null, endpointDescription, EndpointConfiguration.Create(m_configuration)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -143,6 +143,51 @@ namespace Opc.Ua.Client.Tests.ClientBuilder
|
||||
Assert.That(opts.Endpoint, Is.Not.Null);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
// URL shortcuts and transport quotas
|
||||
// ----------------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// The URL shortcuts build their endpoint from the application's
|
||||
/// transport quotas instead of the built-in defaults (issue #4905).
|
||||
/// </summary>
|
||||
[TestCase("opc.tcp://server:4840")]
|
||||
[TestCase("https://server:4843/")]
|
||||
[TestCase("wss://server:4843/")]
|
||||
public void UrlShortcutUsesApplicationTransportQuotas(string url)
|
||||
{
|
||||
ITelemetryContext telemetry = NUnitTelemetryContext.Create();
|
||||
ApplicationConfiguration configuration = CreateConfig(telemetry);
|
||||
configuration.TransportQuotas = new TransportQuotas
|
||||
{
|
||||
OperationTimeout = 60_000,
|
||||
MaxStringLength = 1024 * 1024,
|
||||
MaxByteStringLength = 2 * 1024 * 1024,
|
||||
MaxArrayLength = 300_000,
|
||||
MaxMessageSize = 8 * 1024 * 1024,
|
||||
MaxBufferSize = 128 * 1024
|
||||
};
|
||||
var builder = new ManagedSessionBuilder(configuration, telemetry);
|
||||
builder = url.StartsWith("https", StringComparison.Ordinal)
|
||||
? builder.UseWebApiEndpoint(url)
|
||||
: url.StartsWith("wss", StringComparison.Ordinal)
|
||||
? builder.UseWssOpenApiEndpoint(url)
|
||||
: builder.UseEndpoint(url, MessageSecurityMode.None, SecurityPolicies.None);
|
||||
|
||||
EndpointConfiguration? quotas = builder.Build().Endpoint?.Configuration;
|
||||
|
||||
Assert.That(quotas, Is.Not.Null);
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(quotas!.OperationTimeout, Is.EqualTo(60_000));
|
||||
Assert.That(quotas.MaxStringLength, Is.EqualTo(1024 * 1024));
|
||||
Assert.That(quotas.MaxByteStringLength, Is.EqualTo(2 * 1024 * 1024));
|
||||
Assert.That(quotas.MaxArrayLength, Is.EqualTo(300_000));
|
||||
Assert.That(quotas.MaxMessageSize, Is.EqualTo(8 * 1024 * 1024));
|
||||
Assert.That(quotas.MaxBufferSize, Is.EqualTo(128 * 1024));
|
||||
});
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
// WithWebApiAuthentication
|
||||
// ----------------------------------------------------------------
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
/* ========================================================================
|
||||
* Copyright (c) 2005-2026 The OPC Foundation, Inc. All rights reserved.
|
||||
*
|
||||
* OPC Foundation MIT License 1.00
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation
|
||||
* files (the "Software"), to deal in the Software without
|
||||
* restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following
|
||||
* conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
* OTHER DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
* The complete license agreement can be found here:
|
||||
* http://opcfoundation.org/License/MIT/1.00/
|
||||
* ======================================================================*/
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using NUnit.Framework;
|
||||
using Opc.Ua.Client.TestFramework;
|
||||
using Opc.Ua.Server.TestFramework;
|
||||
using Opc.Ua.Tests;
|
||||
using Quickstarts.ReferenceServer;
|
||||
|
||||
namespace Opc.Ua.Client.Tests.ClientBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// Verifies against a running server that a session connected through
|
||||
/// <see cref="ManagedSessionBuilder.UseEndpoint(string, MessageSecurityMode, string)"/>
|
||||
/// decodes with the application's transport quotas (issue #4905).
|
||||
/// </summary>
|
||||
[TestFixture]
|
||||
[NonParallelizable]
|
||||
[Category("Client")]
|
||||
[Category("Integration")]
|
||||
public sealed class ManagedSessionBuilderTransportQuotaTests
|
||||
{
|
||||
/// <summary>
|
||||
/// The session message context reflects the configured limits rather
|
||||
/// than the built-in endpoint defaults.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task UseEndpointUrlSessionUsesApplicationTransportQuotasAsync()
|
||||
{
|
||||
ITelemetryContext telemetry = NUnitTelemetryContext.Create();
|
||||
string root = Path.Combine(Path.GetTempPath(), "mtq-" + Guid.NewGuid().ToString("N")[..8]);
|
||||
var fixture = new ServerFixture<ReferenceServer>(context => new ReferenceServer(context))
|
||||
{
|
||||
AutoAccept = true,
|
||||
SecurityNone = true
|
||||
};
|
||||
try
|
||||
{
|
||||
await fixture.StartAsync(root).ConfigureAwait(false);
|
||||
await using var client = new ClientFixture(telemetry);
|
||||
await client.LoadClientConfigurationAsync(root, "TransportQuotaClient").ConfigureAwait(false);
|
||||
ApplicationConfiguration configuration = client.Config;
|
||||
configuration.TransportQuotas = new TransportQuotas
|
||||
{
|
||||
MaxStringLength = 1024 * 1024,
|
||||
MaxByteStringLength = 2 * 1024 * 1024,
|
||||
MaxArrayLength = 300_000,
|
||||
MaxMessageSize = 8 * 1024 * 1024
|
||||
};
|
||||
using var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(60));
|
||||
|
||||
await using Client.ManagedSession session = await new ManagedSessionBuilder(configuration, telemetry)
|
||||
.UseEndpoint(
|
||||
$"{Utils.UriSchemeOpcTcp}://localhost:{fixture.Port}",
|
||||
MessageSecurityMode.None,
|
||||
SecurityPolicies.None)
|
||||
.ConnectAsync(timeout.Token)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(session.MessageContext.MaxStringLength, Is.EqualTo(1024 * 1024));
|
||||
Assert.That(session.MessageContext.MaxByteStringLength, Is.EqualTo(2 * 1024 * 1024));
|
||||
Assert.That(session.MessageContext.MaxArrayLength, Is.EqualTo(300_000));
|
||||
Assert.That(session.MessageContext.MaxMessageSize, Is.EqualTo(8 * 1024 * 1024));
|
||||
});
|
||||
}
|
||||
finally
|
||||
{
|
||||
await fixture.StopAsync().ConfigureAwait(false);
|
||||
if (Directory.Exists(root))
|
||||
{
|
||||
Directory.Delete(root, recursive: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user