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

AWS/Java/Node.js/Spreadsheets/Docker/Jenkins/コミュニティ・勉強会レポを主とした技術系ブログ

Adminerが手軽で便利。MySQLなどデータベースに繋ぐ

Adminerとは

Webブラウザから使用するPHP製データベースクライアントです。
サブタイトルにDatabase management in a single PHP fileと記載のある通りphpファイルを置くだけです。
phpMyAdminに似ていますが、MySQL以外にも対応しています。

発音はアドマイナーです。

公式サイト https://www.adminer.org/
GitHub vrana/adminer: Database management in a single PHP file

phpMyAdminよりイイ

インストールは公式サイトからダウンロードして、FTPPHPファイル置くだけですから手軽です。使いにくいレンタルサーバーの管理画面からphpMyAdminを入れてetc...とやらなくて良いのです。
たとえばCakeであればFTP./app/webroot/配下にダウンロードしたadminer-4.3.1-mysql.phpを置くだけで使えます。

何が良いかって、緊急時にサクッと繋げられるのが良いですね。

f:id:ryoichi0102:20170926174522p:plain

上記キャプチャがログイン画面です。
一通りの情報を入力するところがありますね。
ポートの指定は サーバー名:ポート番号 です。(例 127.0.0.1:3306)
3306の場合は省略可能です。

ログイン後はphpMyAdminと同じように、SQLの発行やデータのインポート/エクスポートなど色々出来ます。

※ 緊急時の利用であれば、作業が終わった後にアップしたphpファイルを消しておくことをオススメします。

余談: ファーストサーバーがZenlogicに移行した影響で…

通知から1ヶ月ぐらいでサクッと移行されてしまった気がするのですが、移行前のMySQL環境がポート番号を変更され、localhostからの接続ができないというナカナカの対応をしてくれたようです。

Request URL: /
Stack Trace:
#0 /app/webroot/index.php(108): Dispatcher->dispatch(Object(CakeRequest), Object(CakeResponse))
#1 {main} yyyy-mm-dd hh:mm:ss Error: [MissingConnectionException] Database connection "Mysql" is missing, or could not be created. Exception Attributes: array (
'class' => 'Mysql',
'message' => 'SQLSTATE[28000] [1045] Access denied for user \'mysqluser\'@\'localhost\' (using password: YES)',
'enabled' => true, )

こんなん出るしね。
復旧にはDB接続設定を、localhostからループバックアドレス127.0.0.1に変更して、ポートの指定を変更しないといけないという対応。
特に下線の対応がなかなかのノーヒントというか、宝探し的というか、情報が纏まっていないというか…

DBのダンプを取って移行しようにも、外部からの接続が不可だし(これは仕方ないとして)、phpMyAdminが不可になってて、管理画面からのバックアップ機能も停止。
陸の孤島的な。。

そんな時はAdminerですよ!

参考

Adminer / Bugs and Features / #287 Connect to another port

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)が設定されていない可能性がありますので確認してください。