Blog

[spring] @Configuration と @Component は違う、あるいは @Bean lite mode について

Spring の @Configuration は @Component の stereotype だと思っていたが、挙動が違うとのこと。

https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/context/annotation/Bean.html

@Bean Lite Mode というものがあって、@Component を利用すると CGLIB proxy を利用した aspect 系の処理が動かないらしい。

https://github.com/spring-projects/spring-framework/issues/14061

歴史を振り返ると、spring 3 の頃にバグ報告されたから lite mode ということにしたようにもみえる(深追いしてない)。

(全く知らなくてまつださんに教えてもらった)


【20191123 追記】

Spring framework 5.2 以後では、@Configuration(proxyBeanMethods=false) と書けるようになっているから、これを利用するのが良さそう(意図が明確なので)。

`ClassLoader#getResources("")` の返り値が Java9 以後は jar:file: も含むようになっている

package com.example;

public class Example {
    public static void main(String[] args) throws java.io.IOException {
        System.err.println("JVM: " + java.lang.management.ManagementFactory.getRuntimeMXBean().getVmVersion());

        ClassLoader classLoader = Example.class.getClassLoader();
        System.out.println(classLoader);

        java.util.Enumeration<java.net.URL> resources = classLoader.getResources("");
        while (resources.hasMoreElements()) {
            System.out.println("-- " + resources.nextElement());
        }
    }
}

このようなプログラムの実行結果が、Java 9 以後では異なる。

Run with Java 8

JVM: 25.201-b09
sun.misc.Launcher$AppClassLoader@2a139a55
-- file:/Users/tokuhirom/work/urlclassloader-behavoiour/build/classes/java/main/

https://docs.oracle.com/javase/8/docs/api/java/lang/ClassLoader.html#getResources-java.lang.String-

Run with Java 11

JVM: 11.0.1+13-LTS
jdk.internal.loader.ClassLoaders$AppClassLoader@799f7e29
-- file:/Users/tokuhirom/work/urlclassloader-behavoiour/build/classes/java/main/
-- jar:file:/Users/tokuhirom/.gradle/caches/modules-2/files-2.1/net.bytebuddy/byte-buddy-agent/1.10.2/fbfe9bf099287c35b8336ea9da194f301a112a11/byte-buddy-agent-1.10.2.jar!/META-INF/versions/9/

https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/ClassLoader.html#getResources(java.lang.String)

こんな感じ。

Jackson 2.10 についてのメモ

ざっくりいうと


Jackson 2.10 features - @cowtowncoder - Medium を参考のこと。

Jackson 2.10 は、Jackson 3 に向けての Migration 用のリリースになっている。2.9までのインターフェースも @Deprecated 状態で残しつつ、3以後のあたらしいインターフェースも実装されている。今のうちに新しいインターフェースに移行しておくと後々楽になる、と思う。 (Spring Boot 2.2 は Jackson 2.10 に依存している)

2.10 では以下の3つのメジャーなイシューを解決している。

  1. デシリアライズ時におけるセキュリティイシューの根本解決
  2. 3.0 における新しいインターフェースへの移行促進
  3. module-info.class 関連

で、1 はまあいいとして、2 が重要だなと思っている。新しいインターフェースがどういうものかというと以下のような感じ。

の2点が新しいインターフェースの特徴。mutable な deserializer とか、最近は流行らないからね。

Jackson 3.0 以後では ObjectMapper は configuration 系のメソッドは持たない。

Spring Framework 5.2 からは WebClient の retrieve でちゃんと http status を取れる

Spring Framework 5.2 がリリースされた。

Support for Kotlin Coroutines.

も大きいのだが、、個人的には以下に注目したい。

Refinements to WebClient API to make the retrieve() method useful for most common cases, specifically adding the ability to retrieve status and headers and addition to the body. The exchange() method is only for genuinely advanced cases, and when using it, applications can now rely on ClientResponse#createException to simplify selective handling of exceptions.

これまで、Webclient を利用した場合、.retreive().bodyToMono(String.class) などとして response body のみを取得するメソッドしかなく、異常に使いづらかった。 HTTP Status Code が 2xx 以外の場合には例外が上がる設計になっているのはいいのだが、現実的にはどの HTTP Status Code かは例外ではなく通常の処理としてハンドリングしたいというケースも多いのである。

Spring Framework 5.2 以後では以下のように記述可能になった。

        WebClient client = WebClient.create();
        Mono<ResponseEntity<String>> responseEntityMono = client.get()
                                                                .uri(url)
                                                                .retrieve()
                                                                .toEntity(String.class);

        ResponseEntity<String> responseEntity = responseEntityMono.block();
        assert responseEntity != null;
        log.info("url={} status={} headers={} body={}",
                 url,
                 responseEntity.getStatusCodeValue(),
                 responseEntity.getHeaders(),
                 responseEntity.getBody());

便利。

sparksql で emoji/pictogram を含む行を検出する

select text from table where dt='20190911' and text rlike '[\\uD800-\\uDFFF]'

とかでとりあえず良さそう。

scala で guava のバージョンを確認する

とりあえずこんな感じで。try-with-resources でやりたかったが、あんま短くかけなそうだった。

%spark
import java.util.Properties
import com.google.common.io.Resources

val resourceName = "META-INF/maven/com.google.guava/guava/pom.properties";
val properties = new Properties();
val inputStream = Resources.getResource(resourceName).openStream();
properties.load(inputStream);
println(properties.getProperty("version"))
inputStream.close()

