ActiveStorage+Amazon S3で管理している画像をフロント側で扱うときのCORSまわりでハマったことめも

みんなでつくるダンジョンのテストサーバはHeroku+Amazon S3で構成しており、画像の管理まわりにはActiveStorage(Ruby on Rails)を使っていますが、テストサーバに上げても「うまくうごかない~」となっていたのでした。

S3上にある画像をImageオブジェクトで取得して、Canvasをつかって加工するというJavaScriptプログラムを組んでいました。

let url = "https://s3.example.com/hoge.png";
let image = new Image;
image.crossOrigin = "anonymous";  // オリジン間のリクエストだよーということを伝えるために必要
image.onload = () => {
  let canvas = document.createElement("canvas");
  やりたい処理
}
image.src = url;

これで画像が取得できてめでたし、と思っていたのですが、ブラウザ上にはこんなエラーメッセージが出てしまっていました(URLまわりは実際のものとは異なります)。

Access to Image at 'https://s3.example.com' from origin 'https://heroku.example.com' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'https://heroku.example.com' is therefore not allowed access.

Access-Control-Allow-Origin がうまく設定されていないとのこと。実際にブラウザのデベロッパーツールを確認してみても、確かに Access-Control-Allow-Origin ヘッダが画像のレスポンスにありません。 (Amazon S3とみんなでつくるダンジョンのテストサーバ(=heroku)は異なるオリジンとなるので、リソースを共有するときにはCORSの設定(Access-Control-Allow-Origin)が必要)

もちろんS3のCORS設定は実施済みです。

ActiveStorageで管理しているリソース(今回の場合は画像)を先ほどのJavaScriptプログラムで参照するために、 Railsサーバ側で url_for 関数を使ってリソースのURLをつくるのですが、ここでつくったURLはRailsサーバを向いています。このURLにアクセスするとリダイレクトがかかり、S3を参照するURLが返されます。

railsguides.jp

S3はリクエストにOriginヘッダがないと Access-Control-Allow-Origin ヘッダ情報を返してくれませんが、リダイレクトによってこの辺がうまくいっていないのでは?と思ったのでした。

docs.aws.amazon.com

そこで、先ほどのプログラムを以下のようにしてみます。Imageタグでリダイレクトを受けるのではなく、一度fetchをつかってリダイレクト先のURLを受け取り、そこからImageオブジェクトで画像を取得します。

let url = "https://s3.example.com/hoge.png";
fetch(url).then( response => {
  let image = new Image;
  image.crossOrigin = "anonymous";
  image.onload = () => {
    let canvas = document.createElement("canvas");
    やりたい処理
  }
  image.src = response.url;
});

これで無事にS3からくる画像レスポンスに Access-Control-Allow-Origin ヘッダが付与されてJavaScript上で画像を参照できるようになりました。めでたしめでたし。

ではでは~。

「みんなでつくるダンジョン」のトップページを公開したよ

ここしばらく開発中の「みんなでつくるダンジョン」のトップページを公開しました。

dungeon.garakuta-toolbox.com

サブドメインにしたらURLが長くなってしまった...。garakuta-toolbox.com ちょっと長いドメインを取っちゃったなーと今更ながら思ったりしたのでした。サービスをリリースするときはURLを変えるかも。)

ひとりで開発しているとやる気の底がつきやすく、なんかそろそろ出さないとやる気がでない~という感じになってきたので、とりあえずトップページを公開します。

(ブログ記事の定期的な公開も「やる気の底つき防止」みたいな役割があるのかもなぁと若干思ったり。趣味で作っているけど工夫しないと続かなかったりするのでした。)

そもそも「みんなでつくるダンジョン」とはどのようなもので、どのように楽しむものなのかという情報がまとまっていませんでした(なんだってー!)。 過去ブログを見ないとどのようなものなのか見当もつかない、という感じでよろしくなかったので、情報をまとめて改めてサービスについてお知らせするいい機会になったかもなぁと思っています。

