【Gin】安装,初体验及计划

框架文档手册:

  1. 简书:https://www.jianshu.com/p/98965b3ff638/

  2. 官方文档:https://github.com/gin-gonic/gin

  3. learnku:https://learnku.com/docs/gin-gonic/2018/gin-readme/3819

安装

首先贴一下官方文档中的安装方式,使用 go get 安装

要安装 Gin 包,你需要安装 Go 并且设置好你的 Go 工作空间。

下载并安装它:

$ go get -u github.com/gin-gonic/gin

在你的代码中导入它:

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

(可选的) 导入 net/http 。 在使用类似 http.StatusOK 的常量时必须导入。

import "net/http"

但是在本地测试后发现,下载包实在是慢到爆炸。从golang技术群了解到可以使用go mod来创建项目,这里先说一下我了解及搜集到的go mod相关知识:

go mod 安装gin

学习自:https://www.cnblogs.com/zhaohaiyu/p/11809394.html

go module

go module是Go1.11版本之后官方推出的版本管理工具,并且从Go1.13版本开始,go module将是Go语言默认的依赖管理工具。

在使用中的感受是,go mod和npm大概差不多,都是一个依赖包管理工具,可以很方便的将依赖包缓存到本地,并且可以通过 go mod vendor 将缓存输出为实体vendor目录

GO111MODULE

要启用go module支持首先要设置环境变量GO111MODULE,通过它可以开启或关闭模块支持,它有三个可选值:off、on、auto,默认值是auto。

GO111MODULE=off禁用模块支持,编译时会从GOPATH和vendor文件夹中查找包。 GO111MODULE=on启用模块支持,编译时会忽略GOPATH和vendor文件夹,只根据 go.mod下载依赖。 GO111MODULE=auto,当项目在$GOPATH/src外且项目根目录有go.mod文件时,开启模块支持。

简单来说,设置GO111MODULE=on之后就可以使用go module了,以后就没有必要在GOPATH中创建项目了,并且还能够很好的管理项目依赖的第三方包信息。

使用 go module 管理依赖后会在项目根目录下生成两个文件go.mod和go.sum。

go.mod就相当于php中的composer.json,里面记录了直接引入和间接引入的依赖,go.sum的资料比较少,看了一圈似乎平时打交道的并不多,先暂且跳过

设置环境变量GO111MODULE的方法:

  1. windows:set GO111MODULE=on 或者 go env -w GO111MODULE=on
  2. linux:export GO111MODULE=on
GOPROXY

Go1.11之后设置GOPROXY命令为:

export GOPROXY=https://goproxy.cn

Go1.13之后GOPROXY默认值为https://proxy.golang.org,在国内是无法访问的,所以十分建议大家设置GOPROXY,这里我推荐使用goproxy.cn。

go env -w GOPROXY=https://goproxy.cn,direct

这个是用来设置go mod的代理的

go mod命令

常用的go mod命令如下:

go mod download    下载依赖的module到本地cache(默认为$GOPATH/pkg/mod目录)
go mod edit        编辑go.mod文件
go mod graph       打印模块依赖图
go mod init        初始化当前文件夹, 创建go.mod文件
go mod tidy        增加缺少的module,删除无用的module
go mod vendor      将依赖复制到vendor下
go mod verify      校验依赖
go mod why         解释为什么需要依赖
go.mod

go.mod文件记录了项目所有的依赖信息,其结构大致如下:

module github.com/Q1mi/studygo/blogger
go 1.12
require (
    github.com/DeanThompson/ginpprof v0.0.0-20190408063150-3be636683586
    github.com/gin-gonic/gin v1.4.0
    github.com/go-sql-driver/mysql v1.4.1
    github.com/jmoiron/sqlx v1.2.0
    github.com/satori/go.uuid v1.2.0
    google.golang.org/appengine v1.6.1 // indirect
)
  1. module用来定义包名
  1. require用来定义依赖包及版本
  2. indirect表示间接引用

除了这几个外还有一个 replace 可以用来替换依赖包源的,例如golang官方包需要使用魔法访问,不过你可也可以将包用replace替换为github里的包:replace golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9 => github.com/golang/crypto v0.0.0-20181203042331-505ab145d0a9

依赖的版本

go mod支持语义化版本号,比如go get foo@v1.2.3,也可以跟git的分支或tag,比如go get foo@master,当然也可以跟git提交哈希,比如go get foo@e3702bed2。关于依赖的版本支持以下几种格式:

gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7
gopkg.in/vmihailenco/msgpack.v2 v2.9.1
gopkg.in/yaml.v2 <=v2.2.1
github.com/tatsushid/go-fastping v0.0.0-20160109021039-d7bb493dee3e
latest
replace

在国内访问golang.org/x的各个包都需要翻墙,你可以在go.mod中使用replace替换成github上对应的库。

replace (
    golang.org/x/crypto v0.0.0-20180820150726-614d502a4dac => github.com/golang/crypto v0.0.0-20180820150726-614d502a4dac
    golang.org/x/net v0.0.0-20180821023952-922f4815f713 => github.com/golang/net v0.0.0-20180826012351-8a410e7b638d
    golang.org/x/text v0.3.0 => github.com/golang/text v0.3.0
)
go get

在项目中执行go get命令可以下载依赖包,并且还可以指定下载的版本。

运行go get -u将会升级到最新的次要版本或者修订版本(x.y.z, z是修订版本号, y是次要版本号) 运行go get -u=patch将会升级到最新的修订版本 运行go get package@version将会升级到指定的版本号version 如果下载所有依赖可以使用go mod download命令。

使用 go mod download 下载依赖

整理依赖

我们在代码中删除依赖代码后,相关的依赖库并不会在go.mod文件中自动移除。这种情况下我们可以使用go mod tidy命令更新go.mod中的依赖关系。

可以使用 go mod tidy 更新依赖关系

go mod edit

格式化 因为我们可以手动修改go.mod文件,所以有些时候需要格式化该文件。Go提供了一下命令: go mod edit -fmt

添加依赖项 go mod edit -require=golang.org/x/text

添加依赖使用 go mod edit -require=

移除依赖项 如果只是想修改go.mod文件中的内容,那么可以运行go mod edit -droprequire=package path,比如要在go.mod中移除golang.org/x/text包,可以使用如下命令: go mod edit -droprequire=golang.org/x/text 关于go mod edit的更多用法可以通过go help mod edit查看。

移除依赖使用 go mod edit -droprequire=

在项目中使用go module

既有项目 如果需要对一个已经存在的项目启用go module,可以按照以下步骤操作:

在项目目录下执行go mod init,生成一个go.mod文件。 执行go get,查找并记录当前项目的依赖,同时生成一个go.sum记录每个依赖库的版本和哈希值。 新项目 对于一个新创建的项目,我们可以在项目文件夹下按照以下步骤操作:

执行go mod init 项目名命令,在当前项目文件夹下创建一个go.mod文件。 手动编辑go.mod中的require依赖项或执行go get自动发现、维护依赖。

上面是go mod的使用笔记,实际体验了下,go mod体验还是非常好的,使用了代理后依赖的下载速度非常快,结合go mod命令安装一下gin

使用 go mod 命令安装

首先,以任意非GOPATH目录作为项目目录,创建一个入口文件:

package main

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


func main() {
	r := gin.Default()
	r.GET("/ping", func(c *gin.Context) {
		c.JSON(200, gin.H{
			"message": "pong",
		})
	})
	r.Run() // listen and serve on 0.0.0.0:8080 (for windows "localhost:8080")
}

然后使用go mod init命令初始化创建go.mod

λ go mod init gin
go: creating new go.mod: module gin

然后使用go run main.go运行入口文件,这个时候 go mod 会自动下载所需要的的依赖到缓存

λ go run main.go
go: finding module for package github.com/gin-gonic/gin
go: downloading github.com/gin-gonic/gin v1.6.0
go: found github.com/gin-gonic/gin in github.com/gin-gonic/gin v1.6.0
go: downloading github.com/go-playground/validator/v10 v10.2.0
go: downloading github.com/mattn/go-isatty v0.0.12
go: downloading github.com/golang/protobuf v1.3.3
go: downloading gopkg.in/yaml.v2 v2.2.8
go: downloading github.com/go-playground/universal-translator v0.17.0
go: downloading github.com/leodido/go-urn v1.2.0
go: downloading github.com/go-playground/locales v0.13.0

