#28 Golang 标准库
Golang GoStdLib 2021-02-09简单地列一下 Golang 标准库中的包,知道他们是做什么的。
coding in a complicated world
简单地列一下 Golang 标准库中的包,知道他们是做什么的。
reflect 包通过反射(Reflection),我们可以在程序运行时,动态的增删该一些信息(类型,方法,变量),用来完成一些特殊的任务。
主要是实现不同类型都支持的一些方法。在 ORM,序列化等方面都有应用。
这种动态获取信息的能力让程序的开发变得更加灵活,但也会损失部分性能,毕竟需要进行很多类型检查、值转换等操作。
而且,滥用的话会导致程序变得复杂,可读性下降。
type Person struct {
Name string
Age int
}
func Insert(db *sql.DB, v interface{}) error {
value := reflect.ValueOf(v)
typ := reflect.TypeOf(v)
sql := "INSERT INTO persons ("
placeholders := "VALUES ("
for i := 0; i < typ.Elem().NumField(); i++ {
fieldName := typ.Elem().Field(i).Name
sql += fmt.Sprintf("%s, ", fieldName)
placeholders += "?, "
}
sql = sql[:len(sql)-2] + ")"
placeholders = placeholders[:len(placeholders)-2] + ")"
args := make([]interface{}, typ.Elem().NumField())
for i := 0; i < typ.Elem().NumField(); i++ {
args[i] = value.Elem().Field(i).Interface()
}
_, err := db.Exec(sql+placeholders, args...)
return err
}
package main
import (
"fmt"
"reflect"
)
type Person struct {
Name string
Age int
Address string
}
// 定义结构体方法
func (p Person) Hello() string {
return "Hello, " + p.Name
}
func main() {
p := Person{
Name: "Alice",
Age: 30,
Address: "123 Main St",
}
fmt.Println("获取变量的反射类型:===============")
pType := reflect.TypeOf(p) // &reflect.rtype
fmt.Printf("Type: %s, %#v\n", pType, pType) // main.Person
// fmt.Println("Type:", pType.Elem()) // panic: reflect: Elem of invalid type main.Person
pType2 := reflect.TypeOf(&p)
fmt.Printf("Type: %s, %#v\n", pType2, pType2) // *main.Person
fmt.Printf("Type: %s, %#v\n", pType2.Elem(), pType2.Elem()) // main.Person (指针类型的基础类型)
fmt.Println("获取变量的反射值:===============")
pValue := reflect.ValueOf(p) // main.Person
fmt.Printf("Value: %s, %#v\n", pValue, pValue)
pValue2 := reflect.ValueOf(&p)
fmt.Printf("Value: %s, %#v\n", pValue2, pValue2) // 地址
fmt.Printf("Value: %s, %#v\n", pValue2.Elem(), pValue2.Elem())
fmt.Println("遍历结构体字段:===============")
for i := 0; i < pType.NumField(); i++ {
field := pType.Field(i)
value := pValue.Field(i)
fmt.Printf("%s: %v\n", field.Name, value.Interface())
}
fmt.Println("修改结构体字段值:===============")
ageField1 := pValue.FieldByName("Age")
fmt.Printf("Value: %#v\n", ageField1)
// ageField1.SetInt(31) // panic: reflect: reflect.Value.SetInt using unaddressable value
// ageField1.Elem().SetInt(31) // panic: reflect: call of reflect.Value.Elem on int Value
ageField2 := pValue2.Elem().FieldByName("Age")
fmt.Printf("Value: %#v\n", ageField2)
ageField2.SetInt(31) // 只有指针类型才能通过反射修改
fmt.Println("New Age:", p.Age)
fmt.Println("调用结构体方法:===============")
helloMethod := pValue.MethodByName("Hello")
result := helloMethod.Call(nil)
fmt.Println(result)
// panic: reflect: call of reflect.Value.FieldByName on ptr Value
pValue2.FieldByName("Age")
// panic: reflect: reflect.Value.SetInt using unaddressable value
// a := reflect.Indirect(pValue)
b := reflect.Indirect(pValue2)
b.FieldByName("Age").SetInt(33)
fmt.Printf("%#v\n", b)
}
reflect.TypeOf:获取变量的类型reflect.ValueOf:获取变量的值Type.NumField:获取结构体字段数量Type.Field:获取结构体字段信息
Value.Field:获取结构体字段的值
Value.FieldByName:根据字段名获取结构体字段的值Value.SetInt:设置整数类型字段的值Value.MethodByName:根据方法名获取结构体方法Value.Call:调用结构体方法GO111MODULE 是什么?
每一个 Golang 初学者都会遇到 GOPATH 和 Go Module 的问题,至少在最近的一两年是这样的。
简单的说,就是由于 Golang 诞生于谷歌,所以早期的项目工程化受谷歌内部开发流程影响很大。谷歌内部不同项目的代码放在一起,方便相互引用。GOPATH 就是这种代码管理方式的体现,所有的包都放在一个固定的开发目录下。
但是谷歌外面的世界,可能是受开源生态的影响,我们习惯将代码拆分成不同的包,分散在不通的仓库,需要什么包就导入什么包。所以虽然有一些人一直在吹捧 GOPATH 模式,但是大多数人还是喜欢传统的包模式。
所以在 Go Module 之前,官方或者社区也都有出一些解决方案,其中最有名的是 dep、vender。但随着最终方案 Go Module 的确定,他们已经完成了历史使命,而我最近两个月才开始学 Go,当然就跳过他们了。
package main
import (
"fmt"
"net/http"
)
func hello(w http.ResponseWriter, req *http.Request) {
fmt.Fprintf(w, "hello world\n")
}
func main() {
http.HandleFunc("/", hello)
http.ListenAndServe(":8080", nil)
}
重点是记住这两点:
func hello(w http.ResponseWriter, req *http.Request)http.HandleFunc 注册一个函数到指定路径上ListenAndServe 流程分析https://github.com/golang/go/blob/master/src/net/http/server.go#L3240
https://github.com/golang/go/blob/master/src/net/http/server.go#L2976
func ListenAndServe(addr string, handler Handler) error {
server := &Server{Addr: addr, Handler: handler}
return server.ListenAndServe()
}
func (srv *Server) ListenAndServe() error {
if srv.shuttingDown() {
return ErrServerClosed
}
addr := srv.Addr
if addr == "" {
addr = ":http"
}
ln, err := net.Listen("tcp", addr)
if err != nil {
return err
}
return srv.Serve(ln)
}
主要的逻辑:
net.Listen("tcp", addr) -> net.Listenernet.Listener.Accept() -> net.Connhttp.Server.newConn(conn) -> http.Conngo http.Conn.serve(ctx)http.Conn.readRequest(ctx) -> http.responseserverHandler{c.server}.ServeHTTP(w, w.req)serverHandler.ServeHTTP 就比较关键了:http.Server 上的 Handler;http.DefaultServeMux 实现简单的 URL 路由。ListenAndServe 传了个 nil 进来,srv.Handler 为 nil 就用 DefaultServeMux。type ServeMux struct {
mu sync.RWMutex
m map[string]muxEntry
es []muxEntry // slice of entries sorted from longest to shortest.
hosts bool // whether any patterns contain hostnames
}
func (mux *ServeMux) match(path string) (h Handler, pattern string)
func (mux *ServeMux) redirectToPathSlash(host, path string, u *url.URL) (*url.URL, bool)
func (mux *ServeMux) shouldRedirectRLocked(host, path string) bool
func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string)
func (mux *ServeMux) handler(host, path string) (h Handler, pattern string)
func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request)
func (mux *ServeMux) Handle(pattern string, handler Handler)
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request))