仕様やそこそこ重要な情報はブログとトップページの両方に載せられたら良いなと思っています。

そして相変わらずページデザインは難しい...。トップページのファーストビューには大きな面積の動画を載せたいなぁと思ったのですが、いろいろ考えないといけないことが多くてやめてしまいました。たとえば以下のようなことが懸案事項になりそう。

  • 転送容量の関係で低解像度の動画 or / and 短い動画としないといけない
    • 低解像度の動画にした場合はいい感じにごまかさないといけないけどちょっと手間
    • 以前に似たようなデザインのページを作った時には、動画の上にメッシュっぽいのをかぶせてごまかしたりしていた
  • 動画の編集がそもそも手間...
    • AviUtlとか昔は使っていたけど、そろそろ動画編集ソフトもほしいなぁ(大した編集はしないけど、市販の「動画編集ソフト」というものに慣れておきたい)

ということで、今回は背景スクロールをCSSアニメーションで実装という形にしてみました。かっこいいウェブページを見ると「じぶんもああいうのが作りたいなぁ」と思うのですが、まだまだ修行が足りないようで...という感じです。

ではでは~。

Twitterの埋め込みタイムラインが使えなくなっていた

ドット絵ツイート支援サービス「ぴこぴよ」のトップページには、#picopiyo のツイートをタイムライン表示するウィジェットを配置していましたが、とうとう動かなくなってしまいました。

picopiyo.iconclub.jp

どうやら今までのウィジェットは廃止され、https://publish.twitter.comを使って新しいものを生成して置き換えないといけないのですが、ハッシュタグ検索結果タイムラインのウィジェットは設置できないようです。 (一応ツイート検索APIは存在しますが、これを使って今までのような検索結果タイムラインを表示するのは手間で面倒...)

ということで、ぴこぴよトップページでの #picopiyoタイムライン表示は取りやめることにしました。「こんなツイートができるよ!」という紹介が目的で設置していたのですが、直すのに手間がかかりそうなので致し方ない。。。

今後#picopiyoなツイートを見たい方は、ご自身が利用しているTwitterクライアントで検索していただくか、#picopiyoの検索結果 からご覧ください。(この記事をご覧の方はぜひ#picopiyoの検索結果も観てみてね!)

ちなみに、たまに #picopiyo なツイートを私も眺めているのですが、「使っていただけてうれしいー」という気持ちになって制作意欲の糧にしております。いつもハッシュタグをつけてツイートしていただきありがとうございます!

ではでは~

みんなでつくるダンジョンにプラグインの仕組みを導入してみたよ

作ったマップをつなげて遊べる「みんなでつくるダンジョン(開発中)」には、以下のような機能を入れたいと思っていました。

  • キャラクターのセリフを表示したり、看板に書いてある字を表示したりする機能
    • キャラがしゃべったりしたほうが楽しそうだなぁと思ったので
  • 物体が常に上下左右に動く機能
    • リフトとかを配置できるようにしてちょっとだけゲームっぽく遊べそう

あくまで「ゲーム制作ツール」ではないので多機能なものは不要と考えていますが、それでも多少マップに動きがあったほうが楽しそうだなと思ったのでした。 というわけで、配置したアイテムに機能を付加することができる「プラグイン」という機能を仮組してみていました。

f:id:piyorinpa:20180917205108p:plain
こんな感じにアイテムに機能を付けられるイメージ

たとえば看板の上で決定ボタンを押したときに「看板のテキストを表示させる」みたいな演出をさせたいときには、以下の動画のように配置したアイテムに対してプラグインを設定します。 (前作の「ステージデザイナー」の反省を生かして、なるべく簡単に設定できるようにしたつもりですがどうなんだろう...)

「直線移動プラグイン」を適用すると、リフトが動くようになったりもします。

