Skip to content

Allow running two IServer implementations side by side #58288

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
1 task done
Misiu opened this issue Oct 8, 2024 · 4 comments
Open
1 task done

Allow running two IServer implementations side by side #58288

Misiu opened this issue Oct 8, 2024 · 4 comments
Labels
area-networking Includes servers, yarp, json patch, bedrock, websockets, http client factory, and http abstractions Needs: Attention 👋 This issue needs the attention of a contributor, typically because the OP has provided an update.

Comments

@Misiu
Copy link

Misiu commented Oct 8, 2024

Is there an existing issue for this?

  • I have searched the existing issues

Is your feature request related to a problem? Please describe the problem.

I need to create an API that will allow two types of communication, a standard way using HTTP requests and a second using AMQP messages.
I've implemented a custom IServer and I was able to plug it into the default ASP.NET Core Web API (.NET8) project using:
builder.Services.AddSingleton<IServer, RabbitMqServer>();
but this disables the default communication via HTTP.

I've tried creating a composite server using https://github.com/stop-cran/AspNetCoreExtras.Solace.Server as a reference,

public class CompositeServer(RabbitMqServer rabbitMqServer, KestrelServer kestrelServer) : IServer
{
    public IFeatureCollection Features => kestrelServer.Features;

    public async Task StartAsync<TContext>(IHttpApplication<TContext> application, CancellationToken cancellationToken) where TContext : notnull
    {
        await rabbitMqServer.StartAsync(application, cancellationToken);
        await kestrelServer.StartAsync(application, cancellationToken);
    }

    public async Task StopAsync(CancellationToken cancellationToken)
    {
        await rabbitMqServer.StopAsync(cancellationToken);
        await kestrelServer.StopAsync(cancellationToken);
    }

    public void Dispose()
    {
        rabbitMqServer.Dispose();
        kestrelServer.Dispose();
    }
}

But when I try to start project from VS I get this errors:

