Works by

Ren's blog

プログラミング等IT技術関連でメモする

【Spring】JSONリクエストのBoolean型以外のパラメータを暗黙的にBooleanに変換させない

f:id:rennnosukesann:20181220183020p:plain:w400

Spring2ではJSONの非BooleanパラメータでもBoolean値にマッピングされてしまいます。 これはfasterxml/Jacksonのデフォルト仕様で、このマッピングを防ぐにはSpringで使用されるObjectMapperの設定を修正する必要があります。

環境

  • Kotlin: 1.3.21
  • Spring: 2.1.4 RELEASE
  • JDK : OpenJDK11.0.2

ソースはKotlinです。

Deserializerを定義

JSONリクエストパラメータをKotlin内オブジェクトにマッピングする際の規則を明示的に記述する Deserializer クラスを定義します。

BooleanDeserializer
class BooleanDeserializer : JsonDeserializer<Boolean>() {
    // JSONパラメータをBooleanにマッピングしようとすると呼ばれる
    override fun deserialize(jsonParser: JsonParser, ctxt: DeserializationContext): Boolean {
        return jsonParser.booleanValue // booleanValue : booleanでない値のときに参照されるとJsonParseError
    }
}

deserialize メソッドはJSONパラメータをBooleanにマッピングしようとすると呼ばれます。このとき、 booleanでない値をマッピングしようとするとJsonParseErrorが呼ばれるので、制御したい場合は適宜ハンドリングしてください。

DeserializerをObjectMapperに適用する

次に作成したDeserializerをSpringが使用するObjectMapperに設定し、前述の変換規則が適用されるようにします。

JsonConfig
@Configuration
class JsonConfig {

    @Bean
    fun objectMapperBuilder(): Jackson2ObjectMapperBuilder {
        val builder = Jackson2ObjectMapperBuilder()
        return builder
    }

    @Bean
    fun objectMapper(): ObjectMapper {
        val objectMapper = objectMapperBuilder().build<ObjectMapper>()
        objectMapper.registerModule(createModule())
        return objectMapper
    }

    private fun createModule(): Module {
        val module = SimpleModule()
        // boolean型以外を暗黙的に許可しない
        module.addDeserializer(Boolean::class.java, BooleanDeserializer())
        return module
    }

}

Springで使用される ObjectMapper 及び ObjectMapperBuilderを、@Bean 宣言したメソッドで上書きしています。 さらに createModule() 内で BooleanDecelializer を登録した Module オブジェクトを生成し、それを ObjectMapper に登録しました。

【Cloud Foundry】Cloud Foundryアプリケーションのインスタンスを明示的に指定してHTTPリクエストを送る

f:id:rennnosukesann:20190205125549p:plain:w300

Cloud Foundryインスタンスを複数立ち上げたときに、特定のインスタンスにアクセスするやり方のメモです。

X-CF-APP-INSTANCE ヘッダーでインスタンスを指定

Cloud Foundry上にデプロイしたアプリケーションにアクセスするとき、 X-CF-APP-INSTANCE ヘッダーをつけることでアクセスするインスタンスを指定することができます。

$ curl --request GET --url http://localhost:8080/api/v1/hoges/1 
  --header 'X-CF-APP-INSTANCE: 6aa4cf0f-1dd4-29b6-af62-cc21bc23df10:1'

X-CF-APP-INSTANCE の値は {アプリのGUID}:{インスタンスインデックス} のフォーマットで指定します。上記例では、インデックスの採番順序で2番目(インデックスは0開始)のアプリケーションインスタンスへアクセスしています。

ちなみに、アプリのGUIDは cf app {アプリ名} --guid で取得できます。

$ cf app hoge-app --guid
6aa4cf0f-1dd4-29b6-af62-cc21bc23df10

また、インスタンスのインデックスはインスタンス環境変数 $CF_INSTANCE_INDEX で取得可能です。

参考文献

docs.cloudfoundry.org

docs.cloudfoundry.org

【Spring】Spring Sessionでセッション情報をDb2上に保存する

f:id:rennnosukesann:20181220183020p:plain

Spring でのセッション

SpringではJava ServletのHttpSessionの仕組みを利用することができます。ServletのHttpSessionはSpringフレームワークの上に構築されたアプリケーション上でそのまま利用できるほか、Springの各種ライブラリによってラッピングされた形でも利用することができます。

アプリケーションサーバを複数台構成とした場合のセッション

特に設定を行わなかった場合、Servletのセッションはメモリ上で管理されます。もし複数台のサーバや複数のクラウドインスタンスにアプリケーションがデプロイされていると、各インスタンス間で個別のメモリ領域を持つことになり、互いにセッションの情報が共有されません。そのため、例えばあるインスタンス上アプリケーションでログインに成功したユーザが、別のインスタンス上アプリにアクセスすると未ログイン状態として扱われてしまう・・・といったことが起こります。

