【Go基础】Http编程

news2024/12/22 22:09:46

文章目录

    • 1. http协议
      • 1.1 请求方法
      • 1.2 URL
      • 1.3 协议版本
      • 1.4 请求头
      • 1.5 请求正文
      • 1.6 http response
      • 1.7 https
    • 2. go语言http标准库
    • 3. http router
    • 4. 请求校验
    • 5. http中间件
    • 6. GIN
      • 6.1 路由
      • 6.2 参数获取
      • 6.3 利用postman提交http请求
      • 6.4 生成response
      • 6.5 参数检验
      • 6.6 中间件
      • 6.7 会话
    • 7. Beego

1. http协议

  • http:超文本传输协议Hyper Text Transfer Protocol
  • http属于应用层协议,它在传输层用的是tcp协议
  • 无状态,对事务处理没有记忆能力(对比TCP协议里的确认号),如果要保存状态需要引用其他技术,如:cookie
  • 无连接,每次连接只处理一个请求,早期带宽和计算资源有限,这么做是为了追求传输速度快,后来通过Connection: Keep-Alive实现长连接,http1.1废弃了Keep-Alive,默认支持长连接

在这里插入图片描述

1.1 请求方法

请求方法解释
GET请求获取Request-URI所标识的资源
POST向URI提交数据(例如提交表单或上传数据)
HEAD类似于GET,返回的响应中没有具体的内容,用于获取报头
PUT对服务器上已存在的资源进行更新
DELETE请求服务器删除指定的页面
CONNECTHTTP/1.1预留,能够将连接改为管道方式的代理服务器
OPTIONS查看服务端性能
TRACE回显服务器收到的请求,主要用于测试或诊断
PATCH同PUT,可只对资源的一部分更新,资源不存在时会创建

GET、POST和HEAD是http1.0就有的,后面的请求方法是http1.1新增的。客户端发起一个请求时,这个请求可能要穿过防火墙、代理、网关或其他一些应用程序。每个中间节点都可能会修改原始的HTTP请求。TRACE 方法允许客户端在 最终将请求发送给服务器时,看看它变成了什么样子。TRACE请求会在目的服务器端发起一个环回诊断。行程最后一站的服务器会弹回一条TRACE响应,并在响应主体中携带它收到的原始请求报文。这样客户端就可以查看在所有中间HTTP应用程序组成的请求/响应链上,原始报文是否,以及如何被毁坏或修改过。CONNECT方法是HTTP/1.1协议预留的,能够将连接改为管道方式的代理服务器。通常用于SSL加密服务器的链接与非加密的HTTP代理服务器的通信。OPTIONS方法请求Web服务器告知其支持的各种功能。通过使用OPTIONS,客户端可以在与服务器进行交互之前,确定服务器的能力,这样它就可以更方便地与具备不同特性的代理和服务器进行互操作了。
实际中server对各种request method的处理方式可能不是按协义标准来的,比如server收到PUT请求时偏偏执行DELETE操作,同理仅用一个GET方法也能实现增删改查的全部功能。大多数浏览器只支持GET和POST。

1.2 URL

  • URI:uniform resource identifier,统一资源标识符,用来唯一的标识一个资源
  • URL: uniform resource locator,统一资源定位器,它是一种具体的URI,指明了如何locate这个资源
  • URL举例:
    在这里插入图片描述

1.3 协议版本

现在广泛应用的协议版本是HTTP/1.1

1.4 请求头

Header解释示例
Accept指定客户端能够接收的内容类型Accept: text/plain, text/html
Accept-Charset浏览器可以接受的字符编码集Accept-Charset: iso-8859-5
Accept-Encoding指定浏览器可以支持的web服务器返回内容压缩编码类型Accept-Encoding: compress, gzip
Accept-Language浏览器可接受的语言Accept-Language: en,zh
AuthorizationHTTP授权的授权证书Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==
Cache-Control指定请求和响应遵循的缓存机制Cache-Control: no-cache
Connection表示是否需要持久连接(HTTP 1.1默认进行持久连接)Connection: close
CookieHTTP请求发送时,会把保存在该请求域名下的所有cookie值一起发送给web服务器Cookie: $Version=1; Skin=new;
Content-Length请求的内容长度Content-Length: 348
Content-Type指定正文(body)的数据格式Content-Type: application/x-www-form-urlencoded
User-Agent浏览器信息Mozilla/5.0 (Windows NT 6.1; Win64; x64)

Content-Type

  • application/x-www-form-urlencoded
    • 浏览器的原生form表单,如果不设置 Content-Type 属性,则默认以 application/x-www-form-urlencoded 方式传输数据
    • 正文例如:name=manu&message=this_is_great
  • multipart/form-data
    • 上传文件时使用multipart/form-data,支持多种文件格式
    • 正文例如: name="text"name=“file”; filename="chrome.png"Content-Type: image/png… content of chrome.png
  • application/json
    • 正文例如:{“title”:“test”,“sub”:[1,2,3]}
  • text/xml
    • 正文例如:<?xml version="1.0"?> examples.getStateName

