获取远程主机的IP地址

147
在ASP.NET中,有一个名为System.Web.HttpRequest的类,其中包含ServerVariables属性,可以提供来自REMOTE_ADDR属性值的IP地址。
但是,在ASP.NET Web API中,我找不到类似的方法来获取远程主机的IP地址。
如何获取发出请求的远程主机的IP地址?

我发现最简单的方法在这里:https://dev59.com/Qa_la4cB1Zd3GeqPwq4D - deathrace
7个回答

206

可以做到这一点,但不是很易于发现 - 您需要使用传入请求的属性包,并且您需要访问的属性取决于您是在IIS(Web托管)下使用Web API还是自托管。下面的代码显示了如何完成此操作。

private string GetClientIp(HttpRequestMessage request)
{
    if (request.Properties.ContainsKey("MS_HttpContext"))
    {
        return ((HttpContextWrapper)request.Properties["MS_HttpContext"]).Request.UserHostAddress;
    }

    if (request.Properties.ContainsKey(RemoteEndpointMessageProperty.Name))
    {
        RemoteEndpointMessageProperty prop;
        prop = (RemoteEndpointMessageProperty)request.Properties[RemoteEndpointMessageProperty.Name];
        return prop.Address;
    }

    return null;
}

4
谢谢,我也在寻找这个东西。轻微改进 = 扩展类:https://gist.github.com/2653453 - MikeJansen
32
WebAPI 大多数情况下非常简洁。可惜像这样的代码却需要处理一些琐碎的事情,比如 IP 地址。 - Toad
2
RemoteEndpointMessageProperty是位于System.ServiceModel.Channels命名空间下的类,它属于System.ServiceModel.dll程序集。这不就是WCF的一个程序集吗? - Slauma
4
@Slauma,是的,它们是。ASP.NET Web API目前有两种实现方式:自托管和Web托管。Web托管版本是在ASP.NET之上实现的,而自托管版本是在WCF侦听器之上实现的。请注意,该平台(ASP.NET Web API)本身与托管方式无关,因此未来可能会有人实现不同的托管方式,并以不同的方式暴露主机属性(远程终结点)。 - carlosfigueira
3
很遗憾,如果你使用 Owin 自主托管(这是 Web API 2 推荐的方式),这种方法是不起作用的。如果需要的话,需要另外一种方法。 - Nikolai Samteladze
显示剩余8条评论

80

这个解决方案也适用于使用Owin进行自托管的Web API。部分参考自这里

您可以在您的ApiController中创建一个私有方法,该方法将返回远程IP地址,无论您如何托管您的Web API:

 private const string HttpContext = "MS_HttpContext";
 private const string RemoteEndpointMessage =
     "System.ServiceModel.Channels.RemoteEndpointMessageProperty";
 private const string OwinContext = "MS_OwinContext";

 private string GetClientIp(HttpRequestMessage request)
 {
       // Web-hosting
       if (request.Properties.ContainsKey(HttpContext ))
       {
            HttpContextWrapper ctx = 
                (HttpContextWrapper)request.Properties[HttpContext];
            if (ctx != null)
            {
                return ctx.Request.UserHostAddress;
            }
       }

       // Self-hosting
       if (request.Properties.ContainsKey(RemoteEndpointMessage))
       {
            RemoteEndpointMessageProperty remoteEndpoint =
                (RemoteEndpointMessageProperty)request.Properties[RemoteEndpointMessage];
            if (remoteEndpoint != null)
            {
                return remoteEndpoint.Address;
            }
        }

       // Self-hosting using Owin
       if (request.Properties.ContainsKey(OwinContext))
       {
           OwinContext owinContext = (OwinContext)request.Properties[OwinContext];
           if (owinContext != null)
           {
               return owinContext.Request.RemoteIpAddress;
           }
       }

        return null;
 }

需要引用以下内容:

  • HttpContextWrapper - System.Web.dll
  • RemoteEndpointMessageProperty - System.ServiceModel.dll
  • OwinContext - Microsoft.Owin.dll(如果使用Owin包,则已经有了它)

