LoginSignup
5
3

More than 5 years have passed since last update.

RMagick使ってlocalstackのS3に画像をアップしてブラウザで参照する

Last updated at Posted at 2018-07-06

かなり時間かかっちゃった上に
有力な情報がなかなか得られず結構苦しんだのでメモ

local開発でS3が使いたいんだけど!

そもそものスタートはこちら。
AWS上で動かし知ているRoRのシステムで
諸事情によりレールに乗らずに開発をしていたのですが
ローカルの開発環境であるdocker上でS3のモックが欲しくなり調べて実装してみました。

それ「localstack」があるよ

githubはこちら
https://github.com/localstack/localstack

AWSのモックを提供してくれるとても優秀なプロジェクトです。
一つのインスタンスでサービス毎にポートを割り当てて稼働させているようです。
詳細はドキュメントで。

localstackを立ち上げる

今回はdocker-composeで立ち上げるので docker-compose.yml はこんな感じ

docker-compose.yml
version: '2'
services:
  aws:
    image: localstack/localstack
    ports:
      - 8080:8080
      - 4567-4578:4567-4578
    environment:
      - DEFAULT_REGION=ap-northeast-1
      - HOSTNAME=localstack.test
      - DATA_DIR=/tmp/localstack
    privileged: true

ports でポートを外部に開放してあげる事でブラウザからも参照できます。
環境変数に関しては
DEFAULT_REGION で東京に指定
HOSTNAME は内部でドメインに割り当てるのに必要
DATA_DIR はデータの永続化のために割り当てています。
たぶんこれであっているはず・・・
他にも大量に設定はあるので詳しくはgithubのREADMEでも読んでみてください。

設定が終わったら docker-compose up -d で立ち上げます

プロジェクトからアップをしてみる

環境設定とか

ローカルと本番で向き先を変えたいので設定ファイルと設定の読み込みを作ります

config/s3.yml
default: &default
  id: <%= ENV.fetch("AWS_S3_ACCESS_KEY_ID") %>
  access_key: <%= ENV.fetch("AWS_S3_SECRET_ACCESS_KEY") %>
  region: ap-northeast-1
  endpoint: https://s3.ap-northeast-1.amazonaws.com
  bucket_name: oreno-bucket

development:
  <<: *default
  endpoint: http://localstack.test:4572

test:
  <<: *default

staging:
  <<: *default

production:
  <<: *default
  bucket_name: honban-no-bucket

こんな感じで設定ファイルを作って読み込みのスクリプトを置きます

config/initializers/s3.rb
Rails.application.config.s3 = YAML.load(ERB.new(File.read("#{Rails.root}/config/s3.yml")).result)[Rails.env]

環境情報は Rails.env に入っているのでここを参照
ymlの中でrubyのscriptを使いたいので ERB を使って読み込ませています。

アップロードの処理

コードで書くとこんな感じです

lib/s3/handler.rb
require 'aws-sdk-s3'

class S3::Handle
    def initialize
        @s3 = Aws::S3::Resource.new(
          region:      Rails.application.config.s3["region"],
          endpoint:    Rails.application.config.s3["endpoint"],
          force_path_style: Rails.env.development?,
          credentials: Aws::Credentials.new(
            Rails.application.config.s3["id"],
            Rails.application.config.s3["access_key"]
          )
        )
    end

    def upload_image(bucket_name, key_name, image)
        begin
            @s3.bucket(bucket_name)
                # ここがファイル名とかファイルパスになる
                .object(key_name)
                # `acl` でアクセス制御を追加しておく
                .put(body: image, acl: 'public-read')
            return true
        rescue => err
            return false
        end
    end
end

あんまりrails的な書き方では無いんだろうなと思いつつ・・・
Aws::S3::Resource の引数として設定している中で
endpoint はドメインの向き先の指定を
force_path_style はbucket_nameがサブドメインに含ませるかどうかの設定になります。
localstackを使う際には少なくともこの2点が本番稼働の時と違う設定になるのでお気をつけください。

ブラウザで参照出来なくて困ったにゃん・・・

適当に放り投げて適当に名前付けて保存してブラウザでimgタグで表示しようとしたら出来なかったので
片っ端から jpeg に変換するという暴挙に出ました。

ImageMagickのインストール

yum で入れるなら1行で終わりです。

RUN yum -y install ImageMagick ImageMagick-devel

もしDebian系とかうぶんちゅとかであればググって下さい。
情報はたくさんあります。

RMagickのインストール

これもgemなんで一瞬ですね。
gemファイルに以下の行を追加して gem install でOKです。

gem "rmagick"

jpeg変換をかけてみる

formからPOSTされてきたファイルをjpegに変換するのでcontroller側はこんな感じです。

app/controller/images_controller.rb
class ImagesController
    # ~~~~ 中略 ~~~~
    # 画像のアップロードの処理
    def upload_image
        s3 = S3::Handle.new

        # file名は固定なので今回はこんな感じ
        file_name = 'suteki_na_image.jpg'

        # pathの指定とか出来る
        path = 'oreno/gazou/' + file_name

        # fileの読み込みと変換
        blob = s3.convert_image(params[:file].tempfile, file_name, 'JPEG')
        if s3.upload_image(path, blob)
            render json: {
                # ドメインの設定とか読み込むためにメソッドを噛ましている
                path: s3.make_image_url(path)
            }
        else
            head 500
        end
    end
end

jpqgの実際の変換はこんな感じでやっています。

lib/s3/handler.rb
require 'RMagick'
class S3::Handle
    # ~~~~ 中略 ~~~~
    def convert_image(image, name, extension)
        # `from_blob` じゃないとうまく読み込めなかったりとかするみたい
        img = Magick::Image::from_blob(image.read).first

        # ここで変換
        img.format = extension

        # ここで書き出し
        blob = img.write(name).to_blob

        # オブジェクトを破棄してメモリ開放
        img.destroy!
        return blob
    end
end

ブラウザからアクセスしてみる

画像をアップロードしたらブラウザからアクセスしてみます。
pathは
http(s)://{domain}/{bucket_name}/{file_path}
の構成になるので、今回の場合は
http://localstack.test:4572/oreno-bucket/oreno/gazou/suteki_na_image.jpg
にアクセスすれば画像が見られるはずです。

「bucketが無い」ってエラーが出たんだけど!

aws-sdk-s3create_bucket メソッドがうまく動かずに苦労しましたが
通常運用で動的にbucketを作らないといけない事態は想定していなかったので
普通にaws-cliで作りました。
localstackのidとkeyは適当で大丈夫みたいです。
aws-cliについてはまた別の機会に・・・

まとめ

便利な道具があるのはありがたいけどドキュメントが疲れた(語彙力

5
3
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
5
3