Debuginfo

思考とアウトプット

javascriptでFlickrへの写真のアップロードを実装してみた

トピックとしてはかなり今更感がありますが。。これをするのに一週間かかりました。私の技術力不足もありますが、情報がなくてかなり苦労しました。苦労した分、とっても勉強になりました。

同じようなことをする人にこの努力をさせたくないのでブログに書いておきます。

説明はいらないから、今すぐアップロードしたい人はgithubを見てください。

https://github.com/shoheik/flickrUpload

長いので目次..

目的

手元にある写真をFlickerにブラウザを使ってアップロードする。このとき、画像データをサーバー経由ではなくて、ブラウザから直接アップロードできるようにする。クライアント側に処理をさせてサーバの負荷軽減をするのが目的です。Flickr APIのページには各言語でAPIを実装したモジュールがありますが、サーバ側の処理、またはアプリで行うのが一般的のようです。

以後、時系列的に何をしたかを記します。

準備

API Keyを取得

ここからAPI KeyとShared Secretを取得します。そのプロセスは省略しますが、商用と個人利用を選べるようになっています。スタートアップやテストに利用するときは個人利用の方で良いと思います。

Oauth Token と Oauth Secretを取得

Flickr+技術用語ででググると仕様のベージではなくて、Flickr Discussonのページがヒットします。そして、Samという人が頻繁に質問に答えています。(彼がFlickrの開発者なのかな?)。で、彼曰く、Tokenはexpireしないと言っているので一度認証させたものをアプリに仕込むのはありです。ユーザのトークンを取得して、データベースに保存する場合はセキュリティに注意を払いましょう(もし漏洩したら,,, ユーザの写真をdelete/writeできてしまう。。こ、怖い。。)

で、Oauth Token/Secretの取得方法ですが、一回作って保存しておけばいいので、既存モジュールでスクリプトを書いて、それを使います。私はPerl Mongerですが、Ruby勉強中なので、あえてRubyのモジュール、flickrawを使いました。

使うというよりもコピペです^^

exampleの中のauth.rbをコピーしてきてAPI_KEYとSHARED_SECRETに代入して走らせるだけ。

$ vi Gemfile
source 'https://rubygems.org'
gem 'flickraw'
$ bundle install
$ cp /Users/kamesho/.rbenv/versions/2.0.0-p0/lib//ruby/gems/2.0.0/gems/flickraw-0.9.6/examples/auth.rb .
$ vi auth.rb # add your API key
$ ./auth.rb
# 指示にしたがい URLをコピーして、そこに表示された数字をコピーしてエンター。

表示されたキーをメモっておきます。

アップロードできるか試してみる

flickrawを使って試してもいいのですが、中身を知りたかったのでググってヒットしたajido.hatenablog.comのOAuthでFlickrにアップロードのスクリプトを使いました。

またまたコピペですが、アップロードできるか確認しました。

アップロード

調べてみたところFlickrからブラウザからアップロードする方法はいくつかありそうでした。

  1. HTML Formを利用してアップロード
  2. XmlHttpRequestを利用したアップロード
  3. Flashを利用したアップロード

Flashはやや拒否反応してしまうので、1が駄目だったら2をやってみるという方針で行きました。

その前にOauthの仕様を知る

Flickr公式のUpload APIのページに行くと情報が載ってますが、flickr.people.getUploadStatus methodは古い情報なのでOauth認証とは関係がありません。。私はここで2日無駄にしました orz... Depreciatedぐらい書いておいて欲しいものです。ここで有用な情報はphoto以外は(oauth)signatureの生成に利用するということです。

で、flickrの場合のoauth認証は?というと、例のSamさんが下記のブログにまとめています。

簡単に言うと、各パラメータをソートして、2つのshared_secretとoauth_secretで暗号化したものがoauth_signatureでこれを一つのキーとしてauthorizationに利用するということです。

javascriptoauth.jsというモジュールがあります。ここのexample/signature.html がすごく役に立ちます。

f:id:shoheik:20130323114005p:plain

