LaravelでCSRFトークンを入れているのに419 PAGE EXPIRED

LaravelでPOSTリクエスト等を送信する場合は、CSRF対策用のトークンをhiddenタグにセットします。通常@csrfディレクティブを<form></form>の中に入れることで、hiddenタグが自動生成されます。

これを入れ忘れて、フォーム送信時に419 PAGE EXPIREDというエラーを発生させるのが、よくやりがちなミスですが、今回@csrfを入れているのにも関わらず、419エラーが発生することがありました。

他で紹介されているキャッシュやセッションファイルの削除等を試しましたが、エラーが解消されず、色々と調べた結果、POSTリクエストに含まれる変数の数(inputタグ等の数)が多すぎたため、PHPの設定により上記トークンを含むリクエストの一部が削除されてしまっていたことが分かりました。

php.iniのmax_input_varsという項目を設定することで解消しました。(初期値は1000)

; How many GET/POST/COOKIE input variables may be accepted
max_input_vars = 100000

リクエストのデータサイズが大きい場合は、次の設定も更新する必要があります。

; Maximum size of POST data that PHP will accept.
; Its value may be 0 to disable the limit. It is ignored if POST data reading
; is disabled through enable_post_data_reading.
; http://php.net/post-max-size
post_max_size = 64M

; Maximum allowed size for uploaded files.
; http://php.net/upload-max-filesize
upload_max_filesize = 64M

CentOSにNoto Sans CJK JPをインストール

Noto Sans CJK JPはGoogleとAdobe(Adobe側の名称は源ノ角ゴシック/Source Han Sans JP)が共同開発したオープンソースのフォントです。視認性が高く、無料で使えるため、多くのWebサイトで利用されています。(当サイトも利用しています。)

https://fonts.google.com/specimen/Noto+Sans+JP

https://www.google.com/get/noto/help/cjk/

Webサイトで利用する場合は、HTMLかCSSにWebフォントとして利用するための設定を行えば表示されますが、サーバー側の処理で作成するPDF等で利用するには、フォントをサーバーにインストールする必要があります。

ここではCentOSにNoto Sans CJK JPをインストールする手順についてご説明します。

フォントのダウンロード

$ wget https://noto-website-2.storage.googleapis.com/pkgs/NotoSansCJKjp-hinted.zip
$ unzip NotoSansCJKjp-hinted.zip -d NotoSansCJKjp

フォントディレクトリに移動

