What is navigator.sendBeacon?
navigator.sendBeaconってなんのこっちゃって感じなのでこれを読むと良い。
Beacon
日本語版: Beacon (日本語訳)
何がうれしいの!?って感じですが、例えばリンクをクリックした時にXMLHttpRequestでサーバーに情報を送っているようなケースで
サーバーのレスポンスが悪かったりするとサーバーにデータを送る前に遷移が完了してしまって取りこぼしてしまって正確なデータの収集が出来ないようなケースがあります。
また、サーバーからのレスポンスを待って遷移させるようなケースではサーバー側のレスポンスが悪いとページ遷移の妨げになりユーザーエクスペリエンスの低下につながります。
これをBeacon APIを使うことでユーザー・アクションを妨げることなくブラウザのバックグラウンドで処理を実行して確実にサーバーにデータを送れるようになります。
そして、送ったデータは広告なりアクセス解析なりなんなりと利用すればいいと思います。
サーバーを書いてみる
まずはサーバーを書いてみます。
package main import ( "encoding/base64" "fmt" "io" "io/ioutil" "log" "net/http" apachelog "github.com/gorilla/handlers" "os" ) const ( body string = ` <html> <script> // emit non-blocking beacon to record state update function reportEvent(eventData) { navigator.sendBeacon('/collector', eventData); } // emit non-blocking beacon to record click on a link function reportClick(url) { var clickData = JSON.stringify({ clickTo: url, clickAt: performance.now() }); navigator.sendBeacon('/collector', clickData); } // emit non-blocking beacon when page transitions to background state document.addEventListener('visibilitychange', function() { if (document.visiblityState === 'hidden') { var analyticsData = buildSessionReport(); navigator.sendBeacon('/collector', analyticsData); } }); </script> <body> <a href='http://www.w3.org/' onclick='reportClick(this)'> <button onclick="reportEvent('button event!')">Click me</button> </body> </html> ` base64GifPixel = "R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" ) type BaseHandler struct{} func (bh *BaseHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { fmt.Fprint(w, body) } type BeaconHandler struct { BeaconImage []byte RequireTLS bool } func (bh *BeaconHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { log.Println("send beacon") //if bh.RequireTLS r.TLS == nil { // log.Printf("WARNING: Please Requst with https.\n") //} if s, err := ioutil.ReadAll(r.Body); err == nil { log.Printf("DEBUG: %#v\n", string(s)) } else { log.Printf("DEBUG: Error %#v\n", err) } output := string(bh.BeaconImage) w.Header().Set("Content-Type", "image/gif") //w.Header().Set("Content-Length", string(len(output))) io.WriteString(w, output) return } func main() { port := 8000 mux := http.NewServeMux() // set demo page handler bh := &BaseHandler{} mux.Handle("/hey", bh) // Convert image && set handdler output, _ := base64.StdEncoding.DecodeString(base64GifPixel) beacon := &BeaconHandler{ BeaconImage: output, } mux.Handle("/collector", apachelog.CombinedLoggingHandler(os.Stderr, beacon)) // Extra mux.Handle("/", http.RedirectHandler("/hey", http.StatusFound)) fmt.Printf("Listen: %d\n", port) addr := fmt.Sprintf(":%d", port) srv := http.Server{ Addr: addr, Handler: mux, ErrorLog: nil, //ReadTimeout: 10 * time.Second, //WriteTimeout: 10 * time.Second, } err := srv.ListenAndServe() if err != nil { log.Fatal(err) } }
検証用のサーバースクリプトを用意したのでとりあえず、サーバーを起動してみよう。
% go run server.go
Listen: 8000
ブラウザからアクセスしてみる
navigator.sendBeaconが実装されているブラウザはあまり多くない。
caniuseで確認してみるとFirefoxとChromeには実装されているがそれ以外のサポート状況はあまりよろしくない。
モバイル界隈も最新のAndroidのブラウザはサポートされているがiOS safariがサポートされていない状況となっている。
個人的にiOS safariがサポートされていないのは痛い状況で日本のWEB界隈だとiOS:Androidが7:3ぐらいなので7に実装されてないとnavigator.sendBeaconを利用する側のモチベーションに影響するんじゃないかと思う。
また、Can I useのページにも日本におけるサポートブラウザの比率が掲載されているがまだ 38.87%
ということで半分にも満たない。
普及にはiOS Safariで実装がされることが必須のように思う。
Can I use... Support tables for HTML5, CSS3, etc
自分はFirefox 42からこのURLにアクセス。
アクセスしたらボタンを押して遷移してW3Cのページに移動しよう。
移動したら、コンソールに戻ってみたください。 下のようなログが落ちてるはずです。
% go run server.go Listen: 8000 2015/12/13 02:34:45 send beacon 2015/12/13 02:34:45 DEBUG: "event!" 2015/12/13 02:34:45 send beacon 2015/12/13 02:34:45 DEBUG: "{\"clickTo\":{},\"clickAt\":2601.465}"
ここには詳細は書いてませんが仕様にはPOSTでリクエストすることとなってますので比較的大きなデータでも受け渡すことが可能です。
Beacon-Age ヘッダフィールドというものが定義されています。 これはユーザーアクションからサーバーに送るまで非同期で処理されるためアクションを起こした正確な時間をサーバー側で取得するためのヘッダーとなっています。 ただ、tcpdump使ってhttpの通信みた限りChrome,Firefoxともに実装されていないので頭の片隅に入ってればいいんじゃないかなと。