博多 旨鮨 小野

お祝いで寿司を食べることになったので、木の葉モールにある『博多 旨鮨 小野』に行ってきた。木の葉モールはファミリー向けのショピングモールで、その中の飲食店も子供連れで入りやすくて重宝している。

お昼に行ったけど、お祝いの席でお得なランチメニューを注文するのは興が削がれる気がしたので、 ランチにしてはちょっとだけお高めの『極厳選握り寿司十一貫』を注文。

茶碗蒸しは海鮮の出汁が効いた優しい味で良かった。子供もパクパク食べてた。

f:id:griefworker:20170720221204p:plain

お吸い物。

f:id:griefworker:20170720221229p:plain

そしてメインの寿司。 どれも美味かった。

f:id:griefworker:20170720221341p:plain

イクラ苦手だったけど克服したかも。

f:id:griefworker:20170720222118p:plain

f:id:griefworker:20170720222148p:plain

f:id:griefworker:20170720222744p:plain

f:id:griefworker:20170720222805p:plain

小野グループなだけあって味は期待通りだった。 とにかく小さい子供を連れて入りやすいのがポイント高いので、 お祝いで寿司でもという話になったら、 当分はファーストチョイスだろうな。

博多旨鮨 小野

食べログ 博多旨鮨 小野

『星野、目をつぶって。(1)~(6)』を読んだ

週刊少年マガジンで連載中の『星野、目をつぶって。』をKindleでまとめ買いして読んだ。「クラス一の日陰者・小早川が、 クラス一の人気者・星野海咲の素顔を知ったことがきっかけで、 メイクの依頼を請け負うことになる」というあらすじのマンガ。

ヒロインの星野は後先考えずに行動してメイクを崩すので、メイクを担当している小早川は必然的に巻き込まれながらも、クラスメイトや他の生徒の悩みを解決していく。 本作は一応、ラブコメに分類されているのかな。でも前述のように、お悩み解決の話が多かったので、ラブコメというよりも学園ものというか、青春ものというか、そんな色が強いように思う。ラブコメブコメしてない。

星野に巻き込まれる形で生徒の悩み解決をしてきた小早川だが、最近では星野の協力を得て自分自身を変えるための話にシフトしてきた。ラブコメなんで誰と結ばれるかも気になるが、それと同じくらいに小早川含む主要な登場人物がどう変わっていくのか楽しみ。

余談だけど、最初悪役的な立場で出てきた不良の加納、登場人物の中で一番ハイスペックではないだろうか。 不良な点を除けば、学力は学年8位だし、家事得意だし、 運動能力も星野ほどでは無いがかなり高い。 登場時の印象は最低だったけど、 小早川との因縁が絡んだ怒涛の連続イベントで手のひらクルってなった。

ASP.NET Core MVC で Basic 認証

先日 ASP.NET Core で Basic 認証を行うサンプルを書いた。

tnakamura.hatenablog.com

ただ、これだと全てのパスで認証が必要になってしまう。 もともとやりたかったことは、 「ASP.NET Core MVC で Authorized 属性を付けたアクションだけを認証必須にすること」。

Microsoft.AspNetCore.Authentication をベースに Basic 認証ミドルウェアを作れば実現できそうだったけど、既に同じことをやっている人がいた。

github.com

NuGet でパッケージが公開されているので、使わせてもらうことにした。

using idunno.Authentication;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http.Authentication;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using System.IO;
using System.Security.Claims;
using System.Threading.Tasks;

namespace BasicAuthExample
{
    public class Program
    {
        public static void Main(string[] args)
        {
            var host = new WebHostBuilder()
                .UseKestrel()
                .UseContentRoot(Directory.GetCurrentDirectory())
                .UseIISIntegration()
                .UseStartup<Startup>()
                .UseApplicationInsights()
                .Build();

            host.Run();
        }
    }

    public class Startup
    {
        public Startup(IHostingEnvironment env)
        {
            var builder = new ConfigurationBuilder()
                .SetBasePath(env.ContentRootPath)
                .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
                .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
                .AddEnvironmentVariables();
            Configuration = builder.Build();
        }

        public IConfigurationRoot Configuration { get; }

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc();
        }

