PNGの仕様を眺める

このブログは現在、Hugo という静的サイトジェネレータを使用してページを生成しているのですが、このHugoには多くの便利機能があり、そのうちの一つがImage processing です。 テンプレートの中で、ほんの数行のコードを書くだけで、画像のサイズを調整したり、切り抜いたり、あるいはぼかしたりすることもできます。これまでのところ、このブログでは画像のサイズは結構適当で、場合によっては表示の幅が変わってしまって見栄えが良くないということが(たまに)ありました。 そこで今回、HugoのImage processing機能のうち、Resizeを使用して、実際にサイトが表示されるときの画像を良い感じにしようと思ったのですが、サイト全体の画像描画部分にhookをかけたところ、エラーが出てビルドができなくなってしまいました。エラーメッセージの大半はファイルパスで、今回は本筋ではない上に長いので端折るとして、重要そうなところを抜き出すと次の様な感じ: 1 2 execute of template failed at <$image.Resize>: error calling Resize: : resize : png: invalid format: invalid checksum 要するに、Resizeしようとしたときに、checksumが合わないので対象のpng画像ファイルがおかしい、ということらしいんですよね。しかしこれまでこのブログを更新してきて、(パスとかが間違っていない場合に)画像が表示されなくて困ったこともないし、そもそもpngファイルにchecksumがあるなんてことも知らないし・・・ golang/go#43382 での会話を見たところによるとどうやら、pngファイルには「チャンク」と呼ばれるモノが存在して、これが無視できることも多い(特に実際に表示する時には不要なモノが多い)けれど、Goのimage/pngは今のところはそれらを無視せず、エラーを吐くことになっている、ということらしい。 エラーの対象となったファイルを、issueの説明に書かれているようにpngcheckにかけてみると次のようにCRCエラーがあることが分かりました: 1 2 3 4 5 6 7 $ pngcheck -v arch-1.png File: arch-1.png (272304 bytes) chunk IHDR at offset 0x0000c, length 13 1082 x 778 image, 32-bit RGB+alpha, non-interlaced chunk zTXt at offset 0x00025, length 188903, keyword: mxGraphModel CRC error in chunk zTXt (computed c3f0b5f1, expected 1ce878d9) ERRORS DETECTED in arch-1....

2024-02-16 · nasa9084

switchbot-exporterを書いた

先だって、SwitchBot APIのGo言語用クライアント実装であるgithub.com/nasa9084/go-switchbot を書いた、という記事 を書きましたが、これを使用してPrometheusでSwitchBot温湿度計 の情報を収集できるswitchbot-exporter を書いてみたので紹介します。 switchbot-exporterはblackbox exporter のように、起動時にはターゲットを指定せず、Prometheusがメトリクスを収集する際にrelabel_configでターゲットを与えるタイプのexporterです。 起動時に必要な情報はSwitchBotアプリから取得できるOpenTokenのみです。OpenTokenはコマンドラインオプションか、環境変数SWITCHBOT_OPENTOKEN経由で渡すことができます。 1 2 3 $ switchbot-exporter -switchbot.opentoken=blahblahblah # or $ SWITCHBOT_OPENTOKEN=blahblahblah switchbot-exporter docker image も用意してありますので、例えばKubernetes上で動かすこともできます: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 --- apiVersion: apps/v1 kind: Deployment metadata: name: switchbot-exporter spec: revisionHistoryLimit: 3 selector: matchLabels: app: switchbot-exporter template: metadata: labels: app: switchbot-exporter spec: containers: - name: switchbot-exporter image: nasa9084/switchbot-exporter:0....

2021-03-23 · nasa9084

SwitchBot APIのGoクライアント、go-switchbotを書いた

