goでQRコード生成(終了)

開発:PNGの生成がgoでサクッとできちゃうことがわかったので、QRコードの生成を考え始めました。規格としては、JIS X 510:2018です。ざっと眺めたのですが、これは結構骨かなと。

社長:おおー。規格協会がタダで見せてくれるPDFの質もずいぶんまともになったね。以前は劣化したFaxのようだったものですが。

開発:115ページあるし、なんだか気がノリません。原理を説明している仕様書としては面白いと思うのですが、これをただ実装するのって、ジューシーじゃないというか、遊べる隙間が無いと言うか、人生をロスする気がします。

社長:まあうちはとりあえず、ありきたりのQRコードを生成するだけのユーザだからね。ひょっとして、go のライブラリにあったりしないかな。

基盤:調べてみます。

*** 数分経過 ***

基盤:ありました。barcode というパッケージですね。ライセンスはMIT。Githubで配られているんですが、ここってGoの開発者向けサイトの一環みたいで、今年2020年1月のGoの公式ブログからリンクされてます。で、そのページにある利用例がこれです。

package main

import (
	"image/png"
	"os"

	"github.com/boombuler/barcode"
	"github.com/boombuler/barcode/qr"
)

func main() {
	// Create the barcode
	qrCode, _ := qr.Encode("Hello World", qr.M, qr.Auto)

	// Scale the barcode to 200x200 pixels
	qrCode, _ = barcode.Scale(qrCode, 200, 200)

	// create the output file
	file, _ := os.Create("qrcode.png")
	defer file.Close()

	// encode the barcode as png
	png.Encode(file, qrCode)
}

で、これを走らせると、こういうPNGができました。

社長:おお、iPhoneのカメラをかざすと「Hello World」と認識されます。

基盤:「http://its-more.jp」で作ると、こんな感じ。見た目にも情報量が増えてますね。

ついでに、空文字列だとこんな感じ。これがミニマムでしょうか。

逆に長い文字列、ここではこのプログラムの LICENSE ファイルの600文字くらいを食わせてみましたが、こんな感じです。

それと、こんなふうにボカしても認識できますね。

開発:おおー、素晴らしい! これは助かりました。美しい人生よ、限りない喜びよ。

社長:この胸のトキメキをあなたに。やっぱりあったんですね。まあ、そんなこともあろうかと思って png-go.com というドメインは取って置きました。ピンゴ!(笑)

基盤:必要なのは QR にちなんだドメイン名ですよね。

営業:何かの商売になりますかね?

経理:それって、いつもの安かろうもんのXSOnamae じゃなくて N. Sol. で取ったのはなぜですか?

社長:それがXSOがこのドメインについては何かトラブってましてね、なんか失敗する。で、NSOにいったらさくっと取れまして。XSOの表示の3倍以上の値段でしたねー。さすがドメイン商法の家元!(笑)

経理:… なぜか使われた口座も当社メインバンクじゃなくて、ジャパネット系でした。

社長:それがねー、メインバンクのお上品銀行は、なんか海外送金がダメなんですよ。箱入りなんでしょうかねぇ。2行作っといてよかったです。

2020-0519 SatoxITS

仮想虚無感サーバ

開発:PNGの生成の件は、Goを使えば自分では何もやる事がないということで、終了しました。

社長:ありがたい事だけど、昔の自分の苦労は何だったんだろうって虚無感を感じます。本格的に製品開発にノメる前に、何か面白い事をしたいですね。面白いサーバコンテンツ。

開発:それで考えたのですが、まあこれも昔からありがちな話だとは思いますが、自分では何も個別コンテンツというかリソースを持たないサーバというのはどうかなと。

社長:クッキーだけでやるということですか?

開発:いえ、リソースとしては、クライアント側のローカルファイルとか、サーバも使います。言ってみれば、URLのビューだけを提供するようなサーバです。ブラウザに対するコンサル的なサジェスチョンみたいな。

社長:何かの処理は提供するわけですね。

開発:まずは、PNGの生成ですかね。これは壁紙とかファビコンとか各種のアイコンとかクリッかぶるに使えるかなと。あとはやっぱり、クッキーの編集機能。簡単なエディタになると思いますが、まあフォームでいいのかなと。

社長:そういえば、ブラウザもむかしはCookieの中身を見たり編集できたものだけど、最近のはそうなってないような。

開発:なぜなんでしょうね。あれは実際、かなり困ります。

goによるpngの生成(終了)

さて、pngの容れ物はわかったので、中身を詰めてみよう。イメージデータを詰めるには、、、どうも zlib 形式での圧縮(deflate)は必須らしい。なんでだろう。

