from Kozaneba開発日記2021-08-29 Cloud FunctionsでScrapboxAPIを叩く
いきさつ:
- KozanebaにScrapboxこざねを追加した。
- 各種パラメータを人間が設定するのは面倒
- ページのURLを渡したらScrapboxのAPIを叩いて必要なデータを取ってくるようにしようと考えた。
- しかしScrapboxのAPIのCORS制限により、Kozanebaを開いたブラウザからScrapboxのAPIは直接叩けない。
- そこでCloud Functionsで叩く。
絵で解説
やったことメモ
- 料金プランのアップグレード
- サーバサイドはPythonに慣れてるけど、ずっとPythonだけなのもなんだし、今回のは難しくないはずだからNode.jsでやるか
- 近いうちにscrabox-parserを使いたいし
- Writing Cloud Functions | Cloud Functions Documentation | Google Cloud
- エミュレータにFunctionsを追加するのどうするんだ :
$ firebase init emulators
You're about to initialize a Firebase project in this directory:
/Users/nishio/kozaneba
Before we get started, keep in mind:
* You are initializing within an existing Firebase project directory
できた
- 設定を変更する方法が「init」なのおかしいだろ…
できてない
$ firebase emulators:start --import firebase_emulator_data
⚠ functions: The functions emulator is configured but there is no functions source directory. Have you run firebase init functions?
$ firebase init functions
:
? What language would you like to use to write Cloud Functions? TypeScript
? Do you want to use ESLint to catch probable bugs and enforce style? Yes
? Do you want to install dependencies with npm now? Yes
これでできたかな?
$ firebase emulators:start --import firebase_emulator_data
:
⚠ Error: Cannot find module '/Users/nishio/kozaneba/functions/lib/index.js'. Please verify that the package.json has a valid "main" entry ...
⚠ We were unable to load your functions code. (see above)
- It appears your code is written in Typescript, which must be compiled before emulation.
- You may be able to run "npm run build" in your functions directory to resolve this.
あー、TypeScriptを選択したから先にビルドしろとのことらしい
サンプルコードがあったからコメントアウトを外してビルドしてみる ts
import * as functions from "firebase-functions";
// // Start writing Firebase Functions
// // https://firebase.google.com/docs/functions/typescript
//
export const helloWorld = functions.https.onRequest((request, response) => {
functions.logger.info("Hello logs!", { structuredData: true });
response.send("Hello from Firebase!");
});
今度こそできた
http://localhost:5001/regroup-d4932/us-central1/helloWorld
にブラウザでアクセスしてレスポンスを見る
functions.logger.info("Hello logs!", { structuredData: true });
に関してはエミュレータが走ってるターミナルにこう表示されてた
:
i functions: Beginning execution of "us-central1-helloWorld"
> {"structuredData":true,"severity":"INFO","message":"Hello logs!"}
i functions: Finished "us-central1-helloWorld" in ~1s
さて、ScrapboxAPIを作る requestオブジェクトの仕様はどこにあるかな なるほどExpressと同じか https://expressjs.com/en/4x/api.html#req
ts
export const get_scrapbox_page = functions.https.onRequest(
(request, response) => {
console.log(request.body);
response.json(request.body);
// functions.logger.info("Hello logs!", { structuredData: true });
// response.send("Hello from Firebase!");
}
);
js
fetch("http://localhost:5001/regroup-d4932/us-central1/get_scrapbox_page", {
method: "post",
body: JSON.stringify({ foo: "bar" }),
})
.then((x) => x.json())
.then((x) => console.log(x));
output
{"foo":"bar"}
よし、やりとり部分はできた。 ではScrapboxのAPIを叩こう。
ts
import fetch from "node-fetch";
export const get_scrapbox_page = functions.https.onRequest(
(request, response) => {
const body = JSON.parse(request.body);
const url = body.url;
const api_url = url.replace("scrapbox.io/", "scrapbox.io/api/pages/");
fetch(api_url).then((req) => {
console.log(req);
req.text().then((text) => {
console.log(text);
response.send(text);
});
});
}
);
js
fetch("http://localhost:5001/regroup-d4932/us-central1/get_scrapbox_page", {
method: "post",
body: JSON.stringify({
url: "https://scrapbox.io/nishio/2021-08-28Kozaneba%E9%96%8B%E7%99%BA%E6%97%A5%E8%A8%98",
}),
})
.then((x) => x.json())
.then((x) => console.log(x));
output できた
じゃあアプリからこのAPIを叩こう→CORS https://cloud.google.com/functions/docs/writing/http#handling_cors_requests
- なるほど、こうか。 ts
export const get_scrapbox_page = functions.https.onRequest((req, res) => {
res.set("Access-Control-Allow-Origin", "*");
if (req.method === "OPTIONS") {
// Send response to OPTIONS requests
res.set("Access-Control-Allow-Methods", "GET");
res.set("Access-Control-Allow-Headers", "Content-Type");
res.set("Access-Control-Max-Age", "3600");
res.status(204).send("");
return;
}
const body = JSON.parse(req.body);
const url = body.url;
const api_url = url.replace("scrapbox.io/", "scrapbox.io/api/pages/");
fetch(api_url).then((req) => {
req.text().then((text) => {
res.send(text);
});
});
});
問題なくアプリから叩けるようになった。
できたできた
あとはfirebase deployして…
$ firebase deploy
ESLintが文句を言ってうるさいので無効にする
$ firebase init functions
:
? Do you want to use ESLint to catch probable bugs and enforce style? No
再度デプロイ
$ firebase deploy
:
i functions: creating Node.js 14 function get_scrapbox_page(us-central1)...
✔ functions[get_scrapbox_page(us-central1)]: Successful create operation.
i functions: cleaning up build files...
Function URL (get_scrapbox_page(us-central1)): https://us-central1-regroup-d4932.cloudfunctions.net/get_scrapbox_page
できた
叩くのをlocalhost:5001
からhttps://us-central1-regroup-d4932.cloudfunctions.net/get_scrapbox_page
にかえる
ts
fetch("https://us-central1-regroup-d4932.cloudfunctions.net/get_scrapbox_page", {
method: "post",
body: JSON.stringify({
url: "https://scrapbox.io/nishio/2021-08-28Kozaneba%E9%96%8B%E7%99%BA%E6%97%A5%E8%A8%98",
}),
})
.then((x) => x.json())
.then((x) => console.log(x));
できた