15、HTTP协议解析与实现:HTTP请求/响应格式、HTTP状态码、简单的HTTP服务器、CGI程序
说实话,HTTP协议这东西,你天天都在用。浏览器打开网页、手机刷朋友圈、甚至你写的C程序调用API——背后都是HTTP在干活。但很多人写网络程序时,只是调个库就完事了,真让自己手写一个HTTP服务器,立马就懵了。
我个人觉得,搞网络编程的人,至少得亲手解析一次HTTP报文。为什么?因为只有你亲手拆过请求行、拼过响应头,你才能真正理解那些框架背后到底干了什么。今天咱们就把它彻底搞明白。
HTTP协议长什么样?
HTTP协议其实特别简单。它就是个文本协议,说白了就是一堆字符串按固定格式拼起来。你想想看,浏览器和服务器之间,就是通过这种纯文本的格式在交流。
HTTP请求格式
一个标准的HTTP请求,长这样:
GET /index.html HTTP/1.1
Host: www.example.com
User-Agent: Mozilla/5.0
Accept: text/html
注意看,它分三部分:
- 请求行:第一行,包含方法(GET)、路径(/index.html)、协议版本(HTTP/1.1)
- 请求头:从第二行开始,到空行结束。每行一个键值对,用冒号分隔
- 请求体:空行之后的内容。GET请求一般没有,POST请求才有
HTTP响应格式
服务器返回的响应,格式也差不多:
HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 128
<html>
<body><h1>Hello World</h1></body>
</html>
响应也分三部分:
- 状态行:协议版本 + 状态码 + 状态描述
- 响应头:和请求头格式一样
- 响应体:实际返回的数据
HTTP状态码,你得记住这几个
状态码是服务器告诉客户端「这次请求结果如何」的数字。我整理了一张表,把最常用的列出来:
| 状态码 | 含义 | 说明 |
|---|---|---|
| 200 | OK | 请求成功,正常返回数据 |
| 301 | Moved Permanently | 资源永久重定向 |
| 302 | Found | 临时重定向 |
| 400 | Bad Request | 请求格式错误,服务器看不懂 |
| 404 | Not Found | 资源不存在,最常见 |
| 500 | Internal Server Error | 服务器内部出错 |
| 502 | Bad Gateway | 网关或代理服务器收到无效响应 |
手写一个简单的HTTP服务器
好了,理论说完了,咱们直接上代码。下面这个C程序,就是一个最简单的HTTP服务器。它监听8080端口,收到请求后返回一个静态页面。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#define PORT 8080
#define BUFFER_SIZE 4096
void handle_client(int client_fd) {
char buffer[BUFFER_SIZE];
char method[16], path[256], protocol[16];
// 读取请求
read(client_fd, buffer, sizeof(buffer) - 1);
sscanf(buffer, "%s %s %s", method, path, protocol);
printf("收到请求: %s %s\n", method, path);
// 构造响应
char response[] =
"HTTP/1.1 200 OK\r\n"
"Content-Type: text/html\r\n"
"Content-Length: 68\r\n"
"Connection: close\r\n"
"\r\n"
"<html><body><h1>Hello from C Server!</h1></body></html>";
write(client_fd, response, strlen(response));
close(client_fd);
}
int main() {
int server_fd, client_fd;
struct sockaddr_in address;
int addrlen = sizeof(address);
// 创建socket
server_fd = socket(AF_INET, SOCK_STREAM, 0);
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(PORT);
// 绑定端口
bind(server_fd, (struct sockaddr *)&address, sizeof(address));
listen(server_fd, 3);
printf("服务器启动,监听端口 %d...\n", PORT);
while (1) {
client_fd = accept(server_fd, (struct sockaddr *)&address,
(socklen_t*)&addrlen);
handle_client(client_fd);
}
close(server_fd);
return 0;
}
这段代码虽然简单,但麻雀虽小五脏俱全。它展示了HTTP服务器的核心流程:
- 创建socket,绑定端口,开始监听
- 接受客户端连接
- 读取HTTP请求,解析请求行
- 构造HTTP响应,写回客户端
- 关闭连接
CGI程序:让服务器动态起来
上面那个服务器只能返回静态页面。但实际应用中,我们需要根据用户请求动态生成内容。这时候就要用到CGI(Common Gateway Interface,通用网关接口)。
CGI的原理其实很简单:服务器收到请求后,把请求数据通过环境变量传给一个外部程序,然后读取这个程序的输出,作为响应返回给客户端。
下面是一个简单的CGI程序,用C语言写的。它读取环境变量QUERY_STRING,然后生成一个动态页面:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main() {
char *query = getenv("QUERY_STRING");
char name[256] = "World";
if (query != NULL && strncmp(query, "name=", 5) == 0) {
strncpy(name, query + 5, sizeof(name) - 1);
}
printf("Content-Type: text/html\r\n\r\n");
printf("<html><body>");
printf("<h1>Hello, %s!</h1>", name);
printf("<p>这是CGI程序生成的动态页面</p>");
printf("</body></html>");
return 0;
}
编译后放到服务器的cgi-bin目录下,访问 http://localhost:8080/cgi-bin/hello.cgi?name=Alice,就能看到动态生成的页面了。
HTTP协议解析的核心流程
最后,我用一张图来总结HTTP协议解析的核心流程。这张图展示了一个HTTP请求从接收到响应的完整生命周期:
这张图把整个流程串起来了。你仔细看,从接收连接到关闭连接,每一步都有明确的函数调用。写HTTP服务器,本质上就是把这个流程用代码实现一遍。
公众号:蓝海资料掘金营,微信deep3321