be-hase blog

うぇーい!うぇいーーーい!!

Spring の UriComponentsBuilder のちょっと罠っぽいところ

UriComponentsBuilder は以下のように、URIの構築を簡単にできるやつです。 Spring とか使っている人だと使ったことあると思います。

String result = UriComponentsBuilder
        .fromUriString("https://example.com/")
        .queryParam("hoge", "あ")
        .toUriString();

assertThat(result).isEqualTo("https://example.com/?hoge=%E3%81%82");

最近使っていて気づいたのですが、UriComponentsBuilder.fromUriString(str) or UriComponentsBuilder.fromHttpUrl(str) に渡す URI string に query がついている場合、その部分も encode されるという挙動をします。 つまり、予め encodeしていると2重に encode されてしまうわけです。

こんな感じです。

String result = UriComponentsBuilder
        .fromHttpUrl("https://example.com/?nextPath=%2Fpath%2Fto%2Fsome%2Fpage") // "/path/to/some/page"
        .queryParam("hoge", "あ")
        .toUriString();

assertThat(result).isNotEqualTo(
        "https://example.com/?nextPath=%2Fpath%2Fto%2Fsome%2Fpage&hoge=%E3%81%82");
assertThat(result).isEqualTo(
        "https://example.com/?nextPath=%252Fpath%252Fto%252Fsome%252Fpage&hoge=%E3%81%82");

んー。個人的には、queryParam で指定した部分だけのほうが直感的だと思いますが...。

参考までに、Apache HttpClient にある URIBuilder だと query 部分が2重にencodeされるということはありません。

String result = new URIBuilder("https://example.com/?nextPath=%2Fpath%2Fto%2Fsome%2Fpage")
        .addParameter("hoge", "あ")
        .build().toString();
assertThat(result).isEqualTo(
        "https://example.com/?nextPath=%2Fpath%2Fto%2Fsome%2Fpage&hoge=%E3%81%82");

IntelliJ IDEAにおいて、remote maven repo の snapshot module がうまく更新されないとき

たしかに local repoには最新版落ちてるのになんぞーって時。

File -> Invalidate Caches / Restart を試す。

SpringのModelに共通な値(デフォルト値)を入れたいとき

開発環境依存の定数とかといったような共通な値をmodelに入れたいときがある。
(static fileのURLとか)

実は@ControllerAdvice@ModelAttributeが使用できるみたい。
(@ExceptionHandlerとか@InitBinderでしか使ったことなかった。)

@ControllerAdvice
public class ControllerAdvice {

    @ModelAttribute
    public void addDefaults(Model model) {
        model.addAttribute("hoge", "hoge");
    }
}

今まで、Interceptor使って埋め込んだり、FreeMarkerConfigurerにsetFreemarkerVariablesとかしていたけど、こっちのほうがスッキリする気はする。

Redis Lua script(EVAL)はSLOWLOGにはどう出るのか

先日、RedisのINCRBYFLOATコマンドがSLOWLOGにはSETコマンドとして出て来るといったような話がありました。 https://github.com/antirez/redis/issues/3841

EVALコマンドでLua scriptを実行したときのSLOWLOGは、EVALとして出て来るのか。それともLua script内のRedisコマンド単位で出てくるのか気になるね〜みたいな話がでたので検証してみる次第です。

結果から言うと、(Redis 3.2.1 versionでは)EVALコマンドとして出て来るので問題ないです。

検証する

なんか適当にLua内でsleepさせてみれば良いわけであります。

しかし、RedisのLuaでは使用できるlibに限りがあるので、os.sleepとかは使えません。
どうしたもんかと考えた結果、SET, PEXPIRE, PTTLを以下のように使って20ms sleepさせてみました。
(Redisはデフォだと10msこえるとSLOWLOGに出てくる)

local tempKey = "temp-key"
local cycles

redis.call("SET", tempKey, "1")
redis.call("PEXPIRE", tempKey, 20)

for i = 0, ARGV[1] do
    local pttl = redis.call("PTTL", tempKey)
    cycles = i;
    if pttl == 0 then
        break;
    end