        public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
        {
            loggerFactory.AddConsole(Configuration.GetSection("Logging"));
            loggerFactory.AddDebug();

            // Basic 認証を使用する
            app.UseBasicAuthentication(new BasicAuthenticationOptions()
            {
                Realm = "BasicAuthExample",
                Events = new BasicAuthenticationEvents()
                {
                    OnValidateCredentials = context =>
                    {
                        // ユーザー名とパスワードはとりあえず固定
                        if (context.Username == "tnakamura" &&
                            context.Password == "test12345")
                        {
                            var claims = new Claim[]
                            {
                                new Claim(ClaimTypes.Name, context.Username),
                            };
                            var identity = new ClaimsIdentity(claims, context.Options.AuthenticationScheme);
                            var principal = new ClaimsPrincipal(identity);
                            context.Ticket = new AuthenticationTicket(
                                principal,
                                new AuthenticationProperties(),
                                context.Options.AuthenticationScheme);
                        }
                        return Task.FromResult(true);
                    },
                }
            });

            app.UseMvcWithDefaultRoute();
        }
    }

    public class HomeController : Controller
    {
        [HttpGet("/")]
        public string Index()
        {
            return "Hello World!";
        }

        // アクセスすると認証ダイアログが表示される
        [Authorize]
        [HttpGet("/hello")]
        public string Hello()
        {
            return $"Hello {User.Identity.Name}!";
        }
    }
}

これで、Authorize 属性が付いたアクションの呼び出しに Basic 認証が効くようになった。 今回もユーザー名とパスワードは固定なので、ここはデータベースから取得した方が良いだろう。

OnValidateCredentials に登録するデリゲートでは、context.HttpContext.RequestServices を使って DI コンテナに登録した DbContext や自作リポジトリなんかを取得できるので、実装するのは難しくないはず。

記念撮影

BUMP OF CHICKEN の新曲が iTunes Store で配信されていたので購入。 YouTube の公式チャンネルで PV が公開された通知が Gmail に届いて知ることになるとは。

テレビを全く見なくなったので知らなかったが、 どうやらカップヌードルCM 「HUNGRY DAYS 魔女の宅急便 篇」で起用されているらしい。 ちょっとセンチな気分にさせるメロディと歌詞だった。 夏祭りの終わりのような、 夏休みの終わりのような、 いや、青春時代の終わりのような。 この曲も聞けば聞くほど良くなっていく。 1曲リピートがしばらく止まりそうにない。

記念撮影

記念撮影

『WEB+DB PRESS Vol.99』を読んだ

特集1『良いコードって何だろう?』

良いコードかどうか判断し説明できる能力に「絶対良感」という名前をつけたのは面白い。 著名なライブラリやフレームワークをお手本するのも良いアイデアだと思う。 周りがお手本にできるコードを書いているとは限らないし。 OSSソースコードを読む習慣がつけば理想。 DRY にする目安の3アウトチェンジのルールも、基準が明快で導入しやすいと思う。

特集2『UIテスト自動化』

iOS には XCTest、 Android には Espresso という公式の UI 自動テストツールが提供されていて、 しかもここまで使えるシロモノになっているとは。 ちょっと見ない間にテスト環境も進化していた。 Appium は知っていて、いつか導入しようと思っていたけど、重い腰がなかなか上がらなかった。 標準で提供されているなら始めやすい。 もはや UI 自動テストを書かない理由が無いな。

特集3『実践Kubernetes』

Docker でWebサービスの本番運用を検討していて、Kubernetes を使うかもしれないので調べないといけないな、 と思っていたからタイムリーだった。 Kubernetes は Google Container Engine が抽象化してくれるとはいえ、 アプリケーションエンジニアが扱うのは難しそうな印象。 Azure App Service や Elastic Beanstalk でも Docker コンテナをデプロイできるんで、自分の場合はこれらの PaaS を使う方がよさそうだ。

WEB+DB PRESS Vol.99

