net/http
足够简单,性能也非常不错go get -u github.com/gin-gonic/gin
import “github.com/gin-gonic/gin”
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
func main(){
// 1.创建路由
r := gin.Default()
// 2.绑定路由规则,执行的函数
// gin.Context,封装了request和response
r.GET("/", func(c *gin.Context) {
c.String(http.StatusOK, "hello World!")
})
// 3.监听端口,默认在8080
// Run("里面不指定端口号默认为8080")
// 指定端口避免:8080这个常用端口被占用
r.Run(":8000")
}
net/http
这个包里有着默认路由,但是仍存在着不足,所以使用httprouter使用Restful风格的API(URL定位资源,用HTTP描述操作)
1.获取文章 /blog/getXxx Get blog/Xxx
2.添加 /blog/addXxx POST blog/Xxx
3.修改 /blog/updateXxx PUT blog/Xxx
4.删除 /blog/delXxxx DELETE blog/Xxx
通过Context的Param方法来获取API参数
示例:
package main
import (
"github.com/gin-gonic/gin"
"net/http"
"strings"
)
func main() {
r := gin.Default()
r.GET("/user/:name/*action", func(c *gin.Context) {
name := c.Param("name")
action := c.Param("action")
//c.String(http.StatusOK,"name = "+name+"action = "+action)
//截取/
action = strings.Trim(action, "/")
c.String(http.StatusOK, name+" is "+action)
})
//默认为监听8080端口
r.Run(":8000")
}
结果:
示例:
package main
import (
"fmt"
"github.com/gin-gonic/gin"
"net/http"
)
func main() {
r := gin.Default()
r.GET("/user", func(c *gin.Context) {
//不指定默认值,若是不传参则接受的是空串
//name := c.Query("name")
//指定默认值
//http://localhost:8080/user 才会打印出来默认的值
name := c.DefaultQuery("name", "Re")
c.String(http.StatusOK, fmt.Sprintf("hello %s", name))
})
r.Run(":8000")
}
不传参
传参
示例:
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>提交页面title>
head>
<body>
<form action="http://localhost:8000/form" method="post" action="application/x-www-form-urlencoded">
用户名:<input type="text" name="username" placeholder="请输入你的用户名"> <br>
密 码:<input type="password" name="password" placeholder="请输入你的用户名">
<input type="submit" value="提交">
form>
body>
html>
package main
import (
"fmt"
"github.com/gin-gonic/gin"
"net/http"
)
func main() {
r := gin.Default()
r.POST("/form", func(c *gin.Context) {
// 获取/form的请求类型,及各参数
types := c.DefaultPostForm("type", "post")
username := c.PostForm("username")
password := c.PostForm("password")
c.String(http.StatusOK, fmt.Sprintf("username:%s,password:%s,type:%s", username, password, types))
})
r.Run(":8000")
}
结果:
示例:
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>上传单个文件title>
head>
<body>
<form action="http://localhost:8000/upload" method="post" enctype="multipart/form-data">
上传文件:<input type="file" name="file" >
<input type="submit" value="提交">
form>
body>
html>
package main
import (
"github.com/gin-gonic/gin"
"log"
"net/http"
)
// 上传文件
func main() {
r := gin.Default()
//限制上传最大尺寸
r.MaxMultipartMemory = 8 << 20
r.POST("/upload", func(c *gin.Context) {
_, headers, err := c.Request.FormFile("file")
if err != nil {
log.Printf("Error when try to get file: %v", err)
}
//上传特定的文件
//headers.Size 获取文件大小
//if headers.Size > 1024*1024*2 {
// fmt.Println("文件太大了")
// return
//}
//headers.Header.Get("Content-Type")获取上传文件的类型
//if headers.Header.Get("Content-Type") != "image/png" {
// fmt.Println("只允许上传png图片")
// return
//}
c.SaveUploadedFile(headers, "./video/"+headers.Filename)
c.String(http.StatusOK, "上传成功" + headers.Filename)
})
r.Run(":8000")
结果:
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>上传多个文件title>
head>
<body>
<form action="http://localhost:8000/upload" method="post" enctype="multipart/form-data">
上传文件:<input type="file" name="files" multiple>
<input type="submit" value="提交">
form>
body>
html>
func main() {
// 1.创建路由
// 默认使用了2个中间件Logger(), Recovery()
r := gin.Default()
// 限制表单上传大小 8MB,默认为32MB
r.MaxMultipartMemory = 8 << 20
r.POST("/upload", func(c *gin.Context) {
form, err := c.MultipartForm()
if err != nil {
c.String(http.StatusBadRequest, fmt.Sprintf("get err %s", err.Error()))
}
// 获取所有图片
files := form.File["files"]
// 遍历所有图片
for _, file := range files {
// 逐个存
if err := c.SaveUploadedFile(file, file.Filename); err != nil {
c.String(http.StatusBadRequest, fmt.Sprintf("upload err %s", err.Error()))
return
}
}
c.String(200, fmt.Sprintf("upload ok %d files", len(files)))
})
//默认端口号是8080
r.Run(":8000")
}
结果:
使用示例:
package main
import (
"fmt"
"github.com/gin-gonic/gin"
)
// 路由组
func main() {
// 1.创建路由
// 默认使用了2个中间件Logger(), Recovery()
r := gin.Default()
// 路由组1 ,处理GET请求
v1 := r.Group("/v1")
// {} 是书写规范
{
v1.GET("/login", login)
v1.GET("submit", submit)
}
v2 := r.Group("/v2")
{
v2.POST("/login", login)
v2.POST("/submit", submit)
}
r.Run(":8000")
}
func login(c *gin.Context) {
name := c.DefaultQuery("name", "jack")
c.String(200, fmt.Sprintf("hello %s\n", name))
}
func submit(c *gin.Context) {
name := c.DefaultQuery("name", "lily")
c.String(200, fmt.Sprintf("hello %s\n", name))
}
使用r.NoRoute来设置
例如:
r.NoRoute(func(c *gin.Context) {
c.String(http.StatusNotFound, "sorry,天太冷了,页面跑去钻小被窝了")
})
结果:
User string `form:"username" json:"user" uri:"user" xml:"user" binding:"required"`
Pssword string `form:"password" json:"password" uri:"password" xml:"password" binding:"required"`
··DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>表单提交数据解析到结构体title>
head>
<body>
<form action="http://localhost:8000/loginForm" method="post" enctype="application/x-www-form-urlencoded">
用户名:<input type="text" name="username"><br>
密 码:<input type="password" name="password"><br>
<input type="submit" value="提交">
form>
body>
html>
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
type Login struct {
// binding:"required"修饰的字段,若接收为空值,则报错,是必须字段
User string `form:"username" json:"user" uri:"user" xml:"user" binding:"required"`
Pssword string `form:"password" json:"password" uri:"password" xml:"password" binding:"required"`
}
func main() {
// 1.创建路由
// 默认使用了2个中间件Logger(), Recovery()
r := gin.Default()
// JSON绑定
r.POST("/loginForm", func(c *gin.Context) {
// 声明接收的变量
var form Login
// Bind()默认解析并绑定form格式
// 根据请求头中content-type自动推断
if err := c.Bind(&form); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
// 判断用户名密码是否正确
if form.User != "root" || form.Pssword != "admin" {
c.JSON(http.StatusBadRequest, gin.H{"status": http.StatusBadRequest})
return
}
c.JSON(http.StatusOK, gin.H{"status": http.StatusOK,"body":form})
})
r.Run(":8000")
}
结果:
示例:
func main() {
// 1.创建路由
// 默认使用了2个中间件Logger(), Recovery()
r := gin.Default()
// JSON绑定
r.GET("/:user/:password", func(c *gin.Context) {
// 声明接收的变量
var login Login
// Bind()默认解析并绑定form格式
// 根据请求头中content-type自动推断
if err := c.ShouldBindUri(&login); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
// 判断用户名密码是否正确
if login.User != "root" || login.Pssword != "admin" {
c.JSON(http.StatusBadRequest, gin.H{"status": "304"})
return
}
c.JSON(http.StatusOK, gin.H{"status": "200"})
})
r.Run(":8000")
}
结果:
func main() {
// 1.创建路由
// 默认使用了2个中间件Logger(), Recovery()
r := gin.Default()
// 1.json
r.GET("/someJSON", func(c *gin.Context) {
c.JSON(200, gin.H{"message": "someJSON", "status": http.StatusOK})
})
// 2. 结构体响应
r.GET("/someStruct", func(c *gin.Context) {
var msg struct {
Name string
Message string
Number int
}
msg.Name = "root"
msg.Message = "message"
msg.Number = 123
c.JSON(http.StatusOK, msg)
})
// 3.XML
r.GET("/someXML", func(c *gin.Context) {
c.XML(200, gin.H{"message": "abc"})
})
// 4.YAML响应
r.GET("/someYAML", func(c *gin.Context) {
c.YAML(200, gin.H{"name": "zhangsan"})
})
// 5.protobuf格式,谷歌开发的高效存储读取的工具
// 数组?切片?如果自己构建一个传输格式,应该是什么格式?
//r.GET("/someProtoBuf", func(c *gin.Context) {
// reps := []int64{int64(1), int64(2)}
// // 定义数据
// label := "label"
// // 传protobuf格式数据
// data := &protoexample.Test{
// Label: &label,
// Reps: reps,
// }
// c.ProtoBuf(200, data)
//})
r.Run(":8000")
}
XML页面结果:
html文件:
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{{.title}}title>
head>
<body>
<h1>{{.test}}h1>
body>
html>
go文件:
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
// html模板渲染
func main() {
r := gin.Default()
// 加载模板文件
r.LoadHTMLGlob("static/*")
// 如果项目结构不同,也可以是
// r.LoadHTMLGlob("static/**/*")
r.GET("/index", func(c *gin.Context) {
c.HTML(http.StatusOK, "index.html", gin.H{"title": "我是测试", "test": "这是一个测试文件"})
})
r.Run(":8000")
}
结果:
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
// 重定向
func main() {
r := gin.Default()
r.GET("/index", func(c *gin.Context) {
c.Redirect(http.StatusMovedPermanently, "http://www.baidu.com")
})
r.Run(":8000")
}
package main
import (
"github.com/gin-gonic/gin"
"log"
"time"
)
// 同步异步
func main() {
// 1.创建路由
// 默认使用了2个中间件Logger(), Recovery()
r := gin.Default()
// 1.异步
r.GET("/long_async", func(c *gin.Context) {
// 需要搞一个副本
copyContext := c.Copy()
// 异步处理
go func() {
time.Sleep(30 * time.Second)
log.Println("异步执行:" + copyContext.Request.URL.Path)
}()
})
// 2.同步
r.GET("/long_sync", func(c *gin.Context) {
time.Sleep(30 * time.Second)
log.Println("同步执行:" + c.Request.URL.Path)
})
r.Run(":8000")
}
结果:
package main
import (
"fmt"
"github.com/gin-gonic/gin"
)
// 先定义一个中间件
func MiddleWare() gin.HandlerFunc {
return func(c *gin.Context) {
fmt.Println("中间件开始执行了")
// 设置变量到Context的key中,可以通过Get()取
c.Set("request", "中间件")
status := c.Writer.Status()
fmt.Println("中间件执行完毕", status)
}
}
func main() {
// 1.创建路由
// 默认使用了2个中间件Logger(), Recovery()
r := gin.Default()
// 注册中间件
r.Use(MiddleWare())
// {}为代码规范
{
r.GET("/test", func(c *gin.Context) {
// 取值
req, _ := c.Get("request")
fmt.Println("request:", req)
// 页面接收
c.JSON(200, gin.H{"request": req})
})
}
r.Run(":8000")
}
结果:
源码:
// Next should be used only inside middleware.
// Next 应该只在中间件中被使用
// It executes the pending handlers in the chain inside the calling handler.
// 挂起现在正在执行的handlers
// See example in GitHub.
func (c *Context) Next() {
c.index++
for s := int8(len(c.handlers)); c.index < s; c.index++ {
c.handlers[c.index](c)
}
}
测试:
// 中间件中Next的函数的测试
func main() {
r := gin.New()
mid1 := func(c *gin.Context) {
start := time.Now()
fmt.Println("middleware1 start")
// 注释 or 不注释,查看输入结果
//c.Next()
fmt.Println(time.Since(start))
fmt.Println("middleware1 ending")
}
mid2 := func(c *gin.Context) {
fmt.Println("middleware2 start")
c.Next()
fmt.Println("middleware2 ending")
}
r.Use(mid1)
r.Use(mid2)
r.GET("/", func(c *gin.Context) {
c.JSON(http.StatusOK, "hi")
})
r.Run(":8000")
}
结果:
# mid1中的Next()执行
middleware1 start
middleware2 start
middleware2 ending
758.6µs
middleware1 ending
# mid1中的Next()注释上
middleware1 start
# 不加Next则不会出现响应时间
0s
middleware1 ending
middleware2 start
middleware2 ending
可以看出,再加Next函数后mid1会先挂起,等其余的中间件(mid2)执行完再继续执行Next函数后的语句,并且这个过程是一个压栈的过程,也就是说先执行的Next后的语句后被执行。
Next之前的操作一般用来做验证处理,访问是否允许之类的。
Next之后的操作一般是用来做总结处理,比如格式化输出、响应结束时间,响应时长计算之类的。
示例:
package main
import (
"fmt"
"github.com/gin-gonic/gin"
)
func MiddleWare() gin.HandlerFunc {
return func(c *gin.Context) {
t := time.Now()
fmt.Println("中间件开始执行了")
// 设置变量到Context的key中,可以通过Get()取
c.Set("request", "中间件")
// 执行函数
c.Next()
// 中间件执行完后续的一些事情
status := c.Writer.Status()
fmt.Println("中间件执行完毕", status)
t2 := time.Since(t)
fmt.Println("time:", t2)
}
}
func main() {
// 1.创建路由
// 默认使用了2个中间件Logger(), Recovery()
r := gin.Default()
//局部中间键使用
r.GET("/ce", MiddleWare(), func(c *gin.Context) {
// 取值
req, _ := c.Get("request")
fmt.Println("request:", req)
// 页面接收
c.JSON(200, gin.H{"request": req})
})
r.Run(":8000")
}
结果:
只有在访问"/ce"会执行中间件
示例:
package main
import (
"fmt"
"github.com/gin-gonic/gin"
)
func main() {
// 1.创建路由
// 默认使用了2个中间件Logger(), Recovery()
r := gin.Default()
// 服务端要给客户端cookie
r.GET("cookie", func(c *gin.Context) {
// 获取客户端是否携带cookie
cookie, err := c.Cookie("key_cookie")
if err != nil {
cookie = "NotSet"
// 给客户端设置cookie
// maxAge int, 单位为秒
// path,cookie所在目录
// domain string,域名
// secure 是否智能通过https访问
// httpOnly bool 是否允许别人通过js获取自己的cookie
c.SetCookie("key_cookie", "value_cookie", 60, "/",
"localhost", false, true)
}
fmt.Printf("cookie的值是: %s\n", cookie)
})
r.Run(":8000")
}
// 其中 SetCookie方法的参数的含义为
// maxAge int, 单位为秒
// path,cookie所在目录
// domain string,域名
// secure 是否智能通过https访问
// httpOnly bool 是否允许别人通过js获取自己的cookie
SetCookie(name, value string, maxAge int, path, domain string, secure, httpOnly bool)
结果:
cookie的值是: NotSet
这样便有了cookie的值
示例:
package main
import (
"fmt"
"net/http"
"github.com/gorilla/sessions"
)
// 初始化一个cookie存储对象
// something-very-secret应该是一个你自己的密匙,只要不被别人知道就行
var store = sessions.NewCookieStore([]byte("something-very-secret"))
func main() {
http.HandleFunc("/save", SaveSession)
http.HandleFunc("/get", GetSession)
http.HandleFunc("/del", DelSession)
err := http.ListenAndServe(":8080", nil)
if err != nil {
fmt.Println("HTTP server failed,err:", err)
return
}
}
func SaveSession(w http.ResponseWriter, r *http.Request) {
// Get a session. We're ignoring the error resulted from decoding an
// existing session: Get() always returns a session, even if empty.
// 获取一个session对象,session-name是session的名字
session, err := store.Get(r, "session-name")
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// 在session中存储值
session.Values["foo"] = "bar"
session.Values[42] = 43
// 保存更改
session.Save(r, w)
}
func GetSession(w http.ResponseWriter, r *http.Request) {
session, err := store.Get(r, "session-name")
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
foo := session.Values["foo"]
fmt.Println(foo)
}
func DelSession(w http.ResponseWriter, r *http.Request) {
session, err := store.Get(r, "session-name")
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// 将session的时间设为小于零的数就是删除
session.Options.MaxAge = -1
// 保存更改
session.Save(r, w)
}
结果:
# 先save后get,可以取到值
bar
# 然后del再get,就取不到值了
<nil>
参考: