Y's note

Web技術・プロダクトマネジメント・そして経営について

本ブログの更新を停止しており、今後は下記Noteに記載していきます。
https://note.com/yutakikuchi/

Google先生の検索結果リンクが予想以上に作り込まれていた件

Index

  1. 検索結果のリンクは単なるRedirectorでは無かった
    1. 検索結果のhttps
    2. httpsからhttpページへの遷移ではブラウザはRefererを送らない
    3. Google先生はRerererを送る仕組みを実装してくれた
    4. Refererが送信される処理の流れを追う
    5. httpsからhttpsページへの遷移はどうなるか
    6. Google Analyticsで検索Queryが「not provided」となる本当の理由
  2. まとめ

検索結果のリンクは単なるRedirectorでは無かった

知らなかったのが僕だけだったら凄い恥ずかしい内容なんですが、今までGoogle先生の検索結果として表示されるリンクのURLはGoogle内部でClick集計するためのRedirector機能だと思っていました。カウントアップの集計を記録したら本来のURLに遷移させるような。当然そのClick数を集計する機能も持ち合わせているんでしょうが、もう少しユーザーにも優しくClickした遷移先のサービスの事も考えられた親切な仕組みになっていたのでここにメモを残しておきたいと思います。

検索結果のhttps

2011年頃からGoogle先生は検索結果ページをhttps化させていますね。最初はGoogleのログインユーザーが対象だったと思うんですが、最近は全ユーザーを対象とするようになって来ています。これは検索機能にしては結構チャレンジングな事だと思います。httpと比較すると認証局との通信等を含めて処理が重たくなるし、単純に証明書のお金も掛かるし、セキュリティレベルの高い情報入力ページ以外での導入は避けられがちです。(※最近はhttpsの処理高速化としてSPDYとか技術開発も進んでいるみたいですね。SPDY - Wikipedia はてなブックマーク - SPDY - Wikipedia)
セキュリティ面を気にするユーザー視点からすると通信の暗号化という点は安心できる事だと分かりますが、大半の人は処理がさくさく動いてくれれば特にというレベルでしょうか。

httpsからhttpページへの遷移ではブラウザはRefererを送らない

HTTP/1.1: Security Considerations はてなブックマーク - HTTP/1.1: Security Considerations

Clients SHOULD NOT include a Referer header field in a (non-secure) HTTP request if the referring page was transferred with a secure protocol.

RFCの記述にもあるようにClientはhttpsからhttpページへの遷移の場合はRefererを送らないようにと記述されています。一応各種ブラウザはこのルールを守っていてhttpsからhttpへの遷移ではRefererを送りません。Refererは遷移元を特定するための超重要なHttpHeaderです。このブラウザが「Refererを送らない」という仕様がWebサービスを公開している人にとってはとても痛い事で、Googleからの遷移かどうかが分からなくなってしまいます。

Google先生はRerererを送る仕組みを実装してくれた

ブラウザが「Refererを送らない」問題を偉大なGoogle先生は解消してくれています。httpsの検索結果からhttpのページへRefererを送信しています。処理手順は以下のようになります。

  1. 検索結果のリンクはhttp://www.google.co.jp/urlのようにhttpのスキーマで定義する。
  2. http://www.google.co.jp/url200OKでhtmlを返す。(302 Foundで遷移先URLを返さない)
  3. ブラウザは200OKのhtmlを取得する。取得したhtml内部のjavascriptおよびMETA http-equiv="refresh"で本来の遷移先URLを読み込む。
  4. 200OKのhtmlはhttpなのでRefererが送信される。Refererとなるのは200OKを返したURL。※ただし検索パラメータのq=は空となる。
Refererが送信される処理の流れを追う

実際に検索結果ページの状態を追ってみます。Googleの検索Queryは「Google先生」と入力しています。検索結果一覧のURLは以下のようになり、httpsでq=のパラメータで検索Queryが定義されています。(「q=Google%E5%85%88%E7%94%9F」)

https://www.google.co.jp/search?q=Google%E5%85%88%E7%94%9F&ie=utf-8&oe=utf-8&aq=t&rls=org.mozilla:ja-JP-mac:official&hl=ja&client=firefox-a

