7
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Node.js で外部ライブラリを使わずに API を叩く

Posted at

ちょっと API を叩いて、返ってきた json をいじりたいけど、そのために

$ npm install request

なーんて仰々しい事をしたくない時もあるわけで、外部ライブラリを使わずに行こうとしたら思いの外ハマってしまったのでメモ書き。

自分の環境は v6.9.1

TL;DR

http.requestdata イベントで得られるデータは Buffer。そして断片的。
end イベントの時にまとめる処理を書かないとハマる。あと Buffer.concatArray.prototype.concat みたいな感覚で使おうとするとハマる。というか使えない←
最終的な snippet はこちら

http もしくは https モジュールで可能

フロントエンドでは fetch とかいう新機能(?)新規格(?)があると前に読んだけど、探してみたけど、どうも node とは関係のない話らしい。代わりに http module に request なるメソッドがあるそうな。http はその名の通り暗号通信は門外漢なので、暗号通信を使いたい場合は https モジュールを使うといい。

まずは公式ドキュメントのコードをペッて見て書いたコード

コードをきちんと読まずに「あー、callback にイベントハンドラが返ってきてその中で data イベントを通して取ってくるのね、オッケー」とか思って書いたのが下のコード。
知ってる人からみたら色々抜けている。でもここまでだと上手く動いてるように見えるからタチが悪い。(一応この時から兆候はあるけど、 json の量が多くて気づけてなかった)

http.request(options, resp => {
  console.log(`status: ${resp.statusCode}`);
  console.log(`headers: ${resp.headers}`);

  resp.on("data", data => {
    console.log(`data: ${data}`);
  });
}).on("error", e => {
  console.log(`Error: ${e}`);
}).end();

そして次のステップから転がるようにハマりだす←

返ってくるのは json だからとコードを書くと SyntaxError とか怒られる。

http.request(options, resp => {
  console.log(`status: ${resp.statusCode}`);
  console.log(`headers: ${resp.headers}`);

  resp.on("data", data => {
    const result = JSON.parse(data);
    /** ↑ parse 時に SyntaxError が起きる (レスポンスが十分小さければ大丈夫かも?)
      * しかも エラーメッセージの一貫で json らしきデータも見えるから「どこで?」となる
      * 実は「このデータ、パース仕切れなかった!」という表示なんだけど、こういう時に限って気づかない
      */
    console.log(result);
  });
}).on("error", e => {
  console.log(`Error: ${e}`);
}).end();

何が起きてるんだ!! と思って console.log すると 16進数の嵐

http.request(options, resp => {
  console.log(`status: ${resp.statusCode}`);
  console.log(`headers: ${resp.headers}`);

  resp.on("data", data => {
    console.log(data);
    /** 出力はこんな感じ <Buffer 48 65 6c 6c 6f 77 20 4f 72 6c 64>
      * 最初のときは `data: ${data}` とやっているから勝手に文字列化されて、
      * 文字列が渡ってたように見えるけど実は Buffer
      */
    const result = JSON.parse(data);
    console.log(result);
  });
}).on("error", e => {
  console.log(`Error: ${e}`);
}).end();

toString をしてやっと「あれ、これ、断片化されてる?」と気づく

http.request(options, resp => {
  console.log(`status: ${resp.statusCode}`);
  console.log(`headers: ${resp.headers}`);

  resp.on("data", data => {
    console.log(data.toString("utf8"));
    /** ここで下と同じ json が見えて「これはもしや。。。」となる
    const result = JSON.parse(data);
    /** やっぱりここでエラー */
    console.log(result);
  });
}).on("error", e => {
  console.log(`Error: ${e}`);
}).end();

end イベントの存在に気付き直すも Buffer を "byteの配列" と勘違いし怒られる

http.request(options, resp => {
  console.log(`status: ${resp.statusCode}`);
  console.log(`headers: ${resp.headers}`);

  let bufs = [];
  resp.on("data", bytes => {
    bufs = bufs.concat(bytes);
    /** bytes と書いているけど、Buffer は array とは違うので、やっていることは
      * `bufs.push(bytes)` と同じで、 Buffer の入った配列を作っているだけ
      */
  }).on("end", () => {
    const result = JSON.parse(bufs.toString("utf8"));
    /** Buffer が入った配列なのでやっぱり失敗する */
    console.log(result);
  });
}).on("error", e => {
  console.log(`Error: ${e}`);
}).end();

Buffer のドキュメント で concat の文字を見て喜ぶも concat の使い方を勘違いし、エラー

http.request(options, resp => {
  console.log(`status: ${resp.statusCode}`);
  console.log(`headers: ${resp.headers}`);

  let bufs = Buffer.alloc(0);
  resp.on("data", chunk => {
    bufs = bufs.concat(chunk);
    /** TypeError: a.concat is not a function
      * Buffer の concat は prototype ではなくクラスメソッド `Buffer.concat`
      */
  }).on("end", () => {
    const result = JSON.parse(bufs.toString("utf8"));
    console.log(result);
  });
}).on("error", e => {
  console.log(`Error: ${e}`);
}).end();

やっと完成

http.request(options, resp => {
  console.log(`status: ${resp.statusCode}`);
  console.log(`headers: ${resp.headers}`);

  let bufs = [];
  resp.on("data", chunk => {
    // まずは全部配列に突っ込む
    // 正直、この時点で chunk の順番が保証されているのか知らない←
    bufs.push(chunk);
  }).on("end", () => {
    // Buffer.concat は Buffer の配列を受け取って、全部を連結した Buffer を返す
    const data = Buffer.concat(buffs).toString("utf8");
    const result = JSON.parse(data);
    console.log(result);
  });
}).on("error", e => {
  console.log(`Error: ${e}`);
}).end();

まぁ http で通信するって事は TCP なわけで、そうしたらデータの順序も保証されてるよな。よな? って感覚で使ってる←

この実装はどれくらい安全なのだろうか

普通の API はそんなに膨大な量のデータを返すわけじゃないべ? という前提で作っているけど、データ量が多かったらどうなるんだろうかとかは気になる。
調べるつもりはまだない。

なにはともあれ外部ライブラリなしで API を叩けることは分かった

ツールを作るとかになったらこれを使うことは無いだろうけど、ちょっとした snippet として覚えておくと良さそう

7
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?