golang.tokyo #4に行ってきた。 #golang #golangtokyo
inabaです。
golang.tokyo #4に補欠からの当日滑り込み繰り上がりという運の良さで参加してきました。
会場はペアーズの株式会社エウレカさん。
倍率が2倍ということもあり、狭き門という印象のgolang.tokyoですが、今回から動画撮影も始まったようです。
次回のgolang.tokyoの前あたりに公開されるそうです。
自分も#2,#3と2回補欠だったのでこれは助かりますね!!
ご紹介ありがとうございます!
— crash_academy (@crash_academy) 2017年3月1日
後日https://t.co/Z00WxR8ZGcにて配信させていただきます。
よろしくお願いします|д゚)b
#golangtokyo
また、@tenntennさんからブログ枠の紹介がありました。
毎回3名分のブログ枠が用意されていて、
- 先着順なので参加確定
- まとめることで頭の中の整理になる
- connpassの資料のページに掲示されるので多くの人にブログを見てもらえる
のが主なメリットだそうです。
案外ブログ枠って人気ないのか。確定で参加できるし、まとめることによって勉強になって、しかもconnpassに貼られるので閲覧数伸びて美味しいのに。
— tenntennʕ ◔ϖ◔ʔ ==Go (@tenntenn) 2017年2月22日
今回はConcurrency(コンカレンシー、並行性)がテーマでした。
今回はブログ枠で参加ではないですが、ブログ枠で参加するときのためにまとめてみます。
Concurrency for distributed Web crawlers
WebクローラーのためのGo言語の話です。
- アプリのデータをストアからクローラーで24時間毎に取ってくる。
- 単一のサイト(シングルドメイン)なので間隔が短いとBANされる可能性がある。
- 10万以上のアプリのデータをクロールする。
- 画像のダウンロードがあるので1回のクロール時間が異なってくる。
- コマンダーとクローラーから構成されるクロールプログラム。
- 1回のクロール時間が異なるので、例えば100回クロールする2個のクローラーがあっても直列で実行すると処理終了時間がまちまちになる。
- Goroutineを使って時間をずらして並行に実行していくと実行時間が同じになる。
- SlackBotとしても動いている。
- クローラのステータスを通知させる目的。
- 普通のSlackBotは1回コマンドを打つと1回返答が返ってくる。
- これだと何回もコマンドを打たないとならなくて面倒。
- クローラーに実装されているSlackBotはSlackの編集機能を使う。
- コマンドを受けた最初の投稿は普通に。
- 次の投稿から編集機能を使用し、最初の投稿を編集する。
- 普通の投稿か編集かの判断をしてPostをするGoroutineと投稿する文字列を生成するGoroutineが分けてあり、chanで通信している。
- 並行処理時のTCPコネクションの枯渇問題
- DBからデータを吸い出すのも並行で行っている。
- 何らかの理由でqueryが遅く、並行処理が重なってしまいTCPコネクションを使い果たしてエラーが出ることがある。
- それを防ぐため、バッファサイズを指定したchan(
make(chan int, 100)
)を使用して一度に処理する回数を制限する。 - 制限中にGoroutineが1つ終了するとブロックされていたchanのブロックが解除され、次のGoroutineが動作する仕組み。
Slackの編集機能をBotに使わせるというアイデアには「その手があったか!」と目からウロコでした!
Goのスケジューラー実装とハマりポイント
井手 康貴さん (@niconegoto) / 株式会社メルカリ/株式会社ソウゾウ
https://talks.godoc.org/github.com/niconegoto/talks/concurrency.slide#1
参考記事: qiita.com
The Go scheduler - Morsing's blog
- GoroutineはOSスレッドに対して多対多。
- 1対1、多対1に比べて全てのコアを使えてコンテキストスイッチも高速でいいとこ取り。
- ただし、スケジューラーへの追加が煩雑になる。
- Goはこの煩雑さをラップしていて、簡単(
go f()
と書くだけ。)にGoroutineが使えるようになっている。
- Goはこの煩雑さをラップしていて、簡単(
- 用語(runtimeパッケージ以下を読む時に覚えておくと役に立つ。)
- M(Machine): OSスレッド
- G(Goroutine): ゴルーチン
- P(Processor): プロセッサ。4コアなら4
- Pは自分にスケジューリングされたGが無くなると、別のPからGを半分奪う。
- これにより、全てのスレッドが最大パフォーマンスで動作できる。
- runtime.GOMAXPROCS()が1でもGoは複数のスレッドで実行される。
- ハマりどころ
- ループしてGoroutine外の変数を参照する場合。
- runtime.GOMAXPROCS()が大きいほど切り替わりやすくなる。
今までなんとなく使っていたgo f()
の仕組みがとてもわかりやすく理解できました。
Ridge - A framework like GAE/Go on AWS
藤原 俊一郎さん (@fujiwara) / 株式会社カヤック
- 下記を使用
- API Gateway × Lambdaのデメリット
- 独自オブジェクト
- ルーティング・・・
- テスト・・・
- Goで書きたい!
- Ridgeが行う事
- 流れはリクエスト→API Gateway→AWS Lambda→APEX→Go(Ridge→Handler)
- メリット
- できないこと
- 同一名クエリ(foo=a&foo=b)や同一名ヘッダが受け取れない。
- バイナリ(画像等)が返せない。
- Goroutineを動かし続ける
- 1リクエスト10秒を超える処理
- 向いていない事
- 向いている事
- 頻繁にアクセスが無い。
- レイテンシ要求がシビアでない。
- ChatBotやゲームの終了告知(POSTを受けてJSONを返す)等
- ログはCloudWatch Logsに送られるが、それをLambdaに送ってRidgeでパースしている。
既存のWebアプリもちょこっといじってmux.HandleFuncを使うようにするだけでハンドラの中身を変えなくてもRidgeを使い始められるのがすごいですね!
制限はありますが、ChatBotくらいから試しに使ってみるのはいいかもしれません。
懇親会
途中、ピザとビールが出る懇親会がありました。
eureka さんでピザをいただいています #golangtokyo pic.twitter.com/NCamFSLvUX
— 禁じられたオタク (@__masalo23) 2017年3月1日
美味しかったです!ごちそうさまでした!!
Goを使ったtailコマンドの実装(ライブコーディング)
金子 慎太郎さん (@kaneshin0120) / 株式会社エウレカ
コード: https://gist.github.com/kaneshin/a398720b8e20722a83bc6903e4017435
発表予定だった牧さんが残念ながら都合がつかず来られなくなってしまったため、急遽かねしんさんがライブコーディングをすることに。
懇親会しながらも、みんなの目線はスクリーンに釘付けでした。
tenntennさんのツッコミも入りつつ、かなり盛り上がっていました。
嫁に怒られずにGoを書く技術
@teitei_tkさん / freee株式会社
- LINEBotを作る理由は日本で1番使われているチャットツールだから。
- 非エンジニア(嫁、家族)にも理解してもらいやすい。
- 必要な物
- ようはhttpsで外部からアクセスできるGoが使えるサーバがあればよい。
- これがLINEからの通知を受け取るためのサーバになる。
- 天気のWebAPI(LINEさん提供)があるので使う。
- 公式にオウム返しのサンプルがあるのでそれに手を加える。
- 作ったら家族のグループラインに招待する。
嫁だけでなく、非エンジニアの友人駆動開発にも使えますね。
嫁が居ないのでそんなことを考えてました(泣)
質問の時、tenntennさんが
「質問無いですか?」
「嫁はどうやって作るんですかとかでもいいですよ。」
「ペアーズ使いましょう!」
と自分でツッコんで会場が沸いてましたwww
Gogland
Sergey Ignatovさん (@sergey__ignatov) / JetBrains
JetBrains製のIDEの紹介です。
英語だったので必死に聞きましたが半分はツイッターの#golangtokyoを追っていました。
何件か気になるツイートを紹介します。
gogland、関数定義を参照するのにファイル開かなくても小窓で見えたりしてとても便利そう #golangtokyo
— パン粉 (@gohan_pan_gohan) March 1, 2017
implementsしているinterfaceの一覧が出せる #golangtokyo
— tenntennʕ ◔ϖ◔ʔ ==Go (@tenntenn) March 1, 2017
fmPrin で補完が出てくるのは流石だ #golangtokyo
— コキチーズ🔥 (@k2wanko) March 1, 2017
gogland から EAP が取れるのは年末あたり?とのこと #golangtokyo
— パン粉 (@gohan_pan_gohan) March 1, 2017
自分は、こういうのどうやって作るんだろうっていうのを考えながら見ていました。
これはgo/astとか使って作るのだろうか #golangtokyo
— 178inaba (@i178inaba) March 1, 2017
まとめ
ブログを書くのは結構大変だけど、頭の中が整理できるのでおすすめです。
長めに書いてしまったのは反省点で、今後はもうちょっとまとめられるようにしていきたいと思います。
運営の皆さんご苦労様でした!!
また参加させて頂きます!!
CentOS 7のファイアウォールで80番ポートを許可する
inabaです。
サーバにnginxをインストールして、localhostからはアクセスできるのに、外部(グローバルIPや設定したドメイン)からはアクセスできなくて困っている人はファイアウォールの設定を疑ってみるとよいです。
firewall-cmd
ファイアウォールを操作するコマンドです。
コマンドの使い方を全て覚えるのは大変なのでポート許可に必要な使い方を紹介していきます。
アクティブなゾーンを確認する
ファイアウォールは「事前に定義されたゾーンに対して、指定されたルールの通信を通す。」という事をしています。
なので、まずはどのゾーンへのファイアウォールが動いているかを確認します。
$ firewall-cmd --get-active-zones public interfaces: eth0
--get-active-zones
というオプションをつけると、現在アクティブなゾーンが表示されます。
上記だとpublicというゾーンがアクティブな事がわかります。
複数ある場合は、eth0が外部公開されているインタフェースなのでeth0が定義されているゾーンが外部向けポートを追加する対象になります。
許可されているポートの確認
次は先程確認したアクティブなゾーンpublicの確認です。
現在許可されているポートを確認します。
$ firewall-cmd --info-zone public public (active) target: default icmp-block-inversion: no interfaces: eth0 sources: services: dhcpv6-client ssh ports: protocols: masquerade: no forward-ports: sourceports: icmp-blocks: rich rules:
servicesには定義されているサービス(ルールのような物)が表示されます。
portsにはサービスとは別に開放されているポートが表示されます。
今回はportsには何も表示されていませんが、例えば8080番ポートが開放されていると、ports: 8080/tcpのように表示されます。
上の例だとservicesにdhcpv6-clientとsshが設定されています。
サービスの内容を確認します。
$ firewall-cmd --info-service dhcpv6-client dhcpv6-client ports: 546/udp protocols: source-ports: modules: destination: ipv6:fe80::/64 $ firewall-cmd --info-service ssh ssh ports: 22/tcp protocols: source-ports: modules: destination:
ports
に開放されているポートが表示されます。
dhcpv6-clientでは546番ポート、sshでは22番ポートが開放されています。
定義されているサービスを確認
サービスの一覧は--get-services
オプションで確認できます。
$ firewall-cmd --get-services RH-Satellite-6 amanda-client amanda-k5-client bacula bacula-client ceph ceph-mon dhcp dhcpv6 dhcpv6-client dns docker-registry dropbox-lansync freeipa-ldap freeipa-ldaps freeipa-replication ftp high-availability http https imap imaps ipp ipp-client ipsec iscsi-target kadmin kerberos kpasswd ldap ldaps libvirt libvirt-tls mdns mosh mountd ms-wbt mysql nfs ntp openvpn pmcd pmproxy pmwebapi pmwebapis pop3 pop3s postgresql privoxy proxy-dhcp ptp pulseaudio puppetmaster radius rpc-bind rsyncd samba samba-client sane smtp smtps snmp snmptrap squid ssh synergy syslog syslog-tls telnet tftp tftp-client tinc tor-socks transmission-client vdsm vnc-server wbem-https xmpp-bosh xmpp-client xmpp-local xmpp-server
結構量が多いのですが、この中にhttpというそれっぽいサービスを見つけることができます。
httpサービスの内容を確認します。
$ firewall-cmd --info-service http http ports: 80/tcp protocols: source-ports: modules: destination:
ビンゴです!
80番ポートがルールとして定義されています。
サービスをゾーンに追加
httpサービスが80番ポートを開放するサービスという事がわかったので、publicゾーンにhttpサービスを追加します。
$ firewall-cmd --permanent --zone public --add-service http success $ firewall-cmd --reload success
--permanent
は設定の永続化。(無いと再起動時に設定が消えます。)
--zone public
はゾーンの指定。
--add-service http
は追加するサービスの指定です。
--add-service
だけでは設定が反映されないので、設定をした後に--reload
でファイアウォールに設定を反映させます。
$ firewall-cmd --info-zone public public (active) target: default icmp-block-inversion: no interfaces: eth0 sources: services: dhcpv6-client http ssh ports: protocols: masquerade: no forward-ports: sourceports: icmp-blocks: rich rules:
設定が反映されました。
サービスに許可したいポートが無い場合
例えば8080等のポートを開けたい場合があると思います。
しかし、サービスには一般的に使用されているポートしか定義されておらず、8080番のポートは定義されていません。
そういう場合は--add-service
の代わりに--add-port
を使います。
$ firewall-cmd --permanent --zone public --add-port 8080/tcp success
このような感じです。
今日はここまで。
ConoHaでサーバ立ててユーザ追加したりSSHの設定したり
inabaです。
ConoHaが安くてサーバ立ち上げが早くて良さげです。
なのでVPSを借りて、いじって遊ぶ用のサーバを作ります。
参考記事:
大体は上記に書いてある通りに進めます。
使用するOSはCentOS 7です。
ここには気になった事や追加でやった事等を書いていきます。
wheelグループって何?
wheelグループに追加と書かれている箇所があります。
# wheelグループに追加 $ gpasswd -a newuser wheel
wheelグループとはデフォルトでsudoができるグループです。
所属しているとユーザ個別に設定しなくてもsudoができます。
設定は/etc/sudoers
に記述されています。
$ cat /etc/sudoers ... ## Allows people in group wheel to run all commands %wheel ALL=(ALL) ALL ...
gpasswd
いつもグループへの追加はusermod
でやっていて、gpasswd
は知りませんでした。
usermod
は-a
忘れるとグループが置き換えられる事がありますが、gpasswd
はその心配が無いので良さげです。
パスワード無しでsudo
先程の/etc/sudoers
のwheelグループの設定の下にパスワード無し用の設定がコメントアウトされているのでそのコメントアウトを外し、元の設定をコメントアウトします。
sudo
は結構重要な操作なので自分は一旦パスワード有りで使ってみて、面倒になってきたら外そうと思います。
## Allows people in group wheel to run all commands # %wheel ALL=(ALL) ALL ## Same thing without a password %wheel ALL=(ALL) NOPASSWD: ALL
SSHログイン許可の設定はデフォルトを使う
参考サイトのSSHログインを許可の項目ですが、設定ファイル(/etc/ssh/sshd_config)はデフォルトだとこうなっていました。
#RSAAuthentication yes #PubkeyAuthentication yes AuthorizedKeysFile .ssh/authorized_keys
RSAAuthentication
とPubkeyAuthentication
がコメントアウトされています。
コメントアウトされているという事はデフォルト値が使われるという事。
ここにデフォルト値が書いてあるのですが、
RSAAuthentication
とPubkeyAuthentication
のデフォルト値はyes
です。
自分は極力デフォルトで行けるならデフォルトを使いたい人間なので、設定はいじらず、コメントアウトのまま使います。
SSHキーはローカルにあるものを使う
SSH鍵を作成の項目でホスト側でキーを新たに作ってローカルに秘密鍵をコピーしていますが、自分はローカルにSSHキーが生成してあるのでこれを使います。
ローカル:
$ cat ~/.ssh/id_rsa.pub # 公開鍵 ssh-rsa foobarbaz... # 出た文字列をコピー
サーバ(rootではなく、新しく作ったユーザで実行。):
$ ssh-keygen # ~/.sshディレクトリを手で作るのが面倒なのでコマンドで作る。 # デフォルトで良いのでEnter3回 $ echo 'ssh-rsa foobarbaz...' > ~/.ssh/authorized_keys # ローカルでコピーした公開鍵を登録 $ chmod 600 ~/.ssh/authorized_keys # パーミッションを設定
ssh-keygen
を使っちゃってますが、これは補完が効くコマンドの方が打つのが楽だから使ってます。
作った鍵は今は使わないので、普通にmkdir ~/.ssh
でもいいです。
authorized_keys
のパーミッションの設定が重要
これで結構ハマった。
SSHキーを使ってでログインしようとしても、パスワードを聞かれるので、sshのログが書かれる /var/log/secure
あたりを調べて
Authentication refused: bad ownership or modes for file /home/newuser/.ssh/authorized_keys
と出てきたのでこれで検索。
authorized_keys
のパーミッションにオーナー以外の書き込み権限がついているとダメなようだった。
実際にauthorized_keys
のパーミッションを見てみるとrw-rw-r–になっていたので上記の通りchmod 600 ~/.ssh/authorized_keys
でパーミッションを設定してやると、パスワードを聞かれなくなった!
ログイン攻撃がヤバい
lastb
でログイン失敗履歴が見られるのだが、身に覚えのない失敗が約1000回くらいあった。
怖くなってlast
でログイン成功履歴を見たが、そっちには身に覚えの無い履歴は無かったので良かったが、こんなに攻撃されるのかとビビるね。
精神衛生上良くない。
なので、rootからのログインを禁止とパスワードによるログインを禁止は絶対やるべき。
PermitRootLogin no PasswordAuthentication no
まとめ
何点か書きましたが、sshのauthorized_keys
のパーミッション以外は特にハマること無く進められました。
このサーバで何しよっかな〜。
今日はここまで。
bufio.Scannerのエラーと解決策 #golang
こんばんわ。
inabaです。
今日はAtCoder Beginner Contest 055に参加していました。
その時に起こったエラーを調査したので参考までに。
Runtime Error
ある問題を解いて提出したら何件かでRuntime Errorが出た。
Runtime Errorの内容は見られないのでコードをちょこちょこ変えながらデバッグ。
原因
読み込み文字列が長いのが原因。
その問題の前提条件には10万文字が最大で入ってくるという事が書いてあったが、bufio.Scannerがデフォルトで最大サイズが決まっている事を知らずハマってた。
最大サイズの定数:
MaxScanTokenSize = 64 * 1024
64 * 1024
だから約6万5千文字がデフォルトで読み込める最大文字数。
10万文字なので余裕で超える。
デフォルトを超えるサイズの文字列を(*Scanner).Scan
すると(*Scanner).Err
でbufio.ErrTooLong
が返される。
var sc = bufio.NewScanner(os.Stdin) sc.Split(bufio.ScanWords) sc.Scan() if err := sc.Err(); err == bufio.ErrTooLong { panic(err) }
みたいな感じで調査してやっと見つけた。
解決策
(*Scanner).Buffer
で最大サイズを指定すると65536文字より大きい文字列も読み込んでくれた。
var sc = bufio.NewScanner(os.Stdin) sc.Buffer([]byte{}, math.MaxInt64)
こんな感じです。
とりあえずint64の最大値を指定してますが、いきなり指定サイズまでアロケーションされるという事は無く、現行のサイズ*2で読み込みを再試行していくようなのでまぁいいかなと。
src/bufio/scan.go - The Go Programming Language
今日はここまで。
外部キーが設定されているとTRUNCATEできない #MariaDB #MySQL
inabaです。
最近はgolangでDBをいじっています。
テストもテスト用DBを作り、実際に書き込んで行うようにしていました。
テストをするたびにAUTO_INCREMENT
の値が増えていく
まぁそうでしょう。
なので、テストデータのクリアをするためにTRUNCATE
しようと思ってエラー出てハマりました。
エラー
2つのエラーが出ました。
プレースホルダ
_, err = db.Exec("TRUNCATE TABLE ?", tableName)
上記コードの時は下記のエラーが出ました。
Error 1064: You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near '?' at line 1
テーブル名にプレースホルダは使えないらしい。
元々プレースホルダはキャッシュ目的だからFROM句は変えないですし、
セキュリティ的な使用でも、ユーザの入力をFROM句には使わないから妥当な動きですよね。
外部キー制約
プレースホルダをやめてfmt.Sprintfを使ってやるようにしてみたらエラー内容が変わりました。
_, err = db.Exec(fmt.Sprintf("TRUNCATE TABLE %s", tableName))
下記がエラー。
Error 1701: Cannot truncate a table referenced in a foreign key constraint ...
これは、外部キー制約によってTRUNCATE
ができないというエラーでした。
外部キー制約の回避
外部キー制約を回避する方法があります。
_, err = db.Exec("SET FOREIGN_KEY_CHECKS = 0") _, err = db.Exec(fmt.Sprintf("TRUNCATE TABLE %s", tableName)) _, err = db.Exec("SET FOREIGN_KEY_CHECKS = 1")
上記コードではエラーは出ません。
FOREIGN_KEY_CHECKS
を0
に設定すると、外部キー制約の回避ができます。
別の解決法
AUTO_INCREMENT
が初期化されればよいのであれば、DELETEしてからAUTO_INCREMENT
をリセットすることもできます。
_, err = db.Exec(fmt.Sprintf("DELETE FROM %s", tableName)) _, err = db.Exec(fmt.Sprintf("ALTER TABLE %s AUTO_INCREMENT = 1", tableName))
これなら2行で済みます。
ただ、子テーブルにデータが入っているとエラーになるので、テストで子テーブルも使っている場合は子テーブルの方から消していってください。
今日はここまで。
【Go】structをJSONにする時、ゼロ値を含む/含まないを切り替える方法と注意点 #golang
inabaです。
Go言語でstructをJSONにする時、ゼロ値を含まない場合はタグにomitempty
をつけますよね。
type stampCard struct { Stamp int `json:"stamp,omitempty"` }
しかし、同じstructを使っている時にゼロ値を含む/含まないを切り替えたい時があります。
具体的には下記のようなstructです。
type stampCard struct { InStampRally bool `json:"in_stamp_rally"` Stamp int `json:"stamp,omitempty"` }
上記のstructではInStampRally
がtrueの時だけStamp
の値をJSONに含めたいです。
しかし、このstructのままだとInStampRally
がtrueでもStamp
が0なら含まれません。
package main import ( "encoding/json" "fmt" log "github.com/Sirupsen/logrus" ) type stampCard struct { InStampRally bool `json:"in_stamp_rally"` Stamp int `json:"stamp,omitempty"` } func main() { sc := stampCard{InStampRally: true, Stamp: 0} jsc, err := json.Marshal(pc) if err != nil { log.Error(err) } fmt.Println(string(jsc)) // {"in_stamp_rally":true} }
解決策
解決策はゼロ値を含む/含まないを切り替えたい値をポインタ化します。
type stampCard struct { InStampRally bool `json:"in_stamp_rally"` Stamp *int `json:"stamp,omitempty"` }
これで
という切り替えができます。
package main import ( "encoding/json" "fmt" log "github.com/Sirupsen/logrus" ) type stampCard struct { InStampRally bool `json:"in_stamp_rally"` Stamp *int `json:"stamp,omitempty"` } func main() { scs := []stampCard{ {InStampRally: false}, {InStampRally: true}, } for i, sc := range scs { if sc.InStampRally { scs[i].Stamp = intPointer(0) } } jscs, err := json.Marshal(scs) if err != nil { log.Error(err) } fmt.Println(string(jscs)) // [{"in_stamp_rally":false},{"in_stamp_rally":true,"stamp":0}] } func intPointer(v int) *int { return &v }
上記サンプルコードではstampCard
のスライスをループさせてInStampRally
がtrueなら値が0のintポインターをStamp
に代入しています。
こうすることで下記のようにomitempty
が指定されていてもゼロ値をJSONに出力することができます。
[ { "in_stamp_rally": false }, { "in_stamp_rally": true, "stamp": 0 } ]
注意点
rangeループのvalueからポインターを取ると、ループでvalueの値が変わってもポインターの値は同じなので、注意しないと意図しない値が入ってしまいます。
(ポインターの値が同じ理由は、ループのたびに新しいメモリを確保しないためでしょう。)
DBから取得した値をループさせてJSON化する時等、注意が必要です。
例として、下記は順番に0, 1, 2, 3
のスタンプ数のスタンプカードができる事を期待しているコードです。
package main import ( "encoding/json" "fmt" log "github.com/Sirupsen/logrus" ) type stampCard struct { InStampRally bool `json:"in_stamp_rally"` Stamp *int `json:"stamp,omitempty"` } func main() { stamps := []int{0, 1, 2, 3} var scs []stampCard for _, stamp := range stamps { log.Infof("%p", &stamp) sc := stampCard{InStampRally: true, Stamp: &stamp} scs = append(scs, sc) } jscs, err := json.Marshal(scs) if err != nil { log.Error(err) } fmt.Println(string(jscs)) }
しかし、上記の結果は下記になります。
$ go run main.go | jq INFO[0000] 0xc42000a3d0 INFO[0000] 0xc42000a3d0 INFO[0000] 0xc42000a3d0 INFO[0000] 0xc42000a3d0 [ { "in_stamp_rally": true, "stamp": 3 }, { "in_stamp_rally": true, "stamp": 3 }, { "in_stamp_rally": true, "stamp": 3 }, { "in_stamp_rally": true, "stamp": 3 } ]
まず、ループ中のvalueのポインター値を表示していますが、すべて同じになっています。
その後のJSONは全てのstamp
が3になってしまいました。
全ての値が最後の値に書き換わってしまっています。
修正後のコード
ループのvalueを一旦新しい変数に入れてその変数のポインターを取れば正確な値をJSONに出力できます。
package main import ( "encoding/json" "fmt" log "github.com/Sirupsen/logrus" ) type stampCard struct { InStampRally bool `json:"in_stamp_rally"` Stamp *int `json:"stamp,omitempty"` } func main() { stamps := []int{0, 1, 2, 3} var scs []stampCard for _, stamp := range stamps { tmp := stamp // important!! sc := stampCard{InStampRally: true, Stamp: &tmp} scs = append(scs, sc) } jscs, err := json.Marshal(scs) if err != nil { log.Error(err) } fmt.Println(string(jscs)) }
結果:
[ { "in_stamp_rally": true, "stamp": 0 }, { "in_stamp_rally": true, "stamp": 1 }, { "in_stamp_rally": true, "stamp": 2 }, { "in_stamp_rally": true, "stamp": 3 } ]
今日はここまで。
npm loginは空いているユーザ名を指定するとアカウントを作成する
inabaです。
npm owner add
で自分のパッケージにオーナーを追加できると知り、
試すためにログインが必要だったのでnpm login
でログインした時の話。
$ npm login Username: foobar Password: Email: (this IS public) foobar@example.com
上記を見てもらうとわかりますが、ユーザ名、パスワードの次にEmailの入力があります。
「なんでユーザ名入力してるのにEmailの入力も必要なのかな〜」という疑問が湧いてきたので調べてみた。
実はnpm login
は取得されていないユーザ名を指定すると新しいアカウントを作る挙動になっていた。
loginという名前だったのでこの挙動にはちょっと驚いた。
loginはnpm adduser
のエイリアスなんだそう。
上記issueではloginとadduserは別々のコマンドに分割する方向で考えているというような事が書いてあった。
今日はここまで。