Google先生」の検索結果を見てみると一番上がニコニコ大百科へのリンクになっています。リンクのURLは以下のようになっていて、直接ニコニコ大百科のページには遷移しません。url=のパラメータにニコニコ大百科のURL(http://dic.nicovideo.jp/a/google%E5%85%88%E7%94%9F)が定義されています。抑えておきたいのが、この段階でq=のパラメータは空になっています。検索結果を表示する時にq=を意図的に削除しています。

http://www.google.co.jp/url?sa=t&rct=j&q=&esrc=s&source=web&cd=1&cad=rja&ved=0CC0QFjAA&url=http%3A%2F%2Fdic.nicovideo.jp%2Fa%2Fgoogle%25E5%2585%2588%25E7%2594%259F&ei=rrYDUq6sAY-OkgWLj4HwCg&usg=AFQjCNEvoWx7_20Q8RPFex76Py49tT5AHQ&bvm=bv.50500085,d.dGI

実験する前はこのURLが302 FoundでRedirectさせるURLだと思っていたんですが、違いました。正解は200OKでhtmlを返します。302 FoundだとリンクのClick元であるhttpsページのRequest Headerを送ってしまいますが、200OKのページを挟むことによって200OKページのRequest Headerを送る事ができます。

$ curl --dump-header - "http://www.google.co.jp/url?sa=t&rct=j&q=&esrc=s&source=web&cd=1&cad=rja&ved=0CC0QFjAA&url=http%3A%2F%2Fdic.nicovideo.jp%2Fa%2Fgoogle%25E5%2585%2588%25E7%2594%259F&ei=rrYDUq6sAY-OkgWLj4HwCg&usg=AFQjCNEvoWx7_20Q8RPFex76Py49tT5AHQ&bvm=bv.50500085,d.dGI"
HTTP/1.1 200 OK
Date: Thu, 08 Aug 2013 15:30:52 GMT
Pragma: no-cache
Expires: Fri, 01 Jan 1990 00:00:00 GMT
Cache-Control: no-cache, must-revalidate
X-Frame-Options: ALLOWALL
Content-Type: text/html; charset=Shift_JIS
Server: gws
X-XSS-Protection: 1; mode=block
Transfer-Encoding: chunked

<script>window.googleJavaScriptRedirect=1</script><script>var m={navigateTo:function(b,a,d){if(b!=a&&b.google){if(b.google.r){b.google.r=0;b.location.href=d;a.location.replace("about:blank");}}else{a.location.replace(d);}}};m.navigateTo(window.parent,window,"http://dic.nicovideo.jp/a/google%E5%85%88%E7%94%9F");
</script><noscript><META http-equiv="refresh" content="0;URL='http://dic.nicovideo.jp/a/google%E5%85%88%E7%94%9F'"></noscript>

上のResponse Bodyを分かり易く改行したものを下に載せておきます。

<script>
window.googleJavaScriptRedirect=1
</script>
<script>
var m = { 
          navigateTo:function(b,a,d){
            if( b!=a && b.google ){
              if(b.google.r {
                b.google.r=0;
                b.location.href=d;
                a.location.replace("about:blank");
              }   
            } else {
                a.location.replace(d);
            }   
          }   
       };  
m.navigateTo(window.parent,window,"http://dic.nicovideo.jp/a/google%E5%85%88%E7%94%9F");
</script>
<noscript>
<META http-equiv="refresh" content="0;URL='http://dic.nicovideo.jp/a/google%E5%85%88%E7%94%9F'">
</noscript>

200OKで返されたhtmlはブラウザ上で表示される事はありません。本来の遷移先ページを読み込むためのロジックだけが書かれています。Javascriptが有効なケースにはlocation.replace、無効なケースはMETA http-equiv="refresh"でニコニコ大百科のページに遷移させます。重要なのはこのページがhttpであること。httpなのでブラウザはRefererを送信する事が可能です。Refererは検索結果のClickリンクのURLになります。(q=のパラメータが削除されたURLです。)httpsのページのRefererでは無い事に注意してください。下はLive Http Headersニコニコ大百科のページに遷移した時のRequest HeaderのDumpです。

http://dic.nicovideo.jp/a/google%E5%85%88%E7%94%9F

GET /a/google%E5%85%88%E7%94%9F HTTP/1.1
Host: dic.nicovideo.jp
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:22.0) Gecko/20100101 Firefox/22.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: ja,en-us;q=0.7,en;q=0.3
Accept-Encoding: gzip, deflate
Referer: http://www.google.co.jp/url?sa=t&rct=j&q=&esrc=s&source=web&cd=1&ved=0CC0QFjAA&url=http%3A%2F%2Fdic.nicovideo.jp%2Fa%2Fgoogle%25E5%2585%2588%25E7%2594%259F&ei=PcgDUpCNJobekgWf8ICIBw&usg=AFQjCNEvoWx7_20Q8RPFex76Py49tT5AHQ&bvm=bv.50500085,d.dGI
Connection: keep-alive
httpsからhttpsページへの遷移はどうなるか

検索Queryをhttpsスキーマを持つ「facebook」に変えて試してみます。検索結果一覧ページのURLは次のようになります。スキーマhttpsでq=のパラメータも設定されています。

https://www.google.co.jp/search?q=facebook&ie=utf-8&oe=utf-8&aq=t&rls=org.mozilla:ja-JP-mac:official&hl=ja&client=firefox-a

検索結果のリンクは次のようになります。先程と異なりhttpsスキーマになっています。q=パラメータの値は同様に削除されています。

https://www.google.co.jp/url?sa=t&rct=j&q=&esrc=s&source=web&cd=1&cad=rja&ved=0CCwQFjAA&url=https%3A%2F%2Fja-jp.facebook.com%2F&ei=_-ADUouGEIqokAXd5oGICQ&usg=AFQjCNF2AhfF-R00mWMvTx9TbBKZPxDMYQ&bvm=bv.50500085,d.dGI

リンクを押下した後のRequest Headerです。Refererは問題なく送信されています。

https://ja-jp.facebook.com/

GET / HTTP/1.1
Host: ja-jp.facebook.com
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:22.0) Gecko/20100101 Firefox/22.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: ja,en-us;q=0.7,en;q=0.3
Accept-Encoding: gzip, deflate
Referer: https://www.google.co.jp/url?sa=t&rct=j&q=&esrc=s&source=web&cd=1&ved=0CCwQFjAA&url=https%3A%2F%2Fja-jp.facebook.com%2F&ei=_-ADUouGEIqokAXd5oGICQ&usg=AFQjCNF2AhfF-R00mWMvTx9TbBKZPxDMYQ&bvm=bv.50500085,d.dGI
Connection: keep-alive
Google Analyticsで検索Queryが「not provided」となる本当の理由

