4.2 accept_request函数细节
顺着上一篇的接着分析,再次贴出来accept_request
void accept_request(int client)
{
char buf[1024];
int numchars;
char method[255];
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));
//这里得到是请求行,格式如 :请求方法 URL 协议版本\r\n
//提取请求方法 GET或者POST Tinyhttp也只实现了这两个
i = 0; j = 0;
while (!ISspace(buf[j]) && (i < sizeof(method) - 1))
{
method[i] = buf[j];
i++; j++;
}
method[i] = '\0';
//判断是否方法为GET或者POST,如果不是直接调用unimplemented给client返回错误信息
if (strcasecmp(method, "GET") && strcasecmp(method, "POST"))
{
unimplemented(client);
return;
}
//如果是POST方法,直接置位cgi标志,这里需要再回顾POST和GET方法的区别
if (strcasecmp(method, "POST") == 0)
cgi = 1;
//下面的就是提取请求行的URL
i = 0;
//处理空格
while (ISspace(buf[j]) && (j < sizeof(buf)))
j++;
//获取URL
while (!ISspace(buf[j]) && (i < sizeof(url) - 1) && (j < sizeof(buf)))
{
url[i] = buf[j];
i++; j++;
}
url[i] = '\0';
//直接处理请求,首先判断是否请求方法为GET
if (strcasecmp(method, "GET") == 0)
{
query_string = url;
//提取url中?之前的url
while ((*query_string != '?') && (*query_string != '\0'))
query_string++;
if (*query_string == '?')
{
//在GET方法中置位cgi,并且将url中的?替换为'\0',而query_string指向第一个key=value的地址
cgi = 1;
*query_string = '\0';
query_string++;
}
}
//以打开主页为例,此时的url=/,此时path应为htdocs/
sprintf(path, "htdocs%s", url);
//在htdocs/后面添加index.html,特别注意上面的sprintf代码为hard code!
if (path[strlen(path) - 1] == '/')
strcat(path, "index.html");
//这里要注意进程的默认搜索目录为当前工作目录
if (stat(path, &st) == -1) {
//如果没有找到该资源,直接读取完整个请求头部,并向client发送相应的错误信息
while ((numchars > 0) && strcmp("\n", buf)) /* read & discard headers */
numchars = get_line(client, buf, sizeof(buf));
not_found(client);
}
else
{
//如果url请求的文件是个目录,则变成htdocs/xxx/index.html
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标志。
cgi = 1;
if (!cgi)
serve_file(client, path);
else
execute_cgi(client, path, method, query_string);
}
close(client);
}
稍微回顾下整体的逻辑:
- 最开始读取报文请求行,或者
METHOD
,如果该方法不被支持返回错误信息,断开连接 - 如果是
POST
方法,直接置位cgi
标志 - 无论是
POST
还是GET
,接下来都提取请求行的URL
- 如果是
POST
方法,且url
含传参,则置位cgi
标志- 这里的代码逻辑可以优化,将第二点和第四点合并下,可以清楚很多。
- 无论是
POST
或者GET
都在最后执行相应动作:- 重新定位资源位置
- 判断资源是否可执行,如果可执行则调用
execute_cgi
,否则调用serve_file
。
cgi是原作者实现动态生成网页的工具,并不属于HTTP协议处理的逻辑范畴。
4.2.1 serve_file函数细节
/**********************************************************************/
/* Send a regular file to the client. Use headers, and report
* errors to client if they occur.
* Parameters: a pointer to a file structure produced from the socket
* file descriptor
* the name of the file to serve */
/**********************************************************************/
void serve_file(int client, const char *filename)
{
FILE *resource = NULL;
int numchars = 1;
char buf[1024];
buf[0] = 'A'; buf[1] = '\0';
//舍弃请求头部
while ((numchars > 0) && strcmp("\n", buf)) /* read & discard headers */
numchars = get_line(client, buf, sizeof(buf));
//打开文件,读取资源
resource = fopen(filename, "r");
if (resource == NULL)
not_found(client);
else
{
headers(client, filename);
cat(client, resource);
}
fclose(resource);
}
直接看看server响应报文头部信息,有固定的格式:
/**********************************************************************/
/* Return the informational HTTP headers about a file. */
/* Parameters: the socket to print the headers on
* the name of the file */
/**********************************************************************/
void headers(int client, const char *filename)
{
char buf[1024];
(void)filename; /* could use filename to determine file type */
//状态行
strcpy(buf, "HTTP/1.0 200 OK\r\n");
send(client, buf, strlen(buf), 0);
//响应头部
strcpy(buf, SERVER_STRING);
send(client, buf, strlen(buf), 0);
sprintf(buf, "Content-Type: text/html\r\n");
send(client, buf, strlen(buf), 0);
//空行
strcpy(buf, "\r\n");
send(client, buf, strlen(buf), 0);
}
而发送为响应状态和头部信息之后,就要回传给server数据:
/**********************************************************************/
/* Put the entire contents of a file out on a socket. This function
* is named after the UNIX "cat" command, because it might have been
* easier just to do something like pipe, fork, and exec("cat").
* Parameters: the client socket descriptor
* FILE pointer for the file to cat */
/**********************************************************************/
void cat(int client, FILE *resource)
{
char buf[1024];
fgets(buf, sizeof(buf), resource);
while (!feof(resource))
{
send(client, buf, strlen(buf), 0);
fgets(buf, sizeof(buf), resource);
}
}
今天花了一个小时,就到这里,可以明显的知道,如果我们想静态的展示我们的自己的网页,只需要对源码进行稍微的修改,就可以实现该目的。