我在前一篇文章《公共网关接口(Common Gateway Interface, CGI)简介》中简要地介绍了什么是CGI接口以及它的用途。 今天的文章我将用实际的例子来讲解一个与CGI相关的小项目:利用本地安装的GIT来搭建一个极简的GIT服务器。
本地安装的GIT
本地安装的GIT即是指我们平常要使用 GIT 时必须下载并安装的 GIT 程序。通过运行git
即可知道你有没有安装。
其实很多人不知道,GIT 其实已经自带了一个支持 HTTP 协议的 GIT 服务端,它的名字叫git-http-backend
。
它位于目录$(git --exec-path)
目录下:
1 2 |
|
点击这里查看目录文件列表
$ ls $(git --exec-path)
git git-gc git-rebase--preserve-merges
git-add git-get-tar-commit-id git-receive-pack
git-add--interactive git-grep git-reflog
git-am git-gui git-remote
git-annotate git-gui--askpass git-remote-ext
git-apply git-hash-object git-remote-fd
git-archimport git-help git-remote-ftp
git-archive git-http-backend git-remote-ftps
git-bisect git-http-fetch git-remote-http
git-bisect--helper git-http-push git-remote-https
git-blame git-index-pack git-remote-testsvn
git-branch git-init git-repack
git-bundle git-init-db git-replace
git-cat-file git-instaweb git-request-pull
git-check-attr git-interpret-trailers git-rerere
git-check-ignore git-legacy-rebase git-reset
git-check-mailmap git-log git-rev-list
git-check-ref-format git-ls-files git-rev-parse
git-checkout git-ls-remote git-revert
git-checkout-index git-ls-tree git-rm
git-cherry git-mailinfo git-send-email
git-cherry-pick git-mailsplit git-send-pack
git-citool git-merge git-serve
git-clean git-merge-base git-sh-i18n
git-clone git-merge-file git-sh-i18n--envsubst
git-column git-merge-index git-sh-setup
git-commit git-merge-octopus git-shell
git-commit-graph git-merge-one-file git-shortlog
git-commit-tree git-merge-ours git-show
git-config git-merge-recursive git-show-branch
git-count-objects git-merge-resolve git-show-index
git-credential git-merge-subtree git-show-ref
git-credential-cache git-merge-tree git-stage
git-credential-cache--daemon git-mergetool git-stash
git-credential-netrc git-mergetool--lib git-status
git-credential-osxkeychain git-mktag git-stripspace
git-credential-store git-mktree git-submodule
git-cvsexportcommit git-multi-pack-index git-submodule--helper
git-cvsimport git-mv git-subtree
git-cvsserver git-name-rev git-svn
git-daemon git-notes git-symbolic-ref
git-describe git-p4 git-tag
git-diff git-pack-objects git-unpack-file
git-diff-files git-pack-redundant git-unpack-objects
git-diff-index git-pack-refs git-update-index
git-diff-tree git-parse-remote git-update-ref
git-difftool git-patch-id git-update-server-info
git-difftool--helper git-prune git-upload-archive
git-fast-export git-prune-packed git-upload-pack
git-fast-import git-pull git-var
git-fetch git-push git-verify-commit
git-fetch-pack git-quiltimport git-verify-pack
git-filter-branch git-range-diff git-verify-tag
git-fmt-merge-msg git-read-tree git-web--browse
git-for-each-ref git-rebase git-whatchanged
git-format-patch git-rebase--am git-worktree
git-fsck git-rebase--common git-write-tree
git-fsck-objects git-rebase--interactive mergetools
通过执行命令git http-backend
可以查看是否有这个子命令程序。
1 2 3 4 5 6 |
|
从整体输出可以很明显地看出来,它是一个 HTTP 响应输出。而从Status: 500 Internal Server Error
这一状态格式来看,很明显是一个 CGI 程序。
注意到上面的文件列表中还有另外两个程序,分别是git-receive-pack
、git-upload-pack
。它们分别被用来处理git push
和git fetch/pull
。
Go语言标准中的CGI包
Go语言的标准库中直接提供了对CGI程序的支持,这个包是:net/http/cgi。
它的实现非常的简单,总共就两个文件:host.go
和child.go
。host.go
总共一个导出函数,实现从HTTP协议转换成CGI协议作为环境变量传递并调用CGI程序,child.go
总共3个导出函数,允许在CGI程序中读取CGI协议并还原成HTTP协议的方式进行请求处理。
为什么要这么做?因为:服务器在调用CGI程序时,不是直接以HTTP代理的方式转发请求的,而是以环境变量。本来就启动了一个新的子进程用于处理请求了,如果还要启动一个HTTP服务器仅用于服务这一个请求,实在是太浪费了,所以用环境变量。整个这个过程,类似于序列化与反序列化的过程。
这两个文件并不是同时使用的。host.go
被用于CGI的宿主进程中。而由于现在的后端程序基本不再使用纯CGI的方式写,所以child.go
几乎不用,它的存在仅仅是为了兼容已存在的系统。
host.go 的使用
这个文件里面仅仅导出了一个类型Handler
,这个类型也仅仅只导出了一个函数ServeHTTP
。看名字就知道是干啥的了,实在是简单到无可挑剔。
Handler 类型
类型及重要字段:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
|
方法
仅一个方法:
1
|
|
实现了http.Handler
接口,用于处理HTTP请求(转换成CGI协议并调用CGI程序)。
示例使用
(放到单独的文章中列出)
SGITS: Simple GIT Server
前面那篇文章也提到过,现在的主流的反向代理服务器 nginx 并不支持 CGI(它支持另外一个类似的协议:FastCGI),所以,用想把git-http-backend
跑起来,需要我们自己写一个CGI宿主程序,用于将HTTP请求转换成CGI协议。
OK,来写一个简单GIT服务器吧!把她取名SGITS
:Simple GIT Server。
git-http-backend 要求的环境变量
它至少需要正确设定以下几个环境变量:
-
GIT_PROJECT_ROOT
用于GIT项目仓库的根目录。
-
GIT_HTTP_EXPORT_ALL
允许导出所有的项目。默认是全部不导出的,可以单独设定项目项目进行导出,但是那样太麻烦,所以直接导出全部了。
-
REMOTE_USER
用于在
git push
时的用户验证,即登录。
核心实现
以下是核心代码(然而,已经是个完全能跑起来的GIT服务器了!):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
|
配置文件的支持
为了方便设置参数,我加入了YAML格式的配置文件。如下:
1 2 3 4 5 6 7 8 9 10 11 |
|
保存名为:sgits.yml
,放到 sgits 的工作目录即可自动加载。
祼仓库的创建
GIT服务器要求的是项目的“祼仓库”,即没有工作区的,仅有.git
目录作为其根目录的仓库。
在如上设置的root
项目仓库根目录下执行如下命令可以创建一个祼仓库:
1
|
|
推送/拉取项目
SGITS 启动用,就是一个 GIT 服务器了。可以运行下面的命令来推送和拉取代码,和你向 GitHub 推送和拉取代码几乎完全一样。
1 2 3 |
|
GIT服务的安全性
上面的配置文件中的username
和password
分别设置该服务的唯一用户名和密码。
如果用户名和密码都为空
- 读不需要授权
- 写需要授权(由于没设置用户名和密码,所以没人能写)
如果用户名和密码都不为空:
- 读需要授权
- 写需要授权