Skip to content

Add an adapter for Wired.IO#702

Closed
Kaliumhexacyanoferrat wants to merge 5 commits intomainfrom
feature/wiredio
Closed

Add an adapter for Wired.IO#702
Kaliumhexacyanoferrat wants to merge 5 commits intomainfrom
feature/wiredio

Conversation

@Kaliumhexacyanoferrat
Copy link
Copy Markdown
Owner

@Kaliumhexacyanoferrat Kaliumhexacyanoferrat commented Oct 17, 2025

@MDA2AV fyI - just out of curiosity

@Kaliumhexacyanoferrat Kaliumhexacyanoferrat self-assigned this Oct 17, 2025
@Kaliumhexacyanoferrat
Copy link
Copy Markdown
Owner Author

Current state:

  • Request cannot be mapped as the IRequest lacks some basic HTTP protocol information (such as HTTP version)
  • Cannot set security per endpoint (which would need to be documented)
  • Should be generic (so not relying on the Http11Context but any T)

@MDA2AV
Copy link
Copy Markdown
Collaborator

MDA2AV commented Oct 21, 2025

Wow this is interesting, I'll definitely have a closer look when I have time.

Btw, WiredHttp11(Http11Context) is kind of obsolete, WiredHttp11Express(Http11ExpressContext) is likely the future as it's 20-30% faster plus a better implementation of many features like spa/mpa serving, websockets (wip) etc. But nonetheless all IHttpHandler are supported, just depends on how the Builder<THandler, TContext> is created. Typically a TContext is created for a THandler and they may not be interchangeable like for example:

WiredHttp11(THandler) is built with Http11Context(TContext) by default
WiredHttp11Express(THandler) is built with Http11ExpressContext(TContext) by default

and building WiredHttp11Express with Http11Context may not work, I could create a Http11ExpressContext2 that is compatible with WiredHttp11Express though.

Also not sure if this could affect but while I am keeping backward compatibility with endpoint creation like
.MapGet("/route", scope => async ctx =>{ });

also added a more ergonomic

.MapGet("/route", async ctx =>{ });
or
.MapGet("/route", ctx =>{ }); for non async

the DI dependencies can be solved from the context and this avoids small closures as we can also define

.MapGet("/route", static async ctx =>{ });
which is a pure delegate

I've been considering the Microsoft approach in Minimal API's in which you can directly resolve dependencies and such from the delegate definition which is very ergonomic but that costs a lot performance as we can't cache the full pipeline call as effectively plus reflection issues when building NativeAOT

Edit: Please let me know if you find some issues integrating, I could develop a THandler/TContext with higher GenHttp compatibility by using similar IRequest and IResponse classes

After 9.6.0 is out prob this week in 2-3 days I can add a GenHttp11 or something using GenHttp's IRequest and IResponse directly, that would probably help integrating

@Kaliumhexacyanoferrat
Copy link
Copy Markdown
Owner Author

Kaliumhexacyanoferrat commented Oct 21, 2025

The idea was to give your project a little boost by adding the functionality GenHTTP provides via an adapter. I probably would remove the engine part, that was just to get used on how to use WiredIO as a server (and to be able to run the integration test suite to ensure full coverage). Therefore I would try to implement an adapter that can probably just rely on IBaseRequest (if this is intended to be used by all context classes), so the user can choose whatever context and handler they would like to use.

To be honest I am currently thinking about retiring from HTTP server development and to spin off a framework from GenHTTP that basically provides the functionality but can be used on any .NET server engine using a specific binding. This would provide the same functionality as GenHTTP modules today, but probably in a more clean and reduced way, using code generation to avoid reflection where possible and embracing the rules for dependency injection, logging etc. defined by the server engine used. Basically similar to what exists in the Java ecosystem with servlets and frameworks like JAX-RS. I would then strip all the functionality off GenHTTP 11 and publish it just as a basic server that can be used with this framework.

Btw. do you already have some MapAny() method, so basically a Map("/route*")? I would currently need something like this to enable routing on GenHTTP module level, see

=> app.Map(path + "/{*any}", async context => await Bridge.MapAsync(context, handler, companion: companion, registeredPath: path));

I can solve this with BuildPipeline() but this is too invasive.

