Golang1.20其他的标准库更新
目录
HTTP ResponseController⌗
相关的issue:
- net/http: no way of manipulating timeouts in Handler
- net/http: ResponseController to manipulate per-request timeouts (and other behaviors)
在此之前http sever的超时都是在http server级别指定的,包括ReadTimeout
和WriteTimeout
。http.ResponseController
让我们可以Handler级别,只针对某些专门的endpoint设置专门的超时时间。
func main() {
router := http.DefaultServeMux
router.HandleFunc("/fast", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("fast response"))
})
router.HandleFunc("/slow", func(w http.ResponseWriter, r *http.Request) {
// 对该endpoint单独设置超时
rc := http.NewResponseController(w)
rc.SetReadDeadline(time.Now().Add(6 * time.Second))
rc.SetWriteDeadline(time.Now().Add(6 * time.Second))
time.Sleep(5 * time.Second)
w.Write([]byte("slow response"))
})
server := http.Server{
Addr: ":8080",
Handler: router,
ReadTimeout: time.Second,
WriteTimeout: time.Second,
}
err := server.ListenAndServe()
if err != nil {
panic(err)
}
}
New ReverseProxy Rewrite hook⌗
相关的issue:
原先httputil.ReverseProxy提供了Director
字段在一个请求被转发之前进行修改。但是这会有一些安全问题,某些字段可能意外地被转发或者不被转发。新增的Rewrite
字段将incoming请求和outgoing请求区分开来。
使用的范例代码如下
proxyHandler := &httputil.ReverseProxy{
Rewrite: func(r *httputil.ProxyRequest) {
r.SetURL(outboundURL) // Forward request to outboundURL.
r.SetXForwarded() // Set X-Forwarded-* headers.
r.Out.Header.Set("X-Additional-Header", "header set by the proxy")
},
}
bytes⌗
相关issue
func main() {
str := "Hello, World!"
firstPrefix := "Hello,"
secondPrefix := "World!"
thirdPrefix := ""
fmt.Println(strings.CutPrefix(str, firstPrefix))
fmt.Println(strings.CutPrefix(str, secondPrefix))
fmt.Println(strings.CutPrefix(str, thirdPrefix))
// 输出
// World! true
// Hello, World! false
// Hello, World! true
suffix1 := "World!"
suffix2 := "Hello,"
suffix3 := ""
fmt.Println(strings.CutSuffix(str, suffix1))
fmt.Println(strings.CutSuffix(str, suffix2))
fmt.Println(strings.CutSuffix(str, suffix3))
// 输出
// Hello, true
// Hello, World! false
// Hello, World! true
// Clone for bytes, 对clone的slice修改不会影响原slice
b := []byte("Hello, World!")
fmt.Printf("b: %s %p\n", b, b)
bCloned := bytes.Clone(b)
fmt.Printf("bCloned: %s %p\n", bCloned, bCloned)
// 输出
// b: Hello, World! 0xc000184000
// bCloned: Hello, World! 0xc000184010
bCloned[0] = 'h'
fmt.Printf("b: %s %p\n", b, b)
fmt.Printf("bCloned: %s %p\n", bCloned, bCloned)
// 输出
// b: Hello, World! 0xc000184000
// bCloned: hello, World! 0xc000184010
}
context.WithCancelCause⌗
相关issue
在Go1.20之前,如果context
被取消了,我们只能得到两种error:context.DeadlineExceeded
或者 context.Canceled
。
func withTimeoutDemo() {
// 传递一个带Timeout的context,在ctx.Done()阻塞后,超时抛出异常
timeoutDuration := 3 * time.Second
ctx, cancel := context.WithTimeout(context.Background(), timeoutDuration)
defer cancel()
<-ctx.Done()
switch ctx.Err() {
case context.DeadlineExceeded:
fmt.Println("context timeout exceeded")
case context.Canceled:
fmt.Println("context cancelled by force")
}
// 输出:
// context timeout exceeded
}
但在许多情况下,如果context
被通过调用context.CancelFunc()
函数(如上面例子里分配给cancel
变量)。我们无法知道是为什么被取消的。
Go1.20新增了context.WithCancelCause,我们可以在cancel
的时候,传递cancel的实际error信息,并通过context.Cause
和errors.Is
或者errors.As
获取实际error信息。
var ErrTemporarilyUnavailable = fmt.Errorf("service temporarily unavailable")
func withCancelCauseDemo() {
ctx, cancel := context.WithCancelCause(context.Background())
// 主动取消服务,并传递取消原因
cancel(ErrTemporarilyUnavailable)
// error的类型是*cancelError
switch ctx.Err() {
case context.Canceled:
fmt.Println("context cancelled by force")
}
// 获取取消原因
err := context.Cause(ctx)
if errors.Is(err, ErrTemporarilyUnavailable) {
fmt.Printf("cancellation reason: %s\n", err)
}
// 输出:
// cancellation reason: service temporarily unavailable
}
fmt⌗
相关issue:
Errorf函数支持多个%w
。更多信息可以看Golang1.20新特性 multi errors
html/template⌗
相关issue
ES6开始,`
可以用于js字面值表示。但html/template
目前没有正确地将`
按照预期进行处理。
在go1.20.3及其之后的版本,默认情况下,如果模板在js模板字面值中包含Go模板操作,则go直接抛出error,终止模板处理。html/template中增加了GODEBUG标识jstmpllitinterp=1
可以恢复原先的行为。
比如模板为
<script>var x = `{{.X}}`;</script>
变量为
map[string]interface{}{"X": "`+alert(1);`"}
js字面值x
中包含go的模板变量{{.X}}
,会抛出panic信息
panic: {{.X}} appears in a JS template literal
完整demo代码可见: jstmpllitinterp demo
math/rand⌗
在Go1.20之前,我们可以使用math/rand
在启动的时候接收一个确定的seed value来生成伪随机数。通常可以是使用当前时间的unix timestamp作为seed value。
rand.Seed(time.Now().UnixNano())
Go1.20之前,没有rand.Seed(time.Now().UnixNano()),以下代码每次执行都会输出相同的随机数。
Go1.20之后,不需要rand.Seed(time.Now().UnixNano()),每次执行都会输出不同的随机数。如果要行为和原先一致,可以使用GODEBUG=randautoseed=0
。
这是一个 behaviour change。
func main() {
// rand.Seed(time.Now().UnixNano())
fmt.Println(rand.Int())
}
rand.Seed
在1.20之后就被废弃了,虽然按照Go1兼容性需求,应该可以一直使用,但还是建议替换为rand.New(rand.NewSource(seed))
。
time⌗
相关issue:
Go的time
的格式化,需要固定的格式2006-01-02 15:04:05
,其中任何一个值改变了,都会造成非预期的后果,这里是定义了常量避免这类问题。
func main() {
now := time.Now()
fmt.Println("Without Format - ", now)
// 输出
// 2023-10-22 22:21:19.870200423 +0800 +08 m=+0.000034223
formatted := now.Format("2006-01-02 15:04:05")
fmt.Println("Format 2006-01-02 15:04:05 -", formatted)
// 输出
// 2023-10-22 22:21:19
// 2006-01-02 15:04:05 是固定的格式,不可更改,如果更改,会出现奇怪的结果
formatted = now.Format("2007-01-02 15:04:05")
fmt.Println("Format 2007-01-02 15:04:05 -", formatted)
// 输出
// 22007-10-22 22:21:19
formatted = now.Format(time.DateTime)
fmt.Println(formatted)
// 输出
// 2023-10-22 22:21:19
}
其他⌗
其他特性都可以通过api: audit for Go 1.20找到相关的issue,并了解详细信息。
disallow anonymous interface cycles⌗
虽然这不是标准库的变更,但是也可以了解下。
相关issue
这实际是个breaking change,如下代码在go1.19之前编译不会报错。但在go1.20之后,就会报invalid recursive type: anonymous interface refers to itself (see https://go.dev/issue/56103)
。
受影响的代码需要使用go build -gcflags=all=-d=interfacecycles
。
Go1.20:Go 编译器默认会拒绝这些接口循环,但可以使用 go build -gcflags=all=-d=interfacecycles 来进行构建,以确保旧代码的正常编译。如果在候选发布期间有人向 Go 团队报告大量损坏,将会取消此更改。 Go1.22:等到 1.22 版本后 -d=interfacecycles 标志将被删除,旧代码将不再构建该特性。如果有人报告问题,将可以讨论或是推迟删除,给予更多的改造时间。
type J interface {
m(a interface {
J
})
}
func main() {
var j J
j.m(j)
}