利用 EdgeOne 主要功能之一 边缘函数编写简易IP地址获取工具

1.关于 EdgeOne

最近腾讯云搞了个 EdgeOne ,搞得沸沸扬扬的,而边缘函数作为现代CDN普遍功能,在提升简单小功能的响应速度上具有非常大的帮助. 我没兑换码别找我.

2.定价

EdgeOne 免费试用版提供了 300w request/month,300w COREms/month 的免费配额,基本上和 Cloudflare 差不多.

EdgeOne Clodflare
edgeone cloudflare
300w R/month 300w COREms/month 10w R/day
超出不知道怎么计费,还没超过,不过现在没绑卡,超出也不可能扣钱就是了 超出发账单

3.功能

这里我们主要讲 EdgeOne 的边缘函数功能, 其他的功能估计经常使用的人已经了如指掌了.

3.1.创建一个边缘函数

  • 点击 新建函数 跳转到以下界面: CreateNew
  • 点击下一步跳转到以下步骤: CreateFunction 函数名称注释只要合法,随便写,函数代码可以先按照他的不改,我们后期再做修改. 如果添加成功,那么会显示如下界面: success 我们点击新增触发规则/或直接点击暂不需要.

3.2.创建触发规则

如果在上一步选择新增触发规则,那么就会来到以下界面: createNewRule

  • 匹配类型: 选择让指定的请求通过我们创建的函数
    例如我们设置为 HOST 等于 123.com, 那么所有通过腾讯云EdgeOne的请求只要符合请求头/sni中 HOST123.com 的请求都会通过我们设定的边缘函数

3.3.设置环境变量

回到函数管理->点击函数名进入函数管理->向下拉可以看到环境变量块. functionManager 可以添加以文本或者JSON添加环境变量.作为示例,我这里添加一个 default_output_key 值为 clientIp 的变量用于演示. 在添加完毕后,不要忘了点击右上角的 部署 进行变量的部署

3.4.编写边缘JS代码

如果没有问题,那么你在他部署完毕后应该可以使用他提供的 默认访问域名 访问到你的 Hello World 边缘函数了.EdgeOne 提供了一些示例代码可以参考,同时也提供了RuntimeAPI用于参考. 在这里我也创建一个自己的边缘函数示例代码,用于获取请求者IP信息,不过在介绍我的方法之前,先介绍以下主要的几个方法和类型:

3.4.1.addEventListener :fetch

注册事件监听器,是边缘函数的运行入口。addEventListener 仅支持注册一个事件监听器。当前仅支持 fetch 请求事件,通过注册 fetch 事件监听器,生成 HTTP 请求事件 FetchEvent ,进而实现对 HTTP 请求的处理。通俗来说就是你请求的入口,通过监听这个事件来做到回调,这部分和 cloudflare 的边缘函数有些许不同,但大体上是差不多的.

3.4.2.Request

根据 官方文档Request 的描述, Request 代表 HTTP 请求对象,基于 Web APIs 标准 Request 进行设计. 我们要知道其中有 eo 这个属性(IncomingRequestEoProperties)对我我们编写一个IP查询函数十分重要.

3.4.3.Response

根据 官方文档 , 我们可以知道 Response 代表 HTTP 响应,基于 Web APIs 标准 Response 进行设计。这个知不知道无所谓,反正就是用来做返回 body header 等功能.

3.4.3.编写主方法

当我们知道了如何调用和request的使用方法后,我们就能写出如下代码:
函数执行流程:

  • 从环境变量中读取预先设置的 default_output_key 的值;
  • 将所有能使用的ip信息存到 full_info 对象中;
  • 获取URL信息,包括URL参数用于设定返回的body类型,是否返回美化后的Json,是否返回全部可用值,和指定返回的值;
  • 将需要返回的值放入 resp 对象中;
  • 根据 ret_type 决定返回类型,可选返回Json或者直接返回文本;
  • 将数据编为JSON格式或者以 key=value\n 格式返回;
