Node.jsとExpressでローカルサーバーを構築する(4) ―Requestで他サーバーから画像を取得する―


リンク

  1. Node.jsとExpressでローカルサーバーを構築する(1) ―Node.jsとnpmの導入―
  2. Node.jsとExpressでローカルサーバーを構築する(2) ―Expressでルーティング―
  3. Node.jsとExpressでローカルサーバーを構築する(3) ―JSONを返すスタブAPI―
  4. Node.jsとExpressでローカルサーバーを構築する(4) ―Requestで他サーバーから画像を取得する―(本記事)

概要

ローカルサーバーを構築するに当たり、サムネイルなどの沢山の画像ファイルを全てローカルに準備するのが面倒だったので、特定のパスへのリクエストを受け取ったときに、本番や開発、専用サービスなどの他サーバーからデータを持ってくるようにしてみました。
受け取ったJSONデータ内の画像IDを使って、動的にURL作って画像を取得する的なものを作るときに便利かと思います。

この記事の概要

  • 目的
    • Node.jsとExpressを利用して、PC上にローカルサーバーを立ち上げる
  • 本記事のゴール
    • 特定のパスに対するリクエストに対し、他サーバーの画像を取得して返す
  • 対象者
    • WEBフロント担当者
    • HTML,CSS,JavaScript(es2015含む)の基本的な構文を理解している人
    • HTTP通信、GET、POSTなどについて、ある程度理解している人(ざっくりで良いです)
    • 黒い画面にコマンドを打ち込むことに抵抗がない方
  • 環境・バージョン
    • Windows10
    • Node.js(推奨版) 10.15.01
    • npm 6.4.1
    • Express 4.16.4
    • request 2.88.0

Requestのインストール

Node.jsのRequestを利用して実装します。HTTP通信を便利に行うためのモジュールが詰まっています。

$ npm install request --save

request | npm

Requestで画像ファイルを取得して転送

今回は、子猫画像を取得できるサービスplacekittenから画像を取得したいと思います。
該当する画像パスを/nyaaaaaaaan/{N1}/{N2}とします。N1は横幅、N2は高さを数値で指定します。
コードはこのような感じになります。

const express = require('express');
const app = express();
// requestを読み込み
const request = require('request');

app.listen(8080, () => {
  console.log('Running at Port 8080...');
});

app.get('/nyaaaaaaaan/:width/:height', (req, res) => {
  // 送受信の設定
  const options = {
    url: 'http://placekitten.com/' + `${req.params.width}/${req.params.height}`,
    method: 'GET',
    encoding: null
  };

  request(options, (err, response, body) => {
    res.set('Content-Type', response.headers['content-type']);
    res.send(body);
  });
});

Expressのルーティングメソッドで、対象となるパスをピックアップします。
ルートパラメータを利用して、URL内の指定された位置のパスの値を取得しています。取得した値は、req.paramsオブジェクト内に、パラメータ名をキーとして格納されます。

'/nyaaaaaaaan/:width/:height' ⇒ params: { width: 000, height: 000 }

request関数の第一引数に、問い合わせ先URL文字列または設定情報をまとめたオブジェクトを指定します。
options.urlでは、問い合わせ先のドメインと取得したパスを繋げてリクエストURLを指定しています。
options.encodingは、何も指定しないとデフォルトで文字列(utf-8)にエンコーディングされてしまいます。バイナリーデータを扱いたい場合は、nullを指定する必要があります。

request関数の第二引数に、取得時のコールバック関数を渡します。
そして、expressgetメソッドから第二引数で受け取ったレスポンスオブジェクトを利用して、クライアントに画像を送信します。
この際、requestの第二引数と名前が競合しないようにご注意ください。(たぶん躓いたの私だけ)

app.get('/thumbnail', (req, res) => {
  /* 略 */
  request(options, (err, response, body) => {
    res.set('Content-Type', response.headers['content-type']);
    res.send(body);
  });
});

コールバック関数では3つの引数を受け取ります。

  1. err: エラーオブジェクト(エラー発生時)
  2. response: レスポンスの詳細(Node.jsのhttp.IncomingMessageオブジェクト)
  3. body: 本体データ

画像を送信する際、Content-Typeには画像の種類に応じた値を設定をする必要があるのですが、ここでは先方から受け取ったcontent-typeをそのまま流用しています。

では、サーバーを起動してlocalhost:8080/nyaaaaaaaan/300/200にアクセスしてみましょう。

かわいい。。。
URLの数字を変えると、また別の画像が取得できます。

リファレンス:request(options, callback)(英語)

ストリーミング

上記のコードでは、一旦ファイルを全てサーバーに読み込んだ上で、クライアントに送信しています。
この方法では、ダウンロード中待ち時間が発生しますし、読み込んだデータが一旦メモリを占有します。この記事で作っているのはローカルの簡易サーバーなので、正直これでも特に問題はないです。
ただ、折角Node.jsを使っているので、ストリーミングを利用したいと思います。ここで言うストリーミングは、データを少しずつ読み込んで少しずつクライアントに送信することです。
Node.jsによるHTTP通信は、最初から少しずつ読み込む仕様です。この少しずつ読み込んでいるデータの断片を「チャンク」と言います。

Requestには、ストリーミングを簡単に実現できる機能があるので、それを利用していきます。コードは下記のとおりです。

/**
 * 取得したいサーバのドメイン:https://sample
 * 画像のパス:/thumbnail?id=00000
 */
const TEST_DOMAIN = 'https://sample';

app.get('/thumbnail', (req, res) => {

  request(TEST_DOMAIN + req.originalUrl).pipe(res);

});

シンプル!
ファイルの拡張子を見て、よしなに送信処理を行ってくれるそうです。便利だわ~。
requestに、取得したいファイルのURLを渡します。そしてメソッドチェーンでpipeメソッドに、読み込み後の処理を渡します。ここでは、resオブジェクトをそのまま渡しています。こうすることでrequestで取得→クライアントに送信、という処理をストリーミングで実現しています。

req.originalUrl:ドメインより下のパスを、パラメータごと取得できます。

成功時・失敗時のイベントハンドラーを仕込むことも可能です。(実行は一回のみ)

app.get('/thumbnail', (req, res) => {

  request(TEST_DOMAIN + req.originalUrl)
    // 成功時
    .on('response', (response) => {
      console.log(response.statusCode);
      console.log(response.headers['content-type']);
    })
    // 失敗時
    .on('error', function (err) {
      console.log(err);
      res.sendStatus(404);
    })
    .pipe(res);
});

エラーハンドラを設定した場合、ハンドラ内で応答処理を書かないと、応答無しになってしまいます。

リファレンス:Streaming(英語)

以上で、4回にわたってお届けしたシリーズを終了いたします。
拙い記事を読んでいただき、ありがとうございました。お役に立てていたら幸いです。

参考情報

request | Git
Node.js で HTTP/HTTPS リクエスト を行う方法 - galife
How to request images and output image in Node.js - Stack Overflow
パイプライン処理・標準入出力によるデータ処理 | 奈良女子大学 情報科学科
開発が捗るかもしれない(面白い)ダミー画像,テキストまとめ