// ==================================================================
// 封装 handler,添加到路由表 =========================================
// ==================================================================
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
DefaultServeMux.HandleFunc(pattern, handler)
}
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
if handler == nil {
panic("http: nil handler")
}
// https://github.com/golang/go/blob/master/src/net/http/server.go#L2505
// func (mux *ServeMux) Handle(pattern string, handler Handler)
// 把 handler 注册到 mux.m (map[string]muxEntry) 上
// muxEntry{h: handler, pattern: pattern}
mux.Handle(pattern, HandlerFunc(handler))
}
// 封装普通函数为 Handler
type HandlerFunc func(ResponseWriter, *Request)
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r)
}
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
// ==================================================================
// 路由匹配 =========================================================
// ==================================================================
ServeHTTP -> Handler -> handler -> match
-> redirectToPathSlash -> shouldRedirectRLocked -> 跳转(http.RedirectHandler)
默认路由规则:
http.Handle)不用 http.HandleFunc,而是传入一个 Handler(实现 ServeHTTP 方法的结构体)。
package main
import (
"fmt"
"net/http"
)
type HelloHandler struct {
content string
}
func (h *HelloHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
fmt.Fprintf(w, h.content)
}
func main() {
http.Handle("/", &HelloHandler{"hello world\n"})
http.ListenAndServe(":8080", nil)
}
func Handle(pattern string, handler Handler) { DefaultServeMux.Handle(pattern, handler) }
http.Handle 和 http.HandleFunc 都只是给默认路由(DefaultServeMux)上注册一个规则,知道这一点就可以了。
重点是 mux.Handle 和 Handler 接口。
package main
import (
"fmt"
"net/http"
)
type HelloHandler struct {
content string
}
func (h *HelloHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
fmt.Fprintf(w, h.content)
}
func main() {
http.ListenAndServe(":8080", &HelloHandler{"hello world\n"})
}
知道背后的流程就豁然开朗了。
当然,这样也是可以的:
func hello(w http.ResponseWriter, req *http.Request) {
fmt.Fprintf(w, "hello world\n")
}
func main() {
http.ListenAndServe(":8080", http.HandlerFunc(hello))
}
func(w http.ResponseWriter, req *http.Request)GitHub 上一搜索 golang muxgolang router 就有,这是几个可以参考的方案:
我自己实现一个简单的路由机制,参考 Django:
<name> OR <converter:name>
其中:converter 内置支持 str, int, slug, uuid, path, 可以自定义,默认是 str; name 可以是合法的 Go 变量名。
通过 Handler 套娃实现中间件:
type LogMiddleware struct {
handler Handler
}
func (this LogMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request) {
start := time.Now()
this.handler.ServeHTTP(w, r)
fmt.Printf("%s %s %v\n", r.Method, r.URL.RequestURI(), time.Since(start))
}
mux := ...
http.ListenAndServe(":8000", LogMiddleware{mux})
系统内置 Handler:
grep -E "func.+ServeHTTP" src/net/http -R | grep -Fv "_test.go"
src/net/http/cgi/host.go:func (h *Handler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
src/net/http/fs.go:func (f *fileHandler) ServeHTTP(w ResponseWriter, r *Request) {
src/net/http/httputil/reverseproxy.go:func (p *ReverseProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
src/net/http/pprof/pprof.go:func (name handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
src/net/http/server.go:func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
src/net/http/server.go:func (rh *redirectHandler) ServeHTTP(w ResponseWriter, r *Request) {
src/net/http/server.go:func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
src/net/http/server.go:func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {
src/net/http/server.go:func (h *timeoutHandler) ServeHTTP(w ResponseWriter, r *Request) {
src/net/http/server.go:func (globalOptionsHandler) ServeHTTP(w ResponseWriter, r *Request) {
src/net/http/server.go:func (h initALPNRequest) ServeHTTP(rw ResponseWriter, req *Request) {
src/net/http/triv.go:func (ctr *Counter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
src/net/http/triv.go:func (ch Chan) ServeHTTP(w http.ResponseWriter, req *http.Request) {
有待分析。
Google最近公布了实现Go 1.5自举(Bootstrap)的计划。相关文档的作者是Go核心开发者Russ Cox,他在Go语言上已经耕耘了接近6年。据Russ介绍,Google就“如何从Go源码树中去除所有的C程序”已经酝酿了一年。
官方库里面只看到 PlainAuth 和 CramMd5Auth 两种认证方法。
type loginAuth struct {
username, password string
}
func LoginAuth(username, password string) smtp.Auth {
return &loginAuth{username, password}
}
func (a *loginAuth) Start(server *smtp.ServerInfo) (proto string, toServer []byte, err error) {
return "LOGIN", []byte{}, nil
}
func (a *loginAuth) Next(fromServer []byte, more bool) (toServer []byte, err error) {
if more {
switch strings.ToUpper(string(fromServer)) {
case "USERNAME:":
return []byte(a.username), nil
case "PASSWORD:":
return []byte(a.password), nil
default:
return nil, fmt.Errorf("unknown command %v", fromServer)
}
}
return nil, nil
}
package main
import (
"crypto/tls"
"errors"
"fmt"
"net"
"net/smtp"
)
// ============================================================================
type Transaction struct {
Host string
Port uint16
LocalName string
TlsEnable bool
Username string
Password string
MailFrom string
RcptTo []string
Data []byte
}
func NewTransaction() Transaction {
trans := Transaction{}
return trans
}
func (trans Transaction) Send() error {
addr := fmt.Sprintf("%s:%d", trans.Host, trans.Port)
// SendMail(addr string, a Auth, from string, to []string, msg []byte) error
c, err := smtp.Dial(addr)
if err != nil {
return err
}
defer c.Close()
c.Hello(trans.LocalName)
if ok, _ := c.Extension("STARTTLS"); ok {
serverName, _, _ := net.SplitHostPort(addr)
config := &tls.Config{
InsecureSkipVerify: true,
ServerName: serverName,
}
if err = c.StartTLS(config); err != nil {
return err
}
} else {
fmt.Printf("smtp: server doesn't support STARTTLS\n")
}
if trans.Username != "" {
if ok, _ := c.Extension("AUTH"); !ok {
return errors.New("smtp: server doesn't support AUTH")
}
auth := smtp.PlainAuth("", trans.Username, trans.Password, trans.Host)
if err = c.Auth(auth); err != nil {
fmt.Println("smtp: authentication failed")
return err
}
}
if err = c.Mail(trans.MailFrom); err != nil {
return err
}
for _, addr := range trans.RcptTo {
if err = c.Rcpt(addr); err != nil {
return err
}
}
w, err := c.Data()
if err != nil {
return err
}
_, err = w.Write(trans.Data)
if err != nil {
return err
}
err = w.Close()
if err != nil {
return err
}
return c.Quit()
}
// ============================================================================
func main() {
from := "ninedoors@126.com"
to := "ninedoors@qq.com"
msg := []byte{}
fmt.Println("============================================================")
trans := Transaction{
"smtp.126.com", 25, "peach", false,
from, "password",
from, []string{to},
msg,
}
trans.Send()
}
net/smtp 没有发现有好的日志实现,我只能定制了一个版本实现了日志smtp.Client -> textproto.Conn -> textproto.Writermake
m := make(map[string]int, 5)
发现 len(m) = 0 之后我觉得有必要重新复习一下 make 函数了。
make 的作用:分配内存,初始化,返回值(不是指针)。
PS:返回值的说明:返回的是这三种引用类型本身(指针),而不是指向这三个引用的指针。
PS:new 就会返回指针。还有,new 不限类型,不初始化。
slice -> len, capmap -> 预留大约 n 个元素的空间(具体分配空间的规则不清楚)chan -> buffer sizeThe built-in function make takes a type T, which must be a slice, map or channel type, optionally followed by a type-specific list of expressions. It returns a value of type T (not *T). The memory is initialized as described in the section on initial values.
Call Type T Result
make(T, n) slice slice of type T with length n and capacity n
make(T, n, m) slice slice of type T with length n and capacity m
make(T) map map of type T
make(T, n) map map of type T with initial space for approximately n elements
make(T) channel unbuffered channel of type T
make(T, n) channel buffered channel of type T, buffer size n
Each of the size arguments n and m must be of integer type or an untyped constant. A constant size argument must be non-negative and representable by a value of type int; if it is an untyped constant it is given type int. If both n and m are provided and are constant, then n must be no larger than m. If n is negative or larger than m at run time, a run-time panic occurs.
s := make([]int, 10, 100) // slice with len(s) == 10, cap(s) == 100
s := make([]int, 1e3) // slice with len(s) == cap(s) == 1000
s := make([]int, 1<<63) // illegal: len(s) is not representable by a value of type int
s := make([]int, 10, 0) // illegal: len(s) > cap(s)
c := make(chan int, 10) // channel with a buffer size of 10
m := make(map[string]int, 100) // map with initial space for approximately 100 elements
Calling make with a map type and size hint n will create a map with initial space to hold n map elements. The precise behavior is implementation-dependent.
flag)
Go 自带 flag 包,可以用于解析命令行参数。但用起来非常繁琐,更谈不上优雅,远不如 Python 的 argparse 包简洁明了,易于使用。
flag.Flagflag.FlagSetflag.Getterflag.Valuefunc Bool(name string, value bool, usage string) *bool
func BoolVar(p *bool, name string, value bool, usage string)
func Duration(name string, value time.Duration, usage string) *time.Duration
func DurationVar(p *time.Duration, name string, value time.Duration, usage string)
func Float64(name string, value float64, usage string) *float64
func Float64Var(p *float64, name string, value float64, usage string)
func Func(name, usage string, fn func(string) error)
func Int(name string, value int, usage string) *int
func IntVar(p *int, name string, value int, usage string)
func Int64(name string, value int64, usage string) *int64
func Int64Var(p *int64, name string, value int64, usage string)
func String(name string, value string, usage string) *string
func StringVar(p *string, name string, value string, usage string)
func Uint(name string, value uint, usage string) *uint
func UintVar(p *uint, name string, value uint, usage string)
func Uint64(name string, value uint64, usage string) *uint64
func Uint64Var(p *uint64, name string, value uint64, usage string)
func Var(value Value, name string, usage string)
func Arg(i int) string // i-th argument
func Args() []string // non-flag arguments
func NArg() int // number of arguments remaining after flags have been processed
func NFlag() int // number of command-line flags that have been set
func Parse()
func Parsed() bool
func Set(name, value string) error
func PrintDefaults() // 帮助信息
func UnquoteUsage(flag *Flag) (name string, usage string) // 返回 flag 的名称和用法
func Visit(fn func(*Flag))
func VisitAll(fn func(*Flag))
type ErrorHandling
func NewFlagSet(name string, errorHandling ErrorHandling) *FlagSet
上面的方法 FlagSet 也都有一份。
package main
import (
"flag"
"fmt"
)
var (
addFlag = flag.String("add", "", "Add a new task")
listFlag = flag.Bool("list", false, "List all tasks")
removeFlag = flag.Int("remove", -1, "Remove a task by its index")
removeAllFlag = flag.Bool("remove-all", false, "Remove all tasks")
doneFlag = flag.Int("done", -1, "Mark a task as done by its index")
undoneFlag = flag.Int("undone", -1, "Mark a task as undone by its index")
)
func main() {
flag.Parse()
args := make(map[string]interface{}, 10)
args["add"] = *addFlag
args["list"] = *listFlag
args["remove"] = *removeFlag
args["remove"] = *removeAllFlag
args["done"] = *doneFlag
args["undone"] = *undoneFlag
fmt.Printf("%#v\n", args)
// map[string]interface {}{"add":"", "done":-1, "list":false, "remove":false, "undone":-1}
fmt.Println("Other arguments:", flag.Args())
// Other arguments: []
}
按照 stars 排序,就取头部的一些项目分析一下: