1.web程序
1.介绍
- 标准包
net\http封装web服务相关功能
- 使用简单、性能媲美
nginx
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>登录</title>
</head>
<body>
<form action="login" method="post">
用户名:<input type="text" name="username">
密码:<input type="password" name="password">
<input type="submit" value="登录">
</form>
</body>
</html>
package main
import (
"fmt"
"html/template"
"log"
"net/http"
)
func login(w http.ResponseWriter, r *http.Request) {
fmt.Println("method: ", r.Method)
if r.Method == "GET" {
t, err := template.ParseFiles("dcpuffer/webserver/login.html")
// t, err := template.ParseFiles("./login.html")
if err != nil {
fmt.Fprintf(w, "load login.html failed")
return
}
// 渲染模板,data:模板中的变量
t.Execute(w, nil)
} else {
r.ParseForm() // 如果不解析,就无法从Form中获取到数据
fmt.Println("username: ", r.FormValue("username"))
fmt.Println("password: ", r.FormValue("password"))
// 返回响应
w.Write([]byte("submit success"))
//fmt.Fprintf(w, "submit success")
}
}
func main() {
http.HandleFunc("/login", login) // 设置访问的路由
err := http.ListenAndServe(":9999", nil) // 设置监听端口
if err != nil {
log.Fatal("ListenAndServer: ", err)
}
}
3.模板渲染
- 1.语法:
{{name}},{{.}}表示当前的对象
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>登录</title>
</head>
<body>
<div>
hello {{.name}}
</div>
</body>
</html>
t, err := template.ParseFiles("dcpuffer/webserver/login.html")
// t, err := template.ParseFiles("./login.html")
if err != nil {
fmt.Fprintf(w, "load login.html failed")
return
}
// 渲染模板,data:模板中的变量
t.Execute(w, map[string]string{"name": "小李", "age": "22"})
t, err := template.ParseFiles("dcpuffer/webserver/login.html")
t.Execute(os.Stdout, map[string]string{"name": "小李", "age": "22"})
4.if判断
<body>
{{if gt .Age 18}}
<p>hello old man</p>
{{else}}
<p>heool young man</p>
{{end}}
</body>
not:非
and:与
or:或
eq:等于
ne:不等于
lt:小于
5.循环
<body>
{{range .}} // . 代表一个切片、数组、map
{{if gt .Age 18}}
<p>hello old man</p>
{{else}}
<p>heool young man</p>
{{end}}
{{end}}
</body>
2.Web服务器平滑升级
1.正在处理的请求怎么办
- 1.等待处理完成之后,在退出(优雅关闭)
- 2.Golang1.8之后已经支持
2.新进来的请求怎么办
- 1.Fork一个子进程,继承父进程监听的socket
- 2.子进程启动成功后,接收新的连接
- 3.父进程停止接收新的连接,等已有的请求处理完毕
- 4.优雅重启成功
3.子进程如何继承父进程的文件句柄
- 1.通过
os.Cmd对象中的ExtraFiles参数进行传递
- 2.文件句柄继承实例分析
package proj1
import (
"flag"
"fmt"
"os"
"os/exec"
"time"
)
var child *bool
func init() {
// 解析命令行参数 -child
child = flag.Bool("child", false, "继承于父进程(inherit use only)")
flag.Parse()
}
func startChild(file *os.File) {
// 处理命令行参数
// 给启动命令后添加 -child参数
args := []string{"-child"}
cmd := exec.Command(os.Args[0], args...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
// put socket FD as the first entry
cmd.ExtraFiles = []*os.File{file} // 将父进程的文件句柄交给子进程
err := cmd.Start()
if err != nil {
fmt.Printf("start child failed, err: %v\n", err)
return
}
}
func readFormParent() {
// fd = 0: 标准输出
// fd = 1: 标准输入
// fd = 2: 标准错误输出
// fd = 3: ExtraFiles[0]
// fd = 4: ExtraFiles[1]
f := os.NewFile(3, "") // 利用文件句柄构建一个新的文件
count := 0
for {
str := fmt.Sprintf("hello process, write: %d line\n", count)
count++
_, err := f.WriteString(str)
if err != nil {
fmt.Printf("write string failed, err: %v\n", err)
time.Sleep(time.Second)
continue
}
time.Sleep(time.Second)
}
}
func main() {
if child != nil && *child == true {
fmt.Println("继承父文件的文件句柄")
readFormParent()
return
}
// 父进程的逻辑
file, err := os.OpenFile("/tmp/test_inherit.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0755)
if err != nil {
fmt.Printf("open file failed, err: %v\n", err)
return
}
_, errs := file.WriteString("parent write one line\n")
if errs != nil {
fmt.Printf("parent write failed, err: %v\n", errs)
return
}
startChild(file) // 将文件句柄交给子进程
fmt.Println("parent exited") // 父进程执行完退出
}
4.web server优雅重启实战
- 1.使用go1.8版本的
Shutdown方法进行优雅关闭
- 2.使用
socket继承实现,子进程接管父进程的监听socket
5.信号处理
- 1.通过
kill命令给正在运行的程序发送信号
- 2.不处理的话,程序会
panic处理
- 3.
kill -l查看所以可用的信号
kill -SIGKILL 17082:给进程17082发送一个SIGKILL的信号
![]()
package main
import (
"context"
"errors"
"flag"
"fmt"
"log"
"net"
"net/http"
"os"
"os/exec"
"os/signal"
"syscall"
"time"
)
var (
server *http.Server
listener net.Listener
graceful = flag.Bool("graceful", false, "listen on fd open 3 (internal use only)")
)
func handler(w http.ResponseWriter, r *http.Request) {
time.Sleep(20 * time.Second)
w.Write([]byte("hello world!!!"))
}
func main() {
flag.Parse()
http.HandleFunc("/hello", handler)
server = &http.Server{Addr: ":9999"}
var err error
if *graceful {
log.Print("main: Listening to existing file descriptor 3.")
f := os.NewFile(3, "") // 获取传递过来的文件句柄
listener, err = net.FileListener(f) // 创建一个新的文件对象
} else {
log.Print("main: Listening on a new file descriptor.")
listener, err = net.Listen("tcp", server.Addr)
}
if err != nil {
log.Fatalf("listener error: %v", err)
}
go func() {
err = server.Serve(listener)
log.Println("server.Server err: ", err)
}()
signalHandler()
log.Println("signal end")
}
func reload() error {
t1, ok := listener.(*net.TCPListener)
if !ok {
return errors.New("listener is not tcp listener")
}
f, err := t1.File()
if err != nil {
return err
}
args := []string{"-graceful"}
cmd := exec.Command(os.Args[0], args...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
// put socket FD as the first entry
cmd.ExtraFiles = []*os.File{f} // 将父进程的文件句柄交给子进程
return cmd.Start()
}
func signalHandler() {
// 创建一个signal类型的管道
ch :=make(chan os.Signal, 1)
// 将要捕获的信号传递给管道,进行监听
signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM, syscall.SIGUSER2)
for{
sig := <- ch
fmt.Printf("signal:%v\n", sig)
ctx, _ := context.WithTimeout(context.Background(), 20*time.Second)
switch sig {
case syscall.SIGINT, syscall.SIGTERM:
log.Printf("stop")
signal.Stop(ch)
server.Shutdown(ctx)
log.Printf("graceful shutdown")
return
case syscall.SIGUSR2:
// reload
log.Printf("reload")
err := reload() // 启动一个子进程
if err != nil {
log.Fatalf("fraceful restart error: %v", err)
}
server.Shutdown(ctx) // 结束父进程
log.Printf("fraceful reload")
return
}
}
}