関連記事
GitHub - devlights/blog-summary: ブログ「いろいろ備忘録日記」のまとめ
概要
以下、自分用のメモです。忘れないようにここにメモメモ。。。
使ったこと無かったのですが、net/textproto というパッケージがあるのを最近知りました。
テキストベースのプロトコル(HTTPとかSMTPとか)の通信処理が作りやすくなるライブラリとのこと。
Package textproto implements generic support for text-based request/response protocols in the style of HTTP, NNTP, and SMTP.
(textprotoパッケージは、HTTP、NNTP、SMTPスタイルのテキストベースのリクエスト/レスポンスプロトコルの汎用サポートを実装します。)
\r\nがデリミタになってて、リクエストにコマンドが入ってて、レスポンスにコードが入っているやつですね。
こんな感じ。
HELO hoge.example.com 250 Hello hoge.example.com
こういうプロトコルを作るときに便利なライブラリです。ついでに ドットエンコーディング なデータを処理するための DotReader と DotWriter というのも用意されています。
サンプル
main.go
いちいちファイル分けるのが面倒なので、サーバとクライアント兼用です。
/* net/textproto パッケージのサンプルです。 */ package main import ( "flag" "fmt" "net" "net/textproto" "strings" ) type ( Args struct { IsServer bool } ) const ( OK = 200 NG = 400 CmdGet = "GET" CmdSet = "SET" CmdQuit = "QUIT" ) var ( args Args ) func init() { flag.BoolVar(&args.IsServer, "server", false, "server mode") } func main() { flag.Parse() if err := run(); err != nil { panic(err) } } func run() error { var err error if args.IsServer { err = runServer() } else { err = runClient() } if err != nil { return err } return nil } func runServer() error { l, err := net.Listen("tcp", ":8888") if err != nil { return err } defer l.Close() // サンプルなので1回だけ受付 // クライアントからは // - SET // - GET // - QUIT // の3コマンドのシーケンスが来るとする conn, err := l.Accept() if err != nil { return err } defer conn.Close() // net/textprotoの接続として処理 // // textproto.NewConn() は、引数に io.ReadWriteCloser を要求しているが // net.Conn は、os.Fileと同様に io.ReadWriteCloser を実装している。 tpConn := textproto.NewConn(conn) defer tpConn.Close() // ウェルカムメッセージ err = tpConn.PrintfLine("%d %s", OK, "WELCOME AVAILABLE COMMANDS: {SET, GET, QUIT}") if err != nil { return err } // コマンド処理 data := make(map[string]string) for { line, err := tpConn.ReadLine() if err != nil { return err } parts := strings.Fields(line) if len(parts) == 0 { tpConn.PrintfLine("%d %s", NG, "コマンドが読み取れません") continue } command := strings.ToUpper(parts[0]) cmdArgs := parts[1:] switch command { case CmdGet: if v, ok := data[cmdArgs[0]]; ok { tpConn.PrintfLine("%d %s", OK, v) } else { tpConn.PrintfLine("%d %s", NG, "KEY NOT FOUND") } case CmdSet: data[cmdArgs[0]] = cmdArgs[1] tpConn.PrintfLine("%d %s", OK, "VALUE SET SUCCESSFULLY") case CmdQuit: tpConn.PrintfLine("%d %s", OK, "BYE") return nil default: tpConn.PrintfLine("%d %s", NG, "UNKNOWN COMMAND") } } } func runClient() error { conn, err := net.Dial("tcp", ":8888") if err != nil { return err } defer conn.Close() tpConn := textproto.NewConn(conn) defer tpConn.Close() // helper funcs var ( send = func(tp *textproto.Conn, msg string) error { fmt.Printf("< %s\n", msg) return tp.PrintfLine("%s", msg) } recv = func(tp *textproto.Conn) error { code, msg, err := tp.ReadCodeLine(OK) if err != nil { return err } fmt.Printf("%d %s\n", code, msg) return nil } ) // WELCOME { err = recv(tpConn) if err != nil { return err } } // SET m := fmt.Sprintf("%s %s %s", CmdSet, "Hello", "World") { err = send(tpConn, m) if err != nil { return err } err = recv(tpConn) if err != nil { return err } } // GET m = fmt.Sprintf("%s %s", CmdGet, "Hello") { err = send(tpConn, m) if err != nil { return err } err = recv(tpConn) if err != nil { return err } } // 存在しないコマンド m = fmt.Sprintf("%s %s", "GOLANG", "HELLO") { err = send(tpConn, m) if err != nil { return err } err = recv(tpConn) if err != nil { fmt.Printf("%[1]s (%[1]T)\n", err) } } // QUIT m = CmdQuit { err = send(tpConn, m) if err != nil { return err } err = recv(tpConn) if err != nil { return err } } return nil }
データの送信には *textproto.Conn.PrintfLine を、データの受信には *textproto.Conn.ReadLine を使います。このメソッドで送受信すると \r\n の付与やレスポンスのコード解析などをうまくやってくれる。
Taskfile.yml
# https://taskfile.dev version: "3" tasks: default: cmds: - task: build - task: run build: cmds: - go build -o app . run: cmds: - ./app -server & - sleep 1 - ./app watch: cmds: - cmd: sudo tcpdump -i lo -n 'tcp port 8888' -S -X interactive: true
shell
実行すると以下のように出力されます。
$ task task: [build] go build -o app . task: [run] ./app -server & task: [run] sleep 1 task: [run] ./app 200 WELCOME AVAILABLE COMMANDS: {SET, GET, QUIT} < SET Hello World 200 VALUE SET SUCCESSFULLY < GET Hello 200 World < GOLANG HELLO 400 UNKNOWN COMMAND (*textproto.Error) < QUIT 200 BYE
ついでにパケットも眺めてみましょう。
$ task watch task: [watch] sudo tcpdump -i lo -n 'tcp port 8888' -S -X tcpdump: verbose output suppressed, use -v[v]... for full protocol decode listening on lo, link-type EN10MB (Ethernet), snapshot length 262144 bytes 09:16:24.913973 IP 127.0.0.1.53082 > 127.0.0.1.8888: Flags [S], seq 3624143825, win 43690, options [mss 65495,sackOK,TS val 316896963 ecr 0,nop,wscale 7], length 0 0x0000: 4500 003c f12b 4000 4006 4b8e 7f00 0001 E..<.+@.@.K..... 0x0010: 7f00 0001 cf5a 22b8 d804 0bd1 0000 0000 .....Z"......... 0x0020: a002 aaaa fe30 0000 0204 ffd7 0402 080a .....0.......... 0x0030: 12e3 76c3 0000 0000 0103 0307 ..v......... 09:16:24.913989 IP 127.0.0.1.8888 > 127.0.0.1.53082: Flags [S.], seq 3473127062, ack 3624143826, win 43690, options [mss 65495,sackOK,TS val 316896963 ecr 316896963,nop,wscale 7], length 0 0x0000: 4500 003c 0000 4000 4006 3cba 7f00 0001 E..<..@.@.<..... 0x0010: 7f00 0001 22b8 cf5a cf03 b696 d804 0bd2 ...."..Z........ 0x0020: a012 aaaa fe30 0000 0204 ffd7 0402 080a .....0.......... 0x0030: 12e3 76c3 12e3 76c3 0103 0307 ..v...v..... 09:16:24.913998 IP 127.0.0.1.53082 > 127.0.0.1.8888: Flags [.], ack 3473127063, win 342, options [nop,nop,TS val 316896963 ecr 316896963], length 0 0x0000: 4500 0034 f12c 4000 4006 4b95 7f00 0001 E..4.,@.@.K..... 0x0010: 7f00 0001 cf5a 22b8 d804 0bd2 cf03 b697 .....Z"......... 0x0020: 8010 0156 fe28 0000 0101 080a 12e3 76c3 ...V.(........v. 0x0030: 12e3 76c3 ..v. 09:16:24.914117 IP 127.0.0.1.8888 > 127.0.0.1.53082: Flags [P.], seq 3473127063:3473127112, ack 3624143826, win 342, options [nop,nop,TS val 316896963 ecr 316896963], length 49 0x0000: 4500 0065 abdc 4000 4006 90b4 7f00 0001 E..e..@.@....... 0x0010: 7f00 0001 22b8 cf5a cf03 b697 d804 0bd2 ...."..Z........ 0x0020: 8018 0156 fe59 0000 0101 080a 12e3 76c3 ...V.Y........v. 0x0030: 12e3 76c3 3230 3020 e382 88e3 8186 e381 ..v.200......... 0x0040: 93e3 819d 2120 e382 b3e3 839e e383 b3e3 ....!........... 0x0050: 8389 efbc 9a47 4554 2c20 5345 542c 2051 .....GET,.SET,.Q 0x0060: 5549 540d 0a UIT.. 09:16:24.914128 IP 127.0.0.1.53082 > 127.0.0.1.8888: Flags [.], ack 3473127112, win 342, options [nop,nop,TS val 316896963 ecr 316896963], length 0 0x0000: 4500 0034 f12d 4000 4006 4b94 7f00 0001 E..4.-@.@.K..... 0x0010: 7f00 0001 cf5a 22b8 d804 0bd2 cf03 b6c8 .....Z"......... 0x0020: 8010 0156 fe28 0000 0101 080a 12e3 76c3 ...V.(........v. 0x0030: 12e3 76c3 ..v. 09:16:24.914203 IP 127.0.0.1.53082 > 127.0.0.1.8888: Flags [P.], seq 3624143826:3624143843, ack 3473127112, win 342, options [nop,nop,TS val 316896963 ecr 316896963], length 17 0x0000: 4500 0045 f12e 4000 4006 4b82 7f00 0001 E..E..@.@.K..... 0x0010: 7f00 0001 cf5a 22b8 d804 0bd2 cf03 b6c8 .....Z"......... 0x0020: 8018 0156 fe39 0000 0101 080a 12e3 76c3 ...V.9........v. 0x0030: 12e3 76c3 5345 5420 4865 6c6c 6f20 576f ..v.SET.Hello.Wo 0x0040: 726c 640d 0a rld.. 09:16:24.914212 IP 127.0.0.1.8888 > 127.0.0.1.53082: Flags [.], ack 3624143843, win 342, options [nop,nop,TS val 316896963 ecr 316896963], length 0 0x0000: 4500 0034 abdd 4000 4006 90e4 7f00 0001 E..4..@.@....... 0x0010: 7f00 0001 22b8 cf5a cf03 b6c8 d804 0be3 ...."..Z........ 0x0020: 8010 0156 fe28 0000 0101 080a 12e3 76c3 ...V.(........v. 0x0030: 12e3 76c3 ..v. 09:16:24.914248 IP 127.0.0.1.8888 > 127.0.0.1.53082: Flags [P.], seq 3473127112:3473127140, ack 3624143843, win 342, options [nop,nop,TS val 316896963 ecr 316896963], length 28 0x0000: 4500 0050 abde 4000 4006 90c7 7f00 0001 E..P..@.@....... 0x0010: 7f00 0001 22b8 cf5a cf03 b6c8 d804 0be3 ...."..Z........ 0x0020: 8018 0156 fe44 0000 0101 080a 12e3 76c3 ...V.D........v. 0x0030: 12e3 76c3 3230 3020 5641 4c55 4520 5345 ..v.200.VALUE.SE 0x0040: 5420 5355 4343 4553 5346 554c 4c59 0d0a T.SUCCESSFULLY.. 09:16:24.914285 IP 127.0.0.1.53082 > 127.0.0.1.8888: Flags [P.], seq 3624143843:3624143854, ack 3473127140, win 342, options [nop,nop,TS val 316896963 ecr 316896963], length 11 0x0000: 4500 003f f12f 4000 4006 4b87 7f00 0001 E..?./@.@.K..... 0x0010: 7f00 0001 cf5a 22b8 d804 0be3 cf03 b6e4 .....Z"......... 0x0020: 8018 0156 fe33 0000 0101 080a 12e3 76c3 ...V.3........v. 0x0030: 12e3 76c3 4745 5420 4865 6c6c 6f0d 0a ..v.GET.Hello.. 09:16:24.914327 IP 127.0.0.1.8888 > 127.0.0.1.53082: Flags [P.], seq 3473127140:3473127151, ack 3624143854, win 342, options [nop,nop,TS val 316896963 ecr 316896963], length 11 0x0000: 4500 003f abdf 4000 4006 90d7 7f00 0001 E..?..@.@....... 0x0010: 7f00 0001 22b8 cf5a cf03 b6e4 d804 0bee ...."..Z........ 0x0020: 8018 0156 fe33 0000 0101 080a 12e3 76c3 ...V.3........v. 0x0030: 12e3 76c3 3230 3020 576f 726c 640d 0a ..v.200.World.. 09:16:24.914360 IP 127.0.0.1.53082 > 127.0.0.1.8888: Flags [P.], seq 3624143854:3624143860, ack 3473127151, win 342, options [nop,nop,TS val 316896963 ecr 316896963], length 6 0x0000: 4500 003a f130 4000 4006 4b8b 7f00 0001 E..:.0@.@.K..... 0x0010: 7f00 0001 cf5a 22b8 d804 0bee cf03 b6ef .....Z"......... 0x0020: 8018 0156 fe2e 0000 0101 080a 12e3 76c3 ...V..........v. 0x0030: 12e3 76c3 5155 4954 0d0a ..v.QUIT.. 09:16:24.914391 IP 127.0.0.1.8888 > 127.0.0.1.53082: Flags [P.], seq 3473127151:3473127160, ack 3624143860, win 342, options [nop,nop,TS val 316896963 ecr 316896963], length 9 0x0000: 4500 003d abe0 4000 4006 90d8 7f00 0001 E..=..@.@....... 0x0010: 7f00 0001 22b8 cf5a cf03 b6ef d804 0bf4 ...."..Z........ 0x0020: 8018 0156 fe31 0000 0101 080a 12e3 76c3 ...V.1........v. 0x0030: 12e3 76c3 3230 3020 4259 450d 0a ..v.200.BYE.. 09:16:24.914412 IP 127.0.0.1.8888 > 127.0.0.1.53082: Flags [F.], seq 3473127160, ack 3624143860, win 342, options [nop,nop,TS val 316896963 ecr 316896963], length 0 0x0000: 4500 0034 abe1 4000 4006 90e0 7f00 0001 E..4..@.@....... 0x0010: 7f00 0001 22b8 cf5a cf03 b6f8 d804 0bf4 ...."..Z........ 0x0020: 8011 0156 fe28 0000 0101 080a 12e3 76c3 ...V.(........v. 0x0030: 12e3 76c3 ..v. 09:16:24.914427 IP 127.0.0.1.53082 > 127.0.0.1.8888: Flags [F.], seq 3624143860, ack 3473127161, win 342, options [nop,nop,TS val 316896963 ecr 316896963], length 0 0x0000: 4500 0034 f131 4000 4006 4b90 7f00 0001 E..4.1@.@.K..... 0x0010: 7f00 0001 cf5a 22b8 d804 0bf4 cf03 b6f9 .....Z"......... 0x0020: 8011 0156 fe28 0000 0101 080a 12e3 76c3 ...V.(........v. 0x0030: 12e3 76c3 ..v. 09:16:24.914440 IP 127.0.0.1.8888 > 127.0.0.1.53082: Flags [.], ack 3624143861, win 342, options [nop,nop,TS val 316896963 ecr 316896963], length 0 0x0000: 4500 0034 abe2 4000 4006 90df 7f00 0001 E..4..@.@....... 0x0010: 7f00 0001 22b8 cf5a cf03 b6f9 d804 0bf5 ...."..Z........ 0x0020: 8010 0156 fe28 0000 0101 080a 12e3 76c3 ...V.(........v. 0x0030: 12e3 76c3 ..v. ^C 15 packets captured 30 packets received by filter 0 packets dropped by kernel
そのまんまですが、ちゃんとその通りに流れてて、ペイロードの末尾に「0x0d0a」、つまり、\r\n が付与されていますね。
参考情報
try-golang/examples/basic/bufferop/to_readwritecloser.go at main · devlights/try-golang · GitHub
Goのおすすめ書籍
過去の記事については、以下のページからご参照下さい。
サンプルコードは、以下の場所で公開しています。





