Railsのreponseのcache-controlを上書きする

こんにちは!kossyです!



さて、今回はRailsAPIを実装する際に、responseのcache-controlを上書きする方法について
ブログに残してみたいと思います。

そもそもcache-controlってなに?

Cache-Control 一般ヘッダーフィールドは、リクエストとレスポンスの両方のキャッシュ規則を指定するために用います。キャッシュのディレクティブは単一方向であり、すなわちリクエストで指定されたディレクティブがレスポンスで同じディレクティブが指定されたものとはみなされません。

引用元: Cache-Control - HTTP | MDN

要はキャッシュをどう扱うかを指定するために用いるもののようです。

cache-controlディレクティブには、
・max-age
・no-cache
・no-store

上記のディレクティブを設定できます。
より詳しく知りたい方は引用元のページをご確認下さい。


実装手順

さて、cache-controlの非常に雑な紹介はここまでにして、
実装手順を残したいと思います。


app/controllers/application_controller.rb


class ApplicationController < ActionController::Base

  before_action :prevent_page_caching

  def prevent_page_caching
    response.headers['Cache-Control'] = 'no-cache, no-store, must-revalidate'
  end
end

参考: must-revalidate Cache-Control header disappearing in Rails 5.2 · Issue #32557 · rails/rails · GitHub

application_controller.rbにヘッダの中身を書き換えるprevent_page_cachingメソッドを定義し、before_actionコールバックで
ApplicationControllerを継承した全てのcontrollerでコールバックが実行されるようにしています。

no-cache, no-store, must-revalidateは、ざっくり言うと、

「有効かどうかの確認が取れないとキャッシュ使っちゃダメ」
「キャッシュしちゃダメ」
「キャッシュが有効かどうかの確認を絶対にしてね」

という設定になります。

参考: IPA ISEC セキュア・プログラミング講座:Webアプリケーション編 第5章 暴露対策:プロキシキャッシュ対策



このように設定すると、頻繁に画面のアップデートを行うようなWebアプリの場合だと、
手動でキャッシュを削除せずとも最新の変更が反映されるようになります。

ただ、キャッシュをしなくなるということは画面の描画に時間がかかるようになるので、
UX的な観点から言うと悪手かもしれません。

「手動でキャッシュを削除せずとも最新の変更が反映されるようにしたい」
という要件がもしあった場合、Railsのreponseのcache-controlを上書きする解決策を取ってもいいかもしれません。



参考にさせていただいた記事
must-revalidate Cache-Control header disappearing in Rails 5.2 · Issue #32557 · rails/rails · GitHub
IPA ISEC セキュア・プログラミング講座:Webアプリケーション編 第5章 暴露対策:プロキシキャッシュ対策

EGPとIGP

こんにちは!kossyです!




さて、今回はインターネットのルーティングプロトコルであるEGPとIGPについて、
ブログに残してみたいと思います。



ルーター同士で通信して情報をやりとりするための規約

インターネットの通信の仕組みには、指定したIPアドレスへのデータはパケットという単位で、ルーターのルートテーブルを用いて
バケツリレーのようにして相手先の通信を実現していますが、
それぞれのルーターが相手先のIPアドレスをもつサブネットがどこにあるのかを事前に知っておく必要があります。
この「事前に知っておく」という仕組みがEGPとIGPになります。


EGP

インターネットサービスプロバイダー(ISP)はそのネットワークを管理する「AS番号」という番号を持っています。
参考: インターネット用語1分解説~4バイトAS番号とは~ - JPNIC

EGP(Exterior Gateway Protocol)はこのAS番号をやり取りして、
「どのネットワークの先にどのネットワークが接続されているか」を
やり取りしています。


IGP

IGP(Interior Gateway Protocol)は、EGPの内部のルーター同士でルートテーブルのやり取りをします。
プロバイダーやAWSの内部での詳細なやり取りに使われます。


まとめ

EGPとIGPのおかげで、ルートテーブルに新しいサブネットを登録する必要なく運用できます。


勉強になりました。