1.5 请求正文

GET请求没有请求正文,POST即可以把一部分参数放在url里,也可以把一部分参数放在请求正文里,如下是一个完整的POST请求

POST /post?id=1234&page=1 HTTP/1.1
Content-Type: application/x-www-form-urlencoded

name=manu&message=this_is_great

GET和POST的区别:

  • get的请求参数全部在url里,参数变时url就变;post可以把参数放到请求正文里,参数变时url不变
  • 虽然http协议并没有对url和请求正文做长度限制,但在实际中浏览器对url的长度限制比请求正文要小很多,所以post可以提交的数据比get要大得多
  • get比post更容易受到攻击(源于get的参数直接暴露在url里)

1.6 http response

在这里插入图片描述

响应状态及话术

codephrase说明
200Ok请求成功
400Bad Request客户端有语法错误,服务端不理解
401Unauthorized请求未经授权
403Forbidden服务端拒绝提供服务
404Not Found请求资源不存在
500Internal Server Error服务器发生不可预期的错误
503Server Unavailable服务器当前有问题,过段时间可能恢复

响应头

Header解释示例
Allow对某网络资源的有效的请求行为Allow: GET, HEAD
Date原始服务器消息发出的时间Date: Tue, 15 Nov 2010 08:12:31 GMT
Content-Encoding服务器支持的返回内容压缩编码类型Content-Encoding: gzip
Content-Language响应体的语言Content-Language: en,zh
Content-Length响应体的长度Content-Length: 348
Cache-Control指定请求和响应遵循的缓存机制Cache-Control: no-cache
Content-Type返回内容的MIME类型Content-Type: text/html; charset=utf-8

响应正文可以是html、json、xml、普通文本,等等
完整http response举例:

HTTP/1.1 200 OK 
Date: Fri, 22 May 2009 06:07:21 GMT 
Content-Type: text/html; charset=UTF-8 

<html> 
	<head></head>
	 <body>
		 <!--body goes here--> 
	</body> 
</html>

1.7 https

在这里插入图片描述

HTTP + 加密 + 认证 + 完整性保护 = HTTPS(HTTP Secure)

2. go语言http标准库

http_server.go

import ("net/http";"fmt")
func HelloHandler(w http.ResponseWriter, r *http.Request) {
	fmt.Fprint(w, "Hello Boy") // 把返回的内容写入http.ResponseWriter
}
func main() {
	http.HandleFunc("/", HelloHandler) // 路由,请求要目录时去执行HelloHandler
	// ListenAndServe如果不发生error会一直阻塞,为每一个请求创建一个协程去处理
	http.ListenAndServe(":5656", nil)
}

http_client.go

import ("net/http";"io";"os")
func main(){
	if resp, err := http.Get("http://127.0.0.1:5656"); err != nil {
		panic(err)
	} else {
	    // 注意一定要调用resp.Body.Close(),否则会协程泄漏(同时引发内存泄漏)
		defer resp.Body.Close() 
		io.Copy(os.Stdout, resp.Body) // 把resp.Body输出到标准输出流
	}
}

3. http router

  • 安装 go get -u github.com/julienschmidt/httprouter
  • Router实现了http.Handler接口
  • 为各种request method提供了便捷的路由方式
  • 支持restful请求方式
  • 支持ServeFiles访问静态文件
  • 可以自定义捕获panic的方法
package main

import (
	"fmt"
	"io"
	"net/http"
	"os"

	"github.com/julienschmidt/httprouter"
)

func handle(method string, w http.ResponseWriter, r *http.Request, params httprouter.Params) {
	fmt.Printf("request method: %s\n", r.Method)
	fmt.Printf("request body: ")
	io.Copy(os.Stdout, r.Body) // 把r.Body流里的内容拷贝到os.Stdout流里
	fmt.Println()
	w.Write([]byte("Hi boy, you request " + method))
}

func get(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
	handle("get", w, r, params)
}

func post(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
	handle("post", w, r, params)
}

func panic(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
	var arr []int
	_ = arr[1] // 数组越界panic
}

func main() {
	router := httprouter.New()
	router.GET("/", get)
	router.POST("/", post)

	// *只能有一个,且必须放path的末尾
	router.POST("/user/:name/:type/*addr", func(rw http.ResponseWriter, r *http.Request, p httprouter.Params) {
		fmt.Printf("name:%s, type:%s, addr:%s\n", p.ByName("name"), p.ByName("type"), p.ByName("addr"))
	})

	// 必须以/*filepath结尾,因为要获取我们要访问的路径信息
	// 在浏览器中访问:http://127.0.0.1:5656/file/home.html
	// 或 http://127.0.0.1:5656/file/readme.md
	router.ServeFiles("/file/*filepath", http.Dir("./http/static"))

	// 通过recover捕获panic
	router.PanicHandler = func(w http.ResponseWriter, r *http.Request, err interface{}) {
		w.WriteHeader(http.StatusInternalServerError) // 设置response status
		fmt.Fprintf(w, "error:%s", err) // 线上环境不要把原始错误信息返回给前端,测试阶段可以这么搞
	}
	router.GET("/panic", panic)

	// Router实现了ServerHTTP接口,所以它是一种http.Handler
	http.ListenAndServe(":5656", router)
}

