あけましておめでとうございます!
今年の目標は「半人前卒業」の筆者です。よろしくお願いします。
というわけで、早速昨年末から持ち越した宿題を消化します。
何かというと、GoogleChartで作成したQRコードの画像を、ローカルに保存せず直接APIに投げようとしたところ、思いの外ハマってしまった件。
備忘録も兼ねてまとめていきます。あいかわらず稚拙な文章ですがご容赦ください。
前提
- open-uriを使ってURLから画像ファイルを操作する
(今回はGoogle chartで画像を作成する際のURLを使います) - multipart/form-dataのリクエストで画像ファイルをAPIに送る
環境
Ruby : 2.4.2
Rails : 5.2.0
Net/Httpを用いて実装を試みるもエラー
最初の実装はこんな感じ。
(参考:覚え書き: Ruby で net/http を使ってファイルなどを multipart で POST する)
require 'open-uri'
require 'net/http'
def qrcode_request
image_url = "http://chart.apis.google.com/chart?xxx"
url = URI.parse("#{Settings.api.url}") #settings.ymlに記載されたAPIのURL
file = open(image_url)
data = [ "file", file ]
req = Net::HTTP::Post.new(url.path)
req.set_form(data, "multipart/form-data")
req.basic_auth('username', 'password')
res = Net::HTTP.start(url.host, url.port) do |http|
http.request(req)
end
end
# 実行結果
# no implicit conversion of nil into String excluded from capture: DSN not set
エラーについて調べてみると、似た状況について触れている以下のページを発見しました。
【ruby】openメソッドで画像を開いた時にno implicit conversion of nil into Stringエラーが発生する。
これによると、OpenURIの仕様で、10KB以下のファイルを選択するとStringIOクラスのオブジェクト、それ以上だとTempFileが生成されることに起因するエラーのようです。
こちらを参考にTempFileを生成するよう修正し、もう1度試します。
結果…変化せず。
HTTPクライアントを変えてみる
上記の方法で改善しなかった原因はよくわからないまま、とにかくパラメータを上手く送る方法がないかな〜ということで、Net/Http以外のHTTPクライアントを導入した実装方法を試します。
Faraday
gem install faraday
def qrcode_request_faraday
image_url = "http://chart.apis.google.com/chart?xxx"
conn = Faraday.new(:url => "#{Settings.api.url}") do |builder|
builder.request :multipart
builder.use Faraday::Request::BasicAuthentication, "username","password"
builder.adapter Faraday.default_adapter
end
res = conn.post, {
:image => OpenURI.open_uri(image_url) { |io|
Faraday::UploadIO.new(io,'image/png', 'qr.png')
}
}
end
実行結果…API側で400エラー。
Rest-Client
gem install rest-client
def qrcode_request_rest_client
image_url = "http://chart.apis.google.com/chart?xxx"
@url = "#{Settings.api.url}"
file = open(image_url)
request = RestClient::Request.new(
:method => :post,
:url => @url,
:user => "username",
:password => "password",
:payload => {
:multipart => true,
:image => file
})
response = request.execute
end
実行結果…こちらも400エラー。
OpenURIの仕様を調べる
HTTPクライアントの変更で解決しなかったので、OpenURI周辺をもう一度調べてみたところ、OpenURIはそもそもGET以外のメソッドを使えない仕様であるという情報を入手しました。
How do I make a POST request with open-uri? -Stack Overflow
一応、POSTができない問題を解消するパッチは存在するようです。
open-uriは便利だがGETしかできないので何とかする話
しかし、この中でmultipart/formdataを指定して画像ファイルを直接リクエストとして送る方法は残念ながら見つかりませんでした。
結論
URLから取得した画像をローカルに保存せず直接APIに投げるときは、multipartは使わず、画像をエンコードしてJSONなどで送信し、API側でデコードするのがベターなようです。
より良い方法をご存知の方はコメントいただければ幸いです…。
2019/01/18追記
multipart-postというgemを導入したところ、画像ファイルを直接リクエストとしてうまく送ることができました。
gem install multipart-post
require 'net/http/post/multipart'
def qrcode_request_multipart_post
image_url = "http://chart.apis.google.com/chart?xxx"
url = URI.parse("#{Settings.api.url}")
file = open(image_url)
req = Net::HTTP::Post::Multipart.new(url.path, "image" => UploadIO.new(file, "image/png", "qr.png"))
req.basic_auth("username", "password")
res = Net::HTTP.new(url.host, url.port).start do |http|
http.request(req)
end
end
どうやらOpenURIのPOSTも可能なようです。
API側でCarrierWaveを導入すれば、受け取った画像ファイルの表示、アップロードなどが可能になります。