Go语言web程序

1.web程序

1.介绍

  • 标准包net\http封装web服务相关功能
  • 使用简单、性能媲美nginx

2.Form表单

<!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
	}
    }
}
posted @ 2022-09-29 14:53  fatpuffer  阅读(104)  评论(0)    收藏  举报