今天來用 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.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 };
這樣幾乎都完成了,除了最累的字體。
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
以 10/05 20:00 ~ 10/06 20:00 文章觀看數增加值排名
+132
Day-1 開始玩懷舊遊戲機的事前準備導覽篇
+131
[DAY-22] 填補知識缺口 尋找導師 持續學習
+123
[Day 28] Gitea - 如何自簽憑證與Nginx注意
+115
[Day 3] SRE - Log寫好一點,對團隊好一些
+115
[Day 5] SRE - 發動事件左移之術,預視未來的機制
+114
[Day 4] SRE - 保持精簡的監控
+114
[Day 11] SRE - 事後檢討,拜託拜託讓我吸個經驗值
+113
[Day 10] SRE - ON-CALL
+113
Day 21: Informix(2)
+112
Proxmox VE 網路進階設定 (Bridge、LACP、VLAN)
Bogay 學長,今天榜單有好多 SRE 喔!