技術をかじる猫

適当に気になった技術や言語、思ったこと考えた事など。

Playframework2.5 で Slick3 弄ってみる

まずはアプリケーション仕様

今回は特に何も考えず、Evolution で突っ込んだ文字列を画面に表示するだけ。
ジンプルなことは良いことだ。

教科書はこれ

PlaySlick - 2.5.x

Playframework プロジェクト作成

いつものこと

    activator new

play-scala を選択する。
現時点で正式公開なのは、Play 2.5 なので、2.5 のテンプレができる。

厳密には 2.5.10 ができた(2017/3/9 22時現在)。

まずは写経してみる

liblaryDependencies に突っ込むところからとな。
当然 evolution も突っ込む。

build.sbt にて

val H2_VERSION = "1.4.193"

libraryDependencies ++= Seq(
  jdbc,
  cache,
  ws,
  "com.typesafe.play" %% "play-slick" % "2.0.0",
  "com.typesafe.play" %% "play-slick-evolutions" % "2.0.0",
  "com.h2database" % "h2" % H2_VERSION,
  "org.scalatestplus.play" %% "scalatestplus-play" % "1.5.1" % Test
)

次は DB 設定。
application.conf に下記を追加。

slick.dbs.default.driver="slick.driver.H2Driver$"
slick.dbs.default.db.driver="org.h2.Driver"
slick.dbs.default.db.url="jdbc:h2:mem:play"

db ディレクティブは参照しないらしい。
そっかー的な感覚。

そして、ここまでで起動と。

sbt run

するとエラー

1) A binding to play.api.db.DBApi was already configured at play.api.db.slick.evolutions.EvolutionsModule.bindings(EvolutionsModule.scala:15):
Binding(interface play.api.db.DBApi to ConstructionTarget(class play.api.db.slick.evolutions.internal.DBApiAdapter) in interface javax.inject.Singleton) (via modules: com.google.inject.util.Modules$OverrideModule -> play.api.inject.guice.GuiceableModuleConversions$$anon$1).
  at play.api.db.DBModule.bindings(DBModule.scala:25):
Binding(interface play.api.db.DBApi to ProviderConstructionTarget(class play.api.db.DBApiProvider)) (via modules: com.google.inject.util.Modules$OverrideModule -> play.api.inject.guice.GuiceableModuleConversions$$anon$1)

写経してこれとか不親切が過ぎんだろ。
これに対する答えが

PlaySlickFAQ - 2.5.x

It is very likely that you have enabled the jdbc plugin, and that doesn’t really make sense if you are using Slick for accessing your databases. To fix the issue simply remove the Play jdbc component from your project’s build.

なんか「Slack使ってんのになんでJDBCなんぞ有効にしてんだよ…」的なニュアンスを感じるが…。
まぁいい、build.sbt を修正

val H2_VERSION = "1.4.193"

libraryDependencies ++= Seq(
  cache,
  ws,
  "com.typesafe.play" %% "play-slick" % "2.0.0",
  "com.typesafe.play" %% "play-slick-evolutions" % "2.0.0",
  "com.h2database" % "h2" % H2_VERSION,
  "org.scalatestplus.play" %% "scalatestplus-play" % "1.5.1" % Test
)

Evolution を設定する

Play Slick supports Play database evolutions.

とのことなので、早速ページを見る。

Evolutions - 2.5.x

  1. conf/evolutions/default ディレクトリを作成
  2. (番号).sql を作成
# Example table schema
# --- !Ups

CREATE TABLE IF NOT EXISTS `messages` (
    `id`         bigint(20) NOT NULL AUTO_INCREMENT,
    `message`    varchar(255) NOT NULL,
    `created_at` datetime NOT NULL,
    `updated_at` datetime NOT NULL,
    PRIMARY KEY (id)
);

INSERT INTO `messages` (`message`, `created_at`, `updated_at`)
  VALUES ('Example message', '2017-03-09 13:03:41', '2017-03-09 13:03:41');

# --- !Downs

DROP TABLE IF EXISTS messages;

いざ実行!

Database 'default' needs evolution!