end

準備できたので、実行してみるです。

EVALのとき

$ redis-cli EVAL "$(cat slow.lua)" 0 100000
(integer) 29409
$ redis-cli SLOWLOG GET 1

1) 1) (integer) 4
   2) (integer) 1488512354
   3) (integer) 19775
   4) 1) "EVAL"
      2) "local tempKey = \"temp-key\"\nlocal cycles\n\nredis.call(\"SET\", tempKey, \"1\")\nredis.call(\"PEXPIRE\", tempKey, 20)\n\nfor i = 0, ARGV[1] ... (127 more bytes)"
      3) "0"
      4) "100000"

お、ちゃんとEVAL単位で出てきます。

EVALSHAのとき

毎回Lua scriptをRedisに送るのは帯域の無駄なので、予めRedisにscriptを読ませておく方法があります。
そして、そのscriptのsha1を使ってこんな風に実行します。

EVALSHA sha1 numkeys key [key ...] arg [arg ...]

もし、SLOWLOGでこのコマンドで出てきても、いったいどのLua scriptなのかパッと見でわからんという問題があります。

というわけで、一応試します。

$ redis-cli SCRIPT LOAD "$(cat slow.lua)"
9dabe8fa13e49ee8c41467e697cd852e553ca36c

$ redis-cli EVALSHA 9dabe8fa13e49ee8c41467e697cd852e553ca36c 0 100000
(integer) 35989
$ redis-cli SLOWLOG GET 10
1) 1) (integer) 6
   2) (integer) 1488512445
   3) (integer) 19238
   4) 1) "EVAL"
      2) "local tempKey = \"temp-key\"\nlocal cycles\n\nredis.call(\"SET\", tempKey, \"1\")\nredis.call(\"PEXPIRE\", tempKey, 20)\n\nfor i = 0, ARGV[1] ... (127 more bytes)"
      3) "0"
      4) "100000"

お、ちゃんとEVALとして出て来るので安心です。

まとめ

というわけで安心です。

Vue 2.0, vue-router, vuex で はてブ リーダーぽいやつ作った

最近サーバー側ばっかやっていて全然フロントエンド側触ってなくてやばい !っという理由と、 そういえば去年の秋頃に Vue 2.0 が出ていたし触ってみるか!っという理由で Vue 2.0 を触ってみた。

ただ触るだけじゃつまらないので、( SPA で)はてブ リーダーっぽいやつを作りました。

https://github.com/be-hase/vue-hatena-bookmark

Live demo

Vue 公式が HackerNews リーダー的なものを sample として公開していたので、それじゃはてブ版でも作るかという軽いノリです。

“人気"と"新着"しかないぶん、わりとサクサク動くのでなかなか便利です。

使ってるもの

package.jsonに書いてあるとおりですが、

  • Vue 2.0
  • vue-router
  • vuex
  • vuetify
    • Vue の Material Design componentsみたいなやつ
  • axios

で作っています。

RSSYQL

記事情報はRSSから取得しています。

さらにYQL(Yahoo Query Language) を使用しています。

YQLを使うと、RSSのようなweb上のリソースをSQLのように取得することができます。

例えば、はてブのテクノロジーの新着を見たいなら、

select * from feed where url='http://b.hatena.ne.jp/entrylist/it.rss'

みたいなYQLを書きます。

axiosを使用して取得するなら、こんな感じになります。

axios.get('https://query.yahooapis.com/v1/public/yql', {
    params: {
      q: `select * from feed where url='http://b.hatena.ne.jp/entrylist/it.rss'`,
      format: 'json',
    },
  });

format=json を指定するとJSON で response を受け取れます。

ちなみにYQL, 特に認証しなくても使えるがcall数の制限はあります。 https://developer.yahoo.com/yql/guide/usage_info_limits.html

YQL, CORSできるので今回の例だと特にserver側で動的に書くことはなくて、static file置くだけで動きます。

所感

フロントエンドのフレームワーク、今までBackbone → Reactって触ってきましたが、一番お手軽感あって学習コストも低くて便利だなっていう感じです。
( React経由しているので学習コスト低く感じただけもしれないっていうのはありますが… )

