[译]Go 1.8 新特性

译自 tylerchr 的 What's Coming in Go 1.8

随着Go 1.8 新特性的开发工作已经冻结,Go 1.8 将在2017年2月左右发布,现在让我们看一些在Go 1.8更有趣的API的改变。

HTTP server connection draining

Brad Fitzpatrick最近关闭了一个将近四年的issue,这个issue请求实现http.Server的连接耗尽(draining)的功能。现在可以调用srv.Close可以立即停止http.Server,也可以调用srv.Shutdown(ctx)等待已有的连接处理完毕(耗尽,draining, github.com/tylerb/graceful 的用户应该熟悉这个特性)。

下面这个例子中,服务器当收到SIGINT信号后(^C)会优雅地关闭。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
package main
import (
"context"
"io"
"log"
"net/http"
"os"
"os/signal"
"time"
)
func main() {
// subscribe to SIGINT signals
stopChan := make(chan os.Signal)
signal.Notify(stopChan, os.Interrupt)
mux := http.NewServeMux()
mux.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
time.Sleep(5 * time.Second)
io.WriteString(w, "Finished!")
}))
srv := &http.Server{Addr: ":8081", Handler: mux}
go func() {
// service connections
if err := srv.ListenAndServe(); err != nil {
log.Printf("listen: %s\n", err)
}
}()
<-stopChan // wait for SIGINT
log.Println("Shutting down server...")
// shut down gracefully, but wait no longer than 5 seconds before halting
ctx, _ := context.WithTimeout(context.Background(), 5*time.Second)
srv.Shutdown(ctx)
log.Println("Server gracefully stopped")
}

一旦收到SIGINT信号,服务器会立即停止接受新的连接,srv.ListenAndServe()会返回http.ErrServerClosedsrv.Shutdown会一直阻塞,直到所有未完成的request都被处理完以及它们底层的连接被关闭。

更复杂的处理可以通过context实现,例如使用context.Timeout实现最大的关闭等待时间。你可以尝试复制 https://github.com/tylerchr/examples/tree/master/draining 中的例子并实现它。

通过 http.Pusher 实现 HTTP/2.0 server push

HTTP/2.0 包含 Server Push 特性, 允许 HTTP/2 服务器主动地发送额外的 HTTP response 给客户端,即使客户端没有发送请求。目标是在客户端无需请求的情况下,服务器可以及时地将客户端所需的资源推送给客户端。可以查看wiki HTTP/2 Server Push看具体的例子。

如果一个服务器支持 HTTP/2, 提供给 handler 的 http.ResponseWriter 会实现 http.Pusher 接口。Handler 可以使用这个功能区触发Server Push, 虚拟的请求(synthetic request)可以被注册的 http.Server Handler所处理。

下面的程序处理/index.html, 可以push一个/static/gopher.png:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package main
import "net/http"
func main() {
http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("./static"))))
http.Handle("/index.html", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// server push is available if w implements http.Pusher
if p, ok := w.(http.Pusher); ok {
p.Push("/static/gopher.png", nil}
}
// load the main page
w.Header().Set("Content-Type", "text/html")
w.Write([]byte(`<img src="/static/gopher.png" />`))
}))
http.ListenAndServeTLS(":4430", "cert.pem", "key.pem", nil)
}

你可以从 https://github.com/tylerchr/examples/serverpush 克隆这个例子,下面是在 Chrome 54 访问的结果:

明显地可以在 Initiator 那一列中看到 gopher.pngPush,你也可以看到标蓝色的gopher.png先于/index.html被接收过来,这表明这个资源先于请求之前被推送到客户端。HTML下载完后<img>可以显示。

有人可能会问如何写一个测试用来校验实现 Server Push的 Handler。因为http.NewTLSServer没有启动 HTTP/2 服务器,httptest.ResponseRecorder也没有实现http.Pusher。我的解决方案是包装httptest.ResponseRecorder实现Pusher接口,这里有个例子

database/sql

database/sql包有几个主要的改变,可以让用户更好的控制数据库查询,允许用户更好的利用数据库的特性。

  • 查询可以使用context.Context取消查询
  • 纯数据库列类型可以通过sql.ColumnType得到
  • 如果底层数据库支持,查询可以使用命名参数

更多的细节可以阅读Daniel Theophanes的文章 What is new in database/sql?,他实现了大部分的改变。