よっしゃ〜!!……?
いつまでたってもEvolution が終わらない…(汗

ログみてなるほど、Evolution 実行後にアプリケーションが再起動してる…つまりそのタイミングでインメモリデータベースも…
設定書き換え。

slick.dbs.default.driver="slick.driver.H2Driver$"
slick.dbs.default.db.driver="org.h2.Driver"
slick.dbs.default.db.url="jdbc:h2:./db/play"

これで Evolution 通過。

DBアクセス

ということで、スキーマと Dao 作る

package models

import java.sql.Timestamp
import javax.inject.{Inject, Singleton}

import play.api.db.slick.{DatabaseConfigProvider, HasDatabaseConfigProvider}
import slick.driver.JdbcProfile
import slick.lifted.ProvenShape
import scala.concurrent.duration._

import scala.concurrent.{Await, Future}

case class Message(
  id: Long,
  message: String,
  createdAt: Timestamp = new Timestamp(System.currentTimeMillis()),
  updatedAt: Timestamp = new Timestamp(System.currentTimeMillis())
)

@Singleton
class Tables @Inject() (override protected val dbConfigProvider: DatabaseConfigProvider)
  extends HasDatabaseConfigProvider[JdbcProfile] {

  import driver.api._

  class Messages(tag: Tag) extends Table[Message](tag, "messages") {
    def id      = column[Long]("id", O.PrimaryKey, O.AutoInc)
    def message = column[String]("message")
    def createdAt = column[Timestamp]("created_at")
    def updatedAt = column[Timestamp]("updated_at")

    override def * : ProvenShape[Message] =
      (id, message, createdAt, updatedAt) <> (Message.tupled, Message.unapply)
  }

  val messages = TableQuery[Messages]

  def getAllMessages(): Seq[Message] = {
    import dbConfig.driver.api._
    Await.result(
      db.run(messages.sortBy(_.id).result),
      10 seconds
    )
  }
}

そしたらController から読んでやる。

package controllers

import javax.inject._

import models.Tables
import play.api.mvc._

@Singleton
class HomeController @Inject() (
                               tables: Tables
                               ) extends Controller {

  def index = Action {
    Ok(views.html.index(tables.getAllMessages().map(_.message).mkString(",")))
  }
}

さぁって…と思って試して見ると

JdbcSQLException: テーブル "messages" が見つかりません

What's!?

仕方ないので、ファイルを H2 Database Engine よりダウンロードしてコンソールを起動。
そこで疑問氷解。

どうにもテーブル名やカラム名が大文字になってる。当然、見つからないと言われたSQL(ログから取得) select "id", "message", "created_at", "updated_at" from "messages" order by "id" も通らない。
何これヒデエ(TT

試しにコンソール上で大文字かして見る。

> select ID, MESSAGE, CREATED_AT, UPDATED_AT from "MESSAGES" order by ID;

ID      MESSAGE     CREATED_AT      UPDATED_AT  
1  Example message 2017-03-09 13:03:41.0   2017-03-09 13:03:41.0
(1 行, 9 ms)

FUUUUxxKK!!

OK わかったよ。

@Singleton
class Tables @Inject() (override protected val dbConfigProvider: DatabaseConfigProvider)
  extends HasDatabaseConfigProvider[JdbcProfile] {

  import driver.api._

  class Messages(tag: Tag) extends Table[Message](tag, "MESSAGES") {
    def id      = column[Long]("ID", O.PrimaryKey, O.AutoInc)
    def message = column[String]("MESSAGE")
    def createdAt = column[Timestamp]("CREATED_AT")
    def updatedAt = column[Timestamp]("UPDATED_AT")

    override def * : ProvenShape[Message] =
      (id, message, createdAt, updatedAt) <> (Message.tupled, Message.unapply)
  }

  val messages = TableQuery[Messages]

  def getAllMessages(): Seq[Message] = {
    import dbConfig.driver.api._
    Await.result(
      db.run(messages.sortBy(_.id).result),
      10 seconds
    )
  }
}

これで実行成功...自宅で落ち着いてやると案外すんなり行くんだよねこういうの。

Playframework2.5.x でプロジェクト分離してみた

サブプロジェクト自体は、Playframework2.3 から存在していて、当時はあまり使い道も思い浮かばなかったが、今では大分使いやすくなってたのでメモ。

完全独立した sbt プロジェクトを取り込む

このケースでは、完全に独立した sbt モジュールを取り込みます。
用途的には、コンソールプログラムとWebアプリでロジックを共有するために切り離すとか、そういう用途だ。

因みに、それ自体が独立してコンパイルできる sbt プロジェクトであればいいので、おそらく、Playframework アプリケーションでもいけると思う。
ただし、個人的にはそれは失敗した。(設定も面倒だし、後述の「Play サブプロジェクトを作成する」を参照した方がきっといい)

下記の例は単純に sbt のライブラリを取り込む設定だ。
ディレクトリ構成的には下記。

  • project_root
    • build.sbt
    • modules
      • common_lib
        • build.sbt

common_lib がsbt プロジェクトだ。

project_root/build.sbt を下記のようにする。

// ① common_lib という形で、サブプロジェクトがあることを宣言
lazy val common_lib = project in file("modules/common_lib")

lazy val root =
  (project in file("."))
    .enablePlugins(PlayScala)
    .dependsOn(common_lib) // ② common_lib に依存しているという

root 定義に dependsOn で common_lib に依存していることを表す。
これだけなので、とりあえずこれで、common_lib 内のコードを取り込んだ側で利用可能になる。

Play サブプロジェクトを作成する

プロジェクトを機能ごとに分離したい場合に使用。
コンパイルはプロジェクト単位で行うので、コンパイル時間をどうにかしたい場合(変更のあったプロジェクトと、その依存プロジェクトのみ差分コンパイルが走る)や、機能間依存を極力減らしつつ機能の充実化をする(要するにプロジェクト整理)ために使用する。

ディレクトリ配置は下記のようにしてみた。

  • project_root
    • build.sbt
    • app : アプリケーションソース
    • modules
      • view_template
        • build.sbt
        • app : 共有ソース

project_root/build.sbt の側はこれでOK

lazy val viewTemplate =
  (project in file("modules/view_template"))
    .enablePlugins(PlayScala)

lazy val root =
  (project in file("."))
    .enablePlugins(PlayScala)
    .dependsOn(viewTemplate)
    .aggregate(viewTemplate)

enablePlugins(PlayScala) を食わせると、サブプロジェクト側も Play のプロジェクトとして扱われるようになる。

次に、 modules/view_template/build.sbt 側の中身は下記。
注意が必要なのは、scalaVersion を本体に合わせないとコケる。

name := "view_template"

organization := "net.white-azalea"

version := "0.1-SNAPSHOT"

scalaVersion := "2.11.7"

libraryDependencies ++= Seq(
  jdbc,
  evolutions,
  cache,
  ws,
  filters,
  specs2 % Test,

  /* database definition */
  "com.h2database" % "h2" % "1.4.192"
)

この時、サブプロジェクト側のディレクトリ構成もは、Playframewrok アプリ同様のディレクトリ構成になる。

Controller だけでなく、URL も部分切り出しできるので、それをするなら routes 分離(公式ドキュメント) 見ればいいと思う。

サブプロジェクトの記述を簡単にしたい

続きを読む

プログラミングの経験と信仰

経験は全ての物事の元となるもので、とても重要なものだと思う。
でも、経験を『信仰』にした瞬間、それは害悪となる。

プログラマは誰しも経験した記憶があるだろう。
「それならコレでもできるよ」と長ったらしい作りをした人を。
かく言う私も、これをかつてしたことがある。今では恥じるばかりだが。

あるコボラー達の話をしよう。
これは大学時代の友人から数年前に聞いた話である。

COBOLメインだった会社が生き残りをかけて、Java を主軸にしようとした話だ。
一般の方に説明すると、COBOL 言語とは、全くプログラムを知らない人用(事務方用)に設計され、非常に少ないルールでのみ記述するプログラム言語だ。(今は若干機能が増えた方言がいくつかあるが、やはり先細り中)
ここだけ聞くと何が間違っているかわからないだろうが、ルールが少ないと言う事は覚えやすい反面、できることが少ないと言うことだ。これは平仮名だけで小説を書くに等しい。当然分量は増え、冗長で誰も読みたくない。

ビジネスは変化する。それはアプリだってバージョンアップする必要があると言うことだ。
ひらがな(COBOL)の小説に続編を書くなんて、浪費でしかない。人件費だけがかかりすぎるのだ。
だが、現場は猛反発。「それならCOBOLでもできる!」の一点張りだったそうだ。
最終的に「Javaやるか会社辞めるか選べ」と言って、トップダウンで強硬策までしてやっと乗り換えたと言う話だ。

彼らにとって、これまでの成功経験は絶対的な正義であり、変えるべきではない『信仰』だったと言える。
それを変えようと言うのは、彼らにしてみれば『背信行為』なのだ。

彼らが間違っていたことは、成功体験が信仰となり、経験と混同していたことだ。

経験は猛毒だ。
たとえどれだけ理不尽であり得ない論理であっても、経験してしまえばそれはその人の真実となる。
まして成功体験は、その真実を信仰に補強してしまう。

経験とは応用し、より良い結果を生み出すものだが、信仰とはそれを受け継ぎ、変化を妨げる。
変化も良し悪しはあるだろう。だが少なくとも現代のプログラミングに信仰は無用だ。

オープンソースがメジャーとなり、GitHub がそれを加速させた結果、『誰かの経験』は短期間で幅広く共有でき、『皆の経験』でドンドン進化する。
それは個人程度が持つ『経験』など、霞むくらいの経験の集合知だ。
個人の成功経験で書いたコードと、多くの人の成功経験、失敗した経験をフィードバックして書かれたコード、価値はどっちにあるだろう?

『経験』とは『知っている事柄だから、新しいやり方も咀嚼して理解できる』だったり『知っているからより良くする方法を考えられる』。『知っているから効率のいい運用を考えられる』と言うことではないだろうか?
それは変化を促すものであって、変化をせき止めるものであってはならない。

信仰を捨てれないなら、COBOL のように先細って茹でガエルになりましょう。

しかし、この業界は変化が早い。
実は上記の理由以外にも『変化をせき止める。押し返そうとする』勢力は実はいる。
それも入社仕立てや、30代の若い世代にも大量にいる。

IT の変化が早すぎて、追従し切れない人たちだ。
多くの場合、仕事で使った、説明された技術のみ覚えていく人たちだ。

追従できなくも、仕事を変える事ができないのはある種、可哀想ではあるが…
彼らに言える事は、仕事の外でも勉強するしかありませんねと言うこと位だ。

仕事で使う技術についていけないなら、仕事の外でも学ぶ事だ。
良書と言われる本を読み、知っている人の講義を聞き、有名なOSSのドキュメントを端から端まで読み、コードを読み、テストを読もう。
どう勉強していいかわからない人もいるだろう。コードが自分の知識と離れすぎて読めない人もいるだろう。どの本を読むべきか分からない人もいるだろう。
そんな時は、年下だろうが後輩だろうが頭を下げて教わろう。僕は下げた。凄い奴は凄い。割り切ろう。

好き嫌いの問題では無い、プライドが許さないと言うなら、猫のモフモフより価値のないプライドは猫にでも食わせればいい。
気位の高い猫のことだ、きっとお気に召してくれるはずだ。

IT はルネサンス期。
天動説を強要したキリスト教は地動説への転向を余儀なくされた。
人の造物ごときに神はいない。『信仰』を捨てる心の準備は?皆の経験は本当に『経験』か?

パスワード用ハッシュ、jBCrypt を使ってみた

なんでそんなことになったかというと、Playframework の Crypto クラスがいつの間にか Deplicate になってたという事態。
これにより、色々変動があったのだが、「パスワードのハッシュに Crypto.sign 使うんじゃない」だそうです。

Misuse as a Password Hash Please do not use Crypto.sign or any kind of HMAC, as they are not designed for password hashing. MACs are designed to be fast and cheap, while password hashing should be slow and expensive. Please look at scrypt, bcrypt, or PBKDF2 – jBCrypt in particular is well known as a bcrypt implementation in Java.

で、ここに書かれていたのが jBCrypt です。

で、早速使ってみたわけですが、まずは参照追加。

libraryDependencies ++= Seq(
    // 中略
    "org.mindrot" % "jbcrypt" % "0.3m"
)

そしたらしれっと

def salt: String = BCrypt.gensalt()

val hashed = BCrypt.hashpw("password", salt)

if (BCrypt.checkpw("password", hashed)) "Accept" else "NoooooOO!"

salt をハッシュ化するたびに実行して、それを保存。
hashed には salt 情報も入ってるので、そのまま検証できるっぽい。

SPAするほどでもない時に気軽に使う JavaScript framework, Vue と Knockout 比較

ちょっと鯖欲しかったので Activator new で作って試す。
nodejs で勉強するチャンスだったかもしれないが、時間もなかったのでとりあえず知ってるやつで。

とりあえず、読めるようにしておく。
build.sbt

libraryDependencies ++= Seq(
  jdbc,
  cache,
  ws,
  "org.webjars" %% "webjars-play" % "2.5.0",
  "org.webjars" % "vue" % "2.1.3",          // これと
  "org.webjars.npm" % "knockout" % "3.2.0", // これ
  "org.scalatestplus.play" %% "scalatestplus-play" % "1.5.1" % Test
)

単純フォーム=画面バインディング

Knockout.js

    <div id="knockout-binding">
        <input id="k_text" type="text" value="" data-bind="textInput: ktext" />
        <p data-bind="text: ktext"></p>
    </div>

    <script type="text/javascript" src='@routes.Assets.versioned("lib/knockout/build/output/knockout-latest.debug.js")'></script>
    <script type="text/javascript">
        var myKoData = {
            ktext: ko.observable('Example')
        };
        ko.applyBindings(myKoData);
    </script>

まずは単純バインディング。テキストを変更すると、リアルタイムに p タグメッセージが変わる。

シンプルといえばシンプルだが、オブジェクトに「ko.observable」を用いているのが引っかかる。
これは、値を変更した際に更新をライブラリに通知する役割があり、それを受けて他のバインディング箇所を更新しているらしい。

ちなみに、一度バインドした画面内でもう一度 ko.applyBindings(myKoData) を実行するとエラーが発生する。

  • knockout-latest.debug.js:2786 Uncaught Error: You cannot apply bindings multiple times to the same element.

つまり、バインド時点で必要なデータは揃ってる必要がある。

Vue.js

    <div id="vue-simple-binding">
        <input id="v_text" type="text" value="" v-model="vtext" />
        <p>{{ vtext }}</p>
    </div>
    <script type="text/javascript" src='@routes.Assets.versioned("lib/vue/vue.js")'></script>
    <script>
        var myVueData = {
            vtext: "Example"
        };

        var vueData = new Vue({
            el: "#vue-simple-binding",
            data: myVueData
        });
    </script>

全く同じ仕様の動作実装。

Vue オブジェクトにデータを食わせるところが異なる。
knockout は画面全体に対するバインドだが、Vueel パラメータでバインドの範囲を制限する。

これは、画面の部品ごとに別々のバインドを実行できるということだろう。

存在しないオブジェクトをバインドし、後から値を設定する

動かないパターン。
これはオブジェクトの値が変化したことをコンポーネントが認識できない。

    <div id="knockout-binding">
        <input type="button" onclick="myKoData.after = 'example';" />
        <p data-bind="text: after"></p>
    </div>

    <script type="text/javascript" src='@routes.Assets.versioned("lib/knockout/build/output/knockout-latest.debug.js")'></script>
    <script type="text/javascript">
        var myKoData = { };
        ko.applyBindings(myKoData);
    </script>

エラー内容は、下記の通り。つまり、事前に設定したものしかバインドできないと言っている。

  • knockout-latest.debug.js:2890 Uncaught ReferenceError: Unable to process binding "text: function (){return after }"

動作可能なものは下記。事前にフィールドを用意しておくのだ。

    <script type="text/javascript">
        var myKoData = { after: ko.observable(""); };
        ko.applyBindings(myKoData);
    </script>

Vue.js

おおよそ想像はつくと思うが、こっちもエラーがでる。

    <div id="vue-simple-binding">
        <input id="v_text" type="button" onclick="vueData.vtext = 'Example';"  />
        <p>{{ vtext }}</p>
    </div>
    <script type="text/javascript" src='@routes.Assets.versioned("lib/vue/vue.js")'></script>
    <script>
        var myVueData = {};

        var vueData = new Vue({
            el: "#vue-simple-binding",
            data: myVueData
        });
    </script>

vue.js:515 [Vue warn]: Property or method "vtext" is not defined on the instance but referenced during render. Make sure to declare reactive data properties in the data option. (found in root instance)

こっちも事前にプロパティは用意しなければならない。
エラー内容が Vue の方が親切だ。

    <script>
        var myVueData = { vtext: null };

        var vueData = new Vue({
            el: "#vue-simple-binding",
            data: myVueData
        });
    </script>

配列にオブジェクトバインディング

二つのフレームワークは共に、事前にプロパティを設定して置かなければならなかった。
まぁわからない話ではない。

しかし、 ajax リクエストで遅延取得するようなデータのバインディングを処理できなければ用途は限られる。

knockout.js

    <div id="knockout-binding">
        <input type="button" onclick="myKoData.list([ { name: 'Example' } ]);" />
        <ul data-bind="foreach: list">
            <li data-bind="text: name"></li>
        </ul>
    </div>

    <script type="text/javascript" src='@routes.Assets.versioned("lib/knockout/build/output/knockout-latest.debug.js")'></script>
    <script type="text/javascript">
        var myKoData = { list: ko.observable([]) };
        ko.applyBindings(myKoData);
    </script>

結果は成功。
では要素の追加はどうか?

<input type="button" onclick="myKoData.list.push({ name: 'Example' });" />

に変更して行ってみるが、結果は失敗。

(index):19 Uncaught TypeError: myKoData.list.push is not a function

ko.observableを実行した際には、getter, setter 実装が追加されるため、そもそも論配列ではないのだという。
まぁ当然か。

定義を var myKoData = { list: ko.observableArray() }; に変更すると成功する。要は、knockout の独自定義配列だ。
では、さらにクエスチョン。

配列そのものの入れ替えは効くのか?というと、基本その手段は用意されないらしい。

ドキュメント | Knockout.js 日本語ドキュメント

Vue.js

    <div id="vue-simple-binding">
        <input id="v_text" type="button" onclick="vueData.list = [{ name: 'Example' }];"  />
        <ul>
            <li v-for="data in list">{{ data.name }}</li>
        </ul>
    </div>
    <script type="text/javascript" src='@routes.Assets.versioned("lib/vue/vue.js")'></script>
    <script>
        var vueData = new Vue({
            el: "#vue-simple-binding",
            data: { list: [] }
        });
    </script>

はい成功。
ボタン動作を push に変えてみる。

<input id="v_text" type="button" onclick="vueData.list.push({ name: 'Example' });" />

これも成功。

まとめ

どっちのフレームワークも、書き方に癖がある。

  • Knockout は独自オブジェクトを使うのが気になる
  • VueJS は、HTML 正式互換ではない attribute を使ってるのが気になる(HTML で任意属性は「data-XXXX」だけである)。

画面が単純であれば、どっちを汚すかの世界だと思われる。

一方で、画面に複数の独立したコンポーネントを定義したい場合、Knockout 側は部品ごとに独立して JS をおくことができない。
これは1画面1バインドでしか対応していないためだ。

もし、複数の定義が欲しい場合は、 コンポーネント 構文が必要とのこと。
割と大げさだ。
再利用せず、1画面内で複数の独立した部品の定義をするなら Vue の方がシンプルだろう。

Vue にもコンポーネント はあるが、これはあくまで再利用可能なコンポーネントを指す。

因みに、実行速度は、単純に書くとknockout の方が早い。これは、Vue は画面内の HTML を取り込んでメモリ内部コンパイルを実行するためだ。
事前コンパイルしておけば、そのロスは減るので、速度差はほぼ意識せずに済むレベルにはなる。
とはいえここも判断材料だろう。

Playframework 2.5 で足りてなさそうなセキュリティヘッダ設定を追加する

セキュリティヘッダなんて、ヘッダに組み込むだけのものなので、ザクッと。

package filters

import javax.inject.{Inject, Singleton}

import akka.stream.Materializer
import play.api.Configuration
import play.api.mvc.{Filter, RequestHeader, Result}

import scala.concurrent.{ExecutionContext, Future}

/**
 * Add custom security header responds.
 * Created by azalea on 2016/08/29.
 */
@Singleton
class SecureHeaderFilter @Inject() (
  implicit override val mat: Materializer,
  exec: ExecutionContext,
  conf: Configuration
) extends Filter {

  private def prefix(k: String) = s"play.filters.$k"

  val XFrameOptions = conf.getString(prefix("frameOptions"))
  val xssProtection = conf.getString(prefix("xssProtection"))
  val contentTypeOptions = conf.getString(prefix("contentTypeOptions"))
  val permittedCrossDomainPolicies = conf.getString(prefix("permittedCrossDomainPolicies"))
  val contentSecurityPolicy = conf.getString(prefix("contentSecurityPolicy"))
  val strictTransport = conf.getString(prefix("strictTransport"))
  val downloadOptions = conf.getString(prefix("downloadOptions"))
  val cacheControl = conf.getString(prefix("cacheControl"))
  val pragma = conf.getString(prefix("pragma"))
  val expires = conf.getString(prefix("expires"))

  val headers = Seq(
    XFrameOptions.map(v => "X-Frame-Options" -> v),
    xssProtection.map(v => "X-XSS-Protection" -> v),
    contentTypeOptions.map(v => "X-XSS-Protection" -> v),
    contentSecurityPolicy.map(v => "Content-Security-Policy" -> v),
    permittedCrossDomainPolicies.map(v => "X-Permitted-Cross-Domain-Policies" -> v),
    strictTransport.map(v => "Strict-Transport-Security" -> v),
    downloadOptions.map(v => "X-Download-Options" -> v),
    cacheControl.map(v => "Cache-Control" -> v),
    pragma.map(v => "pragma" -> v),
    expires.map(v => "expires" -> v)
  ).flatten

  /**
   * add secure headers.
   *
   * @param nextFilter next filter function.
   * @param rh         request header.
   * @return           header annotated result.
   */
  override def apply(nextFilter: (RequestHeader) => Future[Result])(rh: RequestHeader): Future[Result] = {
    nextFilter(rh).map(res => res.withHeaders(headers:_*))
  }
}

設定ファイルはこんな感じ。もともとあった Play のセキュリティ設定に追記する形。

play.filters {
  headers {
    # The X-Frame-Options header. If null, the header is not set.
    frameOptions = "SAMEORIGIN"

    # The X-XSS-Protection header. If null, the header is not set.
    xssProtection = "1; mode=block"

    # The X-Content-Type-Options header. If null, the header is not set.
    contentTypeOptions = "nosniff"

    # The X-Permitted-Cross-Domain-Policies header. If null, the header is not set.
    permittedCrossDomainPolicies = "master-only"

    # The Content-Security-Policy header. If null, the header is not set.
    contentSecurityPolicy = "default-src 'self'"

    # My custom security headers.
    # The Strict-Transport-Security header.
    #strictTransport = "max-age=31536000; includeSubDomains"

    # The X-Download-Options header.
    #downloadOptions = "noopen"

    # The Cache-Control header.
    cacheControl = "no-cache, no-store, must-revalidate"

    # The pragma header. for old cache server.
    pragma = "no-cache"

    # The expires header.
    expires = "-1"
  }

上から順に説明すると、

  • X-Frame-Options
    画面内フレームコンテンツの制限。 DENY:フレームの全面禁止、SAMEORIGIN:同じドメインコンテンツは許可、ALLOW-FROM origin_uri 指定URLのコンテンツはフレーム表示許可
    意図しない、詐欺サイト表示対策など。
  • X-XSS-Protection
    ブラウザの XSS 保護機能を有効化する。0: で保護を無効化。1で有効化
  • X-Content-Type-Options
    サーバが返してきたヘッダに従ってファイルを処理する。
    特に IE なんかだと、ファイルの中身を確認して勝手に対応アプリを開く悪癖があって、拡張子を js にした JavaScript なんかを仕込む攻撃の対策
  • X-Permitted-Cross-Domain-Policies
    crossdomain.xml の置き換え。JavaScript で複数のサーバと通信する場合の、許可証設定。
  • Content-Security-Policy
    HTML,JavaScript,音声,画像などなど、アクセスしていいドメインの制限。 default-src 'self' は同一ドメイン以外からの一切のデータ取得禁止。
  • Strict-Transport-Security
    このURLは HTTPS でアクセスセーよというお達し。中身はお察し
  • X-Download-Options
    ダウンロードしたファイルは、HTML 内での img タグとかを除いて、勝手に開くんじゃねーよ指定。
    アップロード・ダウンロードをするシステムでは有用
  • Cache-Control
    プロクシとか、キャッシュサーバとかに「キャッシュしてんじゃねーよ」と通知。
    個人情報入りのページをキャッシュされたら個人情報漏洩になるでしょ〜が!
    no-cache: キャッシュすんじゃねーぞ。有効性確認しなけりゃキャッシュ使うんじゃねーぞ
    no-store: リクエスト・レスポンスの一部分をローカルストレージに保存するんじゃない must-revalidate: 毎回サーバに確認しろ
  • pragma
    Cache-Control の no-cache と同様。古いキャッシュサーバ向け。
  • expires
    キャッシュコンテンツの有効期限。-1 では常に「そのキャッシュは無効」

Scala 関数型デザイン&プログラミング:Exercize3.16-3.23

Scala 関数型デザイン&プログラミング:Exercize3.2 - 3.13 - 謎言語使いの徒然 の続き。

Exercise 3.16

各要素に +1 したリストを返す関数を作れ。

なんか仕様的に map 関数に似てるなーと思った。
とりあえず フォイ

  def map[A, B](as: List[A], func: A => B): List[B] =
    foldRight(as, Nil:List[B])((a, b) => Cons(func(a), b))

  def increment(as: List[Int]): List[Int] = map[Int, Int](as, v => v + 1)

  /*  printRec(List.increment(List(1, 2, 3, 4))) */

Exercise 3.17

List[Double] を List[String] にする関数を作りなさい。

はいはい mapmap

  def toStringList[A](as: List[A]): List[String] = map[A, String](as, _.toString)

しかしこの map 型指定しないとコンパイルコケるのはダサいな。

Exercise 3.18

下記シグネチャで map 作れ def map[A, B](as: List[A], func: A => B): List[B]

上記 map のシグネチャだけ変更。
何か型推論がいい仕事始めて、「map[A, String](as, _.toString)」が「map(as)( _.toString)」になった。

引数が一つであることを(curry にでもして)保証すると精度でも上がるのかね?

Exercise 3.19

与えられた条件を満たされるまでリストから要素を削除する filter 関数を記述せよ。 def filter[A](as: List[A])(f: A => Boolean): List[A]

まぁ foldRight あれば余裕な

  def filter[A](as: List[A])(f: A => Boolean): List[A] =
    foldRight(as, Nil: List[A])((a, b) => if (f(a)) Cons(a, b) else b)
  /* printRec(List.filter(List(1,2,3,4,5,6,7,8,9))(_ % 2 == 0)) */

Exercise3.20

map と似た動きをする関数、flatMap を作れ。シグネチャは下記の通り。 def flatMap[A,B](as: List[A])(f: A => List[B]): List[B] flatMap(List(1, 2, 3))(i => List(i, i)) と書いたら、List(1, 1, 2, 2, 3, 3) を返せ。

Scala 標準の flatMap ほど高機能ではないか…?

  def flatMap[A,B](as: List[A])(f: A => List[B]): List[B] = concat(map(as)(f))
  /* printRec(List.flatMap(List(1, 2, 3))(a => List(a, a))) */

Exercise3.21

flatMap を使って filter を実装せよ。

何だ? f(x)==false の時 Nil にでもするのか?

  def filterViaFlatMap[A](as: List[A])(f: A => Boolean): List[A] =
    flatMap(as)(a => if(f(a)) List(a) else Nil: List[A])

  /* printRec(List.filterViaFlatMap(List(1,2,3,4,5))(_ % 2 == 0)) */

何だろう…効率的な実装に見えない。

Exercise3.22

同数二つの List[Int] を受け取って、対応する要素同士で足し算する関数を作れ。

  def addPairwise(list: List[Int], list1: List[Int]): List[Int] = {
    def addPairwise(a: List[Int], b: List[Int], sum: List[Int]): List[Int] = {
      (a, b) match {
        case (Nil, _) => sum
        case (_, Nil) => sum
        case (Cons(ah, at), Cons(bh, bt)) => addPairwise(at, bt, Cons(ah + bh, sum))
      }
    }

    reverse(addPairwise(list, list1, Nil: List[Int]))
  }
  /* printRec(List.addPairwise(List(1,2,3), List(1,2,3))) */

こうですか?わかりません。

Exercise3.23

Exercise3.22 の処理を、加算、減算にとらわれずに実行できるようにせよ。

  def zipWith[A, B, C](al: List[A], bl: List[B])(f: (A, B) => C): List[C] = {
    def zipWith(a: List[A], b: List[B], r: List[C]): List[C] = {
      (a, b) match {
        case (Nil, _) => r
        case (_, Nil) => r
        case (Cons(ah, at), Cons(bh, bt)) => zipWith(at, bt, Cons(f(ah, bh), r))
      }
    }

    reverse(zipWith(al, bl, Nil))
  }

addPairwise を zipWith で処理すれば終了。