4. 请求校验

跨站脚本攻击Cross-Site Scripting, XSS(为了区别于CSS),通过注入脚本获取敏感信息

<html>
<head>
	<title>留言板</title>
</head>
<body>
	<div id="board">
		<!--从数据库中读出用户的留言内容,展示在这里-->
		<script>alert("hey!you are attacked")</script>
	</div> 
</body>
</html>

CSRF(Cross-site request forgery,跨站请求伪造)

  • 登录A网站(银行网站)的个人中心:www.bank.com/my.php
  • 登录危险的B网站: <img src=http://www.bank.com/Transfer.php?toBankId=11&money=1000>,从B网站向A网站发起了转账请求(携带着A网站的认证Cookie)

jsonp

  • 主流浏览器不允许跨域访问数据(端口不同也属于跨域)
  • <script>标签的src属性不受同源策略限制
  • 通过script的src请求返回的数据,浏览器会当成js脚本去处理,所以服务端可以返回一个在客户端存在的js函数

首先安装go get github.com/go-playground/validator

type RegistRequest struct {
    UserName string `validate:"gt=0"` // >0 长度大于0
    PassWord string `validate:"min=6,max=12"` // 密码长度[6, 12]
    PassRepeat string `validate:"eqfield=PassWord"` // 跨字段相等校验
    Email string `validate:"email"` // 需要满足email的格式
}

范围约束

  • 对于字符串、切片、数组和map,约束其长度:len=10, min=6, max=10, gt=10
  • 对于数值,约束其取值:min, max, eq, ne, gt, gte, lt, lte, oneof=6 8

跨字段约束

  • 跨字段就在范围约束的基础上加field后缀
  • 如果还跨结构体(cross struct)就在跨字段的基础上在field前面加cs:范围约束 cs field

字符串约束

  • contains包含子串
  • containsany包含任意unicode字符, containsany=abcd
  • containsrune包含rune字符, containsrune= ☻
  • excludes不包含子串
  • excludesall不包含任意的unicode字符,excludesall=abcd
  • excludesrune不包含rune字符,excludesrune=☻
  • startswith以子串为前缀
  • endswith以子串为后缀

唯一性uniq

  • 对于数组和切片,约束没有重复的元素
  • 对于map,约束没的重复的value
  • 对于元素类型为结构体的切片,unique约束结构体对象的某个字段不重复,通过unqiue=field指定这个字段名:Friends []User `validate:“unique=Name”`

自定义约束

func validateEmail(fl validator.FieldLevel) bool {
    input := fl.Field().String()
    if pass, _ := regexp.MatchString(`^([\w\.\_]{2,10})@(\w{1,})\.([a-z]{2,4})$`, input); pass {
	  return true
 }
 return false
}

// 注册一个自定义的validator
val.RegisterValidation("my_email", validateEmail)

Email string `validate:"my_email"`

5. http中间件

中间件的作用:将业务代码和非业务代码解耦。非业务代码指限流、超时控制、打日志等等
中间件的实现原理:传入一个http.Handler,外面套上一些非业务功能代码,再返回一个http.Handler;支持中间件层层嵌套;通过HandlerFunc把一个func(rw http.ResponseWriter, r *http.Request)函数转为Handler

func timeMiddleWare(next http.Handler) http.Handler {
    return http.HandlerFunc(func(rw http.ResponseWriter, r     *http.Request) {
        begin := time.Now()
        next.ServeHTTP(rw, r)
        timeElapsed := time.Since(begin)
        log.Printf("request %s use %d ms\n", r.URL.Path, timeElapsed.Milliseconds())
    })
}

6. GIN

Gin是一款高性能的、简单轻巧的http Web框架
安装方式go get -u github.com/gin-gonic/gin

6.1 路由

Gin的路由是基于httprouter做的,支持GET、POST、PUT、PATCH、DELETE、OPTIONS、HEAD;支持路由分组,不用重复写上级路径

package main

