1
0

More than 1 year has passed since last update.

Faradayを使ってJSONと画像ファイルのマルチパート送信を行う方法

Last updated at Posted at 2023-09-17

はじめに

TumblrではAPIを使って、記事情報と記事に使用する画像を同時にアップロードできます。

しかし、Rubyでそれを行う方法を見つけ出すのに苦労したため、ここに方法を書き残しておきます。

動作確認環境

  • Ruby 3.1.2
  • faraday 2.7.2
  • faraday-multipart 1.0.4

前提条件

OAuth2認証でアクセストークンを取得していること。

https://www.tumblr.com/docs/en/api/v2#oauth2-authorization

使用するAPI

Tumblr APIの/posts

https://www.tumblr.com/docs/en/api/v2#posts---createreblog-a-post-neue-post-format

APIドキュメントによると、こんな感じで記事本文のJSONデータと、添付画像をマルチパート送信する必要があるそうです。

{
    "content": [
        {
            "type": "image",
            "media": [
                {
                    "type": "image/jpeg",
                    "identifier": "some-identifier",
                    "width": 250,
                    "height": 200
                }
            ]
        }
    ]
}
--TumblrBoundary
Content-Disposition: form-data; name="json"
Content-Type: application/json

{JSON encoded parameters}
--TumblrBoundary
Content-Disposition: form-data; name="some-identifier"; filename="filename.jpg"
Content-Type: image/jpeg

{image data}
--TumblrBoundary--

コード例

こんな感じで送信できました。

require 'json'
require 'faraday'
require 'faraday/multipart'

access_token = 'your_access_token'

connection = Faraday.new("https://api.tumblr.com") do |conn|
  conn.request :authorization, 'Bearer', access_token
  conn.request :multipart
end

# JSONのパートを作成
content = {
  content: [
    {
      "type": "image",
      "media": [
        {
          "type": "image/jpeg",
          "identifier": "my_image" # この名前は任意
        }
      ]
    },
    {
      type: 'text',
      text: 'Hello, world!'
    },
  ]
}.to_json
json_part = Faraday::Multipart::ParamPart.new(content, 'application/json')

# 画像のパートを作成
file_path = '/path/to/my_image.jpg'
file_part = Faraday::Multipart::FilePart.new(
  file_path, "image/jpeg", File.basename(file_path))

# "my_image" は content の identifier と一致させる
payload = { json: json_part, my_image: file_part }

# Tumblrに投稿
response = connection.post(
  "/v2/blog/your-awesome-blog.tumblr.com/posts", payload)

if response.status.to_i == 201
  puts '投稿に成功しました。'
else
  puts '投稿に失敗しました。'
  puts "エラーコード: #{response.status}"
  puts "エラーメッセージ: #{JSON.parse(response.body)['meta']['msg']}"
end

備考: Faraday::UploadIO はFaraday 2.0では使えない

ネットを検索すると、「Faradayでマルチパート送信を行うときは Faraday::UploadIO クラスを使いましょう」という記事がいくつか見つかったのですが、Faraday 2.0では使えなくなっていました。

Faraday 2.0以降では別途 faraday-multipart gem をインストールして、 上のコードのように Faraday::Multipart::FilePart クラスを使う必要があるようです。

試行錯誤したけどうまくいかなったやり方あれこれ

いろいろ試行錯誤した結果、最終的に上で紹介したコードに落ち着きました。
ここから下は試行錯誤中にうまくいかなかった方法を紹介します。

oauth2 gem単体ではマルチパート送信 できなかった →できた

OAuth2認証をしてAPI連携するときはoauth2 gemが使えます。

画像アップロードを伴わない、テキストだけの記事作成であればoauth2 gemだけで送信できます。

consumer_key = 'your_consumer_key'
consumer_secret = 'your_consumer_secret'
access_token = 'your_access_token'

client = OAuth2::Client.new(
  consumer_key, consumer_secret, site: 'https://api.tumblr.com')
access_token_object = OAuth2::AccessToken.new(client, access_token)

params =  {
  headers: {
    'Content-Type' => 'application/json'
  },
  body: {
    content: [
      {
        type: 'text',
        text: 'Hello, world!'
      }
    ]
  }.to_json
}

response = access_token_object.post(
  "/v2/blog/your-awesome-blog.tumblr.com/posts", params)

if response.status.to_i == 201
  puts '投稿に成功しました。'
else
  puts '投稿に失敗しました。'
  puts "エラーコード: #{response.status}"
  puts "エラーメッセージ: #{JSON.parse(response.body)['meta']['msg']}"
