自定义Linux C HTTP服务器对接git-http-backend发送引用列表异常问题
核心问题拆解
你基于C语言开发的自定义HTTP服务器,在集成git-http-backend处理Git克隆请求时,出现两个关键异常:
- 首次请求后客户端未发起后续POST请求,服务器
read陷入无限循环 - 重启服务器后客户端报错
fatal: git fetch-pack: expected ACK/NAK, got a flush packet
问题根源分析
1. HTTP响应结构不规范
git-http-backend返回的内容包含无状态行的HTTP头和完整的Git协议数据,但你拼接响应时存在两个错误:
- 未在HTTP头与Git协议数据之间添加空行分隔符(
\r\n\r\n),Git客户端无法区分响应头和协议体,导致无法解析引用列表 - 手动追加
0000Flush Packet,破坏了Git协议的完整性(git-http-backend本身会生成合法的结束标识)
2. CGI输出读取不完整
你可能未循环读取git-http-backend的全部输出,导致丢失了原生的0000结束标识。CGI程序的输出会通过管道分块返回,单次read无法保证获取所有数据,遗漏的结束标识会让客户端认为数据未传输完成,不会发起后续POST请求。
3. 套接字数据发送不完整
使用write发送数据时未处理部分发送的情况,导致Git协议数据被截断。网络套接字可能因缓存限制无法一次性发送全部数据,若不循环调用write直到所有字节发送完成,客户端会收到不完整的引用列表,终止流程。
4. HTTP连接管理错误
Git over HTTP默认使用Keep-Alive连接,客户端会在同一连接上发送GET(获取引用)和POST(传输pack)请求。若你在发送完GET响应后错误关闭套接字,或未正确处理Connection: keep-alive头,会导致客户端无法发起后续请求,服务器陷入read阻塞。
修复步骤
步骤1:规范HTTP响应拼接
不要手动添加0000,按以下结构拼接响应:
HTTP/1.1 200 OK\r\n [git-http-backend返回的HTTP头]\r\n \r\n [git-http-backend返回的完整Git协议数据]
重点确保HTTP头与协议体之间的空行(\r\n\r\n)完整,这是HTTP协议的强制要求。
步骤2:完整读取CGI输出
循环读取git-http-backend的输出直到EOF,确保获取所有数据:
char cgi_buf[4096]; ssize_t read_len; while ((read_len = read(cgi_pipe_fd, cgi_buf, sizeof(cgi_buf))) > 0) { // 将读取到的数据追加到响应缓冲区 }
不要提前终止读取,否则会丢失Git协议的结束标识。
步骤3:保证套接字数据完整发送
封装循环发送逻辑,确保所有字节都被发送到客户端:
const char *resp_data = response_buffer; size_t resp_len = response_length; while (resp_len > 0) { ssize_t sent = write(client_sock, resp_data, resp_len); if (sent < 0) { // 处理发送错误 break; } resp_data += sent; resp_len -= sent; }
步骤4:正确处理Keep-Alive连接
- 解析
git-http-backend返回的Connection头,若值为keep-alive,则保持客户端套接字打开,等待后续POST请求 - 使用
select监听套接字时设置合理超时,避免无限阻塞 - 若
Connection头为close,则在发送完响应后关闭套接字
步骤5:验证协议数据格式
用nc命令模拟请求,检查服务器响应的合法性:
echo -e "GET /git/<project_dir>/info/refs?service=git-upload-pack HTTP/1.1\r\nHost: <ip>:8080\r\n\r\n" | nc <ip> 8080
确认Git协议部分以001e# service=git-upload-pack开头,且末尾存在0000结束标识。
内容的提问来源于stack exchange,提问作者Viktoriia Pashchenko

