Webアプリを創る

統計LODのバグから見える沖縄の歴史

2017年1月18日

統計LODの「統計に用いる標準地域コード」を使っていて面白いバグを見つけたのでメモしておきます。

沖縄県では、北部が本土に近いので国頭(くにがみ)郡で、南部はシマの尻となるので島尻郡です。それにもかかわらず、沖縄県の最も北に位置し東シナ海上に浮かぶ伊平屋村及び伊是名村は国頭郡ではなくて島尻郡に属しています。

伊平屋島は、琉球王朝の第一尚氏縁の地であり、伊是名島は、第二尚氏縁の地です。そのため琉球王朝の時代には直轄地だったという歴史があり、その後の経過を経て島尻郡に属するようになったようです。

具体的な統計 LOD のバグの内容ですが 検索用画面で以下の Query を実行してみてください。

PREFIX rdf:<http://www.w3.org/1999/02/22-rdf-syntax-ns#>
PREFIX rdfs:<http://www.w3.org/2000/01/rdf-schema#>
PREFIX org:<http://www.w3.org/ns/org#>
PREFIX dcterms:<http://purl.org/dc/terms/>
PREFIX sacs:<http://data.e-stat.go.jp/lod/terms/sacs#>
PREFIX sac:<http://data.e-stat.go.jp/lod/sac/>
PREFIX sace:<http://data.e-stat.go.jp/lod/sace/>
PREFIX sacr:<http://data.e-stat.go.jp/lod/sacr/>

SELECT ?p ?o WHERE { 
  sace:C394 ?p ?o .  
}

Output を Text にすると次のように検索結果が返されます。

-------------------------------------------------------------------------------------------------
| p                         | o                                                                 |
=============================================================================
| rdf:type                  | sacs:CodeChangeEvent                                              |
| org:resultingOrganization | sac:C47361-20020401                                               |
| org:resultingOrganization | sac:C47352-20020401                                               |
| org:resultingOrganization | sac:C47351-20020401                                               |
| dcterms:description       | "Nakazato-son(47351) and Gushikawa-son(47352) are merged, and Kumejima-cho(47361) in Shimajiri-gun is newly established."@en |
| dcterms:description       | "仲里村(47351)、具志川村(47352)が合併し、久米島町(47361)を新設"@ja|
| org:originalOrganization  | sac:C47352-20001222                                               |
| org:originalOrganization  | sac:C47361-19720607                                               |
| org:originalOrganization  | sac:C47351-20001222                                               |
| dcterms:identifier        | "394"                                                             |
| sacs:reasonForChange      | sacr:establishmentOfNewMunicipalityByMmerging                |
| dcterms:date              | "2002-04-01"^^<http://www.w3.org/2001/XMLSchema#date>       |
-------------------------------------------------------------------------------------------------

org:originalOrganization(変更前の期間つき標準地域コードのリソース)に、sac:C47361-19720607 があります。確かに 47361 は現在の久米島町の市町村コードではありますが、1972年には久米島町はなかったはずです。実は C47361-19720607 は、宮古郡城辺町(現宮古市)の昔の市町村コードです。47361 という市町村コードは使い回しされていたのです。

市町村コードは廃止されたコードは欠番とされ、新たなコードとして別の自治体に交付しないという原則に例外があったのです。恐らくそのことを知らないエンジニアが間違ったプログラムを書いたのですが、影響はあまりないのでそのままになっていると思います。

なぜこういうことが発生したのか、まず、1965年(昭和40年)の沖縄県の国勢調査の結果をみてみると、以下の図のように琉球政府の時代には郡は使わずに北部地区という名称を使っていたようです。

1965年の沖縄県の国勢調査のの画像

1970年(昭和45年)の沖縄県の国勢調査の結果をみると、復帰前ですが本土と同じように市町村コードが振られて郡が復活しています。その際に北部地区をそのまま国頭郡にしてしまったようです。

1970年の沖縄県の国勢調査のの画像

本土復帰後になって、伊平屋村及び伊是名村が島尻郡に属していることがわかり修正しようとしたのですが、運悪く島尻郡に属する町村が多すぎて伊是名村が47360というコードになり宮古郡のコードと衝突してしまったのです。それで宮古郡のコードを47370に変更し、宮古郡に属する町村のコードも変更せざるを得なくなったようです。そして、合併で久米島町ができたときに 47361 になり城辺町の昔のコードを使い回してしまったということのようです。

統計LODから取得した標準地域コードを利用して、統計メモ帳のページを追加中なので、完成したらそのページの方も紹介したいと思います。

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# はビジネス向けが強くて、科学計算に使っている人はそれほど多くないと思います。プログラム言語が沢山あるということは、その数だけ用途があるということだと思うので、用途によってその分野に強いプログラム言語を使うというのは仕方がないことのように思います。

Bootstrap 4 Alpha 6 を使ってみた

2017年1月11日

