著者は、 Diversity in Tech Fund を選択して、 Write forDOnationsプログラムの一環として寄付を受け取りました。

序章

Web開発者が使用する最も一般的な画像形式は、JPEGPNGです。 .jpgファイルは写真に最適ですが、.pngファイルは、ロゴや図など、同じような色の大きなブロックを持つ画像に最適です。 ただし、これらの形式は、それぞれ1992年と1996年に最初に開発されました。 近年、ビデオ圧縮の進歩により、Web用の2つの新しい主要な画像形式webpavifが開発されました。 「次世代」(または「次世代」)画像形式として知られるこれらの形式は、自由に使用でき、画像が大幅に小さくなり(20%から50%削減)、Webブラウザで広くサポートされるようになります。 webpavifは画像が小さくなるため、これらの新しい形式で画像を提供する方が効率的で高速です。

ただし、「次世代」フォーマットの使用にはいくつかの課題があります。 まず、古いブラウザのユーザーに最大限の互換性を持たせたい場合は、必要に応じてpngjpgを使用することをお勧めします。 第二に、アップロードされた画像の大規模なライブラリがある場合、それらの変換には時間がかかります(そして費用がかかります)。

これは、イメージプロキシが役立つ場合があります。 画像プロキシは、Webリクエストに基づいてその場でカスタム画像を生成または取得するアプリケーションです。 これらは、画像のトリミングやサイズ変更、透かしや画像効果の追加、フォーマットの変更に使用できます。 イメージプロキシを使用すると、エンドユーザーには同じように見えるが、はるかに効率的なイメージを配信できます。

使用可能なプロキシの1つは、imgproxyと呼ばれます。 Evil Martians に支えられており、必要に応じて、商用の「Pro」拡張機能を備えたセルフホストの無料バージョンとして利用できます。 imgproxyGoで記述され、libvipsを使用して画像変換を強化します。 libvipsは、利用可能な最も高速で効率的な画像処理ライブラリの1つであり、ユーザーの要求に応じて画像を生成するのに最適です。

このチュートリアルでは、imgproxyを設定し、画像を変更するためのプロキシURLを生成します。 また、imgproxyを構成して、署名付きURLを使用して画像を安全に提供し、サポートされている場合は自動的に「次世代」画像形式に切り替えます。

前提条件

このチュートリアルに従うには、次のものが必要です。

  • Dockerがインストールされたサーバーまたは開発マシン。 Ubuntu 20.04の場合は、ガイド Ubuntu 20.04 にDockerをインストールして使用する方法(または別のバージョンまたはディストリビューションを選択する方法)に従うことができます。 macOSまたはWindowsの場合は、 DockerDesktopの製品ドキュメントを参照してください。
  • サンプルスクリプトを実行するためのスクリプト言語。 このチュートリアルでは、Rubyを使用します。これは、ガイド Ubuntu20.04でrbenvを使用してRubyonRailsをインストールする方法に従ってUbuntuにインストールできます。 macOSでは、組み込みバージョンを使用できます。
  • ブラウザ開発者ツールにある程度精通していると便利です。 Chromeについては、公式ドキュメントまたはチュートリアルChromeツールを使用してパフォーマンスのボトルネックを見つける方法を確認してください。

ステップ1—Dockerを使用してimgproxyをインストールする

この手順では、imgproxyをインストールして、正しく実行されていることを確認します。 imgproxyをインストールする方法はいくつかありますが、このチュートリアルでは、 Docker を使用します。これは、最も移植性の高い方法であり、本番環境に移行する際の変更が最小限であるためです。 。

Dockerがインストールされているサーバーまたは開発マシンで、次のコマンドを実行して、Dockerが実行されていることを確認します。

  1. docker info

出力の開始は次のようになります(コンテナまたはイメージの数がわずかに異なっていても心配しないでください)。

Output
Client: Context: default Debug Mode: false Plugins: buildx: Docker Buildx (Docker Inc., v0.7.1) compose: Docker Compose (Docker Inc., v2.2.1) scan: Docker Scan (Docker Inc., v0.14.0) Server: Containers: 15 Running: 0 Paused: 0 Stopped: 15 Images: 47 Server Version: 20.10.11 ...

