#22 IPAPAPI - IP as Picture API

今天来用 Cloudflare Workers 写个有趣的东西吧!

你的 IP 是不是

很酷吧!

现在为你介绍... IP as Picture API,简称 IPAPAPI。

如何做到的?

还记得我们在 #20 Telegram Bot Webhook 讯息收发 中在介绍 wrnagler dev 时有提到原本给你的 Code 跑下去会回传 Worker 收到的东西吗?

其中也有包含到你的 IP 喔!

所以我们可以用 Worker 来动态生成包含请求来源 IP 的图片,为了在 10ms 内完成图片,我们使用 SVG 向量图来直接操作文字图内文字。

路由

在这个程序中,我们只需要处理两条路由规则即可,其中一条是 favicon.ico,另一条则是请求图片的规则:

// src/main.js
import { Router } from "itty-router";
import { response } from "./response";
import { create } from "./creator";
import colorFix from "./color_fix";

const router = Router();
// 也可以将请求指向其他地方的图档当作 favicon,但这里其实没什麽必要,所以直接回传空字串
router.all("/favicon.ico", () => response({ data: "", status: 200 }));

// 对於其他所有请求,我们一律当作对图片的请求
router.all("*", async (request) => {
    const { query, headers } = request;
    // 我们用 Cloudflare 加上的 CF-CONNECTING_IP 当作来源 IP
    const ip = headers.get("cf-connecting-ip") || headers.get("x-forwarded-for") || request.headers.get("x-real-ip");
    // 解构请求中的 Query String,就是问号後面的参数
    const config = {
        ip,
        font: (query.font || query.f || "baloo").trim().toLowerCase(),
        size: +(query.size || query.s || 20),
        width: +(query.width || query.w || 140),
        height: +(query.height || query.h || 60),
        radius: +(query.radius || query.r || 0),
        color: "#" + (query.color || query.c || "2E3440"),
        background: "#" + (query.background || query.b || "ECEFF4"),
    };
    // 修正错误的颜色
    config.color = colorFix(config.color);
    config.background = colorFix(config.background);

    // 在 console 显示标准化後的生成设定
    console.log(JSON.stringify(config, null, 2));
    // 把设定丢到产生器去
    const product = create(config);
    // 用 response 处理 Header 之类的东西
    return response({ data: product });
});

async function main() {
    addEventListener("fetch", (event) => {
        event.respondWith(router.handle(event.request));
    });
}

// 丢给 index.js 去执行,个人习惯而已,当然也可以直接跑
export { main };