import (
	"net/http"

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

//你所需要的东西全都封装在了gin.Context里面,包括http.Request和ResponseWriter
func boy(c *gin.Context) {
	c.String(http.StatusOK, "hi boy") // 通过gin.Context.String返回一个text/plain类型的正文
}

func girl(c *gin.Context) {
	c.String(http.StatusOK, "hi girl")
}

func main() {
	// gin.SetMode(gin.ReleaseMode) // 发布模式,默认是Debug模式
	engine := gin.Default() // 默认的engine已自带了Logger和Recovery两个中间件
	engine.GET("/", boy)
	engine.POST("/", girl)

	// 路由分组
	oldVersion := engine.Group("/v1")
	oldVersion.GET("/student", boy) // http://localhost:5656/v1/student
	oldVersion.GET("/teacher", boy) // http://localhost:5656/v1/teacher

	newVersion := engine.Group("/v2")
	newVersion.GET("/student", girl) // http://localhost:5656/v2/student
	newVersion.GET("/teacher", girl) // http://localhost:5656/v2/teacher

	engine.Run(":5656")
}

6.2 参数获取

  • c.Query():从GET请求的URL中获取参数
  • c.Param():从Restful风格的url中获取参数
  • c.PostForm():从post表单中获取参数
  • c.FormFile():获取上传的文件,消息类型为form-data
  • c. MultipartForm():multipart/form-data可以上传多个form-data 并且用分隔符进行分割
// 从GET请求的URL中获取参数
func url(engine *gin.Engine) {
	engine.GET("/student", func(ctx *gin.Context) {
		name := ctx.Query("name")
		addr := ctx.DefaultQuery("addr", "China") // 如果没传addr参数,则默认为China
		ctx.String(http.StatusOK, name+" live in "+addr)
	})
}

// 从Restful风格的url中获取参数
func restful(engine *gin.Engine) {
	engine.GET("/student/:name/*addr", func(ctx *gin.Context) {
		name := ctx.Param("name")
		addr := ctx.Param("addr")
		ctx.String(http.StatusOK, name+" live in "+addr)
	})
}

// 从post表单中获取参数
func post(engine *gin.Engine) {
	engine.POST("/student", func(ctx *gin.Context) {
		name := ctx.PostForm("name")
		addr := ctx.DefaultPostForm("addr", "China") // 如果没传addr参数,则默认为China
		ctx.String(http.StatusOK, name+" live in "+addr)
	})
}

// 上传单个文件
func upload_file(engine *gin.Engine) {
	// 限制表单上传大小为8M,默认上限是32M
	engine.MaxMultipartMemory = 8 << 20
	engine.POST("/upload", func(ctx *gin.Context) {
		file, err := ctx.FormFile("file")
		if err != nil {
			fmt.Printf("get file error %v\n", err)
			ctx.String(http.StatusInternalServerError, "upload file failed")
		} else {
			ctx.SaveUploadedFile(file, "./data/"+file.Filename) // 把用户上传的文件存到data目录下
			ctx.String(http.StatusOK, file.Filename)
		}
	})
}

// 上传多个文件
func upload_multi_file(engine *gin.Engine) {
	engine.POST("/upload_files", func(ctx *gin.Context) {
		form, err := ctx.MultipartForm() // MultipartForm中不止包含多个文件
		if err != nil {
			ctx.String(http.StatusBadRequest, err.Error())
		} else {
			// 从MultipartForm中获取上传的文件
			files := form.File["files"]
			for _, file := range files {
				ctx.SaveUploadedFile(file, "./data/"+file.Filename) // 把用户上传的文件存到data目录下

			}
			ctx.String(http.StatusOK, "upload "+strconv.Itoa(len(files))+" files")
		}
	})
}

还可以定义struct,绑定到参数

type Student struct {
	Name string `form:"username" json:"name" uri:"user" xml:"user" yaml:"user" binding:"required"`
	Addr string `form:"addr" json:"addr" uri:"addr" xml:"addr" yaml:"addr" binding:"required"`
}

func formBind(engine *gin.Engine) {
	engine.POST("/stu/form", func(ctx *gin.Context) {
		var stu Student
		// 跟ShouldBind对应的是MustBind,MustBind内部会调用ShouldBind,如果ShouldBind发生error会直接c.AbortWithError(http.StatusBadRequest, err)
		if err := ctx.ShouldBind(&stu); err != nil {
			fmt.Println(err)
			ctx.String(http.StatusBadRequest, "parse paramter failed")
		} else {
			ctx.String(http.StatusOK, stu.Name+" live in "+stu.Addr)
		}
	})
}

func jsonBind(engine *gin.Engine) {
	engine.POST("/stu/json", func(ctx *gin.Context) {
		var stu Student
		if err := ctx.ShouldBindJSON(&stu); err != nil {
			fmt.Println(err)
			ctx.String(http.StatusBadRequest, "parse paramter failed")
		} else {
			ctx.String(http.StatusOK, stu.Name+" live in "+stu.Addr)
		}
	})
}

func uriBind(engine *gin.Engine) {
	// GET请求的参数在uri里
	engine.GET("/stu/uri/:user/:addr", func(ctx *gin.Context) {
		fmt.Println(ctx.Request.URL)
		var stu Student
		if err := ctx.ShouldBindUri(&stu); err != nil {
			fmt.Println(err)
			ctx.String(http.StatusBadRequest, "parse paramter failed")
		} else {
			ctx.String(http.StatusOK, stu.Name+" live in "+stu.Addr)
		}
	})
}

func xmlBind(engine *gin.Engine) {
	engine.POST("/stu/xml", func(ctx *gin.Context) {
		var stu Student
		if err := ctx.ShouldBindXML(&stu); err != nil {
			fmt.Println(err)
			ctx.String(http.StatusBadRequest, "parse paramter failed")
		} else {
			ctx.String(http.StatusOK, stu.Name+" live in "+stu.Addr)
		}
	})
}

func yamlBind(engine *gin.Engine) {
	engine.POST("/stu/yaml", func(ctx *gin.Context) {
		var stu Student
		if err := ctx.ShouldBindYAML(&stu); err != nil {
			fmt.Println(err)
			ctx.String(http.StatusBadRequest, "parse paramter failed")
		} else {
			ctx.String(http.StatusOK, stu.Name+" live in "+stu.Addr)
		}
	})
}

6.3 利用postman提交http请求

提交普通post请求

在这里插入图片描述

上传文件

在这里插入图片描述

提交json

在这里插入图片描述

提交xml

在这里插入图片描述

提交yaml

在这里插入图片描述

6.4 生成response

  • c.String():response Content-Type=text/plain
  • c.JSON():response Content-Type= application/json
  • c.XML():response Content-Type= application/xml
  • c.HTML():前端写好模板,后端往里面填值
  • c.Redirect():重定向
func text(engine *gin.Engine) {
	engine.GET("/user/text", func(c *gin.Context) {
		c.String(http.StatusOK, "hi boy") // response Content-Type:text/plain
	})
}

func json1(engine *gin.Engine) {
	engine.GET("/user/json1", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{"name": "zcy", "addr": "bj"}) // response Content-Type:application/json
	})
}