参考にさせていただいたサイト
https://wa3.i-3-i.info/diff157routing.html
インターネット用語1分解説~4バイトAS番号とは~ - JPNIC

yield @variable if block_given? ってなに?

こんにちは!kossyです!



さて、今回は、トークンベースの認証の仕組みを提供するgemであるdevise_token_authの
コードリーディングをしていた時に書かれていたある構文について、ブログに残してみたいと思います。



yield @variable if block_given?

なんだこれ?と思ったコードは以下です。

    def validate_token
      # @resource will have been set by set_user_by_token concern
      if @resource
        yield @resource if block_given?
        render_validate_token_success
      else
        render_validate_token_error
      end
    end

引用: devise_token_auth/app/controllers/devise_token_auth/token_validations_controller.rb at master · lynndylanhurley/devise_token_auth · GitHub


順番に紐解いてみます。


まず、block_given?は、メソッドを実行する時にブロックが渡されていればtrueを返し、渡されていない時はfalseを返します。
Rubyではどのメソッドでも何の宣言も無しにブロック引数を渡すことができます。
block_given?メソッドの定義元もkarnelモジュールなので、どこでも使うことができます。

参考
www.sejuku.net

次にyield @resource ですが、yieldは渡されたブロックをメソッド内で呼び出すことができます。
また、引数を渡すことが可能で、@resourceを渡されたブロック内で使うこともできます。


もう一度当該コードを確認すると、

    def validate_token
      # @resource will have been set by set_user_by_token concern
      if @resource
        yield @resource if block_given?
        render_validate_token_success
      else
        render_validate_token_error
      end
    end

「もしvalidate_tokenメソッドの呼び出し時に@resourceがtrueの場合でブロックが渡されていれば、渡されたブロック内で@resourceを使って処理をした後に、render_validate_token_successを呼び出す」
「@resourceがnilまたはfalseの場合はrender_validate_token_error」

と書いてあるようです。

validate_tokenメソッドをブロック引数付きで呼び出すユースケースとしては、認証後にユーザーのプロフィール情報に基づいて特定のアクションを実行するケースが考えられます。

例えば、最後に tokenの確認を行なった日時を更新したい場合、以下のような書き方が可能です。

# user = @resource です
validate_token do |user|
  user.update(token_last_validated_at: Time.zone.now)
end

yield @resource if block_given? という記述を行うことで、メソッドに柔軟性を与えることが可能になると思います。

勉強になりました。

参考にさせていただいた記事

Kernel.#block_given? (Ruby 3.3 リファレンスマニュアル)
devise_token_auth/app/controllers/devise_token_auth/token_validations_controller.rb at master · lynndylanhurley/devise_token_auth · GitHub
【Ruby入門】yieldの使い方まとめ | 侍エンジニアブログ
https://qiita.com/tsubasakat/items/0de47f8814b0848f1d16
Rubyのyieldって結局何なの?|よしだ

Rubyの標準モジュール「securerandom」を使ってトークンを作ってみた

こんにちは!kossyです!



さて、今回はRubyの標準モジュールである「securerandom」を使って、
トークンを作成する手順についてブログに残してみたいと思います。




環境
Ruby 2.5.1
Rails 5.2.3
MacOS Mojave





libディレクトリにtoken_generator.rbを作成
トークンの生成は色々な箇所で使うことになるかと思いますので、
モジュールにしておきます。

require 'securerandom'

module TokenGenerator
  def self.generate_token(num = 20)
    "#{SecureRandom.urlsafe_base64(num)}"
  end
end

このように実装することで、

token = Tokengenerator.generate_token

という感じでどこでもトークンが作成できるようになります。


SecureRandom.urlsafe_base64は、urlセーフなBase64方式の文字列を生成します。
URLセーフやBase64については下記が参考になりました。
qiita.com
qiita.com

Railsのvalidationでのlambdaの使い方

こんにちは!kossyです!




さて、今回は、Railsのvalidationでのlambdaの使い方を
ブログに残してみたいと思います。




後置ifでlambdaを渡す

サンプルコードを晒してみます。

