Go语言笔记
原文链接 https://lingxiankong.github.io/2016-12-20-go-note.html
注:以下为加速网络访问所做的原文缓存,经过重新格式化,可能存在格式方面的问题,或偶有遗漏信息,请以原文为准。
教程
Go官方文档:https://golang.org/doc/
查看标准库列表:
几个不错的翻译教程:
- https://github.com/Unknwon/the-way-to-go_ZH_CN/blob/master/eBook/directory.md
- https://github.com/astaxie/build-web-application-with-golang/blob/master/zh/preface.md 包含基础教程和实现 web 应用
新手想学 golang,可以先到这里感受一下:https://play.golang.org/
在系统中使用不同版本的Go:GVM 类似于python的virtualenv: gvp
golang 编码格式约定:https://github.com/golang/go/wiki/CodeReviewComments
Go 命令
有一个go可执行程序,如何run?go build会在当前目录生成可执行程序(或者-o <exe路径>
),如果是go install,会在$GOPATH/bin/下创建可执行程序。
如果是普通包,go install 会在本机安装包,在别的代码中就可以import了。而go build不会产生任何文件。
goimports 比 gofmt 多一个自动排序 import 的功能
go clean,代码提交前清除当前源码包和关联源码包里面编译生成的文件。 -i 清除关联的安装的包和可运行文件,也就是通过go install安装的文件 -n 把需要执行的清除命令打印出来,但是不执行
测试某个类:
go test -v -test.run TestMain
/usr/local/go/bin/go test -timeout 30s github.com/gophercloud/gophercloud/openstack/loadbalancer/v2/l7policies/testing -run ^TestRequiredRuleCreateOpts$
安装和试用
这里有一个安装脚本,安装 go 1.9以及 glide
更新本文时,Go的最新版是1.10,我直接在Ubuntu虚拟机中,下载Go的二进制包,并设置我的环境变量。
apt-get purge -y golang-go # 不能自动跟后面的命令一起执行
mkdir -p /opt/go
export GOROOT=/opt/go # go 的安装目录
export GOPATH=$HOME/go # GOPATH 目录用于存放src/bin/pkg,从1.8版本开始默认是~/go
export PATH=$PATH:$GOROOT/bin:$GOPATH/bin/
sudo touch /etc/profile.d/go.sh
sudo tee /etc/profile.d/go.sh >/dev/null <<'EOF'
export GOROOT=/opt/go
export GOPATH=$HOME/go
export PATH=$PATH:$GOROOT/bin:$GOPATH/bin/
EOF
# 到 https://golang.org/dl/ 查看版本
version=1.10.4
source ~/.bashrc && cd /opt && wget https://storage.googleapis.com/golang/go${version}.linux-amd64.tar.gz
tar xzf go${version}.linux-amd64.tar.gz && rm -f go${version}.linux-amd64.tar.gz
# 我执行了这一步,不知道是不是必须的,有个文档说Go 的工具链是用 C 语言编写的,因此在安装 Go 之前你需要先安装相关的 C 工具
apt-get install -y bison ed gawk gcc libc6-dev make
cd -
安装脚本:
curl -SL https://gist.githubusercontent.com/lingxiankong/ad724e785abd133632cc604210b45659/raw/de29e5dbe1ad597891b40c3862a118d05be11da8/install_go.sh -o ~/install_go.sh && source ~/install_go.sh 1.10.4
或者借助 gimme 安装:
eval "$(curl -sL https://raw.githubusercontent.com/travis-ci/gimme/master/gimme | GIMME_GO_VERSION=1.10.4 bash)"
安装完毕,就这么简单。然后就可以编写Go代码了。一个最简单的hello world如下:
package main
func main() {
println("Hello", "world")
}
保存为hello.go文件,命令行运行go run hello.go,就会看到输出。一些教程还会举个稍微高级的例子,打印Go的版本号:
package main
import (
"fmt"
"runtime"
)
func main() {
fmt.Printf("%s\n", runtime.Version())
}
很多教程上来就说Go如何好,但我觉得,如果你真有阅读Go代码的需求(比如我是为了阅读Swarm源码),可略过那些章节,直接学习如何写Go语言即可。因为在很熟悉一门编程语言并有与之相关的项目经验前,那些东西除了吹嘘,没有任何实质意义。
再贴一个最简单的用标准库实现http web server:
package main
import (
"io"
"log"
"net/http"
"os"
)
func HelloServer(w http.ResponseWriter, req *http.Request) {
hostname, _ := os.Hostname()
log.Println(hostname)
io.WriteString(w, "Hello, world! I am "+hostname+" :)\n")
}
func main() {
http.HandleFunc("/", HelloServer)
log.Fatal(http.ListenAndServe(":12345", nil))
}
如下是我自己看教程的一些笔记,没有过目不忘的本事,只能靠反复看笔记了。
先谈谈我自己的感受,大概扫了一遍Go语言基础教程,发现Go其实融合了Java、C、Objective-C、Python等语言的一些特性,学Go的过程中,脑子里一直有好几个小人在打架,是并行的几个线程自我否定的过程,比较痛苦。但多掌握一门编程语言不是坏事,就算是锻炼自己脑子了。如果你先前没有其他编程语言经验,那么恭喜,对于Go语言你会很快上手的。另外,一门编程语言真正强大的是它的库函数,所以教程中有关库函数的讲解其实也可以忽略,因为你真正要用的时候,最好还是翻阅权威的官方文档(Go自带package文档参阅这里,国内用户可以访问这里)。
笔记
点比较散。有些东西当熟悉了Go语言之后再回过头来看,可能会比较简单。
Go 把 runtime 嵌入到了每一个可执行文件当中,所以go可执行文件的运行不需要依赖任何其它文件,它只需要一个单独的静态文件即可(所以导致可执行文件比较大)。
包
如果对一个包进行更改或重新编译,所有引用了这个包的客户端程序都必须全部重新编译。
main包中的main函数既没有参数,也没有返回类型(与C家族中的其它语言恰好相反)
每一个源文件都可以包含且只包含一个 init 函数,init 函数是不能被调用的。初始化总是以单线程执行,并且按照包的依赖关系顺序执行。一个可能的用途是在开始执行程序之前对数据进行检验或修复,以保证程序状态的正确性;也经常被用在当一个程序开始之前调用后台执行的 goroutine. 需要注意,如果某个init
函数内部用 go 关键字启动了新的 goroutine,这个 goroutine 只有在 main.main 函数之后才能被执行到。
可以用import _ <package path>
引入一个包,仅仅是为了调用init()函数,而无法通过包名来调用包中的其他函数。
而 import . <package path>
的意思是可以直接引用该包中的函数而不需要包名。
依赖管理
- v1.7中已经将vendor纳入标准特性中,并废弃GO15VENDOREXPERIMENT环境变量
- godep的“版本管理”本质上是通过缓存第三方库的某个revision的快照实现的(在
Godeps/_workspace
目录中) - vendor,vendor 标准化了项目依赖的第三方库的存放位置。从 go 1.5 开始出现官方的 vendor 支持。对vendor目录的解释,2015.12:https://blog.gopheracademy.com/advent-2015/vendor-folder/,vendor 使用时要注意交叉依赖的问题,即不同的包依赖同一个包的不同版本。但 vendor 仅仅是提供一个目录,缺乏管理机制(比如如何更新)
- glide, deprecated,比如 fn/fission 项目的依赖管理就是用 glide,关于 glide 的介绍:https://blog.bluerain.io/p/Golang-Glide.html
- dep,官方出品,目前处于开发和实验阶段,引入两个文件
Gopkg.toml
和Gopkg.lock
,Gopkg.toml 定义都依赖哪些包/branch/version,Gopkg.lock 定义中多了 revision 信息,即具体的 commit id- 安装
go get -u github.com/golang/dep/cmd/dep
- 介绍 2017.06:http://tonybai.com/2017/06/08/first-glimpse-of-dep/
- 2017.10,https://supereagle.github.io/2017/10/05/golang-dep/
dep ensure
,根据配置文件下载安装依赖包dep status
查看当前的依赖情况dep ensure -add github.com/golang/glog
dep ensure -update
,比如修改了Gopkg.toml,该命令更新 lock 文件和 vendor 下的数据使其满足约束。或者直接指定更新的库,dep ensure -update github.com/spf13/pflag
- 如果要测试自己的 repo代替 Gopkg.toml 中的依赖,在
constraint
中指定 source 为自己的 repo 以及branch,name不变
- 安装
变量
- 浮点数的类型有float32和float64两种(没有float类型),默认是float64。
var _ I = T{}
,这个语句其实在测试结构体T是否实现了接口I,下划线忽略变量名- reflect
- 在一个变量上调用 reflect.TypeOf() 可以获取变量的正确类型
reflect.ValueOf(s)
获取对象的值
- 声明数组:
var arr1 [5]int
,数组的初始化有很多种方式(比如自动计算长度:var arr1 [...]int{1,2,3}
),数组是值类型,数组的长度的固定的,可以通过传递数组的引用在函数中修改数据。用 for...range 的方式迭代数组会更安全,每轮迭代对数组元素访问时可以不用判断下标越界。 - 声明切片:
var slice1 []type
,不需要说明长度,初始化:var slice1 []type = arr1[start:end]
,切片是引用类型。切片类似于Python中的list,切片使用make() 创建。切片的cap就相当于数组的长度。切片在内存中的组织方式实际上是一个有 3 个域的结构体:指向相关数组的指针,切片长度以及切片容量。当相关数组还没有定义时,可以使用 make() 函数来创建一个切片同时创建好相关数组:var slice1 []type = make([]type, len)
- 声明 map:
var map1 map[string]int
,map 是引用类型,用 make() 创建。判断一个map中是否存在某个key:val1, isPresent = map1[key1]
。map 不是thread-safe,在多个go-routine存取时,必须使用mutex lock机制 - new 和 make 的区别:make 只能用于 slice,map,channel 三种内建引用类型。new适用于值类型如数组和结构体的内存分配。new(T) 分配零值填充的T类型的内存空间,返回的是 T 的指针,
var x interface{} = nil
- 对于使用
:=
定义的变量,如果新变量p与同名已定义变量 (这里就是那个全局变量p)不在一个作用域中时,那么golang会新定义这个变量p,遮盖住全局变量p
函数
函数支持可变参数,
func myFunc(a, b, args ...int) {}
,这里 args 其实是一个切片。如果是传递数组,可以这样myFunc(1, 2, arr...)
,这样其实是传递数组中每个元素。如果可变参数中值的类型都不一样,可以使用struct或空接口。函数参数默认值:不支持。Go语言追求显式的表达,避免隐含
实现类似于 python
if...in...
import sort
func findString(a string, list []string) bool {
sort.Strings(list)
index := sort.SearchStrings(list, a)
if index < len(list) && list[index] == a {
return true
}
return false
}
程序结构
Go支持的代码结构语句:
- if-else
定义函数时,最好额外返回一个执行成功与否的变量,如下写法会被经常用到:
if value, ok := readData(); ok {
}
- for/for-range
for就类似于C语言中的结构了。Go不支持while语句,无限循环可以用for { } 代替。
for-range结构是Go独有的,类似于有些语言的for-each,写法是for idx, val := range coll { }
,需要注意,val 始终为集合中对应索引的值拷贝,是只读的。
对 channel 的遍历:
for inValue := range in {
}
- switch
python 没有 switch。switch无需使用break退出分支,默认行为是匹配到一个分支运行结束后,就退出switch语句。如果希望继续执行,使用fallthrough。
- select
channel轮询
对数组的遍历两种方式:
// 第一种 for i:=0; i < len(arr1); i++ { arr1[i] = 1 } // 第二种 for index,value := range arr1 { ... }
recover 只能在 defer 修饰的函数中使用:用于取得 panic 调用中传递过来的错误值,如果是正常执行,调用 recover 会返回 nil,且没有其它效果。
Go支持标签和goto语法,但尽可能少用。
结构体
P := new(person)
返回的 P 是一个指针- 类型对应的方法必须和类型定义在同一个包中。因此无法直接给 int 等类型添加自定义方法。但可以通过自定义类型的方式,比如:
type ages int
- 如果在 struct 的定义中引用了另一个 struct 的类型,会继承这个 struct 的属性和方法
- 获取一个接口变量varI真正的值,
value, ok := varI.(T)
,简化形式:value := element.(T)
。也可以用switch t := varI.(type)
对接口变量的类型进行判断。 - 测试一个变量v是否实现了某个接口:
sv, ok := v.(varI)
结构体和 json
- 定义可转化为 json 的结构体时,json 字段必须是导出的,如果结构体变量名称和 json 字段不同,可使用成员标签定义
json.Marshal
把结构体转换为[]byte
数组
type Move struct {
Title string
Year int `json:"released"`
Color bool `json:"color,omitempty"`
Actors []string
NotParse bool `json:"-"` // 不解析
}
var movies = []Movie{...}
data, err := json.Marshal(movies) // 或者是 MarshalIndent(movies, "", " ")
同步
为了保证同一时刻只有一个线程会修改一个struct,通常的做法是在struct的定义中加入sync.Mutex类型的互斥锁变量。在函数中,可以通过lock和unlock函数加锁和解锁。在 sync 包中还有一个 RWMutex 读写锁:他能通过 RLock() 来允许同一时间多个线程对变量进行读操作,但是只能一个线程进行写操作。如果使用 Lock() 将和普通的 Mutex 作用相同。
sync.WaitGroup 的用法是程序需要并发执行多个goroutine,但需要等所有goroutine都结束才能继续。
package main
import (
"fmt"
"sync"
)
var waitgroup sync.WaitGroup
func Afunction(shownum int) {
defer waitgroup.Done()
fmt.Println(shownum)
}
func main() {
for i := 0; i < 10; i++ {
waitgroup.Add(1) //每创建一个goroutine,就把任务队列中任务的数量+1
go Afunction(i)
}
waitgroup.Wait() //.Wait()这里会发生阻塞,直到队列中所有的任务结束就会解除阻塞
}
测试
func TestSum(t *testing.T) // 定义测试函数
testing.T的Error, Errorf, FailNow, Fatal, FatalIf方法,说明测试不通过
channel
往 channel 发送数据也必须在协程中,因为 channel 的发送和接收都是阻塞操作且无缓冲。发送操作在接收者准备好之前是阻塞的
关闭之后不能写但可以读,最好在生产者/发送者 close(ch),接收者需要判断 channel 是否已关闭
在 select 中,如果多个可以处理,则随机选择一个
有时候一个函数仅发送数据或接收数据,定义时可以:
func source(ch chan<- int){
for { ch <- 1 }
}
- 如果不管通道中存放的是什么,只关注长度,可以创建如下通道
type Empty interface {}
type semaphore chan Empty
sem := make(semaphore, N)
如何优雅的关闭channel:http://www.tapirgames.com/blog/golang-channel-closing
示例代码
// 基本定义,无缓冲 channel,不限容量
ch := make(chan int)
// 有缓冲 channel
ch := make(chan string, 100)
// 等待接收数据。如果 channel 关闭了 ok == false
if v, ok := <- ch; ok {
process(v)
}
// 或者使用for 语句,直到通道被关闭
for input := range ch {
process(input)
}
// 防止 channel 超时
package main
import (
"fmt"
"time"
)
func main() {
c := make(chan int)
o := make(chan bool)
go func() {
for {
select {
case i := <-c:
fmt.Println(i)
case <-time.After(time.Duration(3) * time.Second):
fmt.Println("timeout")
o <- true
break
}
}
}()
<-o
}
- 测试通道关闭,设置超时
package main
import (
"fmt"
"time"
)
func prepareData(ch chan string) {
for _, lb := range []string{"lingxian", "kong", "catalyst"} {
ch <- lb
time.Sleep(3 * time.Second)
}
close(ch)
fmt.Println("channel closed!")
}
func main() {
ch := make(chan string)
go prepareData(ch)
for i := 0; i < 2; i++ {
go func(ch <-chan string, i int) {
for {
select {
case s, ok := <-ch:
if !ok {
fmt.Printf("goroutine %d exits\n", i)
return
}
fmt.Printf("goroutine %d: %s\n", i, s)
time.Sleep(1 * time.Second)
case <-time.After(time.Duration(3) * time.Second):
fmt.Printf("goroutine %d timeout\n", i)
break
}
}
}(ch, i)
}
time.Sleep(20 * time.Second)
}
signal
- 使用场景:如果希望捕获操作系统信号并做相应的处理(比如优雅停止等)
- 你只需定义关心哪些信号,如果不设置表示监听所有的信号
sigs := make(chan os.Signal, 0)
done := make(chan bool, 1)
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM, syscall.SIGUSR1, syscall.SIGUSR2)
go func(doneCh chan<- bool) {
sig := <-sigs
fmt.Println("Got signal:", sig)
done <- true
}(done)
context
在父子线程间传递,父线程终止时传递信息给子线程也终止
与 signal 配合的一个例子:
package main
import (
"context"
"fmt"
"os"
"os/signal"
"time"
)
func main() {
ctx, cancel := context.WithCancel(context.Background())
exitCh := make(chan struct{})
go func(ctx context.Context) {
for {
fmt.Println("In loop. Press ^C to stop.")
// Do something useful in a real usecase.
// Here we just sleep for this example.
time.Sleep(time.Second)
select {
case <-ctx.Done():
fmt.Println("received done, exiting in 500 milliseconds")
time.Sleep(500 * time.Millisecond)
exitCh <- struct{}{}
return
default:
}
}
}(ctx)
signalCh := make(chan os.Signal, 1)
signal.Notify(signalCh, os.Interrupt)
go func() {
select {
case <-signalCh:
cancel()
return
}
}()
<-exitCh
}
博客
- 2016.08,http://lanlingzi.cn/post/technical/2016/0802_go_context/
- http://www.nljb.net/default/Golang之Context的使用/
- 避免在context中存储自定义数据:https://www.calhoun.io/pitfalls-of-context-values-and-how-to-avoid-or-mitigate-them/
// 在父线程中 ctx, cancel := context.WithCancel(context.Background()) defer cancel() // 调用 cancel 后,在子线程中 select { case <- ctx.Done(): return -1 default: // 没有结束 ... 执行 ... }
时间相关
计时器(循环触发)
ticker := time.NewTicker(updateInterval) defer ticker.Stop() ... select { case u:= <-ch1: ... case v:= <-ch2: ... case <-ticker.C: logState(status) // 计时结束 default: // no value ready to be received ... }
定时器(触发一次),上面例子的
time.After
字符串 string
使用fmt.Printf,换行需要加
\n
,%v
代表使用类型的默认输出格式,%#v
提供最典型的调试信息,%t
表示布尔型,%g
表示浮点型,%p
表示指针。int 不能使用 %s 打印,必须是%dpython 里面的原生字符串(比如
r('xxx')
)在 go 中用反引号表示。什么时候用
string
什么时候用*string
,参见这里。简单说,使用 string,在unmarshal时如果字段 missing 则赋值为空字符串,而如果使用*string
,缺失的字段值为 nil。如果一个字段被 update 的时候可以为空字符串,那么最好设计成*string
,否则不指定时就会被自动 update 成空字符串。同样,bool 类型如果不指定默认为 false字符串拼接除了 Sprintf(返回字符串),还可以使用
strings.Join([]string{s,"ewrwer"},"")
Println
s := fmt.Sprintf("vip port: %s", lb.VipPortID)
fmt.Println(s)
错误处理
- 博客
- 如何优雅的处理 error,打印调用堆栈
一些技巧
go build
- 可以在编译时设置变量:
CGO_ENABLED=0 GOOS=linux go build -ldflags "-w -s -X 'github.com/lingxiankong/openstackcli-go/cmd.version=1.0.0" -o mycli main.go
。参见这里
- 可以在编译时设置变量:
自己写 project 的一些心得:
构造结构体,最后要有逗号 在函数里面的变量是函数内作用域 短变量声明最少声明一个新变量,否则要使用等号赋值
select
- select默认是阻塞的,只有当监听的channel中有发送或接收可以进行时才会运行,当多个channel都准备好的时候,select是随机的选择一个执行的
- http://yanyiwu.com/work/2014/11/08/golang-select-typical-usage.html
装饰器的效果
func errorHandler(fn fType1) fType1 { return func(a type1, b type2) { defer func() { if e, ok := recover().(error); ok { log.Printf(“run time panic: %v”, err) } }() fn(a, b) } }
2017 Go语言进展:https://talks.golang.org/2017/state-of-go.slide
shuffle array: https://www.calhoun.io/how-to-shuffle-arrays-and-slices-in-go/
debug
- delve,支持goroutine
- https://github.com/derekparker/delve/tree/master/Documentation/cli
- 介绍:https://yq.aliyun.com/articles/57578
- 示例:
dlv debug main.go break main.main break funcname continue next(后续可以直接按回车) print var # 对于已经运行的程序 dlv attach PID
第三方库
- CLI: https://github.com/urfave/cli fn 在用,fission CLI 也用 从help message构造CLI解析, fission server 使用: https://github.com/docopt/docopt.go cobra,见下
- https://github.com/spf13/pflag 增强的flag库
- 配置项管理:
- Go Config 支持配置项动态更新,同时支持命令行参数,环境变量,配置文件,https://github.com/micro/go-config,一篇介绍
- https://github.com/spf13/viper 这里有个配置文件对应 struct 定义的例子,unmarshal 时的 annotation 不是 json,而是
mapstructure:
- 如果只是 ini 文件:https://github.com/go-ini/ini
- 解析配置文件: https://blog.gopheracademy.com/advent-2014/reading-config-files-the-go-way/
- https://github.com/boltdb/bolt 简单键值对数据库
- docker: https://godoc.org/github.com/fsouza/go-dockerclient
- cron job runner: https://godoc.org/github.com/robfig/cron
- openstack go client http://gophercloud.io,由 rackspace 维护,最近 commit 在2016年
- webservice,k8s 使用:https://godoc.org/github.com/emicklei/go-restful
- debug https://github.com/derekparker/delve GDB https://github.com/astaxie/build-web-application-with-golang/blob/master/zh/11.2.md
- URL 操作 https://github.com/PuerkitoBio/purell
日志
https://github.com/sirupsen/logrus 在日志中方便的显示变量
Debug, Info, Warn, Error, Fatal, Panic
不管 Log rotation
示例
import (
log "github.com/sirupsen/logrus"
)
log.SetOutput(os.Stdout)
log.SetLevel(log.InfoLevel)
log.SetFormatter(&log.TextFormatter{})
// 一般打印
log.WithFields(log.Fields{"error": err}).Fatal("Unable to decode the configuration")
log.WithFields(log.Fields{
"event": event,
"topic": topic,
"key": key,
}).Info("Failed to send event")
// 或者把变量固定
requestLogger := log.WithFields(log.Fields{"request_id": request_id, "user_ip": user_ip})
requestLogger.Info("")
- glog,k8s 使用
import glog "github.com/golang/glog"
glog.V(2).Infoln("Processed", nItems, "elements")
glog.Fatalf("Initialization failed: %s", err)
HTTP 服务 Gorilla Mux
https://github.com/gorilla/mux 定义URL到handler的映射,兼容http.ServeMux
这个库的牛逼之处在于有很多高级的match模式,matcher 可以写在一起
r := mux.NewRouter()
r.HandleFunc("/products", ProductsHandler).
Host("www.example.com").
Methods("GET").
Schemes("http")
- 如果很多routes 有一些重复的 matcher,可以使用 subrouter
s := r.Host("www.example.com").Subrouter()
s.HandleFunc("/products/", ProductsHandler)
- 有了 router,可以结合http.Server
srv := &http.Server{
Handler: r,
Addr: "127.0.0.1:8000",
// Good practice: enforce timeouts for servers you create!
WriteTimeout: 15 * time.Second,
ReadTimeout: 15 * time.Second,
}
log.Fatal(srv.ListenAndServe())
- middleware 的定义
type MiddlewareFunc func(http.Handler) http.Handler
// 比如
func simpleMw(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Do stuff here
log.Println(r.RequestURI)
// Call the next handler, which can be another middleware in the chain, or the final handler.
next.ServeHTTP(w, r)
})
}
r.Use(simpleMw)
层级命令行 cobra
https://github.com/spf13/cobra 编写 CLI 程序,可以自动生成 CLI 框架,推荐!
- 基本用法
// 定义 rootcmd
var rootCmd
var getProjectsCmd = &cobra.Command{}
rootCmd.AddCommand(getProjectsCmd)
// 也可以同时 add 多个
rootCmd.AddCommand(cmdPrint, cmdEcho)
参数约束
Args: cobra.ExactArgs(1), // 定义 positional 参数个数 cmd.PersistentFlags().StringVar(&socketpath, "socketpath", "", "Barbican KMS Plugin unix socket endpoint") cmd.MarkPersistentFlagRequired("socketpath")
脚手架
# in .bashrc
export GOPATH=$HOME/go_project
export PATH=$PATH:$GOPATH/bin
# Avoid the error 'Unknown SSL protocol error in connection to gopkg.in'
Lingxians-MAC[~]$ git config --global http.sslVerify true
Lingxians-MAC[~]$ go get -u -v github.com/spf13/cobra/cobra
Lingxians-MAC[~]$ cobra init $GOPATH/src/tests/runit
Lingxians-MAC[~]$ cd $GOPATH/src/tests/runit
Lingxians-MAC[~/go_project/src/tests/runit]$ ll
total 32
-rw-r--r-- 1 konglingxian staff 11358 Jan 6 13:01 LICENSE
drwxr-xr-x 3 konglingxian staff 102 Jan 6 13:01 cmd
-rw-r--r-- 1 konglingxian staff 677 Jan 6 13:01 main.go
Lingxians-MAC[~/go_project/src/tests/runit]$ cobra add os
os created at /Users/konglingxian/go_project/src/tests/runit/cmd/os.go
# 注意这里不能是‘cobra add get-projects’,cobra 暂时不支持
Lingxians-MAC[~/go_project/src/tests/runit]$ cobra add getProjects -p 'osCmd'
getProjects created at /Users/konglingxian/go_project/src/tests/runit/cmd/getProjects.go
Lingxians-MAC[~/go_project/src/tests/runit]$ tree
.
|____cmd
| |____getProjects.go
| |____os.go
| |____root.go
|____LICENSE
|____main.go
# 然后你自己可以编写逻辑了……
gin
实现 HTTP server 和 API 路径绑定(router),可以与 http.Server 配合实现 http 服务
https://github.com/gin-gonic/gin
fn 和 fission 项目都使用
router := gin.Default()
// 路径参数
router.GET("/user/:name", getUser)
// 可选路径参数
router.GET("/user/:name/*action", userAction)
gophercloud
http://gophercloud.io/docs/compute/#setup
- 输入是鉴权信息,获取一个 provider
- 根据 provider 获取各个 service client
- 导入各个资源的包,调用包的导出函数(service client 为参数)
- 这种结构很怪,感觉是函数式编程
代码示例
http 服务器
- handler,任何实现 ServeHTTP 方法的对象都可以作为 Handler,但http.HandleFunc简化了 handler 的编写
- 预定义了一些通用的 handler,比如FileServer,404的NotFoundHandler,重定向的RedirectHandler
- 其实就算代码没用 ServerMux,net/http 在后台也会默认创建使用 DefaultServeMux
- 使用ServerMux时要注意:/images 会匹配 /images/cute-cat.jpg
- ServerMux 不支持带参数的路由,不能捕获 URL 中的变量,不支持 HTTP 方法匹配。所以才有Gorilla Mux或httprouter库
// 最原始的写法
package main
import (
"io"
"net/http"
)
type helloHandler struct{}
func (h *helloHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello, world!"))
}
func main() {
http.Handle("/", &helloHandler{})
http.ListenAndServe(":12345", nil)
}
// 上述写法要为每个 handler 都定义一个结构体和实现ServeHTTP方法,很不方便,于是net/http提供了HandleFunc方法,允许直接把特定类型的函数作为 handler。其实 http.HandleFunc 第二个参数的定义如下,所以任何满足该参数定义方式的函数都能作为 handler:
type HandlerFunc func(ResponseWriter, *Request)
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r)
}
package main
import (
"fmt"
"log"
"net/http"
)
func handler(w http.ResponseWriter, req *http.Request) {
// io.WriteString(w, "hello, world!\n")
// w.Write([]byte("Hello, world!"))
fmt.Fprintf(w, "URL.Path = %q\n", req.URL.Path)
}
func main() {
http.HandleFunc("/", handler)
log.Fatal(http.ListenAndServe("localhost:8000", nil))
}
// 或者使用 ServerMux,允许定义多个 URL 到 handler 的映射。http.ListenAndServe函数第二个参数就是 mux,因为它是个特殊的 handler,也实现了ServeHTTP方法
package main
import (
"net/http"
"io"
)
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/bye", func(w http.ResponseWriter, r *http.Request) {
io.WriteString(w, "byebye")
})
mux.HandleFunc("/hello", sayhello)
http.ListenAndServe(":8080", mux)
}
func sayhello(w http.ResponseWriter, r *http.Request) {
io.WriteString(w, "hello world")
}
类似于 python 中的 if…in...
import "sort"
files := []string{"Test.conf", "util.go", "Makefile", "misc.go", "main.go"}
target := "Makefile"
sort.Strings(files)
i := sort.SearchStrings(files, target) // 行为比较怪异,如果 target 不在列表中,则返回 target 应该 insert 的 index
if i < len(files) && files[i] == target {
fmt.Printf("found \"%s\" at files[%d]\n", files[i], i)
}
// 写成函数
func FindString(a string, list []string) bool {
if a == "" || len(list) == 0 {
return false
}
sort.Strings(list)
index := sort.SearchStrings(list, a)
if index < len(list) && list[index] == a {
return true
}
return false
}
定时器
// Tick 是循环发送,After 只发送一次
tick := time.Tick(1e8)
boom := time.After(5e8)
for {
select {
case <-tick:
fmt.Println("tick.")
case <-boom:
fmt.Println("BOOM!")
return
default:
fmt.Println(" .")
time.Sleep(5e7)
}
}