同时会将依赖自动写入 go.modgo.sum

λ cat go.mod
module gin

go 1.14

require github.com/gin-gonic/gin v1.6.0 // indirect

λ cat go.sum
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
......

再尝试一下安装mysql库的依赖试试好了

安装依赖

上文数据库操作说过,go中操作数据库需要使用依赖包,如:github.com/go-sql-driver/mysql

使用 go mod 安装试试

C:\project\ggg
λ go mod edit -require=github.com/go-sql-driver/mysql@latest

λ cat go.mod
module gin

go 1.14

require (
        github.com/gin-gonic/gin v1.6.0 // indirect
        github.com/go-sql-driver/mysql v1.5.0
)
删除依赖
C:\project\ggg
λ go mod edit -droprequire=github.com/go-sql-driver/mysql@latest
go mod: -droprequire=github.com/go-sql-driver/mysql@latest: need just path, not path@version

C:\project\ggg
λ go mod edit -droprequire=github.com/go-sql-driver/mysql

偷懒直接修改了下上次执行的命令,这里drop是不需要版本号的

初识gin

回到go中来,可以看到刚才为了创建gin项目,使用了手册中推荐的入门代码:

func main() {
	r := gin.Default()
	r.GET("/ping", func(c *gin.Context) {
		c.JSON(200, gin.H{
			"message": "pong",
		})
	})
	r.Run() // listen and serve on 0.0.0.0:8080 (for windows "localhost:8080")
}

这里可以大概猜一猜每一行是干什么的:

r := gin.Default()

这里目测是获取gin项目默认实体的,既然有 default 那就一定有自定义的方法,而在源码中也发现了,在 default 方法上面就有一个 new() 方法,用来初始化一个自定义的实体

// New returns a new blank Engine instance without any middleware attached.
// By default the configuration is:
// - RedirectTrailingSlash:  true
// - RedirectFixedPath:      false
// - HandleMethodNotAllowed: false
// - ForwardedByClientIP:    true
// - UseRawPath:             false
// - UnescapePathValues:     true
func New() *Engine {
	debugPrintWARNINGNew()
	engine := &Engine{
		RouterGroup: RouterGroup{
			Handlers: nil,
			basePath: "/",
			root:     true,
		},
		FuncMap:                template.FuncMap{},
		RedirectTrailingSlash:  true,
		RedirectFixedPath:      false,
		HandleMethodNotAllowed: false,
		ForwardedByClientIP:    true,
		AppEngine:              defaultAppEngine,
		UseRawPath:             false,
		UnescapePathValues:     true,
		MaxMultipartMemory:     defaultMultipartMemory,
		trees:                  make(methodTrees, 0, 9),
		delims:                 render.Delims{Left: "{{", Right: "}}"},
		secureJsonPrefix:       "while(1);",
	}
	engine.RouterGroup.engine = engine
	engine.pool.New = func() interface{} {
		return engine.allocateContext()
	}
	return engine
}

// Default returns an Engine instance with the Logger and Recovery middleware already attached.
func Default() *Engine {
	debugPrintWARNINGDefault()
	engine := New()
	engine.Use(Logger(), Recovery())
	return engine
}

这些以后再往深挖,这篇先看表层的东西

r.GET("/ping", func(c *gin.Context){})

r是框架实例化的一个实体,通过前面的http包我们知道,golang对http开发来说是很简单的,只需要注册回调到内核,然后在监听服务的时候将内核传入监听内就行(可以使用默认的系统内核,也可以自定)

那么这里就很相似了:

  1. r 代表框架实体
  2. .GET()是一个创建请求回调到内核的方法
  3. "/ping" 是路由
  4. func(c *gin.Context)是回调的handel

通过这一行可以了解到的东西还是很多的:

  1. 创建一个监听回调既然有GET方法,那就应该同样有POST,PUT等符合RESTFUL风格的方法,同时应该会有一个ANY方法来接收所有类型的请求
  2. 同时,根据以往的经验,gin.Context里应该有保存请求的上下文,即最简单的 requestresponse 实体

