Stravaから zoom 12 以上のheatmapタイルを取得する

# ブラウザでタイルを取得する

[stravaからglobal heat mapのデータを頂く](https://qiita.com/Tomonobu3110/items/88d3815e463cfdab498d) で言及しましたが
StravaのHeatmapタイルでZoom 12以上を指定すると
認証(authentication)が必要である旨が表示されます。

```console
$ curl https://heatmap-external-b.strava.com/tiles/ride/hot/12/3636/1610.png
authentication is now required for data access beyond zoom level 12, please login to strava.com and then visit /auth
```

Stravaにログインした後に
指示の通り /auth を訪れると cookie をセットした旨のメッセージが表示され
タイル取得の準備が完了となります。

https://heatmap-external-b.strava.com/auth にブラウザでアクセス

```
Logged in as AthleteId(XXXXXXX), cloudfront cookies set
```
※IDは伏せました

未ログイン時は

```
not logged in
```

多くのcookieを保存しているようですが、
タイル取得に必要なものは以下の3つのようです。

- CloudFront-Key-PairId
- CloudFront-Policy
- CloudFront-Signature

2019-09-19 14:47(JST) の取得で、expireは
2019-09-26 05:47(UTC) = 14:47(JST) に設定されているので賞味期限は7日間
(結構長いじゃん！)

この状態でタイルを取得します。
URLが異なっている (tiles-auth) なので気を付けてください。

URLの例
https://heatmap-external-b.strava.com/tiles-auth/ride/hot/12/3636/1610.png
https://heatmap-external-b.strava.com/tiles-auth/ride/hot/13/7272/3220.png
https://heatmap-external-c.strava.com/tiles-auth/ride/hot/13/7272/3220.png?px=256

# 自動化の方針

課題は Strava にログインし、必要なcookieを取得する事

方針は色々ありますが...

1. curlで突破
2. mechanizeなどのスクレイピングで突破 --> cookie保存 --> そのcookieをcurlに食わす
3. mechanizeなどのスクレイピングで突破 --> そのまま png (= tile) も取得

今回は 3 でやってみます

# 自動ログイン＆タイル取得コード (Perl)

Perl + Mechanize で組んでみました
試行錯誤のあとの残るコードですが、主要部分を以下に示します

ポイントとなると思われる部分をコメントで記述してあります。
それぞれのポイントの解説はコードの後に。
  
```perl
(略)
use IO::Socket::SSL qw( SSL_VERIFY_NONE );
use IO::Socket::SSL qw( debug4 ); # ★ポイント(1)★ SSLのハンドシェイクのデバッグに有効
...
my $URL_STRAVA_LOGIN     = "https://www.strava.com/login";
my $URL_STRAVA_TILE_AUTH = "https://heatmap-external-b.strava.com/auth";
my $URL_STRAVA_TILE_PNG  = "https://heatmap-external-b.strava.com/tiles-auth/ride/hot/15/29088/12880.png";

...

### Mechanize Object Creation
my $mech = WWW::Mechanize->new(
	cookie_jar => {},
	autocheck  => 1,
	ssl_opts   => { 
		#verify_hostname => 0,    # 0 = disable SSL host check
		#SSL_verify_mode => SSL_VERIFY_NONE, 
		#SSL_version     => 'TLSv1_2',  # SSL version TLSv1/TLSv1_1/TLSv1_2
		#SSL_hostname    => 'www.strava.com',
		SSL_hostname    => 'heatmap-external-b.strava.com', # ★ポイント(2)★ SSL_hostnameの設定！！
	},
);
#$mech->proxy(['http', 'https'], undef);
$mech->agent_alias('Linux Mozilla');

...

### Strava login page
$mech->get($URL_STRAVA_LOGIN);
print_result($mech); # print_resultは自前の関数。なくてもよい

### input Strava's credential (user/pass) # ★ポイント(3)★ Facebook連動ログインはあきらめた
$mech->submit_form(
	form_number => 1,
	fields      => {
		email => $user,
		password => $pass,
	}
);

...

### Strava auth for tile
$mech->get($URL_STRAVA_TILE_AUTH); # ★ポイント(4)★ タイルのauth urlに一度訪問する
print_result($mech);

### Strava tile
$mech->get($URL_STRAVA_TILE_PNG); # ★ポイント(5)★ authタイル取得URLは "tiles-auth" なので注意！
open(OUT, "> tile.png");
print OUT $mech->content;
close(OUT);

(略)
```

## ポイント(1) SSLのハンドシェイクのデバッグに有効

コードが動作した後は不要ですが、デバッグ中は有効にしておくと
SSLのハンドシェイクのどこで失敗したかがわかるので非常に有効でした。

```perl
use IO::Socket::SSL qw( debug4 ); # ★ポイント(1)★ SSLのハンドシェイクのデバッグに有効
```

## ポイント(2) SSL_hostnameの設定 【重要】

一番のポイントでした
これがないと 14094410 エラーが発生してアクセスできません。
解決の手がかりはこちらでした。
[OpenSSL APIメモ（handshake failure）](http://work-in-progress.hatenablog.com/entry/2017/12/03/113552)
SNIへの対応が必要です。
具体的にはサーバ側にhostnameをわたす必要があります。
Mechanize(というか IO::Socket::SLL)での指定は以下になります。

```perl
my $mech = WWW::Mechanize->new(
	cookie_jar => {},
	autocheck  => 1,
	ssl_opts   => { 
		#verify_hostname => 0,    # 0 = disable SSL host check
		#SSL_verify_mode => SSL_VERIFY_NONE, 
		#SSL_version     => 'TLSv1_2',  # SSL version TLSv1/TLSv1_1/TLSv1_2
		#SSL_hostname    => 'www.strava.com',
		SSL_hostname    => 'heatmap-external-b.strava.com', # ★ポイント(2)★ SSL_hostnameの設定！！
	},
);
```

その他オプションについてはこちらを参照のこと
https://metacpan.org/pod/IO::Socket::SSL

## ポイント(3) Facebook連動ログインはあきらめ、Strava user/pass でログイン

Facebookのページに飛んだあとどうしてもスマホにPINコードが飛んでこず
Facebookアカウントでのログインはあきらめました。
Stravaにパスワード設定して、email + passでログイン

```perl
$mech->submit_form(
	form_number => 1,
	fields      => {
		email => $user,
		password => $pass,
	}
);
```

## ポイント(4) タイルのauth urlに一度訪問する

Strava様の指示通りに...

```perl
my $URL_STRAVA_TILE_AUTH = "https://heatmap-external-b.strava.com/auth";
$mech->get($URL_STRAVA_TILE_AUTH); # ★ポイント(4)★ タイルのauth urlに一度訪問する
```

## ポイント(5) authタイル取得URLは "tiles-auth" なので注意

Zoom = 11までのタイルとはURLが異なるので注意してください

```perl
my $URL_STRAVA_TILE_PNG  = "https://heatmap-external-b.strava.com/tiles-auth/ride/hot/15/29088/12880.png";
$mech->get($URL_STRAVA_TILE_PNG); # ★ポイント(5)★ authタイル取得URLは "tiles-auth" なので注意！
```

以上です