func json2(engine *gin.Engine) {
	// 匿名结构体
	var stu struct {
		Name string
		Addr string
	}
	stu.Name = "zcy"
	stu.Addr = "bj"
	engine.GET("/user/json2", func(c *gin.Context) {
		c.JSON(http.StatusOK, stu) // response Content-Type:application/json
	})
}

func jsonp(engine *gin.Engine) {
	// type Stident struct {
	// 	Name string
	// 	Addr string
	// }

	// var stu Student

	var stu struct {
		Name string
		Addr string
	}
	stu.Name = "zcy"
	stu.Addr = "bj"
	engine.GET("/user/jsonp", func(ctx *gin.Context) {
		// 如果请求参数里有callback=xxx,则response Content-Type为application/javascript,否则response Content-Type为application/json
		ctx.JSONP(http.StatusOK, stu)
	})
}

func xml(engine *gin.Engine) {
	var stu struct {
		Name string
		Addr string
	}
	stu.Name = "zcy"
	stu.Addr = "bj"
	engine.GET("/user/xml", func(c *gin.Context) {
		c.XML(http.StatusOK, gin.H{"name": "zcy", "addr": "bj"}) // response Content-Type:application/xml
	})
}

func yaml(engine *gin.Engine) {
	var stu struct {
		Name string
		Addr string
	}
	stu.Name = "zcy"
	stu.Addr = "bj"
	engine.GET("/user/yaml", func(c *gin.Context) {
		c.YAML(http.StatusOK, stu)
	})
}

func html(engine *gin.Engine) {
	engine.LoadHTMLFiles("static/template.html")
	engine.GET("/user/html", func(c *gin.Context) {
		// 通过json往前端页面上传值
		c.HTML(http.StatusOK, "template.html", gin.H{"title": "用户信息", "name": "zcy", "addr": "bj"})
	})
}

func redirect(engine *gin.Engine) {
	engine.GET("/not_exists", func(c *gin.Context) {
		c.Redirect(http.StatusMovedPermanently, "http://localhost:5656/user/html")
	})
}

6.5 参数检验

GIN的参数检验是基于go-playground/validator实现的

import (
	"github.com/gin-gonic/gin"
	"github.com/gin-gonic/gin/binding"
	"github.com/go-playground/validator/v10" // 注意要用新版本v10
)

type Student struct {
	// required:必须上传name参数
	Name       string    `form:"name" binding:"required"`
	// score必须为正数 
	Score      int       `form:"score" binding:"gt=0"`
	// 自定义验证before_today,日期格式东8区                                           
	Enrollment time.Time `form:"enrollment" binding:"required,before_today" time_format:"2006-01-02" time_utc:"8"`
	// 毕业时间要晚于入学时间
	Graduation time.Time `form:"graduation" binding:"required,gtfield=Enrollment" time_format:"2006-01-02" time_utc:"8"` 
}

// 自定义验证器
var beforeToday validator.Func = func(fl validator.FieldLevel) bool {
	if date, ok := fl.Field().Interface().(time.Time); ok {
		today := time.Now()
		if date.Before(today) {
			return true
		} else {
			return false
		}
	} else {
		return false
	}
}

6.6 中间件

package main