ちょっと管理画面とかで複雑なことやりたいときとかに使ってみようかなという感じです。

LINE Notifyでたのでちょっと試してみる。ついでにGithub Enterpriseのwebhookを通知するやつを作ったぞ!!

LINE Notifyがリリースされたぞ!!

2016/09/29にLINE DeveloperDay 2016が開催され、いくつかの新機能などが発表されました。
http://developers.linecorp.com/blog/ja/?p=3779

その中のひとつに、LINE Notifyというものがあります。
一言でいうと、簡単にLINEにメッセージを送信することができるサービスです。
messaging APIと違って"LINE Notifyアカウント"からしか送信できないといった制限はありますが、その制限に耐えうるメリットがあるように思えます。

ちょっと試しにcurlから実行してみるぞ!!

なにはともあれ、ちょっと試しにcurlから実行してみます。

LINE Notifyにログインして、マイページにいきます。
https://notify-bot.line.me/my/

ここからPersonal Tokenを発行することができます。

f:id:hsbrysk:20161001170805p:plain

tokenの名前と、送り先を選択するだけです。

f:id:hsbrysk:20161001173948p:plain

発行したPersonal Tokenを用いて、

curl https://notify-api.line.me/api/notify -H 'Authorization: Bearer MY_TOKEN' -d 'message=hello'

とシェルで実行するだけでメッセージを送信することができます。

f:id:hsbrysk:20161001174244p:plain

こういったことが使えるだけでも色々な用途が思いつきます。
(王道っぽいですが)例えば、

  • depoyなどにフックさせて実行してみたり、
  • process downしてるのを検知したら送るようにしてみたり、
  • cron処理結果を送ったり、
  • Jenkinsのビルド後の処理に上記コマンドをいれてみたり、

...どれも手軽に使えて便利っぽい雰囲気を感じます。

すでにこんな使い方もされているようですね。真似してみよう。

YappoLogs: LINE Notify で line command 作ると便利

OAuth認証を実装することで、webサービスとも連携することができるぞ!!

Personal Tokenだけでも便利なんですが、OAuth認証を実装することでwebサービスとも連携することができます。
WEBサービスでメール通知などをしていることが多いと思いますが、同じスキームでLINEに送信することができます。

実際にすでにIFTTT, Mackerel, Githubで連携が実装されています。

IFTTTと連携してみるぞ!!

例えば自分は、同居人と家事等をTrelloで管理して分担している。
いままではIFTTTの送り先をslackにして通知していたのですが、同居人はslackは使用していません。
わざわざこのためにslackをインストールさせてアカウントを作らせるのは面倒なので、自分だけで通知を享受して便利!って感じでした。

LINEだと、すでに同居人も使用しているのでちょっとこのgroupに入ってくれい!という感じで自然に誘導できて簡単便利だぞ、と思いました。

これでトイレットペーパーを買い忘れて冷や汗をかくこともなくなります。

IFTTT連携の仕方は、すでに他の方が記事が書かれているので参考になると思います。
netafull.net

Githubと連携してみるぞ!!

Github連携もしてみます。

LINE Notifyのtop pageにいくと、Githubのアイコンがあるのでそれをクリックします。
https://notify-bot.line.me/github/repos/select

すると、LINE NotifyとのGithub連携する画面が出てきますので、許可します。
許可をすると、このページに遷移します。

f:id:hsbrysk:20161001171822p:plain

連携したrepositoryを選択し、LINEの通知先を選択するだけで完了です。カップヌードルができるよりも早くて楽チン!!

githubのwebhook eventを利用しているので、もしもこのeventは不要だなと思うのがあればGithubのwebhool設定ページのチェックを外しても良さそう。

f:id:hsbrysk:20161001172244p:plain

ちなみに対応しているeventは、

  • create
  • delete
  • commit_comment
  • issues
  • issue_comment
  • push
  • pull_request
  • pull_request_review_comment

とのこと。

Mackerelと連携してみるぞ!

