Johnman.md

プログラミングのことや個人的なことを書きます。たぶん。

【Go】stringがJSON形式かどうかを判定する

stringを受け取った際、そのstringがJSONの形式を持っているかを判定したい場合に使える。

func isJSONString(s string) bool {
    var js json.RawMessage
    err := json.Unmarshal([]byte(s), &js)
    return err == nil
}

やっていることは簡単で、json.RawMessage型の変数を用意して、そこにUnmarshalをかけています。

もしstringがJSONでなければエラーが返されるので、nilと比較すればJSONかどうかがわかるという寸法です。

ちなみに、json.RawMessageじゃなくstringを使ってしまうと、JSONの中に数字なんかが入っていた場合に正しく判定できなくなるので、json.RawMessage型を使う方が無難だと思います。

【Go】GitHub Actionsでテストやビルドにキャッシュを効かせる

概要

最近、身内で使うDiscord botを作っています。

そのCIにGitHub Actionsを使っているんですが、CIが走るたびに結構時間がかかるなぁという印象を持っていました(だいたい1分30秒くらい)。 modulesやビルドにキャッシュを使っていないのでそのせいかなと思い、キャッシュを導入することにしました。

キャッシュを効かせる

今回使ったのは actions/cache です。

github.com

Goのキャッシュを効かせる方法に関しては、github-actions-golangのREADMEに記載されています。

今回はmodulesとビルドのキャッシュを効かせたいのと、対象のOSはLinuxのみなので以下のstepを追加しました。 dependencyが更新された際にキャッシュが変わるよう、keyにはgo.sumから生成したhashを含めています。

- name: use cache
  uses: actions/cache@v2
  with:
    path: |
      # modulesのキャッシュ
      ~/go/pkg/mod
      # ビルドキャッシュ(Linux)
      ~/.cache/go-build
    key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
    restore-keys: |
      ${{ runner.os }}-go-

キャッシュを入れたことで、今まで1分30秒ほどかかっていたCIが56秒で終わるようになりました。 f:id:johnmanjiro13:20210216144256p:plain

まとめ

これで手っ取り早くCIを早くすることができました。ひとまずテストやビルドで待つ時間が減ったので満足してます。

GitHub Actionsはパブリックリポジトリなら無料で使えるし、使うための設定も特に不要なのでめちゃくちゃいいですね。これからも使っていきます。

1月に読んだ本、遊んだゲーム、観た映画

あけましておめでとうございます。
年末年始で書こうと思ってたんですが書けませんでした。

2021年1月に読んだ本、遊んだゲーム、観た映画なんかをまとめておこうと思います。