このような問題に対する解決策はいくつかあるのですが、今回はDB上にセッション情報を保存する方法を紹介します。

適用方法

今回はDBMSとしてDb2を使用します。 またSpringアプリケーションのビルドツールとしてGradleを利用し、 すでにDBMSへの接続は完了しているものとします。

Spring Sessionの導入

セッションの管理をDB上で行えるようにします。 build.gradle に下記パッケージを追加します。

dependencies {
    ....
    implementation('org.springframework.boot:spring-boot-starter-web')
    implementation('org.springframework.boot:spring-boot-starter-data-jpa')
    implementation("org.springframework.session:spring-session-core")
    implementation("org.springframework.session:spring-session-jdbc")
    ....
}

Configuration Beanクラスの定義

@EnableJdbcHttpSession アノテーションを付加した Config クラスを定義します。これにより、JDBC経由でDB上でセッションを管理できるようになります。

import javax.sql.DataSource;

import org.springframework.context.annotation.Bean;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.session.jdbc.config.annotation.web.http.EnableJdbcHttpSession;
import org.springframework.transaction.PlatformTransactionManager;

/**
 * 
 * <p>
 * セッション情報をJDBC経由でDB上で管理するためのConfiguration
 * </p>
 * 
 */
@EnableJdbcHttpSession
public class JdbcHttpSessionConfig {
  @Bean
  public PlatformTransactionManager transactionManager(DataSource dataSource) {
    return new DataSourceTransactionManager(dataSource);
  }
}

Spring設定ファイルの変更

application.properties に下記項目を追加します。

# Session Setting in DB
server.session.jdbc.initialize-schema=always
server.session.jdbc.table-name=SPRING_SESSION

application.yml の場合は以下。

server:
    sesion:
        jdbc:
            initialize-schema: always
            table-name: SPRING_SESSION

2019/06/02追記

application.yml application.properties なしでも動作します。 テーブル名をデフォルトの SPRING_SESSION から変更したい場合 @EnableJdbcHttpSession のプロパティ tableName に値を渡します。

@EnableJdbcHttpSession(tableName = "APP_SESSION")

テーブルの追加

設定は上記で完了なのですが、このままではセッションを保存できません。 既存のDBにセッション情報を追加するテーブルを定義する必要があります。

Spring SessionのGitHubリポジトリDBMS別のテーブルCREATE用SQLがおいてあるので、こちらを活用しましょう。

ex. Db2の場合(少し編集してあります):

CREATE TABLE SPRING_SESSION (
    PRIMARY_ID CHAR(36) NOT NULL,
    SESSION_ID CHAR(36) NOT NULL,
    CREATION_TIME BIGINT NOT NULL,
    LAST_ACCESS_TIME BIGINT NOT NULL,
    MAX_INACTIVE_INTERVAL INT NOT NULL,
    EXPIRY_TIME BIGINT NOT NULL,
    PRINCIPAL_NAME VARCHAR(100),
    CONSTRAINT SPRING_SESSION_PK PRIMARY KEY (PRIMARY_ID)
)
ORGANIZE BY ROW;

CREATE UNIQUE INDEX SPRING_SESSION_IX1 ON SPRING_SESSION (SESSION_ID);
CREATE INDEX SPRING_SESSION_IX2 ON SPRING_SESSION (EXPIRY_TIME);
CREATE INDEX SPRING_SESSION_IX3 ON SPRING_SESSION (PRINCIPAL_NAME);

CREATE TABLE SPRING_SESSION_ATTRIBUTES (
    SESSION_PRIMARY_ID CHAR(36) NOT NULL,
    ATTRIBUTE_NAME VARCHAR(200) NOT NULL,
    ATTRIBUTE_BYTES BLOB NOT NULL,
    CONSTRAINT SPRING_SESSION_ATTRIBUTES_PK PRIMARY KEY (SESSION_PRIMARY_ID, ATTRIBUTE_NAME),
    CONSTRAINT SPRING_SESSION_ATTRIBUTES_FK FOREIGN KEY (SESSION_PRIMARY_ID) REFERENCES SPRING_SESSION(PRIMARY_ID) ON DELETE CASCADE
)
ORGANIZE BY ROW;

Db2は列指向*1を定義する機能を持ち、一部データ分析用DBではこの機能がデフォルトになっていたりします(IBM Cloud Db2 Warehouseなど)。この方針のままだと、BLOB 型定義をサポートしていない仕様のため*2、上記SQLでは元のSQLに明示的に ORGANIZE BY ROW を指定し、メジャーなRDBMS同様の行指向操作がされるようにしています。