WEB+DB PRESS Vol.99

  • 作者: ?橋健一,谷口禎英,井本大登,山崎勝平,大和田純,内村元樹,坂東昌哉,平田敏之,牧大輔,板敷康洋,大?浩崇,穴井宏幸,原口宗悟,久田真寛,ふしはらかん,のざきひろふみ,うらがみ,ひげぽん,池田拓司,はまちや2,竹原,片田雄樹,渋江一晃,WEB+DB PRESS編集部編
  • 出版社/メーカー: 技術評論社
  • 発売日: 2017/06/24
  • メディア: 大型本
  • この商品を含むブログを見る

ASP.NET Core で Basic 認証

ASP.NET Core で Basic 認証を行うサンプルを書いてみた。ユーザーは固定なので、本番で使うときは ASP.NET Core Identity みたいに、ユーザー情報をストアから取得するようにしたほうがいいだろうな。

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using System;
using System.IO;
using System.Security.Claims;
using System.Text;
using System.Threading.Tasks;

namespace BasicAuthExample
{
    // Basic 認証ミドルウェア
    public class BasicAuthenticationMiddleware
    {
        const string USER_NAME = "tnakamura";

        const string PASSWORD = "test12345";

        readonly RequestDelegate _next;

        public BasicAuthenticationMiddleware(RequestDelegate next)
        {
            _next = next;
        }

        public async Task Invoke(HttpContext context)
        {
            // Basic 認証のヘッダー
            // Authorization: Basic <userName:password を Base64 エンコードした文字列>
            // からユーザー名とパスワードを取り出してチェックする
            string header = context.Request.Headers["Authorization"];
            if (header != null && header.StartsWith("Basic"))
            {
                var encodedCredentials = header.Substring("Basic".Length).Trim();
                var credentials = Encoding.UTF8.GetString(Convert.FromBase64String(encodedCredentials));
                var separatorIndex = credentials.IndexOf(':');
                var userName = credentials.Substring(0, separatorIndex);
                var password = credentials.Substring(separatorIndex + 1);

                if (userName == USER_NAME && password == PASSWORD)
                {
                    // コンテキストにユーザー情報をセットする
                    var claims = new[]
                    {
                        new Claim(ClaimTypes.Name, userName),
                        new Claim(ClaimTypes.Role, "User")
                    };
                    var identity = new ClaimsIdentity(claims, "Basic");
                    context.User = new ClaimsPrincipal(identity);

                    await _next(context);
                    return;
                }
            }

            // ブラウザの認証ダイアログを出すには、レスポンスヘッダーに
            // WWW-Authenticate: Baic が必要
            context.Response.Headers["WWW-Authenticate"] = "Basic";
            context.Response.StatusCode = 401;
        }
    }

    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
        }

        public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
        {
            loggerFactory.AddConsole();

            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseMiddleware<BasicAuthenticationMiddleware>();

            app.Run(async (context) =>
            {
                await context.Response.WriteAsync($"Hello {context.User.Identity.Name}!");
            });
        }
    }

    public class Program
    {
        public static void Main(string[] args)
        {
            var host = new WebHostBuilder()
                .UseKestrel()
                .UseContentRoot(Directory.GetCurrentDirectory())
                .UseIISIntegration()
                .UseStartup<Startup>()
                .Build();

            host.Run();
        }
    }
}

Azure App Service で Spring Boot 製 Web アプリケーションの Blue-Green Deployment を行う

Azure App Service で Spring Boot 製 Web アプリケーションを動かすことに成功した。

tnakamura.hatenablog.com

ただし、このままだと新しいバージョンをデプロイするときにサービスを停止しないといけない。実行中に App Service Editor で jar ファイルを更新しようとしたが、ステータスコード 409 のエラーになった。停止すれば更新できた。

開発中でも毎回サービスを停止してデプロイするのは手間だし、万が一動かなかったときはツライ。ステージング環境にデプロイして動作確認してから、本番環境と入れ替えたいので、Blue-Green Deployment をやることにした。

デプロイメントスロットを作成

名前は staging にしておく。 一から設定したくないので、既存の環境をコピーする。

staging にデプロイ

作戦した staging デプロイメントスロットでも App Service Editor は使える。 jar ファイルと web.config を wwwroot 直下にアップロードし、staging を再起動する。

あとは、staging で動作確認したら、スワップを実行して本番環境と入れ替えればいい。