RubyのHTTPリクエストをできるだけシンプルに実装する

## 背景
Rubyの標準ライブラリである`net/http`を用いたHTTPリクエストって、`Net::HTTP.new`したり`Net::HTTP::Get.new`したり`http.start {...}`したり色々と実装が面倒臭いなとずっと思っていたんですが、実は1行で書けることを知ったので `open-uri` との比較も含めて少しまとめてみました。

## 環境
- Ruby 2.6.0

## 目次
1. 一番シンプルな方法
2. リクエストヘッダを指定する方法
3. Basic認証を使う方法
4. Proxyを挟む方法
5. `open-uri` を使う方法

## 一番シンプルな方法
### GET
```ruby
require "net/http"

uri = URI.parse("https://jsonplaceholder.typicode.com/todos/1")
response = Net::HTTP.get_response(uri)

response.code # status code
response.body # response body
```

#### ポイント
- `Net::HTTP.get_response(uri)`の引数は`URI`クラスのインスタンスのみ。文字列のURLを入れると`NoMethodError: undefined method 'hostname'`とかいうエラーが返る。
- response.code は数字が文字列型で返ってくる（`"200"`みたいな）。
- response.body は文字列型なので、JSONレスポンスの場合はパースする必要がある。

### POST
```ruby
require "net/http"

params = { title: "my task" }
uri = URI.parse("https://jsonplaceholder.typicode.com/todos")
response = Net::HTTP.post_form(uri, params)

response.code # status code
response.body # response body
```

#### ポイント
- `Net::HTTP.post_form` 第一引数に`URI`クラスのインスタンス、第二引数にハッシュ形式でリクエストボディを渡す。
- `Content-Type` は `application/x-www-form-urlencoded` 形式になる。
- params の中に File オブジェクトを入れてみたが、文字列に変換されてしまった。

## リクエストヘッダを指定する方法
### GET
#### シンプルな書き方
```ruby
require "net/http"

uri = URI.parse("https://jsonplaceholder.typicode.com/todos/1")
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = uri.scheme === "https"

headers = { "Content-Type" => "application/json" }
response = http.get(uri.path, headers)

response.code # status code
response.body # response body
```

#### よく紹介される書き方

```ruby
require "net/http"

uri = URI.parse("https://jsonplaceholder.typicode.com/todos/1")
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = uri.scheme === "https"

headers = { "Content-Type" => "application/json" }
req = Net::HTTP::Get.new(uri.path)
req.initialize_http_header(headers)

response = http.request(req)

response.code # status code
response.body # response body
```

#### ポイント
- `Net::HTTP.new`の第一引数にホスト名、第二引数にポートを渡す。
- httpsで始まるURLにアクセスする場合は`Net::HTTP#use_ssl=`に`true`を渡す。
- `Net::HTTP#get`　の第一引数にパス名、第二引数にリクエストヘッダを渡す。
- `Net::HTTP::Get` を使う場合は、初期化時の第一引数にはパス名を渡し、`Net::HTTP::Get#initialize_http_header` にハッシュを渡せばリクエストヘッダを指定できる。
- `Net::HTTP::Get#[]=` でもヘッダを指定できる。
  - `req["Content-Type"] = "application/json"` みたいな。

### POST
#### シンプルな書き方
```ruby
require "net/http"
require "json"

uri = URI.parse("https://jsonplaceholder.typicode.com/todos")
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = uri.scheme === "https"

params = { name: "Takuya" }
headers = { "Content-Type" => "application/json" }
response = http.post(uri.path, params.to_json, headers)

response.code # status code
response.body # response body
```

#### よく紹介される書き方
```ruby
require "net/http"

uri = URI.parse("https://jsonplaceholder.typicode.com/todos")
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = uri.scheme === "https"

params = { name: "Nobuto" }
headers = { "Content-Type" => "application/json" }

req = Net::HTTP::Post.new(uri.path)
req.set_form_data(params)
req.initialize_http_header(headers)

response = http.request(req)

response.code # status code
response.body # response body
```

#### ポイント
- `Net::HTTP#post`の第一引数にパス名、第二引数にリクエストボディ、第三引数にリクエストヘッダを渡す。
- `Net::HTTP#post`を使う場合、リクエストボディは文字列を渡す。
- `Net::HTTP::Post#set_form_data`を使う場合、リクエストボディはハッシュを渡す。
- `Content-Type`を指定しなければ自動で`application/x-www-form-urlencoded`が指定される。

## Basic認証を利用する方法
### URIオブジェクトを使う方法
```ruby
require "net/http"

uri = URI.parse("https://username:password@jsonplaceholder.typicode.com/todos/1")
response = Net::HTTP.get_response(uri)

response.code # status code
response.body # response body
```

