概述
继startup之后,主函数进入无限循环,首先调用accept函数。
accept原型:
#include<sys/socket.h>
int accept(int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen);
返回:成功返回非负描述符,出错返回-1
如果accept成功,那么其返回值是由内核自动生成的一个全新描述符,代表与所返回客户端的TCP连接。
之后创建一个新线程通过accept_request处理请求。
accept_request()如下:
void *accept_request(void *client1)
{
int client = *(int *)client1;
char buf[1024]; //缓冲区
int numchars; //读取字符计数
char method[255]; //http请求method
char url[255];
char path[512];
size_t i, j;
struct stat st;
int cgi = 0; /* becomes true if server decides this is a CGI
* program */
char *query_string = NULL;
numchars = get_line(client, buf, sizeof(buf));
i = 0; j = 0;
while (!ISspace(buf[j]) && (i < sizeof(method) - 1))
{
method[i] = buf[j];
i++; j++;
}
method[i] = '\0';
if (strcasecmp(method, "GET") && strcasecmp(method, "POST"))
{
unimplemented(client);
return NULL;
}
if (strcasecmp(method, "POST") == 0)
cgi = 1;
i = 0;
while (ISspace(buf[j]) && (j < sizeof(buf)))
j++;
while (!ISspace(buf[j]) && (i < sizeof(url) - 1) && (j < sizeof(buf)))
{
url[i] = buf[j];
i++; j++;
}
url[i] = '\0';
if (strcasecmp(method, "GET") == 0)
{
query_string = url;
while ((*query_string != '?') && (*query_string != '\0'))
query_string++;
if (*query_string == '?')
{
cgi = 1;
*query_string = '\0';
query_string++;
}
}
sprintf(path, "htdocs%s", url);
if (path[strlen(path) - 1] == '/')
strcat(path, "index.html");
if (stat(path, &st) == -1) {
while ((numchars > 0) && strcmp("\n", buf)) /* read & discard headers */
numchars = get_line(client, buf, sizeof(buf));
not_found(client);
}
else
{
if ((st.st_mode & S_IFMT) == S_IFDIR)
strcat(path, "/index.html");
if ((st.st_mode & S_IXUSR) ||
(st.st_mode & S_IXGRP) ||
(st.st_mode & S_IXOTH) )
cgi = 1;
if (!cgi)
serve_file(client, path);
else
execute_cgi(client, path, method, query_string);
}
close(client);
return NULL;
}
http请求报文:
http请求报文由请求行、消息报头和请求正文组成。
Request Line<CRLF> //请求行
Header-Name: header-value<CRLF> //消息报头,一个或者多个
Header-Name: header-value<CRLF>
...
<CRLF> //空行
body//请求正文
- 请求行:
Method Request_URI HTTP-Version
以方法符为开头,空格分开,后面跟着请求URI和协议版本。其中请求方法有GET、POST、HEAD、PUT、DELETE、TRACE、CONNECT和OPTIONS等。
GET:请求获取Request-URI所标识资源
GET /form.html HTTP/1.1
POST: 请求服务器接受附在后面的数据。
POST /reg.jsp HTTP/ - 消息报头:
由关键字/值对组成,每行一对
User-Agent : 产生请求的浏览器类型
Accept : 客户端希望接受的数据类型,比如 Accept:text/xml(application/json)表示希望接受到的是xml(json)类型
Content-Type:发送端发送的实体数据的数据类型。
比如,Content-Type:text/html(application/json)表示发送的是html类型。
Host : 请求的主机名,允许多个域名同处一个IP地址,即虚拟主机
accept_request() 具体流程
- 使用geiline读取http头第一行并将method存入method数组。
int get_line(int sock, char *buf, int size)
{
int i = 0;
char c = '\0';
int n;
while ((i < size - 1) && (c != '\n'))
{
n = recv(sock, &c, 1, 0);
/* DEBUG printf("%02X\n", c); */
if (n > 0)
{
if (c == '\r')
{
n = recv(sock, &c, 1, MSG_PEEK);
/* DEBUG printf("%02X\n", c); */
if ((n > 0) && (c == '\n'))
recv(sock, &c, 1, 0);
else
c = '\n';
}
buf[i] = c;
i++;
}
else
c = '\n';
}
buf[i] = '\0';
return(i);
}
- 判断请求方法是否为GET或者POST,如果都不是调用unimplemented告知不支持该请求方法;
- 如果请求为PSOT,则cgi置1;
- 跳过空格,提取URL链接。
- 如果是GET请求,在GET请求中,请求参数和对应的值附加在URL后面,利用一个问号(“?”)代表URL的结尾与请求参数的开始,传递参数长度受限制。如index.jsp?10023,其中10023就是要传递 的参数。
- 如果找到了该请求,开启cgi,并保存URL,之后将其与请求地址的主页索引合并,形成path。
- stat是获取文件信息函数,失败返回-1,成功返回0。如果不存在,处理并丢弃剩下的请求头并返回错误信息。
- 如果文件存在但却是个目录,则继续拼接路径,默认访问这个目录下的index.html。
- 如果有执行权限,cgi置1,调用execut_cgi,否则调用serve_file。
10.断开连接。