2017年 まとめ

技術編

  • http client に詳しくなった
  • mockito をまともに使い始めた
  • spring framework をまともに使い始めた
  • spring boot をまともに使い始めた
  • gradle をまともに使い始めた
  • ansible をまともに使い始めた
  • Raspberry Pi を手に入れた

ゲーム編

漫画編

4 桁行かないぐらいだけど書ききれないので割愛

コーヒー編

  • コーヒーに凝り始めた
  • 機器
    • twinbird の サイフォンコーヒーメーカーを買った
    • マキネッタ(モカエキスプレス)を買った(壊れた)
    • デロンギエスプレッソマシンを買った
    • 有田焼セラミックフィルターを買った
    • HARIO のウォーターブリューコーヒーメーカーを買った
  • SCAJ 2017 に行った
  • コーヒー豆を買った(合計推定 10 kg )

その他

  • ネパールに行った
  • 国内の旅行に何回か行った
  • 壊れたエアコンで暑さに耐えきれずに引っ越しをした

wp-comments-post.php への投稿が 405 Method Not Allowed になる問題

確認環境

問題

wordpress の記事のコメント投稿が 405 Method Not Allowed になる

確認される現象

記事詳細ページのコメント欄へコメントできない

原因

条件1 : nginx の設定

nginx の location ディレクティブで .php を指定して fastcgi に引き渡している。

https://codex.wordpress.org/Nginx#General_WordPress_rules

