本文基于http服务器实现(二)来完成一个能处理http静态页面和动态页面请求的web服务器。
涉及到的内容有:
http服务器实现(一)讲的是http服务器软件整体的架构,http服务器实现(二)主要讲的是http报文解析部分,这一节紧随前面的步骤实现一个小型的http服务器。服务器目前支持get请求,可以获取静态页面和动态(cgi) 页面。稍后,将结合火狐浏览器来演示成果。在后面的文章中,会相继完善http服务器的功能。例如,支持处理客户端的post请求,把程序做成守护进程,增加一些信号的处理等等。看完本节之后,你会发现开发一个web服务器的基本功能是如此的简单!但是,开发一个长期运行而不奔溃的健壮的web服务器是一件很困难的事情,需要方方面面考虑到,像高并发处理、效率优化、信号捕捉、安全性能、HTTPS 协议支持、各种后台语言的支持等等。刚调试好了程序,现在把成果分享出来。
快速理解本文的方法是:把本节代码下载下来,然后自己动手实验,通过log打印、实验结果去理解。代码能够编译得过且能运行!
CGI 是Web 服务器运行时外部程序的规范,按CGI 编写的程序可以扩展服务器功能。CGI 应用程序能与浏览器进行交互,还可通过数据库API 与数据库服务器等外部数据源进行通信,从数据库服务器中获取数据。可以简单理解是CGI是可执行程序,例如二进制可执行文件,shell脚本等。CGI经常向标准输出,也就是终端打印出HTML数据。在服务器程序中,我们只需要用dup2函数把标准输出重定向到socket描述符,把CGI输出的数据发送给浏览器,然后浏览器将显示这些数据。来看看一些实例。
如下,是一个C语言写的程序,我们看到str字符指针上存放一串字符串,然后向终端打印出来。
#include
int main()
{
printf("Content-Type: text/html\n\n");
const char* str = ""
"C CGI \r\n"
"this is a C
"
"";
printf("%s",str);
return 0;
}
用gcc c_program.c -o c_program.cgi
编译成二进制文件。c_program.cgi就是一个CGI可执行程序。我们关注的重点是打印出来的数据。我们可以看出打印出来的是
Content-Type: text/html
<html><head><title>C CGItitle>head>
<body><p>this is a Cp>body>html>
我们要做的就是把这段打印传输给浏览器。很简单,只需要用dup2(fd, STDOUT_FILENO)
把标准输出重定向到sockfd。来看看浏览器接收到数据之后是怎样显示的,画面如下:
跟我们预料的一样,通过上面的实验,是否更加形象的明白CGI究竟是怎样的回事呢?
我们还可以用shell来编写,再看看下面的例子:
#!/bin/bash
echo "Content-type:text/html"
echo ""
echo "this is a shell program"
echo ""
echo "a text"
echo ""
通过上面两个例子,大致知道了CGI无非就是要往终端打印出浏览器要接收的数据,运用重定向的方法可以很方便的实现。我们还可以往CGI程序传入参数,然后CGI程序根据我们传进去的参数动态修改打印的数据。这些知识点在下面的程序中都将会遇到。
在这里讲下,我再调试程序的时候遇到的坑和需要注意到的点。
所谓bind,就是指绑定本地接受端口。 指定ip,是为了分辨多ip主机。
比如你的机器有两个ip
192.168.1.105
192.168.2.1
如果你server_sockaddr.sin_addr.s_addr = inet_addr("192.168.1.105");
然后监听6000端口
这时其他机器只有连接上192.168.1.105:6000
才能访问到服务器,连接 192.168.2.1
将会失败。
如果server_sockaddr.sin_addr.s_addr =htonl(INADDR_ANY);
的话,两个ip 的6000端口都能连接得上。
如果不知道本地ip地址,可以在终端用ifconfig命令打印出来,bind绑定的ip必须是本地有才可以,随便绑定一个ip是不会成功的。
在程序中,处理get静态请求的时候,用到了mmap函数。它的作用是把文件的内容映射到内存空间中,我们可以操作这段内存来读写数据。那为什么要这么做呢?常规文件操作需要从磁盘到页缓存再到用户主存的两次数据拷贝。而mmap操控文件,只需要从磁盘到用户主存的一次数据拷贝过程。因此mmap效率更高。
这里简单理解为重定向函数,可以把一个文件描述符重定向到另一个文件描述符。这也是实现CGI机制的关键。
本文我用到了火狐浏览器实验。在访问服务器的时候,火狐弹出了警告:
此网址使用了一个通常用于网络浏览以外目的的端口。出于安全原因,Firefox 取消了该请求
用如下方法或者使用ie浏览器可以规避。
解决方法如下:
在Firefox地址栏输入about:config,然后在右键新建一个字符串键network.security.ports.banned.override
,将需访问的端口号添加上,这里我添加了端口号6000。
以http服务器实现(二)的代码为基础,在header_parse函数下 status == BODY_READ 的地方,添加了process_header_end函数来针对http GET请求的处理。先来看下这部分代码:
int process_header_end()
{
int is_static;
struct stat sbuf;
char buf[MAXLINE], uri[MAXLINE], version[MAXLINE];
char filename[MAXLINE], cgiargs[MAXLINE];
//前面已经把方法解析出来放入method全局变量,这里判断是不是GET请求,注:当前版本只支持GET请求。
if (method != M_GET) {
perror("does not implement this method");
//此处发送501响应
return;
}
//parse_uri函数有两个功能,一是解析出请求的文件路径,二是判断该请求是静态的还是动态的。
is_static = parse_uri(request_uri, filename, cgiargs);
if (stat(filename, &sbuf) < 0) {
perror("couldn't find this file");
return;
}
if (is_static) { //对静态请求处理
if (!(S_ISREG(sbuf.st_mode)) || !(S_IRUSR & sbuf.st_mode)) {
perror("couldn't read the file");
return;
}
serve_static(fd, filename, sbuf.st_size);
}
else { //对动态请求处理
if (!(S_ISREG(sbuf.st_mode)) || !(S_IXUSR & sbuf.st_mode)) {
perror("couldn't run the CGI program");
return;
}
serve_dynamic(fd, filename, cgiargs);
}
return 0;
}
以上代码,利用之前对请求行解析出来的结果,判断是不是GET请求,如果是,继续解析URI,如果是静态GET,就走静态处理路线,如果是CGI请求,就走动态处理路线。接下来看看静态GET请求的代码:
void serve_static(int fd, char *filename, int filesize)
{
printf("this is serve_static\n");
int srcfd;
char *srcp, filetype[MAXLINE], buf[MAXLINE];
//发送响应头给客户端
get_filetype(filename, filetype);
sprintf(buf, "HTTP/1.0 200 OK\r\n");
sprintf(buf, "%sServer: Tiny Web Server\r\n", buf);
sprintf(buf, "%sContent-length: %d\r\n", buf, filesize);
sprintf(buf, "%sContent-type: %s\r\n\r\n", buf, filetype);
if (rio_writen(fd, buf, strlen(buf)) != strlen(buf))
linux_error("rio_writen");
//发送响应体给客户端
if((srcfd = open(filename, O_RDONLY, 0)) < 0)
linux_error("open");
//将文件内容映射到虚拟内存中,提高文件的读写效率
srcp = Mmap(0, filesize, PROT_READ, MAP_PRIVATE, srcfd, 0);
close(srcfd);
//将请求的内容发送给浏览器
if (rio_writen(fd, srcp, filesize) != filesize)
linux_error("rio_writen");
//解除映射
Munmap(srcp, filesize);
close(fd);
}
从上面可以看出,serve_static函数主要干的事情是打开浏览器请求的文件,然后把文件内容映射到虚拟内存中,然后再把虚拟内存中的数据(文件的内容)发送给浏览器。其中,rio_writen函数是对write函数的包装,详情见源码。动态内容请求处理函数如下:
void serve_dynamic(int fd, char *filename, char *cgiargs)
{
printf("this is serve_dynamic\n");
char buf[MAXLINE], *emptylist[] = { NULL };
// 发送响应头第一行
sprintf(buf, "HTTP/1.1 200 OK\r\n");
if (rio_writen(fd, buf, strlen(buf)) != strlen(buf))
linux_error("rio_writen");
//发送响应头选项
sprintf(buf, "Server: Tiny Web Server\r\n");
if (rio_writen(fd, buf, strlen(buf)) != strlen(buf))
linux_error("rio_writen");
printf("filename=%s\n",filename);
//fork出子进程用于发送请求的数据
int pid = fork();
if (pid == 0)// 子进程
{
//setenv("QUERY_STRING", cgiargs, 1);
if( dup2(fd, STDOUT_FILENO) < 0) //重定向标准输出到socket fd
linux_error("dup2");
if (execve(filename, emptylist, environ) < 0)//执行CGI可执行程序,新的程序将替代掉子进程
linux_error("Execve error");
}
else if (pid < 0) //fork错误
linux_error("fork");
else //父进程
{
if (wait(NULL) < 0) //父进程等待子进程执行完成
linux_error("wait");
}
}
前面已经讲过了CGI,通过对这些代码,CGI应该就能够更加清晰理解了。serve_dynamic函数先把响应头一部分发送给浏览器,然后fork出子进程。子进程调用dup2(fd, STDOUT_FILENO)
函数把标准输出重定向到socket描述符,然后调用execve函数执行我们编写好的CGI可执行程序。通过这两个函数,最后CGI向终端输出的数据最后就发送给了浏览器。父进程调用wait等待子进程退出。
首先,服务器程序同级目录创建home.html默认页面,当访问服务器时,如果不添加文件路径,将默认请求此页面的内容。并且创建cgi-bin目录,这里面将存放我们的cgi脚本,名字都可以在程序中指定,获取更加普遍的做法是写到配置文件中,然后程序去解析这个配置文件,如boa开源程序的boa.conf配置文件。创建好之后如下图:
ubuntu@ubuntu:~/project/web-server$ ls
cgi-bin home.html web-server3.c web-server3 namei.jpg
ubuntu@ubuntu:~/project/web-server/cgi-bin$ ls
c_program.c c_program.cgi helloworld.sh shell.cgi
打开火狐浏览器,输入网址http://192.168.1.105:6000/home.html
或http://192.168.1.105:6000
浏览器显示如下:
home.html文件内容:
<html>
<head>
<title>This is my first HTML!title>
heead>
<body>
<h1>This is heading!h1>
<p>Hello world!p>
body>
html>
利用BurpSuite抓包,结果如下:
这是浏览器的请求。注意:Cache-Control: max-age=0下面有一行空行。
GET / HTTP/1.1
Host: 192.168.1.105:6000
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:57.0) Gecko/20100101 Firefox/57.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Connection: keep-alive
Upgrade-Insecure-Requests: 1
Cache-Control: max-age=0
这是服务器的响应。注意:Content-type: text/html下面有一行空行。
HTTP/1.0 200 OK
Server: Tiny Web Server
Content-length: 171
Content-type: text/html
<html>
<head>
<title>This is my first HTML!title>
heead>
<body>
<h1>This is heading!h1>
<p>Hello world!p>
body>
html>
抓包结果对比home.html文件内容,发现浏览器显示的内容跟home.html是一样的。这是浏览器GET请求的静态页面。
接下来,我们再来演示请求静态图片:
输入网址http://192.168.1.105:6000/namei.jpg
输入网址http://192.168.1.105:6000/cgi-bin/shell.cgi
ubuntu@ubuntu:~/project/web-server/cgi-bin$ cat shell.cgi
#!/bin/bash
echo "Content-type:text/html"
echo ""
echo "this is a shell program"
echo ""
echo "a text"
echo ""
以上实验结果会使我们对get请求更加形象地理解。
在调试程序的过程中,发现动态获取cgi页面时,浏览器响应特别慢,而且抓包的时候,抓不到Content-type:text/html头选项,调试了很久,怀疑是服务器发送给浏览器的数据格式不太对,导致了浏览器在解析服务器传来的数据耗费了大量的时间。因为我在浏览器向服务器请求的过程、服务器处理浏览器请求的过程中并无延迟很多。所以,只可能是浏览器解析服务器的数据那一阶段导致的。具体原因,还得排查。不吝赐教!
源码有点长,在http服务器实现(二)的基础上,本节增加的代码大概有200行,参考了Tiny web的代码。为了方便拿去测试学习,就全部贴出来了,全部有600多行,有点长。
/*
* web-server, an http server
* web-server3.c
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define BUFFER_SIZE 4096
#define MAX_QUE_CONN_NM 5
#define PORT 6000
//#define MAXSOCKFD 10
#define FILE_NAME_MAX 512
#define SOCKADDR sockaddr_in
#define S_FAMILY sin_family
#define SERVER_AF AF_INET
#define MAX_HEADER_LENGTH 1024
/****************** METHODS *****************/
#define M_GET 1
#define M_HEAD 2
#define M_PUT 3
#define M_POST 4
#define M_DELETE 5
#define M_LINK 6
#define M_UNLINK 7
/************** REQUEST STATUS (req->status) ***************/
#define READ_HEADER 0
#define ONE_CR 1
#define ONE_LF 2
#define TWO_CR 3
#define BODY_READ 4
#define BODY_WRITE 5
#define WRITE 6
#define PIPE_READ 7
#define PIPE_WRITE 8
#define DONE 9
#define DEAD 10
int PARSE_HEAD_OPTION;//解析http头选项的标志位,为1表示可以解析了
fd_set block_read_fdset;
int max_fd;
#define BOA_FD_SET(fd, where) { FD_SET(fd, where); \
if (fd > max_fd) max_fd = fd; \
}
//request相关,以后可以包装成一个结构体
int method;//用于获取http请求行的方法,GET或HEAD或POST
char request_uri[MAX_HEADER_LENGTH + 1]; // 用于获取客户端请求的uri
char *http_version;//获取http版本,未分配内存,是静态变量,注意一下,可能会出错
int fd; /* socket */
extern char **environ; /* defined by libc */
void select_loop(int server_s);
int process_requests(int server_s);
int header_parse(char *buff, int len);
int process_logline(char *buff);
int process_option_line(char *buff);
char *to_upper(char *str);
#define MAXLINE 8192 /* max text line length */
int parse_uri(char *uri, char *filename, char *cgiargs);
void serve_dynamic(int fd, char *filename, char *cgiargs);
void serve_static(int fd, char *filename, int filesize);
int process_header_end();
void *Mmap(void *addr, size_t len, int prot, int flags, int fd, off_t offset);
void Munmap(void *start, size_t length);
ssize_t rio_writen(int fd, void *usrbuf, size_t n);
void get_filetype(char *filename, char *filetype);
void linux_error(char *msg);
int main(int argc,char* argv[])
{
int sockfd;
int sin_size = sizeof(struct sockaddr);
struct sockaddr_in server_sockaddr, client_sockaddr;
int i = 1;/* 使得重复使用本地地址与套接字进行绑定 */
/*建立socket连接*/
if ((sockfd = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP))== -1)
{
perror("socket");
exit(1);
}
printf("Socket id = %d\n",sockfd);
/*设置sockaddr_in 结构体中相关参数*/
server_sockaddr.sin_family = AF_INET;
server_sockaddr.sin_port = htons(PORT);
//server_sockaddr.sin_addr.s_addr = INADDR_ANY;
server_sockaddr.sin_addr.s_addr = inet_addr("192.168.1.105");
bzero(&(server_sockaddr.sin_zero), 8);
setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &i, sizeof(i));
/*绑定函数bind*/
if (bind(sockfd, (struct sockaddr *)&server_sockaddr, sizeof(struct sockaddr))== -1)
{
perror("bind");
exit(1);
}
printf("Bind success!\n");
/*调用listen函数*/
if (listen(sockfd, MAX_QUE_CONN_NM) == -1)
{
perror("listen");
exit(1);
}
printf("Listening....\n");
select_loop(sockfd);
//recv_mul_file(sockfd);
//close(sockfd);
return 0;
}
void select_loop(int server_s)
{
FD_ZERO(&block_read_fdset);
/* preset max_fd */
max_fd = server_s+1;
while (1) {
BOA_FD_SET(server_s, &block_read_fdset); /* server always set */
printf("before select\n");
//没有可读的文件描述符,就阻塞。
if (select(max_fd + 1, &block_read_fdset,NULL, NULL,NULL) == -1) {
/* what is the appropriate thing to do here on EBADF */
if (errno == EINTR)
continue; /* while(1) */
else if (errno != EBADF) {
perror("select");
}
}
printf("after select\n");
if (FD_ISSET(server_s, &block_read_fdset))
process_requests(server_s);
}
}
int process_requests(int server_s)
{
struct SOCKADDR remote_addr; /* address */
int remote_addrlen = sizeof (struct SOCKADDR);
size_t len;
char buff[BUFFER_SIZE];
bzero(buff,BUFFER_SIZE);
//remote_addr.S_FAMILY = 0xdead;
fd = accept(server_s, (struct sockaddr *) &remote_addr,
&remote_addrlen);
if (fd == -1) {
if (errno != EAGAIN && errno != EWOULDBLOCK)
/* abnormal error */
perror("accept");
return -1;
}
int bytes = read(fd, buff, BUFFER_SIZE);
if (bytes < 0) {
if (errno == EINTR)
bytes = 0;
else
return -1;
}
printf("recv %d bytes from client:%s\n",bytes,buff);
int ret = header_parse(buff,bytes);
return 0;
}
//写一个http解析函数
int header_parse(char *buff, int len)
{
char *check = buff;
int status = READ_HEADER;
char *line_end;//用于标记,改成指针
char *header_line;//记录http头选项每一行开始的位置
int parse_pos = 0;
while (check < (buff + len)) {
switch (status) {
case READ_HEADER:
if (*check == '\r') {
status = ONE_CR;
line_end = check;
} else if (*check == '\n') {
status = ONE_LF;
line_end = check;
}
break;
case ONE_CR:
if (*check == '\n')
status = ONE_LF;
else if (*check != '\r')
status = READ_HEADER;
break;
case ONE_LF:
/* if here, we've found the end (for sure) of a header */
if (*check == '\r') /* could be end o headers */
status = TWO_CR;
else if (*check == '\n')
status = BODY_READ;
else
status = READ_HEADER;
break;
case TWO_CR:
if (*check == '\n')
status = BODY_READ;
else if (*check != '\r')
status = READ_HEADER;
break;
default:
break;
}
parse_pos++; /* update parse position */
check++;
//解析到每一行末后进入
if (status == ONE_LF) {
*line_end = '\0';
/* terminate string that begins at req->header_line */
if (PARSE_HEAD_OPTION) {//解析http头选项,由key:value键值对组成
if (process_option_line(header_line) == -1) {
perror("process_option_line error");
return -1;
}
} else {//解析http头请求行
if (process_logline(buff) == -1){
perror("process_logline error");
return -1;
}
}
/* set header_line to point to beginning of new header */
header_line = check;//记录http头选项每一行开始的位置
} else if (status == BODY_READ){
PARSE_HEAD_OPTION = 0;//解析完请求头部之后置0,为下一个客户端做好准备。
printf("begin parse body!\n");
int retval = process_header_end();//开始处理
//close(fd);//处理完成后,关闭sockfd
return 0;
}
}
return 0;
}
int process_option_line(char *buff)
{
char *if_modified_since; /* If-Modified-Since */
char *content_type;
char *content_length;
char *keepalive;
char *header_referer;
char *header_user_agent;
char c, *value, *line = buff;
value = strchr(line, ':');
if (value == NULL)
return 0;//表示解析结束
*value++ = '\0'; /* overwrite the : */
to_upper(line); /* header types are case-insensitive */
while ((c = *value) && (c == ' ' || c == '\t'))
value++;
if (!memcmp(line, "IF_MODIFIED_SINCE", 18)){
if_modified_since = value;
printf("IF_MODIFIED_SINCE:%s\n",if_modified_since);
}
else if (!memcmp(line, "CONTENT_TYPE", 13)){
content_type = value;
printf("CONTENT_TYPE:%s\n",content_type);
}
else if (!memcmp(line, "CONTENT_LENGTH", 15)){
content_length = value;
printf("CONTENT_LENGTH:%s\n",content_length);
}
else if (!memcmp(line, "CONNECTION", 11) ) {
keepalive = value;
printf("CONNECTION:%s\n",keepalive);
}
else if (!memcmp(line, "REFERER", 8)) {
header_referer = value;
printf("REFERER:%s\n",header_referer);
}
else if (!memcmp(line, "USER_AGENT", 11)) {
header_user_agent = value;
printf("USER_AGENT:%s\n",header_user_agent);
}
return 0;
}
char *to_upper(char *str)
{
char *start = str;
while (*str) {
if (*str == '-')
*str = '_';
else
*str = toupper(*str);
str++;
}
return start;
}
int process_logline(char *buff)
{
static char *SIMPLE_HTTP_VERSION = "HTTP/0.9";
http_version = SIMPLE_HTTP_VERSION;
char *stop, *stop2;
char *logline = buff;
if (!memcmp(logline, "GET ", 4)){
method = M_GET;
printf("http method = GET\n");
}
else if (!memcmp(logline, "HEAD ", 5)){
/* head is just get w/no body */
method = M_HEAD;
printf("http method = HEAD\n");
}
else if (!memcmp(logline, "POST ", 5)){
method = M_POST;
printf("http method = POST\n");
}
else {
//log_error_time();
//fprintf(stderr, "malformed request: \"%s\"\n", req->logline);
//send_r_not_implemented(req);
perror("malformed request\n");
return -1;
}
PARSE_HEAD_OPTION = 1;//设置解析http头选项的标志位
/* Guaranteed to find ' ' since we matched a method above */
stop = logline + 3;
if (*stop != ' ')
++stop;
/* scan to start of non-whitespace */
while (*(++stop) == ' ');
stop2 = stop;
/* scan to end of non-whitespace */
while (*stop2 != '\0' && *stop2 != ' ')
++stop2;
if (stop2 - stop > MAX_HEADER_LENGTH) {
//log_error_time();
//fprintf(stderr, "URI too long %d: \"%s\"\n", MAX_HEADER_LENGTH,
// req->logline);
//send_r_bad_request(req);
perror("URI too long");
return -1;
}
memcpy(request_uri, stop, stop2 - stop);
request_uri[stop2 - stop] = '\0';
printf("request uri = %s\n",request_uri);
if (*stop2 == ' ') {
/* if found, we should get an HTTP/x.x */
unsigned int p1, p2;
/* scan to end of whitespace */
++stop2;
while (*stop2 == ' ' && *stop2 != '\0')
++stop2;
/* scan in HTTP/major.minor */
if (sscanf(stop2, "HTTP/%u.%u", &p1, &p2) == 2) {
/* HTTP/{0.9,1.0,1.1} */
if (p1 == 1 && (p2 == 0 || p2 == 1)) {
http_version = stop2;
printf("http version = %s\n",http_version);
} else if (p1 > 1 || (p1 != 0 && p2 > 1)) {
goto BAD_VERSION;
}
} else {
goto BAD_VERSION;
}
}
return 0;
BAD_VERSION:
//log_error_time();
//fprintf(stderr, "bogus HTTP version: \"%s\"\n", stop2);
//send_r_bad_request(req);
perror("bogus HTTP version");
return -1;
}
int process_header_end()
{
int is_static;
struct stat sbuf;
char buf[MAXLINE], uri[MAXLINE], version[MAXLINE];
char filename[MAXLINE], cgiargs[MAXLINE];
if (method != M_GET) {
perror("does not implement this method");
//此处发送501响应
return;
}
/* Parse URI from GET request */
is_static = parse_uri(request_uri, filename, cgiargs); //line:netp:doit:staticcheck
if (stat(filename, &sbuf) < 0) { //line:netp:doit:beginnotfound
perror("couldn't find this file");
return;
} //line:netp:doit:endnotfound
if (is_static) { /* Serve static content */
if (!(S_ISREG(sbuf.st_mode)) || !(S_IRUSR & sbuf.st_mode)) { //line:netp:doit:readable
perror("couldn't read the file");
return;
}
serve_static(fd, filename, sbuf.st_size); //line:netp:doit:servestatic
}
else { /* Serve dynamic content */
if (!(S_ISREG(sbuf.st_mode)) || !(S_IXUSR & sbuf.st_mode)) { //line:netp:doit:executable
perror("couldn't run the CGI program");
return;
}
serve_dynamic(fd, filename, cgiargs); //line:netp:doit:servedynamic
}
return 0;
}
int parse_uri(char *uri, char *filename, char *cgiargs)
{
char *ptr;
if (!strstr(uri, "cgi-bin")) { /* Static content */ //line:netp:parseuri:isstatic
strcpy(cgiargs, ""); //line:netp:parseuri:clearcgi
strcpy(filename, "."); //line:netp:parseuri:beginconvert1
strcat(filename, uri); //line:netp:parseuri:endconvert1
if (uri[strlen(uri)-1] == '/') //line:netp:parseuri:slashcheck
strcat(filename, "home.html"); //line:netp:parseuri:appenddefault
return 1;
}
else { /* Dynamic content */ //line:netp:parseuri:isdynamic
ptr = index(uri, '?'); //line:netp:parseuri:beginextract
if (ptr) {
strcpy(cgiargs, ptr+1);
*ptr = '\0';
}
else
strcpy(cgiargs, ""); //line:netp:parseuri:endextract
strcpy(filename, "."); //line:netp:parseuri:beginconvert2
strcat(filename, uri); //line:netp:parseuri:endconvert2
return 0;
}
}
void serve_static(int fd, char *filename, int filesize)
{
printf("this is serve_static\n");
int srcfd;
char *srcp, filetype[MAXLINE], buf[MAXLINE];
//Send response headers to client
get_filetype(filename, filetype); //line:netp:servestatic:getfiletype
sprintf(buf, "HTTP/1.0 200 OK\r\n"); //line:netp:servestatic:beginserve
sprintf(buf, "%sServer: Tiny Web Server\r\n", buf);
sprintf(buf, "%sContent-length: %d\r\n", buf, filesize);
sprintf(buf, "%sContent-type: %s\r\n\r\n", buf, filetype);
if (rio_writen(fd, buf, strlen(buf)) != strlen(buf)) //line:netp:servestatic:endserve
linux_error("rio_writen");
// Send response body to client
if((srcfd = open(filename, O_RDONLY, 0)) < 0) //line:netp:servestatic:open
linux_error("open");
srcp = Mmap(0, filesize, PROT_READ, MAP_PRIVATE, srcfd, 0);//line:netp:servestatic:mmap
close(srcfd); //line:netp:servestatic:close
if (rio_writen(fd, srcp, filesize) != filesize) //line:netp:servestatic:write
linux_error("rio_writen");
Munmap(srcp, filesize); //line:netp:servestatic:munmap
close(fd);
}
/*
* get_filetype - derive file type from file name
*/
void get_filetype(char *filename, char *filetype)
{
if (strstr(filename, ".html"))
strcpy(filetype, "text/html");
else if (strstr(filename, ".gif"))
strcpy(filetype, "image/gif");
else if (strstr(filename, ".jpg"))
strcpy(filetype, "image/jpeg");
else
strcpy(filetype, "text/plain");
}
ssize_t rio_writen(int fd, void *usrbuf, size_t n)
{
size_t nleft = n;
ssize_t nwritten;
char *bufp = usrbuf;
while (nleft > 0) {
if ((nwritten = write(fd, bufp, nleft)) <= 0) {
if (errno == EINTR) /* interrupted by sig handler return */
nwritten = 0; /* and call write() again */
else
return -1; /* errno set by write() */
}
nleft -= nwritten;
bufp += nwritten;
}
return n;
}
//内存映射
void *Mmap(void *addr, size_t len, int prot, int flags, int fd, off_t offset)
{
void *ptr;
if ((ptr = mmap(addr, len, prot, flags, fd, offset)) == ((void *) -1))
{
perror("mmap error");
exit(0);
}
return(ptr);
}
void Munmap(void *start, size_t length)
{
if (munmap(start, length) < 0)
{
perror("munmap error");
exit(0);
}
}
void linux_error(char *msg) /* linux style error */
{
perror(msg);
exit(0);
}
void serve_dynamic(int fd, char *filename, char *cgiargs)
{
printf("this is serve_dynamic\n");
char buf[MAXLINE], *emptylist[] = { NULL };
// Return first part of HTTP response
sprintf(buf, "HTTP/1.1 200 OK\r\n");
if (rio_writen(fd, buf, strlen(buf)) != strlen(buf))
linux_error("rio_writen");
sprintf(buf, "Server: Tiny Web Server\r\n");
if (rio_writen(fd, buf, strlen(buf)) != strlen(buf))
linux_error("rio_writen");
printf("filename=%s\n",filename);
int pid = fork();
if (pid == 0)// child
{
//setenv("QUERY_STRING", cgiargs, 1);
if( dup2(fd, STDOUT_FILENO) < 0) //重定向标准输出到socket fd
linux_error("dup2");
if (execve(filename, emptylist, environ) < 0)//有问题
linux_error("Execve error");
}
else if (pid < 0) //fork错误
linux_error("fork");
else //父进程
{
if (wait(NULL) < 0) // Parent waits for and reaps child //line:netp:servedynamic:wait
linux_error("wait"); //line:netp:servestatic:munmap
}
}