HTTP ResponseController

相关的issue:

在此之前http sever的超时都是在http server级别指定的,包括ReadTimeoutWriteTimeouthttp.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.Causeerrors.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)
}