项目:HTTP 服务器框架
步骤:
一. 背景调研(当前不涉及)
二. 需求分析(最核心的一步)
(有产品经理作出完整的文档)
- 实现一个服务器程序
- 支持HTTP协议的服务器
- 从请求角度上,支持GET方法和POST方法
从响应的角度上,支持静态页面和动态页面
静态页面:返回一个服务器上的本地文件 动态页面:根据用户的输入实时计算生成一个响应结果页面
- 能够使用浏览器访问
三. 概要设计(分成几个模块,模块之间的关系)
HTTP服务器框架:
通用的HTTP服务器框架(业界知名的服务器框架有:Nginx 、 httpd(apache))
1). 服务器初始化(socket 的初始化)
2). 处理HTTP请求- 从 socket 中读取数据,并且按照HTTP协议的格式解析数据;
- 根据输入的请求的不同决定是按照静态页面处理还是动态页面处理;
- 如果是按照静态页面,直接将服务器上对应的文件返回到客户端中;
- 如果时按照动态页面,就将输入的信息交给对应的 CGI 程序,由 CGI 程序来计算,生成响应。把响应的结果交还给HTTP服务器框架,由框架完成数据返回到客户端的过程。
业务相关的 CGI 程序
1). 根据不同的业务,来分别实现不同的 CGI 程序;
2). 读取 HTTP 服务器框架交给它的参数,根据自身的业务流程计算生成 HTTP 响应数据,交还给框架;
CGI:是一种标准或协议
CGI 优点:
- 完成了解耦,让通用的HTTP 服务器框架和业务完全的解耦和
- CGI 程序可以用任何的编程语言来实现
CGI 的实现过程:
HTTP 服务器框架创建子进程,子进程根据所请求得 CGI 程序的路径(HTTP 请求中的url_path)进行程序替换,CGI 程序就可以根据业务进行计算了。
1. HTTP 服务器是通过什么样的方式把必要的输入参数传给 CGI 程序:进程间通信中的管道 + 环境变量
2. CGI 程序计算的结果通过通过管道写回给客户端
四. 详细设计(模块的实现细节)
五. 具体的编码开发
1 #include <stdio.h>
2 #include <string.h>
3 #include <stdlib.h>
4 #include <unistd.h>
5 #include <pthread.h>
6 #include <sys/socket.h>
7 #include <netinet/in.h>
8 #include <arpa/inet.h>
9
10 typedef struct sockaddr sockaddr;
11 typedef struct sockaddr_in sockaddr_in;
12
13 #define SIZE (1024*10) //以为宏只是进行简单的文本替换,所以需要加上括号,来避免出现错误
14 typedef struct HttpRequest
15 {
16 char first_line[SIZE]; //C89不行,但C99可以这样写
17 char* method;
18 char* url;
19 char* url_path;
20 char* query_string;
21 int content_length;
22 }HttpRequest;
23
24 int ReadLine(int sock, char buf[], ssize_t max_size)//ssize_t 无符号长整型
25 {
26 // 按行从 socket 中读取数据
27 // 实际上浏览器发送的请求中换行符可能不一样。
28 // 换行符可能有:\n, \r, \r\n
29 // 1.循环从 socket 中读取字符,一次读一个。
30 char c = '\0';
31 ssize_t i = 0; //描述当前读到的字符应该放到缓冲区的哪个下标上
32 while(i < max_size -1)
33 {
34 ssize_t read_size = recv(sock, &c, 1, 0);
35 if(read_size <= 0)
36 {
37 // 此时认为读取数据失败。即使是recv返回0,由于此时我们
38 // 预期是至少能读到换行标记的。此处很可能是因为收到的
39 // 报文是非法的。
40 return -1;
41 }
42
43 // 对读到的字符进行判定。
44 // 2.如果当前字符是 \r
45 if(c == '\r')
46 {
47 // a) 尝试从缓冲区读取下一个字符,判定下一个字符是 \n,
48 // 就把这种情况处理成 \n
49 recv(sock, &c, 1, MSG_PEEK);
50 if(c == '\n')
51 {
52 // 当前的行切割符是一个 \r\n
53 // 接下来就把下一个 \n 字符从缓冲区中删掉就可以了
54 recv(sock, &c, 1, 0);
55 }
56 else
57 {
58 // b) 如果下一个字符是其他字符,就把 \r 修改成 \n(把
59 // \r 和 \n 的情况统一在一起)
60 // 此时行分隔符是 \r,为了方便处理,就把 \r 和 \n
61 // 统一在一起。也就是把 c 中的 \r 改成 \n
62 c = '\n';
63 }
64 }