Panda Noir

JavaScript の限界を究めるブログでした。最近はいろんな分野を幅広めに書いてます。

Nodeで外部のwebサーバーを建ててkillするまで

Goで書いたwebサーバーを、Nodeでテストをしています(地獄)。テストする上で、Nodeのchild_processモジュールが孫プロセスをうまくkillできずに躓きました。そこで、孫プロセスをゴリ押しでkillする方法を調べて実装してみました。

孫プロセスのPIDを取得する

孫プロセスのPIDさえ取得できれば、killすることはとてもカンタンにできます。イメージはこんな感じです。

process.kill(mago_pid); // PIDがmago_pidのプロセスをkill

孫プロセスのPIDの取得は、親子共々殺したい - Qiitaを参考に書いてみます。

const child_process = require('child_process');
const server = child_process.spawn('go', ['run', `server.go`]);
// サーバーのPIDはserver.pidで取得できる
child_process.exec(`ps --ppid=${server.pid} | cut -d' ' -f1`, (error, stdout, stderr) => {
    if (error)
        throw new Error(error);
    // 孫プロセスのPID値を取得できた!
    console.log(stdout, stderr);
});

孫プロセスをkillする

あとは取得したPIDを使ってkillすれば完成です。

const child_process = require('child_process');
const server = child_process.spawn('go', ['run', `server.go`]);

(async () => {
    // サーバーが立ち上がるまで待つ
    await new Promise(resolve => {
        server.stdout.on('data', data => {
            if (data.toString() === 'Start server\nListening port 12345...\n')
                resolve();
        });
    });
    console.log('server started');

    // サーバーのPIDはserver.pidで取得できる
    const {stdout, stderr} = await new Promise((resolve, reject) => {
        child_process.exec(`ps --ppid=${server.pid}`, (error, stdout, stderr) => {
            if (error)
                reject(error);
            resolve({stdout, stderr});
        })
    });
    for (const line of stdout.split('\n').slice(1)) {
        const pid = line.trim().split(' ')[0];
        if (pid === '')
            continue;
        process.kill(pid, 'SIGINT');
    }
    console.log('finish server');
})();

番外編: util.promisifyを使ってもっと見やすく書く

util.promisifyはべんりなのでドンドン使いましょう

const util = require('util');
const child_process = require('child_process'), exec = util.promisify(child_process.exec).bind(util);
const server = child_process.spawn('go', ['run', `server.go`]);

(async () => {
    // サーバーが立ち上がるまで待つ
    await new Promise(resolve => {
        server.stdout.on('data', data => {
            if (data.toString() === 'Start server\nListening port 12345...\n')
                resolve();
        });
    });
    console.log('server started');

    // サーバーのPIDはserver.pidで取得できる
    const {stdout, stderr} = await exec(`ps --ppid=${server.pid}`);

    for (const line of stdout.split('\n').slice(1)) {
        const pid = line.trim().split(' ')[0];
        if (pid === '')
            continue;
        process.kill(pid, 'SIGINT');
    }
    console.log('finish server');
})();

ネットワークでよく出てくる「コネクション」ってなんなんだろう?

ネットワークにおいて「コネクション」という単語はよく耳にします。

  • 「WebSocketはコネクションを確立後、ずっとそのコネクションを使う」
  • 「ロングポーリングは返すたびにクライアントがコネクションを確立し直す」
  • 「TCPでは3ウェイハンドシェイクでコネクションを確立する」

これらを見ると「コネクションは通信経路のことなのかな?」と勘違いしそうになります。実際僕はそう勘違いしていました。今回はそんな実態のよくわからない「コネクション」について解説します。

続きを読む

どんな深さの配列も1次元配列にする

ES2019でArray#flatMapが導入されたので、それを使ってどんな深さの配列でも1次元にする方法を紹介します。

flatMapの仕様

flatMapはmap()してからflat()するメソッドです。

[1,2,3].map(n => [n,-n]); // [[1,-1],[2,-2],[3,-3]]
[[1,-1],[2,-2],[3,-3]].flat();  // [1,-1,2,-2,3,-3]

[1,2,3].flatMap(n => [n,-n]); // [1,-1,2,-2,3,-3]

flatMapを使って1次元にする

コード

flatMapを使って、どんな深さの配列でも1次元に落とし込みます。これは「無限回flat()を行い、1次元になった時点でやめる」のと等価なので、flatInfと名付けました。

const arr = [1, [2, [3, 4, [5]]]];

const flatInf = item => {
    if (Array.isArray(item))
        return item.flatMap(flatInf);
    return item;
};

console.log(flatInf(arr)); // [1, 2, 3, 4, 5]

flatInfの動作

  1. 配列の各要素を「1次元の配列あるいは要素」にする
  2. できた配列をflat()する

これだけです。配列の要素が1次元の配列か要素のみなら、flat()するだけで1次元になります。