SwitchBot は所謂IoT機器を扱っているメーカーで、温度計を専用のハブ経由でインターネットに接続し、アプリから室温を確認したり、室温によってエアコンの設定を変更する、などといったホームオートメーションに役立つガジェットを複数販売しています。 しかし、それらのデータを確認できるのはSwitchBotのアプリからか、Bluetooth経由だけ、という状況で、私もHTTPのAPIを用意してくれたら良いのに、とずっと思っていました。 ところが昨日、社のSlackで、こちらのissue を共有してもらい、どうやら昨年12月ごろにはHTTPのAPI が使えるようになっていたっぽいことが分かりました。 我が家にはHub Miniもあり、インターネットに接続してある状態ですから、早速次の手順でtokenを手に入れて試してみました: スマホでSwitchBotのアプリを開く プロフィールタブ > 設定と進み、アプリバージョンを10回連打する 開発者向けオプションが表示されるので、開いてトークンを取得する Authorizationヘッダにトークンを入れ、https://api.switch-bot.com/v1.0/devices にGETでリクエストを投げてみる 結果、確かに自宅のSwitchBotデバイスの一覧を取得することができました。 こうしちゃいられねぇ!と深夜に書いたGolang用のSwitchBotクライアントがこちらです: https://github.com/nasa9084/go-switchbot ドキュメントはpkg.go.inなどで見て下さい: https://pkg.go.dev/github.com/nasa9084/go-switchbot 今回はFunctional Option PatternとGoogleっぽいAPIの合わせ技構成にしてみました。 例えば、デバイスの一覧を取得したい場合は次の様にすると取得することができます。 1 2 3 client := switchbot.New("SET_YOUR_SWITCHBOT_OPEN_TOKEN") physical, virtual, _ := client.Device().List(context.Background()) 第一返値のphysicalはSwitchbotデバイスの事で、第二返値のvirtualは赤外線で通信するデバイスの事です。SwitchBot APIでは、SwitchBotデバイスの事を物理デバイス、赤外線で接続するデバイス(エアコンなど)のことをvirtual remote deviceと区別して扱います。 API rate limitは1,000 request / dayとなっていて、あまり多いという訳ではないですが、例えば数分に一回、あるいは一時間に一回室温を取って記録するようなPrometheus Exporterを記述するといったことが捗ると思いますので、SwitchBotデバイスを使っている方は是非使ってみて下さい!

2021-02-25 · nasa9084

context.WithTimeoutに0を与えるとどうなるのか

当然と言えば当然なんですけど、特に panic とかそういうことはなく、一瞬でタイムアウトします。まぁ、どうと言うことは無いですが、設定ファイルとかで未定義時に0が来るような実装になっている場合はなんか処理する(0の時は処理をしない、というのは多分あんまりなさそうですし)必要がありますね。 1 2 3 4 5 ctx, cancel := context.WithTimeout(context.Background(), time.Duration(0)) defer cancel() <-ctx.Done() log.Print("timeout") https://play.golang.org/p/63DkfIEImjv もうちょっと細かい話 さすがに短すぎるので、もう少し細かい実装の話。 context.WithTimeoutは内部的には特別な実装は無くて、context.WithDeadlineをtime.Now.Add(timeout)に対して呼んでいます。 で、context.WithDeadlineは返値を返す前にtime.Untilを使って現在時刻とデッドラインまでの差分をチェックしていて、これが0以下ならその場でキャンセル関数を呼んで います。 まぁそんなわけで、余分な待ち時間が発生することもなく、time.WithTimeoutを呼んだ時点でちゃんとタイムアウトされる、ということでした。ちゃんちゃん。

2020-04-08 · nasa9084

Goで(メールサーバを用意せずに)メールを送る

単純な興味というか、特にこれで何かを作るというわけではないのだけれど、ふと思い立って調べてみたら意外と情報が無かったのでメモを残しておきます。 Goでメールを送りたい、と思ったとき、Googleで検索してみると、net/smtp パッケージを使ってgmailのSMTPサーバを使用する、とかSendGridを使用する、とかそういった例ばかりが目につきました。これらはもちろん便利であることは疑いようもない(自前でメールサーバの管理とかやってられないし)んですけど、こういったSMTPサーバやらsendmail/postfixやらを使わなくても、本来SMTPではメールを簡単に送れるはず(なんと言っても「Simple Mail Transfer Protocol」ですから)、と思いました。 とはいえじゃぁどうしたら良いのか、と思ったとき、Goを用いた例というのはぱっと見当たらないのです。仕方ないのでtelnetを使用した例を見ながら、telnetでどうやれば自分のgmail宛てにメールが送れるのか、というのを試しました。 具体的な手順というのは、次の様なものです。なお、以下の手順では(macにtelnetが入っておらずインストールして環境がごちゃごちゃするのも面倒だったので)centos:7のDockerコンテナを使用しています。 # nslookup -type=mx gmail.com Server: 192.168.65.1 Address: 192.168.65.1#53 Non-authoritative answer: gmail.com mail exchanger = 20 alt2.gmail-smtp-in.l.google.com. gmail.com mail exchanger = 10 alt1.gmail-smtp-in.l.google.com. gmail.com mail exchanger = 40 alt4.gmail-smtp-in.l.google.com. gmail.com mail exchanger = 5 gmail-smtp-in.l.google.com. gmail.com mail exchanger = 30 alt3.gmail-smtp-in.l.google.com. Authoritative answers can be found from: # telnet gmail-smtp-in.l.google.com 25 Trying 74.125.204.26... Connected to gmail-smtp-in.l.google.com. Escape character is '^]'. 220 mx.google.com ESMTP 6si12301456pjb....

