Rubyでhttpリクエストを並列処理

# モチベーション
api叩く時、いちいちループ処理のなかで叩くと時間がかかる。どうせならhttpリクエストを一気にやって時間を短縮したい

# Typhoeusの紹介
rubyでhttpリクエストの並列化を調べて見た結果、typhoeusのgemが具合が良さそうである。Rubygem.orgでも1000万ダウンロードを超えてるし、まだメンテもされてそう。
## Typhoeusとは
libcurlのラッパーgem。そもそもlibcurlとはクライアントサイドのurl転送ライブラリであり、よく使うcurlコマンドはこのlibcurlをベースにして動く。つまりruby標準のNet::httpとは別に、curlコマンド的な物を実現するものと認識しておけばOK?ちなみにtypoeusとはティポーエウス(ティポーン)とはギリシャ神話に出てくる神(怪物)らしい。

# コードの例
[Rubyの並列HttpリクエストGem Typhoeusを使ってみる](https://qiita.com/m-dove/items/75fa9401a38f9e7f451b)
 

# 使い方の日本語訳

## 導入
Gemfile

```terminal
gem 'typhoeus'
```

## 簡単な例
1つのリクエストのみ

```ruby
Typhoeus.get("www.example.com", followlocation: true)
```
並列リクエスト

```ruby
hydra = Typhoeus::Hydra.new
10.times.map{ hydra.queue(Typhoeus::Request.new("www.example.com", followlocation: true)) }
hydra.run
```

## 文法
``typhoeus``の基本的なインターフェースは3つのクラスで構成される。``Request``,``Response``,``Hydra``である。 ``Request``はHTTPリクエストのオブジェクトを、``Response``はHTTPレスポンスを扱うクラスである。``Hydra``はHTTP接続を並列しておこなうクラスである。

```ruby
request = Typhoeus::Request.new(
  "www.example.com",
  method: :post,
  body: "this is a request body",
  params: { field1: "a field" },
  headers: { Accept: "text/html" }
)
```

最初の引数はurlで、次の一連の引数はオプションである。オプションは必要なときに書くと良い。ちなみにデフォルトでは``method``はgetである。

もしURLパラメーターを送りたい時は、``:params``ハッシュが使える。しかしもし``x-www-form-urlencoded``パラメーター経由でリクエスを送りたいときは``:body``ハッシュを代わりに使うこと。``:params``はURLパラメータのためにあり、``:body``はリクエストボディのためにある。

**プロキシをとおしてリクエストを送る**
オプションのリストにプロキシのurlを追加する。

```ruby
options = {proxy: 'http://myproxy.org'}
req = Typhoeus::Request.new(url, options)
```

もしプロキシが認証を要求する場合、``proxyuserpwd``オプションキーを加える。

```ruby
options = {proxy: 'http://proxyurl.com', proxyuserpwd: 'user:password'}
req = Typhoeus::Request.new(url, options)
```

このときusernameとpsswordはコロンで別れていることに注意。

クエリーをそれ自身かhydraを使って実装することもできる。

```ruby
request.run
#=> <Typhoeus::Response ... >
```

```ruby
hydra = Typhoeus::Hydra.hydra
hydra.queue(request)
hydra.run```

リクエストオブジェクトはリクエストがrunした後にセットされる。

```ruby
response = request.response
response.code
response.total_time
response.headers
response.body
```

### 単純なリクエストをおくる
typhoeusはシングルリクエストのためにいくつかの便利なメソッドがある。

```ruby
Typhoeus.get("www.example.com")
Typhoeus.head("www.example.com")
Typhoeus.put("www.example.com/posts/1", body: "whoo, a body")
Typhoeus.patch("www.example.com/posts/1", body: "a new body")
Typhoeus.post("www.example.com/posts", body: { title: "test post", content: "this is my test"})
Typhoeus.delete("www.example.com/posts/1")
Typhoeus.options("www.example.com")
```

#### putでbodyにパラメタを書いて送る
postを使う時はcontent-typeなどは自動的に'application/x-www-form-urlencoded'にセットされる。これはbodyを使うか使わないかにかかわらず、put,patch,headなどの他のメソッドには当てはまらない。postのような結果を得るために、content-typeを以下のように示さなければならない。

```ruby
Typhoeus.put("www.example.com/posts/1",
        headers: {'Content-Type'=> "application/x-www-form-urlencoded"},
        body: {title:"test post updated title", content: "this is my updated content"}
    )
```

### エラーを扱う
リクエストが成功したかどうか確認するためにtyphoeusにはエラーを扱うメソッドがある。コールバックはリクエストの後に実行される。リクエストをrunする前にコールバックの内容を実装することを忘れないように。

```ruby

request = Typhoeus::Request.new("www.example.com", followlocation: true)

request.on_complete do |response|
  if response.success?
    # hell yeah
  elsif response.timed_out?
    # aw hell no
    log("got a time out")
  elsif response.code == 0
    # Could not get an http response, something's wrong.
    log(response.return_message)
  else
    # Received a non-successful http response.
    log("HTTP request failed: " + response.code.to_s)
  end
end

request.run
```

これらは並列処理のブロックの中でも同様に動く。

### アップロードを扱う
ファイルはpostリクエストでサーバーにアップロードできる。typhoeusはファイル名をそのままでアップロードし、content typeをセットするためにMime::Typesを使う。

```ruby

Typhoeus.post(
  "http://localhost:3000/posts",
  body: {
    title: "test post",
    content: "this is my test",
    file: File.open("thesis.txt","r")
  }
)
```

### レスポンスボディをストリーミングする
レスポンスをストリームすることも可能である。レスポンスが巨大であることが予想される場合、リクエストに``on_body``をセットすること。typhoeusはレスポンスの塊(チャンク)をコールバックに渡す。もし``on_body``をセットした時、typhoeusは完全なレスポンスを保持しない。

```ruby
downloaded_file = File.open 'huge.iso', 'wb'
request = Typhoeus::Request.new("www.example.com/huge.iso")
request.on_headers do |response|
  if response.code != 200
    raise "Request failed"
  end
end
request.on_body do |chunk|
  downloaded_file.write(chunk)
end
request.on_complete do |response|
  downloaded_file.close
  # Note that response.body is ""
end
request.run
```

もしストリームを途中でやめたい時、``:abort``シンボルを``on_block``から返すことができる。例えば

```ruby
request.on_body do |chunk|
  buffer << chunk
  :abort if buffer.size > 1024 * 1024
end
```

これで適切にストリームは途中で止まり、もしreturnやthrowやraiseで邪魔されると起きるかもしれないメモリリークを防ぐことができる。

### 並列リクエストを送る
大抵はhydraを使ってリクエストをおくるべきである。

```ruby
hydra = Typhoeus::Hydra.hydra

first_request = Typhoeus::Request.new("http://example.com/posts/1")
first_request.on_complete do |response|
  third_url = response.body
  third_request = Typhoeus::Request.new(third_url)
  hydra.queue third_request
end
second_request = Typhoeus::Request.new("http://example.com/posts/2")

hydra.queue first_request
hydra.queue second_request
hydra.run # this is a blocking call that returns once all requests are complete
```

第1と第2のリクエストは作成され、キューされる。もしhydraがrunした時、第1と第2のリクエストを並列処理される。第1のリクエストが終わったら、第3のリクエストが作成されキューされる。この例では第1のリクエストの結果を元にしている。第3のリクエストがキューされた瞬間、hydraはそれを実行し始める。しばらくして第2のリクエストがrunを続ける(第1のリクエストの前に終わる可能性もある)。第3のリクエストが完了すると、``hydra.run``からreturnされる。

![図1.png](https://qiita-image-store.s3.amazonaws.com/0/208338/cd0faeef-2c85-79af-c3e6-d574744e8ece.png)


キューが実行された後に戻ってくるレスポンスの配列を取得する方法

```ruby
hydra = Typhoeus::Hydra.new
requests = 10.times.map {
  request = Typhoeus::Request.new("www.example.com", followlocation: true)
  hydra.queue(request)
  request
}
hydra.run

responses = requests.map { |request|
  request.response.body
}
```

### 最大の並列処理数を設定する
Hydra will also handle how many requests you can make in parallel. Things will get flakey if you try to make too many requests at the same time. The built in limit is 200. When more requests than that are queued up, hydra will save them for later and start the requests as others are finished. You can raise or lower the concurrency limit through the Hydra constructor.

```ruby
Typhoeus::Hydra.new(max_concurrency: 20)
```

### メモ化する
Hydraは1回のrunコールの中でリクエストをメモ化する。[メモ化](https://qiita.com/kk_Ataka/items/6bbc26159d2efdf6fbb9)はこの記事を参照。この時memoizationを有効化しなければならない。こうして1つのリクエストが発行される。しかし、両方のon_conpleteハンドラーが呼ばれる。

```ruby
Typhoeus::Config.memoize = true

hydra = Typhoeus::Hydra.new(max_concurrency: 1)
2.times do
  hydra.queue Typhoeus::Request.new("www.example.com")
end
hydra.run
```

下では2つのリクエストとなる。

```ruby
Typhoeus::Config.memoize = false

hydra = Typhoeus::Hydra.new(max_concurrency: 1)
2.times do
  hydra.queue Typhoeus::Request.new("www.example.com")
end
hydra.run
```

### キャッシュ
Typhoeusはキャッシュをサポートするビルドを含む。例えば次の例では、キャッシュ化されたオブジェクトがリクエストオブジェクトのon_completeハンドラーに渡されている。

```ruby
class Cache
  def initialize
    @memory = {}
  end

  def get(request)
    @memory[request]
  end

  def set(request, response)
    @memory[request] = response
  end
end

Typhoeus::Config.cache = Cache.new

Typhoeus.get("www.example.com").cached?
#=> false
Typhoeus.get("www.example.com").cached?
#=> true
```

Dalliを使う場合、

```ruby
dalli = Dalli::Client.new(...)
Typhoeus::Config.cache = Typhoeus::Cache::Dalli.new(dalli)
```

Railsを使う場合,

```ruby
Typhoeus::Config.cache = Typhoeus::Cache::Rails.new
```

Redisを使う場合

```ruby
redis = Redis.new(...)
Typhoeus::Config.cache = Typhoeus::Cache::Redis.new(redis)
```

3つの全てのアダプターは``default_ttl``というオプションの引数を持つ。これはキャッシュのTTLセットを持たないリクエストのために、キャッシュ化されたレスポンスのデフォルトのTTLをセットする。

### 直接スタブする
hydraによって特定のurlにスタブアウトできたり、テスト中にリモートサーバーにヒットするのを回避できたりする。

```ruby
response = Typhoeus::Response.new(code: 200, body: "{'name' : 'paul'}")
Typhoeus.stub('www.example.com').and_return(response)

Typhoeus.get("www.example.com") == response
#=> true
```

キューされたリクエストはスタブを当てることができる。正規表現でマッチしたurlも特定することもできる。

```ruby
response = Typhoeus::Response.new(code: 200, body: "{'name' : 'paul'}")
Typhoeus.stub(/example/).and_return(response)

Typhoeus.get("www.example.com") == response
#=> true
```

順次結果を返すスタブの配列を到底することもできる。

```ruby
Typhoeus.stub('www.example.com').and_return([response1, response2])

Typhoeus.get('www.example.com') == response1 #=> true
Typhoeus.get('www.example.com') == response2 #=> true

```
テストをする時、自身が期待するものをはっきりさせると、スタブがテストの間持続する。以下はspec_helper.rbファイルに自動的に行うために含めることができる。

```ruby
RSpec.configure do |config|
  config.before :each do
    Typhoeus::Expectation.clear
  end
end
```

### タイムアウトの設定
HTTPタイムアウトしてもexceptionは起きない。リクエストがタイムアウトしたかどうかは次のメソッドで確認できる。

```ruby
Typhoeus.get("www.example.com", timeout: 1).timed_out?
```

タイムアウトレスポンスはsuccess?メソッドもfalseを返す。

タイムアウトには2種類ある。``timeout``と``connecttimeout``である。``timeout``はリクエストが何秒でおわるのかの時間制限である。``connecttimeout``は単に接続段階が何秒で終わるのかの時間制限である.

さらに2つの細かなオプション``tiemout_ms``と``connecttimeout_ms``がある。これらのオプションはミリ秒の精度を提供するが、いつも使えるとは限らない。(リナックス上でnosignalがtrueになっていない時のインスタンスなど)

TODO 後で訳す
When you pass a floating point timeout (or connecttimeout) Typhoeus will set timeout_ms for you if it has not been defined. The actual timeout values passed to curl will always be rounded up.

DNS timeouts of less than one second are not supported unless curl is compiled with an asynchronous resolver.

The default timeout is 0 (zero) which means curl never times out during transfer. The default connecttimeout is 300 seconds. A connecttimeout of 0 will also result in the default connecttimeout of 300 seconds.



### リダイレクトを許可する

```ruby
Typhoeus.get("www.example.com", followlocation: true)
```

### Basic認証
```ruby
Typhoeus::Request.get("www.example.com", userpwd: "user:password")
```

### 圧縮
```ruby
Typhoeus.get("www.example.com", accept_encoding: "gzip")
```

上記はヘッダーに直接ハッシュを記述したときと異なる動作をする。

```ruby
Typhoeus.get("www.example.com", headers: {"Accept-Encoding" => "gzip"})
```

ヘッダーに直接書くと``--comressed``フラグがlibcurlコマンドに含まれない。だから、libcurlはレスポンスを解凍しない。もし``--compressed``フラグを自動的に加えたいのなら、``:accept-encoding``をオプションとして加えること。

### Cookies
```ruby
Typhoeus::Request.get("www.example.com", cookiefile: "/path/to/file", cookiejar: "/path/to/file")
```

``cookiefile``はクッキーが読まれるファイルで``cookiejar``は受け取ったクッキーが書き込まれるファイル。もしクッキーを有効にしたいのなら、2つのファイルパスを同じにしなければならない。

### 他のcurlのオプション
[ここ](http://rubydoc.info/github/typhoeus/ethon/Ethon/Easy/Options)を参照

### SSL

### デバッグのアウトプット

### デフォルトのユーザーエージェント

### specsをrunする

```ruby
bundle install
bundle exec rake
```

### Semantic Versioning
This project conforms to [sermver](http://semver.org/).

### LICENSE
(The MIT License)

Copyright © 2009-2010 Paul Dix

Copyright © 2011-2012 David Balatero

Copyright © 2012-2016 Hans Hasselberg

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.