在源码中可以得到答案:

RESTFUL风格的路由
// Any registers a route that matches all the HTTP methods.
// GET, POST, PUT, PATCH, HEAD, OPTIONS, DELETE, CONNECT, TRACE.
func (group *RouterGroup) Any(relativePath string, handlers ...HandlerFunc) IRoutes {
	group.handle("GET", relativePath, handlers)
	group.handle("POST", relativePath, handlers)
	group.handle("PUT", relativePath, handlers)
	group.handle("PATCH", relativePath, handlers)
	group.handle("HEAD", relativePath, handlers)
	group.handle("OPTIONS", relativePath, handlers)
	group.handle("DELETE", relativePath, handlers)
	group.handle("CONNECT", relativePath, handlers)
	group.handle("TRACE", relativePath, handlers)
	return group.returnObj()
}
  1. 可以看到这里有一个ANY方法,使用ANY会同时注册 GET, POST, PUT, PATCH, HEAD, OPTIONS, DELETE, CONNECT, TRACE. 这些方式的请求到路由中。同时也可以看到,RouterGroup应该就是用来处理路由的结构体。
  2. 还有一点就是,这里的 handel 是一个可变参数 ...HandlerFunc,这个后面再研究
gin.Context
// Context is the most important part of gin. It allows us to pass variables between middleware,
// manage the flow, validate the JSON of a request and render a JSON response for example.
type Context struct {
	writermem responseWriter
	Request   *http.Request
	Writer    ResponseWriter

	Params   Params
	handlers HandlersChain
	index    int8
	fullPath string

	engine *Engine

	// Keys is a key/value pair exclusively for the context of each request.
	Keys map[string]interface{}

	// Errors is a list of errors attached to all the handlers/middlewares who used this context.
	Errors errorMsgs

	// Accepted defines a list of manually accepted formats for content negotiation.
	Accepted []string

	// queryCache use url.ParseQuery cached the param query result from c.Request.URL.Query()
	queryCache url.Values

	// formCache use url.ParseQuery cached PostForm contains the parsed form data from POST, PATCH,
	// or PUT body parameters.
	formCache url.Values
}

很明显了,一次请求需要的东西都在上下文中保存着,比如请求和响应需要用到的结构体,请求参数,完整的请求地址等

c.JSON(200, gin.H{})

  1. c.JSON应该是用来返回数据的方法了
  2. 200很明显是http状态码
  3. gin.H 大概是响应的具体内容,这一点需要后期再观察

可以确定的一点是,响应请求要使用到上下文gin.Context,在demo中有简单的json响应,具体的话应该会将html,json等响应格式分开

// HTML renders the HTTP template specified by its file name.
// It also updates the HTTP code and sets the Content-Type as "text/html".
// See http://golang.org/doc/articles/wiki/
func (c *Context) HTML(code int, name string, obj interface{}) {
	instance := c.engine.HTMLRender.Instance(name, obj)
	c.Render(code, instance)
}
// JSON serializes the given struct as JSON into the response body.
// It also sets the Content-Type as "application/json".
func (c *Context) JSON(code int, obj interface{}) {
	c.Render(code, render.JSON{Data: obj})
}

r.run()

这个不用多说,运行框架的方法

func (engine *Engine) Run(addr ...string) (err error) {
	defer func() { debugPrintError(err) }()

	address := resolveAddress(addr)
	debugPrint("Listening and serving HTTP on %s\n", address)
	err = http.ListenAndServe(address, engine)
	return
}

run方法里可以看出来,监听地址是传入的可变参数字符串,那么既然是可变参数是不是能达到多端口监听响应http服务的效果呢?

address := resolveAddress(addr)
func resolveAddress(addr []string) string {
	switch len(addr) {
	case 0:
		if port := os.Getenv("PORT"); port != "" {
			debugPrint("Environment variable PORT=\"%s\"", port)
			return ":" + port
		}
		debugPrint("Environment variable PORT is undefined. Using port :8080 by default")
		return ":8080"
	case 1:
		return addr[0]
	default:
		panic("too many parameters")
	}
}

