- 投稿日:2021-03-15T22:14:57+09:00
【Golang】標準入力から1文字のキー入力を読み込む(enter いらずの On KeyPress/KeyDown っぽいやつ)
Go言語で標準入力のキー押下(
KeyPress
)を検知するGolang で標準入力から1行ごとではなく、1文字だけ取得する方法を知りたい。
正確には TTY からの標準入力を 1 文字欲しいのです。つまり、ターミナルやコマンドラインなどでのユーザー入力(標準入力)で
y
->enter
でなく、y
キーを押しただけでy
を検知したいのです。「golang 標準入力 1文字 読み込む」で Qiita 記事に絞ってググっても、競プロの課題にあるような「改行区切りで得られた文字列から、最初の文字を取得する」ような例やパイプ渡しの例ばかりだったので、自分のググラビリティとして。
TL; DR (今北産業)
- @mattn さんの素晴らしい
github.com/mattn/go-tty
パッケージのtty.ReadRune()
を使う。
- https://github.com/mattn/go-tty @ GitHub
こういうこと ? をしたい人向け
$ go run . <enter> Key press => a Key press => b Key press => c Key press => d ...(ctrl+c) signal: interrupt動作確認済み環境
- Intel Core i5(2.7 GHz デュアルコア)
- macOS Catalina 10.15.7, Debian GNU/Linux 10 (buster), Alpine Linux v3.13
- ARMv7 Processor rev 4 (v7l)
- Raspbian GNU/Linux 9 (stretch),
- Go version 1.16
TS; DR (マスター、動くものをくれ)
main.gopackage main import ( "fmt" "log" "github.com/mattn/go-tty" ) func main() { tty, err := tty.Open() if err != nil { log.Fatal(err) } defer tty.Close() fmt.Println("Ready. Press any key ...") for { r, err := tty.ReadRune() if err != nil { log.Fatal(err) } fmt.Println("Key press => " + string(r)) } }go.modmodule sample/tty go 1.16 require github.com/mattn/go-tty v0.0.3Go Playground では
tty
からの標準入力のテストができないため、再現性のために Docker も置いておきます。macOS とラズパイ3+B(Raspbian GNU/Linux 9, stretch)の Docker で動きました。あと、Docker 内で
darwin
とarm
向けにビルドすれば Golang 入ってなくてもローカルでバイナリ動きました。DockerfileFROM golang:1.16-alpine COPY . /app WORKDIR /app RUN go mod tidymacOS,Linuxのターミナル(要Docker)$ # ファイルの確認 $ tree . . ├── Dockerfile ├── go.mod └── main.go 0 directories, 2 files $ # 実行環境(コンテナ・イメージ)のビルド $ docker build --tag sample/tty . ... $ # `go run .` と同じことをコンテナ内で実行 $ docker run --rm --interactive --tty sample/tty go run . Ready. Press any key ... Key press => a Key press => b Key press => c Key press => d Key press => e (ctrl+c) signal: interrupt $
- 投稿日:2021-03-15T12:18:51+09:00
<Golang+Nginx+MySQL>Macローカル上にDocker Composeでアプリを立ち上げてみた
Dockerアプリケーションのアーキテクチャー
ざっくりとやること・できることの説明
- docker-composeでNginxとGo言語のAPI, DBとしてMySQLのコンテナを3つ立ち上げる
- Nginxのindex.htmlからGo言語で書いたREST API(go-ginのフレームワーク使用)を経由して、DBに問い合わせ
- DBにCRUD(gormのフレームワーク使用)ができることを確認
- ※DockerとDocker Composeはインストールされているものとする。
バージョン確認
確認してみたら、以下のバージョンでした。
Docker version 20.10.2 docker-compose version 1.27.4 golang:1.14.7-alpine3.12 mysql:5.7 nginx:1.17.3-alpine※厳密にはバージョンの依存関係とかも考えないといけないが、一旦ローカルで動かすので、スルーしますm(__)m
フォルダ構成
docker-local ├── .env ├── api │ └── src │ ├── main.go │ ├── model ── model.go │ └── config ── config.go ├── db │ ├── conf ── my.cnf │ └── db-data ├── docker │ └── nginx │ ├── Dockerfile │ └── nginx.conf ├── docker-compose.yml └── web ├── env │ └── nginx ── site.conf └ src ── index.htmlディレクトリとファイルの用途解説
名前 種類 説明 docker-local フォルダ dockerローカルアプリケーションの親フォルダ .env ファイル docker-composeで使う環境変数のファイル api フォルダ APIのフォルダ src フォルダ ソースフォルダ main.go ファイル goを起動するときのファイル model/model.go ファイル DBとやりとりする時のファイル
struct, dao系のメソッドを記載config/config.go ファイル DBの接続情報等を記載 db フォルダ DBに関するフォルダ conf/my.cnf ファイル 任意のDB設定を記載。マウント用 db-data フォルダ DBのデータをsyncするマウント用 docker フォルダ Dockerfileたちを格納するフォルダ nginx フォルダ NginxのimageをビルドするDockerfileを格納 Dockerfile ファイル nginxのimage nginx.conf ファイル nginxの設定ファイル docker-compose.yml ファイル docker containerを一括でコントロールするyamlファイル web フォルダ フロントのファイルを格納 env フォルダ 環境設定ファイルを格納 nginx/site.conf ファイル nginxのserver設定ファイル
IPアドレス、ポート、ルーティングなど設定src/index.html ファイル 画面表示用ファイル Docker Composeの中身
version: "3.8" services: nginx: image: infra-challenge:nginx-20210312 # command: build: context: ./docker/nginx dockerfile: Dockerfile container_name: nginx ports: - 80:80 volumes: - ./web/env/nginx:/etc/nginx/conf.d - ./web/src:/var/www/html depends_on: - api logging: driver: "none" restart: always networks: - services api: image: golang:1.14.7-alpine3.12 command: go run main.go --host 0.0.0.0 --port 8080 container_name: api ports: - 8080:8080 volumes: - ./api:/go/src/github.com/infra-challenge/api - /Users/username/go:/go working_dir: /go/src/github.com/infra-challenge/api/src environment: - APP_STAGE=local - LOG_LEVEL=debug - MYSQL_CONNECTION depends_on: - db restart: always networks: - services db: image: mysql:5.7 # build: ./docker/mysql container_name: db ports: - 3306:3306 volumes: - ./db/conf/my.cnf:/etc/mysql/conf.d/my.cnf - ./db/db-data:/var/lib/mysql environment: - MYSQL_ROOT_PASSWORD - MYSQL_DATABASE - MYSQL_USER - MYSQL_PASSWORD healthcheck: test: ["CMD-SHELL", "mysqlshow -u ${MYSQL_USER} -p${MYSQL_PASSWORD}"] interval: "5s" retries: 10 logging: driver: "none" restart: always networks: - services networks: services: external: name: local_infra_challenge_networks主な属性の解説
docker compose
の文法バージョンは3.8
だよservices
配下に、コンテナの定義を書いていく。今回はnginx, api, dbの3つnginx
はDockerfile
を使うから、image名は任意で、build
の属性を指定。
context
はDockerfileのある相対パス、dockerfile
はDockerfile名- ※ファイル名が
Dockerfile
となっていたら、build: ./docker/nginx
としてもOKcontainer_name
を指定すると、docker stop
とかするときに、idじゃなくて名前を指定すれば制御できるので便利ports
は<ホスト(Macローカル)>:<ゲスト(Dockerコンテナ)>
でポートを接続volumes
は<ホスト(Macローカル)>:<ゲスト(Dockerコンテナ)>
でマウントフォルダを指定
- マウントとは、フォルダ同士を共有してsync(同期)すること。
depends_on
は他のコンテナが起動成功でき次第〜みたいなやつ- apiとdbは、自分でDockerfileは用意せずに、
Docker-Hub
公式のimageを使っているenvironment
に環境変数を指定する。
docker-compose.yml
と同階層に.env
ファイルを置き指定- DBでは
healthcheck
を行い、起動しているか随時確認- ネットワークを組むことで、コンテナ間通信が可能となり、APIからDBに問い合わせができるようになった
ネットワークについて
作ったネットワークの情報を確認してみると、下記のようなObjectが返ってきますね〜
"Subnet": "172.19.0.0/16",
の中で、Containers(nginx, api, db)がIPアドレスを振られて存在しているのがわかります。
ちなみに、networks
だけでなくlinks
という属性もあり、links
を使うと、一方的にコンテナ間通信ができるようになる設定もあるので、TPOによって使い分ける◎
公式docker:links$ docker network inspect local_infra_challenge_networks [ { "Name": "local_infra_challenge_networks", "Id": "f6a978e6f3aa803be061418c1c7081d132c9538c207ea08b1d732fc55ba2b917", "Created": "2021-03-12T03:57:20.0697997Z", "Scope": "local", "Driver": "bridge", "EnableIPv6": false, "IPAM": { "Driver": "default", "Options": {}, "Config": [ { "Subnet": "172.19.0.0/16", "Gateway": "172.19.0.1" } ] }, "Internal": false, "Attachable": false, "Ingress": false, "ConfigFrom": { "Network": "" }, "ConfigOnly": false, "Containers": { "7a81d8c6f965a07ce446aa0983180bbb4775187ed6873a9e26dbc9540f623392": { "Name": "nginx", "EndpointID": "09336c1acc58f9b75ea3ced604a7d82b168a5f0cd132ae4f2fffd267a416b022", "MacAddress": "02:42:ac:13:00:04", "IPv4Address": "172.19.0.4/16", #<-これ!! "IPv6Address": "" }, "842244244bf00593e25317dd1cafb35ae03cfbc5246a87c666c6434a6ad7540d": { "Name": "db", "EndpointID": "3810c5b1b5f87b3ba4331320fcb2106696f3dcd157a771f9de63aeffc3a14720", "MacAddress": "02:42:ac:13:00:02", "IPv4Address": "172.19.0.2/16", #<-これ!! "IPv6Address": "" }, "c0906b3fc86d92f4b89474b7647e85a36e489469f83905c94785f2f278807532": { "Name": "api", "EndpointID": "e291176e7bd08040c2486d0409dab009a724fb7e3f0da3a5a9585cb23344a999", "MacAddress": "02:42:ac:13:00:03", "IPv4Address": "172.19.0.3/16", #<-これ!! "IPv6Address": "" } }, "Options": {}, "Labels": {} } ]コンテナ間通信をするときは、コンテナ名でエンドポイントを指定することができるから便利
# db:3306 の部分 <コンテナ名:ポート> MYSQL_CONNECTION=local:local@tcp(db:3306)/infra-challengeマウントについて
nginx
volumes: - ./web/env/nginx:/etc/nginx/conf.d - ./web/src:/var/www/html
- nginxには
default.conf
というものがあり、それを自分の設定に書き換えたいので、./web/env/nginx
で書き換えてる/var/www/html
以下がdockerコンテナ内のnginxフォルダ構成になる(する)ので、index.htmlがあるディレクトリを同期api
volumes: - ./api:/go/src/github.com/infra-challenge/api - /Users/username/go:/go
- とりあえずGo Pathが通っているところをdockerコンテナ側にマウントしてる。
- APIのファイル等も同期
db
volumes: - ./db/conf/my.cnf:/etc/mysql/conf.d/my.cnf - ./db/db-data:/var/lib/mysql
- MySQLのデータの格納先が
/var/lib/mysql
になるので、ホストの./db/db-data
に同期して、コンテナを除去してもデータが消えないようにする- 自分のDB設定、例えば、
character-set
とかcollation
とかをマウントして上書き# my.cnf [mysqld] character-set-server=utf8mb4 collation-server=utf8mb4_unicode_ci [mysql] default-character-set=utf8mb4いざ、Docker Composeで起動してみる!!
コマンド
$ cd /Users/username/src/github.com/infra-challege/docker-local $ docker network create --driver bridge local_infra_challenge_networks [~省略~] $ docker-compose build --no-cache [~省略~] $ docker-compose up -d --remove-orphans Creating db ... done Creating api ... done Creating nginx ... done $ docker ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 8fxxxb102ed4 infra-challenge:nginx-20210312 "nginx -g 'daemon of…" 4 seconds ago Up 3 seconds 0.0.0.0:80->80/tcp nginx 8fxxx25640e6 golang:1.14.7-alpine3.12 "go run main.go --ho…" 6 seconds ago Up 4 seconds 0.0.0.0:8080->8080/tcp api e5xxxe97320e mysql:5.7 "docker-entrypoint.s…" 6 seconds ago Up 5 seconds (health: starting) 0.0.0.0:3306->3306/tcp, 33060/tcp db
- docker-composeファイルがある階層に移動
networks
属性を指定したので、そのネットワークを作成する。- nginxはDockerfileをつかってimageを作成するので、ビルドする必要がある。
--no-cache
オプション:構築時にイメージのキャッシュを使わない。- docker-compose upで起動
-d
オプション:バックグラウンド実行--remove-orphans
オプション:Composeファイルで定義されていないサービス用のコンテナを削除Webページにアクセスできるか確認
コンテナ終了コマンド
$ docker-compose down Stopping nginx ... done Stopping api ... done Stopping db ... done Removing nginx ... done Removing api ... done Removing db ... done止まりました。
ハマったポイント
go run main.go
した後に、起動はしていたがポート設定がミスっていて、APIにアクセスできなかった。
- 「なんかうまくいかないな〜〜汗」ってなったので、VSCodeから一旦APIサーバーを止めて再起動して、通信がうまくいったけど、それはコンテナ間通信ではなかった、、、
- コンテナ上ではなくローカルでAPIサーバーが立ち上がってしまい、通信できていると勘違いしてしまった。
index.html
の画面表示するポートとAPI
を司るポートそれぞれが別だったので、その調整にハマった。- nginxには、サーバー設定が必要だった。
nginx.conf
とdefault.conf
というファイルが存在しこれのIPアドレス、ポート等を設定しないといけないので、前もってnginxの知識をいれておかないと簡単には動かなかった# site.conf (default.confの上書き) server { listen 80; listen 8080; # APIで8080ポートも使ってる関係で、これも必要だった server_name localhost; root /var/www/html; location / { index index.html index.htm; root /var/www/html; } error_page 500 502 503 504 /50x.html; location = /50x.html { root /var/www/html/error; } }まとめ
docker-compose.ymlでコンテナを起動して、Dockerアプリケーションを作る感覚がつかめたので、楽しかったですわい!
コンテナのコントロールってすごい簡単で、サクサク動くから、せっかちな自分でも全然苦じゃない。
マスターしていきたいなああああ以上、ありがとうございました!
- 投稿日:2021-03-15T11:40:48+09:00
【2021/2リリース】Go新機能embedを使ってメッセージが定義されたファイルを読み込む
はじめに
Go1.16リリース
Go1.16が2021年2月にリリースされましたね!
様々なアップデートがありましたが、主要でわかりやすい変更点は以下の通りでしょうか。
- M1 Macに対応
- 処理速度やメモリ使用量の改善
go:embed
の追加Go 1.16 adds support of 64-bit ARM architecture on macOS (also known as Apple Silicon) with GOOS=darwin, GOARCH=arm64.
For a representative set of large Go programs, linking is 20-25% faster than 1.15 and requires 5-15% less memory on average for linux/amd64, with larger improvements for other architectures and OSes. Most binaries are also smaller as a result of more aggressive symbol pruning.
The go command now supports including static files and file trees as part of the final executable, using the new //go:embed directive.
この記事でやること
今回はこの中でも
go:embed
を使って、「メッセージ一覧が定義されたjsonファイルを読み込み、そのメッセージを取得する」というモジュールの実装を解説したいと思います!完成形
完成形だけ知りたい方向けにこちらにコードを貼っておきます。
ディレクトリ構成(YOUR_DIRECTORY) ├─ message/ | ├─ message.go | └─ messages.json └─ main.gomessages.json{ "key1": "go:embed使ってみたよ!", }message.gopackage message import ( // import embed _ "embed" "encoding/json" "github.com/labstack/gommon/log" ) //go:embed messages.json var msgJSON []byte var msgs map[string]string // Read メッセージ一覧を読み込む func Read() { if err := json.Unmarshal(msgJSON, &msgs); err != nil { panic("Cannot read messages.json") } } // Get keyからメッセージを取得する(keyがなければ空を返す) func Get(key string) string { msg, exists := msgs[key] if !exists { log.Errorf("Cannnot find this message key: %s", key) } return msg }main.gofunc main() { // 一度だけ呼べばOK message.Read() // メッセージを取り出す fmt.Println(message.Get("key1")) }実行結果$ go run . go:embed使ってみたよ!解説
embedとは
embed
は埋め込みという意味です。
名前の通り、ファイルを変数に埋め込むことができます。ファイルの読み込み自体は今までもできましたが、複数の書き方があったり、やや冗長な感じがありました。
しかし、
embed
を使うことにより、上記message.go
のようにものの数行で書けるようになりました。また、
embed
で読み込んだファイルはビルドされたバイナリにも埋め込まれます。
これには以下のようなメリットがあります。これはGoの利点の一つである、単一の実行ファイルとしてビルドできることで、展開先の依存関係をシンプルに保つことができるという利点を強力に後押しします。設定ファイルや各種アセットをビルドに含めることで、バージョン管理やリリース作業を一層シンプルに整理できることが期待できます。
先ほど紹介した簡易WEBサーバーで例えると、WEBサーバーとコンテンツとなるHTML、CSS、Javascriptが分離している場合、ローカル環境で動いたものを実際の環境にデプロイする場合、実行バイナリと各種アセットをデプロイ対象の環境で適宜整理する必要があります。
これらを全て単一のバイナリに含めることができた場合、作業は実行バイナリを一つコピーして起動するだけになります。
新しいサーバーにデプロイする際の運用フローの整備や、プロダクション向けの構成でコンテナを構築するDockerfileを書いていく事を考えると、go:embedで極限まで簡略化できる部分が想像できるかもしれません。【引用】Go 1.16からリリースされたgo:embedとは
コード解説
message.go
の解説をしていきます。import ( // import embed _ "embed" )
embed
パッケージをimportします。コード中で
embed.~~
のような使い方をしないので、ブランクインポートをします。(正確なニュアンスではないですが)//go:embed messages.json var msgJSON []byteここが
embed
のキモですね。
message.go
と同じディレクトリにあるmessages.json
を変数msgJSON
に埋め込みます。
もちろん必ず同じディレクトリに配置する必要はなく、相対パスで指定することも可能です。var msgs map[string]string // Read メッセージ一覧を読み込む func Read() { if err := json.Unmarshal(msgJSON, &msgs); err != nil { panic("Cannot read messages.json") } }よく見かけるコードですね。
mapのmsgs
にmsgJSON
を代入します。【補足】mapで定義した理由
構造体を定義しておく手法が一般的かと思いますが、
messages.json
の中身が増えるたびに構造体のプロパティを追加していくのが面倒だったのでmapに代入するようにしました。
今回はjsonファイルに文字列しか登場しておらず、map[string]string
で済むのも、mapで定義した理由の1つです。
数字など他の型も考慮する必要がある場合、map[string]interface{}
にする必要があり、型安全ではなくなってしまいます。
このように複数の型を扱う場合や、ちゃんと型で守りたい場合は、構造体を使った方が良いと思います。【参考】Go言語でJSONを扱う
// Get keyからメッセージを取得する(keyがなければ空を返す) func Get(key string) string { msg, exists := msgs[key] if !exists { log.Errorf("Cannnot find this message key: %s", key) } return msg }ここはただmapからkeyでvalueを取り出しているだけです。
jsonファイルに存在しないkeyが引数で渡ってきた場合log出力するようにしています。
ちなみに存在しない場合、msg
は空文字となります。さいごに
Twitterの方でも、モダンな技術習得やサービス開発の様子を発信したりしているので良かったらチェックしてみてください!
2月にリリースされたGo1.16の新機能「embed」を使ってファイルの読み込みをしてみました!
— やぎぬ?行動力エンジニア (@yagi_eng) March 15, 2021
メッセージが定義されたjsonファイルを読み込んでみたのですが、結構いい感じに使えて便利でした
Go1.16の新機能が気になる方やファイル読み込みに悩んでいる方は読んでみてください?https://t.co/SIp6KqJsplまた、BOT開発を通じてGoとLINE BOTにまとめて入門する記事をZennに掲載していますので、良かったらそちらもご覧ください!
ZennでGoとLINE BOTの記事を書いてみました
— やぎぬ?行動力エンジニア (@yagi_eng) November 7, 2020
⬇️BOT開発を通じてGoとLINE BOTにまとめて入門するhttps://t.co/QqsEESJMKa
5ステップに分け、「Hello Worldから始めて、飲食店検索ができるLINE BOTの実装まで」を解説しています
GoやLINE BOTに興味のある人は是非読んでみてください?
24,000字超え?参考
- 投稿日:2021-03-15T11:40:48+09:00
Go1.16新機能embedを使ってメッセージが定義されたファイルを読み込む
はじめに
Go1.16リリース
Go1.16が2021年2月にリリースされましたね!
様々なアップデートがありましたが、主要でわかりやすい変更点は以下の通りでしょうか。
- M1 Macに対応
- 処理速度やメモリ使用量の改善
go:embed
の追加Go 1.16 adds support of 64-bit ARM architecture on macOS (also known as Apple Silicon) with GOOS=darwin, GOARCH=arm64.
For a representative set of large Go programs, linking is 20-25% faster than 1.15 and requires 5-15% less memory on average for linux/amd64, with larger improvements for other architectures and OSes. Most binaries are also smaller as a result of more aggressive symbol pruning.
The go command now supports including static files and file trees as part of the final executable, using the new //go:embed directive.
この記事でやること
今回はこの中でも
go:embed
を使って、「メッセージ一覧が定義されたjsonファイルを読み込み、そのメッセージを取得する」というモジュールの実装を解説したいと思います!完成形
完成形だけ知りたい方向けにこちらにコードを貼っておきます。
ディレクトリ構成(YOUR_DIRECTORY) ├─ message/ | ├─ message.go | └─ messages.json └─ main.gomessages.json{ "key1": "go:embed使ってみたよ!", }message.gopackage message import ( // import embed _ "embed" "encoding/json" "github.com/labstack/gommon/log" ) //go:embed messages.json var msgJSON []byte var msgs map[string]string // Read メッセージ一覧を読み込む func Read() { if err := json.Unmarshal(msgJSON, &msgs); err != nil { panic("Cannot read messages.json") } } // Get keyからメッセージを取得する(keyがなければ空を返す) func Get(key string) string { msg, exists := msgs[key] if !exists { log.Errorf("Cannnot find this message key: %s", key) } return msg }main.gofunc main() { // 一度だけ呼べばOK message.Read() // メッセージを取り出す fmt.Println(message.Get("key1")) }実行結果$ go run . go:embed使ってみたよ!解説
embedとは
embed
は埋め込みという意味です。
名前の通り、ファイルを変数に埋め込むことができます。ファイルの読み込み自体は今までもできましたが、複数の書き方があったり、やや冗長な感じがありました。
しかし、
embed
を使うことにより、上記message.go
のようにものの数行で書けるようになりました。また、
embed
で読み込んだファイルはビルドされたバイナリにも埋め込まれます。
これには以下のようなメリットがあります。これはGoの利点の一つである、単一の実行ファイルとしてビルドできることで、展開先の依存関係をシンプルに保つことができるという利点を強力に後押しします。設定ファイルや各種アセットをビルドに含めることで、バージョン管理やリリース作業を一層シンプルに整理できることが期待できます。
先ほど紹介した簡易WEBサーバーで例えると、WEBサーバーとコンテンツとなるHTML、CSS、Javascriptが分離している場合、ローカル環境で動いたものを実際の環境にデプロイする場合、実行バイナリと各種アセットをデプロイ対象の環境で適宜整理する必要があります。
これらを全て単一のバイナリに含めることができた場合、作業は実行バイナリを一つコピーして起動するだけになります。
新しいサーバーにデプロイする際の運用フローの整備や、プロダクション向けの構成でコンテナを構築するDockerfileを書いていく事を考えると、go:embedで極限まで簡略化できる部分が想像できるかもしれません。【引用】Go 1.16からリリースされたgo:embedとは
コード解説
message.go
の解説をしていきます。import ( // import embed _ "embed" )
embed
パッケージをimportします。コード中で
embed.~~
のような使い方をしないので、ブランクインポートをします。(正確なニュアンスではないですが)//go:embed messages.json var msgJSON []byteここが
embed
のキモですね。
message.go
と同じディレクトリにあるmessages.json
を変数msgJSON
に埋め込みます。
もちろん必ず同じディレクトリに配置する必要はなく、相対パスで指定することも可能です。var msgs map[string]string // Read メッセージ一覧を読み込む func Read() { if err := json.Unmarshal(msgJSON, &msgs); err != nil { panic("Cannot read messages.json") } }よく見かけるコードですね。
mapのmsgs
にmsgJSON
を代入します。【補足】mapで定義した理由
構造体を定義しておく手法が一般的かと思いますが、
messages.json
の中身が増えるたびに構造体のプロパティを追加していくのが面倒だったのでmapに代入するようにしました。
今回はjsonファイルに文字列しか登場しておらず、map[string]string
で済むのも、mapで定義した理由の1つです。
数字など他の型も考慮する必要がある場合、map[string]interface{}
にする必要があり、型安全ではなくなってしまいます。
このように複数の型を扱う場合や、ちゃんと型で守りたい場合は、構造体を使った方が良いと思います。【参考】Go言語でJSONを扱う
// Get keyからメッセージを取得する(keyがなければ空を返す) func Get(key string) string { msg, exists := msgs[key] if !exists { log.Errorf("Cannnot find this message key: %s", key) } return msg }ここはただmapからkeyでvalueを取り出しているだけです。
jsonファイルに存在しないkeyが引数で渡ってきた場合log出力するようにしています。
ちなみに存在しない場合、msg
は空文字となります。さいごに
Twitterの方でも、モダンな技術習得やサービス開発の様子を発信したりしているので良かったらチェックしてみてください!
2月にリリースされたGo1.16の新機能「embed」を使ってファイルの読み込みをしてみました!
— やぎぬ?行動力エンジニア (@yagi_eng) March 15, 2021
メッセージが定義されたjsonファイルを読み込んでみたのですが、結構いい感じに使えて便利でした
Go1.16の新機能が気になる方やファイル読み込みに悩んでいる方は読んでみてください?https://t.co/SIp6KqJsplまた、BOT開発を通じてGoとLINE BOTにまとめて入門する記事をZennに掲載していますので、良かったらそちらもご覧ください!
ZennでGoとLINE BOTの記事を書いてみました
— やぎぬ?行動力エンジニア (@yagi_eng) November 7, 2020
⬇️BOT開発を通じてGoとLINE BOTにまとめて入門するhttps://t.co/QqsEESJMKa
5ステップに分け、「Hello Worldから始めて、飲食店検索ができるLINE BOTの実装まで」を解説しています
GoやLINE BOTに興味のある人は是非読んでみてください?
24,000字超え?参考
- 投稿日:2021-03-15T10:07:17+09:00
RTPで受け取った動画をgolangで画像処理したい
の続き。
概要
動画を受け取り、動画から受け取ったフレームをある間隔で画像処理するようなアプリケーションをgolangで書きたい。
画像処理ライブラリとしてopencvを利用する必要があり、ラッパーとしてgocvを利用した。
試行錯誤した結果 opencv.OpenVideoCaptureという関数のバックエンドとしてgstreamerが使用できるという記事があり、試してみた。
構造
実際にはRTPの前にはWebRTCがあり、今回はSkyway WebRTC Gatewayを用いてWebRTC->RTPに変換している。
WebRTC周りについては別の機会に。
RTPを受け取る方法にはいろいろあると思うが、今回はopencvのgstreamer backendを使用した。この機能はgocvでは最新(0.26)でしか動作しないため、バージョンは要確認。
module version golang 1.15.x opencv 4.5.1 gocv 0.26.0 gstreamer 1.0.0 実装
videocapture.go
package middleware import ( "context" "fmt" "time" "github.com/pkg/errors" "go.uber.org/zap" "gocv.io/x/gocv" ) // Handler function of VideoCaptureServer type VideoCaptureHandler func(ctx context.Context, img *gocv.Mat) error // VideoCaptureServer is server to handle gocv.OpenVideoCapture image type VideoCaptureServer struct { handler VideoCaptureHandler pipeline string duration time.Duration } func NewVideoCaptureServer(handler VideoCaptureHandler, pipeline string, duration time.Duration) *VideoCaptureServer { return &VideoCaptureServer{ handler: handler, pipeline: pipeline, duration: duration, } } // Start start server func (s *VideoCaptureServer) Start(ctx context.Context) error { cap, err := gocv.OpenVideoCapture(s.pipeline) if err != nil { return errors.Wrap(err, "OpenVideoCapture failed") } defer cap.Close() ticker := time.NewTicker(s.duration) defer ticker.Stop() img := gocv.NewMat() for { select { case <-ticker.C: if err := s.handler(ctx, &img); err != nil { logger.Error("exec handler failed", zap.Error(err)) } case <-ctx.Done(): return nil default: if ok := cap.Read(&img); !ok { logger.Error("cap.Read failed") } } } }処理の内容はVideoCaptureHandlerとして関数インターフェースのみ定義し、動画を受け取って処理に引き渡す周りのコードをVideoCaptureServerとして定義。
videocapture_test.go
package middleware import ( "context" "fmt" "testing" "time" "gocv.io/x/gocv" ) func TestVideoCaptureServer(t *testing.T) { ctx := context.Background() window := gocv.NewWindow("test") handler := func(ctx context.Context, img *gocv.Mat) error { window.IMShow(*img) // Xサーバーにテスト画像を表示 window.WaitKey(1) return nil } pipeline := `udpsrc port=8888 caps="application/x-rtp,media=video,clock-rate=90000,encoding-name=H264" ! rtph264depay ! avdec_h264 ! videoconvert ! appsink` //pipelineを使ってOpenVideCaptureを実行し、1秒おきにhandler関数を実行 sv := NewVideoCaptureServer(handler, pipeline, time.Second) sv.Start(ctx) }こちらがテスト。
- Xサーバーにテスト用画像を表示するので、動作させるためには事前にX Serverを起動しておく必要がある。
- gst pipelineでudp port=8888で動画を受け取るように書いているが、mp4ファイルを使ったテストをしたければpipeline文字列を入れ替えるだけで良い。
評価
環境構築に難しい面はあるものの、gocv.OpenVideoCapture関数さえ使いこなせれば簡単にRTPのようなプロトコルを受け取る事が可能で、そこからの処理はほかのサーバーとそう変わらず、難しくないと感じた。
何らかの動画をgolangで受け取るような要件がある場合は、候補になると思う。
なお、pipelineの動作については、事前にgst-launchを使って確認しておくこと。appsinkを使った時のデバッグメッセージは分かりにくいと感じました・・・
- 投稿日:2021-03-15T08:12:23+09:00
JWT を Go で処理するためのパッケージ
Go 言語で JWT (JSON Web Tokens) を処理したい。
以前に書いたコードを見直す機会があって調べたら状況が変化していたので記録に残しておきます。特に記載がない限り 2021 年 3 月 14 日時点の情報になります。
調査対象
- https://github.com/dgrijalva/jwt-go
- https://github.com/square/go-jose
- https://github.com/lestrrat-go/jwx
このうち実際に使ったことがあるのは dgrijalva/jwt-go と square/go-jose です。
dgrijalva/jwt-go
最終リリース: v3.2.0 (2018 年 3 月 9 日)
dgrijalva/jwt-go (fork を含む) を使用しているリポジトリの例
- https://github.com/auth0/go-jwt-middleware
- https://github.com/etcd-io/etcd
- https://github.com/go-kit/kit
- https://github.com/ory/hydra
Go で JWT を処理する場合の定番だと思います。
古くからあるパッケージで GCP 向けの拡張 も公開されているので重宝していました。ただし、2020 年 1 月 7 日を最後にリポジトリは更新されてません。
fork したうえで脆弱性 (CVE-2020-26160) 1 を修正したリポジトリも公開されておりこちらに切り替えているケースもあるようです。 2 3
dgrijalva/jwt-go を使用している場合は form3tech-oss/jwt-go に移行したほうがよさそうです。
I/F は変わっていないので import を書き換えるか go.mod の replace を指定すれば移行できるはずです。square/go-jose
最終リリース: v2.5.1 (2020 年 5 月 1 日)
square/go-jose を使用しているリポジトリの例
- https://github.com/coreos/go-oidc
- https://github.com/docker/hub-tool
- https://github.com/HashiCorp/vault
- https://github.com/kubernetes/kubernetes
- https://github.com/ory/hydra
Ed25519 や JWK (JSON Web Key) を利用したくて見つけました。
ただし、現時点では master からコードがすべて削除されています。 4 5
v3 を開発するためにリポジトリを https://github.com/go-jose/go-jose に移行したようですがこちらも更新されていないようです。※ master は v3 を開発していたブランチですので v2 を利用している場合は影響を受けません。
lestrrat-go/jwx
最終リリース: v1.1.5 (2021 年 3 月 12 日)
dgrijalva/jwt-go や square/go-jose の Issue コメントで紹介されていました。
機能も豊富で継続的にメンテナンスされているようですが Ed25519 サポートは実験的という扱いになります。
go.mod で Go 1.15 以上を要求しているため環境によっては使用できないかもしれません。まとめにならないまとめ
メンテナンスされなくなったことに (自分が) 全く気が付いていなかったことに驚きました。
依存パッケージ更新は Dependabot の力を借りたりしているのですがそもそも更新されていないので気が付くこともなく。問題なく動いているように見えてもたまに既存処理を見直すことは重要かもしれません。