このスクリーンのように代入していくとsignature等が生成されるのでこれで確認できます。(散々悩んだ後にこのページを知りました。もっと早く知っていれば、oauthの問題かxhr(後述)の問題か住み分けができて、早く解決できたように思います。

HTML Formを利用したアップロード

私はトライしましたが、結論をいうと駄目でした。というより、駄目だと思ってXmlHttpRequestに移った後にバグや思い違いを変更してうまくいったのでFormを利用してもうまくいく可能性はあると思います。

このDiscussionの途中あたりにSamさんのiframe+formを使ったアップロードのサンプルがあります。Oauth認証ではなく以前の認証方法で書かれていますが、これをOauthに当てはめれば使えると思いました。 以下のようなjavascritptでフォームを作りました。動作確認はしてませんが、写真をinput fileに入れてそれを送信する的にはできる可能性はあります。しかし、私が今後やりたいことの一つのcanvasで作ったイメージを送信することがあります。これにはinput fileにデータを挿入する必要があるのですが、input fileはreadonlyなので、XmlHttpRequestで実装することにしました。Ajax, form共にmultipart/form-dateである必要があります。(実はFormの送信方法に二通りあるというのをここで初めて知りました^^; )

var form = document.createElement("form");
form.setAttribute("method", 'POST');
form.setAttribute("action", 'http://api.flickr.com/services/upload/');
form.setAttribute("enctype", 'multipart/form-data');
//form.setAttribute("target", 'iframe');

var p = {
    description: "desc1",
    oauth_consumer_key : "aaa"
    oauth_nonce : "bbb"
    oauth_signature : "ccc"
    oauth_signature_method : "HMAC-SHA1",
    oauth_timestamp : "1363492728",
    oauth_token: "ddd"
    oauth_version: "1.0",
    tags: "tags1",
    title: "title1"
};

for (var i in p) {
    var i = document.createElement("input");
    i.setAttribute("type", "hidden");
    i.setAttribute("name", i);
    i.setAttribute("value", p[i]);
    form.appendChild(i);
}

//var file_input = document.createElement("input");
//file_input.setAttribute("type", "file");
//file_input.setAttribute("value", dataURL); //input fileはReadOnly
var file_input = document.getElementById(‘file_input');
form.appendChild(file_input);
document.body.appendChild(form);
form.submit();

Ajax(XmlHttpRequest)を利用したアップロード

Formがうまく動かなかったので重い腰を上げて一番複雑そうなXmlHttpRequestで実装することにしました。(後でわかったのですが、Formで駄目だったのはOauth Signatureが間違ってたように思います。もしかしたら、動く可能性があるので試してみる価値はあると思います)

JQuery.ajaxで実装しなかった理由は、Discussionのページを見るとSamさんが改行\r\nを突っ込んだりしてて、かなり細かい仕様のようで、この振る舞いをJQueryでしているかを確認してくより、実際生のXHRで実装した方が早いと思ったからです。(ブラウザ互換を気にする必要が以後ありますが)

XmlHttpRequest(XHR)は使えるか?

皆さん、ご存知のようにXHRはクロスドメイン制約があります。デフォルトでは同じドメインのサーバしかアクセスできません。しかし、サーバ側(この場合はFlickr側)がそれを許しているとXHRでアクセスできるます。下記が 確認するヘッダのようです。

* Access-Control-Allow-Origin: 
* Access-Control-Allow-methods:
* Access-Controll-Allow-Headers:

何か投げてみてFirebugみたいなツールで レスポンスヘッダを見てみましょう。

f:id:shoheik:20130323114038p:plain

この写真を見るとAccess-Control-Allow-Origin:*となっているので、XHRは使えそうです。しかし、Access-Control-Allow-Headersはありません。Access-Control-Allow-Headers: authorizationの記述がないとAuthorizationヘッダを使う事ができません。しがたって、Flickrでjavacriptを用いるためにはbodyにOauthキーを入れて認証を行う必要があります。

javascript code

githubに上げておきました。基本的にDiscussionで話していることを実装します。Samさんはjavascriptでバイナリを送る方法は知らないと言っていますが、sendAsBinaryで投げればうまくデータを送信することができました。

実行例:

HTML Canvausに画像を入れてバイナリファイルを作ってから送信する。バイナリファイルは他にも取得方法があると思います。OAuth keyを含んだパラメータはサーバ側から取得するようにしてます。セキュリティ的にもクライアントでキーを算出するのはどうかと思うので^^;;

まとめ

冒頭にも書きましたが、https://github.com/shoheik/flickrUploadにモジュールとして上げてみました。 OAuth keyのサーバエンドの実装が知りたい要望があればのちのち書こうと思います。

It’s sooooo tired but it’s always good to think something new :)