spring の 4.3.15, 5.0.5 以後で mockmvc の json path のマッチングで matcher 使わなくてもよくなってる

mockmvc で jsonpath のマッチングを利用する場合、以下のようにする必要があった。

.andExpect(jsonPath("$.id", is(5963)))

これはこれでいいのだが、他の API と一貫性がなく、補完も効きづらく、hamcrest API ではなく assertj をメインで使ってる身としては辛かった。

また long の扱いが渋くて、辛かった。。 https://stackoverflow.com/questions/37996419/how-to-force-andexpectjsonpath-to-return-long-long-instead-int-for-int-num

SPR-16587 MockMvcResultMatchers.jsonPath(String).value() should have a matching method to declare the expected type で、問題が解決されて以下のような書き方ができるようになっている。

.andExpect(jsonPath("$.id").value(5963)))

MockMvc でテストを書いているときに部分一致をしたいケースは殆どないと思われるので、このスタイルで書くのが良いと私は思います。

Spring 4.3.15, 5.0.5 以後で使えるので、このスタイルで書いたほうが良い。

古いコードをコピペし続けているとこのような古い書き方に引きづられがちなので気をつけていきたい。

【追記】 null であることを保証したいときは hamcrest matcher でやらないと駄目。

gRPC-WEB がもたらす我々の生活への変化

gRPC-WEB が GA となった。これが我々の生活にどのような変化をもたらすのかについて考える。

従来の gRPC の課題

gRPC は google が公開している RPC 方式であり、java, golang などの言語で利用可能になっている。 gRPC は protocol buffers over HTTP/2 を基本としているため、通信が multiplexing されるし、schema 定義がきっちりされるのでクライアント側とのコミュニケーションがしやすい。

一方、protocol buffers はbinary であるためにbinaryの取扱が苦手な Browser JavaScript からのアクセスが難しいという問題があった。

grpc-gateway

grpc-gateway という実装があって、これを利用すれば Browser JavaScript からのアクセスも可能ではある。しかし、専用の gateway server を golang で生成して運用することになって煩雑である。

また、client library を protobuf 定義ファイルから生成することができないので、そういった意味では、gRPC の魅力を 100% 引き出すことができていないといえるかもしれない。 もちろん、Swagger の定義を生成することができるので、そこから codegen することはできる。

外部に向けて JSON API を提供する必要がある場合には grpc-gateway は引き続き有力な選択肢となるが、SPA web application のためのエンドポイントの場合には、grpc-web の方が今後は良い選択となると私は考える

また、grpc-gateway はコミュニティ実装であるから、公式ではない。

gRPC-WEB

gRPC-WEB のプロトコルは PROTOCOL-WEB.md で解説されている。

多くのブラウザで動作するように、base64で encode する方法などが protocol で設定されている。

gRPC-WEB の実装

現在のところ nginx module と envoy というプロキシサーバーによる実装、grpcwebproxy という go 実装が提供されている。

nginx module

https://github.com/grpc/grpc-web/tree/master/net/grpc/gateway/nginx

nginx module も提供されている。

grpcweb

https://github.com/improbable-eng/grpc-web/tree/master/go/grpcweb

grpcweb は go のライブラリ実装。既存の golang で書かれた gRPC サーバー実装の中に組み込んで、 gRPC-web で export する機能を追加することができる。 便利。

grpcwebproxy

https://github.com/improbable-eng/grpc-web/tree/master/go/grpcwebproxy

golang で実装されている grpc-web の実装。go で single binary で導入できるので、一番導入が簡単そう。

envoy

Envoy は C++ で書かれた proxy server です。拡張が容易になっていて、C++ で簡単に拡張できるようになっています。

Envoy の grpc-web の実装は envoy の repository の中にあります。C++ ですがコメントがたくさんあるのでわかりやすいですね。

Envoy の中に実装されているのは、Proxy layer でカバーできていればすべてのサーバー実装で使えるので、まずは proxy layer で実装したということのようだ。

Envoy は C++ で書かれているのでビルドがそこそこ面倒なので docker での運用が現実的と思われる。プリビルドバイナリも提供されているが、ubuntu と alpine のみなので centos 勢としては悲しい。

今後の grpc-web の roadmap

https://github.com/grpc/grpc-web/blob/master/ROADMAP.md

このドキュメントで今後の ROADMAP が述べられている。 いくつか気になったところを紹介する。

非バイナリフォーマット

Binary の protobuf を base64 などにエンコーディングする方式は CPU overhead/memory overhead が大きい。よって、gmail などで採用されている、text protocol だが高速に処理できるフォーマットを使うようにすれば良いのではないか、とのこと。

Local Proxies

各言語用のサーバーライブラリの側に、gRPC-WEB サポートを追加しようというプラン。 今は go だけあるっぽい。Java support が待ち遠しい。

TypeScript support

これはほしい。Protobuf から生成されたクライアントに型情報が付けば、IDE 上での作業が快適になることは間違いない。

Web UI Support

gRPC-WEB を実際に試せる web console を作りたい、とのこと。

長期的な展望

whatwg streams api が各ブラウザに実装されて普及すれば、native の gRPC protocol を利用可能になるのではなるとのこと。

MySQL の X Protocol/X DevAPI 周りについて調査したのをまとめたののメモ

