ダイレクトアップロードのクライアント側実装では次の2つのステップが必要です。
- ダイレクトアップロード用のURLを取得する
- S3にダイレクトアップロードする
Flutter /Dart でどう書けば良いのか?私が試した方法を紹介します。
この記事ではサーバ側の実装については述べません。
続きを読むFlutter向けのCI/CDサービスの Codemagic を使っていて、 ios/Runner.xcodeproj/project.pbxproj
を PRODUCT_BUNDLE_IDENTIFIER
を jp.co.takeyuweb.app.${DEFINE_BUILD_ENV}
のように変数を埋め込む形にしたところ、
== Building for iOS == > xcode-project use-profiles Configure code signing settings Searching for files matching /Users/builder/Library/MobileDevice/Provisioning Profiles/*.mobileprovision List available code signing certificates in keychain /private/var/folders/pj/2d8_b7sn6f37c48z4jj09xk80000gn/T/build_k021qytp.keychain Searching for files matching /Users/builder/clone/**/*.xcodeproj Completed configuring code signing settings Did not find matching provisioning profiles for code signing! Generated options for exporting IPA - Method: ad-hoc - Provisioning Profiles: [] - Signing Certificate: - Signing Style: manual - Team Id: Saved export options to /Users/builder/export_options.plist
のようなエラーが出て正しくプロビジョニングプロファイルを選択できなくなりました。
続きを読むローカル開発中のRailsアプリに外部からのリクエストを届かせる必要があるとき、Ngrokは便利です。
たとえば
といったときです。
通常、Railsでは、url_for
のホスト部などはHTTPリクエストのヘッダー情報から取得して組み立ててくれるのですが、たとえば graphql-ruby のフィールドなど、コントローラー以外で Rails.application.routes.url_helpers.url_for(user)
を使った場合など、 default_url_options
がないとうまくいかないケースもあります。
そんなとき、いちいち config/environments/development.rb
を編集するのは面倒ですし、うっかり変更したままコミットする悲しい事故も起こります。
そこでこんなコードを書きました。
こうしておけば必要なときに default_url_options
を変えることができます。
# config/environments/development.rb Rails.application.configure do # (省略) # echo "https://xxxxxxxxxxxx.jp.ngrok.io" > tmp/default_url_options.txt # bin/rails restart default_url_options_path = Rails.root.join('tmp', 'default_url_options.txt') if default_url_options_path.exist? default_url = default_url_options_path.read.strip uri = URI(default_url) config.hosts << uri.host Rails.application.routes.default_url_options = { host: uri.host, port: uri.port, protocol: uri.scheme } else Rails.application.routes.default_url_options = { host: 'localhost', port: 3000 } end end
業務でRailsアプリを作る場合、処理負荷的にも転送量的にも重い画像等は、CDNから配信するようにすることは、最早当たり前といって良いと思います。
最近は CDK によって簡単にCloudFront Distribution設定を維持管理でき、さらに ActiveStorage 等、Rails側の機能改善もあって以前に比べて、簡単に、レールから外れない範囲で書けるようになりました。
今回は以下のような構成で、CloudFront 経由での配信によるエッジキャッシュの利用を実現する方法について紹介します。
Rails 6.1 の新機能 rails_storage_proxy_url
を使うと、ActiveStorage で添付したファイルへのリンクが署名付きURLへのリダイレクトにならず、RailsアプリのURLのままファイルをダウンロードできるようになります。
ActiveStorageはこれまで、S3をバックエンドとして使った場合、S3への署名付きURL=タイムスタンプなどが付与されたURLへのリダイレクトを行ってきました。 しかしこれは扱いづらいことも少なくなく、悩みの種の1つでした。
Rails 6.1 でこの問題に対する回答が(ようやく)公式に用意されたことになります。
続きを読む$ npm update aws-cdk $ npx cdk deploy
グローバルの cdk cli を使っていなかったので npm -g
ではない
たとえば、graphqlなどアップロード機能を提供したいとき、
ActiveStorage::Blob
とURL等を生成をMutationで実装したいことがあります。
試したバージョン: Rails 6.1
Rubyによるサンプルコードです。 実際にはクライアントで実装することになるので、JavaだったりSwiftだったりします。 ファイルの情報を何らかの方法でサーバに送信します。
file_path = "/src/spec/fixtures/files/male.jpg" file_data = File.read(file_path) byte_size = file_data.bytes.size # => 57937 checksum = Base64.strict_encode64(Digest::MD5.digest(file_data)) # => "0Nq1WCcyKbbw4wipYw1xag==" content_type = Mime[File.extname(file_path).split('.').last].to_s # => "image/jpeg"
create_before_direct_upload!
で ActiveStorage::Blob
レコードを作成します。
ActiveStorage::Blob#service_url_for_direct_upload
および ActiveStorage::Blob#service_headers_for_direct_upload
でダイレクトアップロードに使用するURLとHTTPヘッダーを生成することができます。
blob = ActiveStorage::Blob.create_before_direct_upload!( filename: "file.jpg", byte_size: byte_size, checksum: checksum, content_type: content_type, service_name: "amazon" # 今回はS3を使う ) # アップロード用のURLとHTTPヘッダーを生成 blob_signed_id = blob.signed_id # アップロード後の紐付けに使う署名付きURL direct_upload_url = blob.service_url_for_direct_upload direct_upload_headers = blob.service_headers_for_direct_upload
アップロード用の情報には、MD5チェックサムやMIMEタイプなどのデジタル署名が含まれ、一致しないファイルのアップロードを防ぐことができます。 なので、サーバ側で事前にファイルサイズやMIMEタイプなどをみてアップロード許可を出すこともできます。
pp direct_upload_url # => "https://your_bucket_name.s3.ap-northeast-1.amazonaws.com/b6msshsvihnanisrlfwgpab9miqk?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AK000000000000000000%2F20201219%2Fap-northeast-1%2Fs3%2Faws4_request&X-Amz-Date=20201219T021918Z&X-Amz-Expires=300&X-Amz-SignedHeaders=content-length%3Bcontent-md5%3Bcontent-type%3Bhost&X-Amz-Signature=c43651b09080ed543b1deb2f0baa8971251f06ff52360f8d31a1da7d6bc992ee" pp direct_upload_headers # => {"Content-Type"=>"image/jpeg", # "Content-MD5"=>"0Nq1WCcyKbbw4wipYw1xag==", # "Content-Disposition"=> # "inline; filename=\"file.jpg\"; filename*=UTF-8''file.jpg"}
# config/storage.yml amazon: service: S3 access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %> secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %> region: <%= Rails.application.credentials.dig(:aws, :region) %> bucket: your_bucket_name
Rubyによるサンプルコード。
ファイルのバイナリデータを、受け取ったURLあてに、受け取ったHTTPヘッダーと共に送ります。
一点注意が必要なのが Content-Length
ヘッダーでHTTPクライアントによっては自動でつけてくれないんので、その場合は送信するバイナリのサイズ(最初にサーバに伝えたバイト数)を自分で付けないと SignatureDoesNotMatch
になります。
url = URI.parse(direct_upload_url) req = Net::HTTP::Put.new(url.request_uri) req.initialize_http_header(direct_upload_headers) req.body = file_data # RubyのHTTP::Requestクラスはこれで content-length もつけてくれる req.each_header { |k,v| p "#{k}=#{v}" } # => "content-type=image/jpeg" # "content-md5=0Nq1WCcyKbbw4wipYw1xag==" # "content-disposition=inline; filename=\"file.jpg\"; filename*=UTF-8''file.jpg" # "connection=close" # "host=your_bucket_name .s3.ap-northeast-1.amazonaws.com" # "content-length=57937" http = Net::HTTP.new(url.host, url.port) http.use_ssl = true res = http.request(req) # => #<Net::HTTPOK 200 OK readbody=true>
class User < ApplicationRecord has_one_attached :photo end blob = ActiveStorage::Blob.find_signed!(blob_signed_id) User.find(user_id).photo.attach(blob)