同一マシンの中で揮発性で小さめの画像を飛ばすだけなので、圧縮・伸長のオーバヘッドを避けたいのに。マシン内なら楽勝で10Gbps出るのに、Zlibかませたら遅くなる。そもそもCRCもいらない。あれもかなり重い。100Mbpsくらいに落ちてしまう感じだ。たかだか4GHzのCPUで処理してるんだし。そう言えば、zlib の圧縮のパラメータで、圧縮無しってあったっけか…

まあ、せっかく go がPNG用のエンコーダを用意してくれてるんだから、まずはそれを使ってみよう。再び Packege png のページを見る。たぶんこの Encodeという関数だろう。引数はどうなっているのか … 「image.Image」。なんだろう、これ。Rectangle …。…。…。えーっ、つまりこれでこれでごにょごにょイメージを作ってやって、Package png に渡してやると、PNG 形式にしてくれるって事? そうなんですか。

なーんだ

なーんだぁ!

まあ、そういうものが無い方がおかしいと思った。でもまあいい、10年の時の流れを実体験しながら1日で飛び越えられたから。PNGのフォーマットも勉強できたし、何かの役に立つかも知れない。

png Packageによる png の生成

それで、それってどう使うのって、これが Encode の Example ですか。

package main

import (
	"image"
	"image/color"
	"image/png"
	"log"
	"os"
)

func main() {
	const width, height = 256, 256

	// Create a colored image of the given width and height.
	img := image.NewNRGBA(image.Rect(0, 0, width, height))

	for y := 0; y < height; y++ {
		for x := 0; x < width; x++ {
			img.Set(x, y, color.NRGBA{
				R: uint8((x + y) & 255),
				G: uint8((x + y) << 1 & 255),
				B: uint8((x + y) << 2 & 255),
				A: 255,
			})
		}
	}

	f, err := os.Create("image.png")
	if err != nil {
		log.Fatal(err)
	}

	if err := png.Encode(f, img); err != nil {
		f.Close()
		log.Fatal(err)
	}

	if err := f.Close(); err != nil {
		log.Fatal(err)
	}
}

New して Set したら Encode して終了ですか。そうですかー。どれどれ。go run go。出来た。

ステキ。いやはや極楽極楽。いい世の中になったものたろうだなー :-)。もう道具は全て揃ってるって感じ。

自分で作ったのも、青春の思い出としてアーカイブしておこう(笑)。これはこれで、楽しかった。やはり、がっちりした有名な仕様書を読みながら実装するのはワクワクする。しかし、単にバイト列を直線的にバッファに書いていくのをGo言語で効率的にやる方法がまだわからない。まあ、string じゃなくて byte というのでやるんだろう…

/*
<base href=https://www.w3.org/TR/PNG/>
	2020-0518-03 pnggen/0.0.3 (PNG generator) by ITS more :-)
	Reference: PNG Ed.2 Spec.

*/ package main import ( "os" "fmt" "hash/crc32" ) func myid() string { return "pnggen/0.0.3 (PNG Generator)" } func hton32b(hint int)(string){ buf := make([]byte, 4) buf[0] = byte(hint >> 24) buf[1] = byte(hint >> 16) buf[2] = byte(hint >> 8) buf[3] = byte(hint) return string(buf) } func putNetInt32(file *os.File, hint int) int { file.Write([]byte(hton32b(hint))) return 4 } // 5.3 Chunk layout Chunk := Length+Type+Data+CRC func putChunk(file *os.File, ctype string, cdata string) int { crc := crc32.ChecksumIEEE([]byte(ctype+cdata)) // heavy if large olen := putNetInt32(file,len(cdata)) // length of data file.Write([]byte(ctype)) // chunk type file.Write([]byte(cdata)) // chunk data olen += putNetInt32(file,int(crc)) // CRC of type plus data return olen + len(ctype) + len(cdata) } func pnggen() { outfile := "x3.png" genw := 64; genh := 64; fmt.Fprintf(os.Stderr,"-- %s --\n",myid()) file, _ := os.Create(outfile) defer file.Close() //---- Signature -- 5.2 PNG signagure PNGsignature := "\x89PNG\r\n\x1A\n" file.Write([]byte(PNGsignature)) olen := len(PNGsignature) //---- IHDR -- 11.2.2.2 IHDR Image header ihdr := hton32b(genw) // Width ihdr += hton32b(genh) // Hight ihdr += string(1) // Bit depth, 1 for monocrome ihdr += string(0) // Color type, 0 for Grayscalse ihdr += string(0) // Compression method, 0 for deflate/inflate ihdr += string(0) // Filter method, 0 for adaptive ihdr += string(0) // Interlace method, 0 for no interlace olen += putChunk(file,"IHDR",ihdr) //---- PLTE -- 11.2.2.3 PLTE Pallete olen += putChunk(file,"PLTE",""); //---- IDAT -- 11.2.2.4 IDAT Image data olen += putChunk(file,"IDAT","") //---- IEND -- 11.2.2.5 IEND Image trailer olen += putChunk(file,"IEND","") fmt.Fprintf(os.Stderr,"-- created %s (%d x %d) %d bytes\n", outfile,genw,genh,olen) } func main() { pnggen() } //

