了解一下http2和h2c (HTTP/2 over TCP,HTTP/2 without TLS)。
http/1.1 的服务器
我们经常会在代码中启动一个http服务器,最简单的http/1.1服务器如下所示:
1
2
3
4
5
6
7
http.Handle("/foo" , fooHandler)
http.HandleFunc("/bar" , func (w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, %q" , html.EscapeString(r.URL.Path))
})
log.Fatal(http.ListenAndServe(":8080" , nil ))
使用Go开发web服务非常的简单,快速。
http/1.1 的服务器 with TLS
如果想让http/1.1服务器支持TLS, 可以使用如下的代码:
1
2
3
4
5
6
7
http.Handle("/foo" , fooHandler)
http.HandleFunc("/bar" , func (w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, %q" , html.EscapeString(r.URL.Path))
})
log.Fatal(http.http.ListenAndServeTLS(":443" , "server.crt" , "server.key" ,nil ))
至于server.crt
和 server.key
,你可以使用你从CA购买的证书,你也可以使用下面的测试证书。
为了测试,你可以创建CA证书和你的服务器使用的证书。
1、 创建CA证书
1
2
$ openssl genrsa -out rootCA.key 2048
$ openssl req -x509 -new -nodes -key rootCA.key -days 1024 -out rootCA.pem
然后把rootCA.pem
加到你的浏览器的证书中
2、 创建证书
1
2
3
$ openssl genrsa -out server.key 2048
$ openssl req -new -key server.key -out server.csr
$ openssl x509 -req -in server.csr -CA rootCA.pem -CAkey rootCA.key -CAcreateserial -out server.crt -days 500
免费证书
如果你不想从CA花钱购买证书, 也不想配置测试证书,那么你可以使用let's encrypt
的免费证书, 而且let's encrypt
目前支持通配符证书,使用也是很方便的。
Go的扩展包中提供了let's encrypt
的支持。
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
package main
import (
"crypto/tls"
"log"
"net/http"
"golang.org/x/crypto/acme/autocert"
)
func main() {
certManager := autocert.Manager{
Prompt: autocert.AcceptTOS,
HostPolicy: autocert.HostWhitelist("example.com" ),
Cache: autocert.DirCache("certs" ),
}
http.HandleFunc("/" , func (w http.ResponseWriter, r *http.Request) {
w.Write([]byte ("Hello world" ))
})
server := &http.Server{
Addr: ":443" ,
TLSConfig: &tls.Config{
GetCertificate: certManager.GetCertificate,
},
}
go http.ListenAndServe(":80" , certManager.HTTPHandler(nil ))
log.Fatal(server.ListenAndServeTLS("" , "" ))
}
或者更简单的:
1
log.Fatal(http.Serve(autocert.NewListener("example.com" ), handler))
看上面的例子, 把example.com
换成你的域名,证书暂存在certs
文件夹。autocert会定期自动刷新,避免证书过期。它会自动申请证书,并进行验证。
不过比较遗憾的是, autocert目前不支持通配符域名。
HostWhitelist returns a policy where only the specified host names are allowed. Only exact matches are currently supported. Subdomains, regexp or wildcard will not match.
通配符(ACME v2)的支持也已经完成了,但是迟迟未通过review,所以你暂时还不能使用这个特性。 (issue#21081 )
HTTP/2
Go 在 1.6的时候已经支持 HTTP/2
了, 1.8 开始支持PUSH
功能,你什么时候开始采用HTTP/2
的呢?
Go的http/2使用也非常简单,但是必须和TLS一起使用。
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
package main
import (
"log"
"net/http"
"time"
"golang.org/x/net/http2"
)
const idleTimeout = 5 * time.Minute
const activeTimeout = 10 * time.Minute
func main() {
var srv http.Server
srv.Addr = ":8972"
http.HandleFunc("/" , func (w http.ResponseWriter, r *http.Request) {
w.Write([]byte ("hello http2" ))
})
http2.ConfigureServer(&srv, &http2.Server{})
go func () {
log.Fatal(srv.ListenAndServeTLS("server.crt" , "server.key" ))
}()
select {}
}
http2
封装并隐藏了http/2的处理逻辑,对于用户来说,可以不必关心内部的具体实现,想http/1.1一样简单的使用即可。
这里的证书可以使用上面提到证书,或者你购买的1证书,或者免费let's encrypt
证书。
h2c
上面我们说Go的http/2必须使用TLS是不严谨的,如果你想不使用TLS,你可以使用最近添加的h2c功能。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package main
import (
"fmt"
"log"
"net/http"
"golang.org/x/net/http2"
"golang.org/x/net/http2/h2c"
)
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/" , func (w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "Hello h2c" )
})
s := &http.Server{
Addr: ":8972" ,
Handler: mux,
}
http2.ConfigureServer(s, &http2.Server{})
log.Fatal(s.ListenAndServe())
}
使用起来也很简单,单数目前浏览器对http/2都是采用TLS的方式,所以用浏览器访问这个服务的话会退化为http/1.1的协议,测试的话你可以使用Go实现客户端的h2c访问。
客户端代码如下:
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
package main
import (
"crypto/tls"
"fmt"
"log"
"net"
"net/http"
"golang.org/x/net/http2"
)
func main() {
client := http.Client{
Transport: &http2.Transport{
AllowHTTP: true ,
DialTLS: func (network, addr string , cfg *tls.Config) (net.Conn, error) {
return net.Dial(network, addr)
},
},
}
resp, err := client.Get("http://localhost:8972" )
if err != nil {
log.Fatal(fmt.Errorf("error making request: %v" , err))
}
fmt.Println(resp.StatusCode)
fmt.Println(resp.Proto)
}
这个功能的讨论2016年就开始了(issue#14141 ),最终5月份的这个功能加上了,这样你就可以在没有TLS使用http/2高性能的特性了。
虽然有一些第三方的实现,但是显然使用官方库是更好的选择。