- 投稿日:2019-05-03T18:22:30+09:00
Dockerで立ち上げた開発環境をVS Codeで開く!
待望のリモート開発機能がやってきました!
Introducing Remote Development for @code ???️
— Visual Studio Code (@code) 2019年5月2日
A new set of extensions that enable you to open any folder in a container, on a remote machine, or in the Windows Subsystem for Linux (WSL) and take advantage of VS Code's full feature set. #remote
? https://t.co/ChYGQ89Y5f発表記事はこちら Remote Development with VS Code
この機能を使うと、以下の3つの環境にVSCodeからリモート接続して開発することができるようになります。
- Docker上で動作しているWorkspace
- SSHで接続可能なリモートサーバーで動作しているWorkspace
- WindowsのWSLで動作しているWorkspace
Docker開発環境への接続を試す
個人的に一番欲しかった Docker への接続をGoの開発環境に追加する形で試してみます。
試した環境
- macOS Mojave 10.14.4
- Docker Desktop 2.0.0.3
- Docker Engine 18.09.2
- VS Code 1.34.0-insider
VS Code の設定
2019/05/03時点で試すには、 Visual Studio Code - Insiders が必要なので、開きます。
Remote - Containers のExtensionを Insiders にインストール。 (まだ発表されたばかりで表示順序が低いのでスクロールして探す)
開発リポジトリの設定
Quick Start として各開発環境をDockerで動かす例が用意されているので、参考にしつつ進めていきます。
https://github.com/Microsoft/vscode-remote-try-go を参考にしますが、docker-compose ベースではなかったので、docker-composeが使えるように少し変更を加えたものを用意したので、その環境をまず動かしてみましょう。
git clone https://github.com/yoskeoka/remote-dev-goいつもならこのCloneしたディレクトリに移動するところですが、このディレクトリを Remote - Containers Extension で開きます。
Remote-Containers: Open Folder in Container...
を選択します。
remote-dev-go
を Clone した先のディレクトリを開きます。開くと、 docker-compose の build が走るのでしばらく待ちます。
これでDocker内部で動作している開発環境(これを
リモート開発環境
といいます)にVS Code で接続出来ました。
通常通りに開発できますし、~/.gitconfig
をコピーしているので、Gitの操作もローカル開発環境と同じように出来ます。試しに Goのコードを動かしてみます。
https://github.com/Microsoft/vscode-remote-try-go から拝借したserver.go
を起動して、自動リロードがかかるようにMakefile
を用意しているので、それを実行します。VS Codeの
TERMINAL
タブで bash を起動して make を実行します。リモート開発環境$ make start reflex -g '**/*.go' -s -- sh -c "make build && ./bin/server" [00] Starting service [00] make[1]: Entering directory '/root/workspace' [00] go build -o bin/server server.go [00] make[1]: Leaving directory '/root/workspace' [00] Server listening on port 9080実行したものは
docker-compose.yml
に Port Forward の設定をしているため、ローカル開発環境からでもcurl
等で実行できます。ローカル開発環境$ curl localhost:9080 Hello remote world!
Git 操作
Tips にある
.gitconfig
の共有を行なっているため、リモート開発環境から、 VS CodeのGit統合機能で操作することも、git
コマンドで操作することも出来ます。リモート開発環境の設定を変更した場合
リモート開発環境に接続している場合は、左下の緑ボタンから
Remote Containers: Rebuild Container
を選択して反映することができます。リモート開発環境へ再接続する
VS Code を開いた状態で、 左下の緑アイコンまたは、コマンドパレットから
Remote-Containers: Open Folder in Container...
を選択して、remote-dev-go リポジトリのディレクトリを再度開きます。リモート開発環境を完全に停止する
docker-compose で起動しているため、
docker ps
などで Docker Container を削除して下さい。$ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES dd24fb7b0c8a remote-dev-go_remote-dev-go "sleep infinity" 25 minutes ago Up 25 minutes 0.0.0.0:9080->9080/tcp remote-dev-go_remote-dev-go_1 f9f88e005b5a phpmyadmin/phpmyadmin "/run.sh supervisord…" About an hour ago Up About an hour 9000/tcp, 0.0.0.0:8080->80/tcp remote-dev-go_phpmyadmin_1 93a095708b76 mysql:8 "docker-entrypoint.s…" About an hour ago Up About an hour 0.0.0.0:3306->3306/tcp, 33060/tcp remote-dev-go_mysql_1リモート開発環境の管理
VSCode Extensions の管理
リモート開発環境は ローカル開発環境とは別で VS Code Extensionsを追加することが可能です。
settings.json の管理
.devcontainer/settings.vscode.json
の内容をコピーする記述がリモート開発環境の.devcontainer/Dockerfile
にあります。今回用意している例では、 GOPATHをVS Codeに明示するために以下のようになっています。
.devcontainer/settings.vscode.json{ "go.gopath": "/go" }開発用のコマンドの管理
Goで開発しているサーバーをコードの変更を検知して自動リロードをかけるための
reflex
コマンドのインストールを、.devcontainer/Dockerfile
に記述しています。
必要に応じてDockerfile
に使用するコマンドのインストールを追記していけば、開発チーム内で環境の共有が簡単に出来そうです。既存の開発用コンテナとの共存
コンテナベースでのデプロイのための
Dockerfile
や、ローカル開発環境でNginx, MySQL等の依存をdocker-compose
を使って管理している場合は多いと思います。
自分の普段開発している環境でもdocker-compose.yml
で色々と立ち上げているので、その辺とうまく共存できるようにリモート開発環境が立ち上がるように設定を導入してみました。具体的には、プロジェクトルートに元々ある(という想定の)
docker-compose.yml
には一切リモート開発環境関連の記述を入れず、.devcontainer/docker-compose.extend.yml
にリモート開発環境の記述をしています。.devcontainer/docker-compose.extend.ymlversion: '3' services: dev: build: context: .devcontainer dockerfile: Dockerfile volumes: - .:/root/workspace - ~/.gitconfig:/root/.gitconfig ports: - 9080:9080 command: sleep infinityこの追加の Docker Compose 設定については、
.devcontainer/devcontainer.json
に.devcontainer/devcontainer.json(一部)"dockerComposeFile": [ "../docker-compose.yml", "docker-compose.extend.yml" ],のように書いておくことで、リモート開発環境の起動時に次のように両方をうまく読み込んでくれるため、既存のDocker関連の設定を壊す必要はありません。
リモート開発環境起動時に実行されるdocker-composedocker-compose -f /元々の/docker-compose.yml -f /リモート開発環境用の/docker-compose.extend.yml up -d --buildまとめ
これで開発のための環境をローカルに構築しなくても、 Docker と VSCode がインストールしてあれば開発出来るようになりそうです。
VS Code は Code Server として動作させ、ブラウザから操作するということも出来るようになってきているようなので、組み合わせれば Docker とブラウザさえあればどんな開発環境でもローカル環境を汚すことなく起動できるようになるかもしれません。また、発表当日からドキュメントとExamplesの充実具合が凄いです。
この開発リソースの多さが VS Code の強みと言えると思います。参考
リモートサーバーにSSHする場合の記事もありました。
- 投稿日:2019-05-03T18:11:05+09:00
【Go言語のフレームワークBeego】Migrationの使い方
Migrationの使い方
この記事の内容
- BeegoのCLIツールでのMigrationファイルの作成
- Migrationファイルの記載
- BeeのコマンドからMigrateを実行する
- BeeのコマンドからMigrateのrollbackやreset、reflesh
0.準備
Beego等のバージョンはこんな感じ
______ | ___ \ | |_/ / ___ ___ | ___ \ / _ \ / _ \ | |_/ /| __/| __/ \____/ \___| \___| v1.10.0 ├── Beego : 1.11.2 ├── GoVersion : go1.12.2 ├── GOOS : linux ├── GOARCH : amd64 ├── NumCPU : 2 ├── GOPATH : /go ├── GOROOT : /usr/local/go ├── Compiler : gc └── Date : Friday, 3 May 2019Beegoにはファイルを生成してくれる
bee
コマンドを提供してくれているので使います。
使えない人は下記をインストールしておきます。
go get -u github.com/beego/bee
※gomoduleを使ってる人は下記かな?
GO111MODULE=off get -u github.com/beego/bee
1.migrationファイルの生成
bee generate migration テーブル名
を実行するとdatabase/migrations
フォルダがプロジェクト直下に作成され、中にmigrationファイルが生成される$bee generate migration shikaku_mst 2019/05/03 07:21:47 INFO ▶ 0001 Using 'shikaku_mst' as migration name create /go/src/プロジェクト/database/migrations/20190503_072147_shikaku_mst.go 2019/05/03 07:21:47 SUCCESS ▶ 0002 Migration successfully generated!2.Migrationファイルの記載
作成されたファイルの内容を編集する
20190503_072147_shikaku_mst.gopackage main import ( "github.com/astaxie/beego/migration" ) // DO NOT MODIFY type ShikakuMst_20190503_072147 struct { migration.Migration } // DO NOT MODIFY func init() { m := &ShikakuMst_20190503_072147{} m.Created = "20190503_072147" migration.Register("ShikakuMst_20190503_072147", m) } // Run the migrations func (m *ShikakuMst_20190503_072147) Up() { // use m.SQL("CREATE TABLE ...") to make schema update // ここにmigrate実行時の内容を記載 `CREATE TABLE user(id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(255))`) } // Reverse the migrations func (m *ShikakuMst_20190503_072147) Down() { // use m.SQL("DROP TABLE ...") to reverse schema update // ここにrollback実行時の内容を記載 m.SQL("DROP TABLE shikaku_mst") }3.BeeのコマンドからMigrateを実行する
下記のコマンドでmigrationファイルに記載されている内容からMigrateを実行する
`$bee migrate -conn="user:password@tcp(接続先IP:ポート)/DB名"
DefaultのドライバーはMySQLになる$bee migrate -conn="user:password@tcp(接続先IP:ポート)/DB名" ______ | ___ \ | |_/ / ___ ___ | ___ \ / _ \ / _ \ | |_/ /| __/| __/ \____/ \___| \___| v1.10.0 2019/05/03 08:21:23 INFO ▶ 0001 Using 'mysql' as 'driver' 2019/05/03 08:21:23 INFO ▶ 0002 Using '/go/src/XXXX/database/migrations' as 'dir' 2019/05/03 08:21:23 INFO ▶ 0003 Running all outstanding migrations 2019/05/03 08:21:28 INFO ▶ 0004 |> 2019/05/03 08:21:26.192 [I] start upgrade ShikakuMst_20190503_072147 2019/05/03 08:21:28 INFO ▶ 0005 |> 2019/05/03 08:21:26.192 [I] exec sql: CREATE TABLE user(id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(255)) 2019/05/03 08:21:28 INFO ▶ 0006 |> 2019/05/03 08:21:26.224 [I] end upgrade: ShikakuMst_20190503_072147 2019/05/03 08:21:28 INFO ▶ 0007 |> 2019/05/03 08:21:26.224 [I] total success upgrade: 1 migration 2019/05/03 08:21:28 SUCCESS ▶ 0008 Migration successful!これでDBの構築は完了する。
4.BeeのコマンドからMigrateのrollbackやreset、reflesh
BeegoのMigrateでは、DBにMigrationsテーブルを作成しそれぞれのMigrationファイルの実行状況ステータスを記録している。なにかあれば、Migrationsテーブルのレコードを削除すればステータスは初期化出来る。
・ロールバック
下記コマンドで最後に実行したmigrateをロールバックすることが出来る。
bee migrate rollback -conn="user:password@tcp(接続先IP:ポート)/DB名"
・リセット
下記コマンドですべての実行したmigrateをロールバックすることが出来る。
bee migrate reset -conn="user:password@tcp(接続先IP:ポート)/DB名"
・リフレッシュ
下記コマンドですべての実行したmigrateをロールバックし、再びMigrateすることが出来る。
bee migrate refresh -conn="user:password@tcp(接続先IP:ポート)/DB名"
おまけ
いちいち接続情報を記載するのがめんどくさい場合、beeコマンド用の設定情報は下記に記載可能
プロジェクト直下/bee.json
bee.json{ "database": { "conn": "user:password@tcp(接続先IP:ポート)/DB名" } }
- 投稿日:2019-05-03T18:06:38+09:00
Goのnet/httpパッケージ周りについて最低限覚えておきたいポイント
動機
Goのnet/httpパッケージについて、なんとなく雰囲気を理解していた程度だったので、まずは最低限を覚えておきたいポイントをまとめる趣旨で、あれこれ調べたことをまとめてみた。
net/httpパッケージ
Goが標準で用意している、HTTPクライアント/サーバー機能を提供するパッケージです。
一方、巷ではbeegoやginといったサードパーティ製の人気フレームワークが乱立していて、デファクトスタンダートは決まっていない模様。
何れにせよ、フレームワークを使うと何が嬉しいのか知りたいので、この次のステップとして使ってみたいところです。HTTPサーバーの起動
周知の通り、Goではnet/httpパッケージを使えば簡単なHTTPサーバーを起動させることが出来ます。
環境構築を除けば、下記の通りわずか数行程度で済むので、手間の観点だけで見ればApachやNginxよりもリーズナブルな気がします。
なので、簡単なAPIサーバーを作る時に便利かもしれないです。(個人的見解)main.gopackage main import ( "fmt" "net/http" ) func main() { http.HandleFunc("/", handler) http.ListenAndServe(":8080", nil) } func handler(w http.ResponseWriter, r *http.Request) { fmt.Fprint(w, "Hello World!") }コードを書いて
goコマンド
で実行してあげればサーバーが起動します。go run main go起動したら、ブラウザから
http://localhost:8080
にアクセスして、Hello World!と表示されていればOKです。動作確認
サーバー起動後、今度は
curlコマンド
でアクセスしてみると、こんな感じのレスポンスが帰って来ます。
このように、確りとサーバーとして動作していることを確認することもできます。$ curl -vvv localhost:8080 * Rebuilt URL to: localhost:8080/ * Trying ::1... * TCP_NODELAY set * Connected to localhost (::1) port 8080 (#0) > GET / HTTP/1.1 > Host: localhost:8080 > User-Agent: curl/7.54.0 > Accept: */* > < HTTP/1.1 200 OK < Date: Wed, 01 May 2019 00:40:58 GMT < Content-Length: 14 < Content-Type: text/plain; charset=utf-8 < Hello, world. * Connection #0 to host localhost left intactnet/httpのキホン
Goでサーバーを起動する際、肝になるポイントは次の2つになります。
- Handler
- Listen
Handler
Handlerとは、何らかのアクションをトリガーに実行される関数のことです。
ここでは、HTTPリクエストに対するレスポンスを実行するためのHandlerを意味します。Handler作成
まず、HTTPリクエストを受け取った時にレスポンスを実行するHandlerを作ります。
func handler(w http.ResponseWriter, r *http.Request) { fmt.Fprint(w, "Hello World!") }
func handler
(handlerは関数名)は、第一引数にhttp.ResponseWriter型
、第二引数にhttp.Request型
を取ります。ResponseWriter型はレスポンスを指定することが可能で、上記ではHello World!を出力する(wに入る文字列がブラウザで表示される)処理をレスポンスに指定しています。
このパターンはお作法みたいなものなので、覚えておきましょう。
Handler登録
作ったHandlerは、net/httpパッケージが用意している
http.HandleFunc関数
で登録します。http.HandleFunc("/", handler)第一引数にパス(URLパターン)を、第二引数にHandlerを定義することで、HTTPリクエストに対してレスポンスできるようになります。
上記の場合、パスを/で来たHTTPリクエストを返すhandler関数(=Handler)をハンドラとして定義しています。Listen
Listenとは、サーバーのポートが開いている状態、つまりHTTPリクエストを受け付ける状態にあることを意味します。
サーバー起動
Handlerの登録をしたら、net/httpパッケージが用意している
http.ListenAndServe関数
で、サーバーがHTTPリクエストを受け付けるようにします。http.ListenAndServe(":8080", nil)第一引数に、ポート番号を表す文字列を渡し、第二引数にHandlerを渡すのが一般的のようです。
Handlerは、自分でHandlerとその処理を実装する場合を除き、通常はnilを使います。この場合、DefaultServeMux
と呼ばれるデフォルトのHandlerが使用されます。ServeMuxに関しては、こちらの記事の説明が分かりやすかったので抜粋。
ServeMuxは、HTTPリクエストに対しての処理をPathごとに登録することができる構造体です。ServeMuxを使わなくても、HTTPリクエストを処理することはできますが、Pathごとの振り分けは余程のことがない限り必ず使うため、省略できるようになっています。
まとめ
- GoでHTTPサーバーを起動するときのポイントは、HandlerとListenの2つ。
- サーバー起動の流れは、Handler作成 -> Handler登録 -> サーバー起動(Listen)の3ステップ。
今回は、net/httpパッケージを用いた簡単なHTTPサーバーの起動のみでしたが、自作のHandlerを実装したり、APIを返すモックサーバを作ってみたりと、色々調べながら手を動かしてステップアップして行きたい。
参考
基本は全て公式ドキュメントに載っているので、ミニマム雰囲気だけでも掴んでおくのがベターだと思った。
https://golang.org/doc/articles/wiki/あとは、Go言語に詳しいメルカリ上田氏の資料が参考になると思います。
https://www.slideshare.net/takuyaueda967/goweb-69949279
- 投稿日:2019-05-03T17:23:51+09:00
Go言語での初期化における&とnewの挙動の違い
問題提起
Goでは&とnewの初期化に関して、一行で初期化できるかどうか以外の違いは無いという文面をよく見ますが本当にそうでしょうか?
考察
goプログラムによる検証
hoge.gopackage main import "log" type hello map[int][]int32 func main() { var key []int32 hoge := &hello{} log.Println(hoge) fuga := new(hello) log.Println(fuga) (*hoge)[0] = key log.Println(hoge) (*fuga)[0] = key log.Println(fuga) }上記コードの挙動を想像してください。もし同じなら以下のように出力されるはずです
yyyy/mm/dd H:i:s &map[] yyyy/mm/dd H:i:s &map[] yyyy/mm/dd H:i:s &map[0:[]] yyyy/mm/dd H:i:s &map[0:[]]実際には
yyyy/mm/dd H:i:s &map[] yyyy/mm/dd H:i:s &map[] yyyy/mm/dd H:i:s &map[0:[]] panic: assignment to entry in nil mapと、パニックが起きてしまいます。
一つずつ挙動を追ってみましょう。hoge
、fuga
それぞれ宣言の後に以下の2行を追加してみます。hoge.go//上記省略 hoge := &hello{} log.Println(hoge) log.Println(hoge == nil) //false log.Println(*hoge == nil) //false fuga := new(hello) log.Println(fuga) log.Println(fuga == nil) //false log.Println(*fuga == nil) //true //以下省略結果は横にコメントで表示してる通りです。
newで宣言された方でアドレスの先はnil
になっています。
goプログラムだけで挙動を追うのは不可能では無いですが流石に無理があるのでアセンブリを出力してみます。出力されたアセンブリによる検証
$ go tool compile -S -S hoge.go結果は一部の必要な部分のみ表示します。
0x0021 00033 (hoge.go:10) PCDATA $2, $1 0x0021 00033 (hoge.go:10) PCDATA $0, $0 0x0021 00033 (hoge.go:10) LEAQ type."".hello(SB), AX 0x0028 00040 (hoge.go:10) PCDATA $2, $0 0x0028 00040 (hoge.go:10) MOVQ AX, (SP) 0x002c 00044 (hoge.go:10) CALL runtime.newobject(SB) 0x0031 00049 (hoge.go:10) PCDATA $2, $1 0x0031 00049 (hoge.go:10) MOVQ 8(SP), AX 0x0036 00054 (hoge.go:10) PCDATA $2, $0 0x0036 00054 (hoge.go:10) PCDATA $0, $1 0x0036 00054 (hoge.go:10) MOVQ AX, "".hoge+32(SP) 0x003b 00059 (hoge.go:10) CALL runtime.makemap_small(SB) 0x0040 00064 (hoge.go:10) PCDATA $2, $1 0x0040 00064 (hoge.go:10) MOVQ (SP), AX 0x0044 00068 (hoge.go:10) PCDATA $2, $-2 0x0044 00068 (hoge.go:10) PCDATA $0, $-2 0x0044 00068 (hoge.go:10) CMPL runtime.writeBarrier(SB), $0 0x004b 00075 (hoge.go:10) JNE 520 0x0051 00081 (hoge.go:10) MOVQ "".hoge+32(SP), CX 0x0056 00086 (hoge.go:10) MOVQ AX, (CX) 0x0092 00146 (hoge.go:13) PCDATA $2, $1 0x0092 00146 (hoge.go:13) LEAQ type."".hello(SB), AX 0x0099 00153 (hoge.go:13) PCDATA $2, $0 0x0099 00153 (hoge.go:13) MOVQ AX, (SP) 0x009d 00157 (hoge.go:13) CALL runtime.newobject(SB) 0x00a2 00162 (hoge.go:13) PCDATA $2, $1 0x00a2 00162 (hoge.go:13) MOVQ 8(SP), AX 0x00a7 00167 (hoge.go:13) PCDATA $0, $3 0x00a7 00167 (hoge.go:13) MOVQ AX, "".fuga+40(SP)10行目は
hoge := &hello{}
13行目は
fuga := new(hello)
のコードです。
見てわかる通り全然違います。まぁ挙動が全く同じだったらnewと&の2つもいらないですからね。
では順に違う箇所を見ていきましょう。
まず前半(複合リテラルでのmakemap以前)はこのようになっています。
PCDATA命令や、最後のMOV命令の第二オペランド以外は一緒です。ここまではほぼおなじ挙動と言えます。
気になるところは、どちらもruntime.newobject
を呼び出しています。
このコードを追っていくとmallocgc
という関数が返されています。
よって、newobject
では、メモリ領域を確保し、確保したアドレスの番地を返していることが分かりました。
また、以下のコマンドを実行してみた結果より$ go build -gcflags -m hoge.go # command-line-arguments ./hoge.go:11:16: hoge escapes to heap ./hoge.go:10:13: &hello literal escapes to heap ./hoge.go:10:19: &hello literal escapes to heap ./hoge.go:14:16: fuga escapes to heap ./hoge.go:13:16: new(hello) escapes to heap ./hoge.go:17:16: hoge escapes to heap ./hoge.go:20:16: fuga escapes to heap ./hoge.go:11:16: main ... argument does not escape ./hoge.go:14:16: main ... argument does not escape ./hoge.go:17:16: main ... argument does not escape ./hoge.go:20:16: main ... argument does not escape確保されるメモリ領域がヒープであることも確認できます。
これはmallocgcの挙動でしょう。
ここで注意すべきは、newobject
では、メモリ領域を確保しているだけで、データは生成していない。という点でしょう。
これが、log.Println(*fuga == nil)
がtrueになる、ようは*fugaがnilである要因だと思います。では次に後半部です。こちらは複合リテラルでのみ存在する部分です。
0x003b 00059 (hoge.go:10) CALL runtime.makemap_small(SB) 0x0040 00064 (hoge.go:10) PCDATA $2, $1 0x0040 00064 (hoge.go:10) MOVQ (SP), AX 0x0044 00068 (hoge.go:10) PCDATA $2, $-2 0x0044 00068 (hoge.go:10) PCDATA $0, $-2 0x0044 00068 (hoge.go:10) CMPL runtime.writeBarrier(SB), $0 0x004b 00075 (hoge.go:10) JNE 520 0x0051 00081 (hoge.go:10) MOVQ "".hoge+32(SP), CX 0x0056 00086 (hoge.go:10) MOVQ AX, (CX)一番最初に
runtime.makemap_small
が呼ばれています。newobject
同様パラメータとして静的ベースレジスタが渡っています。
makemap_small
の挙動はこちらで確認できます。
分かりやすい関数名のおかげで、この関数でmapの実体を生成していることが推測できます。
また、runtime.writeBarrier
と0を比較して、00520にジャンプしたりなどの分岐や、MOV命令などいくつかありますが、やはりmakemap_small
の存在が&とnewの大きな違いとなっているでしょう。結論
今回考察に用いたコードを見るとわかるように、
hello
型はmap[int][]int32
型のエイリアスです。
また、&やnewは通常であれば構造体の初期化などに用います。
構造体と比べるとmapは特殊な型です。可変長の連想配列で、宣言時(0初期化前)に値は代入出来ません。
本記事で挙動の考察に至ったのも、元を辿るとこのmapの挙動によるものからです。
本来構造体であれば、makemap_small
はもちろん呼び出されませんし、&でもnewでも見かけ上同じ用に用いることができます。
ただ、扱うモノが構造体であっても、&とnewを比較した場合、newの方が高速に初期化できることが分かりました。newでは領域を確保してるだけだからです。最後に、一番最初のコードで
fuga
をpanicを起こす事なく使用したい場合は、以下のように、makemap_small
を呼び出せる方法で初期化してあげましょう。hoge.gopackage main import "log" type hello map[int][]int32 func main() { var key []int32 hoge := &hello{} log.Println(hoge) fuga := new(hello) log.Println(fuga) (*hoge)[0] = key log.Println(hoge) (*fuga) = map[int][]int32 {0 : key} log.Println(fuga) }
map[int][]int32 {0 : key}
で出力されるコード0x0166 00358 (hoge.go:19) CALL runtime.makemap_small(SB) 0x016b 00363 (hoge.go:19) PCDATA $2, $1 0x016b 00363 (hoge.go:19) MOVQ (SP), AX 0x016f 00367 (hoge.go:19) PCDATA $0, $7 0x016f 00367 (hoge.go:19) MOVQ AX, ""..autotmp_23+48(SP) 0x0174 00372 (hoge.go:19) PCDATA $2, $3 0x0174 00372 (hoge.go:19) LEAQ type.map[int][]int32(SB), CX 0x017b 00379 (hoge.go:19) PCDATA $2, $1 0x017b 00379 (hoge.go:19) MOVQ CX, (SP) 0x017f 00383 (hoge.go:19) PCDATA $2, $0 0x017f 00383 (hoge.go:19) MOVQ AX, 8(SP) 0x0184 00388 (hoge.go:19) MOVQ $0, 16(SP) 0x018d 00397 (hoge.go:19) CALL runtime.mapassign_fast64(SB) 0x0192 00402 (hoge.go:19) PCDATA $2, $6 0x0192 00402 (hoge.go:19) MOVQ 24(SP), DI 0x0197 00407 (hoge.go:19) XORPS X0, X0 0x019a 00410 (hoge.go:19) MOVUPS X0, 8(DI) 0x019e 00414 (hoge.go:19) PCDATA $2, $-2 0x019e 00414 (hoge.go:19) PCDATA $0, $-2 0x019e 00414 (hoge.go:19) CMPL runtime.writeBarrier(SB), $0 0x01a5 00421 (hoge.go:19) JNE 507 0x01a7 00423 (hoge.go:19) MOVQ $0, (DI) 0x01ae 00430 (hoge.go:19) MOVQ ""..autotmp_23+48(SP), AX 0x01b3 00435 (hoge.go:19) MOVQ "".fuga+40(SP), CX 0x01b8 00440 (hoge.go:19) MOVQ AX, (CX)出力結果
yyyy/mm/dd H:i:s &map[] yyyy/mm/dd H:i:s &map[] yyyy/mm/dd H:i:s &map[0:[]] yyyy/mm/dd H:i:s &map[0:[]]
- 投稿日:2019-05-03T11:58:11+09:00
Go言語でHTMLをスクレイピングして電車の遅延情報を�教えてくれるSlack-botをつくる
GoとSlackのbotを組み合わせて勉強をしてみたかったので関東の電車運行状況を取得するbotをつくってみました。
本当は勝手につぶやく方式にしたかったのですがとりあえずこの時点で記事にします。
(そのうち改造するかも。)Slack Appの作成
こちらのサイトを参照し作成しました。
GolangでSlack Interactive Messageを使ったBotを書くbotの作成
スクレイピングするパッケージのインストール
goqueryをインストールします。
jQuery的な操作が出来るので経験のあるWebエンジニアには割とすんなり理解できます。$ go get github.com/PuerkitoBio/goquery
情報を取得するWebサイト
こちらがないと始まりません。
今回はYahoo!路線情報さんから取得します。https://transit.yahoo.co.jp/traininfo/area/4/
実装
では実際にコーディングしてアプリをつくります。
「運行状況」というキーワードに反応して上記サイトから運行状況を返すbotです。slack_bot.gopackage main import ( "log" "os" "net/url" "github.com/nlopes/slack" "github.com/PuerkitoBio/goquery" ) // HTMLをスクレイピングして運行状況のテキストを取得する func getRailwayText() string { _url := "https://transit.yahoo.co.jp/traininfo/area/4/" doc, err := goquery.NewDocument(_url) if err != nil { panic(err) } u := url.URL{} u.Scheme = doc.Url.Scheme u.Host = doc.Url.Host var message string // まずはテーブル要素を取得する doc.Find("div.trouble > table > tbody").Each(func(_ int, s *goquery.Selection) { // 次にtrを掘っていく s.Children().Each(func(idx int, ss *goquery.Selection) { // idx==0は見出し列なので飛ばす if (idx > 0) { railway := ss.Children().Find("a").Text() status := ss.Children().Find("span.colTrouble").Text() detail := ss.Children().Next().Next().Text() message = message + status + " @ " + railway + " (" + detail + ")\n" } }) }) return message } func run(api *slack.Client) int { rtm := api.NewRTM() go rtm.ManageConnection() for { select { case msg := <-rtm.IncomingEvents: switch ev := msg.Data.(type) { case *slack.HelloEvent: log.Print("Hello Event") case *slack.MessageEvent: switch ev.Text { case "運行状況": message := getRailwayText() rtm.SendMessage(rtm.NewOutgoingMessage(message, ev.Channel)) case "help": rtm.SendMessage(rtm.NewOutgoingMessage("Usage: 運行状況 関東圏の電車の運行状況を表示します。\n 他の機能?ないよ。", ev.Channel)) } case *slack.InvalidAuthEvent: log.Print("Invalid credentials") return 1 } } } } func main() { api := slack.New("YOUR TOKEN") os.Exit(run(api)) }トークンはSlack APIページの左メニュー[OAuth & Permissions]の[Bot User OAuth Access Token]にあります。
実行
ではローカルのターミナル上で動かしてみます。
$ go run slack_bot.go 2019/05/03 11:03:27 Hello Event
Hello Eventが表示されれば成功です。
実際につくったbotに対してメッセージを送ってみます。
ちゃんと返ってきました!
終わりに
goqueryは欲しいオブジェクトの検索条件を指定するだけで勝手にとってきてくれるので非常に便利でした。
複数のサイトから情報を取得する場合であってもGoお得意の並行処理で効率的に処理したりできそうですね。
slack-botも拡張性が高く、工夫次第で様々なところで効率化ができそうです。参考
- 投稿日:2019-05-03T00:46:08+09:00
登録ワードで Bing News を一括検索する Go 製 CLI ツールを作った
先日の Ruby 会議 2019 で音楽好きの同志 あらたま(@ar_tama)さん が Ruby でニュース検索 Slack Bot を作った話で LT をしていました。
好きなアーティストの来日公演を見逃さないために”いい感じに”情報 push してくれる Bot が欲しかったという話で、僕も公演情報を見逃して涙することがあり共感しかありません。(現状、常に手動巡回)
せっかくなので真似して手を動かしながら対策を考えたかったのと、ニュース検索 Bot が GW の素振りによさそうだったので定期実行はひとまず無しにして、Go で Bing News Search API を叩いて返す CLI ツールをざっくり作ってみた。
こんな感じ。
検索対象単語を登録・削除し、 Bing News Search API を叩いて整形して出力する単純な CLI ツール。これで一瞬で欲しいニュースが一覧で見られて捗るってもんです。(てか、ロイ・エアーズの緊急搬送これで知った。大丈夫かロイじいさん!)
LT で紹介されていた Bing News Search API は今まで使ったことがなかったので試してみたけれど、3,000 無料トランザクション/月で、API Key を含めて REST API を叩くだけの簡単なもので、参考実装も Ruby, Go, C#, Java, PHP, Python, Node.js のサンプルコードがありパパッと組み込めるのが良かった。別に SDK もあるらしい。
このCLIをベースにサーバーレスで動かす実装を追加して Slack Bot 化していきます。
ところで、WEB 開発の素振りネタに悩むことが時々あったけれど、登録単語で 検索系 API を叩いて表示するツールは TODO アプリと同様にコンパクトで出力先も多数考えることができるし、実装も色々幅が考えられるので今後も素振りに使っていきたい。
おすすめでやんす。
コードはこちら
kzkick2nd/gonews-cli: Find articles from Bing News with registered keywords.Bing News Search API