MySQL 8 以後では X Protocol がサポートされている(5.7 系では部分的なサポートであり、X Protocol を本格的に利用する場合には 8 を利用することが推奨されているようだ) 通信は従来の MySQL Protocol と異なり、Protocol Buffers Based となっていて、各言語のドライバの実装が簡単になっている(protocol buffers がその言語でサポートされていれば、だが) これにより今後 libmysqlclient に依存せずに各言語のドライバが実装されるようになって運用管理が簡単になるんじゃないかと私は考えています。 実際に、mysql-connector-nodejsは X Protocol のみをサポートしていて、libmysqlclient への依存がありません。

また、X Protocol/X DevAPI は async を前提に設計されているため、各言語の Connector ではその言語の特性を生かして CompletableFuture/Promise などを利用して実装されています。

X DevAPI というのものがあって、これは MySQL Shell と MySQL Connectors で実装されている API。どの言語を利用していても統一的に MySQL を扱えるプログラミング言語レベルの API になっている。

X Protocol への接続について

X Protocol は mysql 8 ではデフォルトで有効になっている模様。有効かどうかは show plugins などして mysqlx plugin が有効かどうかを確認すれば良い。 port も 3306 ではなく 33060 なので注意。

Node 実装について

Node の MySQL connector は promise based になっており使いやすい

NODE_DEBUG=protobuf という環境変数を設定すれば、protobuf の serialization のログが見れて便利。

Java 実装について

Java 実装も割と普通に実行できる。今まで通りの MySQL Connector/J の実装で実行可能。 生で使うなら X DevAPI は JDBC API の 2億倍使いやすい。

package com.example;

import com.mysql.cj.xdevapi.Session;
import com.mysql.cj.xdevapi.SessionFactory;
import com.mysql.cj.xdevapi.SqlResult;

import java.util.List;
import java.util.stream.Collectors;

public class App {
    public static void main(String[] args) {
        SessionFactory sessionFactory = new SessionFactory();
        Session session = sessionFactory.getSession("mysqlx://[email protected]:33060/test");
        runQuery(session, "SHOW PROCESSLIST");
        runQuery(session, "SELECT SLEEP(15)");
        runQuery(session, "SHOW PROCESSLIST");
        session.close();
    }

    private static void runQuery(Session session, String query) {
        System.out.println(" クエリ開始" + query);
        session.sql(query).executeAsync()
                .thenAccept(rows -> {
                    System.out.println(" クエリ完了 " + query);
                    dumpRows(rows);
                });
    }

    private static void dumpRows(SqlResult rows) {
        List<String> columnNames = rows.getColumnNames();
        System.out.println("\n\n結果結果結果結果結果結果結果結果結果結果結果");
        System.out.println("    " + columnNames.stream().collect(Collectors.joining("\t")));
        System.out.println(rows.fetchAll()
                .stream()
                .map(row -> columnNames.stream()
                        .map(row::getString)
                        .collect(Collectors.joining("\t")))
                .map(line -> "    " + line)
                .collect(Collectors.joining("\n")));
        System.out.println("\n\n終了終了終了終了終了終了終了終了終了終了終了");
    }
}

のような実装では、以下のような結果を得るだろう。

 クエリ開始SHOW PROCESSLIST
 クエリ完了 SHOW PROCESSLIST


結果結果結果結果結果結果結果結果結果結果結果
    Id    User    Host    db    Command    Time    State    Info
    4    event_scheduler    localhost    null    Daemon    342510    Waiting on empty queue    null
    25    root    172.17.0.1:36154    null    Sleep    26244        null
    102    root    172.17.0.1:39884    test    Sleep    1599    null    PLUGIN
    204    root    172.17.0.1:40090    test    Query    0    null    PLUGIN: SHOW PROCESSLIST


終了終了終了終了終了終了終了終了終了終了終了
 クエリ開始SELECT SLEEP(15)
 クエリ開始SHOW PROCESSLIST
 クエリ完了 SELECT SLEEP(15)


結果結果結果結果結果結果結果結果結果結果結果
    SLEEP(15)
    0


終了終了終了終了終了終了終了終了終了終了終了
 クエリ完了 SHOW PROCESSLIST


結果結果結果結果結果結果結果結果結果結果結果
    Id    User    Host    db    Command    Time    State    Info
    4    event_scheduler    localhost    null    Daemon    342525    Waiting on empty queue    null
    25    root    172.17.0.1:36154    null    Sleep    26259        null
    102    root    172.17.0.1:39884    test    Sleep    1614    null    PLUGIN
    204    root    172.17.0.1:40090    test    Query    0    null    PLUGIN: SHOW PROCESSLIST


終了終了終了終了終了終了終了終了終了終了終了

com.mysql.cj.protocol.x.AsyncMessageSender#writeAsync"[SEND] ===> " + message.getMessage().getClass().getSimpleName() + "\n" + message.getMessage().toString()というデバッグログを設置して、com.mysql.cj.protocol.x.ResultMessageListener#createFromMessage"[RECEIVE] <== " + message.getMessage().getClass().getName() + "\n" + message.getMessage().toString() というデバッグログを設置すると、通信の様子を垣間見ることができる。

上記のコードの場合の出力は以下のようになる。

[SEND] ===> CapabilitiesGet

[SEND] ===> CapabilitiesSet
capabilities {
  capabilities {
    name: "tls"
    value {
      type: SCALAR
      scalar {
        type: V_BOOL
        v_bool: true
      }
    }
  }
}