キャラがめちゃくちゃ滑っていますが、そのへんもどうにかしないとなぁ。現在は摩擦係数を固定値にしていますが、いくつかの値から選べるようにするとよさそうかなと思っています。 (ただ、リフトの速度が不連続的に反転するということは、上に乗っかっている物体は多少なりとも滑ってしまうことになりそうなので、その辺のバランスをどうするか考えないとなぁ)

初期公開時は限られた種類のプラグインのみを公開予定ですが、今後もちょっとずつ種類を増やしていきたいなという気持ちでもろもろ設計してみています。

ではでは~。

みんなでつくるダンジョンのテストプレイ動画を撮ったよ

みんなでつくるダンジョンのテストプレイ動画(編集したマップを自キャラで移動したり、他のマップに移動したりしている様子)を撮ってみました。トップページに掲載する動画を撮るための練習として撮ったものです。 こんな感じにいろいろな人が作ったマップを相互につなげて遊べるようなものをつくっております。

(素材はbunaguchiさん、墨cmさんからお借りしたものを使っております)

マップの編集の様子もぺたり。このようにアイテムをぺたぺたと貼りながらアニメーションを適用していきます。さらに位置固定とするかどうか、衝突時の跳ね返り処理をするかどうか、などなどを設定し、マップをつくっていきます。

いままでこのような動画を公開する際はgifアニメーションしかアップしていなかったのですが、 やっぱりちゃんと動画で撮ったほうが滑らかに動くし、今後はめんどくさがらずに動画を撮るようにしよう...。

Windowsだと、Windowsキー+Gキーでかんたんに動画キャプチャできると知ったのでもう大丈夫なはず)

ちなみに、動画内でうごいているキャラクターは墨cmさんに描いていただいたのですが、 なんとわたしのアバター画像(いわゆるうちの子)から描き起こしていただいたものなのです。うれしい~。

f:id:piyorinpa:20180909194414g:plain
わたしのうちの子が動いている~!

ほかにもキャラクターやマップチップ画像をいろいろ描き起こしていただいたので、ちゃんと活用した動画をトップページに載せるぞー。 (このように紹介動画やスクリーンショットにつかっていい素材は引き続き募集中です

もうあと2か月くらいで製作開始から1年くらい経つことになるのだなぁ。訳あって全く開発できていない期間が3か月くらいあったのですが、それにしてももう半年くらいは経っている。。。

ではでは!

ActiveStorageでテストがうまくいかない~のでとりあえず対処したメモ

ActiveStorageを使ったModelのテストがうまくいかない!となったので、対処法のメモをば。 テスト環境はRSpec + factory_bot でつくっています。

has_one_attached をもつ Hoge Modelがあるとして、

# hoge.rb
class Hoge < ApplicationRecord
  has_one_attached :image
end

ファイルがアップロードされたHoge Modelが欲しくなることがありまして、テストデータの定義spec/factories/hoge.rbを以下のように書きました。

# spec/factories/hoge.rb
FactoryBot.define do
  factory :hoge do
    after(:create) do |hoge|
      File.open('image.png') do |f|
        hoge.image.attach(io: f, filename: "#{hoge.id}.png", content_type: 'image/png')
      end
    end
  end
end

ちなみに、テスト時の ActiveStorage の挙動はテストモードにしておきます。

# config/environments/test.rb
config.active_storage.service = :test

(テスト項目ごとに毎度ファイルをアップロードする相当のことを行っているのがイマイチかもなぁと思いつつも)attach メソッドでファイルを都度アップロードすることで、spec内で create(:hoge) したときにファイルがアップロードされ、各種テストが実行できることを期待しました。しかし、なぜか ActiveStorage::IntegrityError が大量に出てしまったのでした。

RailsのIssueを見てみると似たような症状をコメントしている方もいるような。

Upload same file multiple time give uninitialized constant ActiveStorage::IntegrityError · Issue #32855 · rails/rails · GitHub

いずれにしても解決策がわからん!となったので、対処療法的に以下のような実装をしてみました。

まず、ActiveStorageで使っている ActiveStorage::Attachment , AcitiveStorage::Blob Modelのfactoryを定義します。

# spec/factories/active_storage/attachment.rb
FactoryBot.define do
  factory :attachment, class: ActiveStorage::Attachment do
    name ''
    record_type ''
    record_id 0
    association :blob, factory: :blob
  end
end
# spec/factories/active_storage/blob.rb
FactoryBot.define do
  factory :blob, class: ActiveStorage::Blob do
    sequence(:key) { SecureRandom.urlsafe_base64 }  # ユニークなキーでなければならない
    filename 'test_image.png'
    content_type 'image/png'
    metadata ''
    byte_size 13317
    checksum 'R-EvpP3cJEEQCSwQq/FRCF=='

    after(:create) do |blob|
      path = blob.service.send(:path_for, blob.key)
      FileUtils.mkdir_p(File.dirname(path))
      FileUtils.copy_file("#{Rails.root}/spec/images/test_image.png", path) # テストファイル image.png をファイルアップロード後の場所に移動する
    end
  end
end

byte_sizechecksum が決め打ちなど、いろいろと問題のある定義ですが、ひとまずはテストを動かせるようにしたいので目をつむっていただきたく...。byte_sizechecksumdevelopment 環境で実際にファイルをアップロードしたときに生成される active_storage_blobs テーブルのレコード値をそのまま使っています。traitなどを使ってリファクタリングすれば決め打ちでもよいかもしれないと思っています。)