plugin包实现动态插件

新增加的标准库plugin提供了初步的插件支持,它允许程序可以在运行的时候动态的加载插件。

但是这个库看起来还是bug多多,我甚至不能写一个正常的程序来测试它,但是假定它的使用应该如下面的样子:

1
2
3
4
5
6
7
8
9
10
11
// hexify.go
package main
import "encoding/hex"
func Hexify(in string) string {
return hex.EncodeToString([]byte(in))
}
$ go build -buildmode=shared hexify.go
// produces hexify.so
1
2
3
4
5
6
7
8
9
10
11
// main.go
package main
import "plugin"
func main() {
p, _ = plugin.Open("hexify.so")
f := p.Lookup("Hexify")
fmt.Println(f.(func(string) string)("gopher"))
// 676f70686572
}

在这个例子中,hexify.go实现了Hexify函数,它被编译成一个共享库,第二个程序动态加载它。这允许Go程序可以不在编译的时候也能调用其它的库。

别名

别名(aliasing)曾被增加到 Go 1.8 的语言规范中,但是现在又被移除了,看这个说明: this post from Russ Cox,有可能会出现在 Go 1.9中。
这个特性也引起了很多的争议,

指示符别名(Identifier aliasing)用来定义多个类型为同一个类型的语法。一个用处用来重构复杂的代码的时候,允许重新划分包而不必带来类型的不一致。 Ian Lance Taylor举了一个[例子](https://groups.google.com/d/msg/golang-dev/OmjsXkyOQpQ/OrcHWiGUBAAJ):

举个具体的例子,将扩展包golang.org/x/net/context移动到标准库context的过程。因为context已经被广泛地使用,将所有的用户的代码统一转换很困难,因此允许这两个包通用很有必要。

别名的定义如下:

1
type Foo => pkg.Bar

这个语法定义 Foopkg.Bar别名。Foo可以用在任何pkg.Bar出现的地方。以上个例子为例,任何需要类型golang.org/x/net/context的地方都可以用标准库context代替,它们是等价的。

别名也可以用在常量、变量、函数等类型上。

这是一个很有争议的特性,可以参考issue 16339golang-dev post 看大家的讨论。因为它从Go 1.8中移除了,大家可以暂时不用关注这个特性了。

新的slice排序API

统一的slice排序由新的 sort.Slice 函数实现。它允许任意的slice都可以被排序,只需提供一个回调比较函数即可,而不是像以前要提供一个特定的sort.Interface的实现。这个函数没有返回值。想其它的排序函数一样,它提供了原地的排序。

下面的例子根据海拔高度排序知名山峰的slice。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
type Peak struct {
Name string
Elevation int // in feet
}
peaks := []Peak{
{"Aconcagua", 22838},
{"Denali", 20322},
{"Kilimanjaro", 19341},
{"Mount Elbrus", 18510},
{"Mount Everest", 29029},
{"Mount Kosciuszko", 7310},
{"Mount Vinson", 16050},
{"Puncak Jaya", 16024},
}
// does an in-place sort on the peaks slice, with tallest peak first
sort.Slice(peaks, func(i, j int) bool {
return peaks[i].Elevation >= peaks[j].Elevation
})
// peaks is now sorted

通过sort.Interface类型的Len()Swap(i, j int)提供了抽象的排序类型,这是以前的排序方法,而Less(i, j int)作为一个比较回调函数,可以简单地传递给sort.Slice进行排序。

其它

  • 87b1aaa encoding/base64 encoder现在有了严格模式.
  • 6ba5b32 expvar暴露出来,可以用在其它的mux中.
  • 003a598 伪随机码可以通过rand.Uint64()产生 (先前仅支持uint32).
  • 67ea710 增加了一个新的time.Until函数,和time.Since对应.

net/http故意只实现了使用TLS的HTTP/2,你可以查看[issue 14141]https://github.com/golang/go/issues/14141()了解细节。
sort.SliceStable提供了稳定的slice排序,就像以前的sort.Stable一样。

译者增加的内容

Go 1.8 一个很大的特性就是性能的提升,包括二进制文件的大小、编译速度和运行速度。
并且非常大的提升就是提供小于100us GC暂停。

net/http提供了更多的超时设置,比如ReadHeaderTimeoutIdleTimeout

一个完整的改动列表:Go 1.8