[SEND] ===> AuthenticateStart
mech_name: "PLAIN"
auth_data: "test\000root\000"

[SEND] ===> StmtExecute
stmt: "select @@mysqlx_max_allowed_packet"

 クエリ開始SHOW PROCESSLIST
[SEND] ===> StmtExecute
stmt: "SHOW PROCESSLIST"

 クエリ開始SELECT SLEEP(15)
[SEND] ===> StmtExecute
stmt: "SELECT SLEEP(15)"

 クエリ開始SHOW PROCESSLIST
[RECEIVE] <== com.mysql.cj.x.protobuf.MysqlxResultset$ColumnMetaData
type: SINT
name: "Id"
original_name: ""
table: ""
original_table: ""
schema: ""
catalog: "def"
length: 21
flags: 16

[SEND] ===> StmtExecute
stmt: "SHOW PROCESSLIST"

[RECEIVE] <== com.mysql.cj.x.protobuf.MysqlxResultset$ColumnMetaData
type: BYTES
name: "User"
original_name: ""
table: ""
original_table: ""
schema: ""
catalog: "def"
collation: 33
length: 96
flags: 16

[RECEIVE] <== com.mysql.cj.x.protobuf.MysqlxResultset$ColumnMetaData
type: BYTES
name: "Host"
original_name: ""
table: ""
original_table: ""
schema: ""
catalog: "def"
collation: 33
length: 192
flags: 16

[SEND] ===> Close

[RECEIVE] <== com.mysql.cj.x.protobuf.MysqlxResultset$ColumnMetaData
type: BYTES
name: "db"
original_name: ""
table: ""
original_table: ""
schema: ""
catalog: "def"
collation: 33
length: 192

[RECEIVE] <== com.mysql.cj.x.protobuf.MysqlxResultset$ColumnMetaData
type: BYTES
name: "Command"
original_name: ""
table: ""
original_table: ""
schema: ""
catalog: "def"
collation: 33
length: 48
flags: 16

[RECEIVE] <== com.mysql.cj.x.protobuf.MysqlxResultset$ColumnMetaData
type: SINT
name: "Time"
original_name: ""
table: ""
original_table: ""
schema: ""
catalog: "def"
length: 7
flags: 16

[RECEIVE] <== com.mysql.cj.x.protobuf.MysqlxResultset$ColumnMetaData
type: BYTES
name: "State"
original_name: ""
table: ""
original_table: ""
schema: ""
catalog: "def"
collation: 33
length: 90

[RECEIVE] <== com.mysql.cj.x.protobuf.MysqlxResultset$ColumnMetaData
type: BYTES
name: "Info"
original_name: ""
table: ""
original_table: ""
schema: ""
catalog: "def"
collation: 33
length: 300

[RECEIVE] <== com.mysql.cj.x.protobuf.MysqlxResultset$Row
field: "\b"
field: "event_scheduler\000"
field: "localhost\000"
field: ""
field: "Daemon\000"
field: "\216\353)"
field: "Waiting on empty queue\000"
field: ""

[RECEIVE] <== com.mysql.cj.x.protobuf.MysqlxResultset$Row
field: "2"
field: "root\000"
field: "172.17.0.1:36154\000"
field: ""
field: "Sleep\000"
field: "\272\235\003"
field: "\000"
field: ""

[RECEIVE] <== com.mysql.cj.x.protobuf.MysqlxResultset$Row
field: "\314\001"
field: "root\000"
field: "172.17.0.1:39884\000"
field: "test\000"
field: "Sleep\000"
field: "\260\034"
field: ""
field: "PLUGIN\000"

[RECEIVE] <== com.mysql.cj.x.protobuf.MysqlxResultset$Row
field: "\234\003"
field: "root\000"
field: "172.17.0.1:40094\000"
field: "test\000"
field: "Query\000"
field: "\000"
field: ""
field: "PLUGIN: SHOW PROCESSLIST\000"

[RECEIVE] <== com.mysql.cj.x.protobuf.MysqlxResultset$FetchDone

[RECEIVE] <== com.mysql.cj.x.protobuf.MysqlxNotice$Frame
type: 3
scope: LOCAL
payload: "\b\004\022\004\b\002\030\000"

[RECEIVE] <== com.mysql.cj.x.protobuf.MysqlxSql$StmtExecuteOk

 クエリ完了 SHOW PROCESSLIST


結果結果結果結果結果結果結果結果結果結果結果
    Id    User    Host    db    Command    Time    State    Info
    4    event_scheduler    localhost    null    Daemon    342727    Waiting on empty queue    null
    25    root    172.17.0.1:36154    null    Sleep    26461        null
    102    root    172.17.0.1:39884    test    Sleep    1816    null    PLUGIN
    206    root    172.17.0.1:40094    test    Query    0    null    PLUGIN: SHOW PROCESSLIST


終了終了終了終了終了終了終了終了終了終了終了
[RECEIVE] <== com.mysql.cj.x.protobuf.MysqlxResultset$ColumnMetaData
type: SINT
name: "SLEEP(15)"
original_name: ""
table: ""
original_table: ""
schema: ""
catalog: "def"
length: 21
flags: 16

[RECEIVE] <== com.mysql.cj.x.protobuf.MysqlxResultset$Row
field: "\000"

[RECEIVE] <== com.mysql.cj.x.protobuf.MysqlxResultset$FetchDone