と、簡単に書こうと思ったのですがこちらのHelpページが参考になるので参考にしてください。

mackerel.io

グラフとかも送られてくるの、すごい良さそう!

せっかくだし、なにかLINE Notifyを利用して作るぞ!!

さて、せっかくだしなにを作るぞ!
OAuthを実装するのはちょっとサクッと作るには面倒なので、Personal Tokenを利用するくらいが今回は無難そう。

考えた結果、Github Enterprise用のやつを作ってみようと思った。
LINE Notifyからはgithub.comとは手軽に連携できるのですが、Github Enterpriseとはできません。
会社の業務ではGithub Enterprise使っているので、Github Enterpriseでも使いたいぞ!というのがモチベーションです。

ghe-line-notify

そんなわけで、作ったやつがこれです。
https://github.com/be-hase/ghe-line-notify

Github Enterprise用ではありますが、github.comでも使用できます。

使い方

インストール/起動の仕方などは後述。

1. LINE NotifyでPersonal Tokenを発行する

LINE Notifyのマイページから作れます。
https://notify-bot.line.me/my/

tokenの名前(ghe-line-notifyとかで良い)と、送り先を選びます。

2. Personal TokenとWebhook Message Templateをghe-line-notifyに登録する

ヘッダーの"Add Token"をクリックすると、こんなform pageが表示される。

f:id:hsbrysk:20161001172404p:plain

まずはBasicsの情報を入力する。
Github webhookのSecretを利用したい場合は、Secretも入力する。そしたら同じSecretをGithub Webhookにも設定すれば良いです。
例えば違う部署を跨いで複数人でghe-line-notifyを利用する場合は、勝手にwebhookを利用されないようにSecretを使っておくと良いと思います。

次にWebhook Message Templateを入力する。

f:id:hsbrysk:20161001172449p:plain

各webhook eventで送られてるくるpayload jsonを使って、Jinja2テンプレートの記法で書けばです。
なんでJinja2なのかというと、ghe-line-notifyはPythonで実装しているからです。

ちなみに、webhook eventやpayload jsonはこのページを見ればわかります。
https://developer.github.com/webhooks/#events

各textareaには、とりあえず汎用的で便利そうなやつを予め入力してあるので、このまま登録しても良いです。

例えば、commit eventでは次のようなpayload jsonGithubから送られてきます。(一部省略)

{
  "ref": "0.0.1",
  "ref_type": "tag",
  "master_branch": "master",
  "description": "",
  "pusher_type": "user",
  "repository": {
    "id": 35129377,
    "name": "public-repo",
    "full_name": "baxterthehacker/public-repo",
    "owner": {
      "login": "baxterthehacker",
      "id": 6752317,
      ....
      "type": "User",
      "site_admin": false
    },
    "private": false,
    "html_url": "https://github.com/baxterthehacker/public-repo",
    ....
    "watchers": 0,
    "default_branch": "master"
  },
  "sender": {
    "login": "baxterthehacker",
    "id": 6752317,
    ....
    "site_admin": false
  }
}

このcreate eventに対して次のようなtemplateを登録しているとします。

{{ sender.login }} created {{ ref_type }} {{ ref }} at {{ repository.full_name}}

{{ repository.html_url }}

そうすると、LINEには次のようなテキストで送られるわけです。

baxterthehacker created tag 0.0.1 at baxterthehacker/public-repo

https://github.com/baxterthehacker/public-repo

ちなみに、Jinja2でレンダリングした結果が、空文字である場合はLINEには送信しません。 これを応用することで、次のようなif Statementsをいれることで、ref_typeがbranch以外のやつはLINEには送信しないことも可能です。

{%- if ref_type == 'branch' -%}
{{ sender.login }} created {{ ref_type }} {{ ref }} at {{ repository.full_name}}

{{ repository.html_url }}
{%- endif -%}

push eventのときはmaster branchのときだけ通知したり、pull request eventのactionが"opened"のときだけ送るなどといったカスタマイズが自由にできます。
自分自身もまだこの辺どうするのかは手探りなので、ちょっと手間はかかるけど自由にいろいろカスタマイズできるようにしています。

