网络编程
- 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.cn
和b.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")
}