System.IO.IOException
  HResult=0x80131620
  Message=Failed to bind to address http://localhost:5016.
  Source=Microsoft.AspNetCore.Server.Kestrel.Core
  StackTrace:
   at Microsoft.AspNetCore.Server.Kestrel.Core.LocalhostListenOptions.<BindAsync>d__2.MoveNext() in /_/src/Servers/Kestrel/Core/src/LocalhostListenOptions.cs:line 63
   at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.AddressBinder.AddressesStrategy.<BindAsync>d__3.MoveNext() in /_/src/Servers/Kestrel/Core/src/Internal/AddressBinder.cs:line 247
   at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.AddressBinder.<BindAsync>d__0.MoveNext() in /_/src/Servers/Kestrel/Core/src/Internal/AddressBinder.cs:line 32
   at Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServerImpl.<BindAsync>d__31.MoveNext() in /_/src/Servers/Kestrel/Core/src/Internal/KestrelServerImpl.cs:line 309
   at Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServerImpl.<StartAsync>d__28`1.MoveNext() in /_/src/Servers/Kestrel/Core/src/Internal/KestrelServerImpl.cs:line 225
   at Tunnels.CompositeServer.<StartAsync>d__5`1.MoveNext() in C:\Users\tj\source\repos\Tunnels\Tunnels\CompositeServer.cs:line 15
   at Microsoft.AspNetCore.Hosting.GenericWebHostService.<StartAsync>d__40.MoveNext() in /_/src/Hosting/Hosting/src/GenericHost/GenericWebHostService.cs:line 161
   at Microsoft.Extensions.Hosting.Internal.Host.<<StartAsync>b__15_1>d.MoveNext() in /_/src/libraries/Microsoft.Extensions.Hosting/src/Internal/Host.cs:line 136
   at Microsoft.Extensions.Hosting.Internal.Host.<ForeachService>d__18`1.MoveNext() in /_/src/libraries/Microsoft.Extensions.Hosting/src/Internal/Host.cs:line 390
   at Microsoft.Extensions.Hosting.Internal.Host.<StartAsync>d__15.MoveNext() in /_/src/libraries/Microsoft.Extensions.Hosting/src/Internal/Host.cs:line 145
   at Microsoft.Extensions.Hosting.HostingAbstractionsHostExtensions.<RunAsync>d__4.MoveNext() in /_/src/libraries/Microsoft.Extensions.Hosting.Abstractions/src/HostingAbstractionsHostExtensions.cs:line 67
   at Microsoft.Extensions.Hosting.HostingAbstractionsHostExtensions.<RunAsync>d__4.MoveNext() in /_/src/libraries/Microsoft.Extensions.Hosting.Abstractions/src/HostingAbstractionsHostExtensions.cs:line 79
   at Microsoft.Extensions.Hosting.HostingAbstractionsHostExtensions.Run(IHost host) in /_/src/libraries/Microsoft.Extensions.Hosting.Abstractions/src/HostingAbstractionsHostExtensions.cs:line 53
   at Tunnels.Program.Main(String[] args) in C:\Users\tj\source\repos\Tunnels\Tunnels\Program.cs:line 44

  This exception was originally thrown at this call stack:
    Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure.TransportManager.BindAsync(System.Net.EndPoint, Microsoft.AspNetCore.Connections.ConnectionDelegate, Microsoft.AspNetCore.Server.Kestrel.Core.Internal.EndpointConfig, System.Threading.CancellationToken) in TransportManager.cs
    Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServerImpl.StartAsync.__OnBind|0(Microsoft.AspNetCore.Server.Kestrel.Core.ListenOptions, System.Threading.CancellationToken) in KestrelServerImpl.cs
    Microsoft.AspNetCore.Server.Kestrel.Core.Internal.AddressBinder.BindEndpointAsync(Microsoft.AspNetCore.Server.Kestrel.Core.ListenOptions, Microsoft.AspNetCore.Server.Kestrel.Core.Internal.AddressBindContext, System.Threading.CancellationToken) in AddressBinder.cs
    Microsoft.AspNetCore.Server.Kestrel.Core.LocalhostListenOptions.BindAsync(Microsoft.AspNetCore.Server.Kestrel.Core.Internal.AddressBindContext, System.Threading.CancellationToken) in LocalhostListenOptions.cs

Inner Exception 1:
AggregateException: One or more errors occurred. (No registered IConnectionListenerFactory supports endpoint IPEndPoint: 127.0.0.1:5016) (No registered IConnectionListenerFactory supports endpoint IPEndPoint: [::1]:5016)

Inner Exception 2:
InvalidOperationException: No registered IConnectionListenerFactory supports endpoint IPEndPoint: 127.0.0.1:5016

I've tried configuring Kestrel via

builder.WebHost.ConfigureKestrel(options =>
{
    options.ListenAnyIP(5000); // Change to a different port if needed
});

but this gives me this error:

System.InvalidOperationException
  HResult=0x80131509
  Message=No registered IConnectionListenerFactory supports endpoint IPEndPoint: 0.0.0.0:5000
  Source=Microsoft.AspNetCore.Server.Kestrel.Core
  StackTrace:
   at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure.TransportManager.<BindAsync>d__10.MoveNext() in /_/src/Servers/Kestrel/Core/src/Internal/Infrastructure/TransportManager.cs:line 62
   at Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServerImpl.<>c__DisplayClass28_0`1.<<StartAsync>g__OnBind|0>d.MoveNext() in /_/src/Servers/Kestrel/Core/src/Internal/KestrelServerImpl.cs:line 199
   at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.AddressBinder.<BindEndpointAsync>d__3.MoveNext() in /_/src/Servers/Kestrel/Core/src/Internal/AddressBinder.cs:line 90
   at Microsoft.AspNetCore.Server.Kestrel.Core.ListenOptions.<BindAsync>d__61.MoveNext() in /_/src/Servers/Kestrel/Core/src/ListenOptions.cs:line 224
   at Microsoft.AspNetCore.Server.Kestrel.Core.AnyIPListenOptions.<BindAsync>d__1.MoveNext() in /_/src/Servers/Kestrel/Core/src/AnyIPListenOptions.cs:line 42
   at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.AddressBinder.EndpointsStrategy.<BindAsync>d__2.MoveNext() in /_/src/Servers/Kestrel/Core/src/Internal/AddressBinder.cs:line 219
   at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.AddressBinder.<BindAsync>d__0.MoveNext() in /_/src/Servers/Kestrel/Core/src/Internal/AddressBinder.cs:line 32
   at Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServerImpl.<BindAsync>d__31.MoveNext() in /_/src/Servers/Kestrel/Core/src/Internal/KestrelServerImpl.cs:line 309
   at Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServerImpl.<StartAsync>d__28`1.MoveNext() in /_/src/Servers/Kestrel/Core/src/Internal/KestrelServerImpl.cs:line 225
   at Tunnels.CompositeServer.<StartAsync>d__5`1.MoveNext() in C:\Users\tj\source\repos\Tunnels\Tunnels\CompositeServer.cs:line 15
   at Microsoft.AspNetCore.Hosting.GenericWebHostService.<StartAsync>d__40.MoveNext() in /_/src/Hosting/Hosting/src/GenericHost/GenericWebHostService.cs:line 161
   at Microsoft.Extensions.Hosting.Internal.Host.<<StartAsync>b__15_1>d.MoveNext() in /_/src/libraries/Microsoft.Extensions.Hosting/src/Internal/Host.cs:line 136
   at Microsoft.Extensions.Hosting.Internal.Host.<ForeachService>d__18`1.MoveNext() in /_/src/libraries/Microsoft.Extensions.Hosting/src/Internal/Host.cs:line 390
   at Microsoft.Extensions.Hosting.Internal.Host.<StartAsync>d__15.MoveNext() in /_/src/libraries/Microsoft.Extensions.Hosting/src/Internal/Host.cs:line 145
   at Microsoft.Extensions.Hosting.HostingAbstractionsHostExtensions.<RunAsync>d__4.MoveNext() in /_/src/libraries/Microsoft.Extensions.Hosting.Abstractions/src/HostingAbstractionsHostExtensions.cs:line 67
   at Microsoft.Extensions.Hosting.HostingAbstractionsHostExtensions.<RunAsync>d__4.MoveNext() in /_/src/libraries/Microsoft.Extensions.Hosting.Abstractions/src/HostingAbstractionsHostExtensions.cs:line 79
   at Microsoft.Extensions.Hosting.HostingAbstractionsHostExtensions.Run(IHost host) in /_/src/libraries/Microsoft.Extensions.Hosting.Abstractions/src/HostingAbstractionsHostExtensions.cs:line 53
   at Tunnels.Program.Main(String[] args) in C:\Users\tj\source\repos\Tunnels\Tunnels\Program.cs:line 49

Describe the solution you'd like

Ideally, we should be able to easily add additional IServer implementations to projects, so they can work side by side.

Additional context

I want to reuse as many features as I can, my idea is to configure authorization, cache, rate limiting, etc in my app and allow invoking requests via HTTP and AMQP.

If there is an easier way to "dispatch" a request from AMQP message and catch the response please let me know.

@ghost ghost added the area-networking Includes servers, yarp, json patch, bedrock, websockets, http client factory, and http abstractions label Oct 8, 2024
@amcasey
Copy link
Member

amcasey commented Oct 8, 2024

It feels like maybe you want a new transport, in the same way that we added named pipes. Then you'd just have separate endpoints for HTTP and AMQP. Or have I misunderstood your question?

@Misiu
Copy link
Author

Misiu commented Oct 8, 2024

my project has two parts: plugin system API and AMQP based requests/responses.
For my POC I want to have a standard API that I can call via HTTP when used locally (inside a network) and via AMQP (RabbitMQ messages).
The idea is to call the same endpoint via HTTP or via AMQP message.

The project involves creating two APIs, the first will be available on the internet, it will serialize requests and send them via AMQP to the second one, then the message will be processed and the response will be stored as a separate AMQP message on a separate queue.
In the first API, I plan to build a middleware to serialize requests, in the second I initially planned to build a custom transport, but I wasn't able to find a good example of how to do this (I found socket transport , quic and named pipes, but wasn't able to adopt them to event-based communication like RabbitMQ messages), so I ended up using a custom IServer and it is working really good.

I don't mind having a separate server but would like to know which approach would be better/will perform better.

I think that a custom transport should allow me to have both HTTP and AMQP at the same time (I saw that we can have two transports at the same time), but I must admit that the example would help me to get started.

@captainsafia
Copy link
Member

Can you share a little bit more about what you're doing here?

As a general rule, the IServer implementation is optimized for synchronous request-response patterns and would not be a good fit for message-oriented protocols like AMQP. Existing concepts, like caching and auth middleware, don't map directly to a message-oriented implementation. Also, ASP.NET Core doesn't really support running multiple IServer instances at once with any reasonable sharing between them (see #27213).

Are you simply just trying to deserialize the request bodies in the AMQP format in your API or is your intent to build a more full-fledged messaging protocol here?

@captainsafia captainsafia added the Needs: Author Feedback The author of this issue needs to respond in order for us to continue investigating this issue. label Oct 9, 2024
@Misiu
Copy link
Author

Misiu commented Oct 14, 2024

@captainsafia Sorry for the late reply - writing from a hospital.
The idea is to be able to use AMQP as a messaging protocol.
I'm currently using NSQ in an old app, that I'm rewriting right now.

Currently, the entire solution has two parts:

  • server - publicly available, has rest API that is used to "send" tasks to clients.
  • client - a .net .1 app, that is loading plugins from DLLs. It connects to an NSQ queue and handles incoming tasks by calling methods from plugins and putting responses to separate queues.

This was designed this way, to be able to handle long-running operations. When you send a task that will take a long time (for example generating a report or doing a database backup) the server will return a specific HTTP code with the task ID when there is no response from the client within 10 seconds, you can then use the ID to check if the task finished and get the results (separate HTTP method).

I'm rewriting the solution to be able to call the same methods locally and via the server (so locally the client will call local API without proxying the requests via RabbitMQ).
My first idea was to create a custom transport, but because I didn't find any good examples I decided to go with iServer implementation.

The idea is quite simple, call REST API via a standard HTTP request or via AMQP message.

I could create a hosted service that would handle RabbitMQ messages, but I'm not sure how to create a HttpRequest there, pass it to the application, and handle the response.

I hope my idea and request is much easier to understand.

@dotnet-policy-service dotnet-policy-service bot added Needs: Attention 👋 This issue needs the attention of a contributor, typically because the OP has provided an update. and removed Needs: Author Feedback The author of this issue needs to respond in order for us to continue investigating this issue. Status: No Recent Activity labels Oct 14, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-networking Includes servers, yarp, json patch, bedrock, websockets, http client factory, and http abstractions Needs: Attention 👋 This issue needs the attention of a contributor, typically because the OP has provided an update.
Projects
None yet
Development

No branches or pull requests

3 participants