goによるpngの生成

サーバで画像を生成して配信したい。まずは静止画で、アイコン、グラフ、QRコード等。高々100KB程度の画像を想定している。要するに、どのピクセルを何色にするか、だけのデータなのだから、簡単にできるに違いない。

PNG選定までの経緯

静止画ならpngが形式がシンプルで良かった記憶がある。機能的に大きな問題がなければPNGで行きたい気分。

静止画、フォーマットでぐぐると、このブログが先頭にくる。比較対象としてあげられている JPEG, GIF, PNG, TIFF, BMP。生成する画像は写真では無いので、JPEGは必要無い。ただの一枚の静止画なのでTIFFである必要は無い。ブラウザで表示することが目的なので、やはりPNGかな。基本的には、非常に近距離で、小さめの画像をやりとりするのが目的なので、圧縮の機能も必要ないというかOFFにできると良い。

Wiki をみても、PNGが適当と思う。PNGが出てきた経緯からして、それが普及した現在、GIFに戻る必要はないのでは。ひとつだけ気になるのはGIFアニメ。でも、アニメーションPNGというのがあるそうで、これも試してみたら、現在の主要5ブラウザで全てで表示できた。

全て問題なし。PNGが良いと思う。Wiki の PNG の項を見たら、だいたいそう言うメリットがまとめられている。

実装言語としてのGo

それで仕様書は何だったろうとたぐると、大元は RFC 2083 だった。

Goでさくっと実装したい。仕様をざっと見て、共通ライブラリ的なもので、自作の線はないなと思うのは zlib。でもこれは go にパッケージがあるようなのでOK。CRC32も問題ない。そもそも可逆圧縮なんだし、フォーマットと圧縮は直交しているはずだ。

