URLから取得した画像ファイルを直接multipartでPOSTできるか試す【Ruby】

あけましておめでとうございます！
今年の目標は「半人前卒業」の筆者です。よろしくお願いします。

というわけで、早速昨年末から持ち越した宿題を消化します。
何かというと、GoogleChartで作成したQRコードの画像を、ローカルに保存せず直接APIに投げようとしたところ、思いの外ハマってしまった件。
備忘録も兼ねてまとめていきます。あいかわらず稚拙な文章ですがご容赦ください。

## 前提
* open-uriを使ってURLから画像ファイルを操作する<br>
(今回は[Google chart](https://developers.google.com/chart/)で画像を作成する際のURLを使います)
* multipart/form-dataのリクエストで画像ファイルをAPIに送る

#### 環境
Ruby : 2.4.2
Rails : 5.2.0

## Net/Httpを用いて実装を試みるもエラー
最初の実装はこんな感じ。
(参考:[覚え書き: Ruby で net/http を使ってファイルなどを multipart で POST する](http://masamitsu-murase.blogspot.com/2017/01/ruby-nethttp-multipart-post.html))

```ruby:qrcode.rb
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エラーが発生する。](https://teratail.com/questions/102996)
これによると、OpenURIの仕様で、10KB以下のファイルを選択するとStringIOクラスのオブジェクト、それ以上だとTempFileが生成されることに起因するエラーのようです。

こちらを参考にTempFileを生成するよう修正し、もう1度試します。
結果…変化せず。

## HTTPクライアントを変えてみる

上記の方法で改善しなかった原因はよくわからないまま、とにかくパラメータを上手く送る方法がないかな〜ということで、Net/Http以外のHTTPクライアントを導入した実装方法を試します。

### Faraday
https://github.com/lostisland/faraday

```
gem install faraday
```

```qrcode_faraday.rb
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
https://github.com/rest-client/rest-client

```
gem install rest-client
```

```qrcode_rest_client.rb
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
](https://stackoverflow.com/questions/242602/how-do-i-make-a-post-request-with-open-uri)

一応、POSTができない問題を解消するパッチは存在するようです。
[open-uriは便利だがGETしかできないので何とかする話](http://blog.livedoor.jp/sparklegate/archives/50288860.html)

しかし、この中でmultipart/formdataを指定して画像ファイルを直接リクエストとして送る方法は残念ながら見つかりませんでした。

## 結論
URLから取得した画像をローカルに保存せず直接APIに投げるときは、multipartは使わず、画像をエンコードしてJSONなどで送信し、API側でデコードするのがベターなようです。
より良い方法をご存知の方はコメントいただければ幸いです…。

#### 2019/01/18追記
[multipart-post](https://github.com/nicksieger/multipart-post)というgemを導入したところ、画像ファイルを直接リクエストとしてうまく送ることができました。


```
gem install multipart-post 
```

```qrcode_multipart_post.rb
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を導入すれば、受け取った画像ファイルの表示、アップロードなどが可能になります。