Home > other >  Get remote client IP address in Deno
Get remote client IP address in Deno

Time:02-08

How to I get the IP address of the client in Deno?

I have created a test server using the standard http library but I can't figure out a way to extract client's IP. I need that as a security feature for preventing multiple submissions.

In NodeJS/Express there is an ip property of the request object that does the same. req.ip gives the thing I want in Express but what is it's equivalent in Deno?

My code is:

import { serve } from "https://deno.land/[email protected]/http/server.ts";

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

Is there any other work-around to prevent multiple access from the same device?

Thanks

CodePudding user response:

To do this in a type-safe way is a little complicated because of the way that serve is typed. First, I'll show you an example of how to do it, then I'll explain the types afterward.

Example

example.ts:

import {
  serve,
  type ConnInfo,
  type Handler,
  type ServeInit,
} from 'https://deno.land/[email protected]/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`);


Types

Looking at the documentation for the serve function, you can see that it accepts two parameters: a callback of type Handler, and some options of type ServeInit:

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

The Handler callback accepts two parameters: a Request, and an object of type ConnInfo:

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

ConnInfo looks like this:

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

The part that should have the remote IP address (technically, it's the remote hostname, but it's very likely to be an IP address unless you have configured custom DNS settings in your server environment) is the object at connInfo.remoteAddr, which (you can see above) is of type Deno.Addr, which looks like this:

// in the Deno namespace

type Addr = NetAddr | UnixAddr;

This is where it becomes complicated. Deno.Addr is a discriminated union of Deno.NetAddr and Deno.UnixAddr (which means that it could be either one), and the property transport is used to discriminate between the two.

// in the Deno namespace

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

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

A net address has hostname property (the value of which would be the IP address) and a port property, while a unix address has a path property.

The listener which is created internally to support the server is actually only listening on TCP, so I think it's safe to assume that the remote address will be a net address. However, because the type signature of the Handler callback parameter in the serve function doesn't make this explicit (although it should!), TypeScript doesn't know that.

So, it's left up to you as the programmer to make sure that the address is actually a net address before you can access properties that would be on a net address (instead of a unix address) in a type-safe way. That's where the type assertion function assertIsNetAddr comes into play. (A type assertion performs a runtime test which results in a "guarantee" of a condition to the compiler: by throwing an exception if the condition can't be guaranteed.) Because you as the programmer already know more than the TypeScript compiler (that the address is on TCP and will be a net address), you can assert that the address is indeed a net address. Then the compiler will allow you use the address as a net address.

If you want to do something besides throwing an Error in the case that the address is not a net address: instead of an assertion function, you can use a type predicate as a condition in your code.

Here is a link to the TypeScript Playground where I've created a playground with the types used in my example, so you can explore/experiment.


Finally, (this is not type-safe) if you just want to use the value without the checks (because you've done your research and are confident that you will never handle a non-TCP connection, you can simply use a type assertion:

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);
};
  •  Tags:  
  • Related