[IIS]画像のフォーマットとサイズを変換して返すサービス


久しぶりに IIS についての記事を書きます。

先日、上司と話しをしていたら、Windows ストア アプリを作ってくださっているパートナーさんが、「IIS には画像のフォーマットやサイズを変換してくれるサービスがないのでわざわざ別のサーバーを立てている」との話を聞きました。

.NET の Bitmap クラスを使用したことがある人であればご存知の通り、画像のフォーマットの変換もサイズの変更も Bitmap クラスを使用すれは簡単に実現することができます。

IIS は .NET Framework で動作する ASP.NET のアプリケーションサーバーの機能を持っているので、その機能を組み込むことなどはわけはありません。

それこそ、鼻をほじりながらでも可能です。

ということで、今回は、私が実際に鼻をほじりながら(少しだけですが)作った、画像を変換するサービスのサンプルを紹介します。

 

IIS 7.x で画像のフォーマットとサイズを変換して返すサービスを作成する

今回の画像変換サービスを作成するにあたり、仕様は以下としました。

仕様

  1. 渡された URL の画像を取得し、加工した画像をレスポンスする
  2. パラメーターは元となる画像の 「URL」 と、変換する「画像フォーマット」、変換後の「幅」と「高さ」
  3. 幅、高さいずれかのパラメーターがない場合は画像のフォーマット変換のみ行う
  4. フォーマットが指定されていない場合は処理を行わない
  5. エラーの際にはなにもレスポンスを返さない (画像は抜けた状態となる)

開発は Visual Studio 2010、もしくは 2012 で行います。

プロジェクトは、シンプルにしたいので [Web サイト] で行い、ページを返す必要はないのでジェネリックハンドラを使用します。

具体的な手順は以下の通りです。

手順

  1. Visual Studio を起動
     
  2. メニュー [ファイル] – [新規作成] – [Web サイト] を選択
     
  3. [新しい Web サイト] ダイアログボックスが表示されるので、リストビューより [ASP.NET 空の Web サイト] を選択し [OK] ボタンをクリック
     
  4. [ソリューション エクスプローラー] から、作成された Web サイトを右クリックし、表示されたコンテキストメニューより [追加] – [新しい項目の追加] を選択
     
  5. [新しい項目の追加] ダイアログボックスのリストビューから [ジェネリックハンドラー] を選択し [追加] ボタンをクリック
     
  6. [ソリューション エクスプローラー] から、追加された拡張子 ashx のファイルをダブルクリック
     
  7. Visual Studio のテキスト エディタに ashx ファイルがオープンされるので、既定のコードを以下のように書き換え
     

    using System;
    using System.Collections.Generic;
    using System.Web;
    using System.Net;
    using System.Drawing;
    using System.Drawing.Imaging;
    using System.IO;

    //クラス名は作成した ashx の名前に合わせてください
    public class Handler : IHttpHandler {
       
        public void ProcessRequest (HttpContext context) {
            HttpRequest request = context.Request;
            HttpResponse response = context.Response;

            //変換する画像の URLを取得
            string imageURL = request.QueryString["u"];

            //変換する画像のフォーマットを取得
            string responseFormat = request.QueryString["f"];

            //クエリーストリングをイメージフォーマットに
            ImageFormat imgFormat = selectImageFormat(responseFormat);

            if (imgFormat == null || imageURL == "") return;
            //変換後の画像の幅を取得
            int responseImgWidth = nullToZero(request.QueryString["w"]);
            //変換後の画像の高さを取得
            int responseImgHeight = nullToZero(request.QueryString["h"]);

            try
            {
                byte[] buff = changeFormat(getImgStream(imageURL), responseImgWidth, responseImgHeight, imgFormat);
                response.ContentType = "image/" + responseFormat;
                response.Flush();
                response.BinaryWrite(buff);
                response.End();
            }
            catch(Exception ex)
            {
                //エラーが発生したらなにもしない。もうほんと、なにもしない。ぜんっぜんしない。
                return;
            }

        }

        //画像のフォーマットとサイズを変更する
        private byte[] changeFormat(Stream imgStrm, int imgWidth, int imgHeight, ImageFormat imgFormat)
        {
            Stream changedStrm;
            byte[] pixelByteArray;
            bool noSizeFlg = (imgWidth == 0 && imgHeight == 0);
            Bitmap bmp;

            if (noSizeFlg)
            {
                //変換する画像サイズが指定されていない場合
                bmp = new Bitmap(Image.FromStream(imgStrm));
            }
            else
            {
                //変換する画像サイズが指定されている場合
                bmp = new Bitmap(Image.FromStream(imgStrm), imgWidth, imgHeight);
            }

            changedStrm = new MemoryStream();
            bmp.Save(changedStrm, imgFormat);
            changedStrm.Position = 0;
            long strmLength = changedStrm.Length;
            pixelByteArray = new byte[long strmLength];
            changedStrm.Read(pixelByteArray, 0, (int)long strmLength);
            return pixelByteArray;
        }

        //クエリーストリングの内容から画像のフォーマットを設定する
        private ImageFormat selectImageFormat(string imgformat)
        {
            switch (imgformat)
            {
                case "jpg": return ImageFormat.Jpeg;
                case "gif": return ImageFormat.Gif;
                case "bmp": return ImageFormat.Bmp;
                case "png": return ImageFormat.Png;
                case "tiff": return ImageFormat.Tiff;
                case "icon": return ImageFormat.Icon;
                default: return null;
            }
        }

        private Stream getImgStream(string url)
        {
            //WebRequestの作成
            WebRequest webreq = WebRequest.Create(url);

            //タイムアウトの設定
            webreq.Timeout = 3000;

            //プロキシを探さないように
            webreq.Proxy = null;

            //サーバーからの応答を受信するためのWebResponseを取得
            HttpWebResponse webres = (HttpWebResponse)webreq.GetResponse();

            //応答データを受信するためのStreamを取得
            Stream strm = webres.GetResponseStream();

            return strm;

        }

        //null を 0 に変換
        private int nullToZero(string imgSize)
        {
            return (imgSize == null) ? 0 : int.Parse(imgSize);
        } 
       
        public bool IsReusable {
            get {
                return false;
            }
        }

    }

                        

  8. キーボードの [Ctrl] + [S] キーを押下して保存

 