ん、まてよ、そもそもGo には PNG自体のライブラリがあるのではないか?やはり、Package png というのがあった。エンコーディングについては、これに任せれば良いらしい。その中で参照されているPNGの仕様は、W3Cのものだった[https://www.w3.org/TR/PNG/]。2003年版とあるので、いい感じに枯れてるのじゃあるまいか。

PNGのフォーマット

Go で PNG で行こう。実際、もともとその一択だったと思う。準拠する仕様は W3C の 2003 年版すなわち、ISO/IEC 15948:2003 とする。

Portable Network Graphics (PNG) Specification (Second Edition)

Information technology — Computer graphics and image processing — Portable Network Graphics (PNG): Functional specification. ISO/IEC 15948:2003 (E)

W3C Recommendation 10 November 2003

https://www.w3.org/TR/PNG/

仕様書をざっと眺める。PDFにしてみても40〜50ページ程度だから、コンパクトな規格だ。画像が入っているのが IETF RFCとは違うところだな(笑)。目次や Term & definitions の章がしっかりしているのは ISO の血の匂いがする。いろいろ書いてあるけど、とりあえず必要な情報って数ページ程度ではあるまいか?

やりたいこことは PNG 形式を作る事、つまり 4.7 PNG datastream を作る事だ。ヘッダの後は、チャンクの連続だと書いてある。必須(クリティカル)のチャンクは、ヘッダ、パレット、データ、終端の4種類か。

それぞれの定義は 11.2.2,3,4,5 にある。2ページ程度の仕様だ。

だが、値を十進で表記するのやめて欲しい。ISO流なのか?なぜ「73 72 68 82 というオクテット列はASCIIコードとして解釈すると偶然ではなく"IHDR"という文字列になります」と一言、言えないのだろう?
まあいい。感じはわかった。いい感じだ。さくっと行けそうな気がする。

トライアル実装(1)

さっそく何か作ってみよう。うーん、Cだと簡単に書けるのだが … とりあえずCで書こうか … つくり初めてみたが何か変だ。無意識に mkgif.c という名前でプログラムを作っていた(笑)

やはりGoで書こう。… … … こんな感じになった。

Pnggen/0.0.1 ソースコード
//
// 2020-0518-01 pnggen/0.0.1 (PNG generator) by ITS more :-)
// Reference: https://www.w3.org/TR/PNG/
//
package main
import (
        "os"
        "fmt"
)
func hton32b(s string, i int)(string){
        s += string(0xFF & (i >> 24))
        s += string(0xFF & (i >> 16))
        s += string(0xFF & (i >> 8))
        s += string(0xFF & (i))
        return s
}
func putNetInt32(file *os.File, length int){
        lengb := make([]byte, 4)
        lengs := string(lengb)
        lengs = ""
        lengs = hton32b(lengs,length)
        file.Write([]byte(lengs))
}
func main() {
        outfile := "x.png"
        fmt.Fprintf(os.Stderr,"-- pnggen/0.0.1 --\n") 
        file, _ := os.Create(outfile)
        defer file.Close()
        
        PNGsignature := "\x89PNG\r\n\x1A\n" // 5.2 PNG signature
        file.Write([]byte(PNGsignature))
        
        // 5.3 Chunk fields := Length, Chunk Type, Chunk Data, CRC
        ckdbuf := make([]byte, 256) // 5.3 CHUNK DATA buffer
        chunkdtop := string(ckdbuf)
        chunkdtop = ""
        
        // 11.2.2 IHDR Image header
        IHDRtype := "IHDR" // IHDR chunk type
        // IHDR chunk data
        chunkd := hton32b(chunkdtop,64) // Width
        chunkd = hton32b(chunkd,64) // Hight 
        chunkd += string(1) // Bit depth, 1 for monocrome
        chunkd += string(0) // Color type, 0 for Grayscalse
        chunkd += string(0) // Compression method, 0 for deflate/inflate 
        chunkd += string(0) // Filter method, 0 for adaptive
        chunkd += string(0) // Interlace method, 0 for no interlace
        // IHDR CRC
        // to be done
        
        putNetInt32(file,len(chunkd)) // Length
        file.Write([]byte(IHDRtype))
        file.Write([]byte(chunkd))

        fmt.Fprintf(os.Stderr,"-- created %s (%d x %d)\n",outfile,64,64) 
}

プログラムから出力したPNGファイルの中身を確認する。

% go run pnggen.go 
-- pnggen/0.0.1 --
-- created x.png (64 x 64)
% file x.png
x.png: PNG image data, 64 x 64, 1-bit grayscale, non-interlaced
% od -c -t d1 x.png
0000000  211   P   N   G  \r  \n 032  \n  \0  \0  \0  \r   I   H   D   R
         -119  80  78  71  13  10  26  10   0   0   0  13  73  72  68  82
0000020   \0  \0  \0   @  \0  \0  \0   @ 001  \0  \0  \0  \0            
            0   0   0  64   0   0   0  64   1   0   0   0   0    

とりあえず file コマンドは、これが 64 x 64 のPNGだと認識してくれている。CRCはまだつけてないけど、チェックしてないんだな。では Preview で開いてみよう。

おっしゃるとおり、ダメージを受けております。ブラウザは何と言うだろう?

Firefoxは怒った。が、他のブラウザたちはノールックでスルーパス。わりといい加減なんだな。

どうやら必須エリアはそろってないと怒られるようなので、空のパレットと空のイメージデータを追加して、IENDで終端してみる。で、プレビューで見てみると…

できたー!苦節6時間(笑)半分以上は、Go言語の string 型との闘いだった。まだよくわからない。そもそもなぜ、printf / scanf でナマの整数値の読み書き、特にネットワークバイトオーダとの変換ができないのか不思議だ。なぜたかが hton / ntoh するために、面倒なことをしなければならないのか??

ともあれ、最小のPNGファイルは、65バイトであることがわかった。

% go run pnggen.go
-- pnggen/0.0.2 (PNG Generator) --
CRC=82124C73
CRC=4BA88955
CRC=35AF061E
CRC=AE426082
-- created x2.png (64 x 64)

% od -c x2.png
0000000  211   P   N   G  \r  \n 032  \n  \0  \0  \0  \r   I   H   D   R
0000020   \0  \0  \0   @  \0  \0  \0   @ 001  \0  \0  \0  \0 202 022   L
0000040    s  \0  \0  \0  \0   P   L   T   E   K 250 211   U  \0  \0  \0
0000060   \0   I   D   A   T   5 257 006 036  \0  \0  \0  \0 256   B   `
0000100  202   

あれ?IENDが出てないや。出力し忘れた。Previewのチェックも中途半端だな。出力しよう。ということで、最小のPNGファイルは、69バイトであった。

% od -c x2.png
0000000  211   P   N   G  \r  \n 032  \n  \0  \0  \0  \r   I   H   D   R
0000020   \0  \0  \0   @  \0  \0  \0   @ 001  \0  \0  \0  \0 202 022   L
0000040    s  \0  \0  \0  \0   P   L   T   E   K 250 211   U  \0  \0  \0
0000060   \0   I   D   A   T   5 257 006 036  \0  \0  \0  \0   I   E   N
0000100    D 256   B   ` 202                                            
0000105
% od -t x1 x2.png
0000000    89  50  4e  47  0d  0a  1a  0a  00  00  00  0d  49  48  44  52
0000020    00  00  00  40  00  00  00  40  01  00  00  00  00  82  12  4c
0000040    73  00  00  00  00  50  4c  54  45  4b  a8  89  55  00  00  00
0000060    00  49  44  41  54  35  af  06  1e  00  00  00  00  49  45  4e
0000100    44  ae  42  60  82                                            
0000105

data URI にしてみよう。まずはbase64にする。

% delegated -FenMime -b x2.png  
iVBORw0KGgoAAAANSUhEUgAAAEAAAABAAQAAAACCEkxzAAAAAFBMVEVLqIlVAAAAAElEQVQ1
rwYeAAAAAElFTkSuQmCC

data URI はこれで良いはずだ。

data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABAAQAAAACCEkxzAAAAAFBMVEVLqIlVAAAAAElEQVQ1
rwYeAAAAAElFTkSuQmCC

OperaのURL窓に食わせて表示、ダウンロード。Preview で表示。問題なし。みんなで記念撮影しよう。

ミニマムPNGファイルできました。

あれ?そういや zlib かけてないんだけど、通ったじゃん。自動判別してるのかな?あ、圧縮するのはデータだけなのかな。とりあえず現状を保存。

Pnggen/0.0.2 (PNG Generator) ソースコード
//
// 2020-0518-02 pnggen/0.0.2 (PNG generator) by ITS more :-)
// Reference: https://www.w3.org/TR/PNG/
//
package main
import (
        "os"
        "fmt"
	"hash/crc32"
)
func myid() string {
	return "pnggen/0.0.2 (PNG Generator)"
}
func hton32b(i uint32)(string){
	buf := make([]byte, 4)
	buf[0] = byte(0xFF & (i >> 24))
	buf[1] = byte(0xFF & (i >> 16))
	buf[2] = byte(0xFF & (i >>  8))
	buf[3] = byte(0xFF & (i))
	return string(buf)
}
func putNetInt32(file *os.File, i32 uint32){
        intb := make([]byte, 4)
        ints := string(intb)
        ints = ""
        ints = hton32b(i32)
        file.Write([]byte(ints))
}
func main() {
        outfile := "x2.png"
	genw := 64;
	genh := 64;
        fmt.Fprintf(os.Stderr,"-- %s --\n",myid()) 
        file, _ := os.Create(outfile)
        defer file.Close()
        
        PNGsignature := "\x89PNG\r\n\x1A\n" // 5.2 PNG signature
        file.Write([]byte(PNGsignature))
        
        // 5.3 Chunk fields := Length, (Chunk Type, Chunk Data), CRC
        ckdbuf := make([]byte, 256) // 5.3 CHUNK DATA buffer
        chunkd := string(ckdbuf)
        
	//--------------------------------- IHDR
        // 11.2.2 IHDR Image header
	// https://www.w3.org/TR/PNG/#11IHDR
        IHDRtype := "IHDR" // IHDR chunk type
	chunkd = IHDRtype;
        chunkd += hton32b(64) // Width
        chunkd += hton32b(64) // Hight 
        chunkd += string(1) // Bit depth, 1 for monocrome
        chunkd += string(0) // Color type, 0 for Grayscalse
        chunkd += string(0) // Compression method, 0 for deflate/inflate 
        chunkd += string(0) // Filter method, 0 for adaptive
        chunkd += string(0) // Interlace method, 0 for no interlace

        // 5.3 CRC - for chunk type and chunk data
	crc := crc32.ChecksumIEEE([]byte(chunkd))
	fmt.Fprintf(os.Stderr,"CRC=%X\n",crc)
        putNetInt32(file,uint32(len(chunkd)-4)) // chunk data length, excluding type filed
        file.Write([]byte(chunkd))
	putNetInt32(file,crc)

	//--------------------------------- PLTE
	// 11.2.2.3 PLTE Pallete
	// https://www.w3.org/TR/PNG/#11PLTE
	PLTEtype := "PLTE"
	chunkd = PLTEtype;
	// empty pallete

	crc = crc32.ChecksumIEEE([]byte(chunkd))
	fmt.Fprintf(os.Stderr,"CRC=%X\n",crc)
        putNetInt32(file,uint32(len(chunkd)-4)) // chunk data length, excluding type filed
        file.Write([]byte(chunkd))
	putNetInt32(file,crc)

	//--------------------------------- IDAT
	// 11.2.2.4 IDAT Image data
	IDATtype := "IDAT"
	chunkd = IDATtype;
	// empty data

	crc = crc32.ChecksumIEEE([]byte(chunkd))
	fmt.Fprintf(os.Stderr,"CRC=%X\n",crc)
        putNetInt32(file,uint32(len(chunkd)-4)) // chunk data length, excluding type filed
        file.Write([]byte(chunkd))
	putNetInt32(file,crc)

	//--------------------------------- IEND
	// 11.2.2.4 IEND Image trailer
	IENDtype := "IEND"
	chunkd = IENDtype;

	crc = crc32.ChecksumIEEE([]byte(chunkd))
	fmt.Fprintf(os.Stderr,"CRC=%X\n",crc)
        putNetInt32(file,0) // chunk data length, excluding type filed
        file.Write([]byte(chunkd))
	putNetInt32(file,crc)

        fmt.Fprintf(os.Stderr,"-- created %s (%d x %d)\n",outfile,genw,genh) 
}

2020-0518 SatoxITS

Go言語はじめました

これからはプログラムはGoで書こう、そう思ってはいたのですが、環境整備に手間をとられてHello Worldやっただけで止まっていました。DeleGateサーバの経費節減計画も一段落、delegate.orgも無事復活できて和んでいたところ、Goの事を思い出したので、本日決行です。

インストール

まずはインストールですが、Goのサイトからパッケージをダウンロードしてインストール。クリックするだけ超絶簡単。ただ、コマンドラインからgoと打っても、無いといわれます。

% which go
go not found
% echo $PATH
/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin

それでTerminalを起動し直してみると、自動的にパスが追加されて通りました。ドキュメントを見たらそうしろと書いてありました。

% which go
/usr/local/go/bin/go
% echo $PATH
/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/go/bin

うーん、このパスの追加はどういう仕掛けでできてるんでしょうか?すごく興味がありますが、それは置いておき。

性能測定

それでさっそくHello Worldですが(笑)、実行がかなりもっさりしています。

% time go run hello.go
Hello World!!
go run hello.go  0.16s user 0.10s system 113% cpu 0.230 total

0.2秒?インタープリタが重いんでしょうか?コンパイルしてみます。

% time go build hello.go
go build hello.go  0.06s user 0.07s system 141% cpu 0.095 total
% time ./hello
Hello World!!
./hello  0.00s user 0.00s system 77% cpu 0.004 total

4ミリ秒と出ました。妥当な時間です。

XcodeでGo

と思ったら、GoがXcodeのプロジェクトの選択肢にありません。はて?とりあえずスキップ。

GoでHTTPサーバ

とりあえずやってみたいのは HTTP サーバです。こんな感じでどうでしょう?

// 2020-0517-01 Orange Pekoe HTTP Server v0.0.1 by ITS more :-)
package main
import ( "net" )
func main() {
        port, _ := net.ResolveTCPAddr("tcp", ":47401" )
        sock, _ := net.ListenTCP("tcp", port)
        for {   
                conn, _ := sock.Accept()
                go serv(conn)
        }
}
func serv(conn net.Conn) {
        req := make([]byte, 1024)
        conn.Read(req)
        res := "HTTP/1.0 200 Ok\r\n\r\nHello! (^-^)/ ITS more"
        conn.Write([]byte(res))
        conn.Close()
}
Orange Pekoe HTTP Server v0.0.1

動きました。簡単すぎてけしからんですね(笑)

サーバ公開

さっそく公開しましょう(^-^)。delegate9.org に持って行って実行です。Ubuntu で sudo apt install golang-go。しかるのちに go run pekoe.go。Azure の Networking で 47401 を開通させて、アクセスしてみます。

Orange Pekoe HTTP Server working on delegate9.org

問題ないですね。昔の事を思うと、簡単過ぎて悲しくなります。

エコーサーバ

このままサクサク進むと思っていたところ、落とし穴に落ちました。最近の?ブラウザって、text/plain を自分では表示しないのがデフォなんですね。Go言語をまるで知らないので、何かの書き間違いなのだろうかと苦しみました。とりあえず text/html にして <plaintext> を突っ込みました。

// 2020-0517-02 Orange Pekoe HTTP Server v0.0.2 by ITS more :-)
package main
import ( "net" )
func serv(conn net.Conn) {
        qbuf := make([]byte, 1024)
        qlen, _ := conn.Read(qbuf)
        qstr := string(qbuf[:qlen])

        resh := "HTTP/1.0 200 Ok\r\n"
        resh += "Content-Type: text/html\r\n"
        resh += "\r\n"
        resh += "Your request:<plaintext>"
        conn.Write([]byte(resh))
        conn.Write([]byte(qstr))
        conn.Write([]byte("--end--\r\n"))
        conn.Close()
}
func main() {
        port, _ := net.ResolveTCPAddr("tcp", ":47401" )
        sock, _ := net.ListenTCP("tcp", port)
        for {
                conn, _ := sock.Accept()
                go serv(conn)
        }
}
リクエストのエコーできました。

やれやれ。

プログラムを送り込んで実行する

これ、この危険な遊びは、一番やってみたかった事です。Goのデモサイトでもやってますし。たぶん eval とか出来るはずです。… … やはり Eval() でした。

// 2020-0517-03 Orange Pekoe HTTP Server v0.0.3 by ITS more :-)
package main
import (
        "net"
        "fmt"
        "strings"
        "strconv"
        "go/token"
        "go/types"
)
func myid()(string){
        return "OPS/0.0.3 (Orange Pekoe)"
}
func rhdr(rcode int, ctype string, rmsg string)(string){
        return strings.Join([]string{
                "HTTP/1.0 " + strconv.Itoa(rcode) + " "+rmsg,
                "Content-Type: " + ctype,
                "Server: " + myid(),
        "",""},"\r\n")
}
func resp(conn net.Conn, str string){
        conn.Write([]byte(str))
}
func eval(conn net.Conn, xgocode string){
        gocode := udec(xgocode)
        rbuf := make([]byte, 1024)
        rstr := string(rbuf)
        fset := token.NewFileSet()
        rval, _ := types.Eval(fset,nil,token.NoPos,gocode)
        rstr = fmt.Sprintf("%d",rval.Value)
        rmsg := rhdr(200,"text/html","OK")
        rmsg += "Go-Code:<pre>\t" + henc(gocode) + "</pre>"
        rmsg += "Evaluated:<pre>\t"
        resp(conn,rmsg + henc(rstr) + "</pre>\r\n")
        resp(conn,"<i>Powered by "+myid()+"</i>\r\n")
}
func echo(conn net.Conn, qstr string){
        rmsg := rhdr(200,"text/html","OK")
        resp(conn,rmsg)
        resp(conn,"<HR>Your Request<HR><pre>")
        resp(conn,henc(qstr) + "</pre>\r\n")
        resp(conn,"<HR>My Response<HR><pre>")
        resp(conn,henc(rmsg) + "</pre>\r\n")
        resp(conn,"<HR>\r\n")
}
func henc(str string)(string){
        return strings.Replace(str,"<","<",-1)
}
func udec(str string)(string){
        return strings.Replace(str,"%3C","<",-1)
}
func serv(conn net.Conn) {
        qbuf := make([]byte, 1024)
        qlen, _ := conn.Read(qbuf)
        qstr := string(qbuf[:qlen])
        qarg := strings.Split(qstr," ")
        url := qarg[1]

        if( strings.HasPrefix(url,"/eval?") ){
                eval(conn,strings.Split(url,"?")[1])
        }else{
                echo(conn,qstr)
        }
        conn.Close()
}
func main() {
        port, _ := net.ResolveTCPAddr("tcp", ":47401" )
        sock, _ := net.ListenTCP("tcp", port)
        for {
                conn, _ := sock.Accept()
                go serv(conn)
        }
}
Remote calc. 🙂

動いた。

オブジェクトのサイズは

Go言語も少し覚えたし、めでたしめでたしです。というか、C言語ユーザとしては、全く違和感の無い言語ですね。それで、バイナリのサイズはどのくらいでしょうか?

% go build pekoe.go
% ls -l pekoe
-rwx------  1 sato  staff  4601840 May 17 17:17 pekoe
% size pekoe
__TEXT	__DATA	__OBJC	others  	dec
3256320	283720	0	17012720	20552760

おっとびっくり!DeleGateより少しでかい… ネットワーク系がでかいんでしょうか?ちなみに Hello World は…

% ls -l hello
-rwx------  1 sato  staff  2169960 May 17 17:24 hello
% size hello
__TEXT	__DATA	__OBJC	others  	dec
1462272	266632	0	16895080	18623984
% nm -n pekoe | more
...
0000000001001000 t _go.buildid ## seems offset 16MB

それだけでもこれですか… Evalを使ってたらデカくなるだろうとは思いますが。いったい何が入っているのでしょう?

% nm -n hello
X-D

組み込み系コンパイラなら、参照しない関数はざっくり切ってくれるのですが。セキュリティ上もそのほうが良いと思われます。きっと、ビルドのオプションにあるのでしょう。

実行時のメモリ消費は

とりあえず大人しく待ち受け状態でですが、コンパイル版(ネイティブコード)の場合、ps で見るとこんな様子です:

      SZ    RSS    TIME CMD
 4976972   1808 0:00.01 ./pekoe

一方、インタープリタ?版の場合:

      SZ    RSS TIME    CMD
 5016516  14232 0:00.24 go run pekoe.go
 4977996   2580 0:00.00 /var/folders/bw/8dlt3xks44727rb0y662kw_m0000gn/T/go-build753928546/b001/exe/pekoe

評価

これまで何十年、ほぼC言語一筋だったプログラマから見て、Goの記法はとても自然で、ほとんど違和感を感じません。言語、ツール、ライブラリのドキュメントはちゃんとしてます。とても素直でさっぱりとした言語と環境だと思われ、なんとなくこんな感じかなという勘が、だいたい当たります。とても気持ちが良い。辛い思いをしたWin32とか組み込み系 🙁 の世界とはえらい違いです。

私が今必要としているHTTPサーバ機能は、実際のところ、今日作ってみたこれ程度のものなので、それが100行程度で書け、好きなようにイジりまわせるというのは素晴らしいです。性能的にも問題なさそうだし。どこへ行ってもネイティブで動くし … 

もう、Goって最高!

私の歴史が今日、動きました 🙂

2020-0517 SatoxITS

ぱちもん.shop

経理:昨日もまた、XSOnamaeからたくさん請求書が届いてましたが。

社長:そう、おそらく我が社設立以来最大のヒット作「pachimon.shop」をゲットしました。

広報:わかりやすいですね。イメージが湧いてきました。

開発:どういうコンセプトでしょうか?

基盤:よく売れ残ってましたね。

営業:それ、売れますかね?

社長:いや、当社の倫理からして、転売が目的ということはありません。うちの自社製品の産直売り場にしようと思っているのです。

営業:(ため息)

経理:ではこの「pachimonotaro.com」というのは何ですか?

佐藤:それはね、ヒトとして、もののあはれをしれば自然に出てきてしまうもの、抑えることができないものなのです。かの本居宣長も、さかのぼれば紀貫之から、そう言ってますから。

開発:別の言い方をすると、オヤジギャグのことですね。

「日本語OS」をやめた日

OSで使用する言語を英語に変更しました。

英語、というよりはASCIIです。一つにはソフトウェアを開発する際にファイル名やら日付やらメッセージやらにASCII以外が混じっていると、扱いにくい、邪魔臭いためでした。

特に不愉快だったGoogleDrive の「マイドライブ」が「My Drive」になって非常に気分が良いです(笑)

また一般ユーザの立場からみても、Windowsなどが「日本語化」されていることがそれほどのメリットとは感じていませんでした。もともとが英語の概念で作られた機能の名前を、不自然に日本語化してわかりにくかったり、不統一だったりして、使いにくいことも多いです。特にWindows と Mac での日本語の違い。Operaの日本語も少し独自です。

もともとコンピュータのコマンドやメッセージやマニュアルが英語でしかなかった時代に育ったせいかも知れません。

昔、コンピュータやネットワークは非日常的でちょっとシャレた世界でしたから、お仕事臭い日本語が入ってくるのに違和感を覚えたという面もあります。メールの Subject が「件名」と称されるようになった時には、不快感に近い違和感を持ったものです。

もちろん、長い文章だと日本語のほうが良いですが、短くて定型的なコマンド、メニュー、メッセージ、(生成される)ファイル名、その他カタカナで表現される容れ物に入れるのは、やはり英語のほうが良いと思います。

いったん英語のサラ地に戻ってから、本当に必要な部分を日本語にすれば良いかなと思っています。まあ、GUIのカレンダーだけは日本語表示のほうが良いと思いますけど。

2020-0516 SatoxITS

ブランド名

社長:社名を変えようと思います。

全員:ええーっ!

社長:新しい会社名は「ライトクリック」です。

広報:わかりやすいですね。イメージが湧いてきました。

基盤:right click ですか?lightじゃないですよね?

経理:今朝もまたドメイン名取ってましたね。

営業:なぜですか?

社長:創立以来いろいろやって来るうちに、我が社ができること、やるべきことは、そこにこそあると思うようになったからです。そこは未開ではありませんが広い余地が残されており、今後大きく広げられる可能性を秘めていると思うのです。フロントエンドにおけるフロンティアであり、我が社のバックエンドへの最適な入り口と言えます。これは、ニッチではありません。

開発:コンテクストメニューという意味ですね。文脈に適して生成されるプログラマブルでカスタマイズ可能なメニュー。

社長:そういう事です。ウィキではこう要約していますね。

コンテクストメニュー コンテキストメニューとは、グラフィカルユーザインタフェース内のアイテムをクリックすることでポップアップするメニューのことであり、操作や実行中アプリケーション、選択したアイテムのコンテキストによって変化するオプションの一覧を提供する。ショートカットメニューや、右クリックメニューともいう。 [Wikipedia]

社長:これをより一般化して捉えると、コンセプト的にも技術的にも、我が社の方向性にぴったりだと思うわけです。

基盤:Wikiの人はクじゃなくてキ派ですね。プログラマじゃないな。

営業:現社名「ITS more / いつも」は文脈に応じて硬軟使い分けられて使い良いと思っているのですが。社名ではなくブランド名ということではどうでしょう?

社長:たしかに。いつもには思い入れもありますしね。じゃあそうしましょう。

経理:請求書によりますと、.cc, .click, .site, .tech, .tokyo, .work 6ドメイン合わせて 1,428(税込み)。リーズナブルと思います。

社長:1円ドメイン(right-click.work)というのを初めて買いましたよ。6件同時というのも初めてです。例によってXSOnamaeから気が狂ったように**完了通知のメールが来ました(笑)1秒のうちに37件です。いつもうるさいのでようやく、自動振り分けルールをつけました。
で、その確認も兼ねて「light-click」シリーズも取っておきました(笑)こっちのほうが洒落ててよいですね。