Dockerが実行されていることを確認したら、pullサブコマンドを使用して、Dockerhubからimgproxyイメージの最新バージョンをダウンロードできます。

  1. docker pull darthsim/imgproxy:latest

これにより画像がダウンロードされている間、一連のプログレスバーが表示されます。

次に、runサブコマンドを使用してコンテナのインスタンスを起動します。

  1. docker run -p 8080:8080 -it darthsim/imgproxy

-pオプションは、コンテナーのポート8080をマシンのポート8080にマップするようにDockerに指示します。 --itオプションは、インタラクティブな端末が必要であることをDockerに通知します(この場合、imgproxyによって出力されたログを表示する必要があります)。 最後に、darthsim/imgproxyは、以前にダウンロードして実行したいイメージの名前です。

出力は次のようになります。

WARNING [2021-12-24T03:21:18Z] No keys defined, so signature checking is disabled
WARNING [2021-12-24T03:21:18Z] No salts defined, so signature checking is disabled
INFO    [2021-12-24T03:21:18Z] Starting server at :8080

警告について心配する必要はありません。 これらは後の手順で修正します。

この手順では、impgproxyをインストールし、実行されていることを確認しました。 次のステップでは、imgproxyURLを使用して変更された画像をリクエストします。

ステップ2—画像を変更するためのimgproxyURLの作成

このステップでは、変更された画像をリクエストするために使用できるimgproxyURLを作成します。

imgproxyを使用する場合、WebサイトのエンドユーザーはimgproxyにHTTPリクエストを送信します。 次に、imgproxyソースイメージの取得を要求します。 ソースイメージは、プロキシにアクセスできるインターネット上のどこにあってもかまいません。 (これは後の手順で保護します。)ソースイメージが取得されると、imgproxyは要件に応じてイメージを変更し、そのイメージをエンドユーザーに送信します。

URLパラメータで指定することにより、imgproxyに必要な変更の種類を指定します。 imgproxyURLは次の形式を使用します。

http://your-imgproxy-host/%signature/%processing_options/%encoded_source_url.%extension

このURLの%signatureはオプションであり、後の手順で構成します。 %processing_optionsは、imgproxyに画像の切り抜きや透かしなどを行うように指示する場所です。または、画像を変更したくない場合は、画像を完全に省略できます。 %encoded_source_urlは、ソースイメージを指します。これは、imgproxyに変更を要求しているイメージです。 別のURLを埋め込むときに有効なままになるように、エンコードする必要があります。 最後に、オプションの%extensionパラメーターを使用すると、imgproxyの画像ファイルタイプを指定して、.jpgなどの一般的なファイル拡張子を使用して変換できます。

プロキシされたURLを生成したら、imgタグのsrc属性を新しいプロキシされたURLに置き換えることができます。 たとえば、一般的な画像のURLは次のとおりです。

<img src="https://yourserver.com/assets/image.png" />

imgproxyを使用すると、そのURLを次のようなものに置き換えることができます。

<img src="https://imgproxy.yourserver.com/your imgproxy url here />

これは、Webサイトを変更するときにうまく機能します。 ただし、imgproxyの使用方法を学ぶ場合、結果を表示する最も簡単な方法は、お気に入りのWebブラウザーのアドレスバーにURLを入力することです。これにより、結果がすぐに表示されます。 次に、imgproxy URLのさまざまな部分を実行し、ブラウザでアクセスします。

Dockerでimgproxyを起動したため、サーバーはlocalhostで実行され、ポート8080でリッスンしています。 これにより、imgproxyホストが作成されます。

http://localhost:8080

注:このチュートリアルを実行するためにローカルマシンの代わりにリモートサーバーを使用している場合は、次の例の「localhost」をサーバーのIPアドレスに置き換えてください。 正しいURLをお持ちの場合は、http://your-server-ip:8080にアクセスすると、 Hey、I’mimgproxyメッセージが表示されます。

このチュートリアルでは、次のリンクにある子犬のこの写真を使用できます:https://i.imgur.com/KSLD4VV.jpeg。 (この画像は元々 Unsplash からのものです。)または、インターネットでアクセス可能な任意の画像を使用できます。 このチュートリアルでは、イラストやロゴではなく、必ず写真(jpg画像)を使用してください。