Adding a specific IRequest / IResponse implementation in WiredIO sure sounds as clean and efficient as possible (for now), but I guess this is a too tight coupling between the frameworks - at least this would be my feeling.

@MDA2AV
Copy link
Copy Markdown
Collaborator

MDA2AV commented Oct 21, 2025

Yea that would give a huge boost, it's very difficult in .NET ecosystem for people to adhere to anything that isn't asp net, which is understandable.

Hmm I see, that actually makes a lot of sense and there isn't anything like that, that I know of in .NET.

Currently the Map methods are bound with the http method like MapGet, MapPost etc which works by resolving keyed delegates from the DI container where the key is the http method string like "GET", plus the generic route for each endpoint.

app.MapAny($"{httpMethod}_/{*anyRoute}", async context => await Bridge.MapAsync(context, handler, companion: companion, registeredPath: path));

Would this work? I'd need to receive both the http method and route since same route can exist for many http methods. Would be quite simple to implement, probably already available in 1-2 days

Adding a specific IRequest / IResponse implementation in WiredIO sure sounds as clean and efficient as possible (for now), but I guess this is a too tight coupling between the frameworks - at least this would be my feeling.

The way the response building works on Wired.IO is already very similar to GenHttp since it was kind of inspired on it as it fits very well the possibility of alwayd returning a simple Task on all endpoints, so tbh the change wouldn't be a lot, just adding some missing properties on the IRequest mainly like cookies stuff and Http version

@Kaliumhexacyanoferrat
Copy link
Copy Markdown
Owner Author

I would need access to anyRoute as a string variable somewhere, so I can tell what has already been handled by WiredIO and what still needs to be handled by the registered handler. Well that's the typical way - with your implementation we could just subtract the original route (which would need to be accessible somewhere) to determine the remaining part. Both ways would work. Or if you are fine with just root level routing, a Map() without any path would work as well.

@MDA2AV
Copy link
Copy Markdown
Collaborator

MDA2AV commented Oct 21, 2025

Hmm but isn't this called when the user registers an endpoint on GenHttp? Wouldn't you just have access to the route this way?
I was thinking GenHttp would call WiredIO's MapAny and pass it the route it gets from the user webservice for example.
The user would still register the endpoints GenHttp style no?

@Kaliumhexacyanoferrat
Copy link
Copy Markdown
Owner Author

Kaliumhexacyanoferrat commented Oct 21, 2025

Ah, as you only have root level routes we don't need any of this I guess. Let's do a quick example to sync. Use case would be something like:

using GenHTTP.Adapters.WiredIO:

var api = Layout.Create(); // ...

var builder = WiredApp.CreateBuilder()
                                         .Map("/api", api);

If we call POST /api/some/route, we need to register a catch-all endpoint in our WiredApp, that will handle /api/... and tell our integration that the remaining path is (some/route). So we need:

  • A way to match all http verbs
  • A way to register a handler for all routes that start with a specific string
  • Need to know the remaining part which are to be routed by the GenHTTP integration (which is fine because we know /api/ is absolute and not a sub route as it could be in ASP)

Sorry about the formatting, sent from mobile.

@MDA2AV
Copy link
Copy Markdown
Collaborator

MDA2AV commented Oct 21, 2025

Ahh yes WiredIO doesn't have group route thingy, every route is root

I could try implement something like

var builder= WiredApp.CreateBuilder();

// Create a route group for /api
var api = builder.MapGroup("/api");

// Subgroup for /api/users
var users = api.MapGroup("/users");

users.MapGet("/", async ctx => { ... });
users.MapGet("/{id}", async ctx => { ... });
users.MapPost("/", async ctx => { ... });

builder.Build().Run();

Resulting endpoints:
GET /api/users
GET /api/users/{id}
POST /api/users

I'm starting to work on this, definetly a must have, final result should look like

Edit: Once again, sorry and thank you for you patience in this topic, this is kind of a recent project and I've put most the effort in performance improvements so that some of the structure is still not very mature

Edit2: Working on a draft here https://github.com/MDA2AV/Wired.IO/tree/exp_group
Check working example on DevPlayground project, still very crude code but first draft idea is there

@Kaliumhexacyanoferrat
Copy link
Copy Markdown
Owner Author

No worries, I know that there is so much work to do in a webserver 😄

