tokuhirom's blog

Spring framework 5.2 以後=Spring Boot 2.2 以後では WebClient のレスポンスの取り扱いが便利になっている件

https://twitter.com/making/status/1192216994147270656?s=20

WebClient からの response status などを取得しやすくなっている。 便利。

    val logger = LoggerFactory.getLogger(WebfluxSampleApplication::class.java)

    val url = "http://example.com/"

    val webClient = WebClient.builder()
            .build()
    val entityMono = webClient.get().uri("http://example.com/")
            .retrieve()
            .toEntity(String::class.java)

    logger.info("Sent http request. url=$url")

    val entity = entityMono.block()!!

    if (entity.statusCode.is2xxSuccessful) {
        val body = entity.body
        logger.info("HTTP request succeeded. url=$url status=${entity.statusCodeValue} length=${body?.length}")

        // process response data...
        println(body)
    } else {
        logger.info("HTTP request failed. url=$url status=${entity.statusCodeValue} body=${entity.body}")
    }
Created: 2019-11-07 08:58:18 +0900
Updated: 2019-11-07 08:58:18 +0900

assertj 3.12.0 の breaking change がエグい

Iterator<Foo> fooIterable = getFooIterable();
assertThat(fooIterable)
    .extracting(Foo::getId)
    .isEqualTo(List.of(1,2))

みたいな書き方だったのが

Iterator<Foo> fooIterable = getFooIterable();
assertThat(fooIterable)
    .toIterable()
    .extracting(Foo::getId)
    .isEqualTo(List.of(1,2))

と書かなくてはいけなくなった。

該当のコミットはこれ。

https://github.com/joel-costigliola/assertj-core/commit/acafa142aa903afd611de812576e6e80e12e7964#diff-71630712302645dfc9a55762045740e6

もとのメソッドを Deprecated にして一旦置いといても良かったように思うのだが。。

Created: 2019-10-31 00:32:13 +0900
Updated: 2019-10-31 00:32:13 +0900

