ユニファ開発者ブログ

ユニファ株式会社システム開発部メンバーによるブログです。

RailsアプリからGoogleフォトに写真をアップロードしてみる

こんにちは、Webエンジニアの本間です。

昨年も予定通り12月25日に Ruby 2.5 がリリースされましたね。 個人的に嬉しいのは、ブロックパラメーターを経由したメソッド呼び出しが3倍早くなった改善です。 def foo(a, &b) のようなメソッド定義はブロックを受け取ることが明示されていて好きなのですが、今回の改修でこのような書き方をやりやすくなったなぁと思っております。

さて今回は、弊社サービスの「写真サービス るくみー」と「Googleフォト」が連携できないか?という調査を兼ねて、RailsアプリケーションからGoogleフォトに写真をアップロードしてみようと思います。

作成するアプリケーション

f:id:ryu39:20180129172225p:plain

サンプルコード

今回使用しているアプリケーションのソースコードは、以下のリポジトリで公開しています。 bitbucket.org

ローカルで動かすためには Encrypted Secrets を開き、Google APIの client_idclient_secret をセットする必要がありますが、それさえ実施していただければ動くと思います。

google:
  api:
    client_id: your_client_id
    client_secret: your_client_secret

サンプルアプリケーション

herokuを使って、上記サンプルコードのアプリケーションを動かしています。

https://fathomless-stream-32917.herokuapp.com/

突然止まる可能性はありますが、2-3ヶ月は動かしておきますので、気になる方は触っていただければと思います。

準備

Google API呼び出し用のCredentialを登録

Googleディベロッパーコンソール > APIs & Services > Credentials でOAuth用のCredentialを登録します。

  1. 右上のCreate Credentialsから「OAuth client ID」を選択
  2. Application Typeに「Web Application」を選択
  3. Authorized redirect URIsは、OAuthの認可が終わった後にコールバックさせるURLを登録する。今回はlocalhostでのテストのみを想定しているので http://localhost:3000/oauth2/callback を登録。localhost以外でテストする場合、そのURLも登録しておく。Authorized JavaScript originsは、Authorized redirect URIsのパスなしの値を指定しておく。
  4. この情報で作成すると画面内に「Client ID」と「Client secret」が表示されるのでメモ

下記に作成時の画面のスナップショットを貼っておきます。

f:id:ryu39:20180129144118p:plainf:id:ryu39:20180129171410p:plain

写真一覧表示アプリケーションの作成

こんな感じに登録された写真を一覧表示するアプリケーションを作っておきます。

f:id:ryu39:20180129145547p:plain

これは、過去に紹介した Rails 5.2で追加予定のActiveStorageを使ってみる を参考にすれば、比較的サクッと作れるのではないかなぁと思います。

Googleフォトへ写真をアップロード

現在、Googleフォト専用のAPIは提供されてないようです。 しかし、Googleフォトに統合されたPicasaの Picasa Web Albums Data API は残っており、こちらを使ってGoogleフォトに写真をアップロードできるようです。 今回、このAPIを使って写真をアップロードしてみます。

アクセストークンの取得

PicasaのAPIを呼び出すためには、OAuth2のフローにしたがってアクセストークンを取得する必要があります。 サーバーサイドで取得するためには こちらのドキュメント が参考になります。

認可画面へのリダイレクト

まずユーザーから認可をもらうため、Googleの認可用のページにリダイレクトさせます。 リダイレクト先は https://accounts.google.com/o/oauth2/v2/auth なのですが、いくつかパラメーターをつけて遷移させる必要があります。

パラメーター名
client_id クレデンシャルと登録した時に表示されたclient_id
scope https://picasaweb.google.com/data/
redirect_uri http://localhost:3000/oauth2/callback
response_type code

RailsのControllerのコードはこんな感じになります。(client_idとclient_secretは、RailsのEncrypted Secretsを使って保管)

class Oauth2sController < ApplicationController
  def new
    uri = ::URI.parse('https://accounts.google.com/o/oauth2/v2/auth')
    params = {
      client_id: Rails.application.credentials.google[:api][:client_id],
      scope: 'https://picasaweb.google.com/data/',
      redirect_uri: callback_oauth2_url,
      response_type: 'code',
    }
    uri.query = ::URI.encode_www_form(params)

    redirect_to uri.to_s
  end

  def callback
    # snip...
  end
end

このURLにリダイレクトさせると、ユーザーのブラウザでは以下のような表示になります。