Sub group routing is nice but not enough, as there are handlers auch as ReverseProxy or a dynamic handler being returned from a (parameterized) web service method. I cannot know these routes beforehand, so I need a way to catch all.

I thought more about this and will probably register a middleware last that will search for the given path prefix and handle requests that fall below this. If the handler does not return a response I can still call next, so that I do not consume something I cannot handle. For the user this will seem like just another Map call - I'll draft this later to get an idea.

@Kaliumhexacyanoferrat
Copy link
Copy Markdown
Owner Author

Kaliumhexacyanoferrat commented Oct 22, 2025

So this looks good to me. As IBaseRequest and IBaseResponse do not provide basic properties (such as headers), I cannot use generics for now and have switched to the express context. I started to map the request and added todos where WiredIO does not provide the information expected by GenHTTP - will create a list from this later on.

@MDA2AV
Copy link
Copy Markdown
Collaborator

MDA2AV commented Oct 22, 2025

So this looks good to me. As IBaseRequest and IBaseResponse do not provide basic properties (such as headers), I cannot use generics for now and have switched to the express context. I started to map the request and added todos where WiredIO does not provide the information expected by GenHTTP - will create a list from this later on.

Yea I see, IBaseRequest and IBaseResponse exist because while by default WiredIO is a http webserver it should allow custom http servers that may no required stuff like headers etc so these base types needed to contain as little as possible. Not sure but you can also asssume that the context used in GenHttp is always Http and use IExpressResponse and IExpressRequest instead.

I thought more about this and will probably register a middleware last that will search for the given path prefix and handle requests that fall below this. If the handler does not return a response I can still call next, so that I do not consume something I cannot handle. For the user this will seem like just another Map call - I'll draft this later to get an idea.

Yea i totally understand the grouping is probably not enough and it's just easier then to assume all root endpoints then, I'll add the group feature but root endpoints will still be possible. I'd say adding a middleware to look at the prefix route and search the endpoint to be handled sounds good enough.

If it helps you can look into
public Dictionary<string, HashSet> EncodedRoutes { get; set; } // All the existing routes matched to Http method
public List<Func<TContext, Func<TContext, Task>, Task>> Middleware { get; set; } = null!; // All the middlewares
public Dictionary<string, Func<TContext, Task>> Endpoints { get; set; } = null!; // All the endpoints matched to their key (httpmethod + route)

These are all available after running builder.Build(); They will be resolved from IServiceProvider and stored in those list/dicts. It will look different for grouping stuff but I'll keep these for root level only endpoints webserver, so it should remain always compatible even by adding features for grouping etc

Edit: If I understood correctly you need this middleware to run all the concerns/handlers associated with that endpoint "group". Kind of starting to click more because WiredoIO kinda realies on IServiceProvider for a lot of these things, like you can just add a service and resolve/call it in the endpoint definition itself but not in a group way, and that's how things like websockets and reverse proxies can be used, whereas in GenHttp this is all setup in the LayoutBuilder

@Kaliumhexacyanoferrat Kaliumhexacyanoferrat changed the title Add Wired.IO as an engine Add an adapter for Wired.IO Oct 22, 2025
@Kaliumhexacyanoferrat
Copy link
Copy Markdown
Owner Author

Kaliumhexacyanoferrat commented Oct 22, 2025

@MDA2AV I extracted this into an own repository as there are some restrictions (e.g. .NET 9 only) and the integration will probably be kind of beta/experimental at the time GenHTTP 10 is released. As this is not bound to the lifecycle of GenHTTP this allows faster development and release cycles.

https://github.com/Kaliumhexacyanoferrat/wired-genhttp-adapter/

The adapter now works in general, but surely requires some tweaking and fiddling. I will do/add some tests to get it more stable and create a list of issues as discussed. Nevertheless, milestone 1 achieved \o/

Will close this MR and probably create an issue/discussion over there.

image

Regarding the service provider - yes, I saw the extensions and implementations. I think for now the integration as a middleware is fine, see https://github.com/Kaliumhexacyanoferrat/wired-genhttp-adapter/blob/main/Adapter/Mapping/Bridge.cs

@Kaliumhexacyanoferrat Kaliumhexacyanoferrat deleted the feature/wiredio branch October 22, 2025 14:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants