2024年5月25日

开发小记 通过Cloudflare Worker制作一个可以获取访问客户端IP地址的API

作者 TheWhiteDog9487

这几天搓了一个东西,想和各位分享一下。

观前提示:我熟悉的是后端开发,前端在学但是不熟。所以你接下来很可能看到让你血压升高的东西。如有发生,非常抱歉!


又是不知道怎么写开头的一天,难受。
那就直接开始吧。

index.ts

/**
 * Welcome to Cloudflare Workers! This is your first worker.
 *
 * - Run `npm run dev` in your terminal to start a development server
 * - Open a browser tab at http://localhost:8787/ to see your worker in action
 * - Run `npm run deploy` to publish your worker
 *
 * Bind resources to your worker in `wrangler.toml`. After adding bindings, a type definition for the
 * `Env` object can be regenerated with `npm run cf-typegen`.
 *
 * Learn more at https://developers.cloudflare.com/workers/
 */

import { forward } from "./forward.ts"

export default {
	async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
		return await forward(request);}};

forward.ts

import { return_ip } from './apis/return_ip'

async function forward(request: Request) {
    const RequestPath = new URL(request.url).pathname.replace(/\/$/, '');
    switch (RequestPath) {
        case '/ip':
            return await return_ip(request);
        default:
            const res = new Response('指定API不存在', { status: 404 })
            res.headers.set('Content-Type', 'text/plain; charset=utf-8');
            return res;}}

export { forward };

apis/return_ip.ts

async function return_ip(request: Request) {
	console.log( "CF-Connecting-IP: " + request.headers.get('CF-Connecting-IP') );
	console.log( "User-Agent: " + request.headers.get('User-Agent') );
	
	if ( request.headers.get('CF-Connecting-IP') == null ) {
		const res = new Response('未找到IP地址', { status: 404 })
		res.headers.set('Content-Type', 'text/plain; charset=utf-8');
		return res;}
    const res = new Response(request.headers.get('CF-Connecting-IP'), { status: 200 })
	res.headers.set('Content-Type', 'text/plain; charset=utf-8');
	return res;}

export { return_ip };

实现原理:

参考:Restoring original visitor IPs · Cloudflare Support docs

众所周知,访问经过Cloudflare CDN的网站,在流量到达CDN时,Cloudflare会给HTTP请求头加入一个叫做CF-Connecting-IP的头部,它的内容是客户端的网络出口的公网IP。
好了,这就是最重要的理论。如果你还不了解,下面是代码解释。

当访问这个api时,会进入index.ts里的fetch函数。
fetch函数会直接对请求进行转发,送给forward.ts里的forward函数进行处理,并且返回forward函数的返回值。
( 这是我自己的设计习惯,没什么特殊原因,我喜欢而已。
forward函数会首先对客户端访问的url进行处理,把/后面的路径拿出来(包括/),并且去除url最末尾的/(如果有)
/$是正则表达式,/前面的\是转义字符,字符串开始和结尾的两个/是表示这个字符串是正则表达式。
把处理过的path拿去匹配,如果是/ip那就继续下发给apis文件夹里的return_ip.ts里的return_ip函数。如果没匹配上,那直接对客户端抛异常 + HTTP 404 处理。
return_ip函数就特别直接了,直接看请求头里的CF-Connecting-IP,有没有这东西。没有的话继续抛异常+404,有的话返回CF-Connecting-IP里的内容并赠送HTTP 200.
完事

如果你想测试一下,这个是我的API的地址,直接浏览器打开就可以了。


更新

由于长城对Cloudflare的整个workers.dev域进行了封锁,国内的朋友们调用上面那个地址可能有一点啸难度。
你又不能走代理,对吧?走了代理那出口地址就变了,还测个啥?总不能让我用WebRTC吧?
所以,我在我自己的域名上单独划了一个给它,国内是能正常访问到的。

Microsoft Azure香港数据中心
我家里,四川成都 中国移动有线宽带

所以:

# 在国内的朋友可以用这个,不会被长城给拦下来。
# -4 是强制curl使用IPv4,测出来的结果就是IPv4的出口地址。
# -6 同理
curl https://api.worker.thewhitedog9487.xyz/ip -4
curl https://api.worker.thewhitedog9487.xyz/ip -6

# 在海外或需求部署在海外或有办法对抗长城封锁的朋友可以用下面这个。
# -4 和 -6 的参数同上
curl https://api.thewhitedog9487.workers.dev/ip -4
curl https://api.thewhitedog9487.workers.dev/ip -6