HttpListener:请求的地址在此上下文中无效

时间:2018-03-13 23:00:25

标签: c# http mono

使用

创建HttpListener对象时
var server = new HttpListener();
server.Prefixes.Add("http://*:8080/");
server.Start();
一切正常。但是,当我使用

var server = new HttpListener();
server.Prefixes.Add("http://demindiro.com:8080/");
server.Start();

它抛出System.Net.Sockets.SocketException (0x80004005): The requested address is not valid in this context(下面的完整堆栈跟踪)。

在谷歌搜索异常后,很明显它与使用的地址有关。在深入了解HttpListenerEndPointManager Mono 源代码之后,我确定问题可能在这段代码中(GetEPListener):

static EndPointListener GetEPListener (string host, int port, HttpListener listener, bool secure)
{
    IPAddress addr;
    if (host == "*")
        addr = IPAddress.Any;
    else if (IPAddress.TryParse(host, out addr) == false){
        try {
#pragma warning disable 618
            IPHostEntry iphost = Dns.GetHostByName(host);
#pragma warning restore 618
            if (iphost != null)
                addr = iphost.AddressList[0]; // <---
            else
                addr = IPAddress.Any;
        } catch {
            addr = IPAddress.Any;
        }
        // ...    
    }

我发现在几乎所有情况下都使用ÌPAddress.Any,除非它可以将外部IP地址与给定的主机名相关联。

但是,我只能假设这是故意的,因为它是相当明确的,并且似乎HttpListener对其他开发人员来说效果很好,所以在这一点上,我对于出了什么问题一无所知这里。

<小时/> 堆栈跟踪:

Unhandled Exception:
System.Net.Sockets.SocketException (0x80004005): The requested address is not valid in this context
  at System.Net.Sockets.Socket.Bind (System.Net.EndPoint localEP) [0x00043] in <50d80b08c1a5449282b22aedf03ce925>:0 
  at System.Net.EndPointListener..ctor (System.Net.HttpListener listener, System.Net.IPAddress addr, System.Int32 port, System.Boolean secure) [0x00047] in <50d80b08c1a5449282b22aedf03ce925>:0 
  at System.Net.EndPointManager.GetEPListener (System.String host, System.Int32 port, System.Net.HttpListener listener, System.Boolean secure) [0x0009d] in <50d80b08c1a5449282b22aedf03ce925>:0 
  at System.Net.EndPointManager.AddPrefixInternal (System.String p, System.Net.HttpListener listener) [0x0005e] in <50d80b08c1a5449282b22aedf03ce925>:0 
  at System.Net.EndPointManager.AddListener (System.Net.HttpListener listener) [0x0009c] in <50d80b08c1a5449282b22aedf03ce925>:0 
  at System.Net.HttpListener.Start () [0x0000f] in <50d80b08c1a5449282b22aedf03ce925>:0 
  at Playground.Playground.StartHttpListener () [0x00016] in <d51cc6c047ee47c9a05c5e174876cbec>:0 
  at Playground.Playground.Main (System.String[] args) [0x00000] in <d51cc6c047ee47c9a05c5e174876cbec>:0 
[ERROR] FATAL UNHANDLED EXCEPTION: System.Net.Sockets.SocketException (0x80004005): The requested address is not valid in this context
  at System.Net.Sockets.Socket.Bind (System.Net.EndPoint localEP) [0x00043] in <50d80b08c1a5449282b22aedf03ce925>:0 
  at System.Net.EndPointListener..ctor (System.Net.HttpListener listener, System.Net.IPAddress addr, System.Int32 port, System.Boolean secure) [0x00047] in <50d80b08c1a5449282b22aedf03ce925>:0 
  at System.Net.EndPointManager.GetEPListener (System.String host, System.Int32 port, System.Net.HttpListener listener, System.Boolean secure) [0x0009d] in <50d80b08c1a5449282b22aedf03ce925>:0 
  at System.Net.EndPointManager.AddPrefixInternal (System.String p, System.Net.HttpListener listener) [0x0005e] in <50d80b08c1a5449282b22aedf03ce925>:0 
  at System.Net.EndPointManager.AddListener (System.Net.HttpListener listener) [0x0009c] in <50d80b08c1a5449282b22aedf03ce925>:0 
  at System.Net.HttpListener.Start () [0x0000f] in <50d80b08c1a5449282b22aedf03ce925>:0 
  at Playground.Playground.StartHttpListener () [0x00016] in <d51cc6c047ee47c9a05c5e174876cbec>:0 
  at Playground.Playground.Main (System.String[] args) [0x00000] in <d51cc6c047ee47c9a05c5e174876cbec>:0

PS:我明确要使用demindiro.com,而不是*

PS2:我知道有很多关于这个特殊异常的主题,但似乎没有人关注HttpListener.Start()抛出这个异常。

更新 我再次搜索了我的问题,但这次我专门查看了TcpListener,我找到了这个答案:https://stackoverflow.com/a/17092670/7327379

  

TcpListener只能绑定到运行它的计算机的本地IP地址。因此,您指定的IP不是本地计算机的IP。您的公共IP与本地计算机的IP不同,特别是如果您正在使用某种NAT。

     

如果我没记错的话,只需将IPAddress.Any作为你的IP来初始化听众即可。

所以,如果我直截了当,这意味着要绑定到我的外部IP,我不得不从这个

Server <-- local IP --> Modem <-- external IP --> The Internet

到这个

Server <-- external IP --> The Internet

这是对的吗?如果是,如何在没有调制解调器的情况下将服务器连接到互联网?或者我还应该做些什么?

2 个答案:

答案 0 :(得分:3)

考虑到http://demindiro.com似乎有效,这可能是多余的。然而...

这里有一些你需要注意的事情:

通过主机名绑定

通常,主机名绑定是A Bad Idea™。如果主机名解析为多个地址 - 例如,当您启用了IPv6时{1}},或者在同一台机器上有多个IPv4地址等时 - 您无法控制它绑定到哪个地址。在localhost的情况下,您可能(有时令人不愉快)惊讶地发现它与localhost绑定但不与::1绑定。

