2日目、よろしくお願いします。発起人でして、この団体の紹介や設立の背景から始めようと思います。AI部とはソフトバンク株式会社には福利厚生の一環としてクラブ活動をサポートする制度があります。社内には多数の部活がありますが、大きく分けてスポーツ系、文化系のジャンルがあります。スポーツ系はイメージしやすいかと思いますが、バスケやフットサルなど、いわゆるクラブ活動感があるラインナップとなっています。文化系にはテクノロジー企業らしく、ロボットやIoTを扱うPepper部やドローンレースに参加する団体などがあったりします。専用のSlackワークスペースには2019年12月2日現在568名(アクティブメンバーは160名くらい)のソフトバンク関連企業のメンバーが参加しています。メンバー同士のシャッフルランチなどなどが行われています。いう社内認定制度があります。Japanの黒帯制度を参考に設立された制度で、社内に散らばっている技術に強い人にスポット当てるものです。😃😄😊万円(見せられないよ!)の個人予算が割り当てられ、国際会議やイベントへの参加費や渡航費に用いたり、イベント開催の費用に用いたり、ハイスペックなPCを買ったり、何かしらの発注を行ったりできるわけです。ロボット制御などの分野でそれぞれ認定されている方がいます。Meister認定される方が出てくるかもしれません。Meisterの人々にはある程度社内外発信などの貢献が求められます。AI部を立ち上げたのです。設立以前から全社を上げて"AI"を推進していくというスローガンが掲げられていました。エンジニアのみならず、経理・事務等バックオフィス系の方々や営業の方々もそれぞれ独自のAI施策を企画・実行していて非常に盛り上がっています。それぞれがそれぞれで頑張っていると多様性が出て色々独創的なアイデアが創出されます。これは素晴らしいことなのですが、どんな分野でも初学者は得てして同じようなところでつまずいたり、疑問を持ったりするものですよね。そこで、部署横断的にAI関連の話題を扱えるコミュニティスペースを作ったわけです。現在部長の座は、学生時代全能アーキテクチャー若手の会副理事をやっていた松岡さんに譲っており、彼がまた精力的に部を盛り上げてくれています。Twitterなどで有名なコミさんも春から社員となり、きっとまた部の熱量が上がっていくことでしょう。機械学習モデルのデプロイ導入部分を書いていたら熱中してしまいつい長くなりました。近年のディープラーニングの発展には目を見張るものがあります。コンピュータービジョンや自然言語処理、レコメンデーション、自動運転、はたまたゲームの世界などの幅広い領域においてディープラーニングが応用され、華々しい研究成果が毎日のようにニュースとして流れてきています。ベースの開発フレームワークの存在もこの成功の一翼を担ってるかと思います。ディープラーニングのモデルを本番環境へデプロイする際の方法論にはまだ確固たるものはなく、実務者の方々がそれぞれ試行錯誤しているのが現状です。実務者のためのアプローチ」の監訳者前書きの一説です。(ステマ)近年におけるディープラーニングなど機械学習分野の発達はそれはもう本当に凄まじいことになっており、個人で最新論文を追うのはもはや不可能なレベルになっています。開発フレームワークとしてはGoogleが開発したTensorFlowが長らく圧倒的人気を誇っていたのですが、FaceBookが開発したPyTorchというフレームワークのユーザーが急増してきて、現在TensorFlowとPyTorchが二大ライブラリとなっています。https://www.oreilly.com/ideas/one-simple-graphic-researchers-love-pytorch-and-tensorflowただ注意したいのは、ディープラーニングとかいわゆるAIの文脈ではしばしば研究分野と産業分野がごっちゃに語られていることです。PyTorchは学術研究分野やイノベーター・アーリーアダプター達に広く用いられていますが、産業分野ではまだまだTensorFlowに軍配が上がるようです1。研究者は試行錯誤のしやすさを重視し、産業ではパフォーマンスが重視されると述べています。モデルのデプロイにおける要件について以下のように述べています。etc.企業のシステムをPythonで動かしている事例はまだ全体に比べると少なく、iOSやAndroidアプリのバイナリにPythonインタープリターを組み込むことはできないし、ダウンタイムなしにモデルを切り替えたり、バッチ推論したいという事情があります。Extended)といったサービスで対応しているのですね。そもそも「デプロイ」とは関係あるのかないのか微妙なところですが、学生の頃、捻くれていたので「ソリューション」って言葉が苦手でした。就活時に企業研究としていろんな会社のホームページを見ていると「○○のソリューション」みたいな何だかよくわからない何も言っていない気がする言葉が並んでいて気色悪く感じたものです。今はもう慣れてむしろソリューションとしか表現できないなって気持ちになっているのですが、「デプロイ」も初めに聞いたとき何のこっちゃよくわかりませんでした。要するに「システムを利用可能な状態にすること」ですね。Javaで開発したWebアプリケーションをパックしたJARファイルをサーバーに配置することをデプロイと呼んだりするって感じです。こと機械学習モデルにおいては以下の三つのアプローチがよく用いられています。指定したモデルを読み込んで利用するコードを直接メインのアプリケーションの中に記述する。APIなど)としてモデルを配布する。モデルの更新を独立して個別にリリース、バージョニングできる。予測ごとにリモートの呼び出しが必要なので、レイテンシーが発生する可能性あり。2番目のアプローチと同様にモデルを独立に扱う。メインのアプリケーションの実行時にデータとしてモデルをsubscribeする。新しいバージョンのモデルがリリースされたらそのモデルをメモリに読み込んで利用する。メインのシステムがJavaで記述されていて、そこに機械学習モデルをデプロイするケースを考えます。1のアプローチが右図で、2のアプローチが左図にあたります。1のアプローチでは例えばDeeplearning4jなどJavaで開発できるフレームワークで最初からモデル構築したりデータフローを作ったりします。suiteのSameDiffを用いたりしてJavaのオブジェクトに変換します。モデルがやってくれることはテンソルに変換されたデータを受け取り、推論をし、結果のテンソルを出力することです。その前後に必要な前処理や後処理は再度Javaで記述し直す必要があります。開発の分業のしやすさやモジュール化(マイクロサービス化)による柔軟性の利点から、感覚的には2のアプローチがよく用いられている気がします。APIなどを経由して生データを渡し、結果を受け取ることだけで、前処理や後処理は外部サービスに押し込めることができます。API作成のサポートをしてくれる「ソリューション」のひとつがKonduit-Servingです。中心に開発されているJavaベースのオープンソースの機械学習モデルデプロイツールのことです。APIとしてデプロイすることができます。面白いのはPythonコードそのものをデプロイできる点です。これについては後述します。Konduit-Servingは主に以下の3つのライブラリをベースに開発されています。イベントドリブンアプリ開発フレームワークJavaCPPによりJavaでもCUDAによるGPUアクセラレーションの恩恵を受けることができ、TensorFlowやNumpyなどPython界隈で広く用いられているライブラリをJava上で用いることができます2。importの機能を利用しています。Vert.xベースにノンブロッキングで効率的なスケール性のあるAPIを構築できます。SDKが用意されているため、機械学習エンジニアにも安心です。その他流行りのコンテナオーケストレーターKubernetes統合もサポートされており、PrometheusとGrafanaと連携してパフォーマンスや様々なメトリックのモニタリングも簡単に行うことができます。Java上の組み込みPythonインタープリターを利用してPythonコードそのままデプロイ上記の「Pythonコードをそのままデプロイ」の意味を解説します。Konduit-ServingにはPythonインタープリターが内包されています。binary.」と書いたばかりですが、工夫すれば何とかなるのです。Pythonコードに含まれているオブジェクトはメモリに展開され、そのポインターに直接アクセスすることでゼロコピーでJavaからPythonコードを扱えるようになります。私も詳しいところは理解していないのですが、とにかくJava上でPythonを扱うことができるのです。利用方法としてはKonduit-ServingでデプロイされたPythonコードに対して、HTTPのクライアントから入力データを送信します。すると同名の変数のポインタが書き換えられ、組み込みのPythonが実行され、結果を格納した変数の中身をクライアントに返します。これの何が嬉しいかというと、機械学習エンジニアの方が実験に用いたコードをプロダクションに流用できるので、コード書き直しの工数を減らすことができるというのと、任意のPythonライブラリを利用できるメリットがあります。(勘がいい方はお気づきかもしれませんが、Pythonを用いるとGILに囚われてしまい、せっかくのJavaのマルチスレッド性を失ってしまいます。トレードオフということですね。Konduit-Servingを用いてモデルをそのままデプロイする場合はマルチスレッドで高パフォーマンスを発揮できます。顔認識モデルのデプロイここではPyTorchで開発された顔認識モデルを用いた推論コードをKonduit-Servingでデプロイするデモを紹介します。Konduit-ServingのセットアップそれではKonduit-Servingのセットアップから初めてみましょう。(1.8)、Javaのビルド管理ツールのMavenが必要です。Anacondaなどを用いて環境を切ります。Pythonのバージョンは3.7(以上?)が推奨されています。SDKをインストールします。jarファイルを作成します。1.2.1の部分はバージョンによります。ドキュメントには~/.konduit/konduit-serving/konduit.jarにコピーが生成されると書いてありますが、私の環境ではできませんでした。(すぐ修正されるでしょう。)以下ではドキュメントに沿った解説をします。jarのパスを環境変数KONDUIT_JAR_PATHに設定します。するこれで準備完了です。API化サンプルが置いてあるディレクトリに移動します。python/examples/face_detection_pytorchここで用いるモデルはPytorchで開発された非常に軽量な顔認識モデルです。コードをクローンし、依存ライブラリをインストールします。./Ultra-Light-Fast-Generic-Face-Detector-1MB/requirements.txtこちらのリポジトリで公開されている推論コードはこちらです。boxを描画した画像を出力します。今回のサンプルではもう少し単純なものになっています。インプットはBase64エンコードされた画像データ、アウトプットは画像から検出された顔の数とします。上記の推論コードをサンプル用に修正し、簡略化したものがこちらです。str(len(boxes))大事なのは下記の部分です。str(len(boxes))imageがクライアントから送信される入力で、num_boxesが出力です。こちらをKonduit-Servingでデプロイし、クライアントからリクエストを送ってみます。run_server.pyうまく動作すれば、長々とログやデバッグメッセージが表示されたあと以下のように顔の数(この場合51)が返されます。中身は以下の通りです。"num_boxes"print(predicted['num_boxes'])server.stop()今回はBase64エンコードされた画像(image)をJSONで送って顔の数(num_boxes)をJSONに入れて返すというデモでした。そのためPythonConfigで入出力の型を'STR'と指定しています。ServingConfigではフォーマットを'JSON'にしています。encoded_image})の行でエンコードした画像を'image'という文字列をセットにしてサーバーにPOSTしています。返ってきた結果のJsonがpredicted変数に格納されます。print(predicted['num_boxes'])の出力が先ほどの51だったのです。おわりにいかがでしたでしょうか。Konduit-Servingはまだ公開されて間もないライブラリですので、変更が多かったりややドキュメントに不整合があったりしますが、デプロイ支援ツールとして可能性があると思っています。今後また解説記事など公開していくつもりなので乞うご期待。余談ですが、OpenCVのJavaラッパーであるJavaCVはJavaCPPを用いて移植されています。JavaCPPを用いて移植されたパッケージのプリセットたちはここで公開されています。2019の19日目の記事になります。(@blue_islands)です。ひとり開発をしていて、自分の作品とかを何かにまとめたりしていますか?よく見かけるポートフォリオは、ただ作品の完成物を載せているものが多く、実際の苦労話とか、ドヤリングしたい場所など、事細かく乗せることができないものがほとんどです。そこで、自分が開発をしているCHROCO(クロコ)というサービスを作ったのですが、その時の開発の苦労話が少しでもほかの開発者の参考になればと思っています。この記事は、自分の作品たちをまとめるサービスを作った時の戦いの綴りです。CHROCOとは最初に、今回作ったWebサービスであるCHROCOについて先に紹介しておきます。CHROCOでは、「ふつうのポートフォリオでは語れないストーリーを見せる」を実現しています。制作の過程をタイムラインで見せるアイデアを実現した方法、勉強会や発表で使ったスライド、ボツになった企画書、小さな成果や工夫、そういったものをストーリーとして並べることで、制作時の想いや記録をタイムラインに残すことができます。想い入れの強いストーリーは、掘り下げて深みを見せる1つの作品のストーリーの中にさらにストーリーを作ることができ、ストーリーの中のもう一つのストーリーといったことが実現できます。noteに作った時の経緯とか、コンセプトをまとめてあります。note「ストーリーで語るポートフォリオのWebサービス「CHROCO」を作った話」アーキテクチャサービスのアーキテクチャについては、こんなものを利用しています。利用したものも多くいろいろと苦労しています。ま、その話は後の部分で。その他Auth0今回こそ、モダンなフロントエンドにしようと思っていたのですが、thymeleafを使ったあたりから、javascritptはjQueryに走ってしまい、ちょっとクラシックな構成になってしまいました。利用例が少ない点では、Neo4j、Auth0とかなのですかね。CSSはBulmaフレームワークが最近のお気に入りです。苦労した点やはり、苦労した点で行くと、今までつかったことのない、「Neo4j」「Auth0」の扱いでかなり苦労しました。Neo4jで苦労した点Neo4jとはNeo4jはJavaで実装された、オープンソースの最も人気のあるグラフデータベースです。Neo4jというのを使ったのだが、Qiitaとか、日本での記事がなく、英語ドキュメントで苦労した。Neo4jのユニークキーの扱いNeo4jのIDは連番であるが、データ削除時の空き番は再利用されるため、ユニークキーとして利用してはいけない。これは、作っている最中にわかったので、データ構造を再設計する羽目になった。呼び出すことが出来ます。bootの連携のサンプルは下の画像のようにたくさんそろっているのだが、thymeleafのサンプルではなくJSPのサンプルのため、置き換え作業にちょっと悩まされてしまった。Securityのサンプルを利用したため、そもそも触ったことがなかったので、仕組みを理解するのに手間がかかってしまった。おわりにここまで、はじめて使ったものが多くだいぶ苦労してしまったので、きっちりと備忘録として、Qiitaに記事を残しておかないと忘れてしまいそうですね。自分はサービスを作るときは1つ2つは、使ったことのない技術を利用するようにしているのですが、やはり、初めてで実装レベルのものを作るときは参考になるものが少なく、はまりポイントになるので、そこら辺は本当にまとめておきたいと思いました。BootにはTomcatが付いており、Java自体がJVMで動くのでローカルで作業出来るのですが、Javaから別のツールの操作を行うためVagrant上に環境を作成しました。Devtoolsを入れていれば、ホットリロードが出来るので、快適に動かせました。参考https://knasmueller.net/spring-boot-development-environment-virtualboxTweetQiitaで続きを読む投稿日:2019-12-02T17:09:23+09:00CentOSでJDK、Tomcatのバージョンを変更するはじめにこの間やったので、やり方メモ。参考サイト様とほぼ同じ内容。以下の手順でJDK、Tomcatのバージョンを変更します。javacjavaのバージョンを確認インストールしたバージョンになっていたらOKです。新しいバージョンのTomcatをインストールインストールしたいバージョンのTomcatをダウンロードするここではTomcat8をダウンロード。"));}出力がコンソールなのが安っぽいですが空白区切りで複数単語のOR検索まぁ検索処理もちょっと適当なのでisMatchメソッドのとこはあんま突っ込まないで下さい。Bootでログイン機能のテストコードを書いてみる概要いつぞやに書いた記事の続きです。ログイン関連の処理のテストコードを書く際にも色々とハマったので、備忘録として残しておきます。ログイン処理自体についての解説はこちらに記載しているので、よろしければ。間違い等ございましたら、やさしく教えて頂けるとうれしいです(╹◡╹)ゴールこの記事を読むことで、(きっと)以下のことが分かるようになります。環境・コード環境は以下となります。依存ライブラリ等は前回の記事と同一です。ソースコードはGitHubに置いてあります。全体像ログインアプリは、以下の機能を持つシンプルなものです。権限ごとのトップ画面(管理者・ユーザ)ユーザ登録処理これをざっくりと図で表すと図1のようになります。機能自体はシンプルなものですが、あれやこれやを考慮していると、パッケージが増えていき、全体像が掴みづらくなってしまうので、役割単位でふんわりと分類したものを図2に示します。パッケージ全体像テストしたいことコード化するにしろ、しないにしろ、テストを行う上で最も意識しておく必要があるのは、「何を検証したいか」を明確にすることです。今回は、SpringSecurityへ定義した機能が想定した通りに動作しているかということになります。これでは堅苦しくて少し雰囲気が掴みづらいので、もう少しとっつきやすい形にしてしまいましょう。図3のように、SpringSecurityさんが頼んだ通りに頑張ってくれているかとすると、イメージがしやすくなるかと思います。字が汚いので重要なことなので、上図をリストでも表しておきましょう。知らない人は通さないか(認証)知ってる人は通してくれるか(認証)ユーザはユーザページへ案内してくれるか(認可)管理者は管理者ページへ案内してくれるか(認可)以下では、これらが正しく動作しているかを検証するためのテストコードの書き方について見ていきます。ログイン処理まずはシンプルにログイン画面から始めていきます。画面のイメージは、図4のようになります。ログイン画面ログイン処理が正しく動作できているかを検証するためには、以下をテストする必要があります。DBに存在するユーザであれば、ログインできるDBに存在するユーザだが、パスワードが間違っている場合、ログインできないそもそもユーザがDBに存在しない場合、ログインできないこれは、言い換えると、SpringSecurityが想定通りの人だけを通してくれるのかと表すことができます。それでは、実際にSpringSecurityさんが仕事をしてくれるか、検証していきます。知ってる人は通してくれるまずは、単純なケースとして、DB上に存在するユーザでログイン処理を行うと、ログインができるかを検証します。SpringSecurityは、ログイン処理として、以下を実行します。一致した場合、SuccessHandlerを呼び出す処理自体のコードの解説は別記事で行なっているので、ここでは割愛し、早速テストコードを見ていきます。}}解説テストコードの書き方自体は前回の記事でざっくりと書いてきたので、ここでは、新しく登場した要素について触れていきます。formLoginmockMvcのperfromメソッドへ渡す引数として、formLoginなるものが指定されています。ログイン処理はお馴染みのものなので、雰囲気で動作はイメージできるかと思いますが、ここで大事なのは、以下のことです。引数にはSpringSecurityのConfigクラスのloginProcessingUrlを指定CSRFトークンはformLoginメソッドによって自動的に指定されるログイン処理がある程度理解できていれば、特につまずくことはないかと思います。公式さんに書かれている通り、SpringSecurityは、ログインの成功/失敗時に指定したURLへリダイレクトさせるよう動作します。よって、ここでは、リダイレクト先のURLが想定通りかを検証します。後ほど触れていきますが、ここでは、ログインが上手くいったらユーザのトップ画面っぽいURLへ飛ばされるんだなー、ぐらいを理解して頂けたら十分です。知らない人は通さない上記のテストコードで、何やら、SpringSecurityはユーザを通してくれることが分かりました。これだけでは、もしかしたら誰でもウェルカムながばがばセキュリティである可能性が残ってしまっています。ここでは、セキュリティが担保できている、すなわち、通して欲しくない人を通さないかを検証します。以下では、対象のテストコードを抜粋して記載します。正常系と大きな違いはないのですが、リダイレクト先が/?errorとなっていることがポイントです。これが正しく動作していれば、ログイン可能なユーザがログインできることだけでなく、ログインできないはずのユーザがログインに失敗することが検証できます。これで最低限のレベルではありますが、ログイン処理についてテストコードを書くことができました。DaoやService層については、前回の記事と重なるものなので、割愛させて頂いています。興味がございましたら、ソースコードをご参照頂ければと思います。Webアプリケーションはログインして終わり...ではなく、ログインした状態で様々な画面を利用していきます。ログインが必要な画面のテストコードに毎回ログインして、それから画面のテストコードを書いて...としてしまうと、非常に面倒です。これを解決するため、ログイン済みの状態を手動で作ることができれば、とても便利です。それでは、ログイン後の画面のテストコードを見ていく前に、ログイン済みの状態のつくり方に触れていきます。ログインした状態のつくり方さて、ログインした状態をつくれば良い、と書きましたが、具体的には何をすればよいのでしょうか。前処理として、セッションIDを発行して、紐づくユーザオブジェクトを作成しておき、SpringSecurityが扱える形にあれこれして...とSpringSecurityが行なっている処理を一つ一つ再現しても作ることはできますが、ちょっとしんどそうです。実際には、公式で紹介されている通り、アノテーションで処理をちょろっと書くだけでログイン済みの状態を擬似的に作成することができます。ここで、ログインした状態は、正確には、SecurityContextと呼ばれています。コンテキストについての説明は、ここが分かりやすいかと思います。急に話が抽象的になってしまいました。イメージしやすくするため、コンテキストを簡単に図で表したものを図5に示します。セキュリティコンテキストコンテキストがあることによって、画面でかちかちログインしたときと同じ状態をコードによってつくり出すことができます。実際にコンテキストをつくる為のコードを見ていきます。ログイン済みユーザ(仮)まずは、中身に入る前に、テストコードの中でどのようにログイン済みの状態が記述されているか、見てみましょう。これから触れていくコードを書いておくことによって、どのようにテストコードが書きやすくなるか、メリットを先に知っておくと、理解もしやすくなるかと思います。}重要なのは、WithMockCustomUserアノテーションです。パラメータとして、ユーザ名・パスワードを渡す。これだけでログイン済みユーザとして、ログインが必要な画面のテストコードを実行することができます。このアノテーションはカスタムアノテーションで、作るのに少し頑張らないといけないものですが、一度つくってしまえば、ログインが必要なアプリケーションで使い回すことができます。楽をするために頑張って見てみましょう。アノテーションのコードまずは先ほど書かれていた、WithMockCustomUserアノテーションのコードです。カスタムアノテーションについては、こちらをば。password();}解説アノテーション自体は、2つのフィールドを持つシンプルなものなのですが、WithSecurityContextなるすごく長いパラメータを持つアノテーションが難しそうな雰囲気を漂わせています。これは、SpringSecurityContextを定義するためのアノテーションです。こないので、言い方を変えると、SpringSecurityさんに、あらかじめ「この人は覚えておいてね!!」という情報を覚えてもらうためのメモのようなものです。WithMockCustomUserでは、名前の通り、ユーザ情報しか定義していないので、続いて、コンテキストを作成している処理を見てみます。コンテキストの工場コンテキストの工場と言われてもなんのことやらさっぱりなので、実際のコードを見てみることにしましょう。コードは書いてありますが、やっていることはごくシンプルで、ログイン済みの状態すなわちSecurityContextを作っています。個々の処理としては大まかに以下のことをおこなっています。SecurityContextHolderをもとに空のSecurityContextを生成ユーザ名・パスワードによる認証トークンを生成トークンをもとに認証処理を行い、認証情報を格納したオブジェクトをコンテキストに設定SecurityContextHolderは、コンテキストを管理するためのオブジェクトで、スレッド周りのあれこれを管理していたりするのですが、本筋から外れてしまうので、今回は割愛致します。コンテキストを作る大元なんだなーぐらいに思って頂ければ問題ないかと思います。大事なこととして、ここでの認証トークンは、クッキーとよく対比されたり、APIトークンなどと呼ばれるものとは全くの別物だということです。Token」の方が意味的に近いかと思われます。話がそれてしまいましたが、カスタムアノテーションを定義することにより、テストコード上では、ユーザ名・パスワードを指定するだけで簡単にログイン済みの状態、すなわち、SecurityContextを作れました。大変でしたが、ログイン済みの状態が作れるようになったので、ログインが必要な画面のテストコードはぐっと楽に書けるようになります。ログインが必要な画面の例として、ユーザ用トップ画面を見てみます。画面イメージを図6に示します。ユーザ用トップ画面ユーザに挨拶しているだけの単純な画面です。サンプル用の画面なので、デザインは...気にしないでください。今回はログイン関連の処理を検証することが目的なので、この画面については、以下のことが想定した通りに動作しているかを検証していきます。ログイン済みユーザの場合、画面遷移が可能か未ログインユーザの場合、画面遷移ができないか管理者権限でログインした場合、ユーザ用画面へ遷移できないかログアウト処理が有効かおおむねログイン画面と同様、悪いことはできないかを軸に検証すれば良さそうですね。実際のテストコードを見ていきます。知ってる人を案内してくれるかログイン処理と同様、最初は正規ルートでの動作を見ていきます。先ほど紹介したアノテーションが増えたぐらいなので、すっと理解できるかと思います。}}解説WithMockCustomUserについては、これまで触れてきた通りで、ユーザ名・パスワードを指定することで、ログイン済みの状態を作り出しています。ここで、WithSecurityContextTestExecutionListenerという何やら関連がありそうなクラスがTestExecutionListenersに指定されていることが分かります。これは、TestContextManagerがSecurityContextを扱えるようにするための設定です。コンテキストのためのコンテキストと書くと少しややこしくなってしまうので、ここでも図で整理してみましょう。TestContextManagerを図でざっくりと表したものを図7に示します。TestContextManagerコンテキスト以外にも前処理・後処理等も担っているのですが、ここで重要なのは、テストの実行に必要なコンテキストを管理している点です。イメージは掴みづらいかとは思いますが、テストを実行する前に事前にログインした状態をつくっておくためには、必要な処理となるので、意識しておくとよいかと思います。悪だくみを防げるか続いて、悪いことをたくらんでいる人を防げるかを見ていきます。ここで、整理するため、悪いことが何を表しているか、もう一度見てみます。未ログインユーザがユーザトップ画面へURLを直接入力して覗く権限を持たないユーザがユーザトップ画面へURLを直接入力して覗く上記を防ぐための実装自体はSpringSecurityのConfigクラスで定義しているので、ここでは、本当に防げるかどうかをテスト的なリクエストを送信することで確かめてみます。}解説ポイントとなるのは、悪事を防いだらどうするのか、です。ログインしていないユーザの場合、ログイン画面へリダイレクトして欲しいので、ステータスコードがリダイレクト(3始まり)であること、遷移先がログイン画面であることを記述しています。権限を持たないユーザの場合(管理者は見ることができた方が自然っぽいですが...)、ステータスコードとして403Forbiddenが返ってくることを記述しています。これらのテストコードにより、SpringSecurityさんは想定していた仕事は問題なくこなしてくれていることが分かりました。最後に後処理として、ログアウトについても、見ておきたいと思います。}解説ログアウト処理も、SpringSecurityで設定したログアウト用のURLへPOSTリクエストを送信しているだけなので、雰囲気で理解できるかと思います。formLoginメソッドと異なり、通常のPOSTリクエストでは、CSRFトークンは付与してくれません。よって、wirh(SecurityMockMvcRequestPostProcessors.csrf)メソッドでCSRFトークンをリクエストに追加する必要があります。これだけでも、ログアウト処理が動いていることの検証には問題なさそうですが、もう一歩踏み込んでみましょう。ログアウトしたということは、ログインしていない状態へと変化したとも言えます。よって、ログアウト後には、ログインが必要な画面へは、遷移できないことが正しい動作です。これについても、念のため見ておきたいと思います。いっても、難しいことはしておらず、単にログアウト後に画面遷移処理を追加してみただけです。図9を見て頂ければログアウト処理によってログインしていない状態が作り出されたことが分かります。ログアウト後は再度ログインを求められる少し長くなってしまいましたが、これでログイン処理についてもテストコードで検証することができました。まとめに入る前に、ログイン処理とは少しずれてしまいますが、それなりにはまったユーザ登録処理について、補足として簡単に触れておきたいと思います。ユーザ登録処理さてさて、ユーザ登録処理についてですが、処理自体は、SecurityContextを作ることと、ほぼ同じです。実装についてはソースコードをば。ここで取り上げたいのは、バリデーション処理です。動きのイメージとしては、図10のようなものとなります。ユーザ登録バリデーション処理のテストコードの書き方について、色々とはまってしまったので、備忘録としてポイントを書いていきます。書いてありますが、やっていることは値を設定してPOSTリクエストを手動で送信しているだけです。ここで、重要なのは、attributeHasFieldErrorCodeメソッドです。errorを渡しています。これらは、それぞれ、以下の対応関係を持っています。fieldNameについては、MVCアプリを触ったことがあれば、イメージはわくかと思います。errorについては、フィールドを実際に見て頂くとわかるかと思います。rawPassword;}ここで、errorとして引数に渡されたものが、アノテーションの名前と対応しています。指定されているアノテーションは、カスタムアノテーションで、Constraintアノテーションが付与されているので、制約アノテーションとして振る舞います。制約違反が存在すると、BindingResultがエラーとして検知してくれるので、バリデーション処理で扱えるようになります。詳細については、公式を見て頂くのがよいかと思います。相関チェックについては理解しきれておらず、パスワード合致のテストコードとかで心が折れてしまったので、お詳しい方がいらっしゃったら、情報を頂けるとうれしいです。(:まとめログイン処理についても、ざっくりとではありますが、テストコードを記述し、動作が検証できるようになりました。前回の記事で簡単なCRUD、今回の記事でWebアプリで重要なログイン処理を見ていくことで、簡単なアプリケーションであれば、しっかりテストコードを書きながら開発ができるのではないかと思います。コードを書いていると、超いい感じのリファクタリングを突然思いついて、コードをあれこれ変えたくなりますが、こうやってテストコードを書いていれば、検証した振る舞いが崩れていないかはボタン一発で検証できるようになります。他にも、毎回ログインして、画面をかちかちしてエラー画面を出して...としなくても、効率よく、かつ再現性を担保してテストが実行できるようになります。テストコードの書き方を習得するにはコストがそれなりにかかってしまいますが、楽しいコードを書く時間が更に増えてくれるので、楽しみながら少しずつ取り入れていくのがよいかなと。エラそうに言っていますが、私もテストコードに関してはまだまだ勉強中の身なので、頑張ってもっと色々勉強したいです。TweetQiitaで続きを読む投稿日:2019-12-02T02:00:43+09:00OkHttpを使った場合にWebViewのJavascriptInterfaceはどう呼ばれるか軽く調べてみたAndroidのWebViewでは、addJavascriptInterfaceという、JavaオブジェクトをWebViewに埋め込む機能があります。これにより、WebView内で表示しているWebページに埋め込まれたJavascriptから、当該Javaオブジェクトで定義されたメソッドを呼び出せるようになります。今回は、WebViewでのHTTP通信にOkHttpを利用した時、WebViewのコールバックメソッドはどう呼ばれるか軽く調べてみました。読むのがめんどくさい方は最後の「まとめ」だけを読んでいただいてもよいです。WebViewを使用するActivityの実装WebViewを使用するActivityのソースコードはこんな感じです。サンプルコードがお粗末なのはご愛嬌です。各コールバックが呼ばれるタイミングでLogを出力し、どのようにHTTP通信が制御されているのか確認サーバ側の実装サーバ側の実装は以下です。今回はクライアントからrootのエンドポイントにアクセスされることを前提としています。通信フローまとめ上記のWebサーバのrootエンドポイントアクセスした際の通信フローをまとめると、以下のような感じになります。"submitFormByJs"<form>タグにより、"getJsBySrc"エンドポイントへのPOSTリクエストを送出するhtmlを応答。WebViewがこのHTMLを解釈するときに、上記POSTリクエストが実行される。"getJsBySrc"<script>タグの実行により、エンドポイント"doHttpReqFromJsCode"へのGETリクエストを行うhtmlを応答。"doHttpReqFromJsCode.js"XMLHttpReqeustにより、エンドポイント"fromXMLHttpReq"へHTTP通信を行うJavascriptを応答。上記通信が成功したときに、location.hrefによりエンドポイント"onReceivedXHRResAndSetLocationHref"へGET通信によるリダイレクトをさせる。"fromXMLHttpReq"doHttpReqFromJsCode.js内の、XMLHttpRequest実行によりアクセスされる。""onReceivedXHRResAndSetLocationHref"doHttpReqFromJsCode.jsによるXMLHttpRequest通信が成功したときアクセスされる。</html>ログ出力結果WebViewでloadUrl("http://${server_ip}:10000/")した際のログ出力は以下です。※192.168.100.151は筆者のローカルマシンのIPアドレスです。http://192.168.100.151:10000/onReceivedXHRResAndSetLocationHrefOkHttp通信の箇所のLog出力結果は以下。http://192.168.100.151:10000/favicon.icoまとめ上記結果をまとめてみます。(あくまで筆者のローカル環境で検証した結果のため、挙動に差異が出る可能性あり。src="...">で発生するHTTP通信HTTP通信によるJavascript取得(GETリクエストで"xxx.js"にアクセス)JavascriptでのXMLHttpRequestでのHTTP通信HTML内でのlocation.href変更によるリダイレクトHTTPステータス302によるリダイレクトではWebViewのshouldInterceptRequestは呼ばれなかった。OkHttpは(デフォルトでは)HTTPステータス302リダイレクトをよしなにハンドリングして、レスポンスを取得するため、rootエンドポイントからのリダイレクト先"submitFormByJs"に対して、WebViewのshouldInterceptRequestは上記の実装では呼ばれない。src="...">で発生するHTTP通信HTTP通信によるJavascript取得(GETリクエストで"xxx.js"にアクセス)JavascriptでのXMLHttpRequestでのHTTP通信HTML内でのlocation.href変更によるリダイレクト上記の実装では、XMLHttpRequest通信でアクセスする"fromXMLHttpReq"でのJavascriptInterfaceメソッド呼び出しは実行されなかった。レスポンスをWebViewで表示しようとすれば呼ばれるかもしれませんが、時間なかったので調べてません。Native間のデータ連携はセキュリティリスクが伴うので、なるべくやりたくない。・上記の通り、リダイレクトが絡むJavascriptInterfaceの実行制御はトリッキーな実装になりがちなので、既存のWebサービスがあるからといって、WebAPIの作成を怠って、安易にJavascriptInterfaceを採用するべきではないでしょう。WebViewの主目的はWebコンテンツを表示することのはずです。WebAPI代わりに使ったら、無駄なHTML評価や画像取得とか色々無駄な処理が走っちゃいますね。無駄な処理を省くには結局、スクラッチレベルでHTMLを解析し、必要な処理だけ通信を行う処理を書く必要がありそうです(極力、スマホアプリで使用する専用のWebAPIを作りましょう)。・WebViewは文字通りViewの一種なので、単純なWebAPIのように、任意のタイミングでHTTP通信実行とか、複数画面にまたがるデータ保持とか難しい。AndroidシステムのWindowにアタッチした状態でないと、WebViewによるHTMLの評価は走らないのではないでしょうか(よく知りませんが)。AndroidコンポーネントであるServiceクラスにWebViewを定義してHTMLの評価ができるみたいな情報もありますが、できたとしてもバグが出そうですね。商用のアプリでは採用すべきではないでしょう。謎回路とはわたしのことです。本を読み返していると、finalどころじゃなく、忘れてることが多い。JVM君の絵を真似することで、心を落ち着ける。本物のかわいらしさが出ない・・・。ちなみに双子のようなやつに、コンパイラ君もいる。実行結果バイトコードにするのがコンパイラ君なので、機能としてはコンパイラ君のほうが好きだ。あとは練習問題をやった。明日はeclipseをダウンロードしよう。ここは「〇〇をした」の1行でもいいから、続けることが大切。謎回路とはわたしのことです。本を読み返していると、finalどころじゃなく、忘れてることが多い。JVM君の絵を真似することで、心を落ち着ける。本物のかわいらしさが出ない・・・。ちなみに双子のようなやつに、コンパイラ君もいる。実行結果バイトコードにするのがコンパイラ君なので、機能としてはコンパイラ君のほうが好きだ。あとは練習問題をやった。明日はeclipseをダウンロードしよう。ここは「〇〇をした」の1行でもいいから、続けることが大切。全ての要素に対するインターフェースを用意しておき、その要素に関する処理を訪問者にやってもらうというパターンだと理解しています。目的あるオブジェクト構造上の要素で実行されるオペレーションを表現する。パターンにより、オペレーションを加えるオブジェクトのクラスに変更を加えずに、新しいオペレーションを定義することができるようになる。用意された材料を使って料理を作るプログラムを実装します。println("${e.name()}の皮を剥いて切って鍋で煮込みます。println("${e.name()}の芽をとって皮を剥いて切って鍋で煮込みます。println("${e.name()}を切って鍋で煮込みます。println("カレーのルーと${e.name()}を鍋に入れて煮込みます。println("まだ料理中です。}}[out-put]じゃがいもの皮を剥いて切って鍋で煮込みます。にんじんの芽をとって皮を剥いて切って鍋で煮込みます。豚肉を切って鍋で煮込みます。カレーのルーと塩と胡椒を鍋に入れて煮込みます。カレーの完成!野菜炒めを作りたくなったのでFriedVegetablesVisitorを実装します。println("${e.name()}の皮を剥いて切って炒めます。println("${e.name()}の芽をとって皮を剥いて切って炒めます。println("${e.name()}を切って炒めます。println("${e.name()}を鍋に入れて炒めます。println("まだ料理中です。}}出力結果[out-put]じゃがいもの皮を剥いて切って鍋で煮込みます。にんじんの芽をとって皮を剥いて切って鍋で煮込みます。豚肉を切って鍋で煮込みます。カレーのルーと塩と胡椒を鍋に入れて煮込みます。カレーの完成!--------------------------------じゃがいもの皮を剥いて切って炒めます。にんじんの芽をとって皮を剥いて切って炒めます。豚肉を切って炒めます。塩と胡椒を鍋に入れて炒めます。