Golang中gin框架的学习
有这么一行c.JSON(200, gin.H{“message”: “use get method”})
这其中有一个gin.H{ },看样子,这像是一个结构体struct,查看gin框架的源码,声明如下:
所以,这只是一个map结构,别以为是一个struct
gin框架封装了http库,提供了GET、POST、PUT、DELETE、PATCH、HEAD、OPTIONS这些http请求方式。
使用router.method()来绑定路由
声明如下:
其中的METHOD可以是上面的7种方式。使用对应的METHOD访问对应的 url path,返回对应的response。
但是如果使用不对应的method访问path,就会返回404,比如使用post方法访问localhost:8080/get会返回404
切换输出的格式
router.GET("/get", func(c *gin.Context) {
c.JSON(200, gin.H{"message": "use get method"})
})
上面的这段代码,就是在用户访问localhost:8080时,c.JSON()返回一个响应,响应中的状态码是200,响应内容是一个JSON格式的字符串。
那么我们就很容易知道,不同的响应内容格式是通过调用*gin.Context类型变量的不同方法来实现的。
gin框架提供了很多响应内容的格式,常用的方法声明如下:
//返回json格式
func (c *Context) JSON(code int, obj interface{})
//返回xml格式
func (c *Context) XML(code int, obj interface{})
//返回yaml格式
func (c *Context) YAML(code int, obj interface{})
//返回string格式
func (c *Context) String(code int, format string, values ...interface{})
//渲染html模板后返回
func (c *Context) HTML(code int, name string, obj interface{})
注意上面每一个响应中都有一个状态码200。
这个状态码不仅可以手动指定一个数字,比如200,500,404;也可以使用http包中的状态码,语义化的状态码更好理解;
下面解释http协议中所有的状态码了。
package http
// HTTP status codes as registered with IANA.
// See: http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
const (
StatusContinue = 100 // RFC 7231, 6.2.1
StatusSwitchingProtocols = 101 // RFC 7231, 6.2.2
StatusProcessing = 102 // RFC 2518, 10.1
StatusOK = 200 // RFC 7231, 6.3.1
StatusCreated = 201 // RFC 7231, 6.3.2
StatusAccepted = 202 // RFC 7231, 6.3.3
StatusNonAuthoritativeInfo = 203 // RFC 7231, 6.3.4
StatusNoContent = 204 // RFC 7231, 6.3.5
StatusResetContent = 205 // RFC 7231, 6.3.6
StatusPartialContent = 206 // RFC 7233, 4.1
StatusMultiStatus = 207 // RFC 4918, 11.1
StatusAlreadyReported = 208 // RFC 5842, 7.1
StatusIMUsed = 226 // RFC 3229, 10.4.1
StatusMultipleChoices = 300 // RFC 7231, 6.4.1
StatusMovedPermanently = 301 // RFC 7231, 6.4.2
StatusFound = 302 // RFC 7231, 6.4.3
StatusSeeOther = 303 // RFC 7231, 6.4.4
StatusNotModified = 304 // RFC 7232, 4.1
StatusUseProxy = 305 // RFC 7231, 6.4.5
_ = 306 // RFC 7231, 6.4.6 (Unused)
StatusTemporaryRedirect = 307 // RFC 7231, 6.4.7
StatusPermanentRedirect = 308 // RFC 7538, 3
StatusBadRequest = 400 // RFC 7231, 6.5.1
StatusUnauthorized = 401 // RFC 7235, 3.1
StatusPaymentRequired = 402 // RFC 7231, 6.5.2
StatusForbidden = 403 // RFC 7231, 6.5.3
StatusNotFound = 404 // RFC 7231, 6.5.4
StatusMethodNotAllowed = 405 // RFC 7231, 6.5.5
StatusNotAcceptable = 406 // RFC 7231, 6.5.6
StatusProxyAuthRequired = 407 // RFC 7235, 3.2
StatusRequestTimeout = 408 // RFC 7231, 6.5.7
StatusConflict = 409 // RFC 7231, 6.5.8
StatusGone = 410 // RFC 7231, 6.5.9
StatusLengthRequired = 411 // RFC 7231, 6.5.10
StatusPreconditionFailed = 412 // RFC 7232, 4.2
StatusRequestEntityTooLarge = 413 // RFC 7231, 6.5.11
StatusRequestURITooLong = 414 // RFC 7231, 6.5.12
StatusUnsupportedMediaType = 415 // RFC 7231, 6.5.13
StatusRequestedRangeNotSatisfiable = 416 // RFC 7233, 4.4
StatusExpectationFailed = 417 // RFC 7231, 6.5.14
StatusTeapot = 418 // RFC 7168, 2.3.3
StatusUnprocessableEntity = 422 // RFC 4918, 11.2
StatusLocked = 423 // RFC 4918, 11.3
StatusFailedDependency = 424 // RFC 4918, 11.4
StatusUpgradeRequired = 426 // RFC 7231, 6.5.15
StatusPreconditionRequired = 428 // RFC 6585, 3
StatusTooManyRequests = 429 // RFC 6585, 4
StatusRequestHeaderFieldsTooLarge = 431 // RFC 6585, 5
StatusUnavailableForLegalReasons = 451 // RFC 7725, 3
StatusInternalServerError = 500 // RFC 7231, 6.6.1
StatusNotImplemented = 501 // RFC 7231, 6.6.2
StatusBadGateway = 502 // RFC 7231, 6.6.3
StatusServiceUnavailable = 503 // RFC 7231, 6.6.4
StatusGatewayTimeout = 504 // RFC 7231, 6.6.5
StatusHTTPVersionNotSupported = 505 // RFC 7231, 6.6.6
StatusVariantAlsoNegotiates = 506 // RFC 2295, 8.1
StatusInsufficientStorage = 507 // RFC 4918, 11.5
StatusLoopDetected = 508 // RFC 5842, 7.2
StatusNotExtended = 510 // RFC 2774, 7
StatusNetworkAuthenticationRequired = 511 // RFC 6585, 6
)
比如下面的语句:
router.GET("/json", func(c *gin.Context) {
c.JSON(http.StatusNotFound, gin.H{"message": "not found"})
//等价于
//c.JSON(404, gin.H{"message": "not found"})
})
获取URL中的值与request body中的值
参考:https://blog.csdn.net/weixin_34199335/article/details/86362964
gin的文档,接收从客户端发来的各种参数,有两大类方式:
c.Param()
c.Query // 查询URL中的参数
c.DefaultQuery // 查询URL中的参数,URL中有值则使用该值,没有值使用设置的默认值
c.PostForm // (前端request)消息主体body里的x-www-form-urlencoded 参数
c.DefaultPostForm //前端传过来的消息主体中的参数,可以设置默认值
c.QueryMap
c.PostFormMap
c.FormFile
c.MultipartForm
其中,
c.Query() 和 c.DefaultQuery()方法 ,查询的是URL中的参数
c.PostForm()和c.DefaultPostForm()方法,查询的是前端传过来的消息主体中的参数
c.Bind
c.BindJSON
c.BindXML
c.BindQuery
c.BindYAML
c.ShouldBind
c.ShouldBindJSON
c.ShouldBindXML
c.ShouldBindQuery
c.ShouldBindYAML
如下面官方的例子中 ?id=1234&page=1 这个,就是 查询参数(query params)
你会看到这个参数就放在url里的
如果参数不是放在url里的,也可以在body里,比如 body 里的x-www-form-urlencoded 参数,如下面的name=manu&message=this_is_great
对于gin,要使用 name := c.PostForm(“name”) api
关注下 Content-Type 这个字段,表示了body的类型,更多看看这篇文章
四种常见的 POST 提交数据方式://imququ.com/post/four-ways-to-post-data-in-http.html
还有像 c.FormFile,用于处理上传文件的
请求示例
POST /post?id=1234&page=1 HTTP/1.1
Content-Type: application/x-www-form-urlencoded
name=manu&message=this_is_great
代码示例
func main() {
router := gin.Default()
router.POST("/post", func(c *gin.Context) {
id := c.Query("id") // 查询参数
page := c.DefaultQuery("page", "0")
name := c.PostForm("name") // (前端request)消息主体 body 里的x-www-form-urlencoded 参数
message := c.PostForm("message")
fmt.Printf("id: %s; page: %s; name: %s; message: %s", id, page, name, message)
})
router.Run(":8080")
}
结果输出
id: 1234; page: 1; name: manu; message: this_is_great
假如有这么一个请求:
POST /post/test?id=1234&page=1 HTTP/1.1
请求头: Content-Type: application/x-www-form-urlencoded
form表单参数: name=manu&message=this_is_great
gin的实现:
id := c.Query("id") //查询请求URL后面的参数
page := c.DefaultQuery("page", "0") //查询请求URL后面的参数,如果没有填写默认值
name := c.PostForm("name") //从表单中查询参数
/
//POST和PUT主体参数优先于URL查询字符串值。
name := c.Request.FormValue("name")
//返回POST并放置body参数,URL查询参数被忽略
name := c.Request.PostFormValue("name")
//从表单中查询参数,如果没有填写默认值
message := c.DefaultPostForm("message", "aa")
假如gin定义的路由路径为:
router.POST("/post/:uuid", func(c *gin.Context){
...
}
则获取uuid的值方法为
uuid := c.Param("uuid") //取得URL中参数
其他:
s, _ := c.Get("current_manager") //从用户上下文读取值
type User struct{
ID int
Name string
Age int
}
var u User
//从http.Request中读取值到User结构体中,手动确定绑定类型binding.Form
err1 := c.BindWith(&u, binding.Form)
//从http.Request中读取值到User结构体中,根据请求方法类型和请求内容格式类型自动确定绑定类型
err2 := c.Bind(&u)
//用户上下文和session生命周期不同,每一次请求会生成一个对应的上下文,一次http请求结束,该次请求的上下文结束,一般来说session(会话)会留存一段时间
//session(会话)中一般保存用户登录状态等信息,context(上下文)主要用于在一次http请求中,在中间件(流)中进行信息传递
user := sessions.Default©.get(“user”)
ShouldBind(&objA)方法
绑定请求体的常规方法使用c.Request.Body,并且不能多次调用
type formA struct {
Foo string `json:"foo" xml:"foo" binding:"required"`
}
type formB struct {
Bar string `json:"bar" xml:"bar" binding:"required"`
}
func SomeHandler(c *gin.Context) {
objA := formA{}
objB := formB{}
// This c.ShouldBind consumes c.Request.Body and it cannot be reused.
if errA := c.ShouldBind(&objA); errA == nil {
c.String(http.StatusOK, `the body should be formA`)
// Always an error is occurred by this because c.Request.Body is EOF now.
} else if errB := c.ShouldBind(&objB); errB == nil {
c.String(http.StatusOK, `the body should be formB`)
} else {
...
}
}
ShouldBindBodyWith(&objA)方法
同样,可以使用c.ShouldBindBodyWith
func SomeHandler(c *gin.Context) {
objA := formA{}
objB := formB{}
// This reads c.Request.Body and stores the result into the context.
if errA := c.ShouldBindBodyWith(&objA, binding.JSON); errA == nil {
c.String(http.StatusOK, `the body should be formA`)
// At this time, it reuses body stored in the context.
} else if errB := c.ShouldBindBodyWith(&objB, binding.JSON); errB == nil {
c.String(http.StatusOK, `the body should be formB JSON`)
// And it can accepts other formats
} else if errB2 := c.ShouldBindBodyWith(&objB, binding.XML); errB2 == nil {
c.String(http.StatusOK, `the body should be formB XML`)
} else {
...
}
}
c.ShouldBindBodyWith 在绑定之前将body存储到上下文中,这对性能有轻微影响,因此如果你要立即调用,则不应使用此方法
此功能仅适用于这些格式 – JSON, XML, MsgPack, ProtoBuf。对于其他格式,Query, Form, FormPost, FormMultipart, 可以被c.ShouldBind()多次调用而不影响性能
比如:http://localhost:8080/user/jane/20/beijing/female?id=999&height=170&wigth=100
上面的这个链接中,可以通过向上面讲的使用/user/:name/:age/:addr/:sex来分别匹配jane、20、beijing、female。
gin框架中如果使用URL传递参数,那么在绑定的时候,是这样的:/user/:name/:age/:addr/:sex
上面这个path绑定了4个URL参数,分别是name、age、addr、sex
首先gin.Context对象的Params属性保存着URL中的所有参数:
router.GET("/user/:name/:age/:addr/:sex", func(c *gin.Context) {
//打印URL中所有参数
c.JSON(200, fmt.Sprintln(c.Params))
})
如果请求http://localhost:8080/user/jane/20/beijing/female
得到的输出如下:
"[{name jane} {age 20} {addr beijing} {sex female}]"
注意最后面的那个换行符
使用gin.Context对象的Param(key)方法获取某一个key的值,方法声明如下:
router.GET("/user/:name/:age/:addr/:sex", func(c *gin.Context) {
name := c.Param("name") //获取参数的时候,不要写name前面的冒号:
c.JSON(200, name)
})
访问http://localhost:8080/user/jane/20/beijing/female,输出"jane"
获取URL中路径值和获取参数不一样。
比如:http://localhost:8080/user/jane/20/beijing/female?id=999&height=170&wight=100
可以使用下面的方法获取请求参数id、height、weight的值。
获取指定参数的值
//返回URL中key的值
func (c *Context) Query(key string) string
使用gin.Context.Query(key string)可以获取指定key的值,
测试:
router.GET("/user/:name/:age/:addr/:sex", func(c *gin.Context) {
id := c.Query("id")
height := c.Query("height")
weight := c.Query("weight")
c.JSON(200, gin.H{"id": id, "height": height, "weight": weight})
})
访问http://localhost:8080/user/jane/20/beijing/female?id=999&height=170&wight=100,输出内容如下:
{"height":"170","id":"999","weight":"100"}
当然,以前写php的时候,如果要想判断是否接收到了某个参数,如果没有接收到,那么就设置一个默认值,最常用的就是三元运算符,比如下面这样:
$id = empty($_POST['id']) ? 0 : $_POST['id'];
gin框架当然也想到了这么一点,gin.Context.DefaultQuery()方法,允许指定接收的参数名,以及没有接收到该参数值时,设置的默认值,声明如下:
func (c *Context) DefaultQuery(key, defaultValue string) string
只有当请求没有携带key,那么此时的默认值就会生效。其他情况,默认值不生效。即使URL中的该key的值为空,那么也不会启用默认值,获取的值就是空。
注意,这是获取URL中的参数值。
接收multipart/urlencoded form(POST表单数据)
和获取URL中的参数很相似:
GET请求中的参数使用Query(key),DefaultQuery(key,default)来获取
POST请求中的参数使用PostForm(key),DefaultPostForm(key,default)来获取。
package main
import (
"github.com/gin-gonic/gin"
)
func main() {
router := gin.Default()
router.POST("/post", func(c *gin.Context) {
name := c.PostForm("name")
age := c.PostForm("age")
sex := c.DefaultPostForm("sex", "male")
c.JSON(200, gin.H{"name": name, "age": age, "sex": sex})
})
router.Run()
}
获注意要想获取POST方式传递的参数,那么绑定的路由监听方式就必须是router.POST,不能使router.GET。
同时获取URL中的参数和POST的参数
前提:请求方必须是使用POST方式传递,只不过URL中也包含一部分参数而已。
同时,服务器端必须使用router.POST方式来接收,不能使用router.GET方式接收,因为router.GET只能接收GET方式传递的数据。
package main
import (
"github.com/gin-gonic/gin"
)
func main() {
router := gin.Default()
//注意,这里必须使用router.POST
router.POST("/post", func(c *gin.Context) {
name := c.PostForm("name")
age := c.PostForm("age")
sex := c.DefaultPostForm("sex", "male")
addr := c.Query("addr")
hobby := c.DefaultQuery("hobby", "basketball")
c.JSON(200, gin.H{"name": name, "age": age, "sex": sex, "addr": addr, "hobby": hobby})
})
router.Run()
}
请求:localhost:8080/post?addr=beijing&hobby=football
同时使用post方式传递name=abc&age=21111&sex=female
那么得到的响应就是如下内容:
实际在项目开发中,通常将将GET或POST函数中的func(c *gin.Context)封装成一个函数作为参数传入,同时将c.JSON(statuscode, gin.H{key:value, })中的gin.H{}配置成所需要的json格式的数据结构,内容使用自定义的struct类型的数据结构返回,例如:
//main.go
//维度补充信息关联数据库
router.GET("/table/dimension/relate/db", handler.GetDBList)
//package handle
//GetDBList 路由中与db相关的绑定方法,不显示返回内容
func GetDBList(c *gin.Context) {
//通过select语句从数据库中获取需要的数据
dbList, err := api.GetDB()
if err != nil {
fmt.Println("get related db info from data_source error:", err)
response.StatusOK(c, http.StatusInternalServerError, err.Error(), nil)
return
}
//使用map[string]interface{}定义数据结构类型,{"list":dbList}使用从数据库中获取到的数据初始化要response给前端的数据内容
c.JSON(http.StatusOK, map[string]interface{}{
"list": dbList,
})
}
//package api
//GetDB 获取有关数据库的id和数据库名称组成的list
func GetDB() ([]InstanceNameID, error) {
//初始化自定义的InstanceNameID数组,用于存放从数据库中筛选出的数据
res := []InstanceNameID{}
conn := databases.GetDBConn()
err := conn.Table("data_source").
Select("id, instance_name").
Where("data_source.is_deleted=0").Scan(&res).Error
if err != nil {
return []InstanceNameID{}, err
}
return res, err
}
//package api
//InstanceNameID 的数据结构定义, ``中的gorm和json的位置无先后顺序,且gorm的value中尽量带上Column, gorm不要拼错,否则其指定的内容会为空
type InstanceNameID struct {
//自定义的struct中的字段,通常需要在其他包中使用,所以需要将首字母大写,代表是导出字段,在其他包中也可以引用;如果不小心写成小写,则改字段的内容可能为空。
Name string `gorm:"Column:instance_name" json:"name"`
ID int64 `gorm:"Column:id" json:"id"`
}
超文本传输协议(HTTP)的设计目的是保证客户机与服务器之间的通信。
HTTP 的工作方式是客户机与服务器之间的请求-应答协议。
web 浏览器可能是客户端,而计算机上的网络应用程序也可能作为服务器端。
举例:客户端(浏览器)向服务器提交 HTTP 请求;服务器向客户端返回响应。响应包含关于请求的状态信息以及可能被请求的内容。
gin框架封装了http库,提供了GET、POST、PUT、DELETE、PATCH、HEAD、OPTIONS这些http请求方式。
使用router.method()来绑定路由,声明如下:
func (group *RouterGroup) METHOD(relativePath string, handlers ...HandlerFunc) IRouters
两种最常用的 HTTP 方法是:GET 和 POST。
在客户机和服务器之间进行请求-响应时,两种最常被用到的方法是:GET 和 POST。
GET - 从指定的资源请求数据。
POST - 向指定的资源提交要被处理的数据
请注意,查询字符串(名称/值 对)是在 GET 请求的 URL 中发送的,例如下列URL(中?后面的键值对)形式,就需要放在GET方法中:
/test/demo_form.asp?name1=value1&name2=value2
有关 GET 请求的其他一些说明:
请注意,查询字符串(名称/值 对)是在 POST 请求的 HTTP 消息主体中发送的:
POST /test/demo_form.asp HTTP/1.1
Host: w3schools.com
name1=value1&name2=value2
有关 POST 请求的其他一些解释:
GET与POST对比
摘自:HTTP 方法:GET 对比 POST
gin框架中对url参数的解析
Browser(前端) 发出请求, Server(后端)给出响应。
golang要请求远程网页,可以使用net/http包中提供的http.GET()或者http.POST()方法实现。
query string 参数与body参数
query stirng 路由用连接参数的形式,如key1=value1?key2=value2,这是通过urlencode编码后的。 对于query string,经常出现参数不存在的情况,需要提供默认值。
router.GET("/welcome", func(c *gin.Context) {
firstname := c.DefaultQuery("firstname", "Guest")
lastname := c.Query("lastname")
c.String(http.StatusOK, "Hello %s %s", firstname, lastname)
})
DefaultQuery是当不存在的时候提供一个默认的参数。
body格式有四种: application/json、application/x-www-form-urlencoded、application/xml、multipart/form-data
c.PostFROM解析的是x-www-form-urlencoded或from-data的参数。
比如,运行下面的代码go run main.go
package main
import (
"github.com/gin-gonic/gin"
)
func main() {
router := gin.Default()
router.GET("/table/dimension/relate/field", func(c *gin.Context) {
db := c.Query("db")
table := c.DefaultQuery("table", "dimension")
c.JSON(200, gin.H{"db": db, "table": table})
})
router.Run("127.0.0.1:5000")
}
并在浏览器中输入这个URL
将会返回json格式的内容—— {“db”:“dw”,“table”:“dimension”}
ListenAndServe函数
ListenAndServe方法根据addr和handler参数构建了一个Server对象,并调用该对象的ListenAndServe方法进行监听和响应。那么第二个参数Handler的作用是什么呢,为什么通常传nil值?
// ListenAndServe always returns a non-nil error.
func ListenAndServe(addr string, handler Handler) error {
server := &Server{Addr: addr, Handler: handler}
return server.ListenAndServe()
}
Handler的定义如下:
type Handler interface {
ServerHTTP(ResponseWriter, *Request)
}
Handler是一个接口类型,包含了ServerHTTP方法,该方法对客户端的请求(Request对象)进行处理,并通过ResponseWriter将响应信息传送回客户端。所以,Handler是Server对象处理请求的逻辑的关键所在。但是上面ListenAndServe方法的Handler参数为nil,Handler为Nil的Server对象如何实现处理请求的逻辑呢?
注意到Server对象的Handler属性的注释有提到如果Handler值为nil,那么Server对象将使用http.DefaultServeMux来处理请求。
http.DefaultServeMux是一个ServeMux对象,ServeMux对象是一个HTTP请求的分发器,将匹配了相应URL的请求分发给相应的处理函数进行处理。其定义如下:
type ServeMux struct {
mu sync.RWMutex
m map[string]muxEntry
hosts bool // whether any patterns contain hostnames
}
type muxEntry struct {
h Handler
pattern string
}
ServeMux正是通过map结构(map[string]muxEntry)来匹配请求和Handler的,起到了路由的作用。并且ServeMux本身也是一个Handler,因为它实现了Handler接口的ServeHTTP方法。
其中Server定义如下:
// A Server defines parameters for running an HTTP server.
// The zero value for Server is a valid configuration.
type Server struct {
Addr string // TCP address to listen on, ":http" if empty
Handler Handler // handler to invoke, http.DefaultServeMux if nil
// TLSConfig optionally provides a TLS configuration for use
// by ServeTLS and ListenAndServeTLS. Note that this value is
// cloned by ServeTLS and ListenAndServeTLS, so it's not
// possible to modify the configuration with methods like
// tls.Config.SetSessionTicketKeys. To use
// SetSessionTicketKeys, use Server.Serve with a TLS Listener
// instead.
TLSConfig *tls.Config
// ReadTimeout is the maximum duration for reading the entire
// request, including the body.
//
// Because ReadTimeout does not let Handlers make per-request
// decisions on each request body's acceptable deadline or
// upload rate, most users will prefer to use
// ReadHeaderTimeout. It is valid to use them both.
ReadTimeout time.Duration
// ReadHeaderTimeout is the amount of time allowed to read
// request headers. The connection's read deadline is reset
// after reading the headers and the Handler can decide what
// is considered too slow for the body.
ReadHeaderTimeout time.Duration
// WriteTimeout is the maximum duration before timing out
// writes of the response. It is reset whenever a new
// request's header is read. Like ReadTimeout, it does not
// let Handlers make decisions on a per-request basis.
WriteTimeout time.Duration
// IdleTimeout is the maximum amount of time to wait for the
// next request when keep-alives are enabled. If IdleTimeout
// is zero, the value of ReadTimeout is used. If both are
// zero, ReadHeaderTimeout is used.
IdleTimeout time.Duration
// MaxHeaderBytes controls the maximum number of bytes the
// server will read parsing the request header's keys and
// values, including the request line. It does not limit the
// size of the request body.
// If zero, DefaultMaxHeaderBytes is used.
MaxHeaderBytes int
// TLSNextProto optionally specifies a function to take over
// ownership of the provided TLS connection when an NPN/ALPN
// protocol upgrade has occurred. The map key is the protocol
// name negotiated. The Handler argument should be used to
// handle HTTP requests and will initialize the Request's TLS
// and RemoteAddr if not already set. The connection is
// automatically closed when the function returns.
// If TLSNextProto is not nil, HTTP/2 support is not enabled
// automatically.
TLSNextProto map[string]func(*Server, *tls.Conn, Handler)
// ConnState specifies an optional callback function that is
// called when a client connection changes state. See the
// ConnState type and associated constants for details.
ConnState func(net.Conn, ConnState)
// ErrorLog specifies an optional logger for errors accepting
// connections, unexpected behavior from handlers, and
// underlying FileSystem errors.
// If nil, logging is done via the log package's standard logger.
ErrorLog *log.Logger
disableKeepAlives int32 // accessed atomically.
inShutdown int32 // accessed atomically (non-zero means we're in Shutdown)
nextProtoOnce sync.Once // guards setupHTTP2_* init
nextProtoErr error // result of http2.ConfigureServer if used
mu sync.Mutex
listeners map[*net.Listener]struct{}
activeConn map[*conn]struct{}
doneChan chan struct{}
onShutdown []func()
}
更多相关技术内容咨询欢迎前往并持续关注六星社区了解详情。
程序员编程交流QQ群:805358732
如果你想用Python开辟副业赚钱,但不熟悉爬虫与反爬虫技术,没有接单途径,也缺乏兼职经验
关注下方微信公众号:Python编程学习圈,获取价值999元全套Python入门到进阶的学习资料以及教程,还有Python技术交流群一起交流学习哦。
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!