Google Analyticsで検索Queryが「not provided」になっているのはGooglehttps化してRefererが取得できなくなったからだと色々なサイトで説明されていますが、正確には違う事が今回の実験で分かりました。Google先生httpsのページからの遷移でもRefererを送信していて、送信するRefererから意図的にq=のパラメータ箇所を削除しているという事です。なんで意図的にq=のパラメータを削除しているかというと...折角通信の暗号化をしているのに外部にユーザーがどういうQueryを実行したかを知らせたく無かったのではないでしょうか。

まとめ

  • Google先生の検索ページはhttpsでユーザーの通信の気密性を高めています。
  • Google先生の検索ページはhttpsでも遷移先のhttpページにRefererを送信しています。
  • 検索結果のリンクは302 FoundのRedirect処理をせずに、200OKのhtmlを返します。
  • 200OKのhtmlページでは本来の遷移先ページを読み込む処理が書かれていて、200OKのURLから遷移したようにRefererを送信しています。ただしRefererの検索Queryパラメータのq=は予め削除されています。
  • 遷移先がhttp、httpsのどちらのスキーマでもGoogle(https)からは検索Queryが取得できません。Google(http)からは取得できると思います。
  • q=のパラメータが削除されているのでGoogle AnalyticsGoogle先生からの遷移と分かっていてもキーワードが不明であるため「not provided」となっています。単にhttpsしたから「not provided」になっているのではありません。