[我的小项目] SGITS: 一个仅依赖GIT本身的最简GIT服务器

陪她去流浪 桃子 2019年06月09日 编辑 阅读次数:3152

我在前一篇文章《公共网关接口(Common Gateway Interface, CGI)简介》中简要地介绍了什么是CGI接口以及它的用途。 今天的文章我将用实际的例子来讲解一个与CGI相关的小项目:利用本地安装的GIT来搭建一个极简的GIT服务器

本地安装的GIT

本地安装的GIT即是指我们平常要使用 GIT 时必须下载并安装的 GIT 程序。通过运行git即可知道你有没有安装。

其实很多人不知道,GIT 其实已经自带了一个支持 HTTP 协议的 GIT 服务端,它的名字叫git-http-backend。 它位于目录$(git --exec-path)目录下:

1
2
$ echo $(git --exec-path)
/usr/local/Cellar/git/2.21.0/libexec/git-core
点击这里查看目录文件列表
$ 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
$ git http-backend
fatal: No REQUEST_METHOD from server
Status: 500 Internal Server Error
Expires: Fri, 01 Jan 1980 00:00:00 GMT
Pragma: no-cache
Cache-Control: no-cache, max-age=0, must-revalidate

从整体输出可以很明显地看出来,它是一个 HTTP 响应输出。而从Status: 500 Internal Server Error这一状态格式来看,很明显是一个 CGI 程序。

注意到上面的文件列表中还有另外两个程序,分别是git-receive-packgit-upload-pack。它们分别被用来处理git pushgit fetch/pull

Go语言标准中的CGI包

Go语言的标准库中直接提供了对CGI程序的支持,这个包是:net/http/cgi

它的实现非常的简单,总共就两个文件:host.gochild.gohost.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
type Handler struct {
    // 用于指定CGI程序的路径
    // path to the CGI executable
    Path string

    // 用于指定URL路径前缀(会被去掉这个前缀再传递给CGI程序),默认是"/"
    // root URI prefix of handler or empty for "/"
    Root string

    // 用于指定工作目录
    // Dir specifies the CGI executable's working directory.
    // If Dir is empty, the base directory of Path is used.
    // If Path has no base directory, the current working
    // directory is used.
    Dir string

    // 自定义的环境变量
    Env        []string    // extra environment variables to set, if any, as "key=value"

    // 其它
}

方法

仅一个方法:

1
func (h *Handler) ServeHTTP(rw http.ResponseWriter, req *http.Request)

实现了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
package main

import (
	"net/http"
	"net/http/cgi"
)

func handler(w http.ResponseWriter, req *http.Request) {
	h := cgi.Handler{
		Path: "git-http-backend",
		Env: []string{
			"GIT_PROJECT_ROOT=仓库目录",
			"GIT_HTTP_EXPORT_ALL=", // 导出所有项目
			"REMOTE_USER=用户",
		},
	}
	h.ServeHTTP(w, req)
}

func main() {
	http.Handle("/", handler)
	http.ListenAndServe(":3558", nil)
}

配置文件的支持

为了方便设置参数,我加入了YAML格式的配置文件。如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# port to be listened
listen: :3558

# root directory of your projects
root: /home/tao/code

# username to push to remote
username: name

# password for username
password: pass

保存名为:sgits.yml,放到 sgits 的工作目录即可自动加载。

祼仓库的创建

GIT服务器要求的是项目的“祼仓库”,即没有工作区的,仅有.git目录作为其根目录的仓库。

在如上设置的root项目仓库根目录下执行如下命令可以创建一个祼仓库:

1
$ git init --bare repo-name

推送/拉取项目

SGITS 启动用,就是一个 GIT 服务器了。可以运行下面的命令来推送和拉取代码,和你向 GitHub 推送和拉取代码几乎完全一样。

1
2
3
$ git clone http://localhost:3558/repo-name

$ git push origin master

GIT服务的安全性

上面的配置文件中的usernamepassword分别设置该服务的唯一用户名和密码。

如果用户名和密码都为空

  • 读不需要授权
  • 写需要授权(由于没设置用户名和密码,所以没人能写)

如果用户名和密码都不为空:

  • 读需要授权
  • 写需要授权

项目地址

GitHub - movsb/SGITS

标签:git · 我的小项目 · CGI