[RECEIVE] <== com.mysql.cj.x.protobuf.MysqlxNotice$Frame
type: 3
scope: LOCAL
payload: "\b\004\022\004\b\002\030\000"

[RECEIVE] <== com.mysql.cj.x.protobuf.MysqlxSql$StmtExecuteOk

 クエリ完了 SELECT SLEEP(15)


結果結果結果結果結果結果結果結果結果結果結果
    SLEEP(15)
    0


終了終了終了終了終了終了終了終了終了終了終了
[RECEIVE] <== com.mysql.cj.x.protobuf.MysqlxResultset$ColumnMetaData
type: SINT
name: "Id"
original_name: ""
table: ""
original_table: ""
schema: ""
catalog: "def"
length: 21
flags: 16

[RECEIVE] <== com.mysql.cj.x.protobuf.MysqlxResultset$ColumnMetaData
type: BYTES
name: "User"
original_name: ""
table: ""
original_table: ""
schema: ""
catalog: "def"
collation: 33
length: 96
flags: 16

[RECEIVE] <== com.mysql.cj.x.protobuf.MysqlxResultset$ColumnMetaData
type: BYTES
name: "Host"
original_name: ""
table: ""
original_table: ""
schema: ""
catalog: "def"
collation: 33
length: 192
flags: 16

[RECEIVE] <== com.mysql.cj.x.protobuf.MysqlxResultset$ColumnMetaData
type: BYTES
name: "db"
original_name: ""
table: ""
original_table: ""
schema: ""
catalog: "def"
collation: 33
length: 192

[RECEIVE] <== com.mysql.cj.x.protobuf.MysqlxResultset$ColumnMetaData
type: BYTES
name: "Command"
original_name: ""
table: ""
original_table: ""
schema: ""
catalog: "def"
collation: 33
length: 48
flags: 16

[RECEIVE] <== com.mysql.cj.x.protobuf.MysqlxResultset$ColumnMetaData
type: SINT
name: "Time"
original_name: ""
table: ""
original_table: ""
schema: ""
catalog: "def"
length: 7
flags: 16

[RECEIVE] <== com.mysql.cj.x.protobuf.MysqlxResultset$ColumnMetaData
type: BYTES
name: "State"
original_name: ""
table: ""
original_table: ""
schema: ""
catalog: "def"
collation: 33
length: 90

[RECEIVE] <== com.mysql.cj.x.protobuf.MysqlxResultset$ColumnMetaData
type: BYTES
name: "Info"
original_name: ""
table: ""
original_table: ""
schema: ""
catalog: "def"
collation: 33
length: 300

[RECEIVE] <== com.mysql.cj.x.protobuf.MysqlxResultset$Row
field: "\b"
field: "event_scheduler\000"
field: "localhost\000"
field: ""
field: "Daemon\000"
field: "\254\353)"
field: "Waiting on empty queue\000"
field: ""

[RECEIVE] <== com.mysql.cj.x.protobuf.MysqlxResultset$Row
field: "2"
field: "root\000"
field: "172.17.0.1:36154\000"
field: ""
field: "Sleep\000"
field: "\330\235\003"
field: "\000"
field: ""

[RECEIVE] <== com.mysql.cj.x.protobuf.MysqlxResultset$Row
field: "\314\001"
field: "root\000"
field: "172.17.0.1:39884\000"
field: "test\000"
field: "Sleep\000"
field: "\316\034"
field: ""
field: "PLUGIN\000"

[RECEIVE] <== com.mysql.cj.x.protobuf.MysqlxResultset$Row
field: "\234\003"
field: "root\000"
field: "172.17.0.1:40094\000"
field: "test\000"
field: "Query\000"
field: "\000"
field: ""
field: "PLUGIN: SHOW PROCESSLIST\000"

[RECEIVE] <== com.mysql.cj.x.protobuf.MysqlxResultset$FetchDone

[RECEIVE] <== com.mysql.cj.x.protobuf.MysqlxNotice$Frame
type: 3
scope: LOCAL
payload: "\b\004\022\004\b\002\030\000"

[RECEIVE] <== com.mysql.cj.x.protobuf.MysqlxSql$StmtExecuteOk

 クエリ完了 SHOW PROCESSLIST


結果結果結果結果結果結果結果結果結果結果結果
    Id    User    Host    db    Command    Time    State    Info
    4    event_scheduler    localhost    null    Daemon    342742    Waiting on empty queue    null
    25    root    172.17.0.1:36154    null    Sleep    26476        null
    102    root    172.17.0.1:39884    test    Sleep    1831    null    PLUGIN
    206    root    172.17.0.1:40094    test    Query    0    null    PLUGIN: SHOW PROCESSLIST


終了終了終了終了終了終了終了終了終了終了終了

現在の Java connector/mysqld の実装では、select sleep(15) などのクエリが発行された場合、その後のクエリの結果が先に帰ってくることはない。これは実際問題、session が状態を持つ以上、そうならざるを得ない。このため、他の状態を持たないプロトコルのクライアントと同じ気分で使っているとハマるかも。 そして、transaction は session に紐づく が、 1session あたり 1 TCP connection 以上 という実装に現時点ではなっている(以上、というのは slave への自動送信などを x devapi 上で 1 セッションとして扱う可能性があるため)。 (通信を多重化することも可能だったと思うが、現在の実装はそうなっていない。なんでだろうか。MySQL Server の実装上の制約?)

Nginx caches DNS records forever in proxy_redirect directive

