作者:Jack47
转载请保留作者和原文出处
欢迎关注我的微信公众账号程序员杰克,两边的文章会同步,也可以添加我的RSS订阅源。
本文通过从无到有创建一个利用Go语言实现的非常简单的HttpServer,来让大家熟悉利用Go语言时的基本流程,工具和代码的基本布局,为学习Go语言时碰到的环境问题扫清障碍。
以一个简单的场景来举例,有一个可复用的库(hugger)和一个使用这个库的应用程序(hugmachine),代码都托管在Github上。
代码布局
go工具
go
命令是一个管理Go源代码的工具,可以用来做很多事情:
- build 编译包和依赖
- clean 删除对象文件
- fmt 对代码风格格式化
- get 下载并安装包和依赖(递归地进行)
- install 编译并安装包和依赖
- list 列出所有的包
- run 编译和运行Go程序
- test 运行以包为单位的测试
- vet 做Go源代码的检查,发现编译器没有发现的错误
go
命令大部分(除了run)都是以包为单位的,而不是单个的文件。
概述
go
这个工具要求程序猿按照特定的方式把代码组织在一起,主要体现在:
- Go程序猿通常把所有的Go代码(多个项目)都放到一个工作区(workspace)下,这跟其他的编程环境不一样,其他的编程环境里,通常是每个项目有各自的工作区
- 一个工作区下可以包含不同版本控制(Git, VCS)下的代码仓库
Go语言中,一个工作区的根目录下包含三个基本的目录:
-
src
里面是Go的源代码文件 -
pkg
里面是包(package)对象文件 -
bin
里面是可执行的命令
其中后两个目录是go
工具自动产生的:它把源代码构建过程中生成的对象文件放到pkg
目录下,然后把对应生成的二进制文件放到bin
目录下。
GOPATH
环境变量
GOPATH
环境变量是必须要设置的,$GOPATH
环境变量必须指向Go工作区(workspace)的根。开发Go代码,大部分情况下都只需设置这一个环境变量。Go的构建过程会按照go help gopath
里描述的约定来走,例如Go会去GOPATH
列出了的所有目录里寻找Go代码(所有的包都在$GOPATH/src
目录下),会把新的包下载到GOPATH
列出的第一个目录里。当然并不是所有的项目都会按照这些约定来,它们可以不使用go
工具集。
接下来我们以一个简单的场景来举例,从零开始实现一个可复用的库(hugger)和一个使用这个库的应用程序(hugmachine),它们的代码都托管在Github上,各自在自己的repository里。你在Github上的这两个repository都会被clone
到$GOPATH/src
下的各个文件夹下。即src/github.com/Jack47/
下的每个目录都是一个独立git clone
出来的repository。
你的代码布局可能看起来是这样的:
$GOPATH
src/
github.com/
Jack47/
hugger/
.git/
hugger.go
hugger_test.go
README.md
hugmachine/
.git/
hugmachine.go
hugmachine_test.go
README.md
创建工作区(workspace)
先在Github上创建这两个新的repositories。然后在本地设置GOPATH
,建好上级目录,克隆出两个repository出来:
cd ~/
mkdir go-workspace
export GOPATH=~/go-workspace
cd $GOPATH
mkdir -p src/github.com/Jack47
cd src/github.com/Jack47
git clone git@github.com:Jack47/hugmachine.git
git clone git@github.com:Jack47/hugger.git
自己的程序库(Libraries)
按惯例,代码仓库的名称和它包含的Go包(package)的名称应该是保持一致的。我们的hugger
代码仓库包含定义了hugger
包的hugger.go
文件:
package hugger
func Hugger() string {
return "You are warmly hugged!\n"
}
应用程序
一个应用程序--将被编译成可执行命令的Go代码--总是需要定义为package main
并有一个main()
函数。
因此hugmachine.go
可能看起来是这样的:
package main
import (
"os"
"io"
"net/http"
"strconv"
"github.com/Jack47/hugger"
logging "github.com/op/go-logging"
)
func heartbreakerHandler(w http.ResponseWriter, req *http.Request) {
logger.Infof("Meet heartbreaker from %s", req.Host)
io.WriteString(w, hugger.Hug())
}
var LISTENING_PORT = 1024
var logger = logging.MustGetLogger("hugmachine.log")
func main() {
logging.NewLogBackend(os.Stderr, "", 0)
http.HandleFunc("/heartbreaker", heartbreakerHandler)
logger.Infof("Listening on port %d", LISTENING_PORT)
err := http.ListenAndServe("0.0.0.0:"+strconv.Itoa(LISTENING_PORT), nil)
if err != nil {
logger.Fatal("ListenAndServe: " + err.Error())
}
}
import path
在Go中,约定是把代码的位置包含在import path
中,例如 hugger
包:
$GOPATH/src/github.com/Jack47/hugger
在hugmachine.go
中引用hugger
时,是这样写的:
import(
"github.com/Jack47/hugger"
)
让你的包可以通过go get下载
go get
有一个特点是它可以识别出知名的代码托管网站的路径,例如Github,Bitbucket。这样它就可以把包的import path
转换成正确的命令来check out
出代码。这样你甚至可以通过go get import-path/package-name
来自动下载并安装package-name
及其依赖包。例如:
$ go get github.com/golang/example/hello
$ $GOPATH/bin/hello
Hello, Go examples!
如果go get
时指定的包已经在工作区下,那么go get
会跳过checkout
远程代码这一步,执行类似go install
的操作。此时如果需要更新这个包,使用go get -u
。但是所有通过go get
checkout出来的Github repo都默认使用只读的 https://
。为了能把修改push
回github,需要修改.git/config
里的origin/master
引用,指向Github上的SSH的repo。
依赖
你的项目可能会直接依赖很多已有的包。上面的hugmachine
程序直接依赖于github.com/op/go-logging
和github.com/Jack47/hugger
。通过go list ...
命令可以看到这个项目所有的依赖包。可以通过在你的工作区(workspace)的根路径上运行 go get -v ./...
(go help packages
来看完整的对...
语法的解释)来安装所有的依赖(直接和间接依赖)。go get
命令跟go install
命令类似,它会尝试build
然后递归安装所有的依赖包到工作区下$GOPATH/src
目录下。
构建
在开发过程中可以通过命令go build ...hugger
来构建hugger
库。也可以指定到包名的全路径,go build github.com/Jack47/hugger
。
使用命令go build ...hugmachine
来编译 hugmachine.go
和相关依赖为可执行文件。此时在 $GOPATH
目录下会出现hugmachine
可执行文件。也可以使用go install ...hugmachine
,他会编译并安装指定的包到$GOPATH/bin
目录下。
此时执行hugmachine
命令,会看到程序运行起来了:
$ ./hugmachine
2016/10/28 09:31:43 Listening on port 1024
通过在浏览器里访问http://localhost:1024/heartbreaker ,可以看到hugmachine
的返回结果。
如果你的工程项目比较复杂,可以参考下这些开源软件的代码布局:
filebeat
参考资料
Github Code Layout
go lang wiki
如果您看了本篇博客,觉得对您有所收获,请点击右下角的“推荐”,让更多人看到!