carloでHTTP通信してみる

carloとは

github.com

githubの説明文をgoogleで翻訳しました。

Carloは、ノードアプリケーションにGoogle Chromeレンダリング機能を提供し、Puppeteerプロジェクトを使用してローカルにインストールされたブラウザインスタンスと通信し、ノードとブラウザ間の通信のためのリモート呼び出しインフラストラクチャを実装します。

ポイントは「ローカルにインストールされたブラウザインスタンスと通信」の部分。 ローカルのchromeを使うから必然的にバイナリサイズが小さくなる。

疑問 クロスドメインなサイトにajaxでHTTP通信できるの?

結論としては「ajaxではできない」
UI(html)はchrome上で動いてるだけだから。

↑すいません、これ嘘でした。 やり方は追記で。

ただnode.jsとの間で通信できるから

UI => node => HTTP

のような構図でnodeを間に挟めばいけそう。

1. hello world

とりあえず表示するまで

setup

npm init
npm install carlo --save

これでpackage.jsonが作られる。

htmlを作る

www/index.html

<body>Hello</body>

index.jsでhtmlを読み込む

const carlo = require('carlo');

carlo.launch().then(async app => {
  app.serveFolder(__dirname + '/www');
  app.on('exit', () => process.exit());
  await app.load('index.html');
});

起動

node .

これでウィンドウにHelloが表示される

2. UIからnodeを呼ぶ

carloにrpcというクラスがあるのでそれを使います。

index.jsに呼ばれる側を作る

const carlo = require('carlo');
const { rpc } = require('carlo/rpc');// 追加

carlo.launch().then(async app => {
  app.serveFolder(__dirname + '/www');
  app.on('exit', () => process.exit());
  await app.load('index.html', rpc.handle(new Backend) /* 追加 */);
});

// htmlから扱うクラス追加
class Backend {
  hello(name) {
    console.log(`Hello ${name}`);
    return 'Backend is happy';
  }
}

htmlからnodeを呼ぶ

www/index.html

<script>
// 追加
async function run() {
  console.log("run");
  const [backend] = await carlo.loadParams();
  
  console.log(await backend.hello('from frontend'));
}
</script>
    
<body onload="run()">Hello</body>

carlo.loadParams()でnode側のインスタンスを取り出してhello()を実行します。
するとnode側の実装でconsole.log()が実行され、ターミナル上にfrom frontendと表示されます。

実行

ターミナル上にHello from frontendが表示されました。 UI上のconsoleにはBackend is happyが表示されました。

3. node-fetchで通信する

chromeで使えるfetch関数はnodeにはないので、それをインストールしてhtmlから利用します。

node-fetchのインストール

npm install node-fetch --save

nodeに通信部分を実装する

index.js

const carlo = require('carlo');
const { rpc } = require('carlo/rpc');
const nodeFetch = require('node-fetch');// 追加

carlo.launch().then(async app => {
  console.log(rpc);
  app.serveFolder(__dirname + '/www');
  app.on('exit', () => process.exit());
  await app.load('index.html', rpc.handle(new Backend));
});

class Backend {
  hello(name) {
    console.log(`Hello ${name}`);
    return 'Backend is happy';
  }

  // 追加: 通信して結果を返す
  async fetch(url) {
    const res = await nodeFetch(url);
    return res.text()
  }
}

UIから呼び出す

<script>
async function run() {
  console.log("run");
  const [backend] = await carlo.loadParams();

  console.log(await backend.hello('from frontend'));

  // 追加
  const text = await backend.fetch('https://www.google.com/')
  console.log(text);
}
</script>
    
<body onload="run()">Hello</body>

実行

これでUIのconsole上にgoogleのソースが表示されました

まとめ

carloはchromeのラッパーみたいなものなのでUI上からクロスドメインなHTTP通信はできないが、nodeを介せばできることがわかった。
気軽に使えそうな予感。

追記

「UI側でクロスドメインな通信は無理」と書きましたが、できました。 方法としては起動オプションでセキュリティをoffにすればOK。
こんな感じです。

index.js

const carlo = require('carlo');

carlo.launch({
    args:['--disable-web-security']// 追加
  }).then(async app => {
  app.serveFolder(__dirname + '/www');
  app.on('exit', () => process.exit());
  await app.load('index.html');
});

ポイント launchのオプションに--disable-web-securityを渡すところ。これでクロスドメインの制限が外れる

inde.html

<script>
async function run() {
  console.log("run");
  const res = await fetch('https://www.yahoo.co.jp');
  const text = await res.text();
  console.log(text);
}
</script>
    
<body onload="run()">Hello</body>

fetchで普通に取得可能になる

セキュリティには気をつけろ

これで目標は達成したけど、webセキュリティを外すと何が起こるのかわからないので気をつけようw
node側でApp.serveHandler(handler)を使えばHttpRequestにフィルターがかけれるのでそれでアクセス制限かけるのもアリだと思う