Golang1.18.4 第二篇-网络编程

104 阅读10分钟

网络编程

  • socket编程
    • 网络连接过程
    • TCP
    • UDP
    • unix网络
  • http
    • gin网络框架

socket编程

互联网的通信是通过socket进行编程。其中每台设备会有一个IP地址进行网络识别,同一台机器,可以有多个程序同时进行网络传输,为了区分不同的程序使用到端口号。

因此,IP和端口唯一识别一个socket的地址。

IP的定于type IP []byte。IP有特定的格式。

package main

import (
	"fmt"
	"net"
)

func main() {
	ip1 := net.ParseIP("127.0.0.1")
	ip2 := net.ParseIP("www.baidu.com")
	ip3 := net.ParseIP("aaaa.bbbb.cccc.dddd")
	fmt.Printf("ip1 %T  %v \n", ip1, ip1)
	fmt.Printf("ip2 %T  %v \n", ip2, ip2)
	fmt.Printf("ip3 %T  %v \n", ip3, ip3)
}

/*
ip1 net.IP  127.0.0.1
ip2 net.IP  <nil>
ip3 net.IP  <nil>
*/

TCPAddr可以通过func ResolveTCPAddr(net, addr string) (*TCPAddr, os.Error)获取TCPAddr类型。

type TCPAddr struct {
    IP IP 
    Port int
}

// 解析
	{
		addr, err := net.ResolveTCPAddr("tcp4", "192.168.2.2:8080")
		if err != nil {
			fmt.Println("err", err)
			return
		}
		fmt.Println("addr is", addr)
	}
	{
		addr, err := net.ResolveTCPAddr("tcp4", "baidu.com:800")
		if err != nil {
			// 如果无法连接互联网,针对域名类的解析会发生错误
			//err lookup baidu.com: no such host
			fmt.Println("err", err)
			return
		}
		fmt.Println("addr is", addr) //addr is 220.181.38.148:800
	}
//
addr is 192.168.2.2:8080
addr is 220.181.38.148:800

TCP

创建TCP服务器

package main

import (
	"fmt"
	"net"
)

func main() {
	// 只能本地访问
	// listener, err := net.Listen("tcp", "127.0.0.1:8080")

	// 所有的IP都可以访问 也可以是":8080"表示接受所有的地址
	listener, err := net.Listen("tcp", "0.0.0.0:8080")
	if err != nil {
		fmt.Println("err ", err)
		return
	}
	defer listener.Close()

	conn, err1 := listener.Accept()
	if err1 != nil {
		fmt.Println("error1  ", err1)
		return
	}
	defer conn.Close()

	buf := make([]byte, 1024, 2000)
	n_read, error2 := conn.Read(buf)
	if error2 != nil {
		fmt.Println("error2 ", error2)
		return
	}
	fmt.Println("read ", n_read, " bytes")
	fmt.Println(string(buf[:n_read]))

	conn.Write([]byte("hello world"))
}

// mac可以使用nc localhost 8080进行连接测试。

TCP客户端

package main

import (
	"fmt"
	"net"
)

func main() {
	conn, err := net.Dial("tcp", "xuejs.cn:8080")
	if err != nil {
		fmt.Println("err ", err)
		return
	}
	defer conn.Close()

	conn.Write([]byte("123456789"))

	buffer := make([]byte, 1024)
	n_read, error := conn.Read(buffer)
	if error != nil {
		fmt.Println("error ", error)
	}
	fmt.Println("收到了", string(buffer[:n_read]))
}

TCP服务端支持并发

使用goroutine。每次有新的连接,都通过goroutine进行处理。

package main

import (
	"fmt"
	"net"
	"strings"
)

func main() {
	listener, error := net.Listen("tcp", ":8080")
	if error != nil {
		fmt.Println("error is ", error)
		return
	}
	defer listener.Close()
	// for循环,一直等待客户端
	for {
		conn, error2 := listener.Accept()

		// 如果关闭了listener,则再次accept就会报错,此时需要退出for循环
		// listener.Close()
		if error2 != nil {
			fmt.Println("error2 ", error2)
			break
		}
		go func() {
			defer conn.Close()
			for {
				// 读取数据
				buf := make([]byte, 1024)
				n_read, read_error := conn.Read(buf)
				if read_error != nil {
					fmt.Println("read_error is ", read_error)
					return
				}
				read_str := string(buf[:n_read])

				// 因为nc命名在每次进行数据传输的时候都会带着换行符
				if read_str == "finish\n" {
					break
				}
				upper_string := strings.ToUpper(string(buf[:n_read]))
				conn.Write([]byte(upper_string))
			}

		}()
	}
}

