メンチカツには醤油でしょ!!

ITエンジニア徒然 (AWS/Java/JavaScript/Google Spreadsheets/Jenkins/Mac/外部コミュニティ・勉強会レポ)

ThymeleafのJava 8 LocalDateTime対応で選択変数式th:objectとth:textの実装方法

Java8のLocalDateTimeをThymeleafで使うには

build.gradleにthymeleaf-extras-java8timeを足して
@Configurationの付いたクラス(ThymeleafConfigとか)にjava8TimeDialect()を実装して
htmlのth:textでは"${#temporals.format(myDatetime}, 'yyyy/MM/dd HH:mm')}"と書く方法が定番かと思われます。

(LocalDateTimeでもLocalDateでもLocalTimeでも同じですね。)

選択式変数と組み合わせて使う場合にはちょっと工夫が必要でした。
選択式変数とはforeachのようなシチュエーションで使う構文で、下記のようにth:objectで指定するとループ内ではアスタリスクで指定できるという利点があります。

<table><tr th:each="record : ${records}" th:object="${record}">
  <td th:text="*{value}">
</tr></table>

環境

* Spring Boot 1.5.6
* Thymeleaf 2.1.5
* Thymeleaf Module for Java 8 Time API compatibility 2.1.0.RELEASE

実装

build.gradle (抜粋)
dependencies {
  compile('org.springframework.boot:spring-boot-starter-web')
  compile('org.springframework.boot:spring-boot-starter-thymeleaf')
  compile('org.thymeleaf.extras:thymeleaf-extras-java8time:2.1.0.RELEASE')
  // 略
}
ShowListController.java (抜粋)
  @RequestMapping(method = RequestMethod.GET)
  public String index(Model model) {

    List<MyBean> myList = query(); // 何かしらの検索処理.

    model.addAttribute("myList", myList);

    return "showList";
  }
MyBean.java (抜粋)
@Data
public class MyBean implements Serializable {
  private int myNumber;
  private int myString;
  private LocalDateTime myDatetime;
}
showList.html (抜粋)
<tr th:each="myBean : ${myList}" th:object="${myBean}">
  <td th:text="*{myNumber}"></td>
  <td th:text="*{myString}"></td>
  <!-- こう書くとダメ -->
  <td th:text="${#temporals.format(*{myDatetime}, 'yyyy/MM/dd HH:mm')}"></td>
  <!-- これはまぁOK -->
  <td th:text="${#temporals.format(myList.myDatetime, 'yyyy/MM/dd HH:mm')}"></td>
  <!-- こう書くと良い -->
  <td th:text="*{#temporals.format(myDateTime, 'yyyy/MM/dd HH:mm')}"></td>
</tr>

ダメな方の実装だと
There was an unexpected error (type=Internal Server Error, status=500).
Exception evaluating SpringEL expression: "#temporals.format(*{myDatetime}, 'yyyy/MM/dd HH:mm')" (showList:46)
ってエラーが出てStacktraceは
org.springframework.expression.spel.SpelParseException: Expression [#temporals.format(*{myDatetime}, 'yyyy/MM/dd HH:mm')] @46: EL1070E: Problem parsing left operand
って感じです。

チュートリアルをちょっと変更する感じだと、こう書きがちなんですが、
これは慣れの問題ですかね。

ちなみに
"#{temporals.format(myDatetime, 'yyyy/MM/dd HH:mm')}"
と書いても表示は
??temporals.format_ja??
とか表示されてダメです。(まぁこれは当然ですね)

間違えて
"${#temporals.format(myDatetime, 'yyyy/MM/dd HH:mm')"
と書くと
java.lang.IllegalArgumentException: Cannot apply format on null
になったり、こねくり回してると
org.springframework.expression.spel.SpelEvaluationException: EL1011E: Method call: Attempted to call method format(null,java.lang.String) on null context object
が出たりしますが、
*{#temporals.format(myDateTime, 'yyyy/MM/dd HH:mm')}"
が正解です。

参考

masatoshitada.hatenadiary.jp

qiita.com

S3でhttpsをホスティングするためにCloudFront+ACMを利用する

S3のホスティングhttps化する

(2017/08 現在)
静的なウェブサイトなどをホスティングする際にはS3が超便利で今はこれ以外の選択肢が考えられないほどですが、このS3でホスティングしているサイトをAmazon CloudFrontACM(AWS Certificate Manager)を使ってhttps化します。

http://www.your-domain.com.s3-website-ap-northeast-1.amazonaws.com/
↓ (こんな感じ) ↓
https://www.your-domain.com/

前提

・S3でサイトはアップロード済
・独自メイン取得済 (お名前.comとかでも良い、Route53は必須ではない)
Amazon SESなどを利用して独自ドメインのメール送受信はできる状態

ちなみに、ドメインの取得は後からにして、とりあえずCloudFrontを設定してみるって方は一部の設定を飛ばせばCloudFront⇒S3という流れは作れますし、あとから追加で設定を入れることも可能です。

ドメインにメール受信設定がされていない場合はこちらの記事も参考にしてください。

S3の設定 - バケット命名

S3の独自ドメイン名はホスティング予定のドメイン名と合わせましょう。
今回は前述の例のように、取得したドメインがyour-domain.comで、S3のバケット名がwww.your-domain.comとして話を進めます。

先にACMの設定を!

先にACM(AWS Certificate Manager)の設定をしないと、CloudFrontの設定最中にACMの設定が割り込んでしまい煩雑になるので先にACM設定をしちゃいます。
なおこの順番はマストではなく推奨です。

証明書のリクエスト - ACM設定

リージョンはバージニア北部(us-east-1)が選択されていることを確認してください。
ドメイン名を *.your-domain.com で入力して、確認とリクエスト ボタンを押下します。
すると、ドメイン宛にメールが送られてきます。
f:id:ryoichi0102:20170807150930p:plain

メール内のリンクを踏んでI Approveボタンを押下します。
Success! 画面に遷移すればACMの設定は完了です。
f:id:ryoichi0102:20170807150949p:plain

CloudFront 設定

コンソールでCloudFrontの画面に行き、Create Distributionボタンを押下します。
f:id:ryoichi0102:20170807151238p:plain

Select a delivery method for your content.

今回はWebですのでWeb側の方のGet Startedボタンを押下します。
f:id:ryoichi0102:20170807151436p:plain

Create Distribution

キャプチャは長いですが、キャプチャの下に書き換える所を纏めてあります。
f:id:ryoichi0102:20170807152822j:plain

Origin Settingsセクション

Origin Domain Name : S3のバケット名を一部入力すればアシストが表示されますので、選択。www.my-domain.com.s3.amazonaws.comという形式に。

Origin Path : ルートに配置しているindex.htmlをhttps://www.my-domain.com/で表示させる前提の場合は空で。入力する場合は/始まりです。

Origin ID : Origin Domain Nameを選択すると、自動的に入力されると思います。S3-www.your-domain.comのような形式。

Restrict Bucket Access : バケットへのアクセスを制限する場合はYesに。設定後は基本S3ではなくCloudFrontからアクセスすると思いますので、Yesに。

Origin Access Identity : Create a New Identityのまま。Commentは空のままで良い。

Grant Read Permissions on Bucket :バケットへの読取権限を与えるかどうか。Noを選択すると自分でやらないといけなくなるので、Yes, Update Bucket Policyを選択します。

Default Cache Behavior Settings セクション

Viewer Protocol Policy : Redirect HTTP to HTTPS、HTTPへのアクセスをHTTPSにリダイレクトするのでこれを選択します。

Distribution Settings セクション

Alternate Domain Names (CNAMEs) : www.your-domain.comのように(後続の手順でDNSレコードのCNAMEとして登録する値を指定します。

SSL Cretificate : 前述のACM設定を先にしていないと、Default CloudFront Distributionが選択されておりCustom SSL Certificateが選択できず、Request or Import a Certificate with ACMボタンを押下しこれを先に設定することになり面倒です。(東京リージョンだけACM設定している場合などもこうなります。)
ACM設定は既にあると思いますので、Custom SSL Certificateを選択し対象のドメインを選択します。

Default Root Object : index.htmlなどhttps://www.your-domain.com/で指定した場合に表示されるページ(htmlなど)を指定します。

最下部のCreate Distributionボタンを押下すると画面が遷移しますが、まだ完了してません。
f:id:ryoichi0102:20170807153715p:plain

In ProgressがDeployedになれば完了です。
数分かかるかも知れません。
f:id:ryoichi0102:20170807153828j:plain

CloudFront 動作確認

コンソールよりID(Distribution ID)をクリックすると詳細画面に遷移します。
その中で、Domain Nameと記載ある所に、CloudFrontのサブドメインが表示されています。これがCloudFrontを介したドメイン名です。
http://a1b2c3d4e5f6.cloudfront.net/ にアクセスして
https://a1b2c3d4e5f6.cloudfront.net/ にリダイレクトされることを確認しましょう。

f:id:ryoichi0102:20170807154552j:plain

表示されない場合 : Access Denied

This XML file does not appear to have any style information associated with it. The document tree is shown below.
<Error>
<script/>
<Code>AccessDenied</Code>
<Message>Access Denied</Message>
<RequestId>A1B2C3D4E5F6G7H8</RequestId>
<HostId>***************************************************************************=</HostId>
</Error>

http://a1b2c3d4e5f6.cloudfront.net/ がダメで、http://a1b2c3d4e5f6.cloudfront.net/index.html なら表示される場合は前述のDefault Root Objectの設定が原因と思われます。

ドメインDNSレコード設定

www.your-domain.com の CNAME に a1b2c3d4e5f6.cloudfront.net (さきほどCloudFrontでDomain Nameに表示されていた値)を設定します。

繋がらない場合 : The request could not be satisfied.

ERROR The request could not be satisfied.
Bad Request
Generated by cloudfront (CloudFront)
Request ID: ~~~

と出る場合は、前述のAlternate Domain Names (CNAMEs)が設定されていない可能性がありますので確認してください。

WordPressのBroken Link Checkerをアップデートしたら500エラーで死んだのでプラグイン削除

取り急ぎ

現象と対応方針

WordPressのBroken Link Checkerをアップデートしたら
サイト自体が500エラーで死んでしまい、管理画面にも入れず。

Broken Link Checkerプラグインが原因でしたので削除しました。

Broken Link Checkerは2017/08/02のバージョン1.11.4です。

原因

PHPのバージョンが古いとこうなります。
根本解決としては、アップデートしましょう。

プラグインを外す手順

FTPWordPressのインストールされているサーバーにログインします

WordPressディレクトリの /wp-content/plugins/broken-link-checkerをbroken-link-checker-2 など適当な名前にリネームします。

・サイトのアクセスは復活するはず

・管理画面ログイン

プラグイン一覧よりBroken Link Checkerを削除

 

プラグインをどうするかは後日考えますm(_ _)m

参考

Topic: Version 1.11.4 is broken « WordPress.org Forums