templateの挙動はヘッダーにある"Template Playground"ページから確認できます。

3. Githubのwebhookに登録する

Tokenの登録が完了すると、次のような画面が表示されます。

f:id:hsbrysk:20161001172625p:plain

一意なcodeが生成され、Github webhookに登録するPayload URLが表示されるので、Github上に登録します。
Content-typeはapplication/jsonにします。
Secretを使った場合は、Secretも入力しましょう。

f:id:hsbrysk:20161001172741p:plain

なお、登録したTokenは"Token List"に表示され、随時編集もできる。
やっぱりmessageを変えたいぞ!!ってときは変更できます。

インストール/起動方法とか

READMEに書いてあります。
https://github.com/be-hase/ghe-line-notify

すぐに動かして見たい場合は、READMEにあるherokuボタンを押せば完了です。

しかし、Github Enterpriseを使うような環境ではHerokuとは接続できないでしょう。
その場合は、READMEに書いてあるようにpython3をinstallしたサーバで、

git clone https://github.com/be-hase/ghe-line-notify
cd ghe-line-notify

pip install -r requirements/common.txt

python manage.py db upgrade

gunicorn -c gunicorn_config.py app:app

とかすれば良いです。
なお、python3.5.xでしか動作確認はしていないので、うまくいかなかったら最新版にあげてみてください...。

もしくは、Dockerfileも置いてあるのでDockerで動かしても良さそうです。

alembicを使ってDB migrateをしています。
デフォルトだと、sqliteを使用します。
失ったら死ぬ!といったデータでもないですし、sqliteで十分なのでは...と思っていますが、もしも変えたい場合は環境変数GHE_LN_DATABASE_URIを指定すればいいです。

heroku上で立ち上げるときは、herokuのaddonで用意されているpostgresqlを使用するようにしています。

今後修正実装しようかなと思っていること

「gheのOAuth2連携できるようにして!」って同僚から言われたので、あとで作ってみようかなと思っている。

まとめ

  • Personal Token発行するだけでサクっとcurlからでも使えるので便利!
    • 今回紹介したghe-line-notifyみたいなツールもサクっと作ることができる!
  • OAuth2実装すれば、自分のWEBサービスとかからも通知できる!

なにか他にも面白そうなのがあれば、githubとかにあげてこうかなと思っています。

Reluminでslowlog見れるようにした

やったこと

ReluminっていうRedis Cluster Admin Toolでslowlog見れるようにしてくれい!という依頼があったので、実装してみた。
こんな感じになります。

f:id:hsbrysk:20160624010805p:plain

GitHub - be-hase/relumin: Redis cluster admin tool

横軸に時間とって、縦軸にslowlogの実行時間(micro seconds)をノードごとに色分けしてscatter plotしていっている。
hoverするとツールチップでslowlogのコマンドとかも見れる感じ。

graphの下には、tableで羅列している。

Redisみたいにシングルスレッドでイベントループしてるやつで長い実行時間のcommandあると危険なので、
頻発するようだったら対処してあげてみてください。

今後

時間と気合があったらやることリスト

  • INFOコマンドで取得したmetricsに対してはalertできる機能あるので、slowlogもalertしてあげても良いかもしれない。
  • Redis3.0.6くらいから付属しているredis-trib.rbには良い感じに均等にリバランシングする機能がついたので、Relumin上でも実装してワンクリックでリバランシングできるようにしても良いかもしれない。
    • 今だとRelumin上でノードごとに複数回リシャードしないといけないので、少し面倒くさい。
  • Jedis2.9からclusterでもpassword設定できるようになるので、その対応しておこうかな。
    • でも他の言語のclientはまだpasswordのサポートしてなさそうだからあとでいいかな。
  • React(SPA)で書いてるフロント部分、Typescriptで書きなおそうかな。
  • metricsとかメタデータ保存しているstorage, RDBで実装するの面倒だったのでRedis使ってるんだけど明らかにお金もったいないのでRDBも使用できるようにしようかな。
    • チーム内でこじんまりと使うぶんにはよかったけど...