まだIT業界で消耗してるの? 😊ソフトウェア開発者のブログ

カテゴリー: プログラミング

.NET Core Tools 1.0 が公開されて思うこと

2017年3月10日

3月7日に Visual Studio 2017 が正式リリースされましたが、それと一緒に、やっと .NET Core Tools 1.0 が正式リリースされました。.NET Core RC1 が公開されたのは、2015年11月18日でした。その当時は、ASP.NET 5 のためのフレームワークという色彩が強かったのですが、その後にいろんな経緯があって Tools の部分は1年4ヶ月もかかって正式リリースされました。いろいろあったようですが結果的には良かったよぅな気がします。

ところで、Visual Studio は今年 20 周年を迎えたそうです。自分が始めて Visual Studio を使ったのは、Visual Web Developer 2005 Express Edition でした。ASP.NET Web Form のアプリが作れるのですが、Web Form は、Web アプリを作るツールであるのもかかわらず、HTML、CSS が使いづらく、Microsoft のコンポーネントを使わないといけないという全く不便なツールでした。

それで、もう使うのを止めようと思ったのですが、Express Edition を使っていても Standard Edition のアプウグレード版を購入できるというキャンペーンがあったので、Standard Edition を購入して、ASP.NET Web Form を使わずに Web サイトを作りました。それが、徳島の交通状況 Google Maps API 版で、ソースは GitHub の TKGMapに公開しています。

Visual Studio 2015 Standard のパッケージの写真

その後も Web Form には馴染めなかったので、Python を勉強し始めました。その時に丁度 ASP.NET MVC がアナウンスされたので、それを使って見たら結構使いやすかったのでそれをメインに使うようになりました。ASP.NET Core は RC の時から使っていますが、ASP.NET MVC の時も CTP から使っていました。

.NET Core 及び ASP.NET Core については、いろいろな評価がありますが、既存のユーザーをスムーズに移行させられるかということと新しいユーザーを獲得できるかということが重要だと思います。

既存ユーザーの件については、ライブラリーの .NET Core 対応が余り進んでいないという点からみると成功していないように思います。RC から RTM になるまで 1年4ヶ月もかかってしまった原因の一つになっていると思いますが、.NET Core 2.0 に期待したいと思います。.NET Core 2.0 では、.NET Statndard 2.0 対応となり、APIが現在の2倍以上にして.NET Framework や Xamarin との互換性がよくなるということです。過去のバージョンとの互換性はやはり必要だと思います。

新規ユーザーは従来より増えているというのは確かですが、どの程度伸びるかは今後に期待したいと思います。Web Form に馴染めなかった自分に取っては、ASP.NET Core は、以前の ASP.NET MVC よりもわかりやすくなって随分よくなったと感じています。特に、Web Form の影響が大きい System.Web が無くなったのはすっきりしたと思います。

市区町村コード表(履歴付)を作成する .NET Core アプリを公開しました

2017年2月20日

国勢調査等の市区町村別の統計データを過去から集計して比較しようとすると、平成の大合併により過去のデータは合併前の市町村のデータを集計する必要があり、普通に手作業ですると非常に手間のかかる作業になります。

政府統計の総合窓口(e-stat)の統計LODで、1970年以降の標標準地域コードのデータが公開されているので、それを利用して、任意の時点での市区町村コード表の作成や対応表が作成できるデータベースを作成するコンソールアプリケーションを作成しました。

.NET Core で作成したので、Windows だけでなく、Mac や Linux でも動作するし、.NET Core はインストールも手軽で簡単に動作させられるようになったので GitHub で公開してみました。URL は、以下のとおりです。

NAreaCode

このアプリを作っていて気がついた点を2点メモしておきます。

コマンドラインの解析

コマンドラインの解析には、Microsoft.Extensions.CommandLineUtils を使いました。CommandLineUtils は、.NET Core になってやっと組み込まれた機能です。

以前の Microsoft はエンタープライズ向けのことには熱心なのですがシンプルに処理をするということにはあまり力が入っていなかったように思っていましたが、.NET Core、ASP.NET Core になって、シンプルに作りたい場合にも使いやすくなったと思います。コマンドラインの解析は、簡単そうですが実際に自分で作るとなると結構手間がかかるので、.NET Core になって改善された典型的な例だと思います。

コード表の処理速度