这种解决方案的一个小问题是,您必须加载所有三种情况的库,而在运行时实际上只会使用其中一种。如此处建议,可以通过使用dynamic变量来克服这个问题。还可以将GetClientIpAddress方法编写为HttpRequestMethod的扩展。

using System.Net.Http;

public static class HttpRequestMessageExtensions
{
    private const string HttpContext = "MS_HttpContext";
    private const string RemoteEndpointMessage =
        "System.ServiceModel.Channels.RemoteEndpointMessageProperty";
    private const string OwinContext = "MS_OwinContext";

    public static string GetClientIpAddress(this HttpRequestMessage request)
    {
       // Web-hosting. Needs reference to System.Web.dll
       if (request.Properties.ContainsKey(HttpContext))
       {
           dynamic ctx = request.Properties[HttpContext];
           if (ctx != null)
           {
               return ctx.Request.UserHostAddress;
           }
       }

       // Self-hosting. Needs reference to System.ServiceModel.dll. 
       if (request.Properties.ContainsKey(RemoteEndpointMessage))
       {
            dynamic remoteEndpoint = request.Properties[RemoteEndpointMessage];
            if (remoteEndpoint != null)
            {
                return remoteEndpoint.Address;
            }
        }

       // Self-hosting using Owin. Needs reference to Microsoft.Owin.dll. 
       if (request.Properties.ContainsKey(OwinContext))
       {
           dynamic owinContext = request.Properties[OwinContext];
           if (owinContext != null)
           {
               return owinContext.Request.RemoteIpAddress;
           }
       }

        return null;
    }
}

现在您可以像这样使用它:

public class TestController : ApiController
{
    [HttpPost]
    [ActionName("TestRemoteIp")]
    public string TestRemoteIp()
    {
        return Request.GetClientIpAddress();
    }
}

1
这个解决方案应该使用命名空间"System.Net.Http"来工作。因为这是一个在程序集System.Web.Http.dll,v5.2.2.0中的类名。 - Wagner Bertolini Junior
1
我急于完成一项工作,花了我超过20分钟的时间来查看构建错误。我只是复制了代码并为其创建了一个类,在编译时它没有显示方法,当我在VS上使用“转到定义”时,它带我去了我的类,我一直不明白发生了什么,直到我找到了另一个类。扩展是一个相当新的功能,不是所有时候都会使用,因此,我认为节省这些时间是个好主意。 - Wagner Bertolini Junior
1
在使用OWIN时,您可以使用OwinHttpRequestMessageExtensions来获取OWIN上下文,例如:request.GetOwinContext().Request.RemoteIpAddress。 - Stef Heyenrath
1
实际上应该是 var ctx = request.Properties[MsHttpContext] as HttpContextWrapper; 如果你进行了强制类型转换,就不需要检查 null,因为如果转换失败,你会得到一个异常。 - Stef Heyenrath
2
注意,此功能无法在Azure Functions中运行。 - user160357
显示剩余3条评论

34

如果你真的想要一个单行代码,并且不计划自主托管Web API:

((System.Web.HttpContextWrapper)Request.Properties["MS_HttpContext"]).Request.UserHostAddress;

13

以上答案需要引用 System.Web 才能将属性强制转换为 HttpContext 或 HttpContextWrapper。如果您不想使用此引用,您可以使用动态方式获取 IP:

var host = ((dynamic)request.Properties["MS_HttpContext"]).Request.UserHostAddress;

1
当服务器位于代理或负载均衡器后面时,标记的解决方案将返回代理的内部IP地址。在我们的情况下,我们的生产环境使用负载均衡器,而我们的开发和测试环境则没有,因此我已经修改了标记的解决方案,使其适用于同一代码中的两种情况。
public string GetSourceIp(HttpRequestMessage httpRequestMessage)
    {
        string result = string.Empty;

        // Detect X-Forwarded-For header
        if (httpRequestMessage.Headers.TryGetValues("X-Forwarded-For", out IEnumerable<string> headerValues))
        {
            result = headerValues.FirstOrDefault();
        }
        // Web-hosting
        else if (httpRequestMessage.Properties.ContainsKey("MS_HttpContext"))
        {
            result = ((HttpContextWrapper)httpRequestMessage.Properties["MS_HttpContext"]).Request.UserHostAddress;
        }
        // Self-hosting
        else if (httpRequestMessage.Properties.ContainsKey(RemoteEndpointMessageProperty.Name))
        {
            RemoteEndpointMessageProperty prop;
            prop = (RemoteEndpointMessageProperty)httpRequestMessage.Properties[RemoteEndpointMessageProperty.Name];
            result = prop.Address;
        }

        return result;
    }