2019/06/02追記

@EnableJdbcHttpSession にてテーブル名を変更した場合、上記SQLでもテーブル名を変更します。


これで、DB上にセッションが保持されるはずです!

f:id:rennnosukesann:20190204174125p:plain

参考文献

qiita.com

qiita.com

*3 ja.wikipedia.org

*1:列指向の概念をよくわかっていない...
曰く、通常のRDBMSで行単位での操作をするように、列単位での操作を主とするらしい

*2:https://www-01.ibm.com/support/docview.wss?uid=swg22003176

*3:完全にDBMS=RDBMSの文脈前提で語っている...

【Grafana】Grafanaでアラート時通知を設定する

f:id:rennnosukesann:20190129142110p:plain

Grafanaで可視化対象になっている監視サーバが障害を検知したら、Grafana側でメールやWebhookなどの通知を行うための設定メモです。

Grafanaでメール通知を設定する

ダッシュボード左上のGrafanaアイコンをクリックし、プルダウンから「Alerting」->「Notification channels」をクリック。 f:id:rennnosukesann:20190129110135p:plain

「+New Channel」をクリック。

f:id:rennnosukesann:20190129110402p:plain

「Name」に設定のタイトルを任意に入力。
「Description」には設定内容の概要を任意に入力。
「Type」はEmailを選択。
「Email ID」に送信したいメールアドレスを入力してください。

f:id:rennnosukesann:20190129111342p:plain

「Send Test」を押すと試しに「Email ID」に指定したメールアドレス宛にメールを送ることができます。

メールが受け取れることが確認できたら「Save」をクリック。簡単。

監視対象メトリクスと通知設定を紐付ける

メールの設定が完了したら、今度はメール送信のトリガーとなるアラートを設定します。

アラートを設定したいメトリクスのグラフを選択肢、タイトルをクリックします。クリックして表示されるEditボタンをクリックします。

f:id:rennnosukesann:20190129164016p:plain

出現するGraph設定用ボードの「Alert」タブを開き、「Create Alert」をクリック。

f:id:rennnosukesann:20190129164057p:plain

「Alert Config」でアラートのタイトル・しきい値を設定します。
「Conditions」ではアラート条件を指定します。「WHEN」にはグラフ中のどのタイミングでの値を参照するか決定します。デフォルトは last() で、グラフの最右端の値を参照します。「or」句で他の条件を指定しています。

「IS BELOW」の部分ではメトリクス上で参照する値のしきい値を設定しています。「IS BELOW」であれば参照値がしきい値を下回ったときに、「IS ABOVE」であれば参照値がしきい値を上回った場合にアラートを行います。

f:id:rennnosukesann:20190129164132p:plain

下のプルダウンでは、値が未定・nullのとき/実行時エラー・タイムアウトのときにアラートを行うかどうかを設定できます。

これで設定完了です。

参考文献

Grafana Conditionの設定

docs.grafana.org

【Spring Security】セッションのデフォルト有効期限は30分しかない

f:id:rennnosukesann:20181220183020p:plain

というかServletの仕様ですね。

Spring Securityにおけるセッションの有効期限

Spring Securityでは、セッション情報をJava Servletで扱うようなHttpSessionとして扱えます。またセッションに紐づく情報は、クッキーであるHttpCookieオブジェクトとして扱えます。

    ServletRequestAttributes attr = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();
    HttpServletRequest request = attr.getRequest();
    Session session = request.getSession();

    HttpServletResponse response = attr.getResponse();
    Cookie cookie = new Cookie();
    cookie.setPath("/");
    response.addCookie(cookie);

ただしこのセッション、デフォルトでは有効期限が30分となっているため、より長い期限を設けたい場合は明示的に有効期限を定義する必要があります。

簡単な有効期限の設定方法として、 application.properties(yml) に記述する方法があります。

server.session.timeout=3600 // 秒数で指定(負の数で無限)

また別の方法として、 HttpSession に直接指定する方法もあります。

    // セッションタイムアウト時間を秒数で指定
    //(負の数で無限)
    session.setMaxInactiveInterval(3600);

各クッキーの有効期限を設定する

セッションそのもの(セッションID)の有効期限ではなく、セッションに紐づく個々のクッキーの有効期限にも気をつけるべきです。

こちらも同様に application.properties(yml) に記述できます。

server.session.cookie.max-age=3600

一回ハマったはずなのに、完全に忘れてた。。。

ブログタイトルとデザインを更新しました

突然ですが、本ブログのタイトルと、ブログ全体のデザインを更新させていただきました。

f:id:rennnosukesann:20190113185422p:plain

今までははてなブログで提供されていたデフォルトのブログテーマを使わせていただきましたが、そろそろデザインを変えたいと思い、またどうせ変えるなら自分でカスタムしたいという思いもあり、ブログテーマを一から作ってみることにしました。

ブログデザインを作成するにあたり、いくつか知見があったので共有します。

HTMLをすべて自由記述できるわけではない

はてなブログでは、既存のHTMLの編集はほぼできないようになっています。例えば個人で一からHTMLのテンプレートを作成し、それをアップロードする・・・といった機能はついていません。

これは、既存のHTMLテンプレートを個々にいじられてしまうと、はてなブログとして運用していくのに必要なタグや要素が削除されてしまい、思わぬレイアウトの崩れ等の問題が発生してしまうためかと思われます。

そのため新しくブログデザインを作成する場合には、すでにあるブログテンプレートに対してスタイリングを行うCSSをメインに作っていくことになります。

作成したCSSは、ブログ管理画面のサイドメニュー「デザイン」->「カスタマイズ(レンチのアイコン)」タブ->「デザインCSS」のテキストボックスに貼り付ければ適用できます。

f:id:rennnosukesann:20190113184214p:plain

f:id:rennnosukesann:20190113184233p:plain

とはいえHTMLを全く編集することができないわけではなく、一部ヘッダーなどのコンポーネントの直下などに追加のHTMLを記述することはできます。

HTMLを追加記述可能な箇所は、

  • タイトル下(ヘッダ)
  • 記事上(記事)
  • 記事下(記事)
  • フッタ

の4箇所になります。

編集箇所は先ほどと同じブログ管理画面の「デザイン」メニューになります。例えばヘッダ直下にHTMLを新しく追加したい場合には、下記のように「HTMLを記述できます」と書かれたテキストボックスにHTMLを記述します。

f:id:rennnosukesann:20190113185625p:plain

はてな公式がボイラープレートを用意してくれている

はてなブログのデザインテーマを作成するにはCSSを貼り付ければ良い、ということはわかりました。が、CSSをちまちまと書いていきながら、編集したCSSを管理画面に貼り付けて、逐一チェックする・・・というのはちょっと骨が折れます。

そこで、はてなブログ公式が用意してくれているボイラープレートを拝借します。

github.com

このボイラープレートではSCSSによるスタイルシート作成環境を提供してくれています。ボイラープレートをクローン後、 npm で必要なパッケージをインストールしてサーバを立ち上げるだけで、SCSS編集時に自動でCSSを生成してくれます。立ち上げたローカルサーバへのパスをデザインCSSとしてブログデザインに適用すれば、SCSSをビルドするたびにプレビュー画面に最新の編集状況を表示できます。

詳細ははてなブログ公式ヘルプブログで紹介されています。ボイラープレートの導入手順を紹介している他、編集の手引きまでしてくれています。

help.hatenablog.com

レスポンシブデザインも対応可能(今はしてないけど。。。)

はてなブログのデザインテーマにはレスポンシブデザインの有無が設定できます。

ブログ管理画面のサイドメニュー「デザイン」->「スマートフォンスマホのアイコン)」タブ->「詳細設定」の項目で「レスポンシブデザイン」のチェックを入れます。

f:id:rennnosukesann:20190113191931p:plain

次に、ブログ管理画面のサイドメニュー「デザイン」->「カスタマイズ(レンチのアイコン)」タブ->「デザインCSS」の先頭に下記コメントを追加します。

/* Responsive: yes */

これで、現在のブログがスマホ上のブラウザであってもPCブラウザと同じスタイルで表示されるようになります。

レスポンシブデザインを考慮していないサイトの場合、PCブラウザと同じ表示の仕方でスマホブラウザ上にブラウジングされてしまうため、レスポンシブ対応していないデザインの場合はOFFにすることをおすすめします(僕も現在はそうしています)。


僕がブログデザイン作成で得た治験はざっとこんな感じです。

ブログデザイン作るのは楽しいですね。

まだレスポンシブデザイン未対応なので、今度はレスポンシブ対応も頑張ってみようかと思います。

【Docker】ローカル環境上のイメージ/コンテナを全て削除する

f:id:rennnosukesann:20190108224132p:plain

ローカルにあるDockerイメージ/コンテナを全て削除するコマンド。メモ。

# Dockerイメージの全削除
$ docker rmi $(docker images -aq)

# Dockerコンテナの全削除
$ docker rm $(docker ps -aq)

DockerHubにあるイメージをいろいろ試していると、どうしてもローカルのイメージやコンテナがいっぱいいっぱいになってしまいがち。 特にコンテナは一旦きれいにしたい場合が多いので、重宝します。