作成した市区町村コードのデータは、json形式にしています。例えば、期間付き市区町村コードは以下のような形式になっていて、4588件のレコードがあります。

public class StandardAreaCode
{
    [JsonProperty("id")]
    public int Id { get; set; }
    public string 名称 { get; set; }
    public string ふりがな { get; set; }
    public string 英語名 { get; set; }
    public 自治体種別 種別 { get; set;}
    //北海道、東京都島嶼部、長崎県対馬(旧)は支庁・振興局、その他の町村は郡、区は政令指定都市のコード
    public int 所属 { get; set; }
    //北海道及び旧長崎県対馬支庁の町村のみ
    public string 郡名称 { get; set; }
    public string 郡ふりがな { get; set; }
    public DateTime 施行年月日 { get; set; }
    public DateTime 廃止年月日 { get; set; }
    public List<int> 施行データ { get; set; }
    public List<int> 廃止データ { get; set; }
}

これらのデータを Web アプリケーションの中で使う時に、RDB にインポートするのは面倒なので、メモリーベースで処理をするということで、処理時間を計測してみました。都道府県毎に市区町村コードの一覧を計算する処理で、画面の例は「北海道の市区町村コード一覧」です。また、一覧を計算するコードのメインは以下のようになっていて、LINQ を使うのであれば、リストや配列の場合でも SQL Server を使うのとほぼ同じコードで書くことができます。

public IEnumerable<(int id, string 名称, string ふりがな)> GetAreaCode(int pref, DateTime date) =>
    AreaCodeList
    .Where(x => x.Id / 1000 == pref && x.施行年月日 <= date && x.廃止年月日 > date && x.所属 != 99)
    .Select(x => (id: x.Id, 名称: x.名称, ふりがな: x.ふりがな))
    .OrderBy(x => x.id);

Google Compute Engine の n1-standard-2 (AWS の m4.large とほぼ同性能)を使って処理時間を計測すると以下のようになりました。

  • ファイルサイズ 1126KB
  • データの読み込み時間 45ms
  • 計算時間 0.4~0.008ms

結果としては、SQL Server Express を使うよりも遙かに早いです。計算時間の 0.4ミリ秒は初回の計算の時で計算ルーティンやデータが CPU のキャッシュメモリーに入っていない場合の処理時間です。計算の必要なコードとデータが CPU のキャッシュメモリにキャッシュされてしまえば 0.008ms という圧倒的な速さで処理がなされます。こういう処理ではデータのやりとりにばっかり時間を食っていて、本当の計算時間なんてごく僅かのものだなと感じます。

そういうことで、データー量がそれほど大きくないコード関係は RDB を使うのをやめてメモリーに乗せていこうと思っています。管理も、RDB で管理するよりも、テキストベースにして git で管理した方が遙かに管理しやすいし、入力もデータベース用の入力画面を使うよりも、テキストエディターや Excel で編集できた方が楽です。RDB をやめることの欠点ってといえば、コードを修正する毎にアプリをデプロイしなければいけないので、その手間が増えるのですが、昔と違って今はデプロイが楽になっているのでデータの更新の頻度ににもよりますがそれほどの負担にはならないと思います。

Visual Studio 2017 RC を使ってみた

2017年2月2日

Visual Studio 2017 RC のアップデートがあり、「.NET CoreおよびASP.NET Core のワークロードは、もはやプレビュー版ではない。.NET Core およびASP.NET Core の Toolのいくらかのバグを修正し、使いやすさを改善した」ということなので使ってみました。

.NET Core SDK の preview2 までは、プロジェクトファイルが project.json でしたが、Visual Studio 2017 では、MSBuild の .csproj 形式に変更になりました。それがどうなっているか知りたかったためです。

Visual Studio 2017 で project.json 形式のプロジェクトを開こうとすると、以下のように一方向のアップグレードというメッセージがでます。

アップグレードの画像

ここで、「OK」をクリックすると、.csproj 形式に変更されます。それでアップグレードは完了で、動作します。

ただし、このアップグレードは、完全ではないようなので、Project.json と MSBuild の .csproj 形式の対応を調べたい場合は、Hanselman 氏のブログにあったのですが、次の記事が参考になりました。

Project.json to MSBuild conversion guide