此时,我们可以同时开启多个nc localhost:8080进行通信。

客户端同时处理输入和输出

之前的客户端是每次主动往服务器写入数据,然后接受客户端的请求。在等待服务器的数据的过程中,当前的状态是阻塞的,因此无法继续往服务器写入数据。

如果我们写数据和读数据相互独立。需要开启新的goroutine。

package main

import (
	"fmt"
	"net"
	"os"
)

func main() {
	conn, err := net.Dial("tcp", "xuejs.cn:8080")
	if err != nil {
		fmt.Println("err ", err)
		return
	}
	defer conn.Close()

	// 从服务端接收数据
	go func() {
		buffer := make([]byte, 1024)
		for {
			n_read, read_error := conn.Read(buffer)
			if read_error != nil {
				fmt.Println("read_error:", read_error)
				break
			}
			fmt.Println("read :", string(buffer[:n_read]))
		}
	}()

	// 从命令行接收数据发送给服务器
	for {
		buffer := make([]byte, 1024)
		n_read, io_error := os.Stdin.Read(buffer)
		if io_error != nil {
			fmt.Println("io_error ", io_error)
			break
		}
		// 将数据发送给服务器
		n_write, write_error := conn.Write(buffer[:n_read])
		if write_error != nil {
			fmt.Println("write_error ", write_error)
			break
		}
		fmt.Println("write ", n_write, "bytes, should write ", n_write, "bytes")
	}
}

网络堵塞

网络处理是需要经过网卡的。发送的时候,先发送到网卡,网卡有个缓冲队列。如果缓存队列满了,再次发送就会堵塞。

package main

import (
	"fmt"
	"net"
	"strings"
	"time"
)

func main() {
	go create_server()

	conn, _ := net.Dial("tcp", "localhost:8080")

	go func() {
		buffer := make([]byte, 1024)
		n_read, read_error := conn.Read(buffer)
		if read_error != nil {
			fmt.Println("client: read error ", read_error)
			return
		}
		fmt.Println("client : recieved ", n_read)
	}()

	count := 0
	for {
		count++
		write_data := []byte(strings.Repeat("Hello world", 1000))

		// 如果队列满了,不会发生error。而是会阻塞。一直等待数据都写入到缓冲区。
		n_write, write_error := conn.Write(write_data)
		if write_error != nil {
			fmt.Println("client: write error ", write_error)
			return
		}
		fmt.Println("count:", count, " write ", n_write, " bytes , should write ", len(write_data), " bytes")
	}

}

func create_server() {
	listener, error := net.Listen("tcp", ":8080")
	if error != nil {
		fmt.Println("error is ", error)
		return
	}

	// 单链接
	conn, conn_error := listener.Accept()
	if conn_error != nil {
		fmt.Println("conn_error", conn_error)
		return
	}

	buffer := make([]byte, 1024)

	for {
		n_read, n_read_error := conn.Read(buffer)
		if n_read_error != nil {
			fmt.Println("n_read_error", n_read_error)
			break
		}
		// 休眠
		time.Sleep(time.Second * 1)
		conn.Write(buffer[:n_read])
	}
}


UDP通信

// 客户端
package main

import (
	"fmt"
	"net"
	"os"
)

func main() {

	udp_addr, err := net.ResolveUDPAddr("udp", "xuejs.cn:9000")
	if err != nil {
		fmt.Println("创建UDP失败")
		os.Exit(1)
	}
	// 第二个参数可以是nil,对于多网卡设备,也可以指定某个网卡进行通信
	// 此时无法服务器没有启动监听,也不会失败,因为UDP并不需要握手
	socket, err := net.DialUDP("udp", nil, udp_addr)
	if err != nil {
		fmt.Println("err:", err)
		return
	}
	defer socket.Close() //关闭连接
	sendData := []byte("hello server")
	// 通过write往服务器写入数据
	// 如果服务器一直未开启,也可以写入数据,且不会失败
	_, err = socket.Write(sendData)
	if err != nil {
		fmt.Println("发送数据失败", err)
		return
	}
	data := make([]byte, 4096)
	// 通过read从服务器读取数据
	udp, addr, err := socket.ReadFromUDP(data) //接收数据
	if err != nil {
		fmt.Println("接受数据失败", err)
		return
	}
	fmt.Printf("recv:%v,addr:%v", string(data[:udp]), addr)
}

//服务端
package main

import (
	"fmt"
	"net"
	"os"
)

func main() {

	// 创建UDP地址信息
	udp_addr, err := net.ResolveUDPAddr("udp", "0.0.0.0:8080")
	if err != nil {
		fmt.Println("创建UDP地址失败")
		os.Exit(1)
	}
	// 设置监听
	listen, err := net.ListenUDP("udp", udp_addr)
	if err != nil {
		fmt.Println("err:", err)
		return
	}
	defer listen.Close() //关闭监听
	for {
		var bf [1024]byte
		n, addr, err := listen.ReadFromUDP(bf[:]) //接受UDP数据
		if err != nil {
			fmt.Println("read udp failed,err", err)
			continue
		}
		fmt.Printf("data:%v,addr:%v", string(bf[:n]), addr)
		//写入数据
		_, err = listen.WriteToUDP(bf[:n], addr)
		if err != nil {
			fmt.Println("write udp error", err)
			continue
		}
	}
}

unix网络

unix网络不经过网卡,因此效率比较高,但是只能同一台机器的两个应用进行通信

原生http

package main

import (
	"fmt"
	"net/http"
)

func test1(w http.ResponseWriter, r *http.Request) {
	error := r.ParseForm()
	if error != nil {
		fmt.Println("错误")
		return
	}
	fmt.Println(r.Form)     // 解析的参数
	fmt.Println(r.URL.Path) // 路径
	fmt.Println(r.Host)
	for key, value := range r.Form {
		fmt.Println("key is ", key)
		fmt.Println("valye is ", value)
	}
	fmt.Fprintf(w, "获取到的参数是 %s", r.Form.Get("name"))

}

func main() {
	http.HandleFunc("/", test1)
	err := http.ListenAndServe("127.0.0.1:8080", nil)
	if err != nil {
		fmt.Println("创建服务器失败:", err)
	}
}

// curl http://localhost:8080/?name="zhangsan"

gin框架

gin是一个高性能的http框架。

基本使用

package main

import (
	"net/http"

	"github.com/gin-gonic/gin"
)

func main() {
	//初始化engine
	engine := gin.Default()

	// 设置路由
	engine.GET("/", func(ctx *gin.Context) {
		ctx.String(200, "Hello world")
	})

	/*
		/reg/xiaoming 可以访问
		下面的路径不可以范围
		/reg
		/reg/xiaoming/age
	*/
	engine.GET("/reg/:name", func(ctx *gin.Context) {
		name := ctx.Param("name")
		ctx.String(http.StatusOK, name)
	})

	/*
		*表示可以有可以没有,并且支持多路径
		/login/xiaoming 返回/
		/login/xiaoming/123456 返回/123456
		/login/xiaoming/123456/china 返回/123456/china
	*/
	engine.GET("/login/:name/*password", func(ctx *gin.Context) {
		name := ctx.Param("name")
		passwd := ctx.Param("password")
		ctx.String(200, "name is %s password is %s", name, passwd)
	})

	engine.Run(":8000")
}

获取query参数

	router.GET("/hello", func(ctx *gin.Context) {
		// 获取参数, 如果没有值,则返回空字符串
		age := ctx.Query("age")
		// 如果没有该参数使用默认值
		name := ctx.DefaultQuery("name", "libai")
		hobby := ctx.QueryArray("hobby")
		ctx.JSON(200, gin.H{"age": age, "name": name, "hobby": hobby})
		//http://localhost:8000/hello?name=liha&&hobby=run&&hobby=swim
		//{"age":"","hobby":["run","swim"],"name":"liha"}
	})

获取POST参数

	router.POST("/hi", func(ctx *gin.Context) {
		age := ctx.PostForm("age")
		hobby := ctx.PostFormArray("hobby")
		fmt.Println("hobby is ", hobby)
		name := ctx.DefaultPostForm("name", "wudi")
		ctx.JSON(200, gin.H{"age": age, "name": name, "hobby": hobby})
		// 这种方式返回的boddy是一个内容
		//curl -X POST -d 'hobby=run,jump&&age=22' http://localhost:8000/hi
		//{"age":"22","hobby":["run,jump"],"name":"wudi"}
		//
		// 如果需要设置数组,则采用多次传递参数
		//curl -X POST -d 'hobby=1&hobby=2&age=22' http://localhost:8000/hi
		//{"age":"22","hobby":"run,jump","name":"wudi"}
		// curl -X POST -H "Content-Type:application/json" -d '{"hobby":["run","jump"],"age":22}' http://localhost:8000/hi
	})

单文件上传

	engine.POST("/regist", func(c *gin.Context) {
		file, _ := c.FormFile("file1")
		log.Println(file)
		log.Println(file.Filename)
		res := c.SaveUploadedFile(file, file.Filename)
		fmt.Println("res ", res)
		c.String(http.StatusOK, fmt.Sprintf("%v", res))

	})
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <form action="http://localhost:8000/regist" method="POST" enctype="multipart/form-data">
        
        <input type="file" name="file1" id="">
        <br>
        <input type="file" name="file2" id="">
        <br>
        <input type="submit" value="上传">
    </form>
</body>
</html>

静态文件

router.Static("/static", "./static")

上传多个文件

	// 默认最大为32M
	engine.MaxMultipartMemory = 8 << 20 // 设置为最大为8MB

	engine.POST("/regist", func(c *gin.Context) {
		form, err := c.MultipartForm()
		if err != nil {
			c.String(http.StatusBadRequest, "error")
			return
		}
		files := form.File["filee"]
		fmt.Println("files is ", files)
                for _, file := range files {
                    fmt.Println("file", file.Filename)
		}

		c.String(http.StatusOK, "成功")
	})
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <form action="http://localhost:8000/regist" method="POST" enctype="multipart/form-data">
        
        <input type="file" name="file1" id="" multiple>
        <br>
        <input type="file" name="file2" id="">
        <br>
        <input type="submit" value="上传">
    </form>
</body>
</html>

路由组

	engine := gin.Default()

	g1 := engine.Group("/v1")
	{
		g1.GET("/login", func(c *gin.Context) {

		})
		g1.GET("/regist", func(c *gin.Context) {

		})
	}
	engine.Run("0.0.0.0:8000")

实际项目开发中,一般会被模块拆分为文件。

// router/user.go

package routers

import "github.com/gin-gonic/gin"

func InitUserRouter(c *gin.Engine) {
	user_group := c.Group("/user")

	user_group.GET("/login", func(c *gin.Context) {
		c.String(200, "login")
	})
	user_group.GET("/regist", func(c *gin.Context) {
		c.String(200, "regist")
	})
}
// main.go
package main

import (
	// a为go.mod中module的值
	"a/routers"

	"github.com/gin-gonic/gin"
)

func main() {
	router := gin.Default()
	routers.InitUserRouter(router)

	router.Run(":8000")
}


对象绑定

将参数直接绑定到一个对象。

type User struct {
	Name string `form:"name" json:"name" xml:"name"`
	Age  int    `form:"age" json:"age" xml:"age"`
}

	router.GET("/login", func(ctx *gin.Context) {
		var u User
		if res := ctx.ShouldBind(&u); res != nil {
			ctx.JSON(200, gin.H{"message": fmt.Sprintf("%v", res)})
		} else {
			ctx.JSON(200, u)
		}
		//http://localhost:8000/login?name=libai&&age=20
		//{"name":"libai","age":20}
	})

也可以解析XML

type User struct {
	Name string `form:"name" json:"name" xml:"name"`
	Age  int    `form:"age" json:"age" xml:"age"`
}
	router.POST("/xml", func(ctx *gin.Context) {
		var user User
		res, _ := ctx.GetRawData()
		fmt.Println("res is ", res, string(res))
		if err := xml.Unmarshal(res, &user); err != nil {
			ctx.JSON(200, gin.H{"err": err.Error()})
		} else {
			fmt.Println("user is ", user.Name)
			ctx.JSON(200, user)
		}
		// curl -d '<User><name>libai</name><age>18</age></User>' http://localhost:8000/xml
		// {"name":"libai","age":18}
	})

动态路由数据绑定

/:user/:password

.shoudBindURL(&user)

渲染

	engine := gin.Default()
	engine.GET("/someJSON", func(c *gin.Context) {
		c.JSON(200, gin.H{"message": "some json"})
	})
	
	engine.GET("/someXML", func(ctx *gin.Context) {
		ctx.XML(200, gin.H{"message": "Hello"})
	})
	engine.GET("/someYAML", func(ctx *gin.Context) {
		ctx.YAML(200, gin.H{"message": "Hello"})
	})

重定向

	// 创建路由
	engine := gin.Default()
	engine.GET("/user", func(c *gin.Context) {
		c.Redirect(http.StatusMovedPermanently, "https://www.baidu.com")
	})
	engine.Run("0.0.0.0:8000")

异步返回

	/*
		在gin中,只保证当前处理函数的安全性。
		如下代码,如果先请求/user再请求/login,在子的goroutine中,
	*/
	engine := gin.Default()
	engine.GET("/user", func(c *gin.Context) {
		another_context := c.Copy()
		go func() {
			time.Sleep(time.Second * 4)
			log.Println(another_context.Request.URL.Path)
			log.Println(c.Request.URL.Path)
			//不能使用副本发送数据
			another_context.String(200, c.Request.URL.Path)
			// 而如果使用c,由于c是指针,因此对应了其它请求。
		}()
		c.String(200, c.Request.URL.Path)

	})
	engine.GET("/login", func(c *gin.Context) {
		// time.Sleep(time.Second * 10)
		c.String(200, c.Request.URL.Path)
	})

如果需要异步返回,比如查询数据库之类的,则需要结合chan在不同的goroutine之间进行通信。

package main

import (
	"fmt"
	"net/http"
	"time"

	"github.com/gin-gonic/gin"
)

func main() {

	engine := gin.Default()
	engine.GET("/login", func(c *gin.Context) {
		fmt.Println("/login begin")
		another_context := c.Copy()
		// 下面代码会导致当前函数执行结束,http直接返回
		// go func() {
		// 	time.Sleep(time.Second * 10)
		// 	// 这里不能直接使用
		// 	another_context.String(200c, "/login finished")
		// 	fmt.Println("/login end")
		// }()

		// 改成阻塞状态
		ch := make(chan string)
		go func() {
			time.Sleep(time.Second * 5)
			a := another_context.Request.URL.Path
			ch <- a
		}()
		res, ok := <-ch
		if !ok {
			c.String(http.StatusBadRequest, "失败了")
			return
		}
		c.String(200, res)

	})
	engine.GET("/regist", func(c *gin.Context) {
		fmt.Println("/regist begin")
		time.Sleep(time.Second * 10)
		fmt.Println("/regist end")
		c.String(200, "regist finish")
	})
	engine.Run("0.0.0.0:8000")
}

中间件

默认自带log和recovery中间件。

中间件的基本使用

package main

import (
	"fmt"

	"github.com/gin-gonic/gin"
)

func log(c *gin.Context) {
	fmt.Println("", c.FullPath())
}
func main() {
	router := gin.Default()
	// 匹配到请求之后,先处理中间件的代码,最后处理路由函数
	router.GET("/", log, func(ctx *gin.Context) {
		fmt.Println("finished middleware")
		ctx.String(200, "OK")
	})
	router.Run(":8000")
}