If a domain name resolves to several addresses, all of them will be used in a round-robin fashion. In addition, an address can be specified as a server group.

The parameter value can contain variables. In this case, if an address is specified as a domain name, the name is searched among the described server groups, and, if not found, is determined using a resolver. <<<

http://d.hatena.ne.jp/hirose31/20131112/1384251646

github pages で underscore を含むディレクトリが表示されないとき

具体的には amon.64p.org を github pages に移したのだが、その際にハマった。 sphinx はデフォルトで _static/ 以下に静的ファイルを生成するからだ。

https://help.github.com/articles/files-that-start-with-an-underscore-are-missing/ に解決策が載ってるよ、と tmaesaka さんに教えてもらった。

.nojekyll 置いて解決。

OWASP dependency-check を利用して脆弱性のある Java ライブラリに依存していないか確認する

依存しているライブラリに脆弱性がある場合、それを検出できると嬉しい。

OWASP dependency-check の gradle プラグインを入れると、簡単に検出が可能となる。

設定は以下のようであり、非常に容易である。

buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath('org.owasp:dependency-check-gradle:3.2.1')
    }
}
apply plugin: 'org.owasp.dependencycheck'

check.dependsOn dependencyCheckAnalyze

// https://jeremylong.github.io/DependencyCheck/dependency-check-gradle/configuration.html
dependencyCheck {
    // Threshold value to fail build
    // https://www.first.org/cvss/specification-document
    // 0.1-3.9=low 4.0-6.9=medium 7.0-8.9=High 9.0-10.0=Critical
    failBuildOnCVSS=0
}

failBuildOnCVSS の値を設定することで、CVSS Score が 7.0 以上、つまり脆弱性としての評価が High 以上のもんのの場合は FAIL する、というようなルールを設定することが可能だ。

例えば、spring boot 1.5.0 に依存している場合は以下のような出力になる。

$ ./gradlew check
:dependencyCheckAnalyze
Verifying dependencies for project demo
Checking for updates and analyzing vulnerabilities for dependencies
Generating report for project demo
Found 24 vulnerabilities in project demo


One or more dependencies were identified with known vulnerabilities:

spring-boot-starter-security-1.5.0.RELEASE.jar (org.springframework.boot:spring-boot-starter-security:1.5.0.RELEASE, cpe:/a:pivotal_software:spring_boot:1.5.0, cpe:/a:pivotal_software:spring_security:1.5.0) : CVE-2017-8046, CVE-2018-1196
spring-boot-starter-1.5.0.RELEASE.jar (cpe:/a:pivotal_software:spring_boot:1.5.0, org.springframework.boot:spring-boot-starter:1.5.0.RELEASE) : CVE-2017-8046, CVE-2018-1196
spring-aop-4.3.6.RELEASE.jar (cpe:/a:pivotal_software:spring_framework:4.3.6, org.springframework:spring-aop:4.3.6.RELEASE, cpe:/a:pivotal:spring_framework:4.3.6) : CVE-2018-1199
spring-security-config-4.2.1.RELEASE.jar (org.springframework.security:spring-security-config:4.2.1.RELEASE, cpe:/a:pivotal_software:spring_security:4.2.1) : CVE-2017-4995, CVE-2018-1199
spring-security-web-4.2.1.RELEASE.jar (org.springframework.security:spring-security-web:4.2.1.RELEASE, cpe:/a:pivotal_software:spring_security:4.2.1) : CVE-2017-4995, CVE-2018-1199
spring-boot-1.5.0.RELEASE.jar (cpe:/a:pivotal_software:spring_boot:1.5.0, org.springframework.boot:spring-boot:1.5.0.RELEASE) : CVE-2017-8046, CVE-2018-1196
spring-boot-autoconfigure-1.5.0.RELEASE.jar (cpe:/a:pivotal_software:spring_boot:1.5.0, org.springframework.boot:spring-boot-autoconfigure:1.5.0.RELEASE) : CVE-2017-8046, CVE-2018-1196
spring-boot-starter-logging-1.5.0.RELEASE.jar (cpe:/a:pivotal_software:spring_boot:1.5.0, org.springframework.boot:spring-boot-starter-logging:1.5.0.RELEASE) : CVE-2017-8046, CVE-2018-1196
spring-core-4.3.6.RELEASE.jar (cpe:/a:pivotal_software:spring_framework:4.3.6, org.springframework:spring-core:4.3.6.RELEASE, cpe:/a:pivotal:spring_framework:4.3.6) : CVE-2018-1199
spring-beans-4.3.6.RELEASE.jar (cpe:/a:pivotal_software:spring_framework:4.3.6, org.springframework:spring-beans:4.3.6.RELEASE, cpe:/a:pivotal:spring_framework:4.3.6) : CVE-2018-1199
spring-security-core-4.2.1.RELEASE.jar (org.springframework.security:spring-security-core:4.2.1.RELEASE, cpe:/a:pivotal_software:spring_security:4.2.1) : CVE-2017-4995, CVE-2018-1199
spring-context-4.3.6.RELEASE.jar (cpe:/a:pivotal_software:spring_framework:4.3.6, cpe:/a:pivotal:spring_framework:4.3.6, org.springframework:spring-context:4.3.6.RELEASE) : CVE-2018-1199
spring-expression-4.3.6.RELEASE.jar (cpe:/a:pivotal_software:spring_framework:4.3.6, cpe:/a:pivotal:spring_framework:4.3.6, org.springframework:spring-expression:4.3.6.RELEASE) : CVE-2018-1199
spring-web-4.3.6.RELEASE.jar (cpe:/a:pivotal_software:spring_framework:4.3.6, cpe:/a:pivotal:spring_framework:4.3.6, org.springframework:spring-web:4.3.6.RELEASE) : CVE-2018-1199
logback-classic-1.1.9.jar (cpe:/a:logback:logback:1.1.9, ch.qos.logback:logback-classic:1.1.9) : CVE-2017-5929
logback-core-1.1.9.jar (cpe:/a:logback:logback:1.1.9, ch.qos.logback:logback-core:1.1.9) : CVE-2017-5929