Project.json と .csproj のどちらがいいのかという議論はあると思うのですが、.csproj の方もかなり簡単になっているので、.csproj でいいのかなと思います。自分の場合は、今回の変更については問題はなかったです。

このサイトも、アップグレードして Visual Studio 2017 用にしました。Visual Studio 2017 の方は、バグらしきものはまだ残っていますが、Visual Studio 2015 と同じレベルで使えています。

ASP.NET Core アプリを Linux サーバーで公開」のページの方も.csproj 形式の方に更新しました。

Python を始めました

2017年1月14日

最近 Python を使い始めました。そうはいっても ASP.NET Core の代わりに Python を使うわけではありません。ASP.NET Core は、いいソフトだと思っているので、Web アプリの関係では、これからも使うつもりです。。

自分が .NET Framework ではなくて .NET Core を使う場合に困ることは、System.Drawing がないこと、Excelファイルを読み書きする NPI 等のライブラリーや数値計算用のライブラリーが .NET Core には対応していないことです。

これをどうするか考えていくと、こういう処理は C# ではなくて Python の方が得意なので、C# でライブラリーを探したり自分で作ったりするよりは、Python を使った方がベターだと思ったためです。

ところで、Python の方も Python 3 が出たのは、2008年だからもう8年も経っています。それでも Python 2 系使っているところが多いようです。確かに互換性がないとプログラムの修正は大変です。.NET Core の場合でもサードパーティのライブラリーの対応はなかなか進んでいません。.NET Core の場合は、.NET Core 2 で .NET Standard 2.0 に対応して、従来の .NET Framework と互換性がかなり良くなるようなので、どうなるのか楽しみですが、対応には時間がかかるというのが現実だと思います。

Python 2 と Python 3 のどちらを使うのかという点ですが、やっと殆どのライブラリーが Python 3 に対応したそうで、それに自分の場合は過去のしがらみがないので Python 3 にしました。

Python の環境構築には、miniconda を使いました。Python の環境構築の方法は何種類かありますが、科学計算ライブラリを使う場合は anaconda を使うと管理が楽で、デファクトになっているようです。

Python の IDE は、Visual Studio に Python Tools for Visual Studio をインストールしたものと Jetbrains 社の PyCharm の Commnity Edition を使っていて、どちらが使いやすいかテスト中です。PyCharm は、Web アプリを作らないのであれば、特にPro版を買わなくてもすみそうです。

Excel のファイルを操作するライブラリーには、openpyxl、xlsxwriter、xlrd、xlwt があって、一応 xls と xlsx の両方のファイルを読み書きできるようです。自分が必要なのは取りあえず、xls ファイルのデータを読み込む xlrd なので、それを試してみたら日本語も読むことができました。

現在は、「入門 Python 3」で勉強中です。他の言語が使えるのであれば、Python は1週間あればそれなりには使えるようになります。プログラム言語を複数使うのは、頭がこんがらがってくるので、したくはないのですが、C# はビジネス向けが強くて、科学計算に使っている人はそれほど多くないと思います。プログラム言語が沢山あるということは、その数だけ用途があるということだと思うので、用途によってその分野に強いプログラム言語を使うというのは仕方がないことのように思います。

ASP.NET Core でボットネット対策(その1 middlewareを使う)

2016年12月20日
DDoS 攻撃への対応

この記事は「ASP.NET Advent Calendar 2016 - Qiita」の20日目になります。

昨日のブログでは、ボットネットからアクセスがあることを書きましたが、その中で Google Cloud Datastore は自動スケールするので役に立ちそうだということを書きました。自動スケールされると破産するのではと心配する人もいると思いますが、Google Cloud では費用制限を設定するので破産することはないし、1秒間に1万回の読み込みをしても、1時間だと $21.6 とそれほど大したことはありません。

ただし、攻撃を放置しておくと結構な料金になってしまうので、攻撃を止める方法を考えてみました。

まず、DDoS 攻撃の現状がどうなっているかについては、Googleのシンクタンク部門である Google Ideas とセキュリティ企業の Arbor Networks と協力して、DDoS攻撃の状況をリアルタイムで表示するDigital Attack Mapを公開しています。そのサイトの Understanding DDoS によると、1週間小さな組織のシステムをダウンさせる能力があるボットネットを $150 で借りられるそうです。自分の場合は、最初はこの程度でできる攻撃への対応を考えてみようと思っています。