2020-04-06 · nasa9084

emacs/lsp-mode + goplsでGo用のLSP環境を設定する

Language Server Protocol (以下LSP)はこれまでエディタ/IDEが独自に実装する必要があった、補完や定義参照、静的解析によるエラー分析などをサービスとして実現するためのプロトコルです。 LSPを実装したクライアントは、Language Serverを提供している言語であれば何でも補完や定義参照、静的解析といった便利機能を使用することができます。 Microsoftが2016年にその仕様を公開してから、多くのエディタ用のLSPのクライアント実装が作られ、また各種言語用のLanguage Serverも公開されています。 Go言語も例に漏れずLanguage Serverの実装がいくつか存在します。今回は準公式提供のgopls を使用して設定してみます。 もちろんemacsにも複数のLSP Client実装がありますが、今回はlsp-mode を使用します。 まずはemacs用のパッケージをインストールします。次のモノをpackage-installかpackage-list-packagesか、そのあたりでよしなにインストールします。 lsp-mode lsp-ui company-lsp インストールできたら、(私はuse-packageを使っているので)設定ファイルにuse-packageの設定を入れておきます。ついでにgo用の設定も入れておきましょう。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 ;; Golang (defun lsp-go-install-save-hooks() (add-hook 'before-save-hook #'lsp-format-buffer t t) (add-hook 'before-save-hook #'lsp-organize-imports t t)) (use-package go-mode :ensure t :mode (("\\.go\\'" . go-mode)) :init (add-hook 'go-mode-hook #'lsp-go-install-save-hooks)) ;; Language Server (use-package lsp-mode :ensure t :hook (go-mode ....

2020-02-07 · nasa9084

go-openapi を書き直しています

本記事はGo2 Advent Calendar の20日目の記事です。昨日はyaegashiさんによる、jsonx.go でした。 皆さんはOpenAPI Specification というモノをご存じでしょうか。OpenAPI SpecificationはJSONまたはYAMLでREST APIを表現するための仕様で、現在バージョン3.0.2 が最新です。いわゆるSwagger の後継で、バージョン1系、2系がSwagger、3系以降がOpenAPI、ということになっています(Swaggerなら聞いたことがある/使っているという人も多いのではないでしょうか)。 OpenAPI Specificationは人間にも機械にも(比較的)読みやすい仕様書として、コード生成や、ドキュメントページの生成に使用することが可能です。 個人的には専らコード生成に使用しており、Go言語向けの実装としてgithub.com/nasa9084/go-openapi (以下go-openapi)を実装・公開しています。 go-openapiは2017年ごろから細々と実装を続けており、(多分)二番目か三番目には古いと思われるOpenAPIのGo実装です。 基本的にはただひたすらOpenAPIのオブジェクトをGoの構造体として定義、値のバリデーション関数を用意しているといったもので、特別な機能はほとんどありません。 YAMLのパーサも、go-yaml/yaml を使用しており、自前では実装していません。 そんな中、@goccy さんが、encoding/jsonとコンパチなインターフェースを持ったYAMLパーサを開発した、という話を耳にし、これを機に、とgo-openapiの実装を一から書き直し始めました。 もともと、パースは完全にgo-yaml/yamlに依存しており、Unmarshal系のメソッドも実装していなかった(途中から全部書くのはつらかったので・・・)ため、一部バリデーションに必要な関数を埋め込んだりもできなかったので、書き直したいとは思っていたのです。 現時点ではまだマージしておらず 、書いている途中なのですが、大きな変更点として次の様なものがあります。 もともとパブリックだったフィールドをすべてプライベートに変更し、ゲッターをはやした 各構造体に対応するUnmarshalYAML()メソッドをすべてコード生成するようにした YAMLパーサはgithub.com/goccy/go-yaml に乗り換えた rootオブジェクトを各構造体に埋め込むことで、バリデーションをとりやすくした 今後はよりコード生成に便利なメソッドを追加していきたいと思っています。 時間の都合で今日はここまで。技術的な話が全然無い記事になってしまった・・・

2019-12-20 · nasa9084

net/http.ClientにHookをかける

昨日のこと。jszwedko/go-circleci というパッケージを使用してCircleCI EnterprizeのAPIを叩くという処理を実装していたのですが、どうにもうまくいかない。正直に言ってこのパッケージはドキュメントがしっかりしている、という訳ではないし、エラーメッセージを見ても何がだめなのか(そもそも現在使用しているCircleCI Enterpriseで使用できるかもよくわかっていなかった)わからない。 しかしまぁ、自分でHTTP requestを作ったりしてあれやこれややるのもまぁ面倒であるので、なんとかデバッグしたいと思ったのですが、外部のパッケージをフォークして変更を加えてデバッグする・・・という様なことはもちろんやりたくないわけです。 このパッケージは*http.Client を指定できます。*http.Clientはインターフェースではなく構造体なので、別の実装に置き換えるということはできません。が、その実装はほぼほぼ後述するhttp.RoundTripperなため、http.RoundTripperをラップして、HTTP requestとHTTP responseをログに吐けばまぁ、何が問題かわかるだろう、と考えました。 そんなモノは誰かがすでに書いているだろう、というのはさておき、http.RoundTripperを実際にいじってみるということはやったことがなかったので、勉強がてらnasa9084/go-logtransport なるものを書きました。 書いていく途中で、考えたことなど、記録に残しておくのも良さそうと思ったため、本記事とします。 http.Clientとhttp.RoundTripper Go言語でHTTPのリクエストを発行するには基本として*http.Client というものを使用します。簡便のため、GET 、POST 、Head についてはパッケージグローバルの関数も用意されてはいるのですが、これらも内部的にはパッケージグローバルで宣言されたDefaultClient という*http.Client が使用されています。 *http.Clientはゼロ値で使用できるようにまとめられた構造体で、DefaultClient は*http.Client{}と宣言されています。 普段はこの*http.Clientを使用してHTTPの通信を行うわけですが、実は*http.Clientはそれほど多くの機能は持っていません。実際、持っているフィールドはたったの4つ(Go1.13時点)しかないのです。*http.Clientはリダイレクトやクッキーなどの一部の処理だけを受け持っていて、実際のHTTP通信のほとんどはフィールドとして保持しているhttp.RoudTripperが行います。 http.RoundTripperはインターフェースとして定義されていて、自由に差し替えをすることができます。特に指定していない場合は*http.Transportがデフォルトの実装として使用されます。 Goの他の標準パッケージの例に漏れず、http.RoundTripperは非常にシンプルなインターフェースで、次の様に定義されています。 1 2 3 type RoundTripper interface { RoundTrip(*Request) (*Response, error) } RoundTrip()がHTTP requestを受け取り、HTTP responseを返します。つまり、requestのログをとり、子RoundTripperのRoundTrip()を実行し、Responseのログをとってそのまま返す、という様なラッパーを書けば良さそうです。 1 2 3 4 5 6 7 8 9 func (t *Transport) RoundTrip(r *http.Request) (*http.Response, error) { // Requestのログをとる resp, _ := t.Transport.RoundTrip(r) // responseのログをとる return resp, nil } 実際にrequestとresponseのログをとるには、net/http/httputilパッケージのDump系関数が使用できます。今回はクライアント側の実装なので、httputil.DumpRequestOutとhttputil.DumpResponseを使用します。...

2019-11-01 · nasa9084

空文字列確認は長さをとるべきか?

TL;DR s == ""とlen(s) == 0は等価 文字列比較か、長さ比較か Go言語で文字列が空かどうかを調べるには次の二つの方法があります。 1 2 3 4 5 6 7 8 9 // 1: 文字列を空文字列と比較する if s == "" { // do something } // 2: 文字列の長さが0かどうか調べる if len(s) == "" { // do something } 標準パッケージ・サードパーティパッケージともに、どちらの書き方も散見されます。 どちらを使うのが良いのでしょうか? 答えはどちらでも良いだそうです。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 package benchmark_test import "testing" var somethingString = "hogehogefugafuga" func BenchmarkCompareString(b *testing....

2019-10-09 · nasa9084

Unuboでアプリケーションをサクッとデプロイする

Unubo というPaaSっぽいものが出てきたようなので触ってみました。 まだリリースされたばかりのようで、クレジットカードの登録もせずに無料で使用できます。 とりあえず適当にリポジトリを作って動かしてみました。 登録・ログインを済ませたら、使用したい機能を選択します。今回はGoのアプリケーションなので、AppsセクションにあるGoのアイコンを選択しました。 次の画面でアプリケーション名を入れ、デプロイ先のリージョンを選択します。 DBと接続する場合はDBと同一リージョンじゃなきゃ接続できないそうです。 最後に、細かい設定をできる画面になりますので、必要な情報を入力します。 ここでは、とりあえずGitHubとの連携をして、リポジトリを選択するだけであとは放置します。 最後にDeployボタンをポチッとしてデプロイされるのを待ちます。 どうやら、Deploy成功からアクセスできるようになるまで若干タイムラグがあるようなので、気長に待ちましょう。 有料プランが出てから価格設定や無料枠がどうなるかわかりませんが、まぁぼちぼち便利に使えそうですね。

2019-09-26 · nasa9084