`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)

こんな感じ。

Created: 2019-10-29 14:58:57 +0900
Updated: 2019-10-29 14:58:57 +0900

【解決済み】 Boot 2.2 で AbstractRoutingDataSource しかない場合にアプリケーションが起動しない問題について

https://github.com/spring-projects/spring-boot/issues/18661

Boot 2.2 で scan 対象に AbstractRoutingDataSource しかない場合に、アプリケーションが起動しないという問題を見つけたので報告したところ、シュッと対応してくれた。 次回のリリースで治る見込み。

https://github.com/spring-projects/spring-boot/commit/c5138c56ff4c4fa7ec6c4ff2823e2b86bc7e0ef1

待てない人は、management.health.db.enabled=false を設定すれば良いとのこと。

ただ、本来は AbstractRoutingDataSource の先の DataSource がある場合はそれを DI 対象にしておくのが良いです。

Created: 2019-10-24 08:09:00 +0900
Updated: 2019-10-24 08:09:00 +0900

Jackson 2.10 についてのメモ

ざっくりいうと * Jackson 2.10 は Jackson 3 のインターフェースを含みつつ 2 系の旧インターフェースを新インターフェースを @Deprecated 扱いで含んだリリース * Spring Boot 2.2 は Jackson 2.10 に依存している * Boot 2.2 にした段階で、Deprecation Warnings でまくるからシュッと Jackson 3 にしたほうが良さそう。


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 が重要だなと思っている。新しいインターフェースがどういうものかというと以下のような感じ。

  • Builder Pattern ベースになっている
  • 特定のフォーマット固有のオプションとそうじゃないものの分離

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

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

Created: 2019-10-19 18:00:42 +0900
Updated: 2019-10-19 18:00:42 +0900

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());

便利。

Created: 2019-10-01 11:05:24 +0900
Updated: 2019-10-01 11:05:24 +0900

Python 2 で GMT で出てるやつを適当に JST にする

from datetime import datetime, timedelta

def to_jst(src):
    return str(datetime.strptime(src, '%d %b %Y %H:%M:%S %Z') + timedelta(hours=9)) + " JST"

print(to_jst('26 Sep 2019 03:56:25 GMT'))

依存とかなしでやる。

Created: 2019-09-26 14:58:21 +0900
Updated: 2019-09-26 14:58:21 +0900

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

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

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

Created: 2019-09-13 09:07:07 +0900
Updated: 2019-09-13 09:07:07 +0900

filebeat で jvm unified log を収集する話

  • jvm unified log を収集すると gc pause time を正確に収集可能
  • gc の状態を正確に把握可能 → tuning に役立てられる可能性
  • filebeat で収集したものを elasticsearch に入れて ingest node で grok して visualize すれば良さそう
  • 散布図で visualize するといいが、prometheus では散布図を出すことができない。
Created: 2019-09-12 07:52:53 +0900
Updated: 2019-09-12 07:52:53 +0900

jetbrains toolbox for linux を chromebook で動かす

fuse が動かないので ./jetbrains-toolbox-1.15.5796/jetbrains-toolbox --appimage-extract で解凍。 APPDIR=$PWD/squashfs-root ./squashfs-root/AppRun で実行可能。

と、思ったが、sudo apt fuse すれば普通に実行できた。

Created: 2019-09-10 06:22:12 +0900
Updated: 2019-09-10 06:22:12 +0900

Unable to publish SessionDestroyedEvent for session foobar

https://github.com/spring-projects/spring-session/blob/033d6eecaeabe1adaa4deedc9a3019cf9260fcbc/spring-session-data-redis/src/main/java/org/springframework/session/data/redis/RedisOperationsSessionRepository.java#L536

  • RedisOperationsSessionRepository は MessageListener を実装している
  • SessionDeletedEvent or SessionExpiredEvent 発生時にこのログは出る
  • session の削除または expire イベントが発生したが、すでに Redis の TTL で消えている場合にこのメッセージが発生する
  • 消えているのだから特に何もできることはなく正常動作なので、本来このログは WARN ではなく INFO であるべき?
Created: 2019-09-10 06:12:07 +0900
Updated: 2019-09-10 06:12:07 +0900

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()
Created: 2019-09-03 09:08:02 +0900
Updated: 2019-09-03 09:08:02 +0900

witcher 3 途中でやめた

そもそも剣で切り合うアクションあんま好きじゃない気がしてきた

Created: 2019-07-29 13:28:06 +0900
Updated: 2019-07-29 13:28:06 +0900

pageres から capture-website にしようと思ったが puppeteer にした

長年、pageres を利用して web page から画像を生成していた。 pageres の後継として capture-website があることを知った。

https://github.com/sindresorhus/capture-website

The biggest difference is that Pageres supports capturing multiple screenshots in a single call and it automatically generates the filenames and writes the files. Also, when projects are popular and mature, like Pageres, it becomes harder to make drastic changes. There are many things I would change in Pageres today, but I don't want to risk making lots of breaking changes for such a large userbase before I know whether it will work out or not. So this package is a rethink of how I would have made Pageres had I started it today. I plan to bring some things back to Pageres over time.

しかし、いろいろ試した結果、puppeteer を直接使っても大した差がないことがわかったので、使うのをやめて直接使うことにした。

https://github.com/GoogleChrome/puppeteer

Created: 2019-07-09 08:47:11 +0900
Updated: 2019-07-09 08:47:11 +0900

Excel で特定の行または列を固定する方法

Created: 2019-06-10 15:12:25 +0900
Updated: 2019-06-10 15:12:25 +0900

zipkin の RateLimitingSampler が便利(特に低トラフィックの場合)

zipkin で tracing log を取得する場合、一定の割合でログをサンプリングしてストレージに保存していた(たとえば 0.1% だけストアするなど)

この方法では、低トラフィックな環境ではログが十分取りづらいし、トラフィックが急に増えた場合を考えると、サンプリグレートを上げるのも怖いという問題があった。

しかし、zipkin brave の 5.6.0 以後では RateLimitingSampler という実装が導入されている。これにより、1秒間にどれだけ送るかという上限設定ができるようになり、トラフィック量に応じて sampling rate を変更する手間が省けるようになっている。

https://github.com/apache/incubator-zipkin-brave/pull/819 https://github.com/apache/incubator-zipkin-brave/blob/master/brave/src/main/java/brave/sampler/RateLimitingSampler.java

Spring Cloud Sleuth の場合

Spring cloud sleuth でもこの機能にすでに対応されていて、spring.sleuth.sampler.rate というプロパティを設定すると良い。

https://github.com/spring-cloud/spring-cloud-sleuth/pull/1175

Created: 2019-05-20 15:09:45 +0900
Updated: 2019-05-20 15:09:45 +0900

line-bot-sdk-nodejs を typescript で使う

    npm install --save express
    npm install --save typescript
        npm install --save @line/bot-sdk
    npm install --save @types/express
    npm install --save @types/node

    npm install --save-dev ts-node
    npm install --save-dev ts-node-dev

    npx tsc --init

    mkdir src
    touch src/index.ts

    npx ts-node src/index.ts

とかでセットアップして

npx ts-node-dev --respawn src/index.ts

で起動。

コードはこんな感じ(typescript 慣れてないから怪しい)。

import line = require('@line/bot-sdk');
import express = require('express');


const config: line.Config =  {
  channelAccessToken: process.env.LINE_CHANNEL_ACCESS_TOKEN!,
  channelSecret: process.env.LINE_CHANNEL_SECRET!,
};

// create LINE SDK client
const client = new line.Client(<line.ClientConfig>config);

// create Express app
// about Express itself: https://expressjs.com/
const app = express();

// register a webhook handler with middleware
// about the middleware, please refer to doc
app.post('/callback', line.middleware(<line.MiddlewareConfig>config), (req, res) => {
  Promise
    .all(req.body.events.map(handleEvent))
    .then((result) => res.json(result))
    .catch((err) => {
      console.error(err);
      res.status(500).end();
    });
});


// event handler
function handleEvent(event: line.WebhookEvent) {
  if (event.type !== 'message' || event.message.type !== 'text') {
    // ignore non-text-message event
    return Promise.resolve(null);
  }

  console.log(`Received message: ${event.message.text}`);

  // create a echoing text message
  const echo: line.TextMessage = { type: 'text', text: event.message.text };

  // use reply API
  return client.replyMessage(event.replyToken, echo);
}

// listen on port
const port = process.env.PORT || 3000;
app.listen(port, () => {
  console.log(`listening on ${port}`);
});
Created: 2019-05-07 17:35:43 +0900
Updated: 2019-05-07 17:35:43 +0900

[js] List::Util::all みたいなのを js で

 List::Util::all みたいなものをJSで書くときってどう書くのがいいんだろ
lodash とかにありそうだけど
syohex [8:55 PM]
 Array.prototype.everyとは違うのでしたっけ ?
karupanerura [8:56 PM]
 それだ。some/everyが今はありますね(名前が思い出せなかった
https://qiita.com/Nossa/items/4a425e57ec4b7eedb7cb
Created: 2019-02-02 18:59:20 +0900
Updated: 2019-02-02 18:59:20 +0900

ivy が最近は良いらしい

https://twitter.com/typester/status/1089932791150592001

最近はhelmやめてivyつかってますよ! とのこと。僕も変えてみた。

Created: 2019-01-29 08:02:01 +0900
Updated: 2019-01-29 08:02:01 +0900

YAPC::Tokyo 2019 で keynote してきた

https://www.slideshare.net/tokuhirom/20190126-yapc-tokyo-keynote

keynote の準備に忙しかったりしてあまりトーク聞けなかったけど楽しかったです。

Created: 2019-01-28 16:02:20 +0900
Updated: 2019-01-28 16:02:20 +0900

bash で **/* とかしたい

shopt -s globstar で OK

Created: 2019-01-19 14:21:47 +0900
Updated: 2019-01-19 14:21:47 +0900
Next page