在此workflow中,GitHub与Travis CI之间进行交互,持续集成;Docker Hub链接GitHub项目进行镜像管理。
以自己的一个项目为示例go-web-form
服务器相关配置
server.go
// 创建服务函数,返回negroni.Negroni指针
func NewServer() *negroni.Negroni {
// 返回一个Render实例的指针,Render是一个包,提供轻松呈现JSON,XML,文本,二进制数据和HTML模板的功能
// Directory : Specify what path to load the templates from.
// Layout : Specify a layout template. Layouts can call {{ yield }} to render the current template or {{ partial "css" }} to render a partial from the current template.
// Extensions: Specify extensions to load for templates.
formatter := render.New(render.Options{
Directory: "views",
Extensions: []string{".gtpl"},
Layout: "layout",
})
// 设置router
mx := mux.NewRouter()
initRoutes(mx, formatter)
// negroni.Classic() 返回带有默认中间件的Negroni实例指针:
n := negroni.Classic()
// 让 negroni 使用该 Router
n.UseHandler(mx)
return n
}
initRouters() 函数如下:
func initRoutes(mx *mux.Router, formatter *render.Render) {
// 调用os.Getwd()获取目录, 用于后面静态资源定位
path, _ := os.Getwd()
// 注册路由,处理Methods:GET和POST
mx.HandleFunc("/login", LoginHandler(formatter)).Methods("GET", "POST")
mx.HandleFunc("/upload", UploadHandler(formatter)).Methods("GET", "POST")
mx.NotFoundHandler = NotFoundHandler(formatter)
// 表示路由前缀为"/views"的请求都由该Handler处理
// mx.PathPrefix("")匹配前缀,返回*mux.Route, 链式调用Handler(http.Handler)
// http.StripPrefix("", http.Handler)去除前缀, 并将请求定向到http.Handler
// http.FileServer(http.FileSystem) 返回http.Handler
// http.Dir("")参数应该为绝对路径
mx.PathPrefix("/views").Handler(http.StripPrefix("/views", http.FileServer(http.Dir(path+"/views"))))
mx.PathPrefix("/static/images").Handler(http.StripPrefix("/static/images", http.FileServer(http.Dir(path+"/static/images"))))
}
router.go
LoginHandler
// 定义路由处理函数
func LoginHandler(formatter *render.Render) http.HandlerFunc {
// 返回http.HandlerFunc,处理GET和POST请求
return func(w http.ResponseWriter, req *http.Request) {
fmt.Println("method:", req.Method)
// formatter为一个渲染模板的render实例
// formatter.HTML(http.ResponseWriter, http.StatusCode, HTML模板, 模板绑定的值)
if req.Method == "GET" {
formatter.HTML(w, http.StatusOK, "layout", true)
} else {
// req.ParseForm()获取表单提交的值
req.ParseForm()
// 自定义模板,可以使用ParseFiles利用模板文件获取template.Template对象
// {{define "username"}} …… {{end}} 给模板命名
t, _ := template.New("login").Parse(`{{define "username"}}Hello, {{.}}!{{end}}`)
// t.ExecuteTemplate(http.ResponseWriter, 模板名称, 模板对象的值)
log.Println(t.ExecuteTemplate(w, "username", req.Form.Get("username")))
}
}
}
UploadHandler
func UploadHandler(formatter *render.Render) http.HandlerFunc {
return func(w http.ResponseWriter, req *http.Request) {
fmt.Println("method: ", req.Method)
if req.Method == "GET" {
// Get方法渲染upload模板
formatter.HTML(w, http.StatusOK, "layout", false)
} else {
// 上传文件是需要调用req.ParseMultipartForm, 参数为最大占用存储空间,将request body转化为multipart/form-data,
req.ParseMultipartForm(32 << 20)
// 获取文件, req.FormFile("")参数为input表单的name属性
file, handler, err := req.FormFile("uploadfile")
defer file.Close()
if err != nil {
fmt.Println(err)
return
}
fmt.Fprintf(w, "%v", handler.Header)
// 将上传文件拷贝到本地
f, err := os.OpenFile("./file/"+handler.Filename, os.O_WRONLY|os.O_CREATE, 0666)
defer f.Close()
if err != nil {
fmt.Println(err)
return
}
io.Copy(f, file)
}
}
}
NotFoundHandler
func NotFoundHandler(formatter *render.Render) http.HandlerFunc {
// 此函数处理NotFound
return func(w http.ResponseWriter, req *http.Request) {
// 调用http.Error(http.ResponseWriter, error string, code int)
http.Error(w, "501 Not Implemented", http.StatusNotImplemented)
}
}
界面相关配置
测试使用,比较简陋。
layout.gtpl
login.gtpl和upload.gtpl共用该文件,其中当该模板传入的参数为true时,{{template “login”}}会将login.gtpl include到此处;当该模板传入的参数为false时,{{template “upload”}}会将upload.gtpl include到此处;
{{ define "layout" }}
<html>
<head>
<title>go-web-form</title>
<link rel="icon" href="/static/images/favicon.ico" />
</head>
<body>
{{ if . }}
{{ template "login" }}
{{ else }}
{{ template "upload" }}
{{ end }}
</body>
</html>
{{ end }}
login.gtpl
{{define "login"}}
<form action="/login" method="post">
用户名:<input type="text" name="username" />
密码:<input type="password" name="password" />
<input type="submit" value="登陆" />
</form>
{{end}}
upload.gtpl
{{ define "upload" }}
<form enctype="multipart/form-data" action="/upload" method="post">
<input type="file" name="uploadfile" />
<input type="submit" value="upload" />
</form>
{{ end }}
Dockerfile相关配置
有关Docker Hub以及Docker相关介绍详见传送门
Dockerfile
配置如下:
FROM golang:latest
MAINTAINER liuyh73 "15989067460@163.com"
# 该指令用于配置工作目录,其参数应该使用绝对目录。
WORKDIR $GOPATH/src/github.com/liuyh73/go-web-form
# ADD不但支持将本地文件复制到容器中,还支持本地提取文件和远程url下载
# ADD <src> <dst>
ADD . $GOPATH/src/github.com/liuyh73/go-web-form
RUN go get github.com/gorilla/mux
RUN go get github.com/codegangsta/negroni
RUN go get github.com/unrolled/render
RUN go get github.com/spf13/pflag
RUN go build .
# 该指令指示容器讲监听链接的端口,类似于,将容器中的某一个端口暴露出去,从而在外部访问绑定该端口。
EXPOSE 8080
# ENTRYPOINT允许你配置作为可执行文件运行的容器
ENTRYPOINT ["./go-web-form"]
在项目根目录下配置Dockerfile文件如上所述,然后执行sudo docker build . --rm -t go-web-form
查看是否报错,如果没有报错则证明配置文件正确。(我在自己电脑上使用该命令生成镜像很慢……)
Travis CI相关配置
有关持续集成的相关介绍详见传送门
.travis.yml
配置如下:
language: go
go: latest
sudo: required
services: docker
before_install:
- go get -u github.com/codegangsta/negroni
- go get -u github.com/gorilla/mux
- go get -u github.com/unrolled/render
- go get -u github.com/spf13/pflag
install:
# docker build: --rm remove the middle container, -t names the image as name:tag or name.
# go-web-form is the image name
- docker build --rm -t liuyh73/go-web-form .
# docker run: create a container called go-web-form to run the image (go-web-form).
# the former go-web-form is the container name, the latter go-web-form is the image name
- docker run -d -p 127.0.0.1:8080:8080 --name go-web-form liuyh73/go-web-form
script:
- docker ps | grep -q go-web-form
构建镜像
在项目跟路径下执行:docker build -t liuyh73/go-web-form .
启动容器
测试使用
- login
- upload
以交互模式运行容器:docker exec -it go-web-form /bin/bash
可以看到新增了图片Screenshot from 2018-11-17 18-17-47.png
,其他文件为构建镜像时已经拥有的图片。
将镜像push到Docker Hub
之后此镜像就可以被其他用户所使用。如果项目完整,则可以在我们的云服务器上使用此镜像启动容器来供自己或者他人使用。