http服务器实现(三)

前言

本文基于http服务器实现(二)来完成一个能处理http静态页面和动态页面请求的web服务器。
涉及到的内容有:

  1. CGI 理解
  2. 程序中注意的坑提醒
  3. 源码中对get请求的分析
  4. 用火狐浏览器测试
  5. 未解决的问题
  6. 服务器程序全部源码

http服务器实现(一)讲的是http服务器软件整体的架构,http服务器实现(二)主要讲的是http报文解析部分,这一节紧随前面的步骤实现一个小型的http服务器。服务器目前支持get请求,可以获取静态页面和动态(cgi) 页面。稍后,将结合火狐浏览器来演示成果。在后面的文章中,会相继完善http服务器的功能。例如,支持处理客户端的post请求,把程序做成守护进程,增加一些信号的处理等等。看完本节之后,你会发现开发一个web服务器的基本功能是如此的简单!但是,开发一个长期运行而不奔溃的健壮的web服务器是一件很困难的事情,需要方方面面考虑到,像高并发处理、效率优化、信号捕捉、安全性能、HTTPS 协议支持、各种后台语言的支持等等。刚调试好了程序,现在把成果分享出来。
快速理解本文的方法是:把本节代码下载下来,然后自己动手实验,通过log打印、实验结果去理解。代码能够编译得过且能运行!

一、CGI(通用网关接口)

CGI 是Web 服务器运行时外部程序的规范,按CGI 编写的程序可以扩展服务器功能。CGI 应用程序能与浏览器进行交互,还可通过数据库API 与数据库服务器等外部数据源进行通信,从数据库服务器中获取数据。可以简单理解是CGI是可执行程序,例如二进制可执行文件,shell脚本等。CGI经常向标准输出,也就是终端打印出HTML数据。在服务器程序中,我们只需要用dup2函数把标准输出重定向到socket描述符,把CGI输出的数据发送给浏览器,然后浏览器将显示这些数据。来看看一些实例。
如下,是一个C语言写的程序,我们看到str字符指针上存放一串字符串,然后向终端打印出来。

#include <stdio.h>

int main()
{
    printf("Content-Type: text/html\n\n");
    const char* str = "<html>"
    "<head><title>C CGI</title></head>\r\n"
    "<body><p>this is a C</p></body>"
    "</html>";    
    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 CGI</title></head>
<body><p>this is a C</p></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函数

所谓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是不会成功的。

mmap函数

在程序中,处理get静态请求的时候,用到了mmap函数。它的作用是把文件的内容映射到内存空间中,我们可以操作这段内存来读写数据。那为什么要这么做呢?常规文件操作需要从磁盘到页缓存再到用户主存的两次数据拷贝。而mmap操控文件,只需要从磁盘到用户主存的一次数据拷贝过程。因此mmap效率更高。

dup2函数

这里简单理解为重定向函数,可以把一个文件描述符重定向到另一个文件描述符。这也是实现CGI机制的关键。

Firefox设置

本文我用到了火狐浏览器实验。在访问服务器的时候,火狐弹出了警告:
此网址使用了一个通常用于网络浏览以外目的的端口。出于安全原因,Firefox 取消了该请求
用如下方法或者使用ie浏览器可以规避。
解决方法如下:
在Firefox地址栏输入about:config,然后在右键新建一个字符串键network.security.ports.banned.override,将需访问的端口号添加上,这里我添加了端口号6000。

三、服务器get请求源码分析

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.htmlhttp://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

这里写图片描述
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 <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <netinet/in.h>
#include <errno.h>

#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>

#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
    }
}

猜你喜欢

转载自blog.csdn.net/u014530704/article/details/78964683