背景
Rubyの標準ライブラリであるnet/httpを用いたHTTPリクエストって、Net::HTTP.newしたりNet::HTTP::Get.newしたりhttp.start {...}したり色々と実装が面倒臭いなとずっと思っていたんですが、実は1行で書けることを知ったので open-uri との比較も含めて少しまとめてみました。
環境
- Ruby 2.6.0
目次
- 一番シンプルな方法
- リクエストヘッダを指定する方法
- Basic認証を使う方法
- Proxyを挟む方法
-
open-uriを使う方法
一番シンプルな方法
GET
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
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
シンプルな書き方
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
よく紹介される書き方
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
シンプルな書き方
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
よく紹介される書き方
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オブジェクトを使う方法
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を使う方法
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を挟む方法
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する
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を使う方法
require "open-uri"
open("https://jsonplaceholder.typicode.com/todos/1").read # response body
URI#openを使う方法
require "open-uri"
uri = URI.parse("https://jsonplaceholder.typicode.com/todos/1")
uri.open.read # response body
リクエストヘッダを指定する方法
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が必要なので、特に理由が無ければ前者を使っていきたい。