See the dependency-check report for more details.


:dependencyCheckAnalyze FAILED

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':dependencyCheckAnalyze'.
>

  Dependency-Analyze Failure:
  One or more dependencies were identified with vulnerabilities that have a CVSS score greater then '0.0': CVE-2017-5929, CVE-2017-8046, CVE-2017-4995, CVE-2018-1199, CVE-2018-1196
  See the dependency-check report for more details.



* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output.

BUILD FAILED

Total time: 6.561 secs

report は HTML で出力される(JSON, CSV 等の形式も指定可能)。 実際のレポートはこのような感じになる → https://jeremylong.github.io/DependencyCheck/general/SampleReport.html

ant/maven のタスクも用意されている。

commons-math3 の二次元行列用の型についてのメモ

BlockRealMatrix

キャッシュフレンドリーで速いらしい。

Array2DRowRealMatrix

double[][] で表現されるひじょうに素直な実装

DiagonalMatrix

対角行列用の型。対角行列を扱うならコレを使うとメモリを節約できたりする。

OpenMapRealMatrix

open addressed map ベースの実装。sparse な行列ならコレを使うとよい。

初期化コストに関するベンチマーク

雑ですが。。

    @Test
    fun bench() {
        val tries = 10000000

        val startA = System.currentTimeMillis()
        for (i in 0 until tries) {
            Array2DRowRealMatrix(11, 11)
        }
        val endA = System.currentTimeMillis()
        val startB = System.currentTimeMillis()
        for (i in 0 until tries) {
            BlockRealMatrix(11, 11)
        }
        val endB = System.currentTimeMillis()
        println("A=${endA-startA} B=${endB-startB}")
    }

で、

A=6992 B=2435

みたいな感じ。

commons-math3 の BetaDistribution を利用する場合は rng をキャッシュする

