標準のnetパッケージには、ホスト名とポート番号を操作する関数がいくつか用意されていますが意外と知られていないようなので、便利だけどあまり知られていない関数を3つ紹介します。
TL;DR
Goで、pathの結合はfilepath.Joinを使えというのは広まっている気がしているけど、同様にホストとポートを:で結合するのはnet.JoinHostPortを使ってほしい。fmt.Sprintfの場合、IPv6アドレスには:が含まれるので困ることになる。
— kadota (@plan9user) 2022年10月17日
ホスト名とポート番号を:で区切られたアドレスに変換する
Goでコードを書くとき、localhost:8080
のようにホスト名とポート番号を:
で区切ったひとつの文字列として表現することがあります。この表記はAddressと呼ばれており、例えば以下のような関数で使われています。
package http func ListenAndServe(addr string, handler Handler) error
package net func Dial(network, address string) (Conn, error) type Addr interface { String() string // string form of address (for example, "192.0.2.1:25", "[2001:db8::1]:80") }
ホスト名とポート番号をアドレスへ変換するとき、fmt.Sprintfを使ったコードをよく見かけますが、ホストとしてIPv6アドレスが与えられた場合に意図しない動作を引き起すことがあります。IPv6アドレスは:
で区切って表記するので、単純に結合してしまうとIPv6アドレスとポート番号の区別がつかなくなり不正なアドレスとなります。
var ( host = "::1" port = 80 ) addr := fmt.Sprintf("%s:%d", host, port) c, err := net.Dial("tcp", addr) // err = "dial tcp: address ::1:80: too many colons in address"
正しくは、IPv6アドレスにポート番号を連結する場合は[::1]:80
のようにIPv6アドレスの部分を[]
で囲む必要があります。この書式はRFC 4038で
Therefore, the IP address parsers that take the port number separated with a colon should distinguish IPv6 addresses somehow. One way is to enclose the address in brackets, as is done with Uniform Resource Locators (URLs) [RFC2732]; for example, http://[2001:db8::1]:80.
と定められています。Goのnetパッケージには、この操作を行うnet.JoinHostPort関数が用意されているので、なるべくこちらを使いましょう。ただし、このときportはstringなので型の変換が必要です。
addr := net.JoinHostPort(host, strconv.Itoa(port))
c, err := net.Dial("tcp", addr)
ホスト名とポート番号を分割する
上記とは逆に、:
で接続された文字列をホスト名とポート番号に分割したい場合は、こちらもnet.SplitHostPortが用意されているのでそのまま使えます。
host, port, err := net.SplitHostPort("[::1]:80") if err != nil { log.Fatalln(err) } fmt.Println(host, port) // ::1 80
ポート番号を文字列から数値にする
これはstrconv.Atoiやstrconv.ParseUintでもそこまで困らないかなと思いますが、net.LookupPortが便利に使えます。strconv.Atoiとの違いは、
- 負数の場合、または65535より大きい値の場合はエラーになる
- redisなどサービス名も対応している
といった特徴があります。
_, err := net.LookupPort("tcp", "-1") // err = "address -1: invalid port" _, err := net.LookupPort("tcp", "65536") // err = "address 65536: invalid port" port, err := net.LookupPort("tcp", "redis") if err != nil { log.Fatalln(err) } fmt.Println(port) // 6379