From 2028db671fa7d0ee1df51ad21303000f58929ca5 Mon Sep 17 00:00:00 2001 From: Ahmed Zamil Date: Tue, 4 Aug 2026 06:16:56 +0300 Subject: [PATCH] Port --- Examples/StandaloneWebServer/Program.cs | 7 ++-- Examples/StandaloneWebServer/Web/js/app.js | 5 ++- .../EsiurWebSocketEndpoint.cs | 2 +- Integrations/Esiur.AspNetCore/README.md | 4 +-- Libraries/Esiur/Esiur.csproj | 2 +- Libraries/Esiur/Protocol/EpConnection.cs | 12 +++++-- Libraries/Esiur/Protocol/EpServer.cs | 6 ++-- Libraries/Esiur/README.md | 20 ++++++++--- README.md | 17 ++++++--- Tests/Esiur.CLI.Tests/CommandTests.cs | 2 +- Tests/Esiur.CLI.Tests/ConfigurationTests.cs | 13 +++---- Tests/Unit/AspNetCoreIntegrationTests.cs | 35 +++++++++++++++++-- Tests/Unit/EpConnectionReconnectTests.cs | 10 +++--- Tests/Unit/EpPortTests.cs | 22 ++++++++++++ Tests/Unit/PeerConnectionLimitTests.cs | 4 +-- Tests/Unit/SocketProtocolSecurityTests.cs | 2 +- .../Configuration/ConfigurationResolver.cs | 33 +++++++++++++++-- .../Esiur.CLI/Properties/launchSettings.json | 2 +- Tools/Esiur.CLI/README.md | 6 ++-- 19 files changed, 159 insertions(+), 45 deletions(-) create mode 100644 Tests/Unit/EpPortTests.cs diff --git a/Examples/StandaloneWebServer/Program.cs b/Examples/StandaloneWebServer/Program.cs index 899d6b3..ce10c89 100644 --- a/Examples/StandaloneWebServer/Program.cs +++ b/Examples/StandaloneWebServer/Program.cs @@ -14,12 +14,15 @@ internal class Program private static async Task Main(string[] args) { + var epPort = ushort.Parse( + Environment.GetEnvironmentVariable("ESIUR_PORT") + ?? throw new InvalidOperationException("Set ESIUR_PORT before starting the example.")); var wh = new Warehouse(); // Create a store to keep objects. var system = await wh.Put("sys", new MemoryStore()); // Create a distibuted server - var esiurServer = await wh.Put("sys/server", new EpServer()); + var esiurServer = await wh.Put("sys/server", new EpServer() { Port = epPort }); // Add your object to the store var service = await wh.Put("sys/demo", new Demo()); @@ -86,7 +89,7 @@ internal class Program // Start your server await wh.Open(); - Console.WriteLine("Running on http://localhost:8888"); + Console.WriteLine($"Running on http://localhost:8888/?epPort={epPort}"); } } diff --git a/Examples/StandaloneWebServer/Web/js/app.js b/Examples/StandaloneWebServer/Web/js/app.js index a1d786e..2fd6c45 100644 --- a/Examples/StandaloneWebServer/Web/js/app.js +++ b/Examples/StandaloneWebServer/Web/js/app.js @@ -1,7 +1,10 @@ async function init() { try { + const epPort = new URLSearchParams(window.location.search).get("epPort"); + if (!epPort) + throw new Error("Open this example with the application-defined epPort query parameter."); - connection = await wh.get(`ep://${window.location.hostname}`, { + connection = await wh.get(`ep://${window.location.hostname}:${epPort}`, { autoReconnect: true }); diff --git a/Integrations/Esiur.AspNetCore/EsiurWebSocketEndpoint.cs b/Integrations/Esiur.AspNetCore/EsiurWebSocketEndpoint.cs index 46b5f22..090805e 100644 --- a/Integrations/Esiur.AspNetCore/EsiurWebSocketEndpoint.cs +++ b/Integrations/Esiur.AspNetCore/EsiurWebSocketEndpoint.cs @@ -217,7 +217,7 @@ internal sealed class EsiurWebSocketEndpoint { endPoint = null!; if (address is null - || port is <= IPEndPoint.MinPort or > IPEndPoint.MaxPort + || port is < IPEndPoint.MinPort or > IPEndPoint.MaxPort || address.Equals(IPAddress.Any) || address.Equals(IPAddress.IPv6Any) || address.Equals(IPAddress.None) diff --git a/Integrations/Esiur.AspNetCore/README.md b/Integrations/Esiur.AspNetCore/README.md index 6db1534..0bfdf8b 100644 --- a/Integrations/Esiur.AspNetCore/README.md +++ b/Integrations/Esiur.AspNetCore/README.md @@ -260,10 +260,10 @@ using Esiur.Resource; var clientWarehouse = new Warehouse(); var service = await clientWarehouse.Get( - "ep://api.example.com/sys/service", + "ep://api.example.com:443/sys/service", new EpConnectionContext { - WebSocketUri = new Uri("wss://api.example.com/esiur"), + WebSocketUri = new Uri("wss://api.example.com:443/esiur"), // Add the authentication mode, identity, and protocol required by the server. }); ``` diff --git a/Libraries/Esiur/Esiur.csproj b/Libraries/Esiur/Esiur.csproj index 38b739e..9c770b1 100644 --- a/Libraries/Esiur/Esiur.csproj +++ b/Libraries/Esiur/Esiur.csproj @@ -5,7 +5,7 @@ Ahmed Kh. Zamil https://www.esiur.com true - 3.0.0 + 3.0.1 https://github.com/esiur/esiur-dotnet Ahmed Kh. Zamil diff --git a/Libraries/Esiur/Protocol/EpConnection.cs b/Libraries/Esiur/Protocol/EpConnection.cs index 12fa7ce..ea11758 100644 --- a/Libraries/Esiur/Protocol/EpConnection.cs +++ b/Libraries/Esiur/Protocol/EpConnection.cs @@ -3143,10 +3143,16 @@ public partial class EpConnection : NetworkConnection, IStore return new AsyncReply(true); - var host = Instance.Name.Split(':'); + if (!Uri.TryCreate($"ep://{Instance.Name}", UriKind.Absolute, out var endpoint) + || string.IsNullOrWhiteSpace(endpoint.Host) + || endpoint.Port <= 0 + || endpoint.Port > ushort.MaxValue) + throw new FormatException( + "EP endpoints must include an explicit port (for example, ep://host:port)." + ); - var address = host[0]; - var port = host.Length > 1 ? ushort.Parse(host[1]) : (ushort)10518; + var address = endpoint.Host; + var port = checked((ushort)endpoint.Port); // assign domain from hostname if not provided if (context is EpConnectionContext epContext) diff --git a/Libraries/Esiur/Protocol/EpServer.cs b/Libraries/Esiur/Protocol/EpServer.cs index 8bacb6e..afca77e 100644 --- a/Libraries/Esiur/Protocol/EpServer.cs +++ b/Libraries/Esiur/Protocol/EpServer.cs @@ -131,12 +131,14 @@ public class EpServer : NetworkServer, IResource set; } - //[Attribute] + /// + /// Application-supplied native TCP port. Zero requests an ephemeral port from the OS. + /// public ushort Port { get; set; - } = 10518; + } /// /// Controls whether warehouse initialization opens Esiur's native TCP listener. diff --git a/Libraries/Esiur/README.md b/Libraries/Esiur/README.md index 6dc8b7e..d756aa2 100644 --- a/Libraries/Esiur/README.md +++ b/Libraries/Esiur/README.md @@ -93,8 +93,13 @@ Add an `EpServer` to expose the resource through the Esiur EP protocol. Anonymou ```C# +var epPort = ushort.Parse( + Environment.GetEnvironmentVariable("ESIUR_PORT") + ?? throw new InvalidOperationException("Set ESIUR_PORT.")); + await warehouse.Put("sys/server", new EpServer { + Port = epPort, AllowUnauthorizedAccess = true, // Development only. }); ``` @@ -113,11 +118,15 @@ To sum up >using Esiur.Resource; >using Esiur.Stores; > +>var epPort = ushort.Parse( +> Environment.GetEnvironmentVariable("ESIUR_PORT") +> ?? throw new InvalidOperationException("Set ESIUR_PORT.")); >var warehouse = new Warehouse(); >await warehouse.Put("sys", new MemoryStore()); >await warehouse.Put("sys/hello", new HelloResource()); >await warehouse.Put("sys/server", new EpServer >{ +> Port = epPort, > AllowUnauthorizedAccess = true, // Development only. >}); >await warehouse.Open(); @@ -129,7 +138,8 @@ To access our resource remotely, we need to use it's full path including the pro ```C# var warehouse = new Warehouse(); -dynamic res = await warehouse.Get("ep://localhost/sys/hello"); +var epPort = ushort.Parse(Environment.GetEnvironmentVariable("ESIUR_PORT")!); +dynamic res = await warehouse.Get($"ep://localhost:{epPort}/sys/hello"); ``` Now we can invoke the exported functions and read/write properties; @@ -147,7 +157,8 @@ Summing up >using Esiur.Resource; > >var warehouse = new Warehouse(); ->dynamic res = await warehouse.Get("ep://localhost/sys/hello"); +>var epPort = ushort.Parse(Environment.GetEnvironmentVariable("ESIUR_PORT")!); +>dynamic res = await warehouse.Get($"ep://localhost:{epPort}/sys/hello"); > >var reply = await res.SayHi("Hi, I'm calling you from dotnet"); > @@ -165,7 +176,7 @@ Esiur has a self describing feature which comes with every language it supports, After installing the Esiur NuGet package, a new command named ***Get-Types*** is added to the Visual Studio Package Manager Console. It generates client-side classes for robust static typing. ```ps -Get-Types ep://localhost/sys/hello +Get-Types "ep://localhost:$env:ESIUR_PORT/sys/hello" ``` This will generate and add wrappers for all types needed by our resource. @@ -173,7 +184,8 @@ This will generate and add wrappers for all types needed by our resource. Allowing us to use ```C# var warehouse = new Warehouse(); -var res = await warehouse.Get("ep://localhost/sys/hello"); +var epPort = ushort.Parse(Environment.GetEnvironmentVariable("ESIUR_PORT")!); +var res = await warehouse.Get($"ep://localhost:{epPort}/sys/hello"); var reply = await res.SayHi("Static typing is better"); Console.WriteLine(reply); ``` diff --git a/README.md b/README.md index 1b66937..d0be2ab 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,7 @@ dotnet add package Esiur.AspNetCore --version 3.0.0 For the standalone runtime or a client: ```shell -dotnet add package Esiur --version 3.0.0 +dotnet add package Esiur --version 3.0.1 ``` ## Define a resource @@ -157,7 +157,7 @@ using Esiur.Resource; var client = new Warehouse(); dynamic counter = await client.Get( - "ep://localhost/sys/counter", + "ep://localhost:8080/sys/counter", new EpConnectionContext { WebSocketUri = new Uri("ws://localhost:8080/esiur"), @@ -176,8 +176,12 @@ the case-sensitive `EP` WebSocket subprotocol. For native TCP, omit `WebSocketUri` and include the EP port in the logical URL: ```csharp +var epPort = ushort.Parse( + Environment.GetEnvironmentVariable("ESIUR_PORT") + ?? throw new InvalidOperationException("Set ESIUR_PORT.")); + dynamic counter = await client.Get( - "ep://localhost:10518/sys/counter"); + $"ep://localhost:{epPort}/sys/counter"); ``` ## Standalone hosting @@ -191,13 +195,16 @@ using Esiur.Protocol; using Esiur.Resource; using Esiur.Stores; +var epPort = ushort.Parse( + Environment.GetEnvironmentVariable("ESIUR_PORT") + ?? throw new InvalidOperationException("Set ESIUR_PORT.")); var warehouse = new Warehouse(); await warehouse.Put("sys", new MemoryStore()); await warehouse.Put("sys/counter", new CounterResource()); await warehouse.Put("sys/server", new EpServer { - Port = 10518, + Port = epPort, AllowUnauthorizedAccess = true, // Development only. }); @@ -344,7 +351,7 @@ typed models. Install the v3 CLI as a .NET tool: ```shell dotnet tool install --global Esiur.CLI --version 3.0.0 -esiur get-template ep://localhost:10518/sys/counter --dir Generated +esiur get-template "ep://localhost:${ESIUR_PORT}/sys/counter" --dir Generated ``` Use `--async-setters` to generate asynchronous property setters. The CLI also diff --git a/Tests/Esiur.CLI.Tests/CommandTests.cs b/Tests/Esiur.CLI.Tests/CommandTests.cs index dc0fdae..5dde1b1 100644 --- a/Tests/Esiur.CLI.Tests/CommandTests.cs +++ b/Tests/Esiur.CLI.Tests/CommandTests.cs @@ -20,7 +20,7 @@ public sealed class CommandTests { ["production"] = new ConnectionProfile { - Name = "production", Endpoint = "ep://host", + Name = "production", Endpoint = "ep://host:65535", }, }, }, default); diff --git a/Tests/Esiur.CLI.Tests/ConfigurationTests.cs b/Tests/Esiur.CLI.Tests/ConfigurationTests.cs index fff7f88..7ef19c0 100644 --- a/Tests/Esiur.CLI.Tests/ConfigurationTests.cs +++ b/Tests/Esiur.CLI.Tests/ConfigurationTests.cs @@ -8,13 +8,14 @@ namespace Esiur.CLI.Tests; public sealed class ConfigurationTests { [Theory] - [InlineData("ep://localhost", "ep://localhost")] + [InlineData("ep://localhost:65535", "ep://localhost:65535")] [InlineData("ep://example.test:9000/sys/service", "ep://example.test:9000")] public void EndpointParserExtractsConnectionEndpoint(string value, string expected) => Assert.Equal(expected, EndpointParser.ConnectionEndpoint(value)); [Theory] [InlineData("http://localhost")] + [InlineData("ep://localhost")] [InlineData("ep:///missing-host")] [InlineData("not-an-endpoint")] public void EndpointParserRejectsInvalidEndpoints(string value) => @@ -43,7 +44,7 @@ public sealed class ConfigurationTests ["production"] = new ConnectionProfile { Name = "production", - Endpoint = "ep://host", + Endpoint = "ep://host:65535", Provider = "password", Identity = "ahmed", }, @@ -55,7 +56,7 @@ public sealed class ConfigurationTests Assert.DoesNotContain("secret", text, StringComparison.OrdinalIgnoreCase); var loaded = await store.LoadAsync(default); Assert.Equal("production", loaded.DefaultProfile); - Assert.Equal("ep://host", loaded.Profiles["PRODUCTION"].Endpoint); + Assert.Equal("ep://host:65535", loaded.Profiles["PRODUCTION"].Endpoint); } finally { directory.Delete(true); } } @@ -71,14 +72,14 @@ public sealed class ConfigurationTests { ["saved"] = new ConnectionProfile { - Name = "saved", Endpoint = "ep://saved", OutputFormat = "raw", + Name = "saved", Endpoint = "ep://saved:65535", OutputFormat = "raw", Identity = "stored", }, }, }; var result = ConfigurationResolver.Resolve(configuration, - new GlobalOptions("saved", "ep://explicit", null, "explicit", "json", TimeSpan.FromSeconds(4), false, false)); - Assert.Equal("ep://explicit", result.Endpoint); + new GlobalOptions("saved", "ep://explicit:65535", null, "explicit", "json", TimeSpan.FromSeconds(4), false, false)); + Assert.Equal("ep://explicit:65535", result.Endpoint); Assert.Equal("explicit", result.Identity); Assert.Equal("json", result.OutputFormat); Assert.Equal(TimeSpan.FromSeconds(4), result.Timeout); diff --git a/Tests/Unit/AspNetCoreIntegrationTests.cs b/Tests/Unit/AspNetCoreIntegrationTests.cs index 6454eab..ff608e3 100644 --- a/Tests/Unit/AspNetCoreIntegrationTests.cs +++ b/Tests/Unit/AspNetCoreIntegrationTests.cs @@ -361,6 +361,31 @@ public sealed class AspNetCoreIntegrationTests cleanupCancellation.Token); } + [Fact] + public async Task FrameworkWebSocket_AcceptsProxyEndpointWithUnspecifiedRemotePort() + { + await using var host = await StartApplicationAsync( + configureApplication: application => application.Use( + async (context, next) => + { + context.Connection.RemotePort = IPEndPoint.MinPort; + await next(context); + })); + using var cancellation = new CancellationTokenSource(TestTimeout); + using var socket = new ClientWebSocket(); + socket.Options.AddSubProtocol(FrameworkWebSocket.SubProtocol); + + await socket.ConnectAsync(host.WebSocketAddress, cancellation.Token); + + Assert.Equal(WebSocketState.Open, socket.State); + Assert.Equal(FrameworkWebSocket.SubProtocol, socket.SubProtocol); + await WaitUntilAsync( + () => host.Server.Connections.Count == 1, + cancellation.Token); + + socket.Abort(); + } + [Fact] public async Task HostShutdown_CancelsWebSocketAndCleansUpAdmission() { @@ -640,7 +665,8 @@ public sealed class AspNetCoreIntegrationTests private static WebApplication BuildApplication( Action? configureEsiur = null, Action? configureServer = null, - Action? configureWarehouse = null) + Action? configureWarehouse = null, + Action? configureApplication = null) { var builder = WebApplication.CreateBuilder(new WebApplicationOptions { @@ -665,6 +691,7 @@ public sealed class AspNetCoreIntegrationTests var application = builder.Build(); application.UseWebSockets(); + configureApplication?.Invoke(application); application.MapGet("/health", () => Results.Text("healthy")); application.MapEsiur("/esiur"); return application; @@ -673,7 +700,8 @@ public sealed class AspNetCoreIntegrationTests private static async Task StartApplicationAsync( Action? configureServer = null, Action? configureWarehouse = null, - Action? configureEsiur = null) + Action? configureEsiur = null, + Action? configureApplication = null) { var application = BuildApplication( esiur => @@ -682,7 +710,8 @@ public sealed class AspNetCoreIntegrationTests configureEsiur?.Invoke(esiur); }, configureServer, - configureWarehouse); + configureWarehouse, + configureApplication); using var cancellation = new CancellationTokenSource(TestTimeout); await application.StartAsync(cancellation.Token); diff --git a/Tests/Unit/EpConnectionReconnectTests.cs b/Tests/Unit/EpConnectionReconnectTests.cs index 9e2a4d4..fe27c08 100644 --- a/Tests/Unit/EpConnectionReconnectTests.cs +++ b/Tests/Unit/EpConnectionReconnectTests.cs @@ -27,7 +27,7 @@ public sealed class EpConnectionReconnectTests var open = connection.Connect( hostname: "localhost", - port: 10518, + port: IPEndPoint.MaxPort, domain: "test"); var completed = await Task.WhenAny( @@ -62,7 +62,7 @@ public sealed class EpConnectionReconnectTests var open = connection.Connect( hostname: "localhost", - port: 10518, + port: IPEndPoint.MaxPort, domain: "test"); await delayedSocket.ConnectInvoked.WaitAsync(TimeSpan.FromSeconds(2)); @@ -95,7 +95,7 @@ public sealed class EpConnectionReconnectTests var open = connection.Connect( hostname: "localhost", - port: 10518, + port: IPEndPoint.MaxPort, domain: "test"); connection.AutoReconnect = false; @@ -133,7 +133,7 @@ public sealed class EpConnectionReconnectTests _ = connection.Connect( initialSocket, hostname: "localhost", - port: 10518, + port: IPEndPoint.MaxPort, domain: "test"); initialSocket.Disconnect(); connection.AutoReconnect = false; @@ -160,7 +160,7 @@ public sealed class EpConnectionReconnectTests public SocketState State => state; public INetworkReceiver Receiver { get; set; } = null!; public IPEndPoint RemoteEndPoint { get; } = - new(IPAddress.Loopback, 10518); + new(IPAddress.Loopback, IPEndPoint.MaxPort); public IPEndPoint LocalEndPoint { get; } = new(IPAddress.Loopback, 50000); public int ConnectCount { get; private set; } diff --git a/Tests/Unit/EpPortTests.cs b/Tests/Unit/EpPortTests.cs new file mode 100644 index 0000000..88c383d --- /dev/null +++ b/Tests/Unit/EpPortTests.cs @@ -0,0 +1,22 @@ +using Esiur.Protocol; +using Esiur.Resource; + +namespace Esiur.Tests.Unit; + +public sealed class EpPortTests +{ + [Fact] + public void ServerDoesNotAssignAProtocolPort() + => Assert.Equal(0, new EpServer().Port); + + [Fact] + public async Task ClientEndpointWithoutPortIsRejected() + { + var warehouse = new Warehouse(); + + var exception = await Assert.ThrowsAsync(async () => + await warehouse.Get("ep://localhost/sys/resource")); + + Assert.Contains("explicit port", exception.Message, StringComparison.OrdinalIgnoreCase); + } +} diff --git a/Tests/Unit/PeerConnectionLimitTests.cs b/Tests/Unit/PeerConnectionLimitTests.cs index bb7c034..a97d4fa 100644 --- a/Tests/Unit/PeerConnectionLimitTests.cs +++ b/Tests/Unit/PeerConnectionLimitTests.cs @@ -251,7 +251,7 @@ public class PeerConnectionLimitTests connection, null); - _ = connection.Connect(socket, "example.test", 10518, "example.test"); + _ = connection.Connect(socket, "example.test", IPEndPoint.MaxPort, "example.test"); var deadline = DateTime.UtcNow + TimeSpan.FromSeconds(3); while (socket.State != SocketState.Closed && DateTime.UtcNow < deadline) @@ -269,7 +269,7 @@ public class PeerConnectionLimitTests public SocketState State { get; private set; } = SocketState.Established; public INetworkReceiver Receiver { get; set; } = null!; public IPEndPoint RemoteEndPoint { get; } - public IPEndPoint LocalEndPoint { get; } = new(IPAddress.Loopback, 10518); + public IPEndPoint LocalEndPoint { get; } = new(IPAddress.Loopback, IPEndPoint.MaxPort); public TestSocket(IPAddress address, int port) => RemoteEndPoint = new IPEndPoint(address, port); diff --git a/Tests/Unit/SocketProtocolSecurityTests.cs b/Tests/Unit/SocketProtocolSecurityTests.cs index 714d89a..51eff0a 100644 --- a/Tests/Unit/SocketProtocolSecurityTests.cs +++ b/Tests/Unit/SocketProtocolSecurityTests.cs @@ -382,7 +382,7 @@ public class SocketProtocolSecurityTests public SocketState State { get; private set; } = SocketState.Listening; public INetworkReceiver Receiver { get; set; } = null!; public IPEndPoint RemoteEndPoint => null!; - public IPEndPoint LocalEndPoint { get; } = new(IPAddress.Loopback, 10518); + public IPEndPoint LocalEndPoint { get; } = new(IPAddress.Loopback, IPEndPoint.MaxPort); public ISocket Accept() { diff --git a/Tools/Esiur.CLI/Configuration/ConfigurationResolver.cs b/Tools/Esiur.CLI/Configuration/ConfigurationResolver.cs index 4a8a1b6..c5c2e1a 100644 --- a/Tools/Esiur.CLI/Configuration/ConfigurationResolver.cs +++ b/Tools/Esiur.CLI/Configuration/ConfigurationResolver.cs @@ -74,8 +74,37 @@ public static class ConfigurationResolver if (!Uri.TryCreate(endpoint, UriKind.Absolute, out var uri) || !ValidSchemes.Contains(uri.Scheme, StringComparer.OrdinalIgnoreCase) || string.IsNullOrWhiteSpace(uri.Host) - || !string.IsNullOrEmpty(uri.UserInfo)) - throw new CliException($"Endpoint \"{endpoint}\" is not a valid ep:// or ws:// endpoint.", ExitCodes.InvalidArguments); + || !string.IsNullOrEmpty(uri.UserInfo) + || !HasExplicitPort(endpoint)) + throw new CliException( + $"Endpoint \"{endpoint}\" is not a valid ep:// or ws:// endpoint with an explicit port.", + ExitCodes.InvalidArguments); + } + + static bool HasExplicitPort(string endpoint) + { + var schemeEnd = endpoint.IndexOf("://", StringComparison.Ordinal); + if (schemeEnd < 0) + return false; + + var authorityStart = schemeEnd + 3; + var authorityEnd = endpoint.IndexOfAny(['/', '?', '#'], authorityStart); + if (authorityEnd < 0) + authorityEnd = endpoint.Length; + + var authority = endpoint[authorityStart..authorityEnd]; + var at = authority.LastIndexOf('@'); + if (at >= 0) + authority = authority[(at + 1)..]; + + var colon = authority.StartsWith('[') + ? authority.IndexOf(']') + 1 + : authority.LastIndexOf(':'); + return colon > 0 + && colon < authority.Length - 1 + && authority[colon] == ':' + && ushort.TryParse(authority[(colon + 1)..], out var port) + && port > 0; } } diff --git a/Tools/Esiur.CLI/Properties/launchSettings.json b/Tools/Esiur.CLI/Properties/launchSettings.json index 5d09dc0..60b3406 100644 --- a/Tools/Esiur.CLI/Properties/launchSettings.json +++ b/Tools/Esiur.CLI/Properties/launchSettings.json @@ -2,7 +2,7 @@ "profiles": { "Esiur.CLI": { "commandName": "Project", - "commandLineArgs": "get-template ep://phase.delta.iq/sys/phase --dir c:\\temp\\an" + "commandLineArgs": "get-template ep://phase.delta.iq:65535/sys/phase --dir c:\\temp\\an" } } } diff --git a/Tools/Esiur.CLI/README.md b/Tools/Esiur.CLI/README.md index a7990e5..9099320 100644 --- a/Tools/Esiur.CLI/README.md +++ b/Tools/Esiur.CLI/README.md @@ -19,13 +19,13 @@ Runtime-specific publishes are configured as self-contained, single-file, and no Create and verify a password-authenticated profile: ```console -esiur login production ep://host --provider password --identity ahmed +esiur login production "ep://host:${ESIUR_PORT}" --provider password --identity ahmed ``` The password prompt does not echo input. For automation, supply it on standard input: ```console -printf '%s' "$ESIUR_PASSWORD" | esiur login production ep://host \ +printf '%s' "$ESIUR_PASSWORD" | esiur login production "ep://host:${ESIUR_PORT}" \ --provider password --identity ahmed --password-stdin ``` @@ -86,7 +86,7 @@ Every operational command accepts a saved profile or a temporary endpoint: ```console esiur --profile production describe sys/service -esiur --endpoint ep://host query sys --output json +esiur --endpoint "ep://host:${ESIUR_PORT}" query sys --output json esiur get sys/service Name --timeout 30s ```