unosquare / embedio

A tiny, cross-platform, module based web server for .NET
http://unosquare.github.io/embedio
Other
1.46k stars 176 forks source link

EmbedIO's CheckUri rejects ephemeral port 0 as an invalid port. #506

Closed steve-bate closed 3 years ago

steve-bate commented 3 years ago

Describe the bug EmbedIO's CheckUri rejects ephemeral port 0 as an invalid port.

To Reproduce Steps to reproduce the behavior:

  1. Create a WebServer with "http://127.0.0.1:0"

Expected behavior Creates a WebServer that listens to an ephemeral (assigned by the TCP stack) port.

Additional context We have a system with a large number of servers, sometimes multiple per host, with a local web API control channel. We don't want to assign static ports to these servers. We'll register the dynamically assigned ephemeral ports with some form of service locator.

https://www.lifewire.com/port-0-in-tcp-and-udp-818145

The <= 0 should be < 0 in the following code (EmbedIO.Net.Internal.ListenerPrefix.CheckUri):

                if (!int.TryParse(uri.Substring(colon + 1, root - colon - 1), out var p) || p <= 0 || p >= 65536)
                    throw new ArgumentException("Invalid port.");
rdeago commented 3 years ago

Unfortunately it's not just a matter of <= 0 versus < 0.

ListenerPrefix comes from Mono and the latest Mono still has the same check, leading to think that maybe it's that way for a reason.

.NET 5 has the same check too, at least in the managed HttpListener implementation used on non-Windows systems.

By fixing this issue we'd introduce different behavior between HttpListenerMode.Microsoft and HttpListenerMode.EmbedIO, something that we've always sought to avoid. I'll leave the last word to @geoperez, but I'm inclined towards closing this as "won't fix".

steve-bate commented 3 years ago

I understand and thanks for investigating the topic. I suppose part of the issue is that many HTTP frameworks conflate the requested receive port with a URI "prefix". In the case of an ephemeral port, the requested URI port will be different than the receive port since the system will assign the receive port. I've attempted to work around the issue using IPGlobalProperties.GetActiveTcpConnections to find an open port instead of requesting an ephemeral port assignment, although this approach is vulnerable to race conditions if multiple processes are starting at the same time on the same host. Many developers probably think no one would ever want to start an HTTP listener on an unspecified port. However, combined with service locators, this can be a very useful technique.

rdeago commented 3 years ago

I suppose part of the issue is that many HTTP frameworks conflate the requested receive port with a URI "prefix".

Removing URI prefixes in favor of just specifying port numbers is on our backlog for version 4.0; unfortunately, this won't help unless we also get rid of HttpListener completely, which is easier said than done. This matter has already been laid out in #464.

One possible solution is cloning .NET 5's HttpListener, just as @mariodivece did with Mono for EmbedIO v1, and remove HttpListenerMode, leaving our internal HttpListener as the only option. Then we'd be free to change its code as we see fit, and you can be sure that URI prefixes would go down the kitchen sink on day one by 8A.M. To tell the truth, unless a better option comes up in the near future, I'm strongly tempted to do just that, as I really can't stand all the problems we're having with HttpListener any longer. The problem is we'd lose HTTPS support completely, unless we reimplement it in a cross-framework fashion, which I currently have little to no idea how to do.

stale[bot] commented 3 years ago

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.