匆匆源码中可以看出来,如果在环境变量中设置了PORT的话会使用环境变量中的PORT作为默认监听端口,不然的话就是8080了。 另外自定义的端口只能有一个,也就是说无法做到多端口监听,当传入多个地址的时候会报错:too many parameters

开启并访问一下8080/ping试试吧

λ go run main.go
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.

[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
 - using env:   export GIN_MODE=release
 - using code:  gin.SetMode(gin.ReleaseMode)

[GIN-debug] GET    /ping                     --> main.main.func1 (3 handlers)
[GIN-debug] Environment variable PORT is undefined. Using port :8080 by default
[GIN-debug] Listening and serving HTTP on :8080
[GIN] 2020/03/22 - 13:56:49 | 200 |            0s |       127.0.0.1 | GET      /ping
λ curl 127.0.0.1:8080/ping
{"message":"pong"}
访问默认的8080/试试
λ go run main.go
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.

[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
 - using env:   export GIN_MODE=release
 - using code:  gin.SetMode(gin.ReleaseMode)

[GIN-debug] GET    /ping                     --> main.main.func1 (3 handlers)
[GIN-debug] Environment variable PORT is undefined. Using port :8080 by default
[GIN-debug] Listening and serving HTTP on :8080
[GIN] 2020/03/22 - 13:56:49 | 200 |            0s |       127.0.0.1 | GET      /ping
[GIN] 2020/03/22 - 13:58:05 | 404 |            0s |       127.0.0.1 | GET      /
λ curl 127.0.0.1:8080/
404 page not found
自己编写一个路由,返回个hello world

查看了下手册,.HTML()是用来渲染模板的,很明显=-=暂时还没有学习go的模板相关内容,所以这里还是返回json吧

func main() {
	r := gin.Default()
	r.GET("/ping", func(c *gin.Context) {
		c.JSON(200, gin.H{
			"message": "pong",
		})
	})
	r.GET("/", func(context *gin.Context) {
		context.JSON(200, "Hello world")
	})
	r.Run() // listen and serve on 0.0.0.0:8080 (for windows "localhost:8080")
}
λ go run main.go
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.

[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
 - using env:   export GIN_MODE=release
 - using code:  gin.SetMode(gin.ReleaseMode)

[GIN-debug] GET    /ping                     --> main.main.func1 (3 handlers)
[GIN-debug] GET    /                         --> main.main.func2 (3 handlers)
[GIN-debug] Environment variable PORT is undefined. Using port :8080 by default
[GIN-debug] Listening and serving HTTP on :8080
[GIN] 2020/03/22 - 14:03:08 | 200 |            0s |       127.0.0.1 | GET      /
λ curl 127.0.0.1:8080/
"Hello world"

计划

初体验就到这了,默认的demo中可以了解到的东西就跟上面写的一样了。这里就不重复了。

那么计划中是会使用gin去写一个关于 仓鼠养殖 的小网站,用户能看到的功能大概只会有首页+文章列表+文章详情三块,后台会围绕这3块功能展开。其中首页和文章详情准备做一个真·静态化,将文章详情直接输出成html(毕竟功能只有展示)。有人可能会说太简单,不过这也就是练练手,做那么复杂干嘛=-=

今天就到这来了,早上被云程序员杠精了,一天的心情都不好了,请大家在讨论的时候不要用踩一捧一的形式展开,个人认为所有的东西,存在即合理,不要盲目转语言,不要盲目踩其他语言。不管转什么语言,会多少语言,其开发的核心和思路都是一样的。就像这个gin框架,在使用前我根本没有了解过他的源码到底是怎么样的,但是各种语言各种框架的思想都是万变不离其宗的,通过demo就能猜出来大概是什么样的东西这才是真的东西。

另外我的博客一点都不高端,非常接地气,这个博客站本来要也就是用来记录我的学习/复习过程的,大部分内容都是我自己实实在在的体会,你可以说我的博客不如其他人的专业的博客,但是请不要拿着到处复制过去的博客甩我脸上让我去和你交流。。。真就人均B站学习委员:“恩。很好。讲得不错,放进收藏夹了,我学会了”

程序幼儿员-龚学鹏
请先登录后发表评论
  • latest comments
  • 总共0条评论