中间件可以配置多个router.GET("/", log, filter, auth, func(ctx *gin.Context)

c.Next() 直接执行路由函数并等待该函数执行结束。

package main

import (
	"fmt"
	"time"

	"github.com/gin-gonic/gin"
)

func log(c *gin.Context) {
	// 获取开始请求的时间
	begin := time.Now().Nanosecond()
	// 这里会直接执行路由函数。并阻塞
	c.Next()
	// 获取结束处理的时间
	use_time := time.Now().Nanosecond() - begin
	fmt.Println("", c.FullPath(), " use ", use_time, "ns")
	// /  use  24000 ns
}
func main() {
	router := gin.Default()
	// 匹配到请求之后,先处理中间件的代码,最后处理路由函数
	router.GET("/", log, func(ctx *gin.Context) {
		fmt.Println("finished middleware")
		ctx.String(200, "OK")
	})
	router.Run(":8000")
}

a.Abort() 中间件代码会继续执行,执行完成之后,不会执行路由函数

该函数并不会直接断开连接,而是会进行返回,返回体只有Header没有内容,格式如下

HTTP/1.1 200 OK
Date: Mon, 11 Jul 2022 14:18:26 GMT
Content-Length: 0
package main

import (
	"fmt"
	"time"

	"github.com/gin-gonic/gin"
)

func log(c *gin.Context) {
	// 获取开始请求的时间
	begin := time.Now().Nanosecond()
	// 会直接跳过路由函数,表示直接返回空
	c.Abort()
	// 获取结束处理的时间
	use_time := time.Now().Nanosecond() - begin
	fmt.Println("mylog:", c.FullPath(), " use ", use_time, "ns")
	//  /  use  1000 ns
}
func main() {
	router := gin.Default()
	// 匹配到请求之后,先处理中间件的代码,最后处理路由函数
	router.GET("/", log, func(ctx *gin.Context) {
		fmt.Println("finished middleware")
		ctx.String(200, "OK")
	})
	router.Run(":8000")
}

全局中间件

可以配置1个或者多个全局中间件,针对所有的路由都有效果。

router.use(log1, log2)

路由分组中间件

group := router.Group("/", log)

或者
group := router.Group("/")
group.use(log)

中间件共享数据

ctx.Set("name":"libai")

ctx.Get("name")

完整代码如下

package main

import (
	"fmt"

	"github.com/gin-gonic/gin"
)

func log(c *gin.Context) {
	c.Set("name", "李白")
	c.Next()
	val, _ := c.Get("age")
	fmt.Println("log ", val)
}
func filter(c *gin.Context) {
	val, _ := c.Get("name")
	fmt.Println("filter ", val)
	c.Set("age", 18)
}
func main() {
	router := gin.Default()
	router.GET("/", log, filter, func(ctx *gin.Context) {
		ctx.String(200, "OK")
	})
	router.Run(":8000")
}

模板引擎

// html中的下面代码会被替换成传递过来的值
{{name}}


func main() {
	// 创建路由
	engine := gin.Default()

	engine.LoadHTMLGlob("template/*")
	engine.GET("/user", func(c *gin.Context) {
		c.HTML(200, "index.template", gin.H{"name": "hello world"})
	})

	engine.Run("0.0.0.0:8000")
}

模板引擎2

gin- html模板引擎

模板引擎就是能够将html代码动态化。比如"欢迎XX登录",其中XX可以根据不同的用户显示不同的内容。

模板引擎的基本使用

// 目录结构
.
├── go.mod
├── go.sum
├── main.go
└── template
    ├── a
    │   └── index.html
    ├── b
    │   └── index.html
    └── index.html
// 这个目录不规范,template目录下有index.html。推荐全部放到某个子文件目录下,要不然可能会出现运行错误。
// main.go
package main

import (
	"github.com/gin-gonic/gin"
)

type Student struct {
	Name string `json:"name`
	Age  int    `json:age`
}

func main() {
	router := gin.Default()

	// 如果同时引入使用下面两种方法,必须将LoadHTMLFiles放前面,要不然运行可能会出错
	router.LoadHTMLFiles("template/index.html")
	router.LoadHTMLGlob("template/**/*") // 一般情况下我们使用全局的,其中**表示当前目录

	router.GET("/", func(ctx *gin.Context) {
		s := Student{"李白", 28}
		ctx.HTML(200, "index.html", gin.H{
			"title": "This is a student message",
			"user":  s,
		})
	})
	router.GET("hello", func(ctx *gin.Context) {
		ctx.HTML(200, "helloxxx", gin.H{
			"title": "这是一个自定义主题",
		})
	})
	router.Run(":8000")
}

html代码通过{{ .name}} 其中name表示传递的参数。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    {{.title}}
<br>
{{.user.Name}} {{.user.Age}}
</body>
</html>

对于不同路径下的文件,推荐使用路径+文件名称进行重命名 {{ define "name"}}{{ end}}结束。name虽然可以取任意的名称,但是查找起来可能不太方便。

// b/index.html
{{ define "helloxxx"}}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    {{.title}}
</body>
</html>

{{ end }}

模板引擎3

使用变量,变量语法 {{ $a := title}} 取名的规范为$开头。

{{ define "helloxxx"}}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    {{.title}}
    {{ $a := .title}}
    {{$a}}
    {{$a}}
    {{$a}}
</body>
</html>

{{ end }}

模板引擎4 使用控制语句

if语句

{{ if 条件 }} 语句 {{ end }}
{{ if 条件 }} 语句  {{ else }} 语句 {{ end }}
{{ if 条件 }} 语句  {{ else if 条件 }} 语句 {{ end }}

条件

  • eq ==
  • ne !=
  • ge >=
  • le <=
  • gt >
  • lt <
<body>
    {{.title}}
    {{ if gt .age  100}}
    大于100岁
    {{ else }}
    小于100岁
    {{end}}
</body>

range

{{range index, value := .list}}
    {{value}}
{{end}}

{{range index, value := .list1}}
    {{value}}
{{ else }}
    没有这个值 
{{end}}

with可以简化点语法,将属性名称提前。

{{ .user.name }}
{{ .user.age }}

{{ with .user}}
    {{.name}}
    {{.age}}
{{ end }}

模板引擎5 内置函数

内置函数

  • len 求字符串长度,对于字符串中含有中文的,一个中文占3个。
{{ len .title}}

自定义函数

函数参数类型一定要一致,传递传递不一致,会引发panic。

// 加载自定义函数
	// 必须在加载模板之前使用
	router.SetFuncMap(template.FuncMap{
		"time_to_string": time_to_string,
	})

// 定义函数
func time_to_string(ts int) string {
	t := time.Unix(int64(ts), 0)
	return t.Format("2006-01-02 15:04:05")
}
// 传递参数
"time":  int(time.Now().Unix()),

// 调用自定义函数
当前时间是{{ time_to_string .time}}

模板引擎6 嵌套

模板嵌套

{{ template "common/header.html" .}}注意最后一个点表示引入所有的参数。


{{ define "common/header.html"}}
    {{.title}}
{{end}}
{{define "page/index.html"}}
{{ template "common/header.html" .}}
{{end}}

cookie

对于跨域的问题。可以设置.xuejs.cn可以同时让a.xuejs.cnb.xuejs.cn一起使用。

package main

import (
	"github.com/gin-gonic/gin"
)

func main() {
	router := gin.Default()
	router.LoadHTMLGlob("template/*")
	router.GET("/login", func(ctx *gin.Context) {
		/*
			key
			value
			失效时间
			路径
			域
			是否HTTPS
			是否JS可以访问
		*/
		ctx.SetCookie("name", "zhangsan", 3600, "/", "localhost", true, true)
		ctx.HTML(200, "user.html", nil)
	})
	router.GET("/index", func(ctx *gin.Context) {
		value, error := ctx.Cookie("name")
		if error != nil {
			ctx.JSON(200, gin.H{"msg": "没有cookie"})
		} else {
			ctx.HTML(200, "user.html", gin.H{"name": value})
		}

	})
	router.GET("/logout", func(ctx *gin.Context) {
		// 删除cookie,设置过期时间为负数
		// ctx.SetCookie("name", "zhangsan", -1, "/", "localhost", true, true)
		// 删除cookie也可以设置cookie为空,会自动删除
		ctx.SetCookie("name", "", -1, "/", "localhost", true, true)
		ctx.HTML(200, "logout.html", nil)

	})
	router.Run(":8000")
}

session

其实session的本质就是cookie。只是cookie将值保存在浏览器,而session就是将数据保存在服务器,浏览器通过cookie保存对应的key。

一般情况,我们会使用redis数据库保存session的数据。如果我们直接使用一个map也是可以的。

package main

import (
	"github.com/gin-contrib/sessions"
	"github.com/gin-contrib/sessions/cookie"
	"github.com/gin-gonic/gin"
)

func main() {
	router := gin.Default()

	// 使用session

	// 创建存储引擎,这里是cookie的存储引擎并设置了秘钥
	store := cookie.NewStore([]byte("password123"))
	// 使用中间件,其中第一个参数firsesession表示浏览器的cookie对应的key
	router.Use(sessions.Sessions("firstsession", store))

	router.LoadHTMLGlob("template/*")
	router.GET("/login", func(ctx *gin.Context) {
		ses := sessions.Default(ctx)
		ses.Set("name", "张三")
		ses.Set("age", 18)
		ses.Save()
		ctx.HTML(200, "user.html", nil)
	})
	router.GET("/index", func(ctx *gin.Context) {
		ses := sessions.Default(ctx)
		name := ses.Get("name")
		age := ses.Get("age")
		ctx.JSON(200, gin.H{"name": name, "age": age})
	})
	router.GET("/logout", func(ctx *gin.Context) {
		// 删除cookie,设置过期时间为负数
		// ctx.SetCookie("name", "zhangsan", -1, "/", "localhost", true, true)
		// 删除cookie也可以设置cookie为空,会自动删除
		ctx.SetCookie("name", "", -1, "/", "localhost", true, true)
		ctx.HTML(200, "logout.html", nil)

	})
	router.Run(":8000")
}