unosquare / embedio

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

Binding on all interfaces with wildcard * or + not working #459

Closed SaricVr closed 4 years ago

SaricVr commented 4 years ago

Hello,

I'm facing a strange problem... if I use this code to create a web server

 public static void Start()
 {
            var url = "http://*:9696"; // the same arises if I use "+" instead of "*"

            using (var server = CreateWebServer(url))
                server.RunAsync();
 }

private static WebServer CreateWebServer(string url)
{
            var server = new WebServer(o => o
                .WithUrlPrefix(url)
                .WithMode(HttpListenerMode.EmbedIO))
                .WithLocalSessionManager().WithWebApi("/", m => m
                .WithController<Controller>());

            return server;
}

I am not able to access the server using the IP addresses of my machine, i.e. "localhost", "127.0.0.1", "192.168.0.25". However, if I explicitly set multiple binding urls (using "WithUrlPrefixes" with an array of strings) such as "http://localhost:9696", "http://127.0.0.1:9696", "http://192.168.0.25:9696", then I can connect to every one of them.

Am I doing something wrong? Thank you.

rdeago commented 4 years ago

I don't think you're doing anything wrong. As a matter of fact, our sample program does the same.

Our EndPointManager class, on the other hand, could be misbehaving. When configured to use IPv6 (the default), it translates "*" (or "+", or even "0.0.0.0") to IPAddress.IPv6Any, which is fine... as long as you actually have an IPv6 stack on your machine.

If a client only uses IPv4, there's no problem: EmbedIO will listen on both IP stacks. But if the server only uses IPv4, you'd better set EndPointManager.UseIpv6 = false; before starting your WebServer:

 public static void Start()
 {
            var url = "http://*:9696"; // the same arises if I use "+" instead of "*"

            EndPointManager.UseIpv6 = false; // This should do the trick
            using (var server = CreateWebServer(url))
                server.RunAsync();
 }

private static WebServer CreateWebServer(string url)
{
            var server = new WebServer(o => o
                .WithUrlPrefix(url)
                .WithMode(HttpListenerMode.EmbedIO))
                .WithLocalSessionManager().WithWebApi("/", m => m
                .WithController<Controller>());

            return server;
}

@SaricVr can you confirm that your machine has no IPv6 stack, and that the problem goes away if you set EndPointManager.UseIpv6 to false? It's important that we understand what's going on exactly.

EmbedIO v4 will automatically detect the presence of an IPv6 stack and act accordingly; to tell the truth, it won't even have an EndPointManager any more, but that's another story. However, if there is some other problem I'd like to nail it down and fix it in both v3 and v4.

SaricVr commented 4 years ago

Hi @rdeago, thanks for the answer. My machine has definetly IPv6 capabilities. I run a few tests and noticed the following: if I start the webserver with an explicit url such as "http://localhost:9696", then I see the following listener on port 9696 (both with UseIpv6 = true and UseIpv6 = false).

LocalAddress                        LocalPort RemoteAddress                       RemotePort State       AppliedSetting
------------                        --------- -------------                       ---------- -----       --------------
::1                                 9696      ::                                  0          Listen

It appears to be binding always on the IPv6 interface regardless of the UseIpv6 property value. However, if I insert the wildcard *, then no listener is reported listening on port 9696, indepentently on how I set the UseIpv6 property.

Hope this helps.

SaricVr commented 4 years ago

Hi, I've been able to solve the problem. I noticed that the webserver gets disposed when binding with a wildcard (* or +).

Exception thrown: 'System.ObjectDisposedException' in System.dll

By removing the using block and declaring server as a global variable (preventing it from being garbage collected), everything works correctly.

Now, the debugging of the problem has been really hard because I noticed that logging does not work (that's why I didn't notice the exception initially). Every time I try to use (or that EmbedIO tries to use) the Swan.Logger class this happens: Exception thrown: 'System.IO.IOException' in mscorlib.dll. I had to implement my own ILogger in order to see the log. I guess the problem is related to the Terminal classs of Swan, but this is material for another bug report :)

rdeago commented 4 years ago

Have you tried this?

    // Remove all loggers.
    Logger.NoLogging();

    // Add a file logger
    Logger.RegisterLogger(new FileLogger("path_to_log_file"));

As soon as the first EmbedIO 4.0 prerelease is out, we'll have to add SourceLink and symbol packages to Swan too. Not to mention fix logging.

rdeago commented 4 years ago

@SaricVr if you're willing to spend some minutes on it, here's (more or less) how to "debug all the things", based upon my other comment:

(Please @SaricVr don't think I'm mocking you or anything - on the contrary, you're doing a great service to the team! The "all the things" meme is mostly an inside joke, more aimed at @geoperez than at you.)

geoperez commented 4 years ago

Lol

I've been busy, after breakfast I will take a look what is going on.

SaricVr commented 4 years ago

Ahahah @rdeago no offense taken at all!

Anyway... I think I've been able to track down also this bug (branch 3.X). Essentially, when binding with a wildcard "*" or "+", the disposing of the web server works properly, hence the using keyword makes the web server to close after the start (if used just as I did). However, when a specific ip (or "localhost") is issued as url, the disposing of webserver does not work as intended (I think). This happens because EndpointListener.RemovePrefix never finds the key inside the _prefixes collection (line 173-17).

The reason is that _prefixes is a Dictionary<ListenerPrefix, HttpListener>, but the class ListenerPrefix does not implement a custom comparer, only the ToString() method. The keys inside _prefixes are then compared to using the default comparer of objects, consequently never finding the same prefix since ListenerPrefix is always a new instance created every time from the url string.

I quickly tested a fix obtaining a consistent behavior by implementing an IEqualityComparer within ListenerPrefix.

The reason why the removing works with wildcards is because a different method is used: RemoveSpecial that explicitly compares the content of the prefix.

Let me know if this makes sense to you.

rdeago commented 4 years ago

It totally makes sense! As a matter of fact, @desistud may have beaten you by a few hours with their PR #463. Please take a look at it and see if it does what you need.

stale[bot] commented 4 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.