$ sudo mv NotoSansCJKjp /usr/share/fonts/NotoSansCJKjp
$ sudo chmod 644 /usr/share/fonts/NotoSansCJKjp/*

キャッシュを更新

$ sudo fc-cache -fv

sudo: fc-cache: command not foundと表示が出た場合、フォント管理のためのライブラリFontconfigがインストールされていない可能性があります。その場合は、yumを利用してインストールします。

$ sudo yum install fontconfig -y

インストールの確認

$ fc-list | grep NotoSansCJKjp
/usr/share/fonts/NotoSansCJKjp/NotoSansCJKjp-DemiLight.otf: Noto Sans CJK JP,Noto Sans CJK JP DemiLight:style=DemiLight,Regular
/usr/share/fonts/NotoSansCJKjp/NotoSansMonoCJKjp-Bold.otf: Noto Sans Mono CJK JP,Noto Sans Mono CJK JP Bold:style=Bold,Regular
/usr/share/fonts/NotoSansCJKjp/NotoSansCJKjp-Black.otf: Noto Sans CJK JP,Noto Sans CJK JP Black:style=Black,Regular
/usr/share/fonts/NotoSansCJKjp/NotoSansCJKjp-Light.otf: Noto Sans CJK JP,Noto Sans CJK JP Light:style=Light,Regular
/usr/share/fonts/NotoSansCJKjp/NotoSansCJKjp-Thin.otf: Noto Sans CJK JP,Noto Sans CJK JP Thin:style=Thin,Regular
/usr/share/fonts/NotoSansCJKjp/NotoSansCJKjp-Bold.otf: Noto Sans CJK JP,Noto Sans CJK JP Bold:style=Bold,Regular
/usr/share/fonts/NotoSansCJKjp/NotoSansCJKjp-Medium.otf: Noto Sans CJK JP,Noto Sans CJK JP Medium:style=Medium,Regular
/usr/share/fonts/NotoSansCJKjp/NotoSansCJKjp-Regular.otf: Noto Sans CJK JP,Noto Sans CJK JP Regular:style=Regular
/usr/share/fonts/NotoSansCJKjp/NotoSansMonoCJKjp-Regular.otf: Noto Sans Mono CJK JP,Noto Sans Mono CJK JP Regular:style=Regular

以上でインストールは完了です。

Let’s Encryptでワイルドカードを利用する

証明書の作成

Let’s Encryptでドメインにワイルドカードを使用するには、次のコマンドを実行します。

※例として*.example.comを設定しています。以下、example.comのところを、お使いのドメインに置き換えていただければと思います。

$ certbot certonly --manual --preferred-challenges dns-01 -d *.example.com -m info@example.com

コマンドを実行すると、次のようなメッセージが出力されます。xxxx…のところには、実際には英数字およびアンダースコア等で構成された文字列が表示されます。

Performing the following challenges:
dns-01 challenge for example.com

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Please deploy a DNS TXT record under the name
_acme-challenge.example.com with the following value:

xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Before continuing, verify the record is deployed.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Press Enter to Continue

xxxx…の文字列を、DNSのTXTレコード(キーは_acme-challenge.example.com)として設定します。設定前に、勢い余ってEnterを押さないことに注意します。

TXTレコードの設定が反映されたら、Enterを押して完了です。

TXTレコードを確認するには次のコマンドが便利です。

$ host -t txt _acme-challenge.example.com

証明書の更新

ワイルドカードを使用しない場合、証明書の更新は次のコマンドで行うことができます。

$ certbot renew

ただ、上記手順で証明書を作成した場合、このコマンドでは次のエラーが発生します。

$ certbot renew

Saving debug log to /var/log/letsencrypt/letsencrypt.log

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Processing /etc/letsencrypt/renewal/example.com.conf
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Cert is due for renewal, auto-renewing...
Could not choose appropriate plugin: The manual plugin is not working; there may be problems with your existing configuration.
The error was: PluginError('An authentication script must be provided with --manual-auth-hook when using the manual plugin non-interactively.',)
Failed to renew certificate racks.jp with error: The manual plugin is not working; there may be problems with your existing configuration.
The error was: PluginError('An authentication script must be provided with --manual-auth-hook when using the manual plugin non-interactively.',)

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
All renewals failed. The following certificates could not be renewed:
  /etc/letsencrypt/live/example.com/fullchain.pem (failure)
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1 renew failure(s), 0 parse failure(s)

現在のところ、証明書の更新の際も作成と同じコマンドを使用して、DNS設定を再度行う必要があるようです。

$ certbot certonly --manual --preferred-challenges dns-01 -d *.example.com -m info@example.com

CentOSでLet’s Encryptを自動更新

Let’s Encryptは無料で利用できるSSL証明書です。ただ、90日間で期限が切れてしまうため、定期的に更新する必要があります。

証明書の更新はコマンドのみで実行できるため簡単ですが、それでも手動で行うのは手間が掛かりますし、更新を忘れてしまいがちですので、Cronを利用して自動実行するのが良いと思います。

$ sudo crontab -e
00 04 01 * * certbot renew && systemctl reload nginx

上記の例では、毎月1日早朝4時に証明書の更新を行っています。また、Webサーバーは更新済みの証明書を再度読み込む必要があるため、Nginxサービスに対してreloadコマンドを実行しています。


追記

CentOSでyumでインストールした場合、自動更新用のサービス(certbot-renew)が用意されているので、そちらを利用した方が良いと思われます。

$ sudo systemctl enable --now certbot-renew.timer

更新後にNginxをリロードする場合は、次のファイルに設定します。

$ sudo vi /etc/sysconfig/certbot
POST_HOOK="--post-hook 'systemctl reload nginx'"

UbuntuへのPHPのインストールとバージョンの切り替え

PHPのインストール

まず、PHP用のaptのPPA(Personal Package Archive)リポジトリを追加します。

$ sudo apt install -y software-properties-common
$ sudo add-apt-repository ppa:ondrej/php
$ sudo apt update

次に、追加したPPAリポジトリを利用して、必要なバージョンのPHPをインストールします。ここではPHP 7.3をインストールしています。

$ sudo apt install -y php7.3

PHPのバージョンを切り替える

開発しているアプリケーションに応じて、使用するPHPのバージョンを変えたい場合があると思います。その際には、update-alternativesコマンドを利用すると、簡単にバージョンを切り替えることができます。

例えば、php7.2に切り替えたい場合は、次のように行います。まず、aptコマンドでphp7.2をインストールします。

$ sudo apt install -y php7.2

その上で、update-alternativesコマンドを次のように実行します。既にインストールされているバージョンの一覧が表示されますので、インストールしたいバージョンのSelectionの列の数字を入力し、Enterを押します。

$ sudo update-alternatives --config php
There are 2 choices for the alternative php (providing /usr/bin/php).

  Selection    Path             Priority   Status
------------------------------------------------------------
  0            /usr/bin/php7.3   73        auto mode
  1            /usr/bin/php7.2   72        manual mode
* 2            /usr/bin/php7.3   73        manual mode

Press <enter> to keep the current choice[*], or type selection number: 1
update-alternatives: using /usr/bin/php7.2 to provide /usr/bin/php (php) in manual mode

以上で、バージョンの切り替えが完了します。

Ruby on Railsのマイグレーションのまとめ

Ruby on Railsにはマイグレーションという、データベースの変更管理を簡単に行うための便利な枠組みが用意されています。

この投稿では、このマイグレーション機能の使い方についてまとめたいと思います。(徐々に更新していきます。)

マイグレーションファイルの作成

マイグレーションファイルとは、データベースの変更内容を記述したruby形式のファイルで、app/db/migrate以下に保存します。このファイルをコミットに含めることで、ソースコードの変更に同期する形で、データベースの変更履歴をGit等のソースコードリポジトリ上で管理することができます。

マイグレーションファイルは、手動で作成しても良いですが、作成するためのrailsコマンドが用意されています。

$ rails g migration <変更内容>

変更内容には、どのような変更を行うのかを英語で記述します。例えば、新しくusersテーブルを作成する場合は

$ rails g migration create_users

とします。上記はスネークケースで書きましたが、パスカルケースで

$ rails g migration CreateUsers

としても同じ結果が得られます。このコマンドを実行すると、app/db/migrateディレクトリ以下に次のような名前のファイルが作成されます。

20201207233110_create_users.rb

最初の数字の列はマイグレーションファイルの作成日時を「yyyyMMddhhmmss」形式で入力されます。

ファイルの中身は次のようになります。

class CreateUsers < ActiveRecord::Migration[6.0]
  def change
    create_table :users do |t|
    end
  end
end

Jenkinsで古いビルドを削除する

Jenkinsを利用していると、実行済みのビルドがどんどん溜まっていってストレージを圧迫するため、定期的に古いビルドを削除する必要があります。

個別のビルドについては、ビルドの詳細ページから「Delete build ‘#xxx’」をクリックして削除できますが、一つ一つ実行するのは手間です。

そういう時はスクリプトコンソールを利用すると便利です。スクリプトコンソールは文字通りJenkins上でスクリプト(Groovy)を実行することができる機能で、「Jenkinsの管理」画面からアクセスすることができます。

特定のジョブのビルドをすべて削除する

jobName変数に削除したいジョブ名を設定し、次のスクリプトを実行します。

def jobName = "<ジョブ名>"  
def job = Jenkins.instance.getItem(jobName)  
job.getBuilds().each {
  it.delete()
}  

すべてのジョブについて古いビルドを削除する

MAX_BUILDS定数に、各ジョブにおいて残したい最新のビルドの数を設定し、次のスクリプトを実行します。

// 残すビルド数を設定
MAX_BUILDS = 20

for (job in Jenkins.instance.items) {
  def recent = job.builds.limit(MAX_BUILDS)
  for (build in job.builds) {
    if (!recent.contains(build)) {
      build.delete()
    }
  }
}

Bootstrap 4でモバイル端末ではタブの代わりにセレクトボックスを表示させる

CSSフレームワークのBootstrap 4を使うと、タブを使って表示するコンテンツを切り替える機能を簡単に実現できます。(Bootstrap 4のJavascriptを有効にしておく必要があります。)

<!-- タブ -->
<ul class="nav nav-tabs" id="myTab" role="tablist">
  <li class="nav-item" role="presentation">
    <a class="nav-link active" id="home-tab" data-toggle="tab" href="#home" role="tab" aria-controls="home" aria-selected="true">Home</a>
  </li>
  <li class="nav-item" role="presentation">
    <a class="nav-link" id="profile-tab" data-toggle="tab" href="#profile" role="tab" aria-controls="profile" aria-selected="false">Profile</a>
  </li>
  <li class="nav-item" role="presentation">
    <a class="nav-link" id="contact-tab" data-toggle="tab" href="#contact" role="tab" aria-controls="contact" aria-selected="false">Contact</a>
  </li>
</ul>

<!-- コンテンツ -->
<div class="tab-content" id="myTabContent">
  <div class="tab-pane fade show active" id="home" role="tabpanel" aria-labelledby="home-tab">...</div>
  <div class="tab-pane fade" id="profile" role="tabpanel" aria-labelledby="profile-tab">...</div>
  <div class="tab-pane fade" id="contact" role="tabpanel" aria-labelledby="contact-tab">...</div>
</div>

ただ、レスポンシブ対応のサイトにおいて、スマートフォン等の画面の小さいデバイスでアクセスした場合、タブの数が多いとタブの行が複数行になったりして、見栄えが良くありません。

そのような場合に、タブの代わりにセレクトボックスで表示するコンテンツを選べるようにするのが、一つの対処法だと思います。

まず、既存のタブを画面が小さいデバイスでは表示させないようにするため、上のコードの<ul>タグに対して、d-noneとd-md-flexクラスを追加します。

<!-- タブ -->
<ul class="nav nav-tabs d-none d-md-flex" id="myTab" role="tablist">
...

これによって、デバイスの横幅が768px未満の場合はタブを表示させないようにすることができます。768pxという閾値については、「md」を「sp」や「lg」に変更することで変えることができます。詳しくは、Bootstrap 4の公式ページをご参照ください。

https://getbootstrap.com/docs/4.5/layout/overview/

次に、セレクトボックスのコードをタブのコードの前か後ろに追加します。

<!-- セレクトボックス -->
<select class="form-control nav-select d-md-none">
    <option value="#home">Home</option>
    <option value="#profile">Profile</option>
    <option value="#contact">Contact</option>
</select>

d-md-noneによって、768px以上のデバイスでは表示されなくなります。

最後にセレクトボックスの選択に連動して表示するコンテンツを切り替えるためのJavascript(JQueryを利用)のコードを追加します。

<script>
  $('.nav-select').on('change', function(e) {
    var id = $(this).val();
    $('a[href="' + id + '"]').tab('show');
  });
</script>

以上です。

RailsでエラーをSlackに通知する

Rails標準のLoggerを活用して、エラーログをSlackのチャンネル上に表示する方法について説明いたします。

Slack Notifierのインストール

まず、RailsからSlackへのメッセージの送信に関しては、slack-notifierという便利なgemが公開されておりますので、こちらを利用させていただきます。

https://github.com/stevenosloan/slack-notifier

Slack Notifierをインストールするには

gem "slack-notifier"

をGemfileに追加し、

$ bundle install

を実行します。

Slack通知用のLoggerの作成

次に、Slack Notifierを利用してSlackにメッセージを通知するためのLoggerを、Rails標準のLoggerを継承して作成します。

class SlackLogger < ActiveSupport::Logger
  def initialize
    super(SlackDevice.new)
  end

  class SlackDevice
    def initialize
      webhook_url = '<Webhook用のURL>'
      channel = '<チャンネル名(#含む)>'
      username = '<投稿ユーザー名>'
      @notifier = Slack::Notifier.new webhook_url, channel: channel, username: username
    end

    def write(message)
      @notifier.ping message
    end

    def close
      # Do nothing
    end
  end
end

SlackLoggerにはオーバーライドしたコンストラクタ(initialize)とSlackDeviceというサブクラスを持たせています。

コンストラクタでは、SlackDeviceを初期化し、そのインスタンスを継承元のコンストラクタに渡しています。

通常、継承元のActiveSupport::Loggerの初期化時(内部ではRuby標準のLoggerを呼んでいます。)には、その引数にログを書き込むファイルパスかIOオブジェクトを指定します。ただ、ソースコードを追ってみると、writeメソッドとcloseメソッドを持つオブジェクトであれば、とりあえず動作することが分かります。(将来的にこの挙動が続く保証はありませんので、Rubyのバージョンアップにはご注意ください。)

https://github.com/ruby/logger/blob/master/lib/logger/log_device.rb

今回はその特性を利用し、ファイルパスやIOオブジェクトの代わりに、writeメソッドとcloseメソッドを持つSlackDeviceというクラスを定義し、そのオブジェクトを渡しています。

SlackDeviceはコンストラクタにおいて、先ほどインストールしたSlack Notifierを初期化しています。その際、引数としてSlackへの接続情報を渡します。(実際には、接続情報はハードコードせず、設定ファイルから渡すと良いと思います。)

Webhook用のURLの取得の仕方はSlackの公式ドキュメントをご参照ください。

https://slack.com/intl/ja-jp/help/articles/115005265063-Slack-%E3%81%A7%E3%81%AE-Incoming-Webhook-%E3%81%AE%E5%88%A9%E7%94%A8

また、SlackDeviceのwriteメソッドではSlack Notifierオブジェクトのpingメソッドを実行しています。closeメソッドは特になにもしません。

Loggerの設定

Railsでは標準でプロジェクトルートのlogフォルダ以下に<環境名>.logという名前のログファイルを作成します。

その挙動を維持したまま、Slackへの通知を追加するには、次のコードをapp/config/environments/<環境名>.rbに追加します。(環境別に分ける必要がない場合は、app/config/application.rbでも大丈夫です。)

logger = ActiveSupport::Logger.new("#{Rails.root}/log/#{Rails.env}.log")

# Slack連携用Logger
slack_logger = SlackLogger.new
extended_logger = logger.extend(ActiveSupport::Logger.broadcast(slack_logger))

config.logger = extended_logger

非同期処理への対応

Slackへの通知は同期的に行われるため、ログレベルをdebug(config.log_level = :debug)やinfoに設定していると、画面表示に非常に時間が掛かるようになり、現実的ではありません。ログレベルをerror以上に設定するか、または、Sidekiq等を利用して、Slackへの通知を非同期的に処理する仕組みを入れるのが良いかと思います。

WordPressの独自テーマで固定ページにも抜粋を設定

WordPressの投稿記事における「抜粋」設定機能は、投稿の一覧画面において記事のサマリとして表示させたり、またdescriptionのmetaタグに設定することで検索エンジン等の検索結果に表示させたりするのに便利です。

ただ、投稿では標準で抜粋は有効ですが、固定ページにおいては無効になっています。

これを有効にするためには、利用しているテーマのfunctions.phpに次の行を追加します。

add_post_type_support('page', 'excerpt');

Top