然而,问题是127.0.0.1是一个名称,它不能解析为计算机上存在的内部IP地址,因此您无法直接绑定它。确切地说,demindiro.com是一个主机名,它通过DNS解析为IPv4地址demindiro.com,这几乎可以肯定不是您的计算机所具有的IP地址。

绑定到主机名不会绑定到主机名

而是绑定到主机名在计算机上解析的第一个IP地址。不多也不少。

假设您修改主机文件以使109.132.169.132解析(仅限您的计算机上)有效的本地IPv4地址,例如demindiro.com。如果我在您的网络上并且我连接到该地址,我会得到您的服务。无论我想要访问哪个主机名,只要它解析为该地址我将获得连接。我可以通过IP或任何解析到该IP的主机名直接连接,你永远不会知道。

只有在建立连接并且解析了请求后,您才能(有时)检测哪个主机名是请求的目标。

在HTTP(S)请求的情况下,主机名通常作为请求的一部分发送。这允许Web服务器在同一地址上托管多个站点。 Web服务器接收请求,确定应在本地站点之间路由的位置,然后将请求发送到已解析的站点。在HTTPS中做起来有点困难,但问题主要由SNI解决。

但是,如果我使用没有主机名的绝对路径向您的服务发出HTTP 1.0请求,那么呢?

绑定只能是本地的

换句话说,您无法绑定到运行代码的计算机上不存在的地址。更重要的是,除非直接在您的计算机上终止,否则您无法直接绑定到Internet连接的公共地址。由于我们中没有多少人在我们的桌面上运行拨号或桥接PPPoE,因此您尝试绑定的地址可能无法使用。

一般来说,您需要做的是在您的互联网连接上设置某种NAT重定向,以将端口8080上的传入请求转换为公共地址192.168.0.123,并将它们重定向到您的内部地址({{1}或者其他什么)。

假设109.132.169.132解析为您的公共IP地址。如果没有,那么你就会遇到各种各样的问题。跨公共网络的NAT是......很奇怪。您在链中添加的每个NAT都会增加更多的复杂性,更多的失败点等等。

答案 1 :(得分:2)

您可以将您的网络可视化如下

local machine -> local interface 
              -> interface 1 (Private IP) <-> External IP
              -> interface 2 (Private IP2) <-> External IP

现在,在大多数情况下,云中的VM将具有1个网络接口和1个本地接口。本地接口是环回接口,interface 1将具有Private IP

现在,当您在代码中使用以下内容时

server.Prefixes.Add("http://*:8080/");

表示绑定到计算机上的所有接口。这意味着如果你这样做

curl http://localhost:8080

curl http://<privateIP>:8080

两者都可以。

现在,如果您将example.com映射到下面的externalIP运行命令,它也可以正常运行

curl http://example.com:8080

这是因为您的example.com映射到<extrenalIP>,这会将所有流量转发到<privateIP>。现在当你在下面使用时。代码

server.Prefixes.Add("http://example.com:8080/");

转换为

server.Prefixes.Add("http://<externalIP>:8080/");

这不是上下文中的有效IP,只有机器的127.0.0.1<PrivateIP>有效。因此,如果您有多个接口,那么只有您应该担心不使用*。现在,如果您有多个界面和一个特定的界面来收听您的域名,那么您将在/etc/hosts文件中输入相同内容,如下所示

<PrivateIP> example.com

然后你可以使用

server.Prefixes.Add("http://example.com:8080/");

现在,如果您担心有人可以使用不同的主机名指向您的计算机,并且您不希望服务器在这种情况下做出响应。然后,您必须检查代码中的Host标头,并根据相同的要求拒绝该请求。或者,您可以使用nginx之类的网络服务器,并仅回复针对example.com

的请求