Bootstrap 4 Alpha 6 が1月6日に公開されました。今回のバージョンでかなり完成に近づいたと思うので、テスト的に使ってみました。次は、Beta になる予定で、残っている Issues の数もそれほど多くないので、基本的な所での変更はあまりないだろうと思っています。

自分の場合は、Visual Studio 2015 を使っていて ASP.NET Core で Web アプリケーションを作ることが多いので、最初に Visual Studio の ASP.NET Core Web Application のテンプレートを使って試してみました。

Bootstrap 4 に更新するためには、bower.json を以下のように変更します。bootstrap のバージョンを変更し、ツールチップの表示に Tether という JavaScript のライブラリーを利用するため、そのライブラリーを追加するだけです。

{
  "name": "asp.net",
  "private": true,
  "dependencies": {
    "bootstrap": "4.0.0-alpha.6",
    "jquery": "3.1.1",
    "jquery-validation": "1.14.0",
    "jquery-validation-unobtrusive": "3.2.6",
    "tether": "1.4.0"
  }
}

インストールするだけであれば簡単なのですが、実行させてみると完全にレイアウトが崩れてしまいます。Ver3 から Ver4 への変更は影響がかなり大きいことがわかります。

どのような修正が必要になるのかについては、そのテンプレートを、取りあえず動くようにしたものを GitHub の方に公開していますので参考にしてください。Navbar や Carousel にもかなり修正が入っています。

それで、公式ドキュメントを結構真面目に読んでいます。英語がそれほど得意でないので、前回のブログで紹介した「Google ウェブサイト翻訳ツール」プラグインをつけたドキュメントのページが結構役に立っています。

それらの作業をして、公式ドキュメントの Migrating to v4 を読んだところでは、Ver4 の特徴は、HTML5、CSS3 の機能をフルに活用するようになったということのようです。

自分としては、Bootstrap 4 は以前よりかなり良くなっていると思うので、積極的に使っていきたいです。でも、class 名が結構変わっているので、既存のWebサイトを Bootstrap 4 にバージョンアップしようとすると修正箇所が多くなるので、手間を考えると少し憂鬱ですが、時間をかけて更新していくつもりです。

なお、Ver4 の主な変更点は、以下のようなところです。

  • IE8 と IE9 のサポートの廃止
  • Flexbox によるレイアウト
  • Less から Sass へ
  • 標準のフォントサイズは 16px
  • 使う単位を px から rem に変更
  • ブレークポイントに576pxを追加

Bootstrap 4 のドキュメントに「ウェブサイト翻訳ツール」プラグインをつけてみた

2017年1月4日

Bootstrap 4 Alpha が最初に公開されたのは、昨年の8月19日です。それから1年4ヶ月を経過しましたが、まだ Alpha 5 という状況です。開発に非常に時間がかかっていますが、最近になってやっと開発が加速しているようなので、自分もテストに使い始めました。

Bootstrap 4 Alpha のドキュメントは当然英語しかないのですが、Google翻訳にニューラルネットワークが導入されて精度が大幅に上昇しているので、Bootstrap 4 のドキュメントに「ウェブサイト翻訳ツール」プラグインをつけてみました。Bootstrap のドキュメントは、GutHub で CREATIVE COMMONS で公開されているので、著作権の問題はないので以下の URL で公開していますので興味のある方は見てください。

http://bootstrap.pp22.net/

プラグインを追加するだけで、翻訳ができてしまうのは、素晴らしいです。

Google 翻訳は、まだまだ、おかしなところも多いですが、あれば便利というところまではなっていると思います。以前は使い物にならなかった機械翻訳ですが、やっと少しですが実用的になったようです。

Bootstrap 4 の日本語のドキュメントはBootstrap 4 日本語リファレンスという翻訳中のものがあるのですが、現時点では未翻訳の部分が多いし、Alpha 2 ベースです。ドキュメントの翻訳には時間と手間がかかるので、もう Google 翻訳で我慢してもいいように思います。

Bootstrap 4 の開発状況ですが、9月6日に、現行バージョンである Bootstrap 3 の定期的メンテナンスを終了するという発表(Getting to v4)がありました。この状況については、InfoQの「Bootstrap v3の定期的メンテナンスが終了」という記事が参考になると思います。

現在開発中の Alpha 6 では、Flexbox がデフォルトになり従来の float レイアウトはサポートされなくなります(v4 Alpha 6 ship list)。普通に使っているブラウザーで Flexbox をサポートしていないのは IE9 だけですが、IE9 は今年(2017年)の 4月11日に Windows Vista と共にサポート切れになります。

これらの件は、当初の計画とは違うものですが、開発者の負担の軽減ということでは仕方がないことだと思います。そして、Bootstrap 4 の 問題(Issues) が順調に解消され始めています。近いうちに Alpha 6 が公開され、その次は Beta です。完成も見えてきたように思います。

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 サポートはかなり進んできているという印象です。