async function handleRequest(request) {
  let resp = {};
  let full_info = {};
  let response_body = "";
  let select_all = false;
  let stringify_settings = {
    visual_view : false
  }
  let headers = {
    "Access-Control-Allow-Origin": "*"
  };
  const keys_available = [
    "clientIp", "asn", "countryName", "countryCodeAlpha2",
    "countryCodeAlpha3", "countryCodeNumeric", "regionName",
    "regionCode", "cityName", "latitude", "longitude"
  ];
  const env_default_key = env.default_output_key;
  const key_alias = {
    ip: "clientIp",
    country: "countryName",
    lat: "latitude",
    lng: "longitude",
    city: "cityName",
    region: "regionName"
  };
  let selectedFields = [env_default_key] || [
    "clientIp",
  ];
  let ret_type = "text";
  // 拷贝 ip 信息
  full_info = {
    ...(request.eo?.clientIp && { clientIp: request.eo.clientIp }),
    ...(request.eo?.geo || {})
  };;

  // 获取URL信息
  const urlInfo = new URL(request.url);
  const params = urlInfo.searchParams;
  if (params.keys().length > keys_available.length + 4) {
    return new Response("Too many params.\n", {status:403});
  }
  // 获取url参数
  for (let [key,value] of params.entries()) {
    switch (key) {
      case "type":
        ret_type = value;
        break;
      case "v":
        stringify_settings.visual_view = true;
        break;
      case "j":
        ret_type = "json";
        break;
      case "all":
        select_all = true;
        break;
      default:
        let rKey = key_alias[key] || key;
        if ((keys_available.includes(rKey)) && !selectedFields.includes(rKey)) {
          selectedFields.push(rKey);
        }
        break;
    }
    
  }

  // 注入resp
  if (select_all) {
    keys_available.forEach(key => {
    if (full_info[key] !== undefined) {
        resp[key] = full_info[key];
      }
    });
  } else {
    selectedFields.forEach(key => {
      if (full_info[key] !== undefined) {
        resp[key] = full_info[key];
      }
    });
  }
 

  switch (ret_type) {
    case "json":
      response_body = stringify_settings.visual_view 
      ?JSON.stringify(resp,null,2) + "\n" 
      :JSON.stringify(resp) + "\n";
      
      headers["content-type"] = "application/json; charset=UTF-8";
      break;
    default:
    if (Object.keys(resp).length == 1) {
      response_body = resp[Object.keys(resp)[0]] + "\n";
    } else {
        response_body = Object.entries(resp)
        .map(([k, v]) => `${k}=${v}`)
        .join("\n") + "\n";
    }
      headers["content-type"] = "text/plain; charset=UTF-8";
  }
  return new Response(response_body,{ headers });
}

addEventListener('fetch', event => {
  event.passThroughOnException();
  event.respondWith(handleRequest(event.request));
});

4.Tips

  • 3.4.3的主方法已经可用直接被用于边缘函数.即你可以直接将这个代码块全部写入保存并部署;如果出现访问之后返回空,请检查环境变量是否已成功设置 default_output_key 变量
  • 该方法只是一个简单的示例, 理论上你可以用JS完成任何网页的构建,甚至可以只用边缘函数编写一个完整的Blog

5.Demo

访问地址: http://myip.icu
可选参数:

  • j: 以JSON格式获取输出
  • v: 以利于阅读的格式输出
  • all: 获取所有信息
  • type: 指定输出格式,可选: json,text
  • clientIp or ip: 获取IP
  • asn: 获取ASN号
  • countryName or country: 获取国家名
  • countryCodeAlpha2: 获取国家的 ISO-3611 alpha2 代码
  • countryCodeAlpha3: 获取国家的 ISO-3611 alpha3 代码
  • countryCodeNumeric: 获取国家的 ISO-3611 numeric 代码
  • regionName or region: 获取区域名
  • regionCode: 获取区域代码
  • cityName or city: 获取城市名
  • latitude or lat: 获取纬度
  • longitude or lng: 获取经度

示例:

6.后记

花了一个下午折腾了EdgeOne和IP优选,最后总结出来把EdgeOne设置成Cname + ns服务器放在华为云搞优选是真的很爽,如下: itdogfull

Powered by  LiteBlog
|