mirror of
https://github.com/esiur/esiur-dotnet.git
synced 2026-09-08 10:10:49 +00:00
Port
This commit is contained in:
@@ -14,12 +14,15 @@ internal class Program
|
|||||||
|
|
||||||
private static async Task Main(string[] args)
|
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();
|
var wh = new Warehouse();
|
||||||
|
|
||||||
// Create a store to keep objects.
|
// Create a store to keep objects.
|
||||||
var system = await wh.Put("sys", new MemoryStore());
|
var system = await wh.Put("sys", new MemoryStore());
|
||||||
// Create a distibuted server
|
// 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
|
// Add your object to the store
|
||||||
var service = await wh.Put("sys/demo", new Demo());
|
var service = await wh.Put("sys/demo", new Demo());
|
||||||
|
|
||||||
@@ -86,7 +89,7 @@ internal class Program
|
|||||||
// Start your server
|
// Start your server
|
||||||
await wh.Open();
|
await wh.Open();
|
||||||
|
|
||||||
Console.WriteLine("Running on http://localhost:8888");
|
Console.WriteLine($"Running on http://localhost:8888/?epPort={epPort}");
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
async function init() {
|
async function init() {
|
||||||
try {
|
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
|
autoReconnect: true
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -217,7 +217,7 @@ internal sealed class EsiurWebSocketEndpoint
|
|||||||
{
|
{
|
||||||
endPoint = null!;
|
endPoint = null!;
|
||||||
if (address is 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.Any)
|
||||||
|| address.Equals(IPAddress.IPv6Any)
|
|| address.Equals(IPAddress.IPv6Any)
|
||||||
|| address.Equals(IPAddress.None)
|
|| address.Equals(IPAddress.None)
|
||||||
|
|||||||
@@ -260,10 +260,10 @@ using Esiur.Resource;
|
|||||||
var clientWarehouse = new Warehouse();
|
var clientWarehouse = new Warehouse();
|
||||||
|
|
||||||
var service = await clientWarehouse.Get<IResource>(
|
var service = await clientWarehouse.Get<IResource>(
|
||||||
"ep://api.example.com/sys/service",
|
"ep://api.example.com:443/sys/service",
|
||||||
new EpConnectionContext
|
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.
|
// Add the authentication mode, identity, and protocol required by the server.
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
<Copyright>Ahmed Kh. Zamil</Copyright>
|
<Copyright>Ahmed Kh. Zamil</Copyright>
|
||||||
<PackageProjectUrl>https://www.esiur.com</PackageProjectUrl>
|
<PackageProjectUrl>https://www.esiur.com</PackageProjectUrl>
|
||||||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||||
<Version>3.0.0</Version>
|
<Version>3.0.1</Version>
|
||||||
<RepositoryUrl>https://github.com/esiur/esiur-dotnet</RepositoryUrl>
|
<RepositoryUrl>https://github.com/esiur/esiur-dotnet</RepositoryUrl>
|
||||||
<Authors>Ahmed Kh. Zamil</Authors>
|
<Authors>Ahmed Kh. Zamil</Authors>
|
||||||
<AssemblyVersion></AssemblyVersion>
|
<AssemblyVersion></AssemblyVersion>
|
||||||
|
|||||||
@@ -3143,10 +3143,16 @@ public partial class EpConnection : NetworkConnection, IStore
|
|||||||
return new AsyncReply<bool>(true);
|
return new AsyncReply<bool>(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 address = endpoint.Host;
|
||||||
var port = host.Length > 1 ? ushort.Parse(host[1]) : (ushort)10518;
|
var port = checked((ushort)endpoint.Port);
|
||||||
|
|
||||||
// assign domain from hostname if not provided
|
// assign domain from hostname if not provided
|
||||||
if (context is EpConnectionContext epContext)
|
if (context is EpConnectionContext epContext)
|
||||||
|
|||||||
@@ -131,12 +131,14 @@ public class EpServer : NetworkServer<EpConnection>, IResource
|
|||||||
set;
|
set;
|
||||||
}
|
}
|
||||||
|
|
||||||
//[Attribute]
|
/// <summary>
|
||||||
|
/// Application-supplied native TCP port. Zero requests an ephemeral port from the OS.
|
||||||
|
/// </summary>
|
||||||
public ushort Port
|
public ushort Port
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
set;
|
set;
|
||||||
} = 10518;
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Controls whether warehouse initialization opens Esiur's native TCP listener.
|
/// Controls whether warehouse initialization opens Esiur's native TCP listener.
|
||||||
|
|||||||
@@ -93,8 +93,13 @@ Add an `EpServer` to expose the resource through the Esiur EP protocol. Anonymou
|
|||||||
|
|
||||||
|
|
||||||
```C#
|
```C#
|
||||||
|
var epPort = ushort.Parse(
|
||||||
|
Environment.GetEnvironmentVariable("ESIUR_PORT")
|
||||||
|
?? throw new InvalidOperationException("Set ESIUR_PORT."));
|
||||||
|
|
||||||
await warehouse.Put("sys/server", new EpServer
|
await warehouse.Put("sys/server", new EpServer
|
||||||
{
|
{
|
||||||
|
Port = epPort,
|
||||||
AllowUnauthorizedAccess = true, // Development only.
|
AllowUnauthorizedAccess = true, // Development only.
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
@@ -113,11 +118,15 @@ To sum up
|
|||||||
>using Esiur.Resource;
|
>using Esiur.Resource;
|
||||||
>using Esiur.Stores;
|
>using Esiur.Stores;
|
||||||
>
|
>
|
||||||
|
>var epPort = ushort.Parse(
|
||||||
|
> Environment.GetEnvironmentVariable("ESIUR_PORT")
|
||||||
|
> ?? throw new InvalidOperationException("Set ESIUR_PORT."));
|
||||||
>var warehouse = new Warehouse();
|
>var warehouse = new Warehouse();
|
||||||
>await warehouse.Put("sys", new MemoryStore());
|
>await warehouse.Put("sys", new MemoryStore());
|
||||||
>await warehouse.Put("sys/hello", new HelloResource());
|
>await warehouse.Put("sys/hello", new HelloResource());
|
||||||
>await warehouse.Put("sys/server", new EpServer
|
>await warehouse.Put("sys/server", new EpServer
|
||||||
>{
|
>{
|
||||||
|
> Port = epPort,
|
||||||
> AllowUnauthorizedAccess = true, // Development only.
|
> AllowUnauthorizedAccess = true, // Development only.
|
||||||
>});
|
>});
|
||||||
>await warehouse.Open();
|
>await warehouse.Open();
|
||||||
@@ -129,7 +138,8 @@ To access our resource remotely, we need to use it's full path including the pro
|
|||||||
|
|
||||||
```C#
|
```C#
|
||||||
var warehouse = new Warehouse();
|
var warehouse = new Warehouse();
|
||||||
dynamic res = await warehouse.Get<IResource>("ep://localhost/sys/hello");
|
var epPort = ushort.Parse(Environment.GetEnvironmentVariable("ESIUR_PORT")!);
|
||||||
|
dynamic res = await warehouse.Get<IResource>($"ep://localhost:{epPort}/sys/hello");
|
||||||
```
|
```
|
||||||
|
|
||||||
Now we can invoke the exported functions and read/write properties;
|
Now we can invoke the exported functions and read/write properties;
|
||||||
@@ -147,7 +157,8 @@ Summing up
|
|||||||
>using Esiur.Resource;
|
>using Esiur.Resource;
|
||||||
>
|
>
|
||||||
>var warehouse = new Warehouse();
|
>var warehouse = new Warehouse();
|
||||||
>dynamic res = await warehouse.Get<IResource>("ep://localhost/sys/hello");
|
>var epPort = ushort.Parse(Environment.GetEnvironmentVariable("ESIUR_PORT")!);
|
||||||
|
>dynamic res = await warehouse.Get<IResource>($"ep://localhost:{epPort}/sys/hello");
|
||||||
>
|
>
|
||||||
>var reply = await res.SayHi("Hi, I'm calling you from dotnet");
|
>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.
|
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
|
```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.
|
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
|
Allowing us to use
|
||||||
```C#
|
```C#
|
||||||
var warehouse = new Warehouse();
|
var warehouse = new Warehouse();
|
||||||
var res = await warehouse.Get<MyResource>("ep://localhost/sys/hello");
|
var epPort = ushort.Parse(Environment.GetEnvironmentVariable("ESIUR_PORT")!);
|
||||||
|
var res = await warehouse.Get<MyResource>($"ep://localhost:{epPort}/sys/hello");
|
||||||
var reply = await res.SayHi("Static typing is better");
|
var reply = await res.SayHi("Static typing is better");
|
||||||
Console.WriteLine(reply);
|
Console.WriteLine(reply);
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ dotnet add package Esiur.AspNetCore --version 3.0.0
|
|||||||
For the standalone runtime or a client:
|
For the standalone runtime or a client:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
dotnet add package Esiur --version 3.0.0
|
dotnet add package Esiur --version 3.0.1
|
||||||
```
|
```
|
||||||
|
|
||||||
## Define a resource
|
## Define a resource
|
||||||
@@ -157,7 +157,7 @@ using Esiur.Resource;
|
|||||||
var client = new Warehouse();
|
var client = new Warehouse();
|
||||||
|
|
||||||
dynamic counter = await client.Get<IResource>(
|
dynamic counter = await client.Get<IResource>(
|
||||||
"ep://localhost/sys/counter",
|
"ep://localhost:8080/sys/counter",
|
||||||
new EpConnectionContext
|
new EpConnectionContext
|
||||||
{
|
{
|
||||||
WebSocketUri = new Uri("ws://localhost:8080/esiur"),
|
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:
|
For native TCP, omit `WebSocketUri` and include the EP port in the logical URL:
|
||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
|
var epPort = ushort.Parse(
|
||||||
|
Environment.GetEnvironmentVariable("ESIUR_PORT")
|
||||||
|
?? throw new InvalidOperationException("Set ESIUR_PORT."));
|
||||||
|
|
||||||
dynamic counter = await client.Get<IResource>(
|
dynamic counter = await client.Get<IResource>(
|
||||||
"ep://localhost:10518/sys/counter");
|
$"ep://localhost:{epPort}/sys/counter");
|
||||||
```
|
```
|
||||||
|
|
||||||
## Standalone hosting
|
## Standalone hosting
|
||||||
@@ -191,13 +195,16 @@ using Esiur.Protocol;
|
|||||||
using Esiur.Resource;
|
using Esiur.Resource;
|
||||||
using Esiur.Stores;
|
using Esiur.Stores;
|
||||||
|
|
||||||
|
var epPort = ushort.Parse(
|
||||||
|
Environment.GetEnvironmentVariable("ESIUR_PORT")
|
||||||
|
?? throw new InvalidOperationException("Set ESIUR_PORT."));
|
||||||
var warehouse = new Warehouse();
|
var warehouse = new Warehouse();
|
||||||
|
|
||||||
await warehouse.Put("sys", new MemoryStore());
|
await warehouse.Put("sys", new MemoryStore());
|
||||||
await warehouse.Put("sys/counter", new CounterResource());
|
await warehouse.Put("sys/counter", new CounterResource());
|
||||||
await warehouse.Put("sys/server", new EpServer
|
await warehouse.Put("sys/server", new EpServer
|
||||||
{
|
{
|
||||||
Port = 10518,
|
Port = epPort,
|
||||||
AllowUnauthorizedAccess = true, // Development only.
|
AllowUnauthorizedAccess = true, // Development only.
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -344,7 +351,7 @@ typed models. Install the v3 CLI as a .NET tool:
|
|||||||
|
|
||||||
```shell
|
```shell
|
||||||
dotnet tool install --global Esiur.CLI --version 3.0.0
|
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
|
Use `--async-setters` to generate asynchronous property setters. The CLI also
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ public sealed class CommandTests
|
|||||||
{
|
{
|
||||||
["production"] = new ConnectionProfile
|
["production"] = new ConnectionProfile
|
||||||
{
|
{
|
||||||
Name = "production", Endpoint = "ep://host",
|
Name = "production", Endpoint = "ep://host:65535",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}, default);
|
}, default);
|
||||||
|
|||||||
@@ -8,13 +8,14 @@ namespace Esiur.CLI.Tests;
|
|||||||
public sealed class ConfigurationTests
|
public sealed class ConfigurationTests
|
||||||
{
|
{
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData("ep://localhost", "ep://localhost")]
|
[InlineData("ep://localhost:65535", "ep://localhost:65535")]
|
||||||
[InlineData("ep://example.test:9000/sys/service", "ep://example.test:9000")]
|
[InlineData("ep://example.test:9000/sys/service", "ep://example.test:9000")]
|
||||||
public void EndpointParserExtractsConnectionEndpoint(string value, string expected) =>
|
public void EndpointParserExtractsConnectionEndpoint(string value, string expected) =>
|
||||||
Assert.Equal(expected, EndpointParser.ConnectionEndpoint(value));
|
Assert.Equal(expected, EndpointParser.ConnectionEndpoint(value));
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData("http://localhost")]
|
[InlineData("http://localhost")]
|
||||||
|
[InlineData("ep://localhost")]
|
||||||
[InlineData("ep:///missing-host")]
|
[InlineData("ep:///missing-host")]
|
||||||
[InlineData("not-an-endpoint")]
|
[InlineData("not-an-endpoint")]
|
||||||
public void EndpointParserRejectsInvalidEndpoints(string value) =>
|
public void EndpointParserRejectsInvalidEndpoints(string value) =>
|
||||||
@@ -43,7 +44,7 @@ public sealed class ConfigurationTests
|
|||||||
["production"] = new ConnectionProfile
|
["production"] = new ConnectionProfile
|
||||||
{
|
{
|
||||||
Name = "production",
|
Name = "production",
|
||||||
Endpoint = "ep://host",
|
Endpoint = "ep://host:65535",
|
||||||
Provider = "password",
|
Provider = "password",
|
||||||
Identity = "ahmed",
|
Identity = "ahmed",
|
||||||
},
|
},
|
||||||
@@ -55,7 +56,7 @@ public sealed class ConfigurationTests
|
|||||||
Assert.DoesNotContain("secret", text, StringComparison.OrdinalIgnoreCase);
|
Assert.DoesNotContain("secret", text, StringComparison.OrdinalIgnoreCase);
|
||||||
var loaded = await store.LoadAsync(default);
|
var loaded = await store.LoadAsync(default);
|
||||||
Assert.Equal("production", loaded.DefaultProfile);
|
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); }
|
finally { directory.Delete(true); }
|
||||||
}
|
}
|
||||||
@@ -71,14 +72,14 @@ public sealed class ConfigurationTests
|
|||||||
{
|
{
|
||||||
["saved"] = new ConnectionProfile
|
["saved"] = new ConnectionProfile
|
||||||
{
|
{
|
||||||
Name = "saved", Endpoint = "ep://saved", OutputFormat = "raw",
|
Name = "saved", Endpoint = "ep://saved:65535", OutputFormat = "raw",
|
||||||
Identity = "stored",
|
Identity = "stored",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
var result = ConfigurationResolver.Resolve(configuration,
|
var result = ConfigurationResolver.Resolve(configuration,
|
||||||
new GlobalOptions("saved", "ep://explicit", null, "explicit", "json", TimeSpan.FromSeconds(4), false, false));
|
new GlobalOptions("saved", "ep://explicit:65535", null, "explicit", "json", TimeSpan.FromSeconds(4), false, false));
|
||||||
Assert.Equal("ep://explicit", result.Endpoint);
|
Assert.Equal("ep://explicit:65535", result.Endpoint);
|
||||||
Assert.Equal("explicit", result.Identity);
|
Assert.Equal("explicit", result.Identity);
|
||||||
Assert.Equal("json", result.OutputFormat);
|
Assert.Equal("json", result.OutputFormat);
|
||||||
Assert.Equal(TimeSpan.FromSeconds(4), result.Timeout);
|
Assert.Equal(TimeSpan.FromSeconds(4), result.Timeout);
|
||||||
|
|||||||
@@ -361,6 +361,31 @@ public sealed class AspNetCoreIntegrationTests
|
|||||||
cleanupCancellation.Token);
|
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]
|
[Fact]
|
||||||
public async Task HostShutdown_CancelsWebSocketAndCleansUpAdmission()
|
public async Task HostShutdown_CancelsWebSocketAndCleansUpAdmission()
|
||||||
{
|
{
|
||||||
@@ -640,7 +665,8 @@ public sealed class AspNetCoreIntegrationTests
|
|||||||
private static WebApplication BuildApplication(
|
private static WebApplication BuildApplication(
|
||||||
Action<EsiurBuilder>? configureEsiur = null,
|
Action<EsiurBuilder>? configureEsiur = null,
|
||||||
Action<EpServer>? configureServer = null,
|
Action<EpServer>? configureServer = null,
|
||||||
Action<WarehouseConfiguration>? configureWarehouse = null)
|
Action<WarehouseConfiguration>? configureWarehouse = null,
|
||||||
|
Action<WebApplication>? configureApplication = null)
|
||||||
{
|
{
|
||||||
var builder = WebApplication.CreateBuilder(new WebApplicationOptions
|
var builder = WebApplication.CreateBuilder(new WebApplicationOptions
|
||||||
{
|
{
|
||||||
@@ -665,6 +691,7 @@ public sealed class AspNetCoreIntegrationTests
|
|||||||
|
|
||||||
var application = builder.Build();
|
var application = builder.Build();
|
||||||
application.UseWebSockets();
|
application.UseWebSockets();
|
||||||
|
configureApplication?.Invoke(application);
|
||||||
application.MapGet("/health", () => Results.Text("healthy"));
|
application.MapGet("/health", () => Results.Text("healthy"));
|
||||||
application.MapEsiur("/esiur");
|
application.MapEsiur("/esiur");
|
||||||
return application;
|
return application;
|
||||||
@@ -673,7 +700,8 @@ public sealed class AspNetCoreIntegrationTests
|
|||||||
private static async Task<TestApplication> StartApplicationAsync(
|
private static async Task<TestApplication> StartApplicationAsync(
|
||||||
Action<EpServer>? configureServer = null,
|
Action<EpServer>? configureServer = null,
|
||||||
Action<WarehouseConfiguration>? configureWarehouse = null,
|
Action<WarehouseConfiguration>? configureWarehouse = null,
|
||||||
Action<EsiurBuilder>? configureEsiur = null)
|
Action<EsiurBuilder>? configureEsiur = null,
|
||||||
|
Action<WebApplication>? configureApplication = null)
|
||||||
{
|
{
|
||||||
var application = BuildApplication(
|
var application = BuildApplication(
|
||||||
esiur =>
|
esiur =>
|
||||||
@@ -682,7 +710,8 @@ public sealed class AspNetCoreIntegrationTests
|
|||||||
configureEsiur?.Invoke(esiur);
|
configureEsiur?.Invoke(esiur);
|
||||||
},
|
},
|
||||||
configureServer,
|
configureServer,
|
||||||
configureWarehouse);
|
configureWarehouse,
|
||||||
|
configureApplication);
|
||||||
|
|
||||||
using var cancellation = new CancellationTokenSource(TestTimeout);
|
using var cancellation = new CancellationTokenSource(TestTimeout);
|
||||||
await application.StartAsync(cancellation.Token);
|
await application.StartAsync(cancellation.Token);
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ public sealed class EpConnectionReconnectTests
|
|||||||
|
|
||||||
var open = connection.Connect(
|
var open = connection.Connect(
|
||||||
hostname: "localhost",
|
hostname: "localhost",
|
||||||
port: 10518,
|
port: IPEndPoint.MaxPort,
|
||||||
domain: "test");
|
domain: "test");
|
||||||
|
|
||||||
var completed = await Task.WhenAny(
|
var completed = await Task.WhenAny(
|
||||||
@@ -62,7 +62,7 @@ public sealed class EpConnectionReconnectTests
|
|||||||
|
|
||||||
var open = connection.Connect(
|
var open = connection.Connect(
|
||||||
hostname: "localhost",
|
hostname: "localhost",
|
||||||
port: 10518,
|
port: IPEndPoint.MaxPort,
|
||||||
domain: "test");
|
domain: "test");
|
||||||
|
|
||||||
await delayedSocket.ConnectInvoked.WaitAsync(TimeSpan.FromSeconds(2));
|
await delayedSocket.ConnectInvoked.WaitAsync(TimeSpan.FromSeconds(2));
|
||||||
@@ -95,7 +95,7 @@ public sealed class EpConnectionReconnectTests
|
|||||||
|
|
||||||
var open = connection.Connect(
|
var open = connection.Connect(
|
||||||
hostname: "localhost",
|
hostname: "localhost",
|
||||||
port: 10518,
|
port: IPEndPoint.MaxPort,
|
||||||
domain: "test");
|
domain: "test");
|
||||||
|
|
||||||
connection.AutoReconnect = false;
|
connection.AutoReconnect = false;
|
||||||
@@ -133,7 +133,7 @@ public sealed class EpConnectionReconnectTests
|
|||||||
_ = connection.Connect(
|
_ = connection.Connect(
|
||||||
initialSocket,
|
initialSocket,
|
||||||
hostname: "localhost",
|
hostname: "localhost",
|
||||||
port: 10518,
|
port: IPEndPoint.MaxPort,
|
||||||
domain: "test");
|
domain: "test");
|
||||||
initialSocket.Disconnect();
|
initialSocket.Disconnect();
|
||||||
connection.AutoReconnect = false;
|
connection.AutoReconnect = false;
|
||||||
@@ -160,7 +160,7 @@ public sealed class EpConnectionReconnectTests
|
|||||||
public SocketState State => state;
|
public SocketState State => state;
|
||||||
public INetworkReceiver<ISocket> Receiver { get; set; } = null!;
|
public INetworkReceiver<ISocket> Receiver { get; set; } = null!;
|
||||||
public IPEndPoint RemoteEndPoint { get; } =
|
public IPEndPoint RemoteEndPoint { get; } =
|
||||||
new(IPAddress.Loopback, 10518);
|
new(IPAddress.Loopback, IPEndPoint.MaxPort);
|
||||||
public IPEndPoint LocalEndPoint { get; } =
|
public IPEndPoint LocalEndPoint { get; } =
|
||||||
new(IPAddress.Loopback, 50000);
|
new(IPAddress.Loopback, 50000);
|
||||||
public int ConnectCount { get; private set; }
|
public int ConnectCount { get; private set; }
|
||||||
|
|||||||
@@ -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<FormatException>(async () =>
|
||||||
|
await warehouse.Get<EpConnection>("ep://localhost/sys/resource"));
|
||||||
|
|
||||||
|
Assert.Contains("explicit port", exception.Message, StringComparison.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -251,7 +251,7 @@ public class PeerConnectionLimitTests
|
|||||||
connection,
|
connection,
|
||||||
null);
|
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);
|
var deadline = DateTime.UtcNow + TimeSpan.FromSeconds(3);
|
||||||
while (socket.State != SocketState.Closed && DateTime.UtcNow < deadline)
|
while (socket.State != SocketState.Closed && DateTime.UtcNow < deadline)
|
||||||
@@ -269,7 +269,7 @@ public class PeerConnectionLimitTests
|
|||||||
public SocketState State { get; private set; } = SocketState.Established;
|
public SocketState State { get; private set; } = SocketState.Established;
|
||||||
public INetworkReceiver<ISocket> Receiver { get; set; } = null!;
|
public INetworkReceiver<ISocket> Receiver { get; set; } = null!;
|
||||||
public IPEndPoint RemoteEndPoint { get; }
|
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)
|
public TestSocket(IPAddress address, int port)
|
||||||
=> RemoteEndPoint = new IPEndPoint(address, port);
|
=> RemoteEndPoint = new IPEndPoint(address, port);
|
||||||
|
|||||||
@@ -382,7 +382,7 @@ public class SocketProtocolSecurityTests
|
|||||||
public SocketState State { get; private set; } = SocketState.Listening;
|
public SocketState State { get; private set; } = SocketState.Listening;
|
||||||
public INetworkReceiver<ISocket> Receiver { get; set; } = null!;
|
public INetworkReceiver<ISocket> Receiver { get; set; } = null!;
|
||||||
public IPEndPoint RemoteEndPoint => 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()
|
public ISocket Accept()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -74,8 +74,37 @@ public static class ConfigurationResolver
|
|||||||
if (!Uri.TryCreate(endpoint, UriKind.Absolute, out var uri)
|
if (!Uri.TryCreate(endpoint, UriKind.Absolute, out var uri)
|
||||||
|| !ValidSchemes.Contains(uri.Scheme, StringComparer.OrdinalIgnoreCase)
|
|| !ValidSchemes.Contains(uri.Scheme, StringComparer.OrdinalIgnoreCase)
|
||||||
|| string.IsNullOrWhiteSpace(uri.Host)
|
|| string.IsNullOrWhiteSpace(uri.Host)
|
||||||
|| !string.IsNullOrEmpty(uri.UserInfo))
|
|| !string.IsNullOrEmpty(uri.UserInfo)
|
||||||
throw new CliException($"Endpoint \"{endpoint}\" is not a valid ep:// or ws:// endpoint.", ExitCodes.InvalidArguments);
|
|| !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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
"profiles": {
|
"profiles": {
|
||||||
"Esiur.CLI": {
|
"Esiur.CLI": {
|
||||||
"commandName": "Project",
|
"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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,13 +19,13 @@ Runtime-specific publishes are configured as self-contained, single-file, and no
|
|||||||
Create and verify a password-authenticated profile:
|
Create and verify a password-authenticated profile:
|
||||||
|
|
||||||
```console
|
```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:
|
The password prompt does not echo input. For automation, supply it on standard input:
|
||||||
|
|
||||||
```console
|
```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
|
--provider password --identity ahmed --password-stdin
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -86,7 +86,7 @@ Every operational command accepts a saved profile or a temporary endpoint:
|
|||||||
|
|
||||||
```console
|
```console
|
||||||
esiur --profile production describe sys/service
|
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
|
esiur get sys/service Name --timeout 30s
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user