以上でサービスは完成です。キーボード [F5] キーを押下して実行することができます。

ただし、画像を加工するためには、処理に使用する情報をサービスに渡す必要があります。

このサービスでは、必要情報を URL に含められたクエリーストリングとして情報を受け取ります。

使用するクエリーストリングは以下の通りです。

  • u – URL エンコードされた処理対象となる画像の URL (※)
  • f - 変換する画像のフォーマット (※)

    指定可能な画像フォーマットの内訳は以下のとおりです。

  • jpg   – Jpeg 形式
  • gif    – gif 形式
  • bmp – ビットマップ 形式
  • png – Png 形式
  • tiff – Tiff 形式
  • icon - アイコン形式
  • h - 変換後の画像の高さ
  • w - 変換後の画像の幅
  • ※ 印は必須

    たとえば、画像 http://www.contso.com/pic/test.jpg を高さ、幅ともに 100 ピクセルの png 画像に変換するには以下のようなクエリーストリングをリクエストします。

     

    ?url=http%3a%2f%2fwww.contso.com%2fpic%2ftest.jpg&f=png&w=100&h=100

     

    リクエストする URL 全体の例は以下の通りです。

    例)

    http://localhost/imgexcg/handler.ashx?url=http%3a%2f%2fwww.contso.com%2fpic%2ftest.jpg&f=png&w=100&h=100

     

    作成したコードの配置について

    作成した画像変換サービスを実際の IIS7.x に配置するには、コードを記述した拡張子 ashx ファイルを ASP.NET アプリケーションが動作するように設定した IIS の仮想フォルダにコピーするだけです。

    ASP.NET アプリケーションが動作するように仮想フォルダを設定する具体的な手順については、以下の第六章の内容をご参照ください。

     

    インターネット Web サーバー構築ガイドライン (ドラフト版)
    http://msdn.microsoft.com/ja-jp/ff625168

     

    今回は手順をシンプルにするために、ソースコードをそのまま配置する方式としていますが、ASP.NET アプリケーション用のプロジェクトテンプレートを使用すれば dll  にすることもできます。

    詳しい方法については、お近くの ASP.NET に詳しい人に訊いてください。

    なお、Visual Studio をお持ちでない人のために、前出のサンプルコードを記載済の ashx ファイルを SkyDrive にアップしましたので、プログラムが書けない人もお試しください。

     

    Windows 8 上の IIS 8 での実行について

    今回紹介したコードは Windows 7 上の IIS7.5 で正常に動作することを確認しておりますが、Windows 8 上の IIS8 の既定の設定では正常に動作しません。

    このコードは、当初 Windows 8 + Visual Studio 2012 で作り始めましたが、インターネットからのファイルの取得がうまくいきませんでした。

    いろいろ確認してみたところ、私の手元の環境 (Windows 8 × 2 台) では、ASP.NET プロセスから WebRequest、WebClient を使用した場合、クロスドメインのリクエストが飛ばないことがわかりました。

    これが会社で設定されているグループポリシーの影響なのか、インストールされているアプリケーションの組み合わせの問題なのか、はたまた Windows 8 上の IIS の新しい仕様なのか、現在はまだよくわかりません。

    ちなみに、同僚に試してもらったところ Windows Server 2012 上の IIS8 + ASP.NET ではクロスドメインの呼び出しは問題なくできるようです。

    この Windows 8 上の IIS8 からクロスドメインへのリクエストが出ない原因については、わかり次第この記事に追記したいと思います。

    ----- ・ ----- ・ ----- ・ ----- ・ ----- ・ -----・ ----- ・ -----

    今回が今年最後のブログのポストになります。

    このブログも ASP.NET → IIS → Web → Windows ストア アプリと、投稿する記事のテーマが変わっていますが、たまには今回のように振り返って IIS 関連の記事も良いですね。

    まだまだ皆さんにお伝えしたいネタがありますので、来年もよろしくお願いいたします。

    それでは良いお年を。

     

     

    Real Time Analytics

    Clicky

    Comments (0)

    Skip to main content