DDoS 攻撃への対応

攻撃を止める基本は、ボットのIPアドレスを特定して、そこからのアクセスを拒否することです。IPアドレスが特定できれば、アクセスの拒否については、ファイアーウォール、リバースプロキシーでも簡単にできます。そのため、DoS攻撃の場合は、比較的簡単に防御できます。

しかし、DDoS 攻撃の場合は、大量のボットから攻撃があるので、そもそも正常なアクセスなのか攻撃なのかを区別するのが容易でないし、攻撃元を特定して遮断していく作業は容易でありません。もし、攻撃元がボットをどんどん変更して攻撃してくれば、対応は本当に困難になります。

HTTP リクエストの場合、接続する URL 以外に IPアドレス、Referer、User-Agent の情報が送られてきます。低価格で借りたボットネットであれば、これらの情報に片寄りが生じるはずなので情報をうまく解析すれば、ボットのIPアドレスは特定できるはずです。

また、Pokémon GO で有名になったコイル警備員、すなわち Google の reCAPTCHA を使うことも有効です。アクセス拒否は、もし間違って拒否をするとそのユーザーを失うことになるので、疑わしいという状況では使いたくありません。でも、コイル警備員だとユーザー側の負担は遙かに軽いので、疑わしIPアドレスに対して使う事ができます。

Middleware を作成して特定のIPアドレスからのアクセスを拒否する

まず最初に、ASP.NET Core アプリケーションで、特定のIPアドレスからのアクセスを拒否するケースを考えてみます。

この場合には、HTTP Request があって、そのリクエストが MVC の処理に入るまでに処理をする必要があるため、Middleware を作成して処理する必要があります。ASP.NET Core の Middleware の Document に、middleware pipelineの図(下図)がありますが、

middleware pipeline の画像

その図をみれば、どう処理をしたらいいかをイメージできると思います。標準の MVC Middleware コンポーネントの前にカスタム Middleware コンポーネントを作成して処理をすることになります。

Middleware の作成に Visual Studio を使う場合は、「ミドルウェア クラス」のテンプレートがあるのでそれを使うと便利です。それを少し変更して非同期にすると下のようなコードになります。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
namespace MyApp
{
    // You may need to install the Microsoft.AspNetCore.Http.Abstractions package into your project
    public class MyMiddleware
    {
        private readonly RequestDelegate _next;
        public MyMiddleware(RequestDelegate next)
        {
            _next = next;
        }
        public async Task Invoke(HttpContext httpContext)
        {
            await _next(httpContext);
        }
    }
    // Extension method used to add the middleware to the HTTP request pipeline.
    public static class MyMiddlewareExtensions
    {
        public static IApplicationBuilder UseMyMiddleware(this IApplicationBuilder builder)
        {
            return builder.UseMiddleware<MyMiddleware>();
        }
    }
}

このコードと middleware pipeline の図を眺めていると大まかな使い方は理解できました。Invoke メソッドの、await _next(httpContext) が次のパイプラインを呼び出す処理で、その前後にコードを書くことで独自の処理を行わせることができます。

以下は、上のコードを修正して、特定のIPアドレスからのアクセスを拒否するようにしたサンプルです。

    public class AntiBotMiddleware
    {
        private readonly RequestDelegate _next;
        //拒否するアドレスの指定
        private readonly List<string> _blackIpAddress = new List<string> {"192.168.0.1", "192.168.0.2"};
        public AntiBotMiddleware(RequestDelegate next)
        {
            _next = next;
        }
        public async Task Invoke(HttpContext httpContext)
        {
            //IPアドレスの取得
            string remoteIpAddress = httpContext.Connection.RemoteIpAddress.ToString();
            if (_blackIpAddress.Contains(remoteIpAddress))
            {
                //403 を返す                
                httpContext.Response.StatusCode = 403;
                return;
            }
            await _next(httpContext);
        }
    }

_next を呼ばなければ、次のパイプラインに行かずに返ります。それで、ブラウザーでは「サーバーエラー 403アクセスが拒否されました」と表示されるようになります。