こうすることで、Attachment Modelがつくられたときに Blob Modelもつくられ、Blobに紐づく実ファイルもあるべき場所にコピーされます。

Attachment Modelで has_one_attached を持つModelと Blob の紐づけを行うので、先ほどのspec/factories/hoge.rb を以下のように修正します。 ( has_one_attached :image でModelとアップロードファイルの関連を定義したのでnameには image を設定します。また、record_typeにModel名を、record_idにModelの主キーの値を設定します。)

# spec/factories/hoge.rb
FactoryBot.define do
  factory :hoge do
    after(:create) do |hoge|
     create(:attachment, name: 'image', record_type: 'Hoge', record_id: hoge.id)
    end
  end
end

これでひとまずはファイルがアップロードされた相当の環境を再現することができ、テストが通るようになりました。ただ、以下の点が気になるところです。

  • テスト時にコピーしたファイルの後処理(一応tmpディレクトリ配下にファイルは生成される)
  • アップロードファイルの配置場所を activestorage/lib/active_storage/service/disk_service.rbpath_for メソッドで取得しているが、Private Methodなので今後なくなったり名前が変わったりしないか心配

テストどうやればよいのかわからなくてこまった!という話でした。こうやればいいよ!などあればコメントなどいただけるととっても喜びます。

ではではー。

Jest + WSL でテストが通るときと通らないときがあって困っていたときのメモ

タイトルの通りのトラブルで困っていた時のメモ。7~8つくらいのテストファイル、180くらいのテスト項目のテストを走らせると、エラーになるときとならないときがあってふぇ~わからん!ってなっていました。

package.jsonに以下のように書いて、 npm run test していました。

...省略...
"scripts": {
  "test": "jest"
}

こんな雰囲気のエラーが出たり出なかったりしていたのでした。

ENOENT: no such file or directory, open 'fuga.json'
Cannot find module 'hoge' from 'fuga.js'

開発環境はWindows10, Windows Subsystem for Linux 。ひとまず並列実行をやめるべく、以下のようにpackage.jsonを変更したらテストが安定して通るようになりました。 これどうやったら並列実行でもテストが安定して通るようになるのだろう...。引き続き調べてみないとなぁ。

...省略...
"scripts": {
  "test": "jest -i"
}

開発中のぼやきブログでした。ではでは。