一个 web 应用通常是跑在一个前端代理,如 Nginx 后,这样可以方便的在同一个服务器部署多个应用。这里说的独立部署是指让 go web 程序直接暴露在外面,独占 443、80 端口(俗称裸跑)。这样做除了性能有些提高外,更重要的是部署方便。
golang acme/autocert 实现可以自动申请、自动更新 Let’s Encrypt 证书,不用重启程序,对懒人很友好,这也是懒人喜欢让部署 go 应用独立部署的原因。
一般的在线应用都是使用 ssl+gzip
- ssl 使用 Let’s Encrypt ,go 官方内置的库
golang.org/x/crypto/acme/autocert
- gzip 这个库实现很好
github.com/klauspost/compress/gzhttp
定义 autocert.Manager
1
2
3
4
5
6
m := &autocert.Manager{
Cache: autocert.DirCache("certs"),
Prompt: autocert.AcceptTOS,
Email: "your@example.com",
HostPolicy: autocert.HostWhitelist("golangnote.com", "www.golangnote.com"), // 第一个必须,第二个可以用泛域名 *.golangnote.com
}
定义 http.Server
1
2
3
4
5
6
7
s := &http.Server{
Addr: ":https",
TLSConfig: m.TLSConfig(),
Handler: http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
fmt.Fprintf(w, strings.Repeat("Hello 1024! ", 1024))
}),
}
监听 443 和 80 端口
1
2
3
go http.ListenAndServe(":http", m.HTTPHandler(nil))
_ = s.ListenAndServeTLS("", "") // 留空
这就是一个最简单的能够自动申请、更新 Let’s Encrypt 证书的 go web 程序。
放在一起是这样
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
package main
import (
"fmt"
"golang.org/x/crypto/acme/autocert"
"net/http"
"strings"
)
func main() {
m := &autocert.Manager{
Cache: autocert.DirCache("certs"),
Prompt: autocert.AcceptTOS,
Email: "your@example.com",
HostPolicy: autocert.HostWhitelist("golangnote.com", "www.golangnote.com"), // 第一个必须,第二个可以用泛域名 *.golangnote.com
}
s := &http.Server{
Addr: ":https",
TLSConfig: m.TLSConfig(),
Handler: http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
fmt.Fprintf(w, strings.Repeat("Hello 1024! ", 1024))
}),
}
go http.ListenAndServe(":http", m.HTTPHandler(nil))
_ = s.ListenAndServeTLS("", "") // 留空
}
功能改进
最简的能跑,但用在生产环境,太简单了也不好,至少添加下面几个功能
- gzip 对响应内容做 gzip 压缩
- 设置 HSTS -
Strict-Transport-Security
- 优雅重启
gzip
gzip 简单
1
2
3
&http.Server{
Handler: gzhttp.GzipHandler(yourHandler),
}
这么简单,是因为有这个优秀的库帮忙 github.com/klauspost/compress/gzhttp
设置 HSTS
HTTP Strict Transport Security
(通常简称为 HSTS )是一个安全功能,它告诉浏览器只能通过HTTPS访问当前资源,而不是HTTP。
1
2
3
Strict-Transport-Security: max-age=<expire-time>
Strict-Transport-Security: max-age=<expire-time>; includeSubDomains
Strict-Transport-Security: max-age=<expire-time>; preload
max-age=<expire-time>
设置在浏览器收到这个请求后的<expire-time>
秒的时间内凡是访问这个域名下的请求都使用HTTPS请求,max-age 的时间不能小于 15552000includeSubDomains
可选, 如果这个可选的参数被指定,那么说明此规则也适用于该网站的所有子域名preload
可选,预加载 HSTS
这里需要写一个简单的中间件
如
1
2
3
4
func (m *Middleware) ServeHTTP(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Strict-Transport-Security", "max-age=63072000; includeSubDomains; preload")
m.next.ServeHTTP(w, r)
}
优雅重启
俗称 graceful
优雅重启,这个功能很重要,总不能让程序想断就马上断,得先等待请求完成后再断。
优雅重启的过程
- 接收到中断请求
- 停止新的请求,同时看看有没有还没断开的请求
- 如果没有就直接断开,如果有就等待请求结束,可以设置一个超时时间,避免无限等待
- 程序连接资源释放,如关闭数据库等
- 程序退出
- 再重启
graceful
的实现有好多,看看那个好用就用那个,这里用官方 context
示例实现。
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
ctx, cancel := context.WithCancel(context.Background())
srv := &http.Server{
BaseContext: func(_ net.Listener) context.Context { return ctx },
}
srv.RegisterOnShutdown(cancel)
// web
go srv.ListenAndServeTLS("", "")
// graceful stop
// subscribe to SIGINT signals
signalChan := make(chan os.Signal, 1)
signal.Notify(
signalChan,
syscall.SIGHUP, // kill -SIGHUP XXXX
syscall.SIGINT, // kill -SIGINT XXXX or Ctrl+c
syscall.SIGTERM, // kill -SIGTERM XXXX
syscall.SIGQUIT, // kill -SIGQUIT XXXX
)
<-signalChan
log.Printf("os.Interrupt - shutting down...\n")
go func() {
<-signalChan
log.Fatal("os.Kill - terminating...\n")
}()
// 等待 30秒 ,等请求结束,同时不允许新的请求
gracefulCtx, cancelShutdown := context.WithTimeout(context.Background(), 30*time.Second)
defer cancelShutdown()
if err := srv.Shutdown(gracefulCtx); err != nil {
log.Println("Shutdown err", err)
defer os.Exit(1)
} else {
// app.Close() // !important 关闭数据库等
log.Println("gracefully stopped")
}
defer os.Exit(0)
完工
本文网址: https://golangnote.com/topic/304.html 转摘请注明来源