宇宙創成(サイモン・シン

宇宙創成(上)(新潮文庫)

宇宙創成(上)(新潮文庫)

フェルマーの最終定理や暗号解読を書いたサイモン・シンが書く宇宙の話。
古代ギリシャから始まり、ガリレオアインシュタインを経てビッグバンに到るまでを知ることができる。
なぜ星を構成する物質がわかるのか? なぜビッグバンが起きたなんてことがわかるのか? そんな長年の謎がすっかり解けて、非常におもしろかった。

本はこれくらいしか読みませんでした。

ゲーム

Cyberpunk 2077

【PS4】サイバーパンク2077

【PS4】サイバーパンク2077

  • 発売日: 2020/12/10
  • メディア: Video Game

世間を騒がせている問題児。
バグを抱えながらも世界観や景色が好きでプレイしていたけど、結局は面白いがゆえにバグの残念さ・悲しさを感じ返金処理してしまった。

サイバーパンクはウィッチャーにはなれなかった……。

映画

ボヘミアン・ラプソティ

ボヘミアン・ラプソディ (字幕版)

ボヘミアン・ラプソディ (字幕版)

  • 発売日: 2019/04/17
  • メディア: Prime Video

Queenを映画化した2018年の名作。
当時観に行こうと思っていて結局観に行かなかった。やっぱり観ておけばよかったな。

Queenのドキュメンタリー的なものを想像していたんだけど、実際は当時のQueenを現在の役者が再現したもの。 Queenというよりはフレディ・マーキュリーの話なんだろうな。演奏シーンにはフレディ本人の音声を使っているらしく、映画館の音響で聴きたかったと心底思った。

最後のライブシーンは再現度も含めてもう圧巻。とてもいい映画。ロケットマンもみたいな。

インターステラー

インターステラー(字幕版)

インターステラー(字幕版)

  • 発売日: 2015/03/25
  • メディア: Prime Video

テネットを手がけることになるクリストファー・ノーランの映画。

地球がもうすぐ滅ぶ近未来に、科学の力をつかって移住先の星を探す物語。 相対性理論とかしっかり考証しているらしく、ふんわりとでもいいからその辺のことを知っていないと難しいかも。大きな重力のそばでは時間が遅くなる(時空が歪む)なんてことも説明もなく話される(「相対性理論は?」「知ってる」くらいの会話で終わる)。

逆にその辺の理論が少しでもわかる人にはめちゃくちゃハマる気がする。科学の力ってすげー!

エヴァンゲリヲン新劇場版(序・破・Q)

ヱヴァンゲリヲン新劇場版:序

ヱヴァンゲリヲン新劇場版:序

  • 発売日: 2007/09/01
  • メディア: Prime Video

リンクは代表して序。

シン・エヴァンゲリオン劇場版が公開されるということで、それに向けて観た。 エヴァは観たことなかった(破を一度地上波で観たくらい)のだけど、友達が好きだったりあっちゃんがYouTubeで熱弁していた影響で観てみようかなと。 序と破はめちゃくちゃ面白かった。破の後半は地上波で観たときはポカンだったんだけど、あっちゃんの動画を観た後だったのでなんとかついていくことができた。

ただしQ、てめーはダメだ。
動画である程度考察されている内容をしってはいたもののやっぱり最後の方で理解できなくなっちゃった。何を言ってるのカヲルくん……。

シン・エヴァが公開されるのが楽しみ。

オデッセイ

オデッセイ(字幕版)

オデッセイ(字幕版)

  • 発売日: 2016/04/22
  • メディア: Prime Video

インターステラーに続く宇宙もの。
これも公開当時に知り合いが面白いと語っていて観たかったんだけど観れなかった。

火星に置いていかれた男が地球に戻るためになんとか生き延びる話。これも科学の力ってすげー!って映画。 インターステラーと比べると理論がごちゃごちゃ出てくるわけでもないし、設定も面白いので観やすいと思う。子供の頃観てたら結構好きになったんじゃないかな。

個人的にはこういう科学者が知識でがんばる話がとても好きなので、もっとこういうの観たい。

ジョゼと虎と魚たち

joseetora.jp

1985年に刊行された同名小説のアニメ映画版。
フォロワーがめちゃくちゃよかったとツイートしていたので気になって会社を休んで観にいった。

結果めちゃくちゃよかった……。こういうのでいいんだよこういうので。
王道のストーリーで人によってはシンプルすぎると思うかもしれないけど、王道にはやっぱり王道のよさがある。

ボーイミーツガールみたいな話がとても好きなんだよなぁ、昔から。
ジョゼがちょくちょく「なんでそんなことすんの……」って行動を取るんだけど、それも人となかなか関わる機会がなかったからと思えばかわいいもの。

映画館で観れてよかった。感想投稿キャンペーンにも応募しました。配信とかされたらまた観ます。

まとめ

今月は本1冊、ゲーム1本、映画7本でした。

コロナ影響で緊急事態宣言もでたりして、映画が延期したり映画館自体行きづらくなったりしてますね。はやくコロナ収束しないかなぁ。

今月はもう少し本を読もうと思います。

Goa v3でmultipart/form-dataを扱うために必要なこと

これは Go 2 Advent Calendar 2020 の16日目の記事です。

はじめに

先日、Goaを使って実装している API で Goa v1 から v3 へバージョンアップを行ないました。

Goa では v1 と v2/v3 の間で大きく仕様が変わっっており、ところどころ詰まる点があります。 特に、multipart/form-data を使ってファイルアップロードをしたいというような場合に、実装が大きく変わって若干詰まったのでご紹介します。

Goaでのmultipart/form-dataを使ったファイルアップロード

まず、Goa v1 と v3 でどのように違いがあるかを紹介します。

v1

v1 では、design で MultipartForm() を呼び出すことで multipart/form-data を扱うことができるようになります。

また、ファイルを扱う際には File 型というプリミティブ型があり、これをペイロードの型として設定することで、multipart/form-data で送られたファイルを扱うことができるようになります。

var _ = Resource("profiles", func() {
    Action("submit", func() {
        Routing(POST("profiles"))
        Payload(ProfilePayload)
        MultipartForm() // Uses "multipart/form-data" encoding
        Description("Post accepts a multipart form encoded request")
        Response(OK, ResultMedia)
    })
})

var ProfilePayload = Type("ProfilePayload", func() {
    Attribute("name", String, "Name")
    Attribute("birthday", DateTime, "Birthday")
    Attribute("icon", File, "Icon") // Attribute "icon" contains a file
    Required("name", "birthday", "icon")
})

goa.design

v3

v3 では、MultipartForm()MultipartRequest() になっています。これはv1 と同様、design で呼び出せば問題ありません。

しかし、v1 に存在していた File 型は v3 では廃止されており、代わりに Bytes 型が追加されました。また、v1 では multipart のデコード処理を Goa が裏側で行なってくれていたのですが、v3 では自分でデコード処理を実装する必要があります。

multipart.go の実装

multipart/form-data のデコード処理を実装した multipart.go を準備します。Goa のリポジトリに例が載っているので、そちらの実装が参考になります。今回はファイルアップロードの例なので、Bytes型のファイル本体とString型のファイル名が渡ってくる場合の実装を示します(といっても、Goa の例とそこまで大きな違いはありません)。

github.com

func ImageCreateDecoderFunc(mr *multipart.Reader, p **image.ImageCreatePayload) error {
    payload := image.ImageCreatePayload{}
    for {
        part, err := mr.NextPart()
        if err == io.EOF {
            break
        }
        if err != nil {
            return fmt.Errorf("failed to load part. err: %s", err)
        }
        _, params, err := mime.ParseMediaType(part.Header.Get("Content-Disposition"))
        if err != nil {
            return fmt.Errorf("failed to parse part. err: %s", err)
        }
        switch params["name"] {
        case "inputFile":
            bytes, err := ioutil.ReadAll(part)
            if err != nil {
                return fmt.Errorf("cannot read binary. err: %s", err)
            }
            payload.InputFile = bytes
        case "inputName":
            name, err := ioutil.ReadAll(part)
            if err != nil {
                return fmt.Errorf("cannot read input name. err: %s", err)
            }
            payload.InputName = string(key)
        }
    }
    *p = &payload
    return nil
}

これで multipart 形式のデータをデコードすることができるようになります。

おわりに

今回は Goa v3 で multipart/form-data を扱う例を紹介しました。 Goa はv1 から v3 へ移行しようとすると結構大変な部分がありますし、そもそも Goa の design も学ぶ必要があります。しかし、非常に便利なフレームワークです。

ぜひ使ってみてはどうでしょうか。

ファイルの変更を監視してコマンドを実行するスクリプト

openssl sha256 コマンドでファイルのハッシュ値を生成し、それが変わっていたら引数で指定したコマンドを実行するスクリプト

何気に初めて自分で使うためにシェルスクリプトを書きました。

スクリプト

#!/bin/bash

function __usage() {
  cat <<EOS
  usage: $(basename $0) <target filename> <command>
EOS
}

__check() {
  echo `openssl sha256 -r $1 | awk '{print $1}'`
}

if [ $# -ne 2 ]; then
  __usage
  exit 1
fi

echo "watching: $1"
INTERVAL=1 # second
last=`__check $1`
while true; do
  sleep $INTERVAL
  current=`__check $1`
  if [ "$last" != "$current" ]; then
    echo 'execute...'
    eval $2
    last=$current
    echo 'done'
  fi
done

使い方

./watch.sh filename 'command'

ぼくの場合はwebpack --watch でビルドされたファイルをローカルサーバを立ててる別のディレクトリにコピーして画面に反映させるために使いました。

参考にした記事

ファイルの変更を定期的に監視して、特定のコマンドを実行するシェルスクリプト - Qiita

指定したファイルの更新があったらコマンドを自動実行するシェルスクリプト - 午睡二時四十分

MT-03納車と立ちゴケ、そしてこれから

先日、MT-03を納車しました。

もともと高専在学中に二輪免許を取ろうと思っていたんですが、結局そのお金でギターを買っちゃったので取れず。 大学院の休学中にも取ろうかと思い立ちましたが単純にお金がなくて取れず。 そして社会人になってやっと免許を取ることができました。

一緒に免許を取ろうと誘ってくれた同期には感謝です。

MT-03を選んだ経緯

ということで初めてのバイクに選んだのはYAMAHAのMT-03、2020年モデル。
もともとカウルがないバイクがいいなぁと思っていたのと、クラシックな見た目が好きだったのでホーネット250を検討してました。

ただやはり古いバイクだということと、最近モトブログなんかでホーネット乗ってる人が多い影響かめちゃくちゃ高かったので選択肢から外してしましました。 あの音とでっかいタンク、タイヤは魅力的だったんですけどね。後悔はないです。

そこで色々みていた中でいいなと思ったのがMT-03とSR400。
もともと高専時代にビーノに乗ってたこともあってなんとなくYAMAHAに親近感があったので結構YAMAHAに寄りました(大学時代はリトルカブに乗ってたけど)。 ただSRはキックしかないのが個人的には結構大きく、最終的にMT-03を選びました。

ちなみにMT-25じゃなくMT-03にしたのは、250ccでパワー不足とか感じたら嫌だなぁと思ったからです。

立ちゴケ

納車して2日後、今まで電車で通っていた映画館にバイクで行ってみようと思い仕事終わりにバイクに乗ってみました。

駐輪場にバイクを駐めようとした時、ぐらっと右側にバランスを崩してしまい無事立ちゴケ。 一時停止していて、若干坂道になっていたのと、無理にハンドルを切って進もうとしたせいだと思います。

立ちゴケ自体はいずれするもんだろうと思っていたので、そこまでパニックにはならずすぐバイクを立てて傷の確認。 幸いにボディに大きな傷はなく、マフラーに少し傷がついたのとブレーキレバーが曲がったくらいで済みました。

今のところ気持ちは全然平気です。ただもうこけないようにしようと誓いました。
ブレーキレバーは近いうちに交換してみようと思います。純正かなぁ。

f:id:johnmanjiro13:20201013214430j:plainf:id:johnmanjiro13:20201013214438j:plain
マフラーの傷と曲がったブレーキレバー

これから

バイクは納車したんですが、ここのところ台風や秋雨前線の影響で雨が続き全然乗れてません。
土日にも人との予定が入っていたり仕事の輪番で運転を控えたりしてるので、なかなか時間がとれていない現状。

バイクに乗るために自分の現状を変えようかなと思い始めているところです。
人と会う予定を輪番の日に合わせるとかね。

まだまだ乗れてないので本当の楽しみには触れられていない感じもしますが、それでも楽しいです。一人で結構遠くまで乗ったりしたいな。

[Go] handlerに引数を追加する

先日、GoでAPIを書いていた時にServeHTTP関数に引数を追加したい場合があったのでその対処法。

GoのHandlerは次のように定義されています。

type Handler interface {
    ServeHTTP(ResponseWriter, *Request)
}

このinterfaceを満たした関数を実装することでhandlerを作ることができますが、場合によっては引数を増やしたいということがあります。 この時ただServeHTTP関数に引数を追加してしまうと、Handler interfaceを満たさなくなるので使えません。

そこで、引数を追加した関数をラップしたServeHTTP関数をHandlerに持たせます。

title := "handlerに引数を追加する"

type handler struct {

}

func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request ) {
    handle(w, r, title)
}

func handle(w http.ResponseWriter, r *http.Request, title string) {
    // 処理
}

muxでルーティングしている場合にも、無名関数でラップすることで使えます。

r := mux.NewRouter()
r.HandleFunc("/title", func(w http.ResponseWriter, r *http.Request) {
    handler(w, r, title)
})

func handler(w http.ResponseWrite, r *http.Request, title string) {
    // 処理
}

考えてみれば当たり前のことなんですが、今まで使う機会がなかったのでなかなか思いつきませんでした。