(あどてく!) PlayFramework/Scalaで3rd Party Cookieを出力するピクセルトラッキングサーバーを作る

アドテク（広告テクノロジー）では、ブラウザでサイト見ているユーザー（オーディエンス）を特定する技術が求められます。
自社ECサイトで会員登録をしたユーザーを特定するのは簡単ですが、会員登録をしていないユーザー（初めて自社サイトに訪れたユーザー）を特定するには何らかの手段が必要になります。
つまり初めて自社サイトを訪れた人がどんな思考の人か、どんなサイトのどんなページを見ていたか・・。
その情報を特定するためにCookieに特定のIDを付与する技術がアドテクでは使われています。



## 3rd Party Cookieとは

![3rdCookie.png](https://qiita-image-store.s3.amazonaws.com/0/36931/3bbe5959-60b2-7fad-e039-6a4c6cca3b77.png)

今回の想定シナリオは以下のようになります
1. オーディエンスがサイト1(train.com)で大阪へいくための電車料金を調べる
2. サイト1には＜img src="tracking.com/1.gif"＞が埋め込まれており、オーディエンスがサイトを見た瞬間にトラッキングサーバーへ通信が走る。(オーディエンスのブラウザ->トラッキングサーバー)
3. トラッキングサーバーはオーディエンスのブラウザにCookieを出力しトラッキングIDを付与する
4. オーディエンスは電車のチケットは当日駅で買うことにし、ホテルのサイト(hotel.com)でホテルを予約することにする
5. hotel.comにも＜img src="tracking.com/1.gif"＞が埋め込まれており、オーディエンスがサイトを見た瞬間にトラッキングサーバーへ通信が走る。(オーディエンスのブラウザ->トラッキングサーバー)
6. トラッキングサーバーはオーディエンスのブラウザにtracking.comのクッキーが埋め込まれているのを確認し、再度IDを付与することはせずCookieのexpire(期限)だけを更新する。
7. オーディエンスのCookieIDからtrain.comで大阪の情報を調べていたことがログからわかるので、ホテルのページの広告枠に大阪のホテルの案内を出したり、ファーストビューを大阪ホテルのキャンペーンページに変更したりなど広告を最適化する。





トラッキング用のクッキーの種類は大きくわけて２つあります。


| 種類  | 説明　　　　　 |
|:-----------|:------------|
|1st Party Cookie|対象サイトのドメインのクッキーを使ってトラッキングする。絵でいう所のtrain.comのドメインでCookieを出力する|
|3rd Party Cookie|対象サイトとは別のトラッキングザーバーのドメインを使ってトラッキングする。絵でいう所のtracking.com|


1st Party Cookieで代表的なのはGoogleAnalytics(GA)です。
参考：http://nexal.jp/blogs/2061.html

3rdPartyにはSafariをトラッキングできないという致命的な欠陥(※)がありそれを回避するには1stParyCookieが有効です。(他にもローカルストレージをモニョモニョする・・などもありますが）
(※)Safariのデフォルトの設定が「3rd Party Cookieを拒否する」となっているため事実上3rdのCookieを受け付けない。




## ピクセルトラッキングとは
1x1の透明画像（だいたいはgif)をレスポンスとして返す裏でCookieを仕込み、Cookieにユーザーを特定するIDを付与する技術の事。
透明なのでブラウザからは見えない、というかほとんどはhtmlでhidden設定されてるのでブラウザには表示されない。
ピクセルトラッキングのHTMLの記述についてはDoubleClick(google)のサイトが超詳しい
https://support.google.com/dfp_premium/answer/1347585?hl=ja

画像でなくてもいいが、htmlのsrc=などでHTLMのオンロードで自動的に読み込めるものに限る。
またJSはサードパーティ製のサイトのJSを読み込めないブログサービスなどもあるので扱いづらい。
よって基本は画像になる。




## PlayFramework/Scalaでピクセルトラッキングサーバーを作る

### Playインストール

https://playframework.com/

Activatorをダウンロードし
``activator new``でプロジェクトを新規作成する。(Scalaプロジェクト)

### コントローラー実装
```scala:Tracking.scala
package controllers
import play.api._
import play.api.mvc._
import play.api.libs.Codecs
import java.security.MessageDigest
// Java8 lib
import java.util.Base64


object Tracking extends Controller {

  val COOKIE_KEY = "Z_SYSTEM_3RD_PARTY_COOKIE_ID_SCALA"
  val COOKIE_MAX_AFTER_AGE = Some(31622400 + 31622400)

  //ref https://css-tricks.com/snippets/html/base64-encode-of-1x1px-transparent-gif/
  val onePixelGifBase64 = "R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"
　val onePixelGifBytes = Base64.getDecoder().decode(onePixelGifBase64)

  def pixelTracking = Action {request =>
    val cookies = request.cookies
    val cookieValue = cookies.get(COOKIE_KEY).map { cookie =>
      Logger.debug(s"Cookie Exist! ${cookie.value}")
      cookie.value
    }.getOrElse {
      val newValue = uniqueIdGenerator()
      Logger.debug("Cookie Generate! $newValue")
      newValue
    }
    Ok(onePixelGifBytes).withCookies(Cookie(COOKIE_KEY, cookieValue, COOKIE_MAX_AFTER_AGE)).as("image/gif")
  }

  val uniqueIdGenerator = () => {
    val milliTime = System.currentTimeMillis()
    val nanoTime = System.nanoTime()
    val baseString = s"$milliTime $nanoTime"
    Logger.debug(baseString)

    val md = MessageDigest.getInstance("SHA-256")
    md.update(baseString.getBytes())

    val id = Codecs.toHexString(md.digest())
    Logger.debug(id)
    id
  }
}

```
#### val uniqueIdGenerator 関数 (CookieID生成関数)
トラッキング用のIDはどようなアクセス状況下、どのようなサーバーマシン構成でもオーディエンスに対してユニークなIDを割り当てることが求められる。
今回はミリ秒とナノ秒(※)をスペースでつなげてSHA256した16進法文字列をIDとした。
これであれば同じミリ秒でアクセスしたユーザーがいない限りはユニークなIDとなる。
よりアクセス数があるサーバーであればユーザーのIPやサーバーのIPやMACアドレス、プロセスID、Javaプロセスのフリーメモリ数などを足してハッシュ化するといいだろう。
ロジックを使いまわして別トラッキングサーバーを作る場合は、トラッキングサーバーの種別ID（"hogehoge-TrankingServer1")などを連結すればよいだろう。
(※)現在の一般のCPUでは正確なナノ秒は測定できないがSystem.nanoTime()はミリ秒よりは正確な値を返す。

