generated at
plantuml-proxyのprototype code
plantuml-proxyを試しにscrapbox上で書いてみたもの
このあとgitに移した

2020-12-27 少しcodeを書いてみる
directory layout
text
plantuml-maker ├─api/ ├─.gitignore ├─.vercelignore ├─LICENSE ├─README.md └─vercel.json
api/ 以外にファイルを置くとdeploy出来なかったのは、 .vercelignore で無視していたからではないか?
ちゃんと追加すれば認識されるはず
vercel.json
{ "functions": { "api/**/*.[jt]s": { "runtime": "vercel-deno@0.7.6" } }, "routes": [ { "src": "/png/(.+)", "dest": "api/plantuml?type=png&url=$1" }, { "src": "/svg/(.+)", "dest": "api/plantuml?type=svg&url=$1" } ] }
api/plantuml.ts
import { ServerRequest } from "https://deno.land/std/http/server.ts"; import { fetchText, fetchImage, respondImage } from "../src/fetch.ts"; export default async (req: ServerRequest) => { const base = `${req.headers.get("x-forwarded-proto")}://${req.headers.get( "x-forwarded-host" )}`; const url = new URL(req.url, base); // plantUMLのURLを取得する const params = url.searchParams; const plantumlURL = params.get("url"); if (!plantumlURL) { req.respond({ status: 400, body: "No plantuml URL found." }); return; } const imageType = params.get("type"); if (!imageType) { req.respond({ status: 400, body: "No image type is specified." }); return; } if (!['png','svg'].includes(imageType)) { req.respond({ status: 400, body: "Image type must be 'png' or 'svg'." }); return; } try { const plantumlBody = await fetchPlantUMLText(svgURL); const imageData = await fetchPlantUMLImage(plantumlBody, imageType); respondImage(imageData, req); } catch (e) { req.respond({ status: 400, body: e.message }); } };
fetch.ts
import { ServerRequest } from "https://deno.land/std/http/server.ts"; import { deflate } from "https://deno.land/x/denoflate@1.1/mod.ts"; import { encode64 } from "./encoder.ts"; import { textToBuffer } from "./converter.ts"; export async function fetchText(url: string) { const response = await fetch(url); if (!response.ok) { throw new Error("Text response is not OK"); } const text = await response.text(); if (!text.trim()) { throw new Error(`Text is empty`); } return text; } export async function fetchImage(plantUMLText: string, imageType: 'png' | 'svg') { const response = await fetch(`http://www.plantuml.com/plantuml/${imageType}/${encode64(deflate(textToBuffer(text), 9))}`); if (!response.ok) { throw new Error("Failed to fetch a plantUML image from the PlantUML server"); } const imageData = await response.blob(); } function respondImage(imageData: Blob, req: ServerRequest) { const headers = new Headers(); headers.set("Content-Type", `${imageData.type}; charaset=utf-8`); headers.set("Cache-Control", `private, max-age=${60 * 60 * 24}`); req.respond({ headers, body: imageData, }); }

converter.ts
export function textToBuffer(text: string) { const ascii_string = unescape(encodeURIComponent(text)); // 間にこれを噛まさないと文字化けする let buffer = new Uint8Array(ascii_string.length); for (let i = 0; i < ascii_string.length; i++) { buffer[i] = ascii_string.charCodeAt(i); } return buffer; }

sample.pu
Bob->Alice : hello Alice->Bob : 日本語でおk

encoder.ts
// from http://plantuml.sourceforge.net/codejavascript2.html export function encode64(data: Uint8Array) { let r = ''; for (let i = 0; i < data.length; i += 3) { if (i + 2 === data.length) { r += append3bytes(data[i], data[i + 1], 0); } else if (i + 1 === data.length) { r += append3bytes(data[i], 0, 0); } else { r += append3bytes(data[i], data[i + 1], data[i + 2]); } } return r; } function encode6bit(b: number) { if (b < 10) return String.fromCharCode(48 + b); b -= 10; if (b < 26) return String.fromCharCode(65 + b); b -= 26; if (b < 26) return String.fromCharCode(97 + b); b -= 26; if (b === 0) return '-'; if (b === 1) return '_'; return '?'; } function append3bytes(b1: number, b2: number, b3: number) { const c1 = b1 >> 2; const c2 = ((b1 & 0x3) << 4) | (b2 >> 4); const c3 = ((b2 & 0xF) << 2) | (b3 >> 6); const c4 = b3 & 0x3F; return encode6bit(c1 & 0x3F) + encode6bit(c2 & 0x3F) + encode6bit(c3 & 0x3F) + encode6bit(c4 & 0x3F); }

#2022-03-11 17:14:04
#2021-03-19 08:42:10
#2020-12-19 03:21:22