正是我所需要的。大多数值得推广的应用程序都在代理后面,而这就是处理它的方法。 - I Stand With Russia
这太棒了。在Azure无服务器函数中也可以工作。 - Speedcat

0

基于Asaff Belfer的答案,这里提供了我用来部署到Azure Function(无服务器/消耗计划)以获取客户端IP和客户端用户代理的几种基本方法。还包括一些在APIM中应该可以工作的内容,但尚未测试该部分。有关APIM的信息可以在Stefano Demiliani的博客中找到。

注意:这些将返回“(不可用)”以进行本地/自托管。有人可以修复这些以包括自托管,但我无法使用这些位,因为Asaff的答案(以及其他人在这里)所需的Nuget包似乎不适用于我正在使用的目标框架(.NET 6.0)。

public static string GetSourceIp(HttpRequestMessage httpRequestMessage)
{
    string result = "(not available)";
    // Detect X-Forwarded-For header
    if (httpRequestMessage.Headers.TryGetValues("X-Forwarded-For", out IEnumerable<string> headerValues))
    {
        result = headerValues.FirstOrDefault();
    }
    //for use with APIM, see https://demiliani.com/2022/07/11/azure-functions-getting-the-client-ip-address
    if (httpRequestMessage.Headers.TryGetValues("X-Forwarded-Client-Ip", out IEnumerable<string> headerValues2))
    {
        result = headerValues2.FirstOrDefault();
    }
    return result;
}

public static string GetClientUserAgent(HttpRequestMessage httpRequestMessage)
{
    string result = "(not available)";
    // Detect user-agent header
    if (httpRequestMessage.Headers.TryGetValues("user-agent", out IEnumerable<string> headerValues))
    {
        result = string.Join(", ", headerValues);
    }
    return result;
}

使用方法如下:

string clientUserAgent = GetClientUserAgent(httpRequestMessage);
string clientIP = GetSourceIp(httpRequestMessage);

这些是在我的本地开发环境中使用Chrome浏览器返回的:

用户IP地址:(不可用)

用户客户端:Mozilla/5.0,(Windows NT 10.0; Win64; x64),AppleWebKit/537.36,(KHTML, like Gecko),Chrome/108.0.0.0,Safari/537.36

这些是在我在Azure商业版中的无服务器函数中使用Chrome浏览器返回的:

用户IP地址:999.999.999.999:12345 (当然,这不是真实的IP地址。我假设12345部分是端口号。)

用户客户端:Mozilla/5.0,(Windows NT 10.0; Win64; x64),AppleWebKit/537.36,(KHTML, like Gecko),Chrome/108.0.0.0,Safari/537.36


-3
carlosfigueira 提供的解决方案可行,但类型安全的一行代码更好:在您的操作方法中添加 using System.Web,然后访问 HttpContext.Current.Request.UserHostAddress

27
在 Web API 中,不能信任 HttpContext.Current,因为它在整个任务管道中没有正确地保留;由于所有请求处理都是异步的。编写 Web API 代码时,应该尽量避免使用 HttpContext.Current - Andras Zoltan
@Andras,我想更详细地了解为什么使用HttpContext.Current不好,你知道任何有价值的资源吗? - cuongle
13
嗨 @CuongLe,实际上,在处理线程时会有问题(如果在任务之间正确地传递了SynchronizationContext,则不一定直接存在问题);但是,最大的问题是,如果您的服务代码可能会自托管(例如进行测试),那么HttpContext.Current是纯粹的 Asp.Net 结构,在自托管时不存在。 - Andras Zoltan
类型安全并不是万能的。如果在线程中使用(例如,任务等待器,在现代Web API代码中非常常见)或在自托管上下文中使用,此代码将抛出难以调试的“NullReferenceException”。至少大多数其他答案只会返回“null”。 - Aaronaught

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接