f:id:ryu39:20180129151705p:plain

ここで、ユーザーが「許可」のボタンを押すと、認可コードと共にリダイレクト時に指定したコールバックURL(上記の場合 http://localhost:3000/oauth2/callback )にリダイレクトされてます。

認可コードからアクセストークンの取得

params[:code] で認可コードを取得できます。

アクセストークンは、サーバーからGoogleへリクエストを送信することで取得します。 詳細は こちら のドキュメントに記載されています。 今回、パラメーターは以下のようにセットしています。

パラメーター名
code params[:code] の値
client_id クレデンシャル生成時に表示されたclient_id
client_secret クレデンシャル生成時に表示されたclient_secret
grant_type authorization_code
redirect_uri http://localhost:3000/oauth2/callback

上記パラメーターを含むPOSTリクエストを https://www.googleapis.com/oauth2/v4/token に送信すると、レスポンスとしてJSONが返されます。 そのJSONの access_token の値がアクセストークンになっています。

以下は、RailsのController内での処理になります。今回は、取得したアクセストークンをセッション内に保持しています。

class Oauth2sController < ApplicationController
  def new
    # snip...
  end

  def callback
    # snip...

    uri = ::URI.parse('https://www.googleapis.com/oauth2/v4/token')
    req_params = {
      code: params[:code],
      client_id: Rails.application.credentials.google[:api][:client_id],
      client_secret: Rails.application.credentials.google[:api][:client_secret],
      grant_type: 'authorization_code',
      redirect_uri: callback_oauth2_url,
    }
    res = ::Net::HTTP.post_form(uri, req_params)

    result = ::JSON.parse(res.body)
    session[:access_token] = result['access_token']

    # snip...
  end
end

これで、やっとPicasaのAPIを呼び出す準備が整いました。

PicasaのAPIで写真をアップロード

Picasa Web Albums Data API の 写真アップロード のAPIを呼び出すことで、任意の写真をアップロードすることができます。 アップロード先のURLは https://picasaweb.google.com/data/feed/api/user/userID/albumid/albumID となっていますが、userID/albumIDをともに default にすれば「トークン取得時に認可したユーザーのデフォルトのアルバム」にアップロードすることができます。

(2018-05-29追記) Google Photos API が発表されました。今後はこちらを使用することをオススメします。

今回は写真のメタ属性は特に指定せず、ファイル名と写真データのみで1枚だけアップロードしています。 HTTPヘッダーの Authorization にさきほど取得したアクセストークンをセットしています。

class PhotosController < ApplicationController
  def upload_to_google
    @photo = Photo.with_attached_file.find(params[:id])
    file = @photo.file.blob.download

    uri = ::URI.parse('https://picasaweb.google.com/data/feed/api/user/default/albumid/default')
    req = ::Net::HTTP::Post.new(uri)
    req['GData-Version'] = 3
    req['Authorization'] = "Bearer #{session[:access_token]}"
    req['Content-Type'] = @photo.file.blob.content_type
    req['Content-Length'] = file.bytesize
    req['Slug'] = @photo.file.blob.filename
    req.body = file

    res = ::Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http|
      http.request(req)
    }
    # snip...
  end
end

上記のサーバー間通信が成功すると、以下のようにGoogleフォトに写真がアップロードされます。 また、同時に「Drop Box」というデフォルトのアルバムも生成されます。

f:id:ryu39:20180129162031p:plainf:id:ryu39:20180129162035p:plain

以上で、RailsアプリからGoogleフォトに写真をアップロードすることができました。

注意点

今回使用している Picasa Web Albums Data API ですが、いつまでサポートされるかわかりません。

代替のAPIが出るまでサポートされると思っているのですが、もしかすると突然サポートを切られる可能性もあります。 (事実、2018年1月23日〜26日に一時的に写真アップロードのAPIが動作しなくなっていました)

本格的に導入を検討する場合は、上記のリスクを考慮していただければと思います。

まとめ

今回、Picasa Web Albums Data API を使い、RailsアプリからGoogleフォトに写真をアップロードすることができました。 また、APIを呼び出す過程で、OAuth2のフローに沿ったアクセストークンの取得も実施することができました。

概ねやりたいことはできるかな、という印象を持ちましたが、いかんせんPicasa APIの今後のサポート状況が不明のため、Googleからなんらかのアナウンスがあるまでは正式な採用は難しいかな、という印象を受けました。

以上が今回紹介したい内容になります。最後までご覧いただきありがとうございました。