高速でダウンロードできるツールを Go で実装した。

[Code-Hex/pget - GitHub](https://github.com/Code-Hex/pget)　　[![GitHub stars](https://img.shields.io/github/stars/Code-Hex/pget.svg?style=social&label=Star)](https://github.com/Code-Hex/pget)
> Parallel file download client

linux カーネルのダウンロードが約1分で終わる様子
[![asciicast](https://asciinema.org/a/a505e9fpkdpd7ycefyjs3h5bb.png)](https://asciinema.org/a/a505e9fpkdpd7ycefyjs3h5bb)

## これは何なのか:question:
簡単に言うと pget は Go 言語製の高速ダウンロードツールです。 wget っぽく気軽に実行できるように pget と名付けました。

## 作成の経緯:beer:
たまたま Nodejs で Range header に対してスレッドを使って分割ダウンロードするという [ここの記事](http://www.leeroy.me/download-a-file-using-multiple-threads-in-node-js/) を見かけたので今回これを並列処理を得意とする Go 言語で実装してみようと思い作成しました。

調べてみると [Python](https://gist.github.com/chandlerprall/1017266) や、[Java](http://luugiathuy.com/2011/03/download-manager-java/) などのほとんどの言語ですでに実装されてるようですが、どれもただ使えればいいという感じだったので、割と本気で作ってみました。（調べられてないだけの可能性高いです:sweat:）

## 仕組み:bulb:
ファイルをダウンロードする時、ほとんどの場合がリダイレクトを行いファイルを配信するための URL をクライアントへ渡してダウンロードが行われます。この理由から、 pget では初めにリダイレクト先の URL を取得し、その URL へ複数の goroutine を使用して、 Range access を行いファイルを分割でダウンロードして最後にそれらの分割されたファイルを結合し、元のファイルを作成するといった方法をとっています。 

### Range access:satellite:
Range access は「どの位置からどの位置までダウンロードする」と要求を送ることでその範囲分のファイルをダウンロードするといったものです。よくブラウザなどでダウンロードを中断した時に、後からダウンロードを再開できるレジューム機能に使われています。
Range access に対応しているサーバへアクセスするとそのヘッダには

    Accept-Ranges: bytes

という記述が存在します。この時クライアント側で Range access するには

    Range: bytes=0-999

と指定して要求を行います。 [こちら](http://tnamao.hatenablog.com/entry/20100617/p1) のサイトの解説がとても解りやすいです。

### goroutine による分割並列ダウンロード:arrow_down:
Range access でファイルの範囲を指定してダウンロードできるということを説明しました。Range access の仕組みを利用すれば分割でダウンロードすることができます。その上それらの分割ファイルを同時にダウンロードすることができれば、**理想時間**としてその分割した個数倍ダウンロード速度が速くなるということになります。

### 結合:twisted_rightwards_arrows:
pget では分割されたファイル名の末尾に番号が振られます。ダウンロードが完了した後からのファイルを作成し、分割ダウンロードされたファイル名の番号の昇順でからのファイルに書き込んでいきます。そうすることで元のファイルが作成されます。これが分割並列ダウンロードクライアントの仕組みです。

## pget の特徴:exclamation:
記事作成時 (7/13) の pget には次のような特徴があります。

ダウンロードを途中で中断した場合でも、ダウンロードを再開できる。  [![resume.gif](https://qiita-image-store.s3.amazonaws.com/0/118068/da97eb6e-de5e-0033-b44d-4012ecad861f.gif)](https://github.com/Code-Hex/pget)

好きなディレクトリへ名前をつけて保存ができる。
[![output.gif](https://qiita-image-store.s3.amazonaws.com/0/118068/02bd365a-655d-c6e9-1c16-d7ff17ab9e82.gif)](https://github.com/Code-Hex/pget)

他にも

- タイムアウト設定ができる
- pget の最新版がリリースされているか確認ができる
- Windows でも動く


個人的に高速でダウンロードができて、レジュームができるのは強いと思ってます:
## 工夫点:bangbang:
かなり頑張ったのが error 処理です。分割ダウンロードの際、 goroutine は非同期での処理になるので、もし一つの goroutine でエラーが起こった場合全ての goroutine を終了させなければいけません。そしてそのエラーの原因を取得しなければいけないのですが、これが難しかった...

これらは :package: [context](https://godoc.org/golang.org/x/net/context) を利用することで解決できました。詳しくは

https://github.com/Code-Hex/pget/blob/master/requests.go#L67
https://github.com/Code-Hex/pget/blob/master/ch.go

のコードを読んだ方が把握しやすいと思います:+1:


## インストール:tada:
Homebrew

    brew tap Code-Hex/pget
    brew install pget

go get

    $ go get -u github.com/Code-Hex/pget/cmd/pget

[バイナリ](https://github.com/Code-Hex/pget/releases)配布も行っています！

## 考察
-p オプションで起動する goroutine を指定することができますが、搭載されているCPUの数(ハイパースレッディングも考慮する)よりもスケールはしません。なので -p 100 とかしてもそこまで高速化されません。しかし僕のノートブックだと 4 ですが、分割ダウンロードということもあって -p 4 よりも -p 6 の方が早かったりします。CPUの数より少し大きい方が早いようです。

補足ですが、あまりにもたくさんの数で実行するとサーバーに負荷がかかるのでほどほどにお願いします。

## 最後に
もしよかったら [pget](https://github.com/Code-Hex/pget) で Star をつけてもらえるとモチベーションが上がります。そしてなにより、Go 言語を始めたばかりですので、まだおかしい部分があるはずです。そういった部分を issue や PR、Qiita のコメントで教えてもらえるととても嬉しいです。
pget を是非使ってみてください！