この middleware を実行させるためには、Startup.cs の Configure メソッドに登録する必要があります。

        public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
        {
            loggerFactory.AddConsole(Configuration.GetSection("Logging"));
            loggerFactory.AddDebug();
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
                app.UseBrowserLink();
            }
            else
            {
                app.UseExceptionHandler("/Home/Error");
            }
            app.UseStaticFiles();
            app.UseAntiBotMiddleware();
            app.UseMvc(routes =>
            {
                routes.MapRoute(
                    name: "default",
                    template: "{controller=Home}/{action=Index}/{id?}");
            });
        }

ここで、注意すべきことは、登録の順番が重要になってくるということです。今回の middleware は、その性格からいって UseMVC より前に置く必要があります。そして、UseStaticFiles の前におけば、静的ファイルもアクセス拒否の対象になり、後ろに置けば静的ファイルを除いたものがアクセス拒否の対象になります。

このサンプルは、プロキシーサーバーを使っている場合はうまく動作しません。httpContext.Connection.RemoteIpAddress が、プロキシーサーバーのアドレスになるためです。以下は、Nginx をプロキシーサーバーに使っている場合のサンプルです。

        public async Task Invoke(HttpContext httpContext)
        {
            //IPアドレスの取得
            string remoteIpAddress = GetRequestIP(httpContext);
            if (_blackIpAddress.Contains(remoteIpAddress))
            {
                //403 を返す                
                httpContext.Response.StatusCode = 403;
                return;
            }
            await _next(httpContext);
        }
        private static string GetRequestIP(HttpContext httpContext)
        {
            string ip = GetHeaderValueAs("X-Forwarded-For", httpContext);
            if (string.IsNullOrWhiteSpace(ip))
                return  httpContext.Connection.RemoteIpAddress.ToString();
            return ip;
        }
        private static string GetHeaderValueAs(string headerName, HttpContext httpContext)
        {
            StringValues values;
            if (httpContext?.Request?.Headers?.TryGetValue(headerName, out values) ?? false)
            {
                return values[0];
            }
            return null;
        }

なお、このサンプルが動作するためには、Nginx側で、X-Forwarded-For の設定をしておく必要があります。

proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

Dependency Injection を使う

上のサンプルで不便なのは、拒否するアドレスの指定を初期化で行っているところです。拒否するアドレスは画面で検索できたり変更できた方がいいですね。その場合には、Dependency Injection(DI)を使うのが自然です。DI という名前を聞くと難しそうに思うのし、解説を読んでも難しいのですが、以下のように簡単に作って試してみたら動作しました。

まず、普通に Class を作ります。(正式には、最初に Interface を作るようです)

public class AntiBotServise
{
    public List<string> BlackIpAddress { get; set; }
    public AntiBotServise()
    {
        BlackIpAddress = new List<string> {"192.168.0.1", "192.168.0.2"};
    }
}

すべてのリクエストで共通に使用するので、Startup.cs の ConfigureServices で AddSingleton を使って DIサービスコンテナに登録します。

public void ConfigureServices(IServiceCollection services)
{
   services.AddSingleton<AntiBotServise>();
   // Add framework services.
   services.AddMvc();
}

Middleware の方で、次のように Injection します。

public class AntiBotMiddleware
{
    private readonly RequestDelegate _next;
    private readonly AntiBotServise _antiBotServise;
    public AntiBotMiddleware(RequestDelegate next, AntiBotServise antiBotServise)
    {
        _next = next;
        _antiBotServise = antiBotServise;
    }
    public async Task Invoke(HttpContext httpContext)
    {
        //IPアドレスの取得
        string remoteIpAddress = GetRequestIP(httpContext);
        if (_antiBotServise.BlackIpAddress.Contains(remoteIpAddress))
        {
            //403 を返す
            httpContext.Response.StatusCode = 403;
            return;
        }
        await _next(httpContext);
    }
}

Controller や View でも、同様に Injection できるので、画面で検索できたり変更したりできるようになります。

補足

長くなってきたので、middleware で eCAPTCHA を使う話は次回にします。

ASP.NET Advent Calendar では、Azure の話が多いようです。確かにエンタープライズ向けのクラウドでは Azure がベストだと思います。

しかし、DDoS の件を調べていると Google の名前がたびたび出てきます。Google は、公開用 Web サーバーが有力な収益源の一つなので、その関係のサービスは、Google の方がはるかに充実しています。また、.NET Foundation に加盟したことでもわかるように、Google Cloud Platform での .NET サポートはかなり進んできているという印象です。