import (
	"log"
	"net/http"
	"time"

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

var limitCh = make(chan struct{}, 100) // 最多并发处理100个请求

func timeMiddleWare() gin.HandlerFunc {
	return func(ctx *gin.Context) {
		begin := time.Now()
		ctx.Next() // 执行业务逻辑
		timeElapsed := time.Since(begin)
		log.Printf("request %s use %d ms\n", ctx.Request.URL.Path, timeElapsed.Milliseconds())
	}
}

func limitMiddleWare() gin.HandlerFunc {
	return func(ctx *gin.Context) {
		limitCh <- struct{}{} // 并发度达到100时就会阻塞
		log.Printf("concurrence %d\n", len(limitCh))
		ctx.Next() // 执行业务逻辑
		<-limitCh
	}
}

func main() {
	engine := gin.Default()
	engine.Use(timeMiddleWare()) // 全局MiddleWare
	engine.GET("/", func(ctx *gin.Context) {
		ctx.String(http.StatusOK, "hi boy")
	})
	engine.GET("/girl", limitMiddleWare(), func(ctx *gin.Context) {
		// 局部MiddleWare
		ctx.String(http.StatusOK, "hi girl")
	})
	engine.Run(":5656")
}

gin-gonic/contrib上提供了丰富的第三方中间件

6.7 会话

http是无状态的,即服务端不知道两次请求是否来自于同一个客户端。Cookie由服务端生成,发送给客户端,客户端保存在本地。客户端每次发起请求时把Cookie带上,以证明自己的身份。HTTP请求中的Cookie头只会包含name和value信息(服务端只能取到name和value),domain、path、expires等cookie属性是由浏览器使用的,对服务器来说没有意义。Cookie可以被浏览器禁用。
server_session.go

package main

import (
	"encoding/base64"
	"fmt"
	"net/http"
	"strconv"
	"sync"
	"time"

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

var (
	authMap sync.Map
)

// cookie name需要符合规则,否则该cookie会被Gin框架默默地丢弃掉
func genCookieName(ctx *gin.Context) string {
	return base64.StdEncoding.EncodeToString([]byte(ctx.Request.RemoteAddr))
}

// 登录
func login(engine *gin.Engine) {
	engine.POST("/login", func(ctx *gin.Context) {
		// 为客户端生成cookie
		cookie_key := genCookieName(ctx)
		cookie_value := strconv.Itoa(time.Now().Nanosecond())
		// 服务端维护所有客户端的cookie,用于对客户端进行认证
		authMap.Store(cookie_key, cookie_value)
		// 把cookie发给客户端
		ctx.SetCookie(cookie_key, cookie_value,
			3000,        // maxAge,cookie的有效时间,时间单位秒
			"/",         // path,cookie存放目录
			"localhost", // cookie从属的域名
			false,       // 是否只能通过https访问
			true,        // 是否允许别人通过js获取自己的cookie
		)
		fmt.Printf("set cookie %s = %s to client\n", cookie_key, cookie_value)
		ctx.String(http.StatusOK, "登录成功")
	})
}

// 用户中心
func userCenter(engine *gin.Engine) {
	engine.POST("/center", authMiddleWare(), func(ctx *gin.Context) {
		// 为"/center"加个认证中间件
		ctx.String(http.StatusOK, "您已通过身份认证,这里是你的私人空间")
	})
}

func authMiddleWare() gin.HandlerFunc {
	return func(ctx *gin.Context) {
		cookie_key := genCookieName(ctx)
		var cookie_value string
		// 读取客户端的cookie
		for _, cookie := range ctx.Request.Cookies() {
			if cookie.Name == cookie_key {
				cookie_value = cookie.Value
				break
			}
		}

		// 验证Cookie Value是否正确
		if v, ok := authMap.Load(cookie_key); !ok {
			fmt.Printf("INVALID auth cookie %s = %s\n", cookie_key, cookie_value)
			ctx.JSON(http.StatusForbidden, gin.H{cookie_key: cookie_value})
			ctx.Abort() // 验证不通过,调用Abort
		} else {
			if v.(string) == cookie_value {
				ctx.Next() // 本中间件顺利通过
			} else {
				fmt.Printf("INVALID auth cookie %s = %s\n", cookie_key, cookie_value)
				ctx.JSON(http.StatusForbidden, gin.H{cookie_key: cookie_value})
				ctx.Abort() // 验证不通过,调用Abort
			}
		}
	}
}

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

	// 路由
	login(engine)
	userCenter(engine)

	gin.SetMode(gin.ReleaseMode) // 发布模式,屏蔽debug信息
	engine.Run("127.0.0.1:5656") // 测试方法,运行http/client/main.go里的authLogin()方法
}

client_session.go

func main() {
	if resp, err := http.Post("http://127.0.0.1:5656/login", "text/plain", nil); err != nil {
		panic(err)
	} else {
		fmt.Println("response body")
		io.Copy(os.Stdout, resp.Body) // 两个io数据流的拷贝
		os.Stdout.WriteString("\n")
		loginCookies := resp.Cookies() // 读取服务端返回的Cookie
		resp.Body.Close()
		if req, err := http.NewRequest("POST", "http://127.0.0.1:5656/center", nil); err != nil {
			panic(err)
		} else {
			// 下次请求再带上cookie
			for _, cookie := range loginCookies {
				fmt.Printf("receive cookie %s = %s\n", cookie.Name, cookie.Value)
				// cookie.Value += "1" // 修改cookie后认证不通过
				req.AddCookie(cookie)
			}
			client := &http.Client{}
			if resp, err := client.Do(req); err != nil {
				fmt.Println(err)
			} else {
				defer resp.Body.Close()
				fmt.Println("response body")
				io.Copy(os.Stdout, resp.Body) // 两个io数据流的拷贝
				os.Stdout.WriteString("\n")
			}
		}
	}
}

7. Beego

beego是一个大而全的http框架,用于快速开发go应用程序。bee工具提供诸多命令,帮助我们进行 beego 项目的创建、热编译、开发、测试、和部署

go get github.com/astaxie/beego
go get github.com/beego/bee
cd $GOPATH/src
bee new myweb
cd myweb
go build -mod=mod
bee run

beego的八大模块互相独立,高度解耦,开发者可任意选取:

  • 日志模块
  • ORM模块
  • Context模块:封装了request和response
  • Cache模块:封装了memcache、redis、ssdb
  • Config模块:解析.ini、.yaml、.xml、.json、.env等配置文件
  • httplib模块
  • Session模块:session保存在服务端,用于标识客户身份,跟踪会话
  • toolbox模块:健康检查、性能调试、访问统计、计划任务

MVC开发模式

在这里插入图片描述

在Model层可以使用beego提供的ORM功能

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/195058.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

互联网分层模型

互联网的逻辑实现被分为好几层。每一层都有自己的功能&#xff0c;就像建筑物一样&#xff0c;每一层都靠下一层支持。用户接触到的只是最上面的那一层&#xff0c;根本不会感觉到下面的几层。要理解互联网就需要自下而上理解每一层的实现的功能。如上图所示&#xff0c;互联网…

55.Isaac教程--Livox 激光雷达

Livox 激光雷达 ISAAC教程合集地址文章目录Livox 激光雷达支持的硬件和固件在桌面上设置和运行示例应用程序在机器人上设置和运行示例应用程序查看正在运行的应用程序将来Livox 激光雷达 Isaac SDK 支持使用 Livox LIDAR&#xff0c;包括兼容的驱动程序和示例应用程序。 支持的…

Android马甲包的那些事儿

制作Android马甲包最简单的方式就是使用 productFlavors 机制。本文就是在productFlavors机制的基础上制作的马甲包&#xff0c;每个马甲只需要在build.gradle文件中配置一下包名、各种key、签名文件配置启动页、logo、app名等资源配置服务器域名、微信分享回调Activity等代码此…

Windows上tensorflow的GPU死活引用不了(tensorflow 2.11无法调用GPU)

tensorflow对于gpu的支持只到2.10&#xff0c;如果你装了最新的tf(2.11)&#xff0c;需要先卸载2.11。 安装代码&#xff1a; pip install tensorflow2.10 -i https://pypi.tuna.tsinghua.edu.cn/simple/解决过程&#xff1a; 查看CUDA与cuDNN配套版本&#xff1a; https:/…

解决ModuleNotFoundError: No module named ‘pygame‘问题

一、问题描述在开发环境运行Python的源码游戏时&#xff0c;游戏不能正常运行&#xff0c;且提示&#xff08;ModuleNotFoundError: No module named pygame【没有发现模块错误&#xff1a;没有发现名为pygame的模块】&#xff09;如下图所示&#xff1a;二、问题分析通过查看提…

OpenCV实战——基于均值漂移算法检测图像内容

OpenCV实战——基于均值漂移算法检测图像内容0. 前言1. 均值漂移算法2. 检测图像内容3. 完整代码相关链接0. 前言 直方图反投影的结果是一个概率图&#xff0c;表示在特定图像位置找到给定图像内容的概率。假设我们现在知道一个物体在图像中的大概位置&#xff1b;概率图可用于…

在 Navicat Monitor for MySQL/MariaDB 中配置实例

Navicat Monitor for MySQL/MariaDB 是一个无代理的远程服务器监控工具&#xff0c;它包含的功能可以使监控数据库&#xff08;DB&#xff09;实例发挥最大效用和更轻松。此外&#xff0c;基于服务器的架构使其可以通过网页浏览器从任何地方访问&#xff0c;从而为你提供无障碍…

DaVinci 项目设置:图像缩放调整

项目设置/图像缩放调整Project Settings/Image Scaling图像缩放调整 Image Scaling选项卡可用于设置片段在输入、输出时的缩放及相应的插值算法。图像缩放调整Image Scaling主要用于选择缩放处理的插值方法&#xff0c;也可用于载入输入、输出缩放调整的预设。缩放过滤器Resize…

特别提醒|2023年考PMP需关注的5大问题

目前知道的是2023年考试时间为3月、5月、8月、11月&#xff0c;但是3月不给新报名&#xff0c;需要报名的话&#xff0c;就是报5月的考试了。当然有的伙伴会有一些小问题&#xff0c;这里给大家整理了一些基本的问题给大家回答一下&#xff0c;大家如果还有其他的问题可以评论提…

[Flink] 容错机制与状态一致性机制

文章目录1.状态一致性1.1 状态一致性分类2.一致性检查点 checkpoint3.端到端&#xff08;end-to-end&#xff09;状态一致性4. 端到端的精确一次&#xff08;exactly-once&#xff09;保证4.1 幂等写入4.2 事务写入5.FlinkKafka 端到端状态一致性的保证5.1 Exactly-once 两阶段…

常见智力题汇总(建议收藏)

&#x1f680;write in front&#x1f680; &#x1f4dc;所属专栏&#xff1a; 智力题 &#x1f6f0;️博客主页&#xff1a;睿睿的博客主页 &#x1f6f0;️代码仓库&#xff1a;&#x1f389;VS2022_C语言仓库 &#x1f3a1;您的点赞、关注、收藏、评论&#xff0c;是对我最…

Web 前端开发技术 —— JavaScript

Web 前端开发技术 —— JavaScript 总结 JavaScript 内容&#xff01; 文章目录Web 前端开发技术 —— JavaScript一、js 的引用方式与执行顺序1、引用方式在标签中直接写 js 代码复用 js 代码通过 import 方式2、执行顺序3、html、css、js 三者之间的关系二、变量与运算符变量…

C++之函数重载

文章目录前言一、函数重载二、如何支持函数重载&#xff08;C支持函数重载的原理--名字修饰(name Mangling)&#xff09;三、参数有什么区别才能构成函数重载1.参数个数不同2.参数类型不同3.参数顺序不同四、返回值类型不同是否可以构成函数重载总结前言 我们知道在使用C语言进…

jmh的一些作用

目录说明说明 jmh可以用来java基准测试&#xff0c;性能测试用这个测比较标准&#xff0c;可以设置预热、迭代次数&#xff0c;对某块代码精准测试&#xff0c;耗时时间单位有毫秒、纳秒等。 就先说到这\color{#008B8B}{ 就先说到这}就先说到这 在下Apollo\color{#008B8B}{在下…

AcWing 323. 战略游戏(树形DP + 状态机DP)

AcWing 323. 战略游戏&#xff08;树形DP 状态机DP&#xff09;一、问题二、分析1、思路分析2、状态表示3、状态转移4、循环设计5、初末状态三、代码一、问题 二、分析 1、思路分析 这道题最后问的其实就是&#xff0c;在一棵树中&#xff0c;每个边至少选择一个端点的条件下…

【FLASH存储器系列十五】NAND Flash究竟能不能随机读写到某个字节的数据?

网上有很多文章写道&#xff0c;nand flash的读写操作是以page为单位&#xff0c;还有文章说些nand flash时必须按page0、page1、page2…的顺序写&#xff0c;必须先写完前面的page才能写后面的page。难道nandflash就不能随机读到某个字节吗&#xff1f;只能一次性读一页&#…

区区几行代码,就能全面实现 Python 自动探索性数据分析

探索性数据分析是数据科学模型开发和数据集研究的重要组成部分之一。在拿到一个新数据集时首先就需要花费大量时间进行EDA来研究数据集中内在的信息。自动化的EDA Python包可以用几行Python代码执行EDA。 在本文中整理了10个可以自动执行EDA并生成有关数据的见解的Python包&am…

C语言——二分查找与猜数字游戏

文章目录二分查找二分查找的思想二分查找的条件二分查找的实现过程代码举例猜数字游戏游戏说明猜数字游戏思想代码实现打印菜单打印主函数打印游戏函数整体代码演示二分查找 题目&#xff1a; 在一个有序数组中查找具体的某个数字n。 首先我们先定义一个110的数组 &#xff0c;…

immersive-translate(沉浸式双语网页翻译扩展),解决谷歌翻译无法使用问题

前言 谷歌停止了大陆的谷歌翻译服务&#xff0c;所以找到了immersive-translate 插件解决翻译问题。当然 最直接就是 换个浏览器比如 Edge\Firefox等等。 主要特性 智能识别网页主内容区&#xff0c;区别于同类插件翻译网页所有的区域&#xff0c;这可以极大增强译文的阅读…

【C++11】右值引用与移动构造、万能引用与完美转发

目录 一、右值引用 1.1 左值引用和右值引用 1.2 左值引用与右值引用比较 1.3 右值引用的使用场景和意义 二、移动构造 2.1 移动构造的实现 2.2 移动赋值 2.3 默认成员函数 2.4 default关键字 2.5 delete 关键字 2.6 STL中的移动构造 二、完美转发 2.1 模板中的万能…