プロキシURLを作成するための最初のステップは、ソースURLをエンコードすることです。 これは、URLで意味のあるすべての特殊文字(たとえば、:///.)を意味のない安全な文字に置き換えることを意味します。 このように、ソースイメージURLをプロキシURLに埋め込んだ場合、それは有効なままであり、クライアントはそれをプロキシURLから分離できます。

imgproxyURLセーフなBase64エンコーディングを使用します。 これに使用するお気に入りのプログラミング言語がある場合は、オンラインの Base64エンコーディングツールでうまくいきます([URLセーフエンコーディングを実行する(Base64URL形式を使用する)]チェックボックスをオンにしてください)。

注: /plainオプションを使用すると、Base64エンコードの代わりにプレーンURLを使用できますが、これは、クエリパラメーターをエンコードする必要があることを意味します。 一般に、Base64エンコーディングはエラーが発生しにくいです。

URLセーフBase64を使用して、サンプルの子犬の画像はaHR0cHM6Ly9pLmltZ3VyLmNvbS9LU0xENFZWLmpwZWcにエンコードされます。 これを使用して、imgproxy URLを作成し、次のようにWebブラウザーに入力できます。

http://localhost:8080/sig/aHR0cHM6Ly9pLmltZ3VyLmNvbS9LU0xENFZWLmpwZWc

注:別の画像でテストする場合は、代わりにこのURLの最後の部分をBase64でエンコードされたURLに置き換えてください。

これをロードすると、子犬の変更されていない画像が表示されます。

注:画像が表示されない場合は、代わりにエラーメッセージが表示されるか、手順1のdocker runコマンドの出力を確認できます。 エラーが発生したリクエストを含むimgproxyログが表示されます。

次に、URLの.extensionの部分を使用して画像形式を変更します。 ブラウザで以下を開きます。

http://localhost:8080/sig/aHR0cHM6Ly9pLmltZ3VyLmNvbS9LU0xENFZWLmpwZWc.png

これは読み込みに時間がかかります。ブラウザの開発者ツールでネットワークタブを見ると、この画像が元の画像よりもはるかに大きいことがわかります。サンプルの子犬の画像の場合、 pngのバージョンは15.84MBですが、元のjpgのバージョンはわずか1.3MBです。 クライアントが画像をリクエストすると、imgproxyが自動的にjpgからpngに変更しました。

Screenshot showing browser developer tools highlighting an image transfer size of 15.4MB

次のように、これらの形式の1つを指定子として追加することにより、この画像をより効率的な「次世代」形式webpまたはavifに変換することもできます。

http://localhost:8080/sig/aHR0cHM6Ly9pLmltZ3VyLmNvbS9LU0xENFZWLmpwZWc.webp

テストイメージのwebpバージョンは約600KBです。これは、元の.jpgと比較して約53% dの増加です。 ご覧のとおり、この画像形式を使用すると、画像のリクエストを高速化できます。

画像サイズを変更したり、さまざまな効果を追加したりすることもできます。 これを行うには、%processing_optionsパラメーターとしてさまざまな変更を指定します。 この場合、resizing_typefillを使用して、imgproxyに画像のサイズをsizeで幅200、高さ500ピクセルに変更するように依頼します。 (画像アスペクト比を維持し、その外側の画像の一部をトリミングします)、次にマスクサイズ5を使用してガウスぼかしを追加します。

ブラウザで次のURLにアクセスして、これを試してください。

http://localhost:8080/sig/size:200:500/resizing_type:fill/blur:5/aHR0cHM6Ly9pLmltZ3VyLmNvbS9LU0xENFZWLmpwZWc

そのURLをブラウザにロードすると、テスト画像のぼやけたサイズ変更されたバージョンが表示されます。

Screenshot of resized puppy image with blur effect

サイズ変更とぼかしに加えて、使用できるさまざまな変換があります。 その他のオプションについては、imgproxyドキュメントのURLの生成セクションを確認してください。

このステップでは、ソースイメージを提供および変更するためのimgproxyURLを生成しました。 次に、imgproxyを保護して、%signatureURLパラメーターを使用して特に許可したURLからのソース画像のみを提供するようにします。

ステップ3—署名でimgproxyを保護する

これまで、imgproxyURLの「署名」部分でプレースホルダーを使用してきました。 ただし、使用しているURLに署名することが重要です。 つまり、imgproxyが処理するURLは、秘密鍵と塩のペアを使用して作成したURLのみです。 これにより、悪意のあるユーザーがサーバーリソースを使い果たすのを防ぎます。 このステップでは、署名を要求するようにimgproxyを構成し、URLで使用する有効な署名を作成します。

imgproxy12ファクターアプリです。つまり、ファイルを変更せずに環境変数を使用して構成できます。 Dockerは環境変数を設定するための--envフラグを提供するため、これはDockerで実行する場合にうまく機能します。

署名を作成するには、最初にキーと塩のペアが必要です。これは、任意のランダムな16進文字列にすることができます。 コンピューター上のランダムデータを使用するか、オンラインランダムジェネレーターツールを使用してそれらを生成できます。 必ず64桁を指定し、少なくとも2つの文字列を生成します。1つはキー用、もう1つはソルト用です。

以下に示すように、64桁の2つの16進文字列を生成します。

Example Random Hex
49d5e2cd30d80fccc2e30877e4e58b2f0854a8dca6fb2e980b129171910080ed7ffa5dfbfde006e0c1a8ff52e7b5c614f0d3e9ec6e6ed754399fb0e2eb473c59

(文字列は上記とは異なることに注意してください。)次のコマンドで使用するために、生成された文字列を記録します。

次に、手順1で使用したのと同じコマンドを使用してDockerを再起動しますが、今回は2つの--envオプションを追加して、IMGPROXY_KEYおよびIMGPROXY_SALT環境変数をランダムに生成された値に設定します。実行中のコンテナー。 強調表示された部分を、以前に生成した文字列に置き換えます。

  1. docker run --env IMGPROXY_KEY=first-hex-result --env IMGPROXY_SALT=second-hex-result -p 8080:8080 -it darthsim/imgproxy

これで、Webブラウザでhttp://localhost:8080/sig/aHR0cHM6Ly9pLmltZ3VyLmNvbS9LU0xENFZWLmpwZWcを開くと、「403Forbidden」という応答が返されます。 Dockerログに次のようなエントリも表示されます。

Example Docker Log
WARNING [2021-12-28T03:12:34Z] Completed in 134.6µs /sig/aHR0cHM6Ly9pLmltZ3VyLmNvbS9LU0xENFZWLmpwZWc request_id=WQGTfRgeBXPvaQYHkNjab method=GET status=403 client_ip=172.17.0.1 error="Invalid signature"

そのログ行の最後にあるように、imgproxyは、imgproxyが期待するものと一致する署名を指定していないため、403エラーを返しました。 これは、署名を要求するようにimgproxyを正しく構成したことを意味します。 この署名照合は、共有秘密認証の例です。 imgproxyは、要求されたURLの SHA256 ハッシュに加えて、キーとソルトを使用して、内部で署名を計算しています。 同じ署名を生成するには、同じ秘密鍵とソルトを提供する必要があります。 つまり、IMGPROXY_KEYIMGPROXY_SALTの値を持つユーザーまたはプログラムのみが、imgproxyインスタンスが応答するURLを生成できます。 これらの値がないユーザーは、リソースを使用できません。

:キーまたはソルトの値をURLで直接指定しないでください。これは、Webページのソースで、または他のブラウザーやプロキシによってキャッシュされるときに公開されるためです。 そのため、SHA256のような「一方向」ハッシュを使用することが重要です。誰かがあなたの署名を取得してそれを使用して秘密を再作成する方法はありません。

最後に、有効なリクエストを行うには、imgproxyと同じ方法で有効な署名を生成する必要があります。 imgproxy Githubリポジトリで利用可能な署名アルゴリズム実装例は多数ありますが、このチュートリアルでは、Rubyスクリプトを使用します。

nanoまたはお気に入りのテキストエディタを使用して、signature.rbというファイルを作成します。

  1. nano signature.rb

以下の内容をファイルにコピーします。 キーと塩のペアを、以前に生成したIMGPROXY_KEYおよびIMGPROXY_SALTの値に置き換えます。 ローカルコンピューターではなくリモートサーバーを使用している場合は、localhostの出力を最後の行のサーバーIPに置き換える必要があります。

署名.rb
# adapted from https://github.com/imgproxy/imgproxy/blob/master/examples/signature.rb
require "openssl"
require "base64"

key = ["your IMGPROXY_KEY value"].pack("H*")
salt = ["your IMGPROXY_SALT value"].pack("H*")

url = "https://i.imgur.com/KSLD4VV.jpeg"

encoded_url = Base64.urlsafe_encode64(url).tr("=", "").scan(/.{1,16}/).join("/")

path = "/#{encoded_url}"

digest = OpenSSL::Digest.new("sha256")
hmac = Base64.urlsafe_encode64(OpenSSL::HMAC.digest(digest, key, "#{salt}#{path}")).tr("=", "")

signed_path = "/#{hmac}#{path}"

puts "Open http://localhost:8080#{signed_path} in a web browser"

まず、このコードブロックには、署名を生成するために必要なライブラリが必要です。 次に、.pack("H*")を使用してキーとソルト文字列を16進バイトに変換します。これは、OpenSSLライブラリが期待するものだからです(ここでパックとアンパックの詳細を読むことができます)。

次に、スクリプトは URL-safe Base64 が、手順1で行ったのと同様に、ソースURLをエンコードします。 ただし、この場合、Base64でパディングとして使用されている=文字もすべて削除され、/で16文字ごとに分割されます。 これらの変更により、見栄えの良いURLが生成されます。

スクリプトにエンコードされたソースURLが含まれると、SHA256アルゴリズム、秘密鍵、およびソルトを使用して署名が生成されます。 署名自体はBase64でエンコードされるため、結果のURLに安全に含めることができます。

最後に、スクリプトは、ブラウザで開くことができる署名付きURLを印刷します。

このスクリプトを実行して、署名されたURLを取得します。 ターミナルで別のタブを開くか、Ctrl+Cを押してimgproxyを停止し、スクリプトを実行してから、dockerコマンドを使用して再度開始します。

  1. ruby signature.rb

出力は次のようになります。

Output
Open http://localhost:8080/-CAkkjs5IioquivOi5LYyDnVxEULmPK-xIwIwXTleUA/aHR0cHM6Ly9pLmlt/Z3VyLmNvbS9LU0xE/NFZWLmpwZWc in a web browser

強調表示されている部分は、URLによって異なります。 これは署名されたURLであり、キーと塩のペアを知っているのは自分だけなので、自分だけが生成できます。 署名されたURLをブラウザで開くと、403ではなく期待される画像が表示されます。 後の手順で使用できるように、署名されたURLを必ず保存してください。

これで、署名付きURLを要求するようにimgproxyを構成し、有効な署名付きURLを生成しました。 これで、キーとソルトのペアにアクセスできる人またはプログラムのみがimgproxyと通信できます。

次に、imgproxyを構成して、可能な限り最新の画像形式を自動的に提供します。

ステップ4—「次世代」の画像を自動的に提供するようにimgproxyを構成する

これまで、画像を返すときに使用する形式をimgproxyに明示的に指示してきました。 もう1つのオプションは、imgproxyに可能な限り最も効率的な画像形式を使用するように指示することです。 これは、ユーザーのブラウザがサポートしている場合にのみ、「次世代」フォーマット(webpまたはavif)を提供できることを意味します。 それ以外の場合、imgproxyは自動的にpngまたはjpgにフォールバックします。

imgproxyは、Webブラウザがサポートされている画像形式の確認を要求したときに自動的に送信されるAcceptヘッダーを確認することでこれを行います。 MDN上の画像のデフォルトのAcceptヘッダーのリストを確認できます。 たとえば、Chromeは次を送信します。

image/avif,image/webp,image/apng,image/*,*/*;q=0.8

image/avifimage/webp(それぞれの画像形式の MIMEタイプ)の両方が存在するため、Chromeは両方の画像形式をサポートしていることがわかります。

AVIF / WebP Support Detection を使用して、サポートされている最新の画像形式を使用するようにimgproxyに指示できます。 これを有効にするには、IMGPROXY_ENABLE_WEBP_DETECTION環境変数をtrueに設定します。これは、docker runを呼び出すときに指定することで実行できます。

まず、Ctrl+Cを使用して、実行中のDockerイメージを停止します。 次に、次のコマンドを使用して再起動し、強調表示された部分をキーとソルトの値に置き換えてください。

  1. docker run --env IMGPROXY_ENABLE_WEBP_DETECTION=true --env IMGPROXY_KEY=your-key-from-Step-3 --env IMGPROXY_SALT=your-Salt-from-Step 3 -p 8080:8080 -it darthsim/imgproxy

これは、手順3で使用したのと同じdockerコマンドですが、webp検出を有効にするための--envフラグが追加されています。

設定を確認するには、ブラウザで画像を開きます。 手順3で生成した署名付きURLにアクセスします。これは次のようになります。

Example Image URL
http://localhost:8080/-CAkkjs5IioquivOi5LYyDnVxEULmPK-xIwIwXTleUA/aHR0cHM6Ly9pLmlt/Z3VyLmNvbS9LU0xE/NFZWLmpwZWc

webpの画像が提供されます。 これを確認するには、リクエストのサイズを確認してブラウザ開発ツールに入力するか、curlを使用します。 curlを使用して確認するには、ターミナルで新しいタブを開いてimgproxyが実行されたままになるようにし、次のコマンドを使用して、強調表示されたURLを独自のものに置き換えます。

  1. curl -s -v -H "Accept: image/avif,image/webp,image/apng,image/*,*/*;q=0.8" http://localhost:8080/M-RTYvp5xktEK9gG93hPwAB6on9aX7H5XciGsI3XSac/aHR0cHM6Ly9pLmlt/Z3VyLmNvbS9LU0xE/NFZWLmpwZWc

-sフラグは、プレーンテキストで出力する場合、画像があまり意味をなさないため、出力を無音にすることを意味します。 -vは冗長を意味するため、リクエストヘッダーとレスポンスヘッダーを確認できます。 最後に、-Hオプションは、AcceptヘッダーをChromeのヘッダーと同じになるように設定します。

出力は次のようになります。

Output
* Trying ::1:8080... * Connected to localhost (::1) port 8080 (#0) > GET /M-RTYvp5xktEK9gG93hPwAB6on9aX7H5XciGsI3XSac/aHR0cHM6Ly9pLmlt/Z3VyLmNvbS9LU0xE/NFZWLmpwZWc HTTP/1.1 > Host: localhost:8080 > User-Agent: curl/7.77.0 > Accept: image/avif,image/webp,image/apng,image/*,*/*;q=0.8 > * Mark bundle as not supporting multiuse < HTTP/1.1 200 OK < Cache-Control: max-age=3600, public < Content-Disposition: inline; filename="KSLD4VV.webp" < Content-Length: 601246 < Content-Type: image/webp < Expires: Tue, 28 Dec 2021 09:08:32 GMT < Server: imgproxy < Vary: Accept < X-Request-Id: I3sp-dXATs2AClfpHcSg- < Date: Tue, 28 Dec 2021 08:08:32 GMT < * Failure writing output to destination * Closing connection 0

Content-Typeヘッダーで、imgproxyがwebp画像を返したことがわかります。

必要に応じて、IMGPROXY_ENABLE_AVIF_DETECTION環境変数についても同じことができます。 これも同様に機能しますが、webpではなくavif形式の場合です。

この手順では、ユーザーのブラウザでサポートされている場合にのみ、次世代の画像形式を自動的に使用するようにimgproxyを構成しました。

結論

このチュートリアルでは、新しいイメージ形式と、Dockerを介してimgproxyを実行、構成、および保護する方法について学習しました。 また、imgproxyを構成して、webp画像を処理できるブラウザーを使用して任意のWebサイト訪問者に自動的に提供します。 これで、Dockerコンテナを自動的に実行して画像をオンザフライで変換し、 WebVitalsスコアを向上させる場所にimgproxyをデプロイする準備が整いました。

次のステップとして、imgproxyを使用して画像を処理するための追加オプションを調べることができます。 処理オプションの完全なリストは、製品ドキュメントで確認できます。 追加の構成オプションを調べることもできます。 最後に、imgproxyコンテナをDigitalOcean AppPlatformにデプロイできます。