commons-math3 の BetaDistribution を利用する場合、alpha/beta が変わるたびに new BetaDistribution( alpha, beta) とかしてはいけない。 new BetaDistribution(rng, alpha, beta, 1.0) は ``new BetaDistribution(new Well19937c(), alpha, beta)` ということになるからだ。

new Well19937c() は乱数生成系だが、この初期化処理は重いので、キャッシュしないと非常に処理が重くなる。

nuxtjs で bootstrap 使いたい

nuxt.js で twitter bootstrap を有効にする方法

https://bootstrap-vue.js.org/docs/#nuxt-js を参考にやれば良い。

npm i bootstrap-vue --save

して、nuxt.config.js に以下を追加。

{
  modules: [
    'bootstrap-vue/nuxt',
  ]
}

lombok 1.16.20 以後で @lombok.Value された値を Jackson で deserialize しようとするとエラーになる

@lombok.Value された bean に対して ObjectMapper で deserialize した場合、以下のようなエラーがある。

com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `MyGreatResponse` (no Creators, like default construct, exist): cannot deserialize from Object value (no delegate- or property-based Creator) at [Source: (String)"{"FOO":"BAR"}"; line: 1, column: 2]

これは lombok 1.16.20 で以下の変更が入ったためである。

https://projectlombok.org/changelog

lombok 1.16.20 で、immutable 厨したいときは lombok.config に以下のように記述すべし。

lombok.anyConstructor.addConstructorProperties= true

Capturing grafana dashboard and post it to LINE group using LINE Notify

I want to post a screenshot of the Grafana dashboard to LINE Notify.

Grafana distribution includes PhantomJS( Alerting feature uses PhantomJS. https://github.com/grafana/grafana/blob/master/pkg/services/alerting/notifier.go#L96 ).

Then, you can take a snapshot using curl command.

You need to rewrite Grafana dashboard URL. Insert /render prefix for your dashboard URL. For example, when your dashboard URL is http://grafana.example.com/dashboard/db/my-service, PhantomJS URL is http://grafana.example.com/render/dashboard/db/my-service.

If you want to hide a header, add ?kiosk query parameter for the URL.

Grafana supports API keys and Basic Auth. With curl, you can call it like e.g. curl -u 'YOUR_EMAIL:YOUR_PASSWORD' GRAFANA_URL.

It's summarized as follows:

curl  -u 'YOUR_EMAIL:YOUR_PASSWORD' http://grafana.example.com/render/dashboard/db/my-service?kiosk -o img.png

Post to LINE Notify

You can upload dashboard image to LINE Notify using curl command.

curl -X POST https://notify-api.line.me/api/notify 
       -H 'Authorization: Bearer YOUR_PERSONAL_ACCESS_TOKEN' 
       -F 'message=test' 
       -F '[email protected]'

See https://engineering.linecorp.com/ja/blog/detail/94 for more details.

SEE ALSO

http://moznion.hatenadiary.com/entry/2016/09/03/004038

tableau server の "Query View Image" API のデフォルトキャッシュ期間は長い

http://onlinehelp.tableau.com/v10.3/api/rest_api/en-us/help.htm#REST/rest_api_ref.htm%23Query_View_Image%3FTocPath%3DAPI%2520Reference%7C_____68

If you make multiple requests for an image, subsequent calls return a cached version of the image. This means that the returned image might not include the latest changes to the view. To decrease the amount of time that an image is cached, use tabadmin to reduce the value of the vizportal.rest_api.view_image.max_age setting. For more information, see tabadmin set options in the Tableau Server help.

で、これの default value は 720min=12hours なので、とにかくキャッシュが長い。

なんか cache 破棄させるほうほうもありそうなものだが、いまいちよくわからないので、とりあえずデフォルトのキャッシュ時間を短くして運用しましょうかというよう話になった。

Perl Web application に於ける Data::UUID の利用について

本文書について

本文書では Perl における Data::UUID での UUID 生成について議論する。 特に、Linux 環境に於ける Data::UUID->new->create_str の挙動について考察する。

Data::UUID について

Data::UUID は Alexander Golomshtok 氏が開発したモジュールで、メンテナンスされなくなった結果として、現在は rjbs 氏がパッチの適用などの消極的なメンテナンスを実施している。

このモジュールは以下の文言からドキュメントが開始されている。

This module provides a framework for generating v3 UUIDs

しかし、期待に反して、実際にはほとんどのユーザーは v1 UUID を生成している。 Data::UUID->new->create_str を利用するのが一般的なケースであるためだ。

UUIDv1 の構造について

timestamp 60bit, clock sequence 16bit, node 48bit

となっている。

  UUID                   = time-low "-" time-mid "-"
                           time-high-and-version "-"
                           clock-seq-and-reserved
                           clock-seq-low "-" node

↓つまり以下のような形式となる。

   TIMESTAM-PTIM-ESTA-CLOC-NODEID

/tmp/.UUID_NODEID について

Data::UUID->new で nodeid は生成されます。

/tmp/.UUID_NODEID が存在しない場合、nodeid は md5(gettimeofday(), gethostname()) で生成され、 /tmp/.UUID_NODEID に保存されます。 /tmp/.UUID_NODEID が存在する場合、slurp(/tmp/.UUID_NODEID) + $$ の結果が nodeid となります。

このロジックにより、二回目以後の nodeid 生成は process ごとに独立した nodeid になります。

/tmp/.UUID_STATE について

/tmp/.UUID_STATE に clockseq などが保存されます。これにより、プロセスが再起動した場合などにも動作を続けることができる、という設計に見えます。

このファイルの保存タイミングは以下。

しかし、現実的には、あまり意味ないかも。

Data::UUID を web application で利用した場合に発生する可能性がある問題について

以下では pre-fork モデルのウェブアプリケーションで Data::UUID を利用した場合に発生し得る問題について議論する。 (Starlet, Starman などの web server が pre-forking モデルです)

時刻の巻戻り

UUID v1 の特性上、NTP などにより時刻が巻き戻った場合、重複した uuid が発行される可能性があります。 時刻の巻き戻りが発生した場合に発生する重複が許容出来ない場合、Data::UUID の利用は避けるべきでしょう。

clock sequence を初期化するタイミングで初期値を random にするなどの軽減策は取られているものの、問題が発生する可能性はそこそこあります。

local 攻撃への脆弱性

の2つのファイルが生成されるが、tmp directory に生成されるために、悪意のあるユーザーが設置することが可能。

nodeid の生成ロジックの問題

/tmp/.UUID_NODEID がある場合にだけ pid が nodeid に考慮される。 このため、Data::UUID を一度も利用したことがない新しいサーバーの初回起動時などに nodeid が同一の Data::UUID インスタンスを持つ process が複数発生する可能性がある。

AWS などで頻繁にインスタンスの作り直しを実施している場合、この問題が発生しやすいと考えられる。

Data::UUID->new() を fork 前に呼んではいけない

Data::UUID->new の中で nodeid を初期化しているので、fork 前に呼ぶと同一 nodeid ですべての子プロセスで利用されることになり、相手は死ぬ。

srand の呼び出し

Data::UUID->new の中で、現在時刻をシードに srand() を呼び出し、それ以後は rand() をコールしている。。 現在時刻は srand のソースとして使うにはよろしくないので、これは、他の rand() を利用している XS module に悪影響を与えている可能性がある。 (Perl 本体は drand48() を利用しているので問題ない)

対案

完全に random な UUID 生成方法である UUID v4 を利用しとくか、sfujiwara さんが作ってたみたいなやつ使うのがいいんじゃないすかね。 ただし、UUID v4 の生成系を利用する場合、fork 時の seed 最初期化などについては考慮を忘れずに。

spring-boot で tomcat の設定するときのやり方

server:
  use-forwarded-headers: true
  tomcat:
    accesslog:
      directory: /PATH/TO/logs/tomcat/
      pattern: common
      enabled: true
      rotate: true
      # It's required when using remote-ip-header.
      request-attributes-enabled: true
    remote-ip-header: x-forwarded-for
    protocol-header: x-forwarded-proto

なんかこんな感じ? request-attributes-enabled を設定するの忘れると RemoteIpValve で設定した値が AccessLogValve で拾われないので死ぬ。