20190503のGoに関する記事は6件です。

Dockerで立ち上げた開発環境をVS Codeで開く!

待望のリモート開発機能がやってきました!

発表記事はこちら 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 が必要なので、開きます。

スクリーンショット 2019-05-03 14.23.22.png

Remote - Containers のExtensionを Insiders にインストール。 (まだ発表されたばかりで表示順序が低いのでスクロールして探す)

スクリーンショット 2019-05-03 14.35.12.png

開発リポジトリの設定

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 で開きます。

開いたら、左下の緑のアイコンをクリック。
スクリーンショット 2019-05-03 17.03.58.png

Remote-Containers: Open Folder in Container... を選択します。

スクリーンショット 2019-05-03 17.04.10.png

remote-dev-go を Clone した先のディレクトリを開きます。

開くと、 docker-compose の build が走るのでしばらく待ちます。

スクリーンショット 2019-05-03 17.27.34.png

これで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を追加することが可能です。

スクリーンショット 2019-05-03 17.47.44.png

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.yml
version: '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-compose
docker-compose -f /元々の/docker-compose.yml -f /リモート開発環境用の/docker-compose.extend.yml up -d --build

まとめ

これで開発のための環境をローカルに構築しなくても、 Docker と VSCode がインストールしてあれば開発出来るようになりそうです。
VS Code は Code Server として動作させ、ブラウザから操作するということも出来るようになってきているようなので、組み合わせれば Docker とブラウザさえあればどんな開発環境でもローカル環境を汚すことなく起動できるようになるかもしれません。

また、発表当日からドキュメントExamplesの充実具合が凄いです。
この開発リソースの多さが VS Code の強みと言えると思います。

参考

リモートサーバーにSSHする場合の記事もありました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【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 2019

Beegoにはファイルを生成してくれる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.go
package 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名"
  }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Goのnet/httpパッケージ周りについて最低限覚えておきたいポイント

動機

Goのnet/httpパッケージについて、なんとなく雰囲気を理解していた程度だったので、まずは最低限を覚えておきたいポイントをまとめる趣旨で、あれこれ調べたことをまとめてみた。

net/httpパッケージ

Goが標準で用意している、HTTPクライアント/サーバー機能を提供するパッケージです。

一方、巷ではbeegoginといったサードパーティ製の人気フレームワークが乱立していて、デファクトスタンダートは決まっていない模様。
何れにせよ、フレームワークを使うと何が嬉しいのか知りたいので、この次のステップとして使ってみたいところです。

HTTPサーバーの起動

周知の通り、Goではnet/httpパッケージを使えば簡単なHTTPサーバーを起動させることが出来ます。

環境構築を除けば、下記の通りわずか数行程度で済むので、手間の観点だけで見ればApachやNginxよりもリーズナブルな気がします。
なので、簡単なAPIサーバーを作る時に便利かもしれないです。(個人的見解)

main.go
package 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 intact

net/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

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Go言語での初期化における&とnewの挙動の違い

問題提起

Goでは&とnewの初期化に関して、一行で初期化できるかどうか以外の違いは無いという文面をよく見ますが本当にそうでしょうか?

考察

goプログラムによる検証

hoge.go
package 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

と、パニックが起きてしまいます。
一つずつ挙動を追ってみましょう。hogefugaそれぞれ宣言の後に以下の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以前)はこのようになっています。
スクリーンショット 2019-05-03 16.37.29.png
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.go
package 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:[]]
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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.go
package 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]にあります。
スクリーンショット 2019-05-03 11.23.20.png

実行

ではローカルのターミナル上で動かしてみます。

$ go run slack_bot.go
2019/05/03 11:03:27 Hello Event

Hello Eventが表示されれば成功です。
実際につくったbotに対してメッセージを送ってみます。
スクリーンショット 2019-05-03 11.31.48.png

ちゃんと返ってきました!

終わりに

goqueryは欲しいオブジェクトの検索条件を指定するだけで勝手にとってきてくれるので非常に便利でした。
複数のサイトから情報を取得する場合であってもGoお得意の並行処理で効率的に処理したりできそうですね。
slack-botも拡張性が高く、工夫次第で様々なところで効率化ができそうです。

参考

Goとgoqueryでスクレイピング

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

登録ワードで Bing News を一括検索する Go 製 CLI ツールを作った

先日の Ruby 会議 2019 で音楽好きの同志 あらたま(@ar_tama)さん が Ruby でニュース検索 Slack Bot を作った話で LT をしていました。

快適なミュージックライフを支える小さなBotの話

好きなアーティストの来日公演を見逃さないために”いい感じに”情報 push してくれる Bot が欲しかったという話で、僕も公演情報を見逃して涙することがあり共感しかありません。(現状、常に手動巡回)

せっかくなので真似して手を動かしながら対策を考えたかったのと、ニュース検索 Bot が GW の素振りによさそうだったので定期実行はひとまず無しにして、Go で Bing News Search API を叩いて返す CLI ツールをざっくり作ってみた。

こんな感じ。

render1556812069556.gif

検索対象単語を登録・削除し、 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

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む