#### ポイント
- `URI.parse`に渡すURLを`プロトコル://ユーザー名:パスワード@ホスト名・パス名`形式で書けばOKなので、多分これが一番シンプル。

### `Net::HTTP::Get#basic_auth`を使う方法
```ruby
require "net/http"
username, password = "username", "password"
uri = URI.parse("https://jsonplaceholder.typicode.com/todos/1")
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = uri.scheme === "https"

req = Net::HTTP::Get.new(uri.path)
req.basic_auth(username, password)
response = http.request(req)

response.code # status code
response.body # response body
```

## Proxyを挟む方法
```ruby
require "net/http"

base_uri = URI.parse("https://jsonplaceholder.typicode.com/todos/1")
proxy_uri = URI.parse("http://username:password@proxy-server.com")

http = Net::HTTP.new(base_uri.host, base_uri.port, proxy_uri.host, proxy_uri.port, proxy_uri.username, proxy_uri.password)
http.use_ssl = base_uri.scheme === "https"
response = http.get(base_uri.path)

response.code # status code
response.body # response body
```

#### ポイント
- `Net::HTTP.new`の第三引数以降にプロキシサーバーのホスト名、ポート、ユーザー名、パスワードを渡せば、他は通常のHTTPリクエストと同様に書ける。

## `Net::HTTPResponse`について
余談ではあるが、レスポンスとして返ってくる`Net::HTTPResponse`オブジェクトのメソッドについて見てみたい。

### `Net::HTTPResponse#body` -> String
- 文字列型でレスポンスボディを返す。

### `Net::HTTPResponse#code` -> String
- 文字列型でステータスコードを返す。

### `Net::HTTPResponse#http_version` -> String
- 文字列型でHTTPのバージョンを返す。

### `Net::HTTPResponse#message` -> String
- 文字列型でステータスコードのメッセージを返す。

### `Net::HTTPResponse#value` -> nil
- レスポンスコードが2xxの場合は`nil`を返す。
- レスポンスコードが3xxの場合は例外`HTTPRetriableError`を`raise`する
- レスポンスコードが4xxの場合は例外`HTTPServerException`を`raise`する
- レスポンスコードが5xxの場合は例外`HTTPFatalError`を`raise`する
- レスポンスコードが1xxもしくは未知のコードの場合は例外`HTTPError`を`raise`する

```ruby
require "net/http"

uri = URI.parse("https://jsonplaceholder.typicode.com/todos/1")
response = Net::HTTP.get_response(uri)

p response.code # -> "200"
p response.body # -> "{ \"userId\": 1, \"id\": 1, \"title\": \"delectus aut autem\", \"completed\": false}"
p response.http_version # -> "1.1"
p response.message # -> "OK"
p response.value # -> nil
```
なんで`#value`ってプロパティのようなメソッド名なのに例外を`raise`するっていう処理内容なのかちょっと謎。

## `open-uri` を使う方法
### `Kernel.#open`を使う方法
```ruby
require "open-uri"

open("https://jsonplaceholder.typicode.com/todos/1").read # response body
```

### `URI#open`を使う方法
```ruby
require "open-uri"

uri = URI.parse("https://jsonplaceholder.typicode.com/todos/1")
uri.open.read # response body
```

### リクエストヘッダを指定する方法
```ruby
require "open-uri"

open("https://jsonplaceholder.typicode.com/todos/1",
  "User-Agent" => "Ruby/#{RUBY_VERSION}"
).read # response body
```

#### ポイント
- `open-uri`はGETリクエストのみ対応。
- `open-uri`を`require`すると`URI`に`URI#open`メソッドが追加される。
- `open`メソッドの第二引数以降にキーワード引数形式でリクエストヘッダを指定できる。
- ステータスコードが2xxでなかった場合には`OpenURI::HTTPError`を`raise`する。

## 感想
- 何の変哲も無いGET/POSTリクエストについては`Net::HTTP.get_response`や`Net::HTTP.post_form`がシンプルなので積極的に使っていきたい。
- ちょっとカスタマイズする必要がある場合は`Net::HTTP#get`や`Net::HTTP#post`を使う。ほとんどの場合`Net::HTTP::Get`や`Net::HTTP::Post`を使う必要なさそう。
- Rails環境では`net/http`が`require`なしで実行できる一方で`open-uri`は`require`が必要なので、特に理由が無ければ前者を使っていきたい。

## 参考
- [Ruby リファレンスマニュアル > Net::HTTP](https://docs.ruby-lang.org/ja/latest/class/Net=3a=3aHTTP.html)
- [Ruby リファレンスマニュアル > open-uri](https://docs.ruby-lang.org/ja/latest/library/open=2duri.html)