在Deno中获取远程客户端IP地址

4
如何在Deno中获取客户端的IP地址?
我使用标准的http库创建了一个测试服务器,但我无法找到提取客户端IP的方法。为了防止多次提交,我需要这个作为安全功能。
在NodeJS/Express中,请求对象有一个ip属性,可以做到同样的事情。在Express中,req.ip可以得到我想要的东西,但在Deno中它的等价物是什么?
我的代码是:
import { serve } from "https://deno.land/std@0.125.0/http/server.ts";

serve(
    (req) => {
        console.log(/* the client IP */);
        return new Response("hello");
    },
    { port: 8080 }
);

有没有其他方法来防止同一设备的多次访问?

谢谢

2个回答

11

要以类型安全的方式做到这一点有点复杂,因为serve的类型化方式。首先,我会向您展示如何实现,然后再解释类型。

示例

example.ts:

import {
  serve,
  type ConnInfo,
  type Handler,
  type ServeInit,
} from 'https://deno.land/std@0.125.0/http/server.ts';

function assertIsNetAddr (addr: Deno.Addr): asserts addr is Deno.NetAddr {
  if (!['tcp', 'udp'].includes(addr.transport)) {
    throw new Error('Not a network address');
  }
}

function getRemoteAddress (connInfo: ConnInfo): Deno.NetAddr {
  assertIsNetAddr(connInfo.remoteAddr);
  return connInfo.remoteAddr;
}

const handler: Handler = (request, connInfo) => {
  const {hostname, port} = getRemoteAddress(connInfo);
  const message = `You connected from the following address: ${hostname}`;
  return new Response(message);
};

const init: ServeInit = {port: 8080};

serve(handler, init);
console.log(`Listening on port ${init.port}...\nUse ctrl+c to stop`);


类型

查看serve函数的文档,你会发现它接受两个参数:一个Handler类型的回调函数以及一些ServeInit类型的选项:

async function serve(handler: Handler, options?: ServeInit): Promise<void>;

Handler回调接受两个参数:一个Request和一个类型为ConnInfo的对象:

type Handler = (request: Request, connInfo: ConnInfo) => Response | Promise<Response>;

ConnInfo看起来像这样:

interface ConnInfo {
  readonly localAddr: Deno.Addr;
  readonly remoteAddr: Deno.Addr;
}

应该显示远程IP地址的部分(技术上,它是远程主机名,但除非您在服务器环境中配置了自定义DNS设置,否则很可能是一个IP地址)是位于connInfo.remoteAddr对象中的,这个对象(如上所示)是Deno.Addr类型的,看起来像这样:
// in the Deno namespace

type Addr = NetAddr | UnixAddr;

这就是问题变得复杂的地方。Deno.Addr一个区分联合体,由 Deno.NetAddrDeno.UnixAddr 组成(也就是说它可以是其中任意一种),属性 transport 用于区分这两种情况。
// in the Deno namespace

interface NetAddr {
  hostname: string;
  port: number;
  transport: "tcp" | "udp";
}

interface UnixAddr {
  path: string;
  transport: "unix" | "unixpacket";
}

一个网络地址有一个“主机名”属性(该属性的值将是IP地址)和一个“端口”属性,而Unix地址有一个“路径”属性。 监听器是内部创建的,用于支持服务器实际上只侦听TCP,因此我认为可以安全地假定远程地址是一个网络地址。但是,由于serve函数中回调参数Handler的类型签名没有明确说明这一点(虽然它应该!),TypeScript并不知道。
因此,作为程序员,您需要确保地址实际上是网络地址,然后才能以类型安全的方式访问网络地址(而不是Unix地址)上的属性。这就是type assertion function assertIsNetAddr发挥作用的地方。(类型断言执行运行时测试,如果无法保证条件,则通过引发异常向编译器“保证”条件。)因为作为程序员,您已经比TypeScript编译器更了解(地址在TCP上且将是网络地址),所以可以断言该地址确实是网络地址。然后编译器将允许您将地址用作网络地址。

如果您想在地址不是网络地址的情况下做一些除抛出错误之外的事情:可以使用type predicate作为代码中的条件,而不是断言函数。

这里是一个链接,指向TypeScript Playground,在那里我创建了一个带有我的示例中使用的类型的游乐场,以便您可以进行探索/实验。


最后,(这不是类型安全的)如果你只想使用值而不进行检查(因为你已经做过研究并且有信心永远不会处理非TCP连接),你可以简单地使用类型断言

const handler: Handler = (request, connInfo) => {
  const {hostname, port} = connInfo.remoteAddr as Deno.NetAddr;
  const message = `You connected from the following address: ${hostname}`;
  return new Response(message);
};

非常感谢您提供如此详细的解释!现在我对Deno和TS有了更好的理解 :D再次感谢。 - Rishi
1
@Rishi,看到你读完答案后感觉很高兴!(顺便说一下——如果你想跟进的话——我在仓库中开了一个问题链接。) - jsejcksn

1
现在,Deno.serve首选的HTTP服务器API
它的处理程序作为第二个参数接受一个Deno.ServeHandlerInfo对象,该对象具有一个remoteAddr属性,该属性具有一个hostname属性。这是客户端的IP地址。
Deno.serve((req, info) => new Response(info.remoteAddr.hostname));

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