GolangNote

Golang笔记

Golang Web 程序生产环境独立部署示例

Permalink

一个 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

Go: 定义 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

Go: 定义 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 端口

Go: 监听端口
1
2
3
go http.ListenAndServe(":http", m.HTTPHandler(nil))
_ = s.ListenAndServeTLS("", "") // 留空

这就是一个最简单的能够自动申请、更新 Let’s Encrypt 证书的 go web 程序。

放在一起是这样

Go: 最简程序
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 简单

Go: GzipHandler
1
2
3
&http.Server{
    Handler:     gzhttp.GzipHandler(yourHandler),
}

这么简单,是因为有这个优秀的库帮忙 github.com/klauspost/compress/gzhttp

设置 HSTS

HTTP Strict Transport Security(通常简称为 HSTS )是一个安全功能,它告诉浏览器只能通过HTTPS访问当前资源,而不是HTTP。

plaintext: HSTS 语法
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 的时间不能小于 15552000
  • includeSubDomains 可选, 如果这个可选的参数被指定,那么说明此规则也适用于该网站的所有子域名
  • preload 可选,预加载 HSTS

这里需要写一个简单的中间件

Go: HSTS go 中间件
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 示例实现。

Go: context graceful
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 转摘请注明来源

Related articles

Golang WebAssembly 了解一下

Go 1.11 起开始支持 WebAssembly ,也就是说以后可以使用任何语言作为“前端语言”来进行 Web 开发。...

Golang 单实例实现网站多域名请求

有时候写网站,为了统一的后端,把不同业务都集中到一个后端,这时就需要处理多域名的请求,在 Go http server 里实现很简单,只需把不同域名映射到不同的 `http.Handler`。...

Write a Comment to "Golang Web 程序生产环境独立部署示例"

Submit Comment Login
Based on Golang + fastHTTP + sdb | go1.18 Processed in 1ms