end

oauth2 gemは内部的にFaradayを使っているので、うまくやればoauth2 gemからマルチパート送信できるのではないか?と思いましたが、結局うまくやる方法が見つからなかったので、直接Faradayを使う方法に切り替えました。

以下のようにすればoauth2 gemからもマルチパート送信できました。
とはいえ、これなら「Faradayを直接使ったコードと大差ないやん」って感じですが……。

consumer_key = 'your_consumer_key'
consumer_secret = 'your_consumer_secret'
access_token = 'your_access_token'

client = OAuth2::Client.new(consumer_key, consumer_secret, site: 'https://api.tumblr.com') do |conn|
  conn.request :multipart
end
access_token_object = OAuth2::AccessToken.new(client, access_token)

# JSONのパートを作成
content = {
  content: [
    {
      "type": "image",
      "media": [
        {
          "type": "image/jpeg",
          "identifier": "my_image" # この名前は任意
        }
      ]
    },
    {
      type: 'text',
      text: 'Hello, world!'
    },
  ]
}.to_json
json_part = Faraday::Multipart::ParamPart.new(content, 'application/json')

# 画像のパートを作成
file_path = '/path/to/my_image.jpg'
file_part = Faraday::Multipart::FilePart.new(
  file_path, "image/jpeg", File.basename(file_path))

params =  {
  body: {
    json: json_part,
    my_image: file_part
  }
}

response = access_token_object.post(
  "/v2/blog/your-awesome-blog.tumblr.com/posts", params)

if response.status.to_i == 201
  puts '投稿に成功しました。'
else
  puts '投稿に失敗しました。'
  puts "エラーコード: #{response.status}"
  puts "エラーメッセージ: #{JSON.parse(response.body)['meta']['msg']}"
end

OAuth1認証もマルチパート送信ができなかった

Tumbler APIのドキュメントではOAuth1認証とOAuth2認証の二通り認証方法が紹介されています。

https://www.tumblr.com/docs/en/api/v2#authentication

OAuth1認証の場合、 oauth gemを使って次のように画像アップロードを伴わない、テキストだけの記事作成は可能です。

require 'oauth'
require 'json'

access_token = 'your_access_token'
access_token_secret = 'your_access_token_secret'

consumer = OAuth::Consumer.new(
  consumer_key, consumer_secret, site: 'https://api.tumblr.com')
access_token = OAuth::AccessToken.new(
  consumer, access_token, access_token_secret)

params = [
  {
    content: [
      {
        type: 'text',
        text: 'Hello, world!'
      }
    ]
  }.to_json,
  {
    'Content-Type' => 'application/json'
  }
]
response = access_token.post(
  "/v2/blog/your-awesome-blog.tumblr.com/posts", *params)

if response.code.to_i == 201
  puts '投稿が成功しました。'
else
  puts '投稿に失敗しました。'
  puts "エラーコード: #{response.code}"
  puts "エラーメッセージ: #{JSON.parse(response.body)['meta']['msg']}"
end

しかし、oauth2 gemと同様、oauth gemでマルチパート送信する方法はわかりませんでした。

Faradayを使うにしても、Faraday 2.0ではoauth middlewareが使えなくなっており、Faraday 2.0以降ではどのように対応させればよいのかわかりませんでした。

# このコードは昔は動いたっぽいが、Faraday 2.0だと動かない
connection = Faraday.new("https://api.tumblr.com") do |conn|
  credentials = {
    consumer_key: consumer_key,
    consumer_secret: consumer_secret,
    token: access_token,
    token_secret: access_token_secret
  }
  conn.request :oauth, credentials
  #=> :oauth is not registered on Faraday::Request (Faraday::Error)

まとめ(?)

以上のような経緯から、JSONと画像ファイルのマルチパート送信を行う場合は、FaradayとOAuth2認証を組み合わせるのがベスト(というか、それ以外の方法がわからない)という結論になりました。

Faradayはバージョン2.0(2022年1月リリース)でかなり後方互換性を捨てた変更が入っており、それ以前に書かれたネット記事のコードはそのままでは使えないケースがあるので注意が必要です。

しかも、「その機能はFaraday 2.0では削除されたよ」って言われても、代替方法がよくわからないケースもあるので、「じゃあどうすればいいの!!」と迷子になってしまいがちです。

ちょっとニッチかもしれませんが、今回の僕と似たようなユースケースに遭遇した人はこの記事の内容を参考にしてみてください。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0