class User

  before_validation :name, presence: true if lambda { |obj| name.will_save_change_to_name? || name.present? }

end

before_validationはレコードを保存する前に実行されるコールバックで、
サンプルコード内ではnameに対して実行されるように設定しています。
presence: trueはいわゆるnot_null制約で、レコード保存時にnameが存在していないと例外を発生させます。

今回のコードのキモは後置ifに続くlambda文で、
nameが変更されているかチェックする構文がwill_save_change_to_name?です。

nameが変更されているか、nameが存在すればpresence: trueのvalidationが実行されるようになります。

このようにlambdaを渡すと、validationの実行条件をカスタマイズできるようになります。
実際にバリデーションを設定すると、
「このシチュエーションの時はバリデーションして欲しくないなぁ」
といったケースが出てくるので、そんな時にlambdaを渡すやり方を検討して見るといいと思います。

DockerfileのENVとARGの違いとは?

こんにちは!kossyです!




さて、今回はDockerfileを記述する際に用いられるインストラクションの一つである、
ENVとARGの違いについてブログに残してみたいと思います。



そもそもDockerとはなんぞや?という方は、
knowledge.sakura.ad.jp

上記のサイトが参考になるかと思います。

また、書籍はこちらが入門用に最適かと思います。
https://www.amazon.co.jp/dp/B07GP1Q3VT/ref=dp-kindle-redirect?_encoding=UTF8&btkr=1



ENVとARGの違い

どちらも環境変数であるという点は一緒なのですが、用途が異なります。

ENVインストラクションは、
「Dockerfileを元に生成したDockerコンテナ内で使える環境変数」であるのに対して、
ARGインストラクションは、
「imgaeのbuild時に情報を埋め込むために用いられる一時的な環境変数」になっています。




参考にさせていただいたサイト
knowledge.sakura.ad.jp
https://youtu.be/5d8O7jm7Q5c
https://youtu.be/7mwoTs0LoYA

Cloud Storageにアプリをデプロイする際に最新のコードがすぐに反映されるようにする

こんにちは!kossyです!



さて、今回はGCPのCloud Storageにgsutilを使ってアプリケーションをデプロイする際に、
最新のコードがすぐに反映されるようにする方法を
ブログに残してみたいと思います。


これやらないとデプロイしても最新のコードが反映されないことがあるので、
要チェックですよ、、、



解答

先にデプロイコマンドを晒します。