####  def pixelTracking メソッド
このメソッドが実際のリクエストを受け付ける処理となる。

処理は単純で
1. リクエストにCookieの値("Z_SYSTEM_3RD_PARTY_COOKIE_ID_SCALA")があるかを検索
2. Cookieの値があったらCookieのExpire（有効期限）を更新して1x1ピクセルgifを返却
3. Cookieの値がなかったらuniqueIdGenerator関数を実行しCookieIDを生成
4. 生成したCookiIDをレスポンスに付与し、同時に1x1ピクセルgifを返却

### ルーティングの設定

```:/conf/route
GET        /1.gif               controllers.Tracking.pixelTracking
```

http://サーバーのドメイン/1.gif にアクセスすると処理が実行されるようになる。

### トラッキングサーバー起動
activatorを起動し、runでポート9000で起動する

```
activator
[info] Loading project definition from play_scala_study/play_scala_1/project
[info] Set current project to play_scala_1 (in build file:/play_scala_study/play_scala_1/)
[play_scala_1] $ run

--- (Running the application, auto-reloading is enabled) ---

[info] play - Listening for HTTP on /0:0:0:0:0:0:0:0:9000
```
http://localhost:9000/1.gif にアクセスし、画面にgifが表示され、Cookieが書き込まれていることを確認する。（ブラウザの開発者ツールで確認する）
二回目のアクセスでクッキーIDが変更されていないことを確認する。

実際に運用する場合はport80で起動するようポートを指定して起動する。

```
activator
start 80
```

### 自社サービスのサイトのHTMLの実装

トラッキングサーバーは起動したので、別のドメインのサイトのページに以下のような記述を埋め込めばトラッキングは完了です。


```:train.com/index.html
<p>安い電車予約どっとこむ</p>
<img src="http://tracking.com/1.gif" style="display:none">
```

```:hotel.com/index.html
<p>安いホテル予約どっとこむ</p>
<img src="http://tracking.com/1.gif" style="display:none">
```