// 1次元の配列か要素のみの配列
[[1, 2], 3, [4, 5, 6], 7, 8, 9].flat(); // [1, 2, 3, 4, 5, 6, 7, 8, 9]

与えられる配列の各要素の深さは1次元とは限りません。コレを1次元にするために、再帰的にflatInfを適用しています。

おまけ: コードを圧縮する

そこまで変わりませんが。

const arr = [1, [2, [3, 4, [5]]]];

const flatInf=a=>Array.isArray(a)?a.flatMap(flatInf):a;

console.log(flatInf(arr)); // [1, 2, 3, 4, 5]

HTTP/1.1とかHTTP/2とかQUICとかにGraphQLとか、Webサーバー関連の話題についてまとめてみた。

最近ネットワーク周りについて興味があって調べているので、その結果をまとめました。

主な話題

  • HTTPの最近の流れ
  • なぜHTTP/2は高速なのか?
  • QUICとはなにか?何を解消するのか?
  • WebRTCとは?WebSocketとは?
  • RESTとGraphQL、gRPC
  • Edgeサーバー

HTTP関連

HTTPの最近の流れ、歴史

HTTP and 5G (fixed1)

HTTPのこれまでの課題、HTTP/3 over QUICや5Gといった今後の技術についてわかりやすくまとまったスライドです。

HTTP/2はなぜ高速なのか?

HTTP/2の特徴 HTTP/1.1との違いについて | REDBOX Labo

HTTP/1.1からHTTP/2はどのように変わったのかまとめられています。

  • HTTP/1.1は1コネクション1レスポンス。多重化するには複数コネクションを確立する必要がある。
  • HTTP/2では1つのコネクションで複数レスポンスさばける。

QUIC関連

QUICとは?

GoogleのQUICプロトコル:TCPからUDPへWebを移行する | POSTD

「TCPでのコネクション確立、TLSのネゴシエーションというコストがかかる通信をQUICが置き換えるよ」という話です。

QUICで解消できるHTTP/2の課題

Head of Line Blocking - High Performance Web 2015 - Qiita

HTTP/1.1で起きるHoLブロッキングと、HTTP/2で起きるHoLブロッキングそれぞれの解説と、QUICならどちらも解消できるという話です。

その他QUIC

QUICとHTTP/3時代のインターネット解説書はどうあるべきだろう - golden-luckyの日記

QUICについてのよくある誤解や、QUICがOSI参照モデルに当てはまらないよというような話です。一言で要約することが困難なので一読をおすすめします。

リアルタイム通信について

これらは「ブラウザ上でのリアルタイム通信」に関する話題です。リアルタイム通信はチャットやオンラインゲーム、通知などで必要になる技術です。また、ツイッターのタイムラインなどでも利用されます。

リアルタイム通信の種類

リアルタイムなwebアプリを実現する方法(ポーリング、Comet、Server Sent Events、WebSocket) - Qiita

リアルタイム通信の種類についての記事です。

  • ポーリング
  • Comet(ロングポーリング)
  • SSE
  • WebSocket

4つのリアルタイム通信方式の違いについて図でわかりやすく解説されています。

WebRTCで通信ができるまで

WebRTCはサーバーを介さないでクライアント同士が通信を行う(P2P通信する)ための技術です。リアルタイム通信という側面も持ち合わせています。

WebRTCの基本とP2P通信が成立するまでを学ぶ - Qiita

WebRTCで通信ができるまでのプロセスについての記事です。WebSocketも出てきます。

RESTやGraphQLなどAPI周り

APIを扱いたいとき、今まではRESTが多かったです。しかし、GraphQL、gRPCといった選択肢が出てきました。

GraphQLを最速でマスターするための意識改革3ヶ条 - Qiita

GraphQLとRESTの違いや、「GraphQLは操作とデータ取得のみに専念して、認証などは別の層で捌くべし」という記事です。

gRPCって何? - Qiita

gRPCについての記事です。正直僕はこの記事だけでは理解できませんでした。gRPCはフロントエンドとバックエンドの通信はできず、バックエンド間の通信を担うらしい(??)(gRPC-Webなるものはできるそうですが)。

Edgeサーバー

Edgeサーバーとは、クラウドとクライアントの間に置かれるサーバーです。同名のブラウザとは関係ありません。

Edgeサーバーは主にクライアントとクラウド間での通信コストを下げる目的で使用されます。ユーザーに地理的に近い位置にサーバーを配置して、高速で通信できるようにして、Edgeサーバーでできることはしてしまおうというモノです。IoTや自動運転関連でよく出てきます。

リーダブルコードを読んだらコードの質が格段に上がった

リーダブルコードを読む前に書いたコードを見たら、ゴミすぎて全部書き直しました。

今回はこちらのコードを書き直しました。

ひらがなからローマ字への変換可能パターンを列挙するプログラムをつくった - Panda Noir

続きを読む