接下来,我们来说说 colorFix 处理的问题。
我希望 API 能接受 Hex Color (#XXXXXX) 以及 CSS Color String (就是像 red、blue 这种),但上面那样写会有个问题:当使用者输入 royalblue 时,会变成 #royalblue,这可不是颜色啊!
所以我们用 colorFix 去「修好」这些坏掉的颜色。

colorFix

colorFix 会从一个颜色列表中找是否有符合的「真的」颜色,如果有就把「#」拿掉。

// src/colorFix.js
// 可解析颜色列表
const COLOR_LIST = [
    "transparent",
    "aliceblue",
    ...
    "yellowgreen",
];

function colorFix(color) {
    const real = color.replace(/^#/, "");
    if (COLOR_LIST.includes(real)) return real;
    return color;
}

export default colorFix;

creator

creator.js 就是整个程序的核心,负责生成 SVG 档案。

// src/creator.js
import fonts from "./fonts";

// 解构传进来的各项设定
function create({ ip, font, size, width, height, radius, color, background }) {
    // 对於字体做检查,如果没有指定字体,则用预设的 baloo
    let fontCss = fonts[font],
        fontFamily = font;
    if (!fontCss || !fontFamily) {
        fontCss = fonts["baloo"];
        fontFamily = "baloo";
    }
    // 我们的「产品」
    let product = "";
    // 首先包含 SVG 的定义东西,要不然浏览器会没办法解析喔
    product += `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}" viewBox="0 0 ${width} ${height}" fill="${background}">`;
    // SVG 中是可以用 CSS 规则的,所以甚至可以用动画喔
    product += `<style>
        ${fontCss}
        svg {
            border-radius: ${radius}px;
        }
        #ip {
            font-family: ${fontFamily};
            font-size: ${size}px;
            fill: ${color};
            text-anchor: middle;
            dominant-baseline: middle;
        }
    </style>`;
    // 下面这个 rect 是背景喔
    product += `<rect x="0" y="0" width="${width}" height="${height}" rx="${radius}" ry="${radius}" fill="${background}"/>`;
    // 这行则是 IP 的文字
    product += `<text id="ip" x="${width / 2}" y="${height / 2}">${ip}</text>`;
    // 记得结尾
    product += "</svg>";
    return product;
}

export { create };

这样几乎都完成了,除了最累的字体。

Fonts!!

IPAPAPI 总共提供 19 种字体,都是从 Google Fonts 上来的,使用 OFL 授权。

这边有个麻烦的事,我们不能直接用 CSS 规则 @import Google Fonts,必须使用 inline base64 放到档案内。

还记得之前说过,Cloudflare Workers 只支援大小 1 MB 吧,那怎麽塞那麽多字体?
答案是把字体中不用的字大部分都删掉!毕竟我们基本上只有数字 + . + :

然後把每一个字型档转成 base64 放到 JavaScript 档案内:

// src/fonts/baloo.js
const baloo = `
@font-face { 
    font-family: "baloo";
    font-style: normal;
    font-weight: 400;
    font-display: swap;
    src: url(data:application/octet-stream;base64,d09GMgABAAAAAEGQABIAAAAA5HQ...FAAA=);
}`;

export default baloo;

然後丢到 src/fonts/index.js 去:

// src/fonts/index.js
import baloo from "./baloo";
import roboto from "./roboto";
...
import ruthie from "./ruthie";

const fonts = {
    baloo,
    roboto,
    ...
    ruthie,
};

export default fonts;

累死人了,应该写个自动化程序。 XD

然後就完成啦!

它现在住这:Full Counter: IP as Picture API
前面写个 Full Counter 的原因是因为它的概念就是把你丢过来的 IP 一字不差的丢回去。

使用方法与文件:README


每日铁人赛热门 Top 10 (1005)

以 10/05 20:00 ~ 10/06 20:00 文章观看数增加值排名

  1. +132 Day-1 开始玩怀旧游戏机的事前准备导览篇
    • 作者: rei0
    • 系列:在新世代里复活吧!我的童年怀旧游戏机们!
  2. +131 [DAY-22] 填补知识缺口 寻找导师 持续学习
    • 作者: flipsyde
    • 系列:带脑去上班 & No Rules Rules
  3. +123 [Day 28] Gitea - 如何自签凭证与Nginx注意
    • 作者: rainforest
    • 系列:Dev's Ops 启程
  4. +115 [Day 3] SRE - Log写好一点,对团队好一些
    • 作者: rainforest
    • 系列:Dev's Ops 启程
  5. +115 [Day 5] SRE - 发动事件左移之术,预视未来的机制
    • 作者: rainforest
    • 系列:Dev's Ops 启程
  6. +114 [Day 4] SRE - 保持精简的监控
    • 作者: rainforest
    • 系列:Dev's Ops 启程
  7. +114 [Day 11] SRE - 事後检讨,拜托拜托让我吸个经验值
    • 作者: rainforest
    • 系列:Dev's Ops 启程
  8. +113 [Day 10] SRE - ON-CALL
    • 作者: rainforest
    • 系列:Dev's Ops 启程
  9. +113 Day 21: Informix(2)
    • 作者: 阿瑜
    • 系列:FRIENDS
  10. +112 Proxmox VE 网路进阶设定 (Bridge、LACP、VLAN)
    • 作者: Jason Cheng (节省哥)
    • 系列:突破困境:企业开源虚拟化管理平台

Bogay 学长,今天榜单有好多 SRE 喔!


<<:  Day21 NodeJS-Express VI

>>:  [Day_22]函式与递回_(1)

程序语言、Ruby、Rails

新手入门笔记 我为素人,以分享角度提供个人心得笔记等,主要在帮自己做笔记。 内容有误或英文打错,非常...

nginx 反向代理到路径时自动添加路径下的 index.html

在设置反向代理静态网站时,当网页在路径目录下 nginx 不会自索引 index.html (例:h...

DAY27-ASP.NET网页切换导向及状态管理

网页大家都知道是切来切去的 但这之中涉及到许多数据的传递问题 所以蛮需要进行事先规划的 第一个来讲讲...

Dungeon Mizarka 018

MiniMap制作Part2 找到了tile生成时位置错乱的原因,还是因为进位造成的问题。A★内部是...

Day 22 菜鸟的 helm 纪录 - 进阶篇

在昨天介绍了Helm这一工具,那们今天就来介绍如何建立属於自己的Helm repo吧!! ps.如果...