$ gsutil -h "Cache-Control:private, max-age=0, no-transform" cp -r dist/* gs://application-url

gsutilはGoogle Cloud Storage をターミナルから操作するためのツールのことです。
apps-gcp.com

Cache-Control:privateは、下記サイトによると、
developers.google.com

レスポンスが「public」とマークされている場合は、レスポンスに HTTP 認証が関連付けられているとしても、さらにレスポンスのステータス コードが通常キャッシュ可能になっていない場合でも、レスポンスをキャッシュに保存できます。 通常は、明示的なキャッシュ情報(「max-age」など)によってレスポンスがキャッシュ可能であることが指定されているため「public」は必要ありません。

一方、「private」レスポンスは、ブラウザのキャッシュには格納できますが、 通常、対象ユーザーは 1 人のため、中間キャッシュに格納することは認められません。 たとえば、個人的なユーザー情報を含む HTML ページはそのユーザーのブラウザでのみキャッシュに格納でき、CDN では格納できません。

とのこと。
キャッシュの扱い方について決めていると理解すれば良さそうです。


max-age=0は、これまた下記サイトによると、
developers.google.com

取得したレスポンスを再使用できる最大時間を、リクエストの時刻を起点とする秒数で指定します。
たとえば、"max-age=60" は、レスポンスを 60 秒間キャッシュに格納して再使用できることを示します。

とのこと。
今回は0と指定しているので、レスポンスを再使用しないようにしているという理解で良さそうです。


no-transformは、またまた下記サイトによると、
developers.google.com

リソースに対して変換を行うべきではないことを表します。Content-Encoding、Content-Range、Content-Type ヘッダーをプロキシによって変更してはいけません。例えば、不透明なプロキシや GoogleのWeb Light などのブラウザの機能は、キャッシュ空間を節約したり、低速なリンク上でのトラフィック量を減らしたりするために、画像フォーマットを変換することがあります。no-transform ディレクティブはこれを禁止します。

とのこと。
余計なフォーマットの変換などをさせない設定だと思えば良さそうです。

cp 以下のコマンドは指定したURLに向けてリソースを配置しに行っているだけです。



これでデプロイ直後にForce Reloadを行わなくてもよくなりました、、、


最新のコードがmax-ageが原因ではない場合もありますので、その場合は
qiita.com
上記のサイトを参考に、エッジサーバーのキャッシュの設定を確認してみてください。



参考にさせていただいたサイト
AWS S3更新時にLambdaでCloudFrontのInvalidationを自動実行 - Qiita

Rubyのbegin-end構文で変数を定義する

こんにちは!kossyです!




さて、今回はRubyのbegin-end構文で変数を定義する方法についてブログに残してみたいと思います。




begin-end構文とは?

begin-endは例外処理でよく用いられるイメージですが、例外処理以外の用途でも使うことができるようです。

What is use of ||= begin....end block in Ruby? - Stack Overflow

@valiable ||= begin
  処理
end

という構文で使うことができます。


Rails内での使われ方

Railsのコードなら間違いないだろうということで紹介してみます。

rails/engine.rb at 0105fd4a1ec5579fbc824c7a864794ba90351c39 · rails/rails · GitHub

def helpers
  @helpers ||= begin
    helpers = Module.new
    all = ActionController::Base.all_helpers_from_path(helpers_paths)
    ActionController::Base.modules_for_helpers(all).each do |mod|
      helpers.include(mod)
    end
    helpers
  end
end

@helpersというインスタンス変数を定義するためにbegin-end構文を用いてますね。

もう少し探ってみましょう。

rails/app_updater.rb at 0105fd4a1ec5579fbc824c7a864794ba90351c39 · rails/rails · GitHub

def app_generator
  @app_generator ||= begin
    gen = Rails::Generators::AppGenerator.new ["rails"], generator_options, destination_root: Rails.root
    File.exist?(Rails.root.join("config", "application.rb")) ? gen.send(:app_const) : gen.send(:valid_const?)
    gen
  end
end

rails/application.rb at 0105fd4a1ec5579fbc824c7a864794ba90351c39 · rails/rails · GitHub

def secrets
  @secrets ||= begin
    secrets = ActiveSupport::OrderedOptions.new
    files = config.paths["config/secrets"].existent
    files = files.reject { |path| path.end_with?(".enc") } unless config.read_encrypted_secrets
    secrets.merge! Rails::Secrets.parse(files, env: Rails.env)

    # Fallback to config.secret_key_base if secrets.secret_key_base isn't set
    secrets.secret_key_base ||= config.secret_key_base

    secrets
  end
end

冒頭にも記述した通り、

@valiable ||= begin
  処理
end

という構文さえ覚えておけば良さそうです。

ちなみに ||= という式は、
「左辺が偽または未定義であれば、右辺の評価値を代入する」というイディオムになります。



3つほど例を見て気がつきましたが、
インスタンスを生成してインスタンスのattrの値に複雑だったり冗長な処理結果を代入して、インスタンス自身を返り値としたい時に使ってますね。

実務で使う時は上記のシチュエーションに遭遇した時に使えそうです。


勉強になりました。



参考にさせていただいたサイト

GitHub - rails/rails: Ruby on Rails
Ruby で Proc を即時実行する代わりに begin 式で代入する - Qiita

development_secret.txtってなんだ?

こんにちは!kossyです!



さて、今回はRails 6 でアプリケーションを開発している時に見つけたテキストファイルである、
「./tmp/development_secret.txt」についてブログに残してみたいと思います。



dev環境およびテスト環境の場合にsecret_key_baseを生成するために使う

Railsの公式ドキュメントを見てみました。

Rails::Application

The secret_key_base is used as the input secret to the application's key generator, which in turn is used to create all MessageVerifiers/MessageEncryptors, including the ones that sign and encrypt cookies.

In development and test, this is randomly generated and stored in a temporary file in tmp/development_secret.txt.

In all other environments, we look for it first in ENV, then credentials.secret_key_base, and finally secrets.secret_key_base. For most applications, the correct place to store it is in the encrypted credentials file.

secret_key_baseは、アプリケーションのキージェネレーターへの入力シークレットとして使用され、Cookieの署名および暗号化を含むすべてのMessageVerifiers / MessageEncryptorsの作成に使用されます。
開発環境およびテスト環境では、これはランダムに生成され、tmp / development_secret.txtの一時ファイルに保存されます。
他のすべての環境では、まずENVで検索し、次にcredentials.secret_key_base、最後にsecrets.secret_key_baseで検索します。ほとんどのアプリケーションでは、暗号化された資格情報ファイルに正しい場所が保存されています。

ソースコードはこの辺りかと。

def secrets
  @secrets ||= begin
    secrets = ActiveSupport::OrderedOptions.new
    files = config.paths["config/secrets"].existent
    files = files.reject { |path| path.end_with?(".enc") } unless config.read_encrypted_secrets
    secrets.merge! Rails::Secrets.parse(files, env: Rails.env)

    # Fallback to config.secret_key_base if secrets.secret_key_base isn't set
    secrets.secret_key_base ||= config.secret_key_base

    secrets
  end
end

def secret_key_base
  if Rails.env.development? || Rails.env.test?
    secrets.secret_key_base ||= generate_development_secret
  else
    validate_secret_key_base(
      ENV["SECRET_KEY_BASE"] || credentials.secret_key_base || secrets.secret_key_base
    )
  end
end

def generate_development_secret
  if secrets.secret_key_base.nil?
    key_file = Rails.root.join("tmp/development_secret.txt")

    if !File.exist?(key_file)
      random_key = SecureRandom.hex(64)
      FileUtils.mkdir_p(key_file.dirname)
      File.binwrite(key_file, random_key)
    end

    secrets.secret_key_base = File.binread(key_file)
  end

  secrets.secret_key_base
end

developement環境またはtest環境の時はtmp下のtxtファイルを用いてsecret_key_baseを決める実装になってます。


勉強になりました。

  @valiable ||= begin
  end

の書き方は実務でも使えそうです。

docker の コンテナやイメージを一括削除する

こんにちは!kossyです!



さて、今回はdockerのコンテナとイメージを一括削除するコマンドをブログに残してみたいと思います。


コンテナを一括削除する

コマンドはこちらです。

$ docker ps -aq | xargs docker rm

docker ps -ap で、停止中のコンテナも含めた、すべてのコンテナのIDのみを表示できます。
パイプ(複数のコマンドを組み合わせて使うために用いるLinuxコマンド)

xargsもLinuxのコマンドで、こちら(
【 xargs 】コマンド――コマンドラインを作成して実行する:Linux基本コマンドTips(176) - @IT
)によると、

「xargs」は、「標準入力やファイルからリストを読み込み、コマンドラインを作成して実行する」というコマンドです。

 例えばあるコマンドの出力をパイプでxargsコマンドに送り込み、別のコマンドの引数として指定するといった使い方ができます。

という用途で使います。

上記のコマンドは起動中のコンテナがあると実行に失敗するので、
$ docker stop コンテナID
で起動中のコンテナを止めてから実行しましょう。



イメージを一括削除する

コマンドはこちらです。

$ docker images -aq | xargs docker rmi

イメージの削除もLinuxの構文を使って行います。
コンテナと異なるのはdocker ps ではなく docker images を実行するところですね。

dockerの使い方を覚える前にLinuxのコマンドをまず覚える必要がありそうです、、、笑


参考にさせていただいたサイト
Docker一括削除コマンドまとめ - Qiita
【 xargs 】コマンド――コマンドラインを作成して実行する:Linux基本コマンドTips(176) - @IT