upstream php {
        server unix:/tmp/php-cgi.socket;
        server 127.0.0.1:9000;
}
...
server {
        ...
        location ~ \.php$ {
                ...
                fastcgi_pass php;
                ...
        }

条件2: iThemes Security の「コメントスパム」設定が有効

iThemes Security で該当項目を有効にしていると 生成される nginx.conf で以下のような 設定が出力される。

        # コメントスパムを削減 - セキュリティ > 設定 > WordPress の微調整 > コメントスパム
        location = /wp-comments-post.php {
                limit_except POST { deny all; }
                if ($http_user_agent ~ "^$") { return 403; }
                valid_referers server_names jetpack.wordpress.com/jetpack-comment/;
                if ($invalid_referer) { return 403; }
}

上記より

nginx の location ディレクティブは最長一致の設定が用いられる[1]。 そのため条件1で設定した .php の location よりも 条件2の /wp-comments-post.php が優先されるはず。そして 条件2の設定では fastcgi_pass がないためそのまま静的ファイルとして処理される。 nginx は静的ファイルの場合は POST は許可されないため 405 になる[2]。

解決方法

このままだとコメントスパム対応部分が弱いとも考えられるので コメントスパム部分は別のプラグインに任せるか、上記設定を参考にして自分で fastcgi_pass をする設定を追加するなどすればよい気もする。

(余談) iThemes Security が出力する設定を変更することはできないため、対象 location ディレクティブに fastcgi_pass を設定できない。 そして自分は location ディレクティブに fastcgi_pass を記載することなく設定する方法がわからない、というのと wordpress を運用しようとしている人の nginx 力はそんなに高くないと思っているため 技巧を凝らした nginx 設定するのはあんまりよくない気もする。ので「コメントスパムを off にする」ぐらいしか思いつかない。

参考 URL

Spring Boot で Gzip を使ってレスポンスを返す

概要

nginx などの web server では静的ファイルなどを gzip で返すとパフォーマンスがよくなるよーという記事は良くみる。 これを Spring Boot で行うにはどうすればいいか。(アプリケーションから gzip で返す必要性が発生するのかというのは別の話)

設定

https://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html

server.compression.enabled=false # If response compression is enabled.
server.compression.excluded-user-agents= # List of user-agents to exclude from compression.
server.compression.mime-types=text/html,text/xml,text/plain,text/css,text/javascript,application/javascript # Comma-separated list of MIME types that should be compressed.
server.compression.min-response-size=2048 # Minimum response size that is required for compression to be performed.

server.compression.enabled を true にする。とりあえず有効にするのはここまで。

細かい確認

Spring Boot 1.5 と 2.0 ともに下記バージョンのよう。

  • jetty 9.4.7.v20170914
  • tomcat 8.5.23
  • undertow 1.4.21.Final

それぞれどう違うのかというのを確認しようとして関係の図を書いた。 Spring Boot 1.5 と 2.0 では少し実装が違ったけど 1.5 まとめて疲れたのでここまで。

要するにどれも 標準の java.util.zip.Deflater を使っている。 Deflater は中で(native で) zlib 呼んでるらしい。

f:id:nise_nabe:20171222004016p:plain

bsd 向けに ansible でユーザ作成

環境

  • DragonFlyBSD v5.0.0

ansible でユーザ作成をする方法

一般的な方法

BSD 的な方法

コマンドの useradd , groupadd 的なものがないと動かないので動かない。

代わりに pw を使う方法を検討。user, group ともに pw でよいらしい。普通に使うと二度目の実行時などすでに存在している場合にエラーになるので存在チェックのタスクを追加する。

  1. pw show によって存在してるかどうかを確認
  2. 存在する場合は 正常終了、しない場合は異常終了。
  3. そのままだと必ず changed になり結果のノイズになるので changed にならないように
  4. pw useradd または pw groupadd で追加

下記は prometheus のユーザを作成したときの例です。

使用モジュール

- name: check prometheus system group via pw
  command: pw show group  "{{ prometheus_group }}"
  register: prometheus_group_check
  changed_when: False

- name: check prometheus system user via pw
  command: pw show user  "{{ prometheus_user }}"
  register: prometheus_user_check
  changed_when: False

- name: create prometheus system group via pw
  command: pw groupadd -n "{{ prometheus_group }}"
  when: prometheus_group_check.rc != 0

- name: create prometheus system user via pw
  command: pw useradd -n "{{ prometheus_user }}" -g "{{ prometheus_group }}" -s /sbin/nlogin
  when: prometheus_user_check.rc != 0

DragonflyBSD 5.0 + letsencrypt インストールログ

以前書いた記事 の ライブラリが消えていたので書き直し

環境

DragonflyBSD 5.0

ログ

とりあえず standalone で取る場合。

$ sudo pkg install py27-certbot
$ sudo certbot certonly --standalone -d ドメイン名

すると以下のような場所にいろいろできる

$ sudo ls /usr/local/etc/letsencrypt/live/ドメイン名/
README      cert.pem    chain.pem   fullchain.pem   privkey.pem

更新については自動でやってくれないようなので自分で cron に設定するなりして頑張る

ConditionalOnClass を使うライブラリを書いたときにハマったことメモ

概要

Spring Boot の機能として auto configuration を作る Spring Boot ライブラリを作成する場合に、特定のライブラリが読まれているときのみ動く設定などを使いたい場合がある。そういう場合に ConditionalOnClass アノテーションを使うだろうと思う。(そもそも AutoConfiguration を作りたいと思うモチベーションについては割愛)

AutoConfiguration やその作り方については下記記事を参照

以下、自分が ConditionalOnClass アノテーションを利用したときにハマったこと。

実際にハマったこと

ConditonalOnClass はコードを見ると TYPE と METHOD につけることが可能。

https://github.com/spring-projects/spring-boot/blob/v1.5.6.RELEASE/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionalOnClass.java#L32

基本的には ConditionalOnClass アノテーションは Confuguration アノテーションが書かれているクラスやその中で利用する想定とする。そして実際に ConditionalOnClass がついている場合全体を通した場合下記のように挙動が変わる。

  • Configuration Class についていてクラスパスに指定クラスが存在しない場合、そのクラスを Bean として生成しない。
  • メソッドについていてクラスパスに指定クラスが存在しない場合、そのメソッドを実行しない。

差が分からないように見えるが、前者の場合は "生成" されるためそのクラス内で利用している import 対象のクラスがクラスパスに存在しないと生成に失敗する。

これは通常開発時はビルド時にエラーとして失敗するものだが、今回の場合はクラスパスに存在するかしないかで制御するライブラリ側のコードなので compileOnly とするため、このライブラリ利用側のアプリではクラスパスに存在しない場合がある。

利用側アプリではそのライブラリを追加して及び使用したクラスをクラスパスに追加しないときにランタイムで Configuration クラスの生成に失敗するのでアプリのビルドは成功するが起動することができない。

例えば下記のようにそのクラスが存在しなければその Bean 生成はしないというような意図で書いたコードである。(AutoConfiguration 用の spring.factories などの説明は割愛)

@Configuraion
public class MyConfiguration {
   @Bean
   @ConditionalOnClass(MyClassService.class)
   public MyClassService myClassService() {
      return new MyClassService();
   }
}

この場合、 MyConfiguration クラスは普通にインスタンス化しようとするためこのライブラリの利用者は実行時にエラーになる。

解決案

ある指定クラスが存在する場合のみ Bean としたい場合はクラス全体につけるのが良い。当然そのクラスにあるメソッドは指定クラスがないと使われないのでメソッド側に ConditionalOnClass アノテーションを付ける必要はない。

@Configuraion
@ConditionalOnClass(MyClassService.class)
public class MyConfiguration {
   @Bean
   public MyClassService myClassService() {
      return new MyClassService();
   }
}

実際のコードの例としては DataSource あたりが参考になるのではないかと思う。このコードでは各ライブラリ依存部分を中で更に別クラスを書いて ConditionalOnClass を使っている。

https://github.com/spring-projects/spring-boot/blob/35d062f50df674a9564834d14df603db536cc5f5/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceConfiguration.java

spring.properties 相当のファイルを増やす

概要

共通モジュールとそれを利用するモジュールを、同一 gradle プロジェクト内で開発したいときなど 共通モジュール用と利用側モジュールでそれぞれ設定ファイルを書きたい場合がある。

Spring Boot の設定について

Spring Bootでは 設定(プロパティ)を key=value で対応付されている。設定方法はいろいろあり、一覧としては下記のようになる。設定ファイルはこのうち 12, 13, 14 あたりが該当する。

f:id:nise_nabe:20170917161424p:plain

24. Externalized Configuration

具体的な実装を説明すると、設定のプロパティは Java コード上ではおもに org.springframework.core.env.PropertyResolver の実装クラス、つまり ConfigurablaeEnvironment クラスで表現されている。ConfigurableEnvironment 実装クラスは複数の PropertySource を登録することができるようになっている。application.properties は 何らかの方法によって Properties ファイルに変換され、 PropertiesPropertySource としてここに登録され、利用時にキーに対応したものを取得する処理を行っている。

spring.properties 相当のファイルを増やす方法

とりあえず自分の調べた限りだと下記3つぐらいの方法がありそう。

  • profile として共通モジュール用または利用モジュール用を定義する
    • 利点:楽
    • 欠点:さらに別の profile が設定に影響する場合が面倒
  • ApplicationContextInitializer を使って追加した設定ファイルをロードさせる
    • 利点:細かく制御できる
    • 欠点:実装を頑張る必要がある。 IDE サポートがなさそう。
  • spring.config.name をカンマ区切りで使う
    • 利点:標準の機能。良さそう。
    • 欠点:(自分の調べた範囲だと)ドキュメントになさそうで IDE サポートがいまのところない
profile として共通モジュール用または利用モジュール用を定義する

一応 profile として追加することで簡易的に対応する方法もある。ロード順でいうと 12, 13 に相当する部分を利用する。 ただしこの方法の場合、 application.properties のようにそれそのもの + 別の profile と組み合わせるような設定はできない。

ApplicationContextInitializer を使って追加した設定ファイルをロードさせる

標準の application.application(yml) については ConfigFileApplicationListener という ApplicatoinListener のクラスがあり、ApplicationEnvironmentPreparedEvent が発生したときに呼ばれるクラスでロードしている。

(実はクラスは EnvironmentPostProcessor を動かすクラスでもあるため設定後に EnvironmentPostProcess として Factories に 登録しておくことによって設定を書き換えるということができる。)

コードを見ると SpringApplication では Environment 初期化 → ApplicationContext 初期化という順番で処理をしており、Environment 初期化時または、初期化後の何らかのタイミングで Environment の設定に上書きするという処理を入れるとうまくいきそう。コード上をみると以下の方法がありそうということがわかる。

タイミング1: SpringApplicationRunListener#environmentPrepared() で拾う。

タイミング2: EnviromnebtPostProcessor#postProcessEnvironment() で拾う。

タイミング3: ApplicationContextInitializer を使う

タイミング4: SpringApplicationRunListener#contextPrepared() で拾う。

Spring Boot で書かれている github 上のドキュメントには下記のように書かれている。

spring-boot/howto.adoc at v1.5.6.RELEASE · spring-projects/spring-boot · GitHub

  • Programmatically per application by calling the addListeners and addInitializers methods on SpringApplicationbefore you run it.
  • Declaratively per application by setting context.initializer.classes or context.listener.classes.
  • Declaratively for all applications by adding a META-INF/spring.factories and packaging a jar file that the applications all use as a library.

ドキュメントでは タイミング3 について書かれているように見える。 ここで設定ファイルをロードする処理を自分で書くとよさそう。

(Initializer を使ったコード例は時間がないので一旦省略)

spring.config.name をカンマ区切りで使う

spring.config.name はデフォルトの application という名前のファイル名を変更する際に利用する、ようにみえる。しかし実装を見てみると spring.config.name を カンマで区切って指定すると複数の設定ファイルを読むようになる。後に書かれたほうが強い。これをカンマ区切りで書くとよさそう。

https://github.com/spring-projects/spring-boot/blob/v1.5.6.RELEASE/spring-boot/src/main/java/org/springframework/boot/context/config/ConfigFileApplicationListener.java#L639...L653

ただし spring.config.name は設定ファイルを読み込む前に設定しないと意味がない。

24. Externalized Configuration

spring.config.name and spring.config.location are used very early to determine which files have to be loaded so they have to be defined as an environment property (typically OS env, system property or command line argument).

設定コード例

SpringApplication を使う場合。

    public static void main(String[] args) {
        SpringApplication application = new SpringApplication(MyApplication.class);
        Map<String, Object> defaultProperties = new HashMap<>();
        defaultProperties.put("spring.config.name", "application,myapplication");
        application.setDefaultProperties(defaultProperties);
        application.run(args);
    }

SpringApplicationBuilder を使う場合。

    public static void main(String[] args) {
        new SpringApplicationBuilder(MyApplication.class)
                .properties("spring.config.name=application,myapplication")
                .run(args);

    }

ちなみに SpringApplication#defaultProperties() や SpringApplicationBuilder#properties() などで登録した値は MapPropertySource として ConfigurableEnvironment に登録される。優先順位としては一番最後となる。

IDE での spring.config.name の設定(Intellij IDEA)

結論からいうと現状では変更はできるが複数設定はできない様子。

spring.config.name は下記のブログにあるように Intellij IDEA 側の設定を変更することで、追加した設定ファイル上でも spring.properties の補完が動くなどのサポートが得られる。

IntelliJ IDEA 2017.2: Spring Boot Improvements | IntelliJ IDEA Blog

File > Project Structure の中の Facets 以下に下記のように "Customize Spring Boot" とある部分をクリック

f:id:nise_nabe:20170917153502p:plain

すると下記のように spring.config.name を設定するところが出る。ここを編集すると、一応既存の 設定ファイル名を「書き換える」ことはできる。

f:id:nise_nabe:20170917153726p:plain

ただし、現状ではここは spring.config.name のように , 区切りで入力しても反映されない。Spring Boot のドキュメントにも記載されてないのでそれはそうかなという気はしなくもないが、まあ対応してくれると便利ですね。

とりあえずは当初の目的通り モジュールそれぞれで applicatoin.yml と